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; }