80 lines
2.6 KiB
JavaScript
80 lines
2.6 KiB
JavaScript
export const DRAG_START_GUARD_PX = 8;
|
|
|
|
const INTERACTIVE_SELECTOR = "button, input, select, textarea, a[href], [contenteditable]:not([contenteditable='false'])";
|
|
|
|
export function pointInsideExpandedRect(x, y, rect, padding = DRAG_START_GUARD_PX) {
|
|
return x >= rect.left - padding
|
|
&& x <= rect.right + padding
|
|
&& y >= rect.top - padding
|
|
&& y <= rect.bottom + padding;
|
|
}
|
|
|
|
export function isProtectedDragStart(card, event, padding = DRAG_START_GUARD_PX) {
|
|
const target = typeof Element !== "undefined" && event.target instanceof Element ? event.target : null;
|
|
if (target?.closest(INTERACTIVE_SELECTOR)) return true;
|
|
if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) return false;
|
|
return [...card.querySelectorAll(INTERACTIVE_SELECTOR)].some((control) => (
|
|
pointInsideExpandedRect(event.clientX, event.clientY, control.getBoundingClientRect(), padding)
|
|
));
|
|
}
|
|
|
|
export function protectCardDragStart(card, { locked = false } = {}) {
|
|
let dragging = false;
|
|
let restoreListeners = null;
|
|
|
|
const restore = () => {
|
|
restoreListeners?.();
|
|
restoreListeners = null;
|
|
if (!locked && !dragging) card.draggable = true;
|
|
};
|
|
|
|
card.draggable = !locked;
|
|
card.addEventListener("pointerdown", (event) => {
|
|
if (locked || dragging) return;
|
|
restore();
|
|
if (!isProtectedDragStart(card, event)) {
|
|
card.draggable = true;
|
|
return;
|
|
}
|
|
card.draggable = false;
|
|
const pointerId = event.pointerId;
|
|
const finish = (finishEvent) => {
|
|
if (finishEvent.pointerId !== pointerId) return;
|
|
queueMicrotask(restore);
|
|
};
|
|
window.addEventListener("pointerup", finish, true);
|
|
window.addEventListener("pointercancel", finish, true);
|
|
restoreListeners = () => {
|
|
window.removeEventListener("pointerup", finish, true);
|
|
window.removeEventListener("pointercancel", finish, true);
|
|
};
|
|
}, true);
|
|
card.addEventListener("dragstart", (event) => {
|
|
if (locked || !card.draggable || isProtectedDragStart(card, event)) {
|
|
event.preventDefault();
|
|
restore();
|
|
return;
|
|
}
|
|
dragging = true;
|
|
restoreListeners?.();
|
|
restoreListeners = null;
|
|
card.classList.add("is-dragging");
|
|
});
|
|
card.addEventListener("dragend", () => {
|
|
dragging = false;
|
|
card.classList.remove("is-dragging");
|
|
restore();
|
|
});
|
|
|
|
return { restore };
|
|
}
|
|
|
|
export function reorderByKey(items, sourceKey, targetKey) {
|
|
const order = [...items];
|
|
const from = order.indexOf(sourceKey);
|
|
const target = order.indexOf(targetKey);
|
|
if (from < 0 || target < 0 || from === target) return null;
|
|
order.splice(target, 0, order.splice(from, 1)[0]);
|
|
return order;
|
|
}
|