@formkit/drag-and-drop
Version:
Drag and drop package.
1,500 lines (1,496 loc) • 94.8 kB
JavaScript
// 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 - (rect?.left ?? 0),
startTop: offsetY ? offsetY : y - (rect?.top ?? 0),
targetIndex: node.data.index,
transferred: false
};
}
function performSort({
parent,
draggedNodes: draggedNodes2,
targetNodes
}) {
remapNodes(parent.el);
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.some((y) => eq(x, y)))
];
newParentValues.splice(targetNodes[0].data.index, 0, ...draggedValues);
if ("draggedNode" in state)
state.currentTargetValue = targetNodes[0].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],
draggedNodes: draggedNodes2,
previousPosition: originalIndex,
position: targetNodes[0].data.index,
targetNodes,
state
});
}
}
function setActive(parent, newActiveNode, state2) {
if (!newActiveNode) {
state2.activeState = void 0;
return;
}
state2.activeState = {
node: newActiveNode,
parent
};
}
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);
}
}
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
};
setActive(parent, newActiveNode, state2);
}
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,
targetNodes,
state: state2
}) {
remapNodes(initialParent.el);
const draggedValues = draggedNodes2.map((x) => x.data.value);
const currentParentValues = [
...parentValues(currentParent.el, currentParent.data).filter(
(x) => !draggedValues.some((y) => eq(x, y))
)
];
const targetParentValues = parentValues(targetParent.el, targetParent.data);
const reset = initialParent.el === targetParent.el && target