Skip to content

Commit

Permalink
84 - added a new option "dropTargetClasses" that is like dropTargetSt…
Browse files Browse the repository at this point in the history
…yles just with global classes
  • Loading branch information
isaacHagoel committed Jan 13, 2021
1 parent 27cfb28 commit cb3234c
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 18 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ An options-object with the following attributes:
| `dragDisabled` | Boolean | No | `false` | Setting it to true will make it impossible to drag elements out of the dnd-zone. You can change it at any time, and the zone will adjust on the fly |
| `dropFromOthersDisabled` | Boolean | No | `false` | Setting it to true will make it impossible to drop elements from other dnd-zones of the same type. Can be useful if you want to limit the max number of items for example. You can change it at any time, and the zone will adjust on the fly |
| `dropTargetStyle` | Object<String> | No | `{outline: 'rgba(255, 255, 102, 0.7) solid 2px'}` | An object of styles to apply to the dnd-zone when items can be dragged in to it. Note: the styles override any inline styles applied to the dnd-zone. When the styles are removed, any original inline styles will be lost |
| `dropTargetClasses`| Array<String> | No | `[]` | A list of classes to apply to the dnd-zone when items can be dragged in to it. Note: make sure the classes you use are global. |
| `transformDraggedElement` | Function | No | `() => {}` | A function that is invoked when the draggable element enters the dnd-zone or hover overs a new index in the current dnd-zone. <br />Signature:<br />function(element, data, index) {}<br />**element**: The dragged element. <br />**data**: The data of the item from the items array.<br />**index**: The index the dragged element will become in the new dnd-zone.<br /><br />This allows you to override properties on the dragged element, such as innerHTML to change how it displays. |
| `autoAriaDisabled` | Boolean | No | `false` | Setting it to true will disable all the automatically added aria attributes and aria alerts (for example when the user starts/ stops dragging using the keyboard).<br /> **Use it only if you intend to implement your own custom instructions, roles and alerts.** In such a case, you might find the exported function `alertToScreenReader(string)` useful. |

Expand Down
19 changes: 18 additions & 1 deletion cypress/integration/util.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {areObjectsShallowEqual, getDepth} from "../../src/helpers/util";
import {areArraysShallowEqualSameOrder, areObjectsShallowEqual, getDepth} from "../../src/helpers/util";
import {printDebug, setDebugMode} from "../../src/constants";

