70 lines
2.4 KiB
JavaScript
70 lines
2.4 KiB
JavaScript
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 };
|
|
}
|