import { useEffect, useRef, useState } from "react"; function getDefaultPlacement(event, element) { const rect = element.getBoundingClientRect(); return event.clientY > rect.top + rect.height / 2 ? "after" : "before"; } export function usePointerReorder({ targetSelector, getTargetId, canDropOn = () => true, getPlacement = getDefaultPlacement, onMove }) { const [draggingId, setDraggingId] = useState(""); const [dropTarget, setDropTarget] = useState({ id: "", placement: "before" }); const optionsRef = useRef({ getTargetId, canDropOn, getPlacement, onMove }); useEffect(() => { optionsRef.current = { getTargetId, canDropOn, getPlacement, onMove }; }, [canDropOn, getPlacement, getTargetId, onMove]); useEffect(() => { if (!draggingId) return undefined; function getDropTarget(event) { const { getTargetId, canDropOn, getPlacement } = optionsRef.current; const element = document.elementFromPoint(event.clientX, event.clientY); const target = element?.closest?.(targetSelector); if (!target || !canDropOn(target, draggingId) || getTargetId(target) === draggingId) { return { id: "", placement: "before" }; } return { id: getTargetId(target), placement: getPlacement(event, target) }; } function handlePointerMove(event) { setDropTarget(getDropTarget(event)); } function handlePointerUp(event) { const { onMove } = optionsRef.current; const target = getDropTarget(event); if (target.id) onMove(draggingId, target.id, target.placement); setDraggingId(""); setDropTarget({ id: "", placement: "before" }); } window.addEventListener("pointermove", handlePointerMove); window.addEventListener("pointerup", handlePointerUp, { once: true }); window.addEventListener("pointercancel", handlePointerUp, { once: true }); return () => { window.removeEventListener("pointermove", handlePointerMove); window.removeEventListener("pointerup", handlePointerUp); window.removeEventListener("pointercancel", handlePointerUp); }; }, [draggingId, targetSelector]); function startDrag(event, id) { if (event.button !== 0) return; event.preventDefault(); event.currentTarget.setPointerCapture?.(event.pointerId); setDraggingId(id); setDropTarget({ id: "", placement: "before" }); } return { draggingId, dropTarget, startDrag }; }