@mantine/hooks
Version:
A collection of 50+ hooks for state and UI management
266 lines (265 loc) • 8.48 kB
JavaScript
"use client";
let react = require("react");
//#region packages/@mantine/hooks/src/use-drag/use-drag.ts
const VELOCITY_DECAY_MS = 100;
function sign(n) {
if (n > 0) return 1;
if (n < 0) return -1;
return 0;
}
function getThresholdVector(threshold) {
const t = threshold ?? 0;
if (typeof t === "number") return [t, t];
return t;
}
function createInitialState() {
return {
isActive: false,
pointerId: -1,
startXY: [0, 0],
prevXY: [0, 0],
startTimestamp: 0,
prevTimestamp: 0,
thresholdMet: false,
firstFired: false,
lockedAxis: null,
canceled: false,
lastVelocity: [0, 0]
};
}
function useDrag(handler, options = {}) {
const [active, setActive] = (0, react.useState)(false);
const handlerRef = (0, react.useRef)(handler);
handlerRef.current = handler;
const optionsRef = (0, react.useRef)(options);
optionsRef.current = options;
const stateRef = (0, react.useRef)(createInitialState());
const documentControllerRef = (0, react.useRef)(null);
return {
ref: (0, react.useCallback)((node) => {
if (!node) return;
const elementController = new AbortController();
const applyAxisConstraint = (v) => {
const opts = optionsRef.current;
const s = stateRef.current;
if (opts.axis === "x") return [v[0], 0];
if (opts.axis === "y") return [0, v[1]];
if (opts.axis === "lock") {
if (s.lockedAxis === null) {
const t = opts.axisThreshold ?? 1;
if (Math.abs(v[0]) > t || Math.abs(v[1]) > t) s.lockedAxis = Math.abs(v[0]) >= Math.abs(v[1]) ? "x" : "y";
}
if (s.lockedAxis === "x") return [v[0], 0];
if (s.lockedAxis === "y") return [0, v[1]];
}
return v;
};
const resetDrag = () => {
const s = stateRef.current;
s.isActive = false;
s.pointerId = -1;
s.thresholdMet = false;
s.firstFired = false;
s.lockedAxis = null;
s.canceled = false;
setActive(false);
document.body.style.userSelect = "";
document.body.style.webkitUserSelect = "";
documentControllerRef.current?.abort();
documentControllerRef.current = null;
};
const cancel = () => {
if (stateRef.current.isActive) {
stateRef.current.canceled = true;
resetDrag();
}
};
const activateDrag = () => {
setActive(true);
document.body.style.userSelect = "none";
document.body.style.webkitUserSelect = "none";
};
const onPointerDown = (event) => {
if (optionsRef.current.enabled === false) return;
if (event.button !== 0) return;
if (stateRef.current.isActive) return;
const s = stateRef.current;
s.isActive = true;
s.pointerId = event.pointerId;
s.startXY = [event.clientX, event.clientY];
s.prevXY = [event.clientX, event.clientY];
s.startTimestamp = event.timeStamp;
s.prevTimestamp = event.timeStamp;
s.thresholdMet = false;
s.firstFired = false;
s.lockedAxis = null;
s.canceled = false;
s.lastVelocity = [0, 0];
const [tx, ty] = getThresholdVector(optionsRef.current.threshold);
if (tx === 0 && ty === 0) {
s.thresholdMet = true;
s.firstFired = true;
activateDrag();
handlerRef.current({
xy: [event.clientX, event.clientY],
initial: [event.clientX, event.clientY],
movement: [0, 0],
delta: [0, 0],
distance: [0, 0],
direction: [0, 0],
velocity: [0, 0],
elapsedTime: 0,
first: true,
last: false,
active: true,
tap: false,
canceled: false,
cancel,
event
});
}
documentControllerRef.current?.abort();
documentControllerRef.current = new AbortController();
const sig = documentControllerRef.current.signal;
document.addEventListener("pointermove", onPointerMove, { signal: sig });
document.addEventListener("pointerup", onPointerUp, { signal: sig });
document.addEventListener("pointercancel", onPointerCancel, { signal: sig });
};
const onPointerMove = (event) => {
const s = stateRef.current;
if (!s.isActive || event.pointerId !== s.pointerId) return;
const rawMovement = [event.clientX - s.startXY[0], event.clientY - s.startXY[1]];
if (!s.thresholdMet) {
const [tx, ty] = getThresholdVector(optionsRef.current.threshold);
if (Math.abs(rawMovement[0]) < tx && Math.abs(rawMovement[1]) < ty) {
s.prevXY = [event.clientX, event.clientY];
s.prevTimestamp = event.timeStamp;
return;
}
s.thresholdMet = true;
activateDrag();
}
const movement = applyAxisConstraint(rawMovement);
const delta = applyAxisConstraint([event.clientX - s.prevXY[0], event.clientY - s.prevXY[1]]);
const timeDelta = event.timeStamp - s.prevTimestamp;
const velocity = timeDelta > 0 ? [Math.abs(delta[0]) / timeDelta, Math.abs(delta[1]) / timeDelta] : s.lastVelocity;
s.lastVelocity = velocity;
const isFirst = !s.firstFired;
s.firstFired = true;
s.prevXY = [event.clientX, event.clientY];
s.prevTimestamp = event.timeStamp;
handlerRef.current({
xy: [event.clientX, event.clientY],
initial: [...s.startXY],
movement,
delta,
distance: [Math.abs(movement[0]), Math.abs(movement[1])],
direction: [sign(delta[0]), sign(delta[1])],
velocity,
elapsedTime: event.timeStamp - s.startTimestamp,
first: isFirst,
last: false,
active: true,
tap: false,
canceled: false,
cancel,
event
});
};
const onPointerUp = (event) => {
const s = stateRef.current;
if (!s.isActive || event.pointerId !== s.pointerId) return;
const opts = optionsRef.current;
if (!s.thresholdMet) {
if (opts.filterTaps) {
const mov = applyAxisConstraint([event.clientX - s.startXY[0], event.clientY - s.startXY[1]]);
const dist = [Math.abs(mov[0]), Math.abs(mov[1])];
const isTap = Math.max(dist[0], dist[1]) < (opts.tapThreshold ?? 3);
handlerRef.current({
xy: [event.clientX, event.clientY],
initial: [...s.startXY],
movement: mov,
delta: mov,
distance: dist,
direction: [sign(mov[0]), sign(mov[1])],
velocity: [0, 0],
elapsedTime: event.timeStamp - s.startTimestamp,
first: true,
last: true,
active: false,
tap: isTap,
canceled: false,
cancel,
event
});
}
resetDrag();
return;
}
const movement = applyAxisConstraint([event.clientX - s.startXY[0], event.clientY - s.startXY[1]]);
const distance = [Math.abs(movement[0]), Math.abs(movement[1])];
const delta = applyAxisConstraint([event.clientX - s.prevXY[0], event.clientY - s.prevXY[1]]);
const velocity = event.timeStamp - s.prevTimestamp > VELOCITY_DECAY_MS ? [0, 0] : s.lastVelocity;
const maxDistance = Math.max(distance[0], distance[1]);
const tap = opts.filterTaps === true && maxDistance < (opts.tapThreshold ?? 3);
handlerRef.current({
xy: [event.clientX, event.clientY],
initial: [...s.startXY],
movement,
delta,
distance,
direction: [sign(delta[0]), sign(delta[1])],
velocity,
elapsedTime: event.timeStamp - s.startTimestamp,
first: !s.firstFired,
last: true,
active: false,
tap,
canceled: false,
cancel,
event
});
resetDrag();
};
const onPointerCancel = (event) => {
const s = stateRef.current;
if (!s.isActive || event.pointerId !== s.pointerId) return;
const movement = applyAxisConstraint([event.clientX - s.startXY[0], event.clientY - s.startXY[1]]);
handlerRef.current({
xy: [event.clientX, event.clientY],
initial: [...s.startXY],
movement,
delta: [0, 0],
distance: [Math.abs(movement[0]), Math.abs(movement[1])],
direction: [0, 0],
velocity: [0, 0],
elapsedTime: event.timeStamp - s.startTimestamp,
first: !s.firstFired,
last: true,
active: false,
tap: false,
canceled: true,
cancel,
event
});
resetDrag();
};
node.addEventListener("pointerdown", onPointerDown, { signal: elementController.signal });
return () => {
elementController.abort();
documentControllerRef.current?.abort();
documentControllerRef.current = null;
if (stateRef.current.isActive) {
stateRef.current.isActive = false;
setActive(false);
document.body.style.userSelect = "";
document.body.style.webkitUserSelect = "";
}
};
}, []),
active
};
}
//#endregion
exports.useDrag = useDrag;
//# sourceMappingURL=use-drag.cjs.map