UNPKG

@formkit/drag-and-drop

Version:

Drag and drop package.

1,481 lines (1,476 loc) 94.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { addClass: () => addClass, addEvents: () => addEvents, addNodeClass: () => addNodeClass, addParentClass: () => addParentClass, animations: () => animations, copyNodeStyle: () => copyNodeStyle, createEmitter: () => createEmitter, dragAndDrop: () => dragAndDrop, dragStateProps: () => dragStateProps, dragValues: () => dragValues, dragstartClasses: () => dragstartClasses, dropOrSwap: () => dropOrSwap, emit: () => emit, eventCoordinates: () => eventCoordinates, getElFromPoint: () => getElFromPoint, getRealCoords: () => getRealCoords2, handleClickNode: () => handleClickNode, handleClickParent: () => handleClickParent, handleDragend: () => handleDragend, handleDragstart: () => handleDragstart, handleEnd: () => handleEnd3, handleLongPress: () => handleLongPress, handleNodeDragover: () => handleNodeDragover3, handleNodeDrop: () => handleNodeDrop, handleNodeKeydown: () => handleNodeKeydown, handleNodePointerdown: () => handleNodePointerdown, handleNodePointermove: () => handleNodePointermove, handleNodePointerover: () => handleNodePointerover2, handleNodePointerup: () => handleNodePointerup, handleParentBlur: () => handleParentBlur, handleParentDragover: () => handleParentDragover3, handleParentDrop: () => handleParentDrop, handleParentFocus: () => handleParentFocus, handleParentKeydown: () => handleParentKeydown, handleParentPointerover: () => handleParentPointerover2, handlePointercancel: () => handlePointercancel, initDrag: () => initDrag, insert: () => insert, isBrowser: () => isBrowser, isDragState: () => isDragState, isNode: () => isNode, isSynthDragState: () => isSynthDragState, noDefault: () => noDefault, nodeEventData: () => nodeEventData, nodes: () => nodes, on: () => on, parentEventData: () => parentEventData, parentValues: () => parentValues, parents: () => parents, performSort: () => performSort, performTransfer: () => performTransfer, preventSortOnScroll: () => preventSortOnScroll, remapFinished: () => remapFinished, remapNodes: () => remapNodes, removeClass: () => removeClass, resetState: () => resetState, setAttrs: () => setAttrs, setDragState: () => setDragState, setParentValues: () => setParentValues, setupNode: () => setupNode, setupNodeRemap: () => setupNodeRemap, sort: () => sort, state: () => state, synthMove: () => synthMove, tearDown: () => tearDown, tearDownNode: () => tearDownNode, tearDownNodeRemap: () => tearDownNodeRemap, throttle: () => throttle, transfer: () => transfer, treeAncestors: () => treeAncestors, updateConfig: () => updateConfig, validateDragHandle: () => validateDragHandle, validateDragstart: () => validateDragstart, validateSort: () => validateSort, validateTransfer: () => validateTransfer }); module.exports = __toCommonJS(src_exports); // src/plugins/animations/index.ts function animations(animationsConfig = {}) { const slideUp = [ { transform: `translateY(${animationsConfig.yScale || 50}%)` }, { transform: `translateY(${animationsConfig.yScale || 0}%)` } ]; const slideDown = [ { transform: `translateY(-${animationsConfig.yScale || 50}%)` }, { transform: `translateY(${animationsConfig.yScale || 0}%)` } ]; const slideLeft = [ { transform: `translateX(${animationsConfig.xScale || 50}%)` }, { transform: `translateX(${animationsConfig.xScale || 0}%)` } ]; const slideRight = [ { transform: `translateX(-${animationsConfig.xScale || 50}%)` }, { transform: `translateX(${animationsConfig.xScale || 0}%)` } ]; return (parent) => { const parentData = parents.get(parent); if (!parentData) return; return { setup() { if (document.head.querySelector("[data-drag-and-drop]")) return; }, setupNodeRemap(data) { if (!isDragState(state)) return; const duration = animationsConfig.duration || 150; if (data.node.data.value === state.draggedNode.data.value) { switch (state.incomingDirection) { case "below": animate(data.node.el, slideUp, duration); break; case "above": animate(data.node.el, slideDown, duration); break; case "left": animate(data.node.el, slideRight, duration); break; case "right": animate(data.node.el, slideLeft, duration); break; } return; } if (!state.affectedNodes.map((x) => x.data.value).includes(data.node.data.value)) return; const nodeRect = data.node.el.getBoundingClientRect(); const nodeIndex = state.affectedNodes.findIndex( (x) => x.data.value === data.node.data.value ); const draggedNodeIndex = state.draggedNode.data.index; const ascendingDirection = draggedNodeIndex >= state.targetIndex; let adjacentNode; if (ascendingDirection) { adjacentNode = state.affectedNodes[nodeIndex + 1] ? state.affectedNodes[nodeIndex + 1] : state.affectedNodes[nodeIndex - 1]; } else { adjacentNode = state.affectedNodes[nodeIndex - 1] ? state.affectedNodes[nodeIndex - 1] : state.affectedNodes[nodeIndex + 1]; } if (adjacentNode) { const xDiff = Math.abs( nodeRect.x - adjacentNode.el.getBoundingClientRect().x ); const yDiff = Math.abs( nodeRect.y - adjacentNode.el.getBoundingClientRect().y ); if (xDiff > yDiff && ascendingDirection) { animate(data.node.el, slideRight, duration); } else if (xDiff > yDiff && !ascendingDirection) { animate(data.node.el, slideLeft, duration); } } else { switch (state.incomingDirection) { case "below": animate(data.node.el, slideDown, duration); break; case "above": animate(data.node.el, slideUp, duration); break; case "left": animate(data.node.el, slideLeft, duration); break; case "right": animate(data.node.el, slideRight, duration); break; } } } }; }; } function animate(node, animation, duration) { if (!state) return; state.preventEnter = true; node.animate(animation, { duration, easing: "ease-in-out" }); setTimeout(() => { if (!state) return; state.preventEnter = false; }, duration); } // src/plugins/insert/index.ts var insertState = { draggedOverNodes: [], draggedOverParent: null, targetIndex: 0, ascending: false, coordinates: { x: 0, y: 0 }, insertPoint: null, dragging: false }; var documentController; function insert(insertConfig) { return (parent) => { const parentData = parents.get(parent); if (!parentData) return; const insertParentConfig = { ...parentData.config, insertConfig }; return { teardown() { if (parentData.abortControllers.root) { parentData.abortControllers.root.abort(); } }, setup() { insertParentConfig.handleNodeDragover = insertConfig.handleNodeDragover || handleNodeDragover; insertParentConfig.handleParentPointerover = insertConfig.handleParentPointerover || handleParentPointerover; insertParentConfig.handleNodePointerover = insertConfig.handleNodePointerover || handleParentPointerover; insertParentConfig.handleParentDragover = insertConfig.handleParentDragover || handleParentDragover; const originalHandleend = insertParentConfig.handleEnd; insertParentConfig.handleEnd = (state2) => { handleEnd(state2); originalHandleend(state2); }; parentData.on("dragStarted", () => { documentController = addEvents(document, { dragover: checkPosition, pointermove: checkPosition }); }); parentData.on("dragEnded", () => { documentController?.abort(); }); parentData.config = insertParentConfig; state.on("dragStarted", () => { const insertPoint = parentData.config.insertConfig?.insertPoint({ el: parent, data: parentData }); if (!insertPoint) return; if (!document.body.contains(insertPoint)) document.body.appendChild(insertPoint); Object.assign(insertPoint, { position: "absolute", display: "none" }); insertState.insertPoint = insertPoint; }); window.addEventListener("scroll", defineRanges.bind(null, parent)); window.addEventListener("resize", defineRanges.bind(null, parent)); }, remapFinished() { defineRanges(parent); } }; }; } function checkPosition(e) { if (!isDragState(state)) return; const el = document.elementFromPoint(e.clientX, e.clientY); if (!(el instanceof HTMLElement)) return; if (!parents.has(el)) { const insertPoint = insertState.insertPoint; if (insertPoint && insertPoint === el) return; if (insertPoint) insertPoint.style.display = "none"; if (insertState.draggedOverParent) { removeClass( [insertState.draggedOverParent.el], insertState.draggedOverParent.data.config.dropZoneClass ); } insertState.draggedOverNodes = []; insertState.draggedOverParent = null; state.currentParent = state.initialParent; } } function ascendingVertical(nodeCoords, nextNodeCoords) { const center = nodeCoords.top + nodeCoords.height / 2; if (!nextNodeCoords) { return { y: [center, center + nodeCoords.height / 2 + 10], x: [nodeCoords.left, nodeCoords.right], vertical: true }; } return { y: [ center, nodeCoords.bottom + Math.abs(nodeCoords.bottom - nextNodeCoords.top) / 2 ], x: [nodeCoords.left, nodeCoords.right], vertical: true }; } function descendingVertical(nodeCoords, prevNodeCoords) { const center = nodeCoords.top + nodeCoords.height / 2; if (!prevNodeCoords) { return { y: [center - nodeCoords.height / 2 - 10, center], x: [nodeCoords.left, nodeCoords.right], vertical: true }; } return { y: [ prevNodeCoords.bottom + Math.abs(prevNodeCoords.bottom - nodeCoords.top) / 2, center ], x: [nodeCoords.left, nodeCoords.right], vertical: true }; } function ascendingHorizontal(nodeCoords, nextNodeCoords, lastInRow = false) { const center = nodeCoords.left + nodeCoords.width / 2; if (!nextNodeCoords) { return { x: [center, center + nodeCoords.width], y: [nodeCoords.top, nodeCoords.bottom], vertical: false }; } if (lastInRow) { return { x: [center, nodeCoords.right + 10], y: [nodeCoords.top, nodeCoords.bottom], vertical: false }; } else { const nextNodeCenter = nextNodeCoords.left + nextNodeCoords.width / 2; return { x: [center, center + Math.abs(center - nextNodeCenter) / 2], y: [nodeCoords.top, nodeCoords.bottom], vertical: false }; } } function descendingHorizontal(nodeCoords, prevNodeCoords) { const center = nodeCoords.left + nodeCoords.width / 2; if (!prevNodeCoords) { return { x: [nodeCoords.left - 10, center], y: [nodeCoords.top, nodeCoords.bottom], vertical: false }; } return { x: [ prevNodeCoords.right + Math.abs(prevNodeCoords.right - nodeCoords.left) / 2, center ], y: [nodeCoords.top, nodeCoords.bottom], vertical: false }; } function getRealCoords(el) { const { top, bottom, left, right, height, width } = el.getBoundingClientRect(); const scrollLeft = window.scrollX || document.documentElement.scrollLeft; const scrollTop = window.scrollY || document.documentElement.scrollTop; const adjustedTop = top + scrollTop; const adjustedBottom = bottom + scrollTop; const adjustedLeft = left + scrollLeft; const adjustedRight = right + scrollLeft; return { top: adjustedTop, bottom: adjustedBottom, left: adjustedLeft, right: adjustedRight, height, width }; } function defineRanges(parent) { const parentData = parents.get(parent); if (!parentData) return; const enabledNodes = parentData.enabledNodes; enabledNodes.forEach((node, index) => { node.data.range = {}; let aboveOrBelowPrevious = false; let aboveOrBelowAfter = false; let prevNodeCoords = void 0; let nextNodeCoords = void 0; if (enabledNodes[index - 1]) prevNodeCoords = getRealCoords(enabledNodes[index - 1].el); if (enabledNodes[index + 1]) nextNodeCoords = getRealCoords(enabledNodes[index + 1].el); const nodeCoords = getRealCoords(node.el); if (prevNodeCoords) { aboveOrBelowPrevious = nodeCoords.top > prevNodeCoords.bottom || nodeCoords.bottom < prevNodeCoords.top; } if (nextNodeCoords) { aboveOrBelowAfter = nodeCoords.top > nextNodeCoords.bottom || nodeCoords.bottom < nextNodeCoords.top; } const fullishWidth = parent.getBoundingClientRect().width * 0.8 < nodeCoords.width; if (fullishWidth) { node.data.range.ascending = ascendingVertical(nodeCoords, nextNodeCoords); node.data.range.descending = descendingVertical( nodeCoords, prevNodeCoords ); } else if (aboveOrBelowAfter && !aboveOrBelowPrevious) { node.data.range.ascending = ascendingHorizontal( nodeCoords, nextNodeCoords, true ); node.data.range.descending = descendingHorizontal( nodeCoords, prevNodeCoords ); } else if (!aboveOrBelowPrevious && !aboveOrBelowAfter) { node.data.range.ascending = ascendingHorizontal( nodeCoords, nextNodeCoords ); node.data.range.descending = descendingHorizontal( nodeCoords, prevNodeCoords ); } else if (aboveOrBelowPrevious && !nextNodeCoords) { node.data.range.ascending = ascendingHorizontal(nodeCoords); } else if (aboveOrBelowPrevious && !aboveOrBelowAfter) { node.data.range.ascending = ascendingHorizontal( nodeCoords, nextNodeCoords ); node.data.range.descending = descendingHorizontal(nodeCoords); } }); } function handleNodeDragover(data) { data.e.preventDefault(); } function handleParentDragover(data, state2) { if (!state2 || !insertState) return; data.e.stopPropagation(); data.e.preventDefault(); const { x, y } = eventCoordinates(data.e); const clientX = x; const clientY = y; const scrollLeft = window.scrollX || document.documentElement.scrollLeft; const scrollTop = window.scrollY || document.documentElement.scrollTop; insertState.coordinates.x = clientX + scrollLeft; insertState.coordinates.y = clientY + scrollTop; const nestedParent = data.targetData.parent.data.nestedParent; let realTargetParent = data.targetData.parent; if (nestedParent) { const rect = nestedParent.el.getBoundingClientRect(); if (insertState.coordinates.y > rect.top && insertState.coordinates.y < rect.bottom) realTargetParent = nestedParent; } realTargetParent.el === state2.currentParent?.el ? moveBetween(realTargetParent) : moveOutside(realTargetParent, state2); state2.currentParent = realTargetParent; } function moveBetween(data) { if (data.data.config.sortable === false) return; if (data.el === insertState.draggedOverParent?.el && insertState.draggedOverParent.data.getValues(data.el).length === 0) { return; } else if (insertState.draggedOverParent?.el) { removeClass( [insertState.draggedOverParent.el], insertState.draggedOverParent.data.config.dropZoneClass ); insertState.draggedOverParent = null; } const foundRange = findClosest(data.data.enabledNodes); if (!foundRange) return; const key = foundRange[1]; if (foundRange) { const position = foundRange[0].data.range ? foundRange[0].data.range[key] : void 0; if (position) positioninsertPoint( position, foundRange[1] === "ascending", foundRange[0] ); } } function moveOutside(data, state2) { if (data.el === state2.currentParent.el) return false; const targetConfig = data.data.config; if (targetConfig.treeGroup && state2.draggedNode.el.contains(data.el)) return false; if (targetConfig.dropZone === false) return false; const initialParentConfig = state2.initialParent.data.config; if (targetConfig.accepts) { return targetConfig.accepts( data, state2.initialParent, state2.currentParent, state2 ); } else if (!targetConfig.group || targetConfig.group !== initialParentConfig.group) { return false; } const values = data.data.getValues(data.el); if (!values.length) { addParentClass([data.el], targetConfig.dropZoneClass); insertState.draggedOverParent = data; const insertPoint = insertState.insertPoint; if (insertPoint) insertPoint.style.display = "none"; } else { removeClass([state2.currentParent.el], targetConfig.dropZoneClass); const enabledNodes = data.data.enabledNodes; const foundRange = findClosest(enabledNodes); if (!foundRange) return; const key = foundRange[1]; if (foundRange) { const position = foundRange[0].data.range ? foundRange[0].data.range[key] : void 0; if (position) positioninsertPoint( position, foundRange[1] === "ascending", foundRange[0] ); } } } function findClosest(enabledNodes) { let foundRange = null; for (let x = 0; x < enabledNodes.length; x++) { if (!state || !enabledNodes[x].data.range) continue; if (enabledNodes[x].data.range.ascending) { if (insertState.coordinates.y > enabledNodes[x].data.range.ascending.y[0] && insertState.coordinates.y < enabledNodes[x].data.range.ascending.y[1] && insertState.coordinates.x > enabledNodes[x].data.range.ascending.x[0] && insertState.coordinates.x < enabledNodes[x].data.range.ascending.x[1]) { foundRange = [enabledNodes[x], "ascending"]; return foundRange; } } if (enabledNodes[x].data.range.descending) { if (insertState.coordinates.y > enabledNodes[x].data.range.descending.y[0] && insertState.coordinates.y < enabledNodes[x].data.range.descending.y[1] && insertState.coordinates.x > enabledNodes[x].data.range.descending.x[0] && insertState.coordinates.x < enabledNodes[x].data.range.descending.x[1]) { foundRange = [enabledNodes[x], "descending"]; return foundRange; } } } } function handleParentPointerover(data, state2) { data.detail.e.stopPropagation(); const { x, y } = eventCoordinates(data.detail.e); state2.coordinates.y = y; state2.coordinates.x = x; const nestedParent = data.detail.targetData.parent.data.nestedParent; let realTargetParent = data.detail.targetData.parent; if (nestedParent) { const rect = nestedParent.el.getBoundingClientRect(); if (state2.coordinates.y > rect.top && state2.coordinates.y < rect.bottom) realTargetParent = nestedParent; } const enabledNodes = realTargetParent.data.enabledNodes; const foundRange = findClosest(enabledNodes); if (!foundRange) return; const key = foundRange[1]; if (foundRange) { const position = foundRange[0].data.range ? foundRange[0].data.range[key] : void 0; if (position) positioninsertPoint( position, foundRange[1] === "ascending", foundRange[0] ); } data.detail.targetData.parent.el === state2.currentParent.el ? moveBetween(realTargetParent) : moveOutside(realTargetParent, state2); } function positioninsertPoint(position, ascending, node) { if (!state) return; insertState.draggedOverNodes = [node]; if (!insertState.insertPoint) return; if (position.vertical) { const topPosition = position.y[ascending ? 1 : 0] - insertState.insertPoint.getBoundingClientRect().height / 2; insertState.insertPoint.style.top = `${topPosition}px`; const leftCoordinate = position.x[0]; const rightCoordinate = position.x[1]; insertState.insertPoint.style.left = `${leftCoordinate}px`; insertState.insertPoint.style.right = `${rightCoordinate}px`; insertState.insertPoint.style.height = "4px"; insertState.insertPoint.style.width = rightCoordinate - leftCoordinate + "px"; } else { const leftPosition = position.x[ascending ? 1 : 0] - insertState.insertPoint.getBoundingClientRect().width / 2; insertState.insertPoint.style.left = `${leftPosition}px`; const topCoordinate = position.y[0]; const bottomCoordinate = position.y[1]; insertState.insertPoint.style.top = `${topCoordinate}px`; insertState.insertPoint.style.bottom = `${bottomCoordinate}px`; insertState.insertPoint.style.width = "4px"; insertState.insertPoint.style.height = bottomCoordinate - topCoordinate + "px"; } insertState.targetIndex = node.data.index; insertState.ascending = ascending; insertState.insertPoint.style.display = "block"; } function handleEnd(state2) { const insertPoint = insertState.insertPoint; if (!insertState.draggedOverParent) { const draggedParentValues = parentValues( state2.initialParent.el, state2.initialParent.data ); const transferred = state2.initialParent.el !== state2.currentParent.el; const draggedValues = state2.draggedNodes.map((node) => node.data.value); if (!transferred && insertState.draggedOverNodes[0] && insertState.draggedOverNodes[0].el !== state2.draggedNodes[0].el) { const newParentValues = [ ...draggedParentValues.filter((x) => !draggedValues.includes(x)) ]; let index = insertState.draggedOverNodes[0].data.index; if (insertState.targetIndex > state2.draggedNodes[0].data.index && !insertState.ascending) { index--; } else if (insertState.targetIndex < state2.draggedNodes[0].data.index && insertState.ascending) { index++; } newParentValues.splice(index, 0, ...draggedValues); setParentValues(state2.initialParent.el, state2.initialParent.data, [ ...newParentValues ]); if (state2.initialParent.data.config.onSort) { } } else if (transferred && insertState.draggedOverNodes.length) { const targetParentValues = parentValues( state2.currentParent.el, state2.currentParent.data ); const draggedParentValues2 = parentValues( state2.initialParent.el, state2.initialParent.data ); let index = insertState.draggedOverNodes[0].data.index || 0; if (insertState.ascending) index++; const insertValues = state2.dynamicValues.length ? state2.dynamicValues : draggedValues; targetParentValues.splice(index, 0, ...insertValues); setParentValues(state2.currentParent.el, state2.currentParent.data, [ ...targetParentValues ]); draggedParentValues2.splice(state2.initialIndex, draggedValues.length); setParentValues(state2.initialParent.el, state2.initialParent.data, [ ...draggedParentValues2 ]); const data = { sourceParent: state2.initialParent, targetParent: state2.currentParent, draggedNodes: state2.draggedNodes, targetNodes: insertState.draggedOverNodes, state: state2 }; if (state2.initialParent.data.config.insertConfig?.insertEvent) state2.initialParent.data.config.insertConfig.insertEvent(data); if (state2.currentParent.data.config.insertConfig?.insertEvent) state2.currentParent.data.config.insertConfig.insertEvent(data); } } else if (insertState.draggedOverParent) { const draggedValues = state2.draggedNodes.map((node) => node.data.value); const draggedParentValues = parentValues( state2.initialParent.el, state2.initialParent.data ); const newParentValues = [ ...draggedParentValues.filter((x) => !draggedValues.includes(x)) ]; const draggedOverParentValues = parentValues( insertState.draggedOverParent.el, insertState.draggedOverParent.data ); const insertValues = state2.dynamicValues.length ? state2.dynamicValues : draggedValues; draggedOverParentValues.push(...insertValues); setParentValues( insertState.draggedOverParent.el, insertState.draggedOverParent.data, [...draggedOverParentValues] ); setParentValues(state2.initialParent.el, state2.initialParent.data, [ ...newParentValues ]); const data = { sourceParent: state2.initialParent, targetParent: state2.currentParent, draggedNodes: state2.draggedNodes, targetNodes: insertState.draggedOverNodes, state: state2 }; if (state2.initialParent.data.config.insertConfig?.insertEvent) state2.initialParent.data.config.insertConfig.insertEvent(data); if (state2.currentParent.data.config.insertConfig?.insertEvent) state2.currentParent.data.config.insertConfig.insertEvent(data); removeClass( [insertState.draggedOverParent.el], insertState.draggedOverParent.data.config.dropZoneClass ); } if (insertPoint) insertPoint.style.display = "none"; const dropZoneClass = isSynthDragState(state2) ? state2.initialParent.data.config.synthDropZoneClass : state2.initialParent.data.config.dropZoneClass; removeClass( insertState.draggedOverNodes.map((node) => node.el), dropZoneClass ); const dragPlaceholderClass = state2.initialParent.data.config.dragPlaceholderClass; removeClass( state2.draggedNodes.map((node) => node.el), dragPlaceholderClass ); insertState.draggedOverNodes = []; insertState.draggedOverParent = null; } // src/plugins/drop-or-swap/index.ts var dropSwapState = { draggedOverNodes: Array(), initialDraggedIndex: void 0, transferred: false, dragging: false }; var documentController2; function dropOrSwap(dropSwapConfig = {}) { return (parent) => { const parentData = parents.get(parent); if (!parentData) return; const dropSwapParentConfig = { ...parentData.config, dropSwapConfig }; return { setup() { dropSwapParentConfig.handleNodeDragover = dropSwapConfig.handleNodeDragover || handleNodeDragover2; dropSwapParentConfig.handleParentDragover = dropSwapConfig.handleParentDragover || handleParentDragover2; dropSwapParentConfig.handleNodePointerover = dropSwapConfig.handleNodePointerover || handleNodePointerover; dropSwapParentConfig.handleParentPointerover = dropSwapConfig.handleParentPointerover || handeParentPointerover; const originalHandleend = dropSwapParentConfig.handleEnd; dropSwapParentConfig.handleEnd = (state2) => { handleEnd2(state2); originalHandleend(state2); }; parentData.on("dragStarted", () => { documentController2 = addEvents(document, { dragover: rootDragover, handleRootPointerover: rootPointerover }); }); parentData.on("dragEnded", () => { documentController2?.abort(); }); parentData.config = dropSwapParentConfig; } }; }; } function rootDragover(_e) { if (!isDragState(state)) return; removeClass( [state.currentParent.el], state.currentParent.data.config.dropZoneParentClass ); state.currentParent = state.initialParent; } function rootPointerover(_e) { if (!isSynthDragState(state)) return; removeClass( [state.currentParent.el], state.currentParent.data.config.synthDropZoneParentClass ); state.currentParent = state.initialParent; } function updateDraggedOverNodes(data, state2) { const targetData = "detail" in data ? data.detail.targetData : data.targetData; const config = targetData.parent.data.config; const dropZoneClass = isSynthDragState(state2) ? config.synthDropZoneClass : config.dropZoneClass; removeClass( dropSwapState.draggedOverNodes.map((node) => node.el), dropZoneClass ); const enabledNodes = targetData.parent.data.enabledNodes; if (!enabledNodes) return; dropSwapState.draggedOverNodes = enabledNodes.slice( targetData.node.data.index, targetData.node.data.index + state2.draggedNodes.length ); addNodeClass( dropSwapState.draggedOverNodes.map((node) => node.el), dropZoneClass, true ); state2.currentTargetValue = targetData.node.data.value; state2.currentParent = targetData.parent; addClass( state2.currentParent.el, isSynthDragState(state2) ? config.synthDropZoneParentClass : config.dropZoneParentClass, state2.currentParent.data, true ); } function handleNodeDragover2(data, state2) { data.e.preventDefault(); data.e.stopPropagation(); updateDraggedOverNodes(data, state2); } function handleParentDragover2(data, state2) { data.e.preventDefault(); data.e.stopPropagation(); const currentConfig = state2.currentParent.data.config; removeClass( dropSwapState.draggedOverNodes.map((node) => node.el), currentConfig.dropZoneClass ); removeClass([state2.currentParent.el], currentConfig.dropZoneParentClass); const config = data.targetData.parent.data.config; addClass( data.targetData.parent.el, config.dropZoneParentClass, data.targetData.parent.data, true ); dropSwapState.draggedOverNodes = []; state2.currentParent = data.targetData.parent; } function handeParentPointerover(data) { const currentConfig = data.detail.state.currentParent.data.config; removeClass( dropSwapState.draggedOverNodes.map((node) => node.el), currentConfig.synthDropZoneClass ); removeClass( [data.detail.state.currentParent.el], currentConfig.synthDropZoneParentClass ); const config = data.detail.targetData.parent.data.config; addClass( data.detail.targetData.parent.el, config.synthDropZoneParentClass, data.detail.targetData.parent.data, true ); dropSwapState.draggedOverNodes = []; data.detail.state.currentParent = data.detail.targetData.parent; } function handleNodePointerover(data) { if (!isSynthDragState(data.detail.state)) return; updateDraggedOverNodes(data, data.detail.state); } function swapElements(arr1, arr2, index1, index2) { const indices1 = Array.isArray(index1) ? index1 : [index1]; if (arr2 === null) { const elementsFromArr1 = indices1.map((i) => arr1[i]); const elementFromArr2 = arr1[index2]; arr1.splice(index2, 1, ...elementsFromArr1); indices1.forEach((i, idx) => { arr1[i] = idx === 0 ? elementFromArr2 : void 0; }); return arr1.filter((el) => el !== void 0); } else { const elementsFromArr1 = indices1.map((i) => arr1[i]); const elementFromArr2 = arr2[index2]; arr2.splice(index2, 1, ...elementsFromArr1); indices1.forEach((i, idx) => { arr1[i] = idx === 0 ? elementFromArr2 : void 0; }); return [arr1.filter((el) => el !== void 0), arr2]; } } function handleEnd2(state2) { const isSynth = isSynthDragState(state2); removeClass( [state2.currentParent.el], isSynth ? state2.currentParent.data.config.synthDropZoneParentClass : state2.currentParent.data.config.dropZoneParentClass ); removeClass( dropSwapState.draggedOverNodes.map((node) => node.el), isSynth ? state2.currentParent.data.config.synthDropZoneClass : state2.currentParent.data.config.dropZoneClass ); const values = parentValues(state2.currentParent.el, state2.currentParent.data); const draggedValues = state2.draggedNodes.map((node) => node.data.value); const newValues = values.filter((x) => !draggedValues.includes(x)); const targetIndex = dropSwapState.draggedOverNodes[0]?.data.index; const draggedIndex = state2.draggedNodes[0].data.index; const initialParentValues = parentValues( state2.initialParent.el, state2.initialParent.data ); if (targetIndex === void 0) { if (state2.initialParent.el === state2.currentParent.el) return; const newInitialValues = initialParentValues.filter( (x) => !draggedValues.includes(x) ); setParentValues( state2.initialParent.el, state2.initialParent.data, newInitialValues ); setParentValues( state2.currentParent.el, state2.currentParent.data, values.concat(draggedValues) ); return; } let swap = false; const shouldSwap = state2.initialParent.data.config.dropSwapConfig?.shouldSwap; if (shouldSwap) swap = shouldSwap({ sourceParent: state2.initialParent, targetParent: state2.currentParent, draggedNodes: state2.draggedNodes, targetNodes: dropSwapState.draggedOverNodes, state: state2 }); if (state2.initialParent.el === state2.currentParent.el) { newValues.splice(targetIndex, 0, ...draggedValues); setParentValues( state2.currentParent.el, state2.currentParent.data, swap ? swapElements(values, null, draggedIndex, targetIndex) : newValues ); } else { if (swap) { const res = swapElements( initialParentValues, newValues, state2.initialIndex, targetIndex ); setParentValues( state2.initialParent.el, state2.initialParent.data, res[0] ); setParentValues( state2.currentParent.el, state2.currentParent.data, res[1] ); } else { const newInitialValues = initialParentValues.filter( (x) => !draggedValues.includes(x) ); setParentValues( state2.initialParent.el, state2.initialParent.data, newInitialValues ); newValues.splice(targetIndex, 0, ...draggedValues); setParentValues( state2.currentParent.el, state2.currentParent.data, newValues ); } } } // src/index.ts function checkTouchSupport() { if (!isBrowser) return false; return "ontouchstart" in window || navigator.maxTouchPoints > 0; } var isBrowser = typeof window !== "undefined"; var dropped = false; var documentController3; var windowController; var touchDevice = false; var nodes = /* @__PURE__ */ new WeakMap(); var parents = /* @__PURE__ */ new WeakMap(); var treeAncestors = {}; var synthNodePointerDown = false; function createEmitter() { const callbacks = /* @__PURE__ */ new Map(); const emit2 = function(eventName, data) { if (!callbacks.get(eventName)) return; callbacks.get(eventName).forEach((cb) => { cb(data); }); }; const on2 = function(eventName, callback) { const cbs = callbacks.get(eventName) ?? []; cbs.push(callback); callbacks.set(eventName, cbs); }; return [emit2, on2]; } var [emit, on] = createEmitter(); var baseDragState = { activeDescendant: void 0, affectedNodes: [], currentTargetValue: void 0, on, emit, newActiveDescendant: void 0, originalZIndex: void 0, pointerSelection: false, preventEnter: false, longPress: false, longPressTimeout: 0, remapJustFinished: false, selectednodes: [], selectedParent: void 0, preventSynthDrag: false }; var state = baseDragState; function resetState() { const baseDragState2 = { activeDescendant: void 0, affectedNodes: [], on, emit, currentTargetValue: void 0, originalZIndex: void 0, pointerId: void 0, preventEnter: false, remapJustFinished: false, selectednodes: [], preventSynthDrag: false, selectedParent: void 0, pointerSelection: false, synthScrollDirection: void 0, draggedNodeDisplay: void 0, synthDragScrolling: false, longPress: false, longPressTimeout: 0 }; state = { ...baseDragState2 }; } function setDragState(dragStateProps2) { Object.assign(state, dragStateProps2); dragStateProps2.initialParent.data.emit("dragStarted", state); dropped = false; state.emit("dragStarted", state); return state; } function handleRootPointerdown(_e) { if (state.activeState) setActive(state.activeState.parent, void 0, state); if (state.selectedState) deselect(state.selectedState.nodes, state.selectedState.parent, state); state.selectedState = state.activeState = void 0; } function handleRootPointerup(_e) { if (!isSynthDragState(state)) return; const config = state.currentParent.data.config; if (isSynthDragState(state)) config.handleEnd(state); } function handleRootKeydown(e) { if (e.key === "Escape") { if (state.selectedState) deselect(state.selectedState.nodes, state.selectedState.parent, state); if (state.activeState) setActive(state.activeState.parent, void 0, state); state.selectedState = state.activeState = void 0; } } function handleRootDrop(_e) { } function handleRootDragover(e) { if (!isDragState(state)) return; e.preventDefault(); } function dragAndDrop({ parent, getValues, setValues, config = {} }) { if (!isBrowser) return; touchDevice = checkTouchSupport(); if (!documentController3) documentController3 = addEvents(document, { dragover: handleRootDragover, pointerdown: handleRootPointerdown, pointerup: handleRootPointerup, keydown: handleRootKeydown, drop: handleRootDrop }); if (!windowController) windowController = addEvents(window, { resize: () => { touchDevice = checkTouchSupport(); } }); tearDown(parent); const [emit2, on2] = createEmitter(); const parentData = { getValues, setValues, config: { dragDropEffect: config.dragDropEffect ?? "move", dragEffectAllowed: config.dragEffectAllowed ?? "move", draggedNodes, dragstartClasses, deepCopyStyles: config.deepCopyStyles ?? false, handleNodeKeydown, handleParentKeydown, handleDragstart, handleNodeDragover: handleNodeDragover3, handleParentDragover: handleParentDragover3, handleNodeDrop, handlePointercancel, handleEnd: handleEnd3, handleDragend, handleParentBlur, handleParentFocus, handleNodePointerup, handleNodePointerover: handleNodePointerover2, handleParentPointerover: handleParentPointerover2, handleParentScroll, handleNodePointerdown, handleNodePointermove, handleNodeDragenter, handleNodeDragleave, handleParentDrop, multiDrag: config.multiDrag ?? false, nativeDrag: config.nativeDrag ?? true, performSort, performTransfer, root: config.root ?? document, setupNode, setupNodeRemap, reapplyDragClasses, tearDownNode, tearDownNodeRemap, remapFinished, scrollBehavior: { x: 0.95, y: 0.95 }, threshold: { horizontal: 0, vertical: 0 }, ...config }, enabledNodes: [], abortControllers: {}, privateClasses: [], on: on2, emit: emit2 }; const nodesObserver = new MutationObserver(nodesMutated); nodesObserver.observe(parent, { childList: true }); parents.set(parent, parentData); if (config.treeAncestor && config.treeGroup) treeAncestors[config.treeGroup] = parent; config.plugins?.forEach((plugin) => { plugin(parent)?.tearDown?.(); }); config.plugins?.forEach((plugin) => { plugin(parent)?.tearDown?.(); }); config.plugins?.forEach((plugin) => { plugin(parent)?.setup?.(); }); setup(parent, parentData); remapNodes(parent, true); } function dragStateProps(data, draggedNodes2) { const { x, y } = eventCoordinates(data.e); const rect = data.targetData.node.el.getBoundingClientRect(); return { affectedNodes: [], ascendingDirection: false, clonedDraggedEls: [], dynamicValues: [], coordinates: { x, y }, draggedNode: { el: data.targetData.node.el, data: data.targetData.node.data }, draggedNodes: draggedNodes2, incomingDirection: void 0, initialIndex: data.targetData.node.data.index, initialParent: { el: data.targetData.parent.el, data: data.targetData.parent.data }, currentParent: { el: data.targetData.parent.el, data: data.targetData.parent.data }, longPress: data.targetData.parent.data.config.longPress ?? false, longPressTimeout: 0, currentTargetValue: data.targetData.node.data.value, scrollEls: [], startLeft: x - rect.left, startTop: y - rect.top, targetIndex: data.targetData.node.data.index, transferred: false }; } function performSort({ parent, draggedNodes: draggedNodes2, targetNode }) { const draggedValues = draggedNodes2.map((x) => x.data.value); const targetParentValues = parentValues(parent.el, parent.data); const originalIndex = draggedNodes2[0].data.index; const enabledNodes = [...parent.data.enabledNodes]; const newParentValues = [ ...targetParentValues.filter((x) => !draggedValues.includes(x)) ]; newParentValues.splice(targetNode.data.index, 0, ...draggedValues); if ("draggedNode" in state) state.currentTargetValue = targetNode.data.value; setParentValues(parent.el, parent.data, [...newParentValues]); if (parent.data.config.onSort) parent.data.config.onSort({ parent: { el: parent.el, data: parent.data }, previousValues: [...targetParentValues], previousNodes: [...enabledNodes], nodes: [...parent.data.enabledNodes], values: [...newParentValues], draggedNode: draggedNodes2[0], previousPosition: originalIndex, position: targetNode.data.index }); } function setActive(parent, newActiveNode, state2) { const activeDescendantClass = parent.data.config.activeDescendantClass; if (state2.activeState) { { removeClass([state2.activeState.node.el], activeDescendantClass); if (state2.activeState.parent.el !== parent.el) state2.activeState.parent.el.setAttribute("aria-activedescendant", ""); } } if (!newActiveNode) { state2.activeState?.parent.el.setAttribute("aria-activedescendant", ""); state2.activeState = void 0; return; } state2.activeState = { node: newActiveNode, parent }; addNodeClass([newActiveNode.el], activeDescendantClass); state2.activeState.parent.el.setAttribute( "aria-activedescendant", state2.activeState.node.el.id ); } function deselect(nodes2, parent, state2) { const selectedClass = parent.data.config.selectedClass; if (!state2.selectedState) return; const iterativeNodes = Array.from(nodes2); removeClass( nodes2.map((x) => x.el), selectedClass ); for (const node of iterativeNodes) { node.el.setAttribute("aria-selected", "false"); const index = state2.selectedState.nodes.findIndex((x) => x.el === node.el); if (index === -1) continue; state2.selectedState.nodes.splice(index, 1); } clearLiveRegion(parent); } function setSelected(parent, selectedNodes, newActiveNode, state2, pointerdown = false) { state2.pointerSelection = pointerdown; for (const node of selectedNodes) { node.el.setAttribute("aria-selected", "true"); addNodeClass([node.el], parent.data.config.selectedClass, true); } state2.selectedState = { nodes: selectedNodes, parent }; const selectedItems = selectedNodes.map( (x) => x.el.getAttribute("aria-label") ); if (selectedItems.length === 0) { state2.selectedState = void 0; clearLiveRegion(parent); return; } setActive(parent, newActiveNode, state2); updateLiveRegion( parent, `${selectedItems.join( ", " )} ready for dragging. Use arrow keys to navigate. Press enter to drop ${selectedItems.join( ", " )}.` ); } function updateLiveRegion(parent, message) { const parentId = parent.el.id; const liveRegion = document.getElementById(parentId + "-live-region"); if (!liveRegion) return; liveRegion.textContent = message; } function clearLiveRegion(parent) { const liveRegion = document.getElementById(parent.el.id + "-live-region"); if (!liveRegion) return; liveRegion.textContent = ""; } function handleParentBlur(_data, _state) { } function handleParentFocus(data, state2) { const firstEnabledNode = data.targetData.parent.data.enabledNodes[0]; if (!firstEnabledNode) return; if (state2.selectedState && state2.selectedState.parent.el !== data.targetData.parent.el) { setActive(data.targetData.parent, firstEnabledNode, state2); } else if (!state2.selectedState) { setActive(data.targetData.parent, firstEnabledNode, state2); } } function performTransfer({ currentParent, targetParent, initialParent, draggedNodes: draggedNodes2, initialIndex, targetNode, state: state2 }) { const draggedValues = draggedNodes2.map((x) => x.data.value); const currentParentValues = parentValues( currentParent.el, currentParent.data ).filter((x) => !draggedValues.includes(x)); const targetParentValues = parentValues(targetParent.el, targetParent.data); const reset = initialParent.el === targetParent.el && targetParent.data.config.sortable === false; let targetIndex; if (targetNode) { if (reset) { targetIndex = initialIndex; } else if (targetParent.data.config.sortable === false) { targetIndex = targetParent.data.enabledNodes.length; } else { targetIndex = targetNode.data.index; } targetParentValues.splice(targetIndex, 0, ...draggedValues); } else { targetIndex = reset ? initialIndex : targetParent.data.enabledNodes.length; targetParentValues.splice(targetIndex, 0, ...draggedValues); } setParentValues(currentParent.el, currentParent.data, currentParentValues); setParentValues(targetParent.el, targetParent.data, targetParentValues); if (targetParent.data.config.onTransfer) { targetParent.data.config.onTransfer({ sourceParent: currentParent, targetParent, initialParent, draggedNodes: draggedNodes2, targetIndex, state: state2 }); } if (currentParent.data.config.onTransfer) { currentParent.data.config.onTransfer({ sourceParent: currentParent, targetParent, initialParent, draggedNodes: draggedNodes2, targetIndex, state: state2 }); } } function parentValues(parent, parentData) { return [...parentData.getValues(parent)]; } function findArrayCoordinates(obj, targetArray, path = []) { let result = []; if (obj === targetArray) result.push(path); if (Array.isArray(obj)) { const index = obj.findIndex((el) => el === targetArray); if (index !== -1) { result.push([...path, index]); } else { for (let i = 0; i < obj.length; i++) { result = result.concat( findArrayCoordinates(obj[i], targetArray, [...path, i]) ); } } } else if (typeof obj === "object" && obj !== null) { for (const key in obj) { result = result.concat( findArrayCoordinates(obj[key], targetArray, [...path, key]) ); } } return result; } function setValueAtCoordinatesUsingFindIndex(obj, targetArray, newArray) { const coordinates = findArrayCoordinates(obj, targetArray); let newValues; coordinates.forEach((coords) => { let current = obj; for (let i = 0; i < coords.length - 1; i++) { const index = coords[i]; current = current[index]; } const lastIndex = coords[coords.length - 1]; current[lastIndex] = newArray; newValues = current[lastIndex]; }); return newValues; } function setParentValues(parent, parentData, values) { const treeGroup = parentData.config.treeGroup; if (treeGroup) { const ancestorEl = treeAncestors[treeGroup]; const ancestorData = parents.get(ancestorEl); if (!ancestorData) return; const ancestorValues = ancestorData.getValues(ancestorEl); const initialParentValues = parentData.getValues(parent); const updatedValues = setValueAtCoordinatesUsingFindIndex( ancestorValues, initialParentValues, values ); if (!updatedValues) { console.warn("No updated value found"); return; } parentData.setValues(updatedValues, parent); return; } parentData.setValues(values, parent); } function dragValues(state2) { return [...state2.draggedNodes.map((x) => x.data.value)]; } function updateConfig(parent, config) { const parentData = parents.get(parent); if (!parentData) return; parents.set(parent, { ...parentData, config: { ...parentData.config, ...config } }); dragAndDrop({ parent, getValues: parentData.getValues, setValues: parentData.setValues, config }); } function handleParentDrop(data, state2) { data.e.stopPropagation(); dropped = true; const config = data.targetData.parent.data.config; config.handleEnd(state2); } function tearDown(parent) { const parentData = parents.get(parent); if (!parentData) return; if (parentData.abortControllers.mainParent) parentData.abortControllers.mainParent.abort(); } function isDragState(state2) { return "draggedNode" in state2 && !!state2.draggedNode; } function isSyn