Files
cinny/src/app/hooks/usePan.ts
T

78 lines
2.1 KiB
TypeScript
Raw Normal View History

import { MouseEventHandler, useEffect, useRef, useState } from 'react';
2023-10-06 13:44:06 +11:00
export type Pan = {
translateX: number;
translateY: number;
};
const INITIAL_PAN = {
translateX: 0,
translateY: 0,
};
export const usePan = (active: boolean) => {
const [pan, setPan] = useState<Pan>(INITIAL_PAN);
const [cursor, setCursor] = useState<'grab' | 'grabbing' | 'initial'>(
active ? 'grab' : 'initial',
2023-10-06 13:44:06 +11:00
);
// Track the exact handler references that were passed to addEventListener so
// we can remove them even if the component re-renders or unmounts mid-drag.
const attachedRef = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>(
null,
);
2023-10-06 13:44:06 +11:00
useEffect(() => {
setCursor(active ? 'grab' : 'initial');
}, [active]);
const handleMouseDown: MouseEventHandler<HTMLElement> = (evt) => {
if (!active) return;
evt.preventDefault();
setCursor('grabbing');
const handleMouseMove = (e: MouseEvent) => {
e.preventDefault();
e.stopPropagation();
setPan((p) => ({
translateX: p.translateX + e.movementX,
translateY: p.translateY + e.movementY,
}));
};
const handleMouseUp = (e: MouseEvent) => {
e.preventDefault();
setCursor('grab');
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
attachedRef.current = null;
};
attachedRef.current = { move: handleMouseMove, up: handleMouseUp };
2023-10-06 13:44:06 +11:00
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
};
useEffect(() => {
if (!active) setPan(INITIAL_PAN);
}, [active]);
// Remove listeners if the component unmounts while a drag is in progress.
useEffect(
() => () => {
if (attachedRef.current) {
document.removeEventListener('mousemove', attachedRef.current.move);
document.removeEventListener('mouseup', attachedRef.current.up);
attachedRef.current = null;
}
},
[],
);
2023-10-06 13:44:06 +11:00
return {
pan,
cursor,
onMouseDown: handleMouseDown,
};
};