describe("util", () => {
Expand Down Expand Up @@ -55,4 +55,21 @@ describe("util", () => {
expect(consoleStub).to.equal(null);
});
});
describe("areArraysShallowEqual", () => {
it("return true when equal same order", () => {
expect(areArraysShallowEqualSameOrder([1, "hello", null], [1, "hello", null]));
expect(areArraysShallowEqualSameOrder([], []));
});
it("return false when equal but different order", () => {
expect(areArraysShallowEqualSameOrder([1, "hello", null], ["hello", 1, null]));
});
it("return false when different size", () => {
expect(areArraysShallowEqualSameOrder(["hello"], ["hello", 1, null]));
expect(areArraysShallowEqualSameOrder(["hello", 1, null], ["hello"]));
});
it("return false when different", () => {
expect(areArraysShallowEqualSameOrder([1, "hello", null], ["1", "hello", null]));
expect(areArraysShallowEqualSameOrder([1, 2], []));
});
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"dist"
],
"description": "*An awesome drag and drop library for Svelte 3 (not using the browser's built-in dnd, thanks god): Rich animations, nested containers, touch support and more *",
"version": "0.7.4",
"version": "0.8.0",
"repository": {
"type": "git",
"url": "git+https://github.com/isaacHagoel/svelte-dnd-action.git"
Expand Down
13 changes: 9 additions & 4 deletions src/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {toString} from "./helpers/util";
* Dispatches two events that the container is expected to react to by modifying its list of items,
* which will then feed back in to this action via the update function
*
* @typedef {Object} Options
* @property {Array} items - the list of items that was used to generate the children of the given node (the list used in the #each block
* @typedef {object} Options
* @property {array} items - the list of items that was used to generate the children of the given node (the list used in the #each block
* @property {string} [type] - the type of the dnd zone. children dragged from here can only be dropped in other zones of the same type, default to a base type
* @property {number} [flipDurationMs] - if the list animated using flip (recommended), specifies the flip duration such that everything syncs with it without conflict, defaults to zero
* @property {boolean} [dragDisabled]
* @property {boolean} [dropFromOthersDisabled]
* @property {Object} [dropTargetStyle]
* @property {Function} [transformDraggedElement]
* @property {object} [dropTargetStyle]
* @property {string[]} [dropTargetClasses]
* @property {function} [transformDraggedElement]
* @param {HTMLElement} node - the element to enhance
* @param {Options} options
* @return {{update: function, destroy: function}}
Expand Down Expand Up @@ -47,6 +48,7 @@ function validateOptions(options) {
dragDisabled,
dropFromOthersDisabled,
dropTargetStyle,
dropTargetClasses,
transformDraggedElement,
autoAriaDisabled,
...rest
Expand All @@ -62,4 +64,7 @@ function validateOptions(options) {
if (itemWithMissingId) {
throw new Error(`missing '${ITEM_ID_KEY}' property for item ${toString(itemWithMissingId)}`);
}
if (dropTargetClasses && !Array.isArray(dropTargetClasses)) {
throw new Error(`dropTargetClasses should be an array but instead it is a ${typeof dropTargetClasses}, ${toString(dropTargetClasses)}`);
}
}
8 changes: 6 additions & 2 deletions src/helpers/styler.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,27 +150,31 @@ export function unDecorateShadowElement(shadowEl) {
* will mark the given dropzones as visually active
* @param {Array<HTMLElement>} dropZones
* @param {Function} getStyles - maps a dropzone to a styles object (so the styles can be removed)
* @param {Function} getClasses - maps a dropzone to a classList
*/
export function styleActiveDropZones(dropZones, getStyles = () => {}) {
export function styleActiveDropZones(dropZones, getStyles = () => {}, getClasses = () => []) {
dropZones.forEach(dz => {
const styles = getStyles(dz);
Object.keys(styles).forEach(style => {
dz.style[style] = styles[style];
});
getClasses(dz).forEach(c => dz.classList.add(c));
});
}

/**
* will remove the 'active' styling from given dropzones
* @param {Array<HTMLElement>} dropZones
* @param {Function} getStyles - maps a dropzone to a styles object
* @param {Function} getClasses - maps a dropzone to a classList
*/
export function styleInactiveDropZones(dropZones, getStyles = () => {}) {
export function styleInactiveDropZones(dropZones, getStyles = () => {}, getClasses = () => []) {
dropZones.forEach(dz => {
const styles = getStyles(dz);
Object.keys(styles).forEach(style => {
dz.style[style] = "";
});
getClasses(dz).forEach(c => dz.classList.contains(c) && dz.classList.remove(c));
});
}

Expand Down
18 changes: 18 additions & 0 deletions src/helpers/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,21 @@ export function areObjectsShallowEqual(objA, objB) {
}
return true;
}

/**
* Shallow compares two arrays
* @param arrA
* @param arrB
* @return {boolean} - whether the arrays are shallow equal
*/
export function areArraysShallowEqualSameOrder(arrA, arrB) {
if (arrA.length !== arrB.length) {
return false;
}
for (let i = 0; i < arrA.length; i++) {
if (arrA[i] !== arrB[i]) {
return false;
}
}
return true;
}
12 changes: 10 additions & 2 deletions src/keyboardAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,11 @@ function handleDrop(dispatchConsider = true) {
source: SOURCES.KEYBOARD
});
}
styleInactiveDropZones(typeToDropZones.get(draggedItemType), dz => dzToConfig.get(dz).dropTargetStyle);
styleInactiveDropZones(
typeToDropZones.get(draggedItemType),
dz => dzToConfig.get(dz).dropTargetStyle,
dz => dzToConfig.get(dz).dropTargetClasses
);
focusedItem = null;
focusedItemId = null;
focusedItemLabel = "";
Expand Down Expand Up @@ -224,7 +228,11 @@ export function dndzone(node, options) {
draggedItemType = config.type;
isDragging = true;
const dropTargets = Array.from(typeToDropZones.get(config.type)).filter(dz => dz === focusedDz || !dzToConfig.get(dz).dropFromOthersDisabled);
styleActiveDropZones(dropTargets, dz => dzToConfig.get(dz).dropTargetStyle);
styleActiveDropZones(
dropTargets,
dz => dzToConfig.get(dz).dropTargetStyle,
dz => dzToConfig.get(dz).dropTargetClasses
);
if (!config.autoAriaDisabled) {
let msg = `Started dragging item ${focusedItemLabel}. Use the arrow keys to move it within its list ${focusedDzLabel}`;
if (dropTargets.length > 1) {
Expand Down
45 changes: 37 additions & 8 deletions src/pointerAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
DRAGGED_LEFT_TYPES,
DRAGGED_OVER_INDEX_EVENT_NAME
} from "./helpers/dispatcher";
import {areObjectsShallowEqual, toString} from "./helpers/util";
import {areArraysShallowEqualSameOrder, areObjectsShallowEqual, toString} from "./helpers/util";
import {getBoundingRectNoTransforms} from "./helpers/intersection";

const DEFAULT_DROP_ZONE_TYPE = "--any--";
Expand Down Expand Up @@ -229,7 +229,11 @@ function handleDrop() {
}
printDebug(() => ["dropped in dz", shadowElDropZone]);
let {items, type} = dzToConfig.get(shadowElDropZone);
styleInactiveDropZones(typeToDropZones.get(type), dz => dzToConfig.get(dz).dropTargetStyle);
styleInactiveDropZones(
typeToDropZones.get(type),
dz => dzToConfig.get(dz).dropTargetStyle,
dz => dzToConfig.get(dz).dropTargetClasses
);
let shadowElIdx = findShadowElementIdx(items);
// the handler might remove the shadow element, ex: dragula like copy on drag
if (shadowElIdx === -1) shadowElIdx = originIndex;
Expand Down Expand Up @@ -297,6 +301,7 @@ export function dndzone(node, options) {
dragDisabled: false,
dropFromOthersDisabled: false,
dropTargetStyle: DEFAULT_DROP_TARGET_STYLE,
dropTargetClasses: [],
transformDraggedElement: () => {}
};
printDebug(() => [`dndzone good to go options: ${toString(options)}, config: ${toString(config)}`, {node}]);
Expand Down Expand Up @@ -390,7 +395,8 @@ export function dndzone(node, options) {

styleActiveDropZones(
Array.from(typeToDropZones.get(config.type)).filter(dz => dz === originDropZone || !dzToConfig.get(dz).dropFromOthersDisabled),
dz => dzToConfig.get(dz).dropTargetStyle
dz => dzToConfig.get(dz).dropTargetStyle,
dz => dzToConfig.get(dz).dropTargetClasses
);

// removing the original element by removing its data entry
Expand All @@ -413,6 +419,7 @@ export function dndzone(node, options) {
dragDisabled = false,
dropFromOthersDisabled = false,
dropTargetStyle = DEFAULT_DROP_TARGET_STYLE,
dropTargetClasses = [],
transformDraggedElement = () => {}
}) {
config.dropAnimationDurationMs = dropAnimationDurationMs;
Expand All @@ -427,18 +434,40 @@ export function dndzone(node, options) {
config.transformDraggedElement = transformDraggedElement;

// realtime update for dropTargetStyle
if (isWorkingOnPreviousDrag && !finalizingPreviousDrag && !areObjectsShallowEqual(dropTargetStyle, config.dropTargetStyle)) {
styleInactiveDropZones([node], () => config.dropTargetStyle);
styleActiveDropZones([node], () => dropTargetStyle);
if (
isWorkingOnPreviousDrag &&
!finalizingPreviousDrag &&
(!areObjectsShallowEqual(dropTargetStyle, config.dropTargetStyle) ||
!areArraysShallowEqualSameOrder(dropTargetClasses, config.dropTargetClasses))
) {
styleInactiveDropZones(
[node],
() => config.dropTargetStyle,
() => dropTargetClasses
);
styleActiveDropZones(
[node],
() => dropTargetStyle,
() => dropTargetClasses
);
}
config.dropTargetStyle = dropTargetStyle;
config.dropTargetClasses = [...dropTargetClasses];

// realtime update for dropFromOthersDisabled
if (isWorkingOnPreviousDrag && config.dropFromOthersDisabled !== dropFromOthersDisabled) {
if (dropFromOthersDisabled) {
styleInactiveDropZones([node], dz => dzToConfig.get(dz).dropTargetStyle);
styleInactiveDropZones(
[node],
dz => dzToConfig.get(dz).dropTargetStyle,
dz => dzToConfig.get(dz).dropTargetClasses
);
} else {
styleActiveDropZones([node], dz => dzToConfig.get(dz).dropTargetStyle);
styleActiveDropZones(
[node],
dz => dzToConfig.get(dz).dropTargetStyle,
dz => dzToConfig.get(dz).dropTargetClasses
);
}
}
config.dropFromOthersDisabled = dropFromOthersDisabled;
Expand Down
1 change: 1 addition & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface Options {
flipDurationMs?: number; // if the list animated using flip (recommended), specifies the flip duration such that everything syncs with it without conflict
dragDisabled?: boolean;
dropFromOthersDisabled?: boolean;
dropTargetClasses?: [string]
dropTargetStyle?: Record<string, string>;
transformDraggedElement?: TransformDraggedElementFunction;
autoAriaDisabled?: boolean;
Expand Down

0 comments on commit cb3234c

Please sign in to comment.