Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,22 @@ npm install --save-dev svelte-dnd-action

An options-object with the following attributes:
| Name | Type | Required? | Default Value | Description |
| ------------------------- | -------------- | ------------------------------------------------------------ | ------------------------------------------------- | ------------------------------------------------------------ |
| ------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `items` | Array<Object> | Yes. Each object in the array **has to have** an `id` property (key name can be overridden globally) with a unique value (within all dnd-zones of the same type) | N/A | The data array that is used to produce the list with the draggable items (the same thing you run your #each block on). The dndzone should not have children that don't originate in `items` |
| `flipDurationMs` | Number | No | `0` | The same value you give the flip animation on the items (to make them animated as they "make space" for the dragged item). Set to zero or leave out if you don't want animations |
| `type` | String | No | Internal | dnd-zones that share the same type can have elements from one dragged into another. By default, all dnd-zones have the same type |
| `cursorStartDrag` | String | No | `grab` | cursor style when click on drag item occur |
| `cursorDragging` | String | No | `grabbing` | cursor style when the drag occurs |
| `cursorDrop` | String | No | `grab` | cursor style when the drag ends |
| `cursorHover` | String | No | `grab` | cursor style when mouse hover over draggable element |
| `constrainAxisX` | Boolean | No | `false` | Constrain dragging by X axis. Drag will be allowed only by Y axis. |
| `constrainAxisY` | Boolean | No | `false` | Constrain dragging by Y axis. Drag will be allowed only by X axis. |
| `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 |
| `morphDisabled` | Boolean | No | `false` | By default, when dragging over a zone, the dragged element is morphed to look like it would if dropped. You can prevent it by setting this option. |
| `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 |
| `zoneTabIndex` | Number | No | `0` | Allow user to set custom tabindex to the list container when not dragging. Can be useful if you want to make the screen reader to skip the list container. You can change it at any time. |
| `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 into 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 into it. Note: make sure the classes you use are global. |
| `dropTargetClasses` | Array<String> | No | `[]` | A list of classes to apply to the dnd-zone when items can be dragged into 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. If what you are after is altering styles, do it to the children, not to the dragged element itself |
| `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. |
| `centreDraggedOnCursor` | Boolean | No | `false` | Setting it to true will cause elements from this dnd-zone to position their center on the cursor on drag start, effectively turning the cursor to the focal point that triggers all the dnd events (ex: entering another zone). Useful for dnd-zones with large items that can be dragged over small items. |
Expand Down Expand Up @@ -235,15 +241,14 @@ setDebugMode(true);
If you are using Typescript, you will need to add the following block to your `global.d.ts` (at least until [this svelte issue](https://github.com/sveltejs/language-tools/issues/431) is resolved):

```typescript
declare type Item = import('svelte-dnd-action').Item;
declare type DndEvent<ItemType = Item> = import('svelte-dnd-action').DndEvent<ItemType>;
declare type Item = import("svelte-dnd-action").Item;
declare type DndEvent<ItemType = Item> = import("svelte-dnd-action").DndEvent<ItemType>;
declare namespace svelte.JSX {
interface HTMLAttributes<T> {
onconsider?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void;
onfinalize?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void;
}
interface HTMLAttributes<T> {
onconsider?: (event: CustomEvent<DndEvent<ItemType>> & {target: EventTarget & T}) => void;
onfinalize?: (event: CustomEvent<DndEvent<ItemType>> & {target: EventTarget & T}) => void;
}
}

```

You may need to edit `tsconfig.json` to include `global.d.ts` if it doesn't already: "include": ["src/**/*", "global.d.ts"].
Expand Down Expand Up @@ -307,15 +312,14 @@ You can use generics to set the type of `items` you are expecting in `DndEvent`.
items = e.detail.items;
}

let items: Dog[] = [
{ id: 1, name: 'Fido', breed: 'bulldog' },
{ id: 2, name: 'Spot', breed: 'labrador' },
{ id: 3, name: 'Jacky', breed: 'golden retriever' }
];
let items: Dog[] = [
{id: 1, name: "Fido", breed: "bulldog"},
{id: 2, name: "Spot", breed: "labrador"},
{id: 3, name: "Jacky", breed: "golden retriever"}
];
</script>
```


### Contributing [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/isaacHagoel/svelte-dnd-action/issues)

There is still quite a lot to do. If you'd like to contribute please get in touch (raise an issue or comment on an existing one).
Expand Down
19 changes: 18 additions & 1 deletion src/action.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ import {toString} from "./helpers/util";
* @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 {string} [cursorStartDrag]
* @property {string} [cursorDragging]
* @property {string} [cursorDrop]
* @property {string} [cursorHover]
* @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} [constrainAxisX] - Constrain dragging by X axis. Drag will be allowed only by Y axis.
* @property {boolean} [constrainAxisY] - Constrain dragging by Y axis. Drag will be allowed only by X axis.
* @property {boolean} [dragDisabled]
* @property {boolean} [morphDisabled] - whether dragged element should morph to zone dimensions
* @property {boolean} [dropFromOthersDisabled]
Expand Down Expand Up @@ -47,6 +53,12 @@ function validateOptions(options) {
items,
flipDurationMs,
type,
cursorStartDrag,
cursorDragging,
cursorDrop,
cursorHover,
constrainAxisX,
constrainAxisY,
dragDisabled,
morphDisabled,
dropFromOthersDisabled,
Expand Down Expand Up @@ -78,5 +90,10 @@ function validateOptions(options) {
}

function isInt(value) {
return !isNaN(value) && (function(x) { return (x | 0) === x; })(parseFloat(value));
return (
!isNaN(value) &&
(function (x) {
return (x | 0) === x;
})(parseFloat(value))
);
}
12 changes: 6 additions & 6 deletions src/helpers/styler.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function trs(property) {
* @param {Point} [positionCenterOnXY]
* @return {Node} - the cloned, styled element
*/
export function createDraggedElementFrom(originalElement, positionCenterOnXY) {
export function createDraggedElementFrom(originalElement, positionCenterOnXY, cursorStyle) {
const rect = originalElement.getBoundingClientRect();
const draggedEl = svelteNodeClone(originalElement);
copyStylesFromTo(originalElement, draggedEl);
Expand Down Expand Up @@ -46,7 +46,7 @@ export function createDraggedElementFrom(originalElement, positionCenterOnXY) {
// this is a workaround for a strange browser bug that causes the right border to disappear when all the transitions are added at the same time
window.setTimeout(() => (draggedEl.style.transition += `, ${trs("width")}, ${trs("height")}`), 0);
draggedEl.style.zIndex = "9999";
draggedEl.style.cursor = "grabbing";
draggedEl.style.cursor = cursorStyle;

return draggedEl;
}
Expand All @@ -55,8 +55,8 @@ export function createDraggedElementFrom(originalElement, positionCenterOnXY) {
* styles the dragged element to a 'dropped' state
* @param {HTMLElement} draggedEl
*/
export function moveDraggedElementToWasDroppedState(draggedEl) {
draggedEl.style.cursor = "grab";
export function moveDraggedElementToWasDroppedState(draggedEl, cursorStyle) {
draggedEl.style.cursor = cursorStyle;
}

/**
Expand Down Expand Up @@ -114,13 +114,13 @@ function copyStylesFromTo(copyFromEl, copyToEl) {
* @param {HTMLElement} draggableEl
* @param {boolean} dragDisabled
*/
export function styleDraggable(draggableEl, dragDisabled) {
export function styleDraggable(draggableEl, dragDisabled, cursorStyle) {
draggableEl.draggable = false;
draggableEl.ondragstart = () => false;
if (!dragDisabled) {
draggableEl.style.userSelect = "none";
draggableEl.style.WebkitUserSelect = "none";
draggableEl.style.cursor = "grab";
draggableEl.style.cursor = cursorStyle;
} else {
draggableEl.style.userSelect = "";
draggableEl.style.WebkitUserSelect = "";
Expand Down
47 changes: 42 additions & 5 deletions src/pointerAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ import {areArraysShallowEqualSameOrder, areObjectsShallowEqual, toString} from "
import {getBoundingRectNoTransforms} from "./helpers/intersection";

const DEFAULT_DROP_ZONE_TYPE = "--any--";
const DEFAULT_START_DRAG_CURSOR_TYPE = "grab";
const DEFAULT_DRAGGING_CURSOR_TYPE = "grabbing";
const DEFAULT_DROP_CURSOR_TYPE = "grab";
const DEFAULT_HOVER_CURSOR_TYPE = "grab";
const MIN_OBSERVATION_INTERVAL_MS = 100;
const MIN_MOVEMENT_BEFORE_DRAG_START_PX = 3;
const DEFAULT_DROP_TARGET_STYLE = {
Expand Down Expand Up @@ -214,7 +218,11 @@ function handleDraggedIsOverIndex(e) {
function handleMouseMove(e) {
e.preventDefault();
const c = e.touches ? e.touches[0] : e;
currentMousePosition = {x: c.clientX, y: c.clientY};
const {constrainAxisX, constrainAxisY} = dzToConfig.get(e.currentTarget) || dzToConfig.get(originDropZone);
currentMousePosition = {
x: constrainAxisX ? dragStartMousePosition.x : c.clientX,
y: constrainAxisY ? dragStartMousePosition.y : c.clientY
};
draggedEl.style.transform = `translate3d(${currentMousePosition.x - dragStartMousePosition.x}px, ${
currentMousePosition.y - dragStartMousePosition.y
}px, 0)`;
Expand All @@ -229,14 +237,14 @@ function handleDrop() {
window.removeEventListener("mouseup", handleDrop);
window.removeEventListener("touchend", handleDrop);
unWatchDraggedElement();
moveDraggedElementToWasDroppedState(draggedEl);

if (!shadowElDropZone) {
printDebug(() => "element was dropped right after it left origin but before entering somewhere else");
shadowElDropZone = originDropZone;
}
printDebug(() => ["dropped in dz", shadowElDropZone]);
let {items, type} = dzToConfig.get(shadowElDropZone);
let {items, type, cursorDrop} = dzToConfig.get(shadowElDropZone);
moveDraggedElementToWasDroppedState(draggedEl, cursorDrop);
styleInactiveDropZones(
typeToDropZones.get(type),
dz => dzToConfig.get(dz).dropTargetStyle,
Expand Down Expand Up @@ -321,7 +329,13 @@ export function dndzone(node, options) {
const config = {
items: undefined,
type: undefined,
cursorStartDrag: DEFAULT_START_DRAG_CURSOR_TYPE,
cursorDragging: DEFAULT_DRAGGING_CURSOR_TYPE,
cursorDrop: DEFAULT_DROP_CURSOR_TYPE,
cursorHover: DEFAULT_HOVER_CURSOR_TYPE,
flipDurationMs: 0,
constrainAxisX: false,
constrainAxisY: false,
dragDisabled: false,
morphDisabled: false,
dropFromOthersDisabled: false,
Expand Down Expand Up @@ -384,6 +398,14 @@ export function dndzone(node, options) {
dragStartMousePosition = {x: c.clientX, y: c.clientY};
currentMousePosition = {...dragStartMousePosition};
originalDragTarget = e.currentTarget;
const {cursorStartDrag} = dzToConfig.get(originalDragTarget) || dzToConfig.get(node);
moveDraggedElementToWasDroppedState(originalDragTarget, cursorStartDrag);
addMaybeListeners();
}
function handleMouseUp(e) {
originalDragTarget = e.currentTarget;
const {cursorHover} = dzToConfig.get(originalDragTarget) || dzToConfig.get(node);
moveDraggedElementToWasDroppedState(originalDragTarget, cursorHover);
addMaybeListeners();
}

Expand All @@ -406,7 +428,7 @@ export function dndzone(node, options) {
const placeHolderElData = {...shadowElData, [ITEM_ID_KEY]: SHADOW_PLACEHOLDER_ITEM_ID};

// creating the draggable element
draggedEl = createDraggedElementFrom(originalDragTarget, centreDraggedOnCursor && currentMousePosition);
draggedEl = createDraggedElementFrom(originalDragTarget, centreDraggedOnCursor && currentMousePosition, config.cursorDragging);
// We will keep the original dom node in the dom because touch events keep firing on it, we want to re-add it after the framework removes it
function keepOriginalElementInDom() {
if (!draggedEl.parentElement) {
Expand Down Expand Up @@ -445,6 +467,12 @@ export function dndzone(node, options) {
items = undefined,
flipDurationMs: dropAnimationDurationMs = 0,
type: newType = DEFAULT_DROP_ZONE_TYPE,
cursorStartDrag = DEFAULT_START_DRAG_CURSOR_TYPE,
cursorDragging = DEFAULT_DRAGGING_CURSOR_TYPE,
cursorDrop = DEFAULT_DROP_CURSOR_TYPE,
cursorHover = DEFAULT_HOVER_CURSOR_TYPE,
constrainAxisX = false,
constrainAxisY = false,
dragDisabled = false,
morphDisabled = false,
dropFromOthersDisabled = false,
Expand All @@ -458,8 +486,14 @@ export function dndzone(node, options) {
unregisterDropZone(node, config.type);
}
config.type = newType;
config.cursorStartDrag = cursorStartDrag;
config.cursorDragging = cursorDragging;
config.cursorDrop = cursorDrop;
config.cursorHover = cursorHover;
registerDropZone(node, newType);
config.items = [...items];
config.constrainAxisX = constrainAxisX;
config.constrainAxisY = constrainAxisY;
config.dragDisabled = dragDisabled;
config.morphDisabled = morphDisabled;
config.transformDraggedElement = transformDraggedElement;
Expand Down Expand Up @@ -512,7 +546,8 @@ export function dndzone(node, options) {
const shadowElIdx = findShadowElementIdx(config.items);
for (let idx = 0; idx < node.children.length; idx++) {
const draggableEl = node.children[idx];
styleDraggable(draggableEl, dragDisabled);
const {cursorHover} = dzToConfig.get(draggableEl) || dzToConfig.get(node);
styleDraggable(draggableEl, dragDisabled, cursorHover);
if (idx === shadowElIdx) {
config.transformDraggedElement(draggedEl, draggedElData, idx);
if (!morphDisabled) {
Expand All @@ -525,8 +560,10 @@ export function dndzone(node, options) {
draggableEl.removeEventListener("touchstart", elToMouseDownListener.get(draggableEl));
if (!dragDisabled) {
draggableEl.addEventListener("mousedown", handleMouseDown);
draggableEl.addEventListener("mouseup", handleMouseUp);
draggableEl.addEventListener("touchstart", handleMouseDown);
elToMouseDownListener.set(draggableEl, handleMouseDown);
elToMouseDownListener.set(draggableEl, handleMouseUp);
}
// updating the idx
elToIdx.set(draggableEl, idx);
Expand Down
6 changes: 6 additions & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ export declare type Item = Record<string, any>;
export interface Options {
items: Item[]; // the list of items that was used to generate the children of the given node
type?: string; // the type of the dnd zone. children dragged from here can only be dropped in other zones of the same type, defaults to a base type
cursorStartDrag?: string; //
cursorDragging?: string; //
cursorDrop?: string; //
cursorHover?: string; //
flipDurationMs?: number; // if the list animated using flip (recommended), specifies the flip duration such that everything syncs with it without conflict
constrainAxisX?: boolean; // Constrain dragging by X axis. Drag will be allowed only by Y axis.
constrainAxisY?: boolean; // Constrain dragging by Y axis. Drag will be allowed only by X axis.
dragDisabled?: boolean;
morphDisabled?: boolean;
dropFromOthersDisabled?: boolean;
Expand Down