UNPKG

@formkit/drag-and-drop

Version:

Drag and drop package.

1 lines 171 kB
{"version":3,"sources":["../src/plugins/animations/index.ts","../src/plugins/insert/index.ts","../src/plugins/drop-or-swap/index.ts","../src/index.ts"],"sourcesContent":["import type { SetupNodeData, Node } from \"../../types\";\nimport type { AnimationsConfig } from \"./types\";\nimport { state, parents, isDragState } from \"../../index\";\n\nexport function animations(animationsConfig: Partial<AnimationsConfig> = {}) {\n const slideUp = [\n {\n transform: `translateY(${animationsConfig.yScale || 50}%)`,\n },\n {\n transform: `translateY(${animationsConfig.yScale || 0}%)`,\n },\n ];\n\n const slideDown = [\n {\n transform: `translateY(-${animationsConfig.yScale || 50}%)`,\n },\n {\n transform: `translateY(${animationsConfig.yScale || 0}%)`,\n },\n ];\n\n const slideLeft = [\n {\n transform: `translateX(${animationsConfig.xScale || 50}%)`,\n },\n {\n transform: `translateX(${animationsConfig.xScale || 0}%)`,\n },\n ];\n\n const slideRight = [\n {\n transform: `translateX(-${animationsConfig.xScale || 50}%)`,\n },\n {\n transform: `translateX(${animationsConfig.xScale || 0}%)`,\n },\n ];\n return (parent: HTMLElement) => {\n const parentData = parents.get(parent);\n\n if (!parentData) return;\n\n return {\n setup() {\n if (document.head.querySelector(\"[data-drag-and-drop]\")) return;\n },\n\n setupNodeRemap<T>(data: SetupNodeData<T>) {\n if (!isDragState(state)) return;\n\n const duration = animationsConfig.duration || 150;\n\n if (data.node.data.value === state.draggedNode.data.value) {\n switch (state.incomingDirection) {\n case \"below\":\n animate(data.node.el, slideUp, duration);\n\n break;\n case \"above\":\n animate(data.node.el, slideDown, duration);\n\n break;\n case \"left\":\n animate(data.node.el, slideRight, duration);\n\n break;\n case \"right\":\n animate(data.node.el, slideLeft, duration);\n\n break;\n }\n\n return;\n }\n\n if (\n !state.affectedNodes\n .map((x) => x.data.value)\n .includes(data.node.data.value)\n )\n return;\n\n const nodeRect = data.node.el.getBoundingClientRect();\n\n const nodeIndex = state.affectedNodes.findIndex(\n (x) => x.data.value === data.node.data.value\n );\n\n const draggedNodeIndex = state.draggedNode.data.index;\n\n const ascendingDirection = draggedNodeIndex >= state.targetIndex;\n\n let adjacentNode;\n\n if (ascendingDirection) {\n adjacentNode = state.affectedNodes[nodeIndex + 1]\n ? state.affectedNodes[nodeIndex + 1]\n : state.affectedNodes[nodeIndex - 1];\n } else {\n adjacentNode = state.affectedNodes[nodeIndex - 1]\n ? state.affectedNodes[nodeIndex - 1]\n : state.affectedNodes[nodeIndex + 1];\n }\n\n if (adjacentNode) {\n const xDiff = Math.abs(\n nodeRect.x - adjacentNode.el.getBoundingClientRect().x\n );\n\n const yDiff = Math.abs(\n nodeRect.y - adjacentNode.el.getBoundingClientRect().y\n );\n\n if (xDiff > yDiff && ascendingDirection) {\n animate(data.node.el, slideRight, duration);\n } else if (xDiff > yDiff && !ascendingDirection) {\n animate(data.node.el, slideLeft, duration);\n }\n } else {\n switch (state.incomingDirection) {\n case \"below\":\n animate(data.node.el, slideDown, duration);\n\n break;\n case \"above\":\n animate(data.node.el, slideUp, duration);\n\n break;\n case \"left\":\n animate(data.node.el, slideLeft, duration);\n\n break;\n case \"right\":\n animate(data.node.el, slideRight, duration);\n\n break;\n }\n }\n },\n };\n };\n}\n\nfunction animate(\n node: Node,\n animation: Keyframe[] | PropertyIndexedKeyframes,\n duration: number\n) {\n if (!state) return;\n\n state.preventEnter = true;\n\n node.animate(animation, {\n duration: duration,\n easing: \"ease-in-out\",\n });\n\n setTimeout(() => {\n if (!state) return;\n\n state.preventEnter = false;\n }, duration);\n}\n","import type { InsertConfig } from \"../../types\";\nimport type {\n DragState,\n NodeDragEventData,\n NodeRecord,\n ParentEventData,\n PointeroverParentEvent,\n ParentRecord,\n SynthDragState,\n InsertEvent,\n InsertState,\n} from \"../../types\";\n\nimport {\n parents,\n parentValues,\n setParentValues,\n state,\n addParentClass,\n isDragState,\n isSynthDragState,\n eventCoordinates,\n removeClass,\n addEvents,\n} from \"../../index\";\n\nexport const insertState: InsertState<unknown> = {\n draggedOverNodes: [],\n draggedOverParent: null,\n targetIndex: 0,\n ascending: false,\n coordinates: { x: 0, y: 0 },\n insertPoint: null,\n dragging: false,\n};\n\nlet documentController: AbortController | undefined;\n\n// WIP: This is a work in progress and not yet fully functional\nexport function insert<T>(insertConfig: InsertConfig<T>) {\n return (parent: HTMLElement) => {\n const parentData = parents.get(parent);\n\n if (!parentData) return;\n\n const insertParentConfig = {\n ...parentData.config,\n insertConfig,\n };\n\n return {\n teardown() {\n if (parentData.abortControllers.root) {\n parentData.abortControllers.root.abort();\n }\n },\n setup() {\n insertParentConfig.handleNodeDragover =\n insertConfig.handleNodeDragover || handleNodeDragover;\n\n insertParentConfig.handleParentPointerover =\n insertConfig.handleParentPointerover || handleParentPointerover;\n\n insertParentConfig.handleNodePointerover =\n insertConfig.handleNodePointerover || handleParentPointerover;\n\n insertParentConfig.handleParentDragover =\n insertConfig.handleParentDragover || handleParentDragover;\n\n const originalHandleend = insertParentConfig.handleEnd;\n\n insertParentConfig.handleEnd = (\n state: DragState<T> | SynthDragState<T>\n ) => {\n handleEnd(state);\n\n originalHandleend(state);\n };\n\n parentData.on(\"dragStarted\", () => {\n documentController = addEvents(document, {\n dragover: checkPosition,\n pointermove: checkPosition,\n });\n });\n\n parentData.on(\"dragEnded\", () => {\n documentController?.abort();\n });\n\n parentData.config = insertParentConfig;\n\n state.on(\"dragStarted\", () => {\n const insertPoint = parentData.config.insertConfig?.insertPoint({\n el: parent,\n data: parentData,\n });\n\n if (!insertPoint) return;\n\n if (!document.body.contains(insertPoint))\n document.body.appendChild(insertPoint);\n\n Object.assign(insertPoint, {\n position: \"absolute\",\n display: \"none\",\n });\n\n insertState.insertPoint = insertPoint;\n });\n\n window.addEventListener(\"scroll\", defineRanges.bind(null, parent));\n\n window.addEventListener(\"resize\", defineRanges.bind(null, parent));\n },\n\n remapFinished() {\n defineRanges(parent);\n },\n };\n };\n}\n\nfunction checkPosition(e: DragEvent | PointerEvent) {\n if (!isDragState(state)) return;\n\n const el = document.elementFromPoint(e.clientX, e.clientY);\n\n if (!(el instanceof HTMLElement)) return;\n\n if (!parents.has(el)) {\n const insertPoint = insertState.insertPoint;\n\n if (insertPoint && insertPoint === el) return;\n\n if (insertPoint) insertPoint.style.display = \"none\";\n\n if (insertState.draggedOverParent) {\n removeClass(\n [insertState.draggedOverParent.el],\n insertState.draggedOverParent.data.config.dropZoneClass\n );\n }\n\n insertState.draggedOverNodes = [];\n\n insertState.draggedOverParent = null;\n\n state.currentParent = state.initialParent;\n }\n}\n\nfunction ascendingVertical(\n nodeCoords: Coordinates,\n nextNodeCoords?: Coordinates\n) {\n const center = nodeCoords.top + nodeCoords.height / 2;\n\n if (!nextNodeCoords) {\n return {\n y: [center, center + nodeCoords.height / 2 + 10],\n x: [nodeCoords.left, nodeCoords.right],\n vertical: true,\n };\n }\n\n return {\n y: [\n center,\n nodeCoords.bottom + Math.abs(nodeCoords.bottom - nextNodeCoords.top) / 2,\n ],\n x: [nodeCoords.left, nodeCoords.right],\n vertical: true,\n };\n}\n\nfunction descendingVertical(\n nodeCoords: Coordinates,\n prevNodeCoords?: Coordinates\n) {\n const center = nodeCoords.top + nodeCoords.height / 2;\n\n if (!prevNodeCoords) {\n return {\n y: [center - nodeCoords.height / 2 - 10, center],\n x: [nodeCoords.left, nodeCoords.right],\n vertical: true,\n };\n }\n\n return {\n y: [\n prevNodeCoords.bottom +\n Math.abs(prevNodeCoords.bottom - nodeCoords.top) / 2,\n center,\n ],\n x: [nodeCoords.left, nodeCoords.right],\n vertical: true,\n };\n}\n\nfunction ascendingHorizontal(\n nodeCoords: Coordinates,\n nextNodeCoords?: Coordinates,\n lastInRow = false\n) {\n const center = nodeCoords.left + nodeCoords.width / 2;\n\n if (!nextNodeCoords) {\n return {\n x: [center, center + nodeCoords.width],\n y: [nodeCoords.top, nodeCoords.bottom],\n vertical: false,\n };\n }\n\n if (lastInRow) {\n return {\n x: [center, nodeCoords.right + 10],\n y: [nodeCoords.top, nodeCoords.bottom],\n vertical: false,\n };\n } else {\n const nextNodeCenter = nextNodeCoords.left + nextNodeCoords.width / 2;\n\n return {\n x: [center, center + Math.abs(center - nextNodeCenter) / 2],\n y: [nodeCoords.top, nodeCoords.bottom],\n vertical: false,\n };\n }\n}\n\nfunction descendingHorizontal(\n nodeCoords: Coordinates,\n prevNodeCoords?: Coordinates\n) {\n const center = nodeCoords.left + nodeCoords.width / 2;\n\n if (!prevNodeCoords) {\n return {\n x: [nodeCoords.left - 10, center],\n y: [nodeCoords.top, nodeCoords.bottom],\n vertical: false,\n };\n }\n\n return {\n x: [\n prevNodeCoords.right +\n Math.abs(prevNodeCoords.right - nodeCoords.left) / 2,\n center,\n ],\n y: [nodeCoords.top, nodeCoords.bottom],\n vertical: false,\n };\n}\n\ninterface Coordinates {\n top: number;\n bottom: number;\n left: number;\n right: number;\n height: number;\n width: number;\n}\n\nfunction getRealCoords(el: HTMLElement): Coordinates {\n const { top, bottom, left, right, height, width } =\n el.getBoundingClientRect();\n\n const scrollLeft = window.scrollX || document.documentElement.scrollLeft;\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n\n const adjustedTop = top + scrollTop;\n const adjustedBottom = bottom + scrollTop;\n const adjustedLeft = left + scrollLeft;\n const adjustedRight = right + scrollLeft;\n\n return {\n top: adjustedTop,\n bottom: adjustedBottom,\n left: adjustedLeft,\n right: adjustedRight,\n height,\n width,\n };\n}\n\nfunction defineRanges(parent: HTMLElement) {\n const parentData = parents.get(parent);\n\n if (!parentData) return;\n\n const enabledNodes = parentData.enabledNodes;\n\n enabledNodes.forEach((node, index) => {\n node.data.range = {};\n\n let aboveOrBelowPrevious = false;\n\n let aboveOrBelowAfter = false;\n\n let prevNodeCoords = undefined;\n\n let nextNodeCoords = undefined;\n\n if (enabledNodes[index - 1])\n prevNodeCoords = getRealCoords(enabledNodes[index - 1].el);\n\n if (enabledNodes[index + 1])\n nextNodeCoords = getRealCoords(enabledNodes[index + 1].el);\n\n const nodeCoords = getRealCoords(node.el);\n\n if (prevNodeCoords) {\n aboveOrBelowPrevious =\n nodeCoords.top > prevNodeCoords.bottom ||\n nodeCoords.bottom < prevNodeCoords.top;\n }\n\n if (nextNodeCoords) {\n aboveOrBelowAfter =\n nodeCoords.top > nextNodeCoords.bottom ||\n nodeCoords.bottom < nextNodeCoords.top;\n }\n\n const fullishWidth =\n parent.getBoundingClientRect().width * 0.8 < nodeCoords.width;\n\n if (fullishWidth) {\n node.data.range.ascending = ascendingVertical(nodeCoords, nextNodeCoords);\n node.data.range.descending = descendingVertical(\n nodeCoords,\n prevNodeCoords\n );\n } else if (aboveOrBelowAfter && !aboveOrBelowPrevious) {\n node.data.range.ascending = ascendingHorizontal(\n nodeCoords,\n nextNodeCoords,\n true\n );\n node.data.range.descending = descendingHorizontal(\n nodeCoords,\n prevNodeCoords\n );\n } else if (!aboveOrBelowPrevious && !aboveOrBelowAfter) {\n node.data.range.ascending = ascendingHorizontal(\n nodeCoords,\n nextNodeCoords\n );\n node.data.range.descending = descendingHorizontal(\n nodeCoords,\n prevNodeCoords\n );\n } else if (aboveOrBelowPrevious && !nextNodeCoords) {\n node.data.range.ascending = ascendingHorizontal(nodeCoords);\n } else if (aboveOrBelowPrevious && !aboveOrBelowAfter) {\n node.data.range.ascending = ascendingHorizontal(\n nodeCoords,\n nextNodeCoords\n );\n\n node.data.range.descending = descendingHorizontal(nodeCoords);\n }\n });\n}\n\nexport function handleNodeDragover<T>(data: NodeDragEventData<T>) {\n data.e.preventDefault();\n}\n\nexport function handleParentDragover<T>(\n data: ParentEventData<T>,\n state: DragState<T>\n) {\n if (!state || !insertState) return;\n\n data.e.stopPropagation();\n\n data.e.preventDefault();\n\n const { x, y } = eventCoordinates(data.e as DragEvent | PointerEvent);\n\n // Get the client coordinates\n const clientX = x;\n const clientY = y;\n\n // Get the scroll positions\n const scrollLeft = window.scrollX || document.documentElement.scrollLeft;\n const scrollTop = window.scrollY || document.documentElement.scrollTop;\n\n // Calculate the coordinates relative to the entire document\n insertState.coordinates.x = clientX + scrollLeft;\n insertState.coordinates.y = clientY + scrollTop;\n\n const nestedParent = data.targetData.parent.data.nestedParent;\n\n let realTargetParent = data.targetData.parent;\n\n if (nestedParent) {\n const rect = nestedParent.el.getBoundingClientRect();\n\n if (\n insertState.coordinates.y > rect.top &&\n insertState.coordinates.y < rect.bottom\n )\n realTargetParent = nestedParent;\n }\n\n realTargetParent.el === state.currentParent?.el\n ? moveBetween(realTargetParent)\n : moveOutside(realTargetParent, state);\n\n state.currentParent = realTargetParent;\n}\n\nexport function moveBetween<T>(data: ParentRecord<T>) {\n if (data.data.config.sortable === false) return;\n\n if (\n data.el === insertState.draggedOverParent?.el &&\n insertState.draggedOverParent.data.getValues(data.el).length === 0\n ) {\n return;\n } else if (insertState.draggedOverParent?.el) {\n removeClass(\n [insertState.draggedOverParent.el],\n insertState.draggedOverParent.data.config.dropZoneClass\n );\n insertState.draggedOverParent = null;\n }\n\n const foundRange = findClosest(data.data.enabledNodes);\n\n if (!foundRange) return;\n\n const key = foundRange[1] as \"ascending\" | \"descending\";\n\n if (foundRange) {\n const position = foundRange[0].data.range\n ? foundRange[0].data.range[key]\n : undefined;\n\n if (position)\n positioninsertPoint(\n position,\n foundRange[1] === \"ascending\",\n foundRange[0]\n );\n }\n}\n\nfunction moveOutside<T>(data: ParentRecord<T>, state: DragState<T>) {\n if (data.el === state.currentParent.el) return false;\n\n const targetConfig = data.data.config;\n\n if (targetConfig.treeGroup && state.draggedNode.el.contains(data.el))\n return false;\n\n if (targetConfig.dropZone === false) return false;\n\n const initialParentConfig = state.initialParent.data.config;\n\n if (targetConfig.accepts) {\n return targetConfig.accepts(\n data,\n state.initialParent,\n state.currentParent,\n state\n );\n } else if (\n !targetConfig.group ||\n targetConfig.group !== initialParentConfig.group\n ) {\n return false;\n }\n\n const values = data.data.getValues(data.el);\n\n if (!values.length) {\n addParentClass([data.el], targetConfig.dropZoneClass);\n\n insertState.draggedOverParent = data as ParentRecord<unknown>;\n\n const insertPoint = insertState.insertPoint;\n\n if (insertPoint) insertPoint.style.display = \"none\";\n } else {\n removeClass([state.currentParent.el], targetConfig.dropZoneClass);\n\n const enabledNodes = data.data.enabledNodes;\n\n const foundRange = findClosest(enabledNodes);\n\n if (!foundRange) return;\n\n const key = foundRange[1] as \"ascending\" | \"descending\";\n\n if (foundRange) {\n const position = foundRange[0].data.range\n ? foundRange[0].data.range[key]\n : undefined;\n\n if (position)\n positioninsertPoint(\n position,\n foundRange[1] === \"ascending\",\n foundRange[0]\n );\n }\n }\n}\n\nfunction findClosest<T>(enabledNodes: NodeRecord<T>[]) {\n let foundRange: [NodeRecord<T>, string] | null = null;\n\n for (let x = 0; x < enabledNodes.length; x++) {\n if (!state || !enabledNodes[x].data.range) continue;\n\n if (enabledNodes[x].data.range!.ascending) {\n if (\n insertState.coordinates.y >\n enabledNodes[x].data.range!.ascending!.y[0] &&\n insertState.coordinates.y <\n enabledNodes[x].data.range!.ascending!.y[1] &&\n insertState.coordinates.x >\n enabledNodes[x].data.range!.ascending!.x[0] &&\n insertState.coordinates.x < enabledNodes[x].data.range!.ascending!.x[1]\n ) {\n foundRange = [enabledNodes[x], \"ascending\"];\n\n return foundRange;\n }\n }\n\n if (enabledNodes[x].data.range!.descending) {\n if (\n insertState.coordinates.y >\n enabledNodes[x].data.range!.descending!.y[0] &&\n insertState.coordinates.y <\n enabledNodes[x].data.range!.descending!.y[1] &&\n insertState.coordinates.x >\n enabledNodes[x].data.range!.descending!.x[0] &&\n insertState.coordinates.x < enabledNodes[x].data.range!.descending!.x[1]\n ) {\n foundRange = [enabledNodes[x], \"descending\"];\n\n return foundRange;\n }\n }\n }\n}\n\nexport function handleParentPointerover<T>(\n data: PointeroverParentEvent<T>,\n state: SynthDragState<T>\n) {\n data.detail.e.stopPropagation();\n\n const { x, y } = eventCoordinates(data.detail.e as PointerEvent);\n\n state.coordinates.y = y;\n\n state.coordinates.x = x;\n\n const nestedParent = data.detail.targetData.parent.data.nestedParent;\n\n let realTargetParent = data.detail.targetData.parent;\n\n if (nestedParent) {\n const rect = nestedParent.el.getBoundingClientRect();\n\n if (state.coordinates.y > rect.top && state.coordinates.y < rect.bottom)\n realTargetParent = nestedParent;\n }\n\n const enabledNodes = realTargetParent.data.enabledNodes;\n\n const foundRange = findClosest(enabledNodes);\n\n if (!foundRange) return;\n\n const key = foundRange[1] as \"ascending\" | \"descending\";\n\n if (foundRange) {\n const position = foundRange[0].data.range\n ? foundRange[0].data.range[key]\n : undefined;\n\n if (position)\n positioninsertPoint(\n position,\n foundRange[1] === \"ascending\",\n foundRange[0]\n );\n }\n\n data.detail.targetData.parent.el === state.currentParent.el\n ? moveBetween(realTargetParent)\n : moveOutside(realTargetParent, state);\n}\n\nfunction positioninsertPoint<T>(\n position: { x: number[]; y: number[]; vertical: boolean },\n ascending: boolean,\n node: NodeRecord<T>\n) {\n if (!state) return;\n\n insertState.draggedOverNodes = [node];\n\n if (!insertState.insertPoint) return;\n\n if (position.vertical) {\n const topPosition =\n position.y[ascending ? 1 : 0] -\n insertState.insertPoint.getBoundingClientRect().height / 2;\n\n insertState.insertPoint.style.top = `${topPosition}px`;\n\n const leftCoordinate = position.x[0];\n\n const rightCoordinate = position.x[1];\n\n insertState.insertPoint.style.left = `${leftCoordinate}px`;\n\n insertState.insertPoint.style.right = `${rightCoordinate}px`;\n\n insertState.insertPoint.style.height = \"4px\";\n\n insertState.insertPoint.style.width =\n rightCoordinate - leftCoordinate + \"px\";\n } else {\n const leftPosition =\n position.x[ascending ? 1 : 0] -\n insertState.insertPoint.getBoundingClientRect().width / 2;\n insertState.insertPoint.style.left = `${leftPosition}px`;\n\n const topCoordinate = position.y[0];\n\n const bottomCoordinate = position.y[1];\n\n insertState.insertPoint.style.top = `${topCoordinate}px`;\n\n insertState.insertPoint.style.bottom = `${bottomCoordinate}px`;\n\n insertState.insertPoint.style.width = \"4px\";\n\n insertState.insertPoint.style.height =\n bottomCoordinate - topCoordinate + \"px\";\n }\n\n insertState.targetIndex = node.data.index;\n\n insertState.ascending = ascending;\n\n insertState.insertPoint.style.display = \"block\";\n}\n\nexport function handleParentDrop<T>(_data: NodeDragEventData<T>) {}\n\nexport function handleEnd<T>(state: DragState<T> | SynthDragState<T>) {\n const insertPoint = insertState.insertPoint;\n\n if (!insertState.draggedOverParent) {\n const draggedParentValues = parentValues(\n state.initialParent.el,\n state.initialParent.data\n );\n\n const transferred = state.initialParent.el !== state.currentParent.el;\n\n const draggedValues = state.draggedNodes.map((node) => node.data.value);\n\n //const originalIndex = state.draggedNodes[0].data.index;\n\n //const targetIndex = insertState.targetIndex;\n\n if (\n !transferred &&\n insertState.draggedOverNodes[0] &&\n insertState.draggedOverNodes[0].el !== state.draggedNodes[0].el\n ) {\n const newParentValues = [\n ...draggedParentValues.filter((x) => !draggedValues.includes(x)),\n ];\n\n let index = insertState.draggedOverNodes[0].data.index;\n\n if (\n insertState.targetIndex > state.draggedNodes[0].data.index &&\n !insertState.ascending\n ) {\n index--;\n } else if (\n insertState.targetIndex < state.draggedNodes[0].data.index &&\n insertState.ascending\n ) {\n index++;\n }\n\n newParentValues.splice(index, 0, ...draggedValues);\n\n setParentValues(state.initialParent.el, state.initialParent.data, [\n ...newParentValues,\n ]);\n\n if (state.initialParent.data.config.onSort) {\n //const sortEventData = {\n // parent: {\n // state.initialParent.el,\n // state.initialParent.data,\n // },\n // previousValues: [...draggedParentValues],\n // state.initialParent.data.enabledNodes],\n // values: [...newParentValues],\n // draggedNode: state.draggedNode,\n // previousPosition: originalIndex,\n // position: index,\n //};\n //state.initialParent.data.config.onSort(sortEventData);\n }\n } else if (transferred && insertState.draggedOverNodes.length) {\n const targetParentValues = parentValues(\n state.currentParent.el,\n state.currentParent.data\n );\n const draggedParentValues = parentValues(\n state.initialParent.el,\n state.initialParent.data\n );\n\n // For the time being, we will not be remoing the value of the original dragged parent.\n let index = insertState.draggedOverNodes[0].data.index || 0;\n\n if (insertState.ascending) index++;\n\n const insertValues = state.dynamicValues.length\n ? state.dynamicValues\n : draggedValues;\n\n targetParentValues.splice(index, 0, ...insertValues);\n\n setParentValues(state.currentParent.el, state.currentParent.data, [\n ...targetParentValues,\n ]);\n\n draggedParentValues.splice(state.initialIndex, draggedValues.length);\n\n setParentValues(state.initialParent.el, state.initialParent.data, [\n ...draggedParentValues,\n ]);\n\n const data: InsertEvent<T> = {\n sourceParent: state.initialParent,\n targetParent: state.currentParent,\n draggedNodes: state.draggedNodes,\n targetNodes: insertState.draggedOverNodes as NodeRecord<T>[],\n state,\n };\n\n if (state.initialParent.data.config.insertConfig?.insertEvent)\n state.initialParent.data.config.insertConfig.insertEvent(data);\n if (state.currentParent.data.config.insertConfig?.insertEvent)\n state.currentParent.data.config.insertConfig.insertEvent(data);\n }\n } else if (insertState.draggedOverParent) {\n const draggedValues = state.draggedNodes.map((node) => node.data.value);\n\n const draggedParentValues = parentValues(\n state.initialParent.el,\n state.initialParent.data\n );\n const newParentValues = [\n ...draggedParentValues.filter((x) => !draggedValues.includes(x)),\n ];\n const draggedOverParentValues = parentValues(\n insertState.draggedOverParent.el,\n insertState.draggedOverParent.data\n );\n\n const insertValues = state.dynamicValues.length\n ? state.dynamicValues\n : draggedValues;\n\n draggedOverParentValues.push(...insertValues);\n\n setParentValues(\n insertState.draggedOverParent.el,\n insertState.draggedOverParent.data,\n [...draggedOverParentValues]\n );\n\n setParentValues(state.initialParent.el, state.initialParent.data, [\n ...newParentValues,\n ]);\n\n const data: InsertEvent<T> = {\n sourceParent: state.initialParent,\n targetParent: state.currentParent,\n draggedNodes: state.draggedNodes,\n targetNodes: insertState.draggedOverNodes as NodeRecord<T>[],\n state,\n };\n\n if (state.initialParent.data.config.insertConfig?.insertEvent)\n state.initialParent.data.config.insertConfig.insertEvent(data);\n if (state.currentParent.data.config.insertConfig?.insertEvent)\n state.currentParent.data.config.insertConfig.insertEvent(data);\n\n removeClass(\n [insertState.draggedOverParent.el],\n insertState.draggedOverParent.data.config.dropZoneClass\n );\n }\n\n if (insertPoint) insertPoint.style.display = \"none\";\n\n const dropZoneClass = isSynthDragState(state)\n ? state.initialParent.data.config.synthDropZoneClass\n : state.initialParent.data.config.dropZoneClass;\n\n removeClass(\n insertState.draggedOverNodes.map((node) => node.el),\n dropZoneClass\n );\n\n const dragPlaceholderClass =\n state.initialParent.data.config.dragPlaceholderClass;\n\n removeClass(\n state.draggedNodes.map((node) => node.el),\n dragPlaceholderClass\n );\n\n insertState.draggedOverNodes = [];\n\n insertState.draggedOverParent = null;\n}\n","import type {\n DropSwapConfig,\n NodeDragEventData,\n DragState,\n SynthDragState,\n NodeRecord,\n PointeroverNodeEvent,\n ParentDragEventData,\n PointeroverParentEvent,\n DropSwapState,\n} from \"../../types\";\nimport {\n parents,\n parentValues,\n setParentValues,\n addNodeClass,\n isSynthDragState,\n removeClass,\n addClass,\n state,\n addEvents,\n isDragState,\n} from \"../../index\";\n\nexport const dropSwapState: DropSwapState = {\n draggedOverNodes: Array<NodeRecord<unknown>>(),\n initialDraggedIndex: undefined,\n transferred: false,\n dragging: false,\n};\n\n/**\n * Abort controller for the document.\n */\nlet documentController: AbortController | undefined;\n\nexport function dropOrSwap<T>(dropSwapConfig: DropSwapConfig<T> = {}) {\n return (parent: HTMLElement) => {\n const parentData = parents.get(parent);\n\n if (!parentData) return;\n\n const dropSwapParentConfig = {\n ...parentData.config,\n dropSwapConfig,\n };\n\n return {\n setup() {\n dropSwapParentConfig.handleNodeDragover =\n dropSwapConfig.handleNodeDragover || handleNodeDragover;\n\n dropSwapParentConfig.handleParentDragover =\n dropSwapConfig.handleParentDragover || handleParentDragover;\n\n dropSwapParentConfig.handleNodePointerover =\n dropSwapConfig.handleNodePointerover || handleNodePointerover;\n\n dropSwapParentConfig.handleParentPointerover =\n dropSwapConfig.handleParentPointerover || handeParentPointerover;\n\n const originalHandleend = dropSwapParentConfig.handleEnd;\n\n dropSwapParentConfig.handleEnd = (\n state: DragState<T> | SynthDragState<T>\n ) => {\n handleEnd(state);\n\n originalHandleend(state);\n };\n\n parentData.on(\"dragStarted\", () => {\n documentController = addEvents(document, {\n dragover: rootDragover,\n handleRootPointerover: rootPointerover,\n });\n });\n\n parentData.on(\"dragEnded\", () => {\n documentController?.abort();\n });\n\n parentData.config = dropSwapParentConfig;\n },\n };\n };\n}\n\nfunction rootDragover(_e: DragEvent) {\n if (!isDragState(state)) return;\n\n removeClass(\n [state.currentParent.el],\n state.currentParent.data.config.dropZoneParentClass\n );\n\n state.currentParent = state.initialParent;\n}\n\nfunction rootPointerover(_e: CustomEvent) {\n if (!isSynthDragState(state)) return;\n\n removeClass(\n [state.currentParent.el],\n state.currentParent.data.config.synthDropZoneParentClass\n );\n\n state.currentParent = state.initialParent;\n}\n\nfunction updateDraggedOverNodes<T>(\n data: PointeroverNodeEvent<T> | NodeDragEventData<T>,\n state: DragState<T> | SynthDragState<T>\n) {\n const targetData =\n \"detail\" in data ? data.detail.targetData : data.targetData;\n\n const config = targetData.parent.data.config;\n\n const dropZoneClass = isSynthDragState(state)\n ? config.synthDropZoneClass\n : config.dropZoneClass;\n\n removeClass(\n dropSwapState.draggedOverNodes.map((node) => node.el),\n dropZoneClass\n );\n\n const enabledNodes = targetData.parent.data.enabledNodes;\n\n if (!enabledNodes) return;\n\n dropSwapState.draggedOverNodes = enabledNodes.slice(\n targetData.node.data.index,\n targetData.node.data.index + state.draggedNodes.length\n );\n\n addNodeClass(\n dropSwapState.draggedOverNodes.map((node) => node.el),\n dropZoneClass,\n true\n );\n\n state.currentTargetValue = targetData.node.data.value;\n\n state.currentParent = targetData.parent;\n\n addClass(\n state.currentParent.el,\n isSynthDragState(state)\n ? config.synthDropZoneParentClass\n : config.dropZoneParentClass,\n state.currentParent.data,\n true\n );\n}\n\nfunction handleNodeDragover<T>(\n data: NodeDragEventData<T>,\n state: DragState<T>\n) {\n data.e.preventDefault();\n\n data.e.stopPropagation();\n\n updateDraggedOverNodes(data, state);\n}\n\nexport function handleParentDragover<T>(\n data: ParentDragEventData<T>,\n state: DragState<T>\n) {\n data.e.preventDefault();\n\n data.e.stopPropagation();\n\n const currentConfig = state.currentParent.data.config;\n\n removeClass(\n dropSwapState.draggedOverNodes.map((node) => node.el),\n currentConfig.dropZoneClass\n );\n\n removeClass([state.currentParent.el], currentConfig.dropZoneParentClass);\n\n const config = data.targetData.parent.data.config;\n\n addClass(\n data.targetData.parent.el,\n config.dropZoneParentClass,\n data.targetData.parent.data,\n true\n );\n\n dropSwapState.draggedOverNodes = [];\n\n state.currentParent = data.targetData.parent;\n}\n\nexport function handeParentPointerover<T>(data: PointeroverParentEvent<T>) {\n const currentConfig = data.detail.state.currentParent.data.config;\n\n removeClass(\n dropSwapState.draggedOverNodes.map((node) => node.el),\n currentConfig.synthDropZoneClass\n );\n\n removeClass(\n [data.detail.state.currentParent.el],\n currentConfig.synthDropZoneParentClass\n );\n\n const config = data.detail.targetData.parent.data.config;\n\n addClass(\n data.detail.targetData.parent.el,\n config.synthDropZoneParentClass,\n data.detail.targetData.parent.data,\n true\n );\n\n dropSwapState.draggedOverNodes = [];\n\n data.detail.state.currentParent = data.detail.targetData.parent;\n}\n\nfunction handleNodePointerover<T>(data: PointeroverNodeEvent<T>) {\n if (!isSynthDragState(data.detail.state)) return;\n\n updateDraggedOverNodes(data, data.detail.state);\n}\n\nfunction swapElements<T>(\n arr1: T[],\n arr2: T[] | null,\n index1: number | number[],\n index2: number\n): T[] | [T[], T[]] {\n const indices1 = Array.isArray(index1) ? index1 : [index1];\n\n if (arr2 === null) {\n const elementsFromArr1 = indices1.map((i) => arr1[i]);\n\n const elementFromArr2 = arr1[index2];\n\n arr1.splice(index2, 1, ...elementsFromArr1);\n\n indices1.forEach((i, idx) => {\n arr1[i] = idx === 0 ? elementFromArr2 : (undefined as unknown as T);\n });\n\n return arr1.filter((el) => el !== undefined);\n } else {\n const elementsFromArr1 = indices1.map((i) => arr1[i]);\n\n const elementFromArr2 = arr2[index2];\n\n arr2.splice(index2, 1, ...elementsFromArr1);\n\n indices1.forEach((i, idx) => {\n arr1[i] = idx === 0 ? elementFromArr2 : (undefined as unknown as T);\n });\n\n return [arr1.filter((el) => el !== undefined), arr2];\n }\n}\n\nfunction handleEnd<T>(state: DragState<T> | SynthDragState<T>) {\n const isSynth = isSynthDragState(state);\n\n removeClass(\n [state.currentParent.el],\n isSynth\n ? state.currentParent.data.config.synthDropZoneParentClass\n : state.currentParent.data.config.dropZoneParentClass\n );\n\n removeClass(\n dropSwapState.draggedOverNodes.map((node) => node.el),\n isSynth\n ? state.currentParent.data.config.synthDropZoneClass\n : state.currentParent.data.config.dropZoneClass\n );\n\n const values = parentValues(state.currentParent.el, state.currentParent.data);\n\n const draggedValues = state.draggedNodes.map((node) => node.data.value);\n\n const newValues = values.filter((x) => !draggedValues.includes(x));\n\n const targetIndex = dropSwapState.draggedOverNodes[0]?.data.index;\n\n const draggedIndex = state.draggedNodes[0].data.index;\n\n const initialParentValues = parentValues(\n state.initialParent.el,\n state.initialParent.data\n );\n\n if (targetIndex === undefined) {\n if (state.initialParent.el === state.currentParent.el) return;\n\n const newInitialValues = initialParentValues.filter(\n (x) => !draggedValues.includes(x)\n );\n\n setParentValues(\n state.initialParent.el,\n state.initialParent.data,\n newInitialValues\n );\n\n setParentValues(\n state.currentParent.el,\n state.currentParent.data,\n values.concat(draggedValues)\n );\n\n return;\n }\n\n let swap = false;\n\n const shouldSwap = state.initialParent.data.config.dropSwapConfig?.shouldSwap;\n\n if (shouldSwap)\n swap = shouldSwap({\n sourceParent: state.initialParent,\n targetParent: state.currentParent,\n draggedNodes: state.draggedNodes,\n targetNodes: dropSwapState.draggedOverNodes as NodeRecord<T>[],\n state,\n });\n\n if (state.initialParent.el === state.currentParent.el) {\n newValues.splice(targetIndex, 0, ...draggedValues);\n\n setParentValues(\n state.currentParent.el,\n state.currentParent.data,\n swap ? swapElements(values, null, draggedIndex, targetIndex) : newValues\n );\n } else {\n if (swap) {\n const res = swapElements(\n initialParentValues,\n newValues,\n state.initialIndex,\n targetIndex\n );\n\n setParentValues(\n state.initialParent.el,\n state.initialParent.data,\n res[0] as T[]\n );\n\n setParentValues(\n state.currentParent.el,\n state.currentParent.data,\n res[1] as T[]\n );\n } else {\n const newInitialValues = initialParentValues.filter(\n (x) => !draggedValues.includes(x)\n );\n\n setParentValues(\n state.initialParent.el,\n state.initialParent.data,\n newInitialValues\n );\n\n newValues.splice(targetIndex, 0, ...draggedValues);\n\n setParentValues(\n state.currentParent.el,\n state.currentParent.data,\n newValues\n );\n }\n }\n}\n","import type {\n DNDPlugin,\n DragAndDrop,\n DragState,\n DragStateProps,\n Node,\n NodeData,\n NodeDragEventData,\n NodeEventData,\n NodePointerEventData,\n NodeRecord,\n NodeTargetData,\n NodesData,\n ParentConfig,\n ParentData,\n ParentEventData,\n ParentTargetData,\n ParentsData,\n PointeroverNodeEvent,\n PointeroverParentEvent,\n SetupNodeData,\n TearDownNodeData,\n BaseDragState,\n SynthDragState,\n ParentKeydownEventData,\n SynthDragStateProps,\n ParentRecord,\n EventHandlers,\n NodeFromPoint,\n ParentFromPoint,\n Coordinates,\n ParentDragEventData,\n} from \"./types\";\n\nexport * from \"./types\";\nexport { animations } from \"./plugins/animations\";\nexport { insert } from \"./plugins/insert\";\nexport { dropOrSwap } from \"./plugins/drop-or-swap\";\n\n// Function to detect if the device supports touch\nfunction checkTouchSupport() {\n if (!isBrowser) return false;\n\n return \"ontouchstart\" in window || navigator.maxTouchPoints > 0;\n}\n\n/**\n * Check to see if code is running in a browser.\n *\n * @internal\n */\nexport const isBrowser = typeof window !== \"undefined\";\n\nlet dropped = false;\n\n/**\n * Abort controller for the document.\n */\nlet documentController: AbortController | undefined;\n\nlet windowController: AbortController | undefined;\n\nlet touchDevice: boolean = false;\n\nexport const nodes: NodesData<any> = new WeakMap<Node, NodeData<unknown>>();\n\nexport const parents: ParentsData<any> = new WeakMap<\n HTMLElement,\n ParentData<unknown>\n>();\n\nexport const treeAncestors: Record<string, HTMLElement> = {};\n\nlet synthNodePointerDown = false;\n\nexport function createEmitter() {\n const callbacks = new Map<string, CallableFunction[]>();\n\n const emit = function (eventName: string, data: any) {\n if (!callbacks.get(eventName)) return;\n callbacks.get(eventName)!.forEach((cb) => {\n cb(data);\n });\n };\n\n const on = function (eventName: string, callback: any) {\n const cbs = callbacks.get(eventName) ?? [];\n\n cbs.push(callback);\n\n callbacks.set(eventName, cbs);\n };\n\n return [emit, on];\n}\n\nexport const [emit, on] = createEmitter();\n\nconst baseDragState = {\n activeDescendant: undefined,\n affectedNodes: [],\n currentTargetValue: undefined,\n on,\n emit,\n newActiveDescendant: undefined,\n originalZIndex: undefined,\n pointerSelection: false,\n preventEnter: false,\n longPress: false,\n longPressTimeout: 0,\n remapJustFinished: false,\n selectednodes: [],\n selectedParent: undefined,\n preventSynthDrag: false,\n};\n\n/**\n * The state of the drag and drop.\n */\nexport let state: BaseDragState<unknown> = baseDragState;\n\nexport function resetState() {\n const baseDragState = {\n activeDescendant: undefined,\n affectedNodes: [],\n on,\n emit,\n currentTargetValue: undefined,\n originalZIndex: undefined,\n pointerId: undefined,\n preventEnter: false,\n remapJustFinished: false,\n selectednodes: [],\n preventSynthDrag: false,\n selectedParent: undefined,\n pointerSelection: false,\n synthScrollDirection: undefined,\n draggedNodeDisplay: undefined,\n synthDragScrolling: false,\n longPress: false,\n longPressTimeout: 0,\n };\n\n state = { ...baseDragState } as BaseDragState<unknown>;\n}\n\n/**\n * @param {DragStateProps} dragStateProps - Attributes to update state with.\n *\n * @mutation - Updates state with node values.\n *\n * @returns void\n */\nexport function setDragState<T>(\n dragStateProps: (SynthDragStateProps & DragStateProps<T>) | DragStateProps<T>\n): DragState<T> | SynthDragState<T> {\n Object.assign(state, dragStateProps);\n\n dragStateProps.initialParent.data.emit(\"dragStarted\", state);\n\n dropped = false;\n\n state.emit(\"dragStarted\", state);\n\n return state as DragState<T> | SynthDragState<T>;\n}\n\n/**\n *\n */\nfunction handleRootPointerdown(_e: PointerEvent) {\n if (state.activeState) setActive(state.activeState.parent, undefined, state);\n\n if (state.selectedState)\n deselect(state.selectedState.nodes, state.selectedState.parent, state);\n\n state.selectedState = state.activeState = undefined;\n}\n\nfunction handleRootPointerup(_e: PointerEvent) {\n if (!isSynthDragState(state)) return;\n\n const config = state.currentParent.data.config;\n\n if (isSynthDragState(state)) config.handleEnd(state);\n}\n\n/**\n * Handles the keydown event on the root element.\n *\n * @param {KeyboardEvent} e - The keyboard event.\n */\nfunction handleRootKeydown(e: KeyboardEvent) {\n if (e.key === \"Escape\") {\n if (state.selectedState)\n deselect(state.selectedState.nodes, state.selectedState.parent, state);\n\n if (state.activeState)\n setActive(state.activeState.parent, undefined, state);\n\n state.selectedState = state.activeState = undefined;\n }\n}\n\nfunction handleRootDrop(_e: DragEvent) {}\n\n/**\n * If we are currently dragging, then let's prevent default on dragover to avoid\n * the default behavior of the browser on drop.\n */\nfunction handleRootDragover(e: DragEvent) {\n if (!isDragState(state)) return;\n\n e.preventDefault();\n}\n\n/**\n * Initializes the drag and drop functionality for a given parent.\n *\n * @param {DragAndDrop} dragAndDrop - The drag and drop configuration.\n *\n * @returns void\n */\nexport function dragAndDrop<T>({\n parent,\n getValues,\n setValues,\n config = {},\n}: DragAndDrop<T>): void {\n if (!isBrowser) return;\n\n touchDevice = checkTouchSupport();\n\n if (!documentController)\n documentController = addEvents(document, {\n dragover: handleRootDragover,\n pointerdown: handleRootPointerdown,\n pointerup: handleRootPointerup,\n keydown: handleRootKeydown,\n drop: handleRootDrop,\n });\n\n if (!windowController)\n windowController = addEvents(window, {\n resize: () => {\n touchDevice = checkTouchSupport();\n },\n });\n\n tearDown(parent);\n\n const [emit, on] = createEmitter();\n\n const parentData: ParentData<T> = {\n getValues,\n setValues,\n config: {\n dragDropEffect: config.dragDropEffect ?? \"move\",\n dragEffectAllowed: config.dragEffectAllowed ?? \"move\",\n draggedNodes,\n dragstartClasses,\n deepCopyStyles: config.deepCopyStyles ?? false,\n handleNodeKeydown,\n handleParentKeydown,\n handleDragstart,\n handleNodeDragover,\n handleParentDragover,\n handleNodeDrop,\n handlePointercancel,\n handleEnd,\n handleDragend,\n handleParentBlur,\n handleParentFocus,\n handleNodePointerup,\n handleNodePointerover,\n handleParentPointerover,\n handleParentScroll,\n handleNodePointerdown,\n handleNodePointermove,\n handleNodeDragenter,\n handleNodeDragleave,\n handleParentDrop,\n multiDrag: config.multiDrag ?? false,\n nativeDrag: config.nativeDrag ?? true,\n performSort,\n performTransfer,\n root: config.root ?? document,\n setupNode,\n setupNodeRemap,\n reapplyDragClasses,\n tearDownNode,\n tearDownNodeRemap,\n remapFinished,\n scrollBehavior: {\n x: 0.95,\n y: 0.95,\n },\n threshold: {\n horizontal: 0,\n vertical: 0,\n },\n ...config,\n },\n enabledNodes: [],\n abortControllers: {},\n privateClasses: [],\n on,\n emit,\n };\n\n const nodesObserver = new MutationObserver(nodesMutated);\n\n nodesObserver.observe(parent, { childList: true });\n\n parents.set(parent, parentData);\n\n if (config.treeAncestor && config.treeGroup)\n treeAncestors[config.treeGroup] = parent;\n\n config.plugins?.forEach((plugin) => {\n plugin(parent)?.tearDown?.();\n });\n\n config.plugins?.forEach((plugin) => {\n plugin(parent)?.tearDown?.();\n });\n\n config.plugins?.forEach((plugin: DNDPlugin) => {\n plugin(parent)?.setup?.();\n });\n\n setup(parent, parentData);\n\n remapNodes(parent, true);\n}\n\nexport function dragStateProps<T>(\n data: NodeDragEventData<T> | NodePointerEventData<T>,\n draggedNodes: Array<NodeRecord<T>>\n): DragStateProps<T> {\n const { x, y } = eventCoordinates(data.e);\n\n const rect = data.targetData.node.el.getBoundingClientRect();\n\n return {\n affectedNodes: [],\n ascendingDirection: false,\n clonedDraggedEls: [],\n dynamicValues: [],\n coordinates: {\n x,\n y,\n },\n draggedNode: {\n el: data.targetData.node.el,\n data: data.targetData.node.data,\n },\n draggedNodes,\n incomingDirection: undefined,\n initialIndex: data.targetData.node.data.index,\n initialParent: {\n el: data.targetData.parent.el,\n data: data.targetData.parent.data,\n },\n currentParent: {\n el: data.targetData.parent.el,\n data: data.targetData.parent.data,\n },\n longPress: data.targetData.parent.data.config.longPress ?? false,\n longPressTimeout: 0,\n currentTargetValue: data.targetData.node.data.value,\n scrollEls: [],\n startLeft: x - rect.left,\n startTop: y - rect.top,\n targetIndex: data.targetData.node.data.index,\n transferred: false,\n };\n}\n\nexport function performSort<T>({\n parent,\n draggedNodes,\n targetNode,\n}: {\n parent: ParentRecord<T>;\n draggedNodes: Array<NodeRecord<T>>;\n targetNode: NodeRecord<T>;\n}) {\n const draggedValues = draggedNodes.map((x) => x.data.value);\n\n const targetParentValues = parentValues(parent.el, parent.data);\n\n const originalIndex = draggedNodes[0].data.index;\n\n const enabledNodes = [...parent.data.enabledNodes];\n\n const newParentValues = [\n ...targetParentValues.filter((x) => !draggedValues.includes(x)),\n ];\n\n newParentValues.splice(targetNode.data.index, 0, ...draggedValues);\n\n if (\"draggedNode\" in state) state.currentTargetValue = targetNode.data.value;\n\n setParentValues(parent.el, parent.data, [...newParentValues]);\n\n if (parent.data.config.onSort)\n parent.data.config.onSort({\n parent: {\n el: parent.el,\n data: parent.data,\n },\n previousValues: [...targetParentValues],\n previousNodes: [...enabledNodes],\n nodes: [...parent.data.enabledNodes],\n values: [...newParentValues],\n draggedNode: draggedNodes[0],\n previousPosition: originalIndex,\n position: targetNode.data.index,\n });\n}\n\n/**\n * This function sets the active node as well as removing any classes or\n * attribute set.\n *\n * @param {ParentEventData} data - The parent event data.\n * @param {NodeRecord} newActiveNode - The new active node.\n * @param {BaseDragState} state - The current drag state.\n */\nfunction setActive<T>(\n parent: ParentRecord<T>,\n newActiveNode: NodeRecord<T> | undefined,\n state: BaseDragState<T>\n) {\n const activeDescendantClass = parent.data.config.activeDescendantClass;\n\n if (state.activeState) {\n {\n removeClass([state.activeState.node.el], activeDescendantClass);\n\n if (state.activeState.parent.el !== parent.el)\n state.activeState.parent.el.setAttribute(\"aria-activedescendant\", \"\");\n }\n }\n\n if (!newActiveNode) {\n state.activeState?.parent.el.setAttribute(\"aria-activedescendant\", \"\");\n\n state.activeState = undefined;\n\n return;\n }\n\n state.activeState = {\n node: newActiveNode,\n parent,\n };\n\n addNodeClass([newActiveNode.el], activeDescendantClass);\n\n state.activeState.parent.el.setAttribute(\n \"aria-activedescendant\",\n state.activeState.node.el.id\n );\n}\n\nfunction deselect<T>(\n nodes: Array<NodeRecord<T>>,\n parent: ParentRecord<T>,\n state: BaseDragState<T>\n) {\n const selectedClass = parent.data.config.selectedClass;\n\n if (!state.selectedState) return;\n\n const iterativeNodes = Array.from(nodes);\n\n removeClass(\n nodes.map((x) => x.el),\n selectedClass\n );\n\n for (const node of iterativeNodes) {\n node.el.setAttribute(\"aria-selected\", \"false\");\n\n const index = state.selectedState.nodes.findIndex((x) => x.el === node.el);\n\n if (index === -1) continue;\n\n state.selectedState.nodes.splice(index, 1);\n }\n\n clearLiveRegion(parent);\n}\n\n/**\n * This function sets the selected nodes. This will clean the prior selected state\n * as well as removing any classes or attributes set.\n */\nfunction setSelected<T>(\n parent: ParentRecord<T>,\n selectedNodes: Array<NodeRecord<T>>,\n newActiveNode: NodeRecord<T> | undefined,\n state: BaseDragState<T>,\n pointerdown = false\n) {\n st