UNPKG

@formkit/drag-and-drop

Version:

Drag and drop package.

1,486 lines (1,480 loc) 98.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 index_exports = {}; __export(index_exports, { addClass: () => addClass, addEvents: () => addEvents, addNodeClass: () => addNodeClass, addParentClass: () => addParentClass, animations: () => animations, dragAndDrop: () => dragAndDrop, dragStateProps: () => dragStateProps, dragValues: () => dragValues, dragstartClasses: () => dragstartClasses, dropOrSwap: () => dropOrSwap, getElFromPoint: () => getElFromPoint, handleClickNode: () => handleClickNode, handleClickParent: () => handleClickParent, handleDragend: () => handleDragend, handleDragstart: () => handleDragstart, handleEnd: () => handleEnd3, handleLongPress: () => handleLongPress, handleNodeBlur: () => handleNodeBlur, handleNodeDragover: () => handleNodeDragover3, handleNodeDrop: () => handleNodeDrop, handleNodeFocus: () => handleNodeFocus, handleNodeKeydown: () => handleNodeKeydown, handleNodePointerdown: () => handleNodePointerdown, handleNodePointerover: () => handleNodePointerover2, handleNodePointerup: () => handleNodePointerup, handleParentDragover: () => handleParentDragover3, handleParentDrop: () => handleParentDrop, handleParentFocus: () => handleParentFocus, handleParentPointerover: () => handleParentPointerover2, handlePointercancel: () => handlePointercancel, initDrag: () => initDrag, insert: () => insert, isBrowser: () => isBrowser, isDragState: () => isDragState, isNode: () => isNode, isSynthDragState: () => isSynthDragState, nodeEventData: () => nodeEventData, nodes: () => nodes, 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, transfer: () => transfer, updateConfig: () => updateConfig, validateDragHandle: () => validateDragHandle, validateDragstart: () => validateDragstart, validateSort: () => validateSort, validateTransfer: () => validateTransfer }); module.exports = __toCommonJS(index_exports); // src/utils.ts function pd(e) { e.preventDefault(); } function sp(e) { e.stopPropagation(); } 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(); function eqRegExp(x, y) { return x.source === y.source && x.flags.split("").sort().join("") === y.flags.split("").sort().join(""); } function eq(valA, valB, deep = true, explicit = ["__key"]) { if (valA === valB) return true; if (typeof valB === "object" && typeof valA === "object" && valA !== null && valB !== null) { if (valA instanceof Map) return false; if (valA instanceof Set) return false; if (valA instanceof Date && valB instanceof Date) return valA.getTime() === valB.getTime(); if (valA instanceof RegExp && valB instanceof RegExp) return eqRegExp(valA, valB); if (valA === null || valB === null) return false; const objA = valA; const objB = valB; if (Object.keys(objA).length !== Object.keys(objB).length) return false; for (const k of explicit) { if ((k in objA || k in objB) && objA[k] !== objB[k]) return false; } for (const key in objA) { if (!(key in objB)) return false; if (objA[key] !== objB[key] && !deep) return false; if (deep && !eq(objA[key], objB[key], deep, explicit)) return false; } return true; } return false; } function splitClass(className) { return className.split(" ").filter((x) => x); } function eventCoordinates(data) { return { x: data.clientX, y: data.clientY }; } // 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, 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", () => { defineRanges(parent); }); state.on("scrollStarted", () => { if (insertState.insertPoint) insertState.insertPoint.el.style.display = "none"; }); state.on("scrollEnded", () => { defineRanges(parent); }); const firstScrollableParent = findFirstOverflowingParent(parent); if (firstScrollableParent) { firstScrollableParent.addEventListener( "scroll", defineRanges.bind(null, parent) ); } window.addEventListener("resize", defineRanges.bind(null, parent)); } }; }; } function findFirstOverflowingParent(element) { let parent = element.parentElement; while (parent) { const { overflow, overflowY, overflowX } = getComputedStyle(parent); const isOverflowSet = overflow !== "visible" || overflowY !== "visible" || overflowX !== "visible"; const isOverflowing = parent.scrollHeight > parent.clientHeight || parent.scrollWidth > parent.clientWidth; const hasScrollPosition = parent.scrollTop > 0 || parent.scrollLeft > 0; if (isOverflowSet && (isOverflowing || hasScrollPosition)) { return parent; } parent = parent.parentElement; } return null; } function checkPosition(e) { if (!isDragState(state)) return; const el = document.elementFromPoint(e.clientX, e.clientY); if (!(el instanceof HTMLElement) || el === insertState.insertPoint?.el) { return; } let isWithinAParent = false; let current = el; while (current) { if (nodes.has(current) || parents.has(current)) { isWithinAParent = true; break; } if (current === document.body) break; current = current.parentElement; } if (!isWithinAParent) { if (insertState.insertPoint) { insertState.insertPoint.el.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 createVerticalRange(nodeCoords, otherCoords, isAscending) { const center = nodeCoords.top + nodeCoords.height / 2; if (!otherCoords) { const offset = nodeCoords.height / 2 + 10; return { y: isAscending ? [center, center + offset] : [center - offset, center], x: [nodeCoords.left, nodeCoords.right], vertical: true }; } const otherEdge = isAscending ? otherCoords.top : otherCoords.bottom; const nodeEdge = isAscending ? nodeCoords.bottom : nodeCoords.top; let midpoint; let range; if (isAscending) { midpoint = nodeEdge + (otherEdge - nodeEdge) / 2; range = [center, midpoint]; } else { midpoint = otherEdge + (nodeEdge - otherEdge) / 2; range = [midpoint, center]; } return { y: range, x: [nodeCoords.left, nodeCoords.right], vertical: true }; } function createHorizontalRange(nodeCoords, otherCoords, isAscending, lastInRow = false) { const center = nodeCoords.left + nodeCoords.width / 2; if (!otherCoords) { if (isAscending) { return { x: [center, center + nodeCoords.width], y: [nodeCoords.top, nodeCoords.bottom], vertical: false }; } else { return { x: [nodeCoords.left - 10, center], y: [nodeCoords.top, nodeCoords.bottom], vertical: false }; } } if (isAscending && lastInRow) { return { x: [center, nodeCoords.right + 10], y: [nodeCoords.top, nodeCoords.bottom], vertical: false }; } if (isAscending) { const nextNodeCenter = otherCoords.left + otherCoords.width / 2; return { x: [center, center + Math.abs(center - nextNodeCenter) / 2], y: [nodeCoords.top, nodeCoords.bottom], vertical: false }; } else { return { x: [ otherCoords.right + Math.abs(otherCoords.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; return { top: top + scrollTop, bottom: bottom + scrollTop, left: left + scrollLeft, right: right + scrollLeft, height, width }; } function defineRanges(parent) { if (!isDragState(state) && !isSynthDragState(state)) return; const parentData = parents.get(parent); if (!parentData) return; const enabledNodes = parentData.enabledNodes; enabledNodes.forEach((node, index) => { node.data.range = {}; const prevNode = enabledNodes[index - 1]; const nextNode = enabledNodes[index + 1]; const nodeCoords = getRealCoords(node.el); const prevNodeCoords = prevNode ? getRealCoords(prevNode.el) : void 0; const nextNodeCoords = nextNode ? getRealCoords(nextNode.el) : void 0; const aboveOrBelowPrevious = prevNodeCoords && (nodeCoords.top > prevNodeCoords.bottom || nodeCoords.bottom < prevNodeCoords.top); const aboveOrBelowAfter = nextNodeCoords && (nodeCoords.top > nextNodeCoords.bottom || nodeCoords.bottom < nextNodeCoords.top); const fullishWidth = parent.getBoundingClientRect().width * 0.8 < nodeCoords.width; if (fullishWidth) { node.data.range.ascending = createVerticalRange( nodeCoords, nextNodeCoords, true ); node.data.range.descending = createVerticalRange( nodeCoords, prevNodeCoords, false ); } else if (aboveOrBelowAfter && !aboveOrBelowPrevious) { node.data.range.ascending = createHorizontalRange( nodeCoords, nextNodeCoords, true, true ); node.data.range.descending = createHorizontalRange( nodeCoords, prevNodeCoords, false ); } else if (!aboveOrBelowPrevious && !aboveOrBelowAfter) { node.data.range.ascending = createHorizontalRange( nodeCoords, nextNodeCoords, true ); node.data.range.descending = createHorizontalRange( nodeCoords, prevNodeCoords, false ); } else if (aboveOrBelowPrevious && !nextNodeCoords) { node.data.range.ascending = createHorizontalRange( nodeCoords, void 0, true ); } else if (aboveOrBelowPrevious && !aboveOrBelowAfter) { node.data.range.ascending = createHorizontalRange( nodeCoords, nextNodeCoords, true ); node.data.range.descending = createHorizontalRange( nodeCoords, void 0, false ); } }); } function handleNodeDragover(data) { const config = data.targetData.parent.data.config; if (!config.nativeDrag) return; data.e.preventDefault(); } function processParentDragEvent(e, targetData, state2, nativeDrag = false) { pd(e); if (nativeDrag && e instanceof PointerEvent) return; const { x, y } = eventCoordinates(e); const clientX = x; const clientY = y; const scrollLeft = window.scrollX || document.documentElement.scrollLeft; const scrollTop = window.scrollY || document.documentElement.scrollTop; state2.coordinates.x = clientX + scrollLeft; state2.coordinates.y = clientY + scrollTop; const nestedParent = targetData.parent.data.nestedParent; let realTargetParent = targetData.parent; if (nestedParent) { const rect = nestedParent.el.getBoundingClientRect(); if (state2.coordinates.y > rect.top && state2.coordinates.y < rect.bottom) realTargetParent = nestedParent; } if (realTargetParent.el === state2.currentParent?.el) { moveBetween(realTargetParent, state2); } else { moveOutside(realTargetParent, state2); } state2.currentParent = realTargetParent; } function handleParentDragover(data, state2) { processParentDragEvent(data.e, data.targetData, state2, true); } function handleParentPointerover(data) { const { detail } = data; const { state: state2, targetData } = detail; if (state2.scrolling) return; processParentDragEvent(detail.e, targetData, state2); } function moveBetween(data, state2) { 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, state2); 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( data, position, foundRange[1] === "ascending", foundRange[0], insertState ); } } function moveOutside(data, state2) { if (data.el === state2.currentParent.el) return false; const targetConfig = data.data.config; if (state2.draggedNode.el.contains(data.el)) return false; if (targetConfig.dropZone === false) return; 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.el.style.display = "none"; } else { removeClass([state2.currentParent.el], targetConfig.dropZoneClass); const enabledNodes = data.data.enabledNodes; const foundRange = findClosest(enabledNodes, state2); 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( data, position, foundRange[1] === "ascending", foundRange[0], insertState ); } } } function findClosest(enabledNodes, state2) { let foundRange = null; for (let x = 0; x < enabledNodes.length; x++) { if (!state2 || !enabledNodes[x].data.range) continue; if (enabledNodes[x].data.range.ascending) { if (state2.coordinates.y > enabledNodes[x].data.range.ascending.y[0] && state2.coordinates.y < enabledNodes[x].data.range.ascending.y[1] && state2.coordinates.x > enabledNodes[x].data.range.ascending.x[0] && state2.coordinates.x < enabledNodes[x].data.range.ascending.x[1]) { foundRange = [enabledNodes[x], "ascending"]; return foundRange; } } if (enabledNodes[x].data.range.descending) { if (state2.coordinates.y > enabledNodes[x].data.range.descending.y[0] && state2.coordinates.y < enabledNodes[x].data.range.descending.y[1] && state2.coordinates.x > enabledNodes[x].data.range.descending.x[0] && state2.coordinates.x < enabledNodes[x].data.range.descending.x[1]) { foundRange = [enabledNodes[x], "descending"]; return foundRange; } } } } function createInsertPoint(parent, insertState2) { const insertPoint = parent.data.config.insertConfig?.insertPoint({ el: parent.el, data: parent.data }); if (!insertPoint) throw new Error("Insert point not found", { cause: parent }); insertState2.insertPoint = { parent, el: insertPoint }; document.body.appendChild(insertPoint); Object.assign(insertPoint.style, { position: "absolute", display: "none" }); } function removeInsertPoint(insertState2) { if (insertState2.insertPoint?.el) insertState2.insertPoint.el.remove(); insertState2.insertPoint = null; } function positionInsertPoint(parent, position, ascending, node, insertState2) { if (insertState2.insertPoint?.el !== parent.el) { removeInsertPoint(insertState2); createInsertPoint(parent, insertState2); } insertState2.draggedOverNodes = [node]; if (!insertState2.insertPoint) return; insertState2.insertPoint.el.style.display = "block"; if (position.vertical) { const insertPointHeight = insertState2.insertPoint.el.getBoundingClientRect().height; const targetY = position.y[ascending ? 1 : 0]; const topPosition = targetY - insertPointHeight / 2; Object.assign(insertState2.insertPoint.el.style, { top: `${topPosition}px`, left: `${position.x[0]}px`, right: `${position.x[1]}px`, width: `${position.x[1] - position.x[0]}px` }); } else { const leftPosition = position.x[ascending ? 1 : 0] - insertState2.insertPoint.el.getBoundingClientRect().width / 2; insertState2.insertPoint.el.style.left = `${leftPosition}px`; Object.assign(insertState2.insertPoint.el.style, { top: `${position.y[0]}px`, bottom: `${position.y[1]}px`, height: `${position.y[1] - position.y[0]}px` }); } insertState2.targetIndex = node.data.index; insertState2.ascending = ascending; } function handleEnd(state2) { if (!isDragState(state2) && !isSynthDragState(state2)) return; const insertPoint = insertState.insertPoint; if (!insertState.draggedOverParent) { const draggedParentValues = parentValues( state2.initialParent.el, state2.initialParent.data ); const transferred = state2.initialParent.el !== state2.currentParent.el; remapNodes(state2.initialParent.el); const draggedValues = state2.draggedNodes.map((node) => node.data.value); const enabledNodes = [...state2.initialParent.data.enabledNodes]; const originalIndex = state2.draggedNodes[0].data.index; const targetIndex = insertState.targetIndex; if (!transferred && insertState.draggedOverNodes[0] && insertState.draggedOverNodes[0].el !== state2.draggedNodes[0].el) { const newParentValues = [ ...draggedParentValues.filter( (x) => !draggedValues.some((y) => eq(x, y)) ) ]; 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) { const sortEventData = { parent: { el: state2.initialParent.el, data: state2.initialParent.data }, previousValues: [...draggedParentValues], previousNodes: [...enabledNodes], nodes: [...state2.initialParent.data.enabledNodes], values: [...newParentValues], draggedNodes: state2.draggedNodes, targetNodes: insertState.draggedOverNodes, previousPosition: originalIndex, position: index, state: state2 }; state2.initialParent.data.config.onSort(sortEventData); } } else if (transferred && insertState.draggedOverNodes.length) { const draggedParentValues2 = parentValues( state2.initialParent.el, state2.initialParent.data ); let index = insertState.draggedOverNodes[0].data.index || 0; if (insertState.ascending) index++; const insertValues = state2.initialParent.data.config.insertConfig?.dynamicValues ? state2.initialParent.data.config.insertConfig.dynamicValues({ sourceParent: state2.initialParent, targetParent: state2.currentParent, draggedNodes: state2.draggedNodes, targetNodes: insertState.draggedOverNodes, targetIndex: index }) : draggedValues; const newParentValues = [ ...draggedParentValues2.filter( (x) => !draggedValues.some((y) => eq(x, y)) ) ]; if (state2.currentParent.el.contains(state2.initialParent.el)) { setParentValues(state2.initialParent.el, state2.initialParent.data, [ ...newParentValues ]); const targetParentValues = parentValues( state2.currentParent.el, state2.currentParent.data ); targetParentValues.splice(index, 0, ...insertValues); setParentValues(state2.currentParent.el, state2.currentParent.data, [ ...targetParentValues ]); } else { setParentValues(state2.initialParent.el, state2.initialParent.data, [ ...newParentValues ]); const targetParentValues = parentValues( state2.currentParent.el, state2.currentParent.data ); targetParentValues.splice(index, 0, ...insertValues); setParentValues(state2.currentParent.el, state2.currentParent.data, [ ...targetParentValues ]); } const data = { sourceParent: state2.initialParent, targetParent: state2.currentParent, initialParent: state2.initialParent, draggedNodes: state2.draggedNodes, targetIndex, targetNodes: insertState.draggedOverNodes, state: state2 }; if (state2.initialParent.data.config.onTransfer) state2.initialParent.data.config.onTransfer(data); if (state2.currentParent.data.config.onTransfer) state2.currentParent.data.config.onTransfer(data); } } else if (insertState.draggedOverParent) { if (state2.currentParent.el.contains(state2.initialParent.el)) { const draggedParentValues = parentValues( state2.initialParent.el, state2.initialParent.data ); const newParentValues = [ ...draggedParentValues.filter( (x) => !draggedValues.some((y) => eq(x, y)) ) ]; setParentValues(state2.initialParent.el, state2.initialParent.data, [ ...newParentValues ]); const draggedOverParentValues = parentValues( insertState.draggedOverParent.el, insertState.draggedOverParent.data ); const draggedValues = state2.draggedNodes.map((node) => node.data.value); const insertValues = state2.initialParent.data.config.insertConfig?.dynamicValues ? state2.initialParent.data.config.insertConfig.dynamicValues({ sourceParent: state2.initialParent, targetParent: state2.currentParent, draggedNodes: state2.draggedNodes, targetNodes: insertState.draggedOverNodes }) : draggedValues; draggedOverParentValues.push(...insertValues); setParentValues( insertState.draggedOverParent.el, insertState.draggedOverParent.data, [...draggedOverParentValues] ); } else { const draggedValues = state2.draggedNodes.map((node) => node.data.value); const draggedOverParentValues = parentValues( insertState.draggedOverParent.el, insertState.draggedOverParent.data ); const insertValues = state2.initialParent.data.config.insertConfig?.dynamicValues ? state2.initialParent.data.config.insertConfig.dynamicValues({ sourceParent: state2.initialParent, targetParent: state2.currentParent, draggedNodes: state2.draggedNodes, targetNodes: insertState.draggedOverNodes }) : draggedValues; draggedOverParentValues.push(...insertValues); setParentValues( insertState.draggedOverParent.el, insertState.draggedOverParent.data, [...draggedOverParentValues] ); const draggedParentValues = parentValues( state2.initialParent.el, state2.initialParent.data ); const newParentValues = [ ...draggedParentValues.filter( (x) => !draggedValues.some((y) => eq(x, y)) ) ]; 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.el.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 ); if (state2.initialParent.data.config.onSort) { state2.initialParent.data.config.onSort({ parent: { el: state2.initialParent.el, data: state2.initialParent.data }, previousValues: [...initialParentValues], previousNodes: [...state2.initialParent.data.enabledNodes], nodes: [...state2.initialParent.data.enabledNodes], values: [...newValues], draggedNodes: state2.draggedNodes, previousPosition: draggedIndex, position: targetIndex, targetNodes: dropSwapState.draggedOverNodes, state: state2 }); } } 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 ); } } if (state2.currentParent.data.config.onTransfer) { state2.currentParent.data.config.onTransfer({ sourceParent: state2.currentParent, targetParent: state2.initialParent, initialParent: state2.initialParent, draggedNodes: state2.draggedNodes, targetIndex, state: state2, targetNodes: dropSwapState.draggedOverNodes }); } if (state2.initialParent.data.config.onTransfer) { state2.initialParent.data.config.onTransfer({ sourceParent: state2.initialParent, targetParent: state2.currentParent, initialParent: state2.initialParent, draggedNodes: state2.draggedNodes, targetIndex, state: state2, targetNodes: dropSwapState.draggedOverNodes }); } } // src/index.ts var isBrowser = typeof window !== "undefined"; var parents = /* @__PURE__ */ new WeakMap(); var nodes = /* @__PURE__ */ new WeakMap(); function isMobilePlatform() { if (!isBrowser) return false; if ("userAgentData" in navigator) { return navigator.userAgentData.mobile === true; } const ua = navigator.userAgent; const isMobileUA = /android|iphone|ipod/i.test(ua); const isIpad = /iPad/.test(ua) || ua.includes("Macintosh") && navigator.maxTouchPoints > 1; return isMobileUA || isIpad; } var baseDragState = { affectedNodes: [], coordinates: { x: 0, y: 0 }, currentTargetValue: void 0, on, emit, originalZIndex: void 0, pointerSelection: false, preventEnter: false, rootUserSelect: void 0, nodePointerdown: void 0, longPress: false, scrolling: false, longPressTimeout: void 0, remapJustFinished: false, selectedNodes: [], selectedParent: void 0, preventSynthDrag: false, pointerDown: void 0, lastScrollContainerX: null, lastScrollContainerY: null, rootScrollWidth: void 0, rootScrollHeight: void 0, dragItemRect: void 0, windowScrollX: void 0, windowScrollY: void 0, lastScrollDirectionX: void 0, lastScrollDirectionY: void 0, scrollDebounceTimeout: void 0, frameIdX: void 0, frameIdY: void 0 }; var state = baseDragState; var dropped = false; var documentController3; var scrollTimeout; function resetState() { if (state.scrollDebounceTimeout) { clearTimeout(state.scrollDebounceTimeout); } if (state.longPressTimeout) { clearTimeout(state.longPressTimeout); } if (state.frameIdX !== void 0) { cancelAnimationFrame(state.frameIdX); } if (state.frameIdY !== void 0) { cancelAnimationFrame(state.frameIdY); } const baseDragState2 = { affectedNodes: [], coordinates: { x: 0, y: 0 }, on, emit, currentTargetValue: void 0, originalZIndex: void 0, pointerId: void 0, preventEnter: false, remapJustFinished: false, selectedNodes: [], nodePointerdown: void 0, rootUserSelect: void 0, preventSynthDrag: false, scrolling: false, selectedParent: void 0, pointerSelection: false, synthScrollDirection: void 0, draggedNodeDisplay: void 0, synthDragScrolling: false, longPress: false, pointerDown: void 0, longPressTimeout: void 0, lastScrollContainerX: null, lastScrollContainerY: null, rootScrollWidth: void 0, rootScrollHeight: void 0, dragItemRect: void 0, windowScrollX: void 0, windowScrollY: void 0, lastScrollDirectionX: void 0, lastScrollDirectionY: void 0, scrollDebounceTimeout: void 0, frameIdX: void 0, frameIdY: void 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() { 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() { if (state.pointerDown) state.pointerDown.node.el.draggable = true; state.pointerDown = void 0; 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) { if (!isDragState(state)) return; dropped = true; const handleEnd4 = state.initialParent.data.config.handleEnd; handleEnd4(state); } function handleRootDragover(e) { if (!isDragState(state)) return; pd(e); const { x, y } = eventCoordinates(e); if (isDragState(state)) { handleSynthScroll({ x, y }, e, state); } } function handleRootPointermove(e) { if (!state.pointerDown || !state.pointerDown.validated) return; const config = state.pointerDown.parent.data.config; if (e.pointerType === "mouse" && !isMobilePlatform()) { return; } if (!isSynthDragState(state)) { pd(e); if (config.longPress && !state.longPress) { clearTimeout(state.longPressTimeout); state.longPress = false; return; } const nodes2 = config.draggedNodes(state.pointerDown); config.dragstartClasses(state.pointerDown.node, nodes2, config, true); const rect = state.pointerDown.node.el.getBoundingClientRect(); const synthDragState = initSynthDrag( state.pointerDown.node, state.pointerDown.parent, e, state, nodes2, rect ); synthMove(e, synthDragState, true); } else if (isSynthDragState(state)) { synthMove(e, state); } } function dragAndDrop({ parent, getValues, setValues, config = {} }) { if (!isBrowser) return; if (!documentController3) { documentController3 = addEvents(document, { dragover: handleRootDragover, pointerdown: handleRootPointerdown, pointerup: handleRootPointerup, keydown: handleRootKeydown, drop: handleRootDrop, pointermove: handleRootPointermove, pointercancel: nodeEventData(config.handlePointercancel), touchmove: (e) => { if (isDragState(state) && e.cancelable) pd(e); }, contextmenu: (e) => { if (isSynthDragState(state)) pd(e); } }); } tearDown(parent); const [emit2, on2] = createEmitter(); const parentData = { getValues, setValues, config: { dragDropEffect: config.dragDropEffect ?? "move", dragEffectAllowed: config.dragEffectAllowed ?? "move", draggedNodes, dragstartClasses, handleNodeKeydown, handleDragstart, handleNodeDragover: handleNodeDragover3, handleParentDragover: handleParentDragover3, handleNodeDrop, handleNodeFocus, handleNodeBlur, handlePointercancel, handleEnd: handleEnd3, handleDragend, handleParentFocus, handleNodePointerup, handleNodePointerover: handleNodePointerover2, handleParentPointerover: handleParentPointerover2, handleParentScroll, handleNodePointerdown, handleNodeDragenter, handleNodeDragleave, handleParentDrop, multiDrag: config.multiDrag ?? false, nativeDrag: config.nativeDrag ?? true, performSort, performTransfer, root: config.root ?? document, setupNode, setupNodeRemap, reapplyDragClasses, tearDownNode, tearDownNodeRemap, remapFinished, 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); 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(node, parent, e, draggedNodes2, offsetX, offsetY) { const { x, y } = eventCoordinates(e); const rect = node.el.getBoundingClientRect(); return { affectedNodes: [], ascendingDirection: false, clonedDraggedEls: [], coordinates: { x, y }, draggedNode: { el: node.el, data: node.data }, draggedNodes: draggedNodes2, incomingDirection: void 0, initialIndex: node.data.index, initialParent: { el: parent.el, data: parent.data }, currentParent: { el: parent.el, data: parent.data }, longPress: parent.data.config.longPress ?? false, longPressTimeout: void 0, currentTargetValue: node.data.value, scrollEls: [], startLeft: offsetX ? offsetX : x