@sirhc77/canvas-math-kit
Version:
A lightweight, interactive canvas-based vector visualizer for math, linear algebra, and ML education. Built with React + TypeScript.
72 lines (71 loc) • 2.98 kB
JavaScript
// hooks/usePointerDrag.ts
import { useEffect, useRef } from 'react';
export function usePointerDrag(canvasRef, items, onChange, { origin, scale, snap, isLocked = false, hitRadius = 0.3, onDragStart = () => null, onDragEnd = () => null, }) {
const dragIndexRef = useRef(null);
const itemsRef = useRef(items);
useEffect(() => {
itemsRef.current = items;
}, [items]);
function getCanvasCoords(e) {
const rect = canvasRef.current.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
const x = (mx - origin.x) / scale;
const y = (origin.y - my) / scale;
return { x, y };
}
function isNear(p, v) {
const dx = p.x - v.x;
const dy = p.y - v.y;
return dx * dx + dy * dy <= hitRadius * hitRadius;
}
function applySnap(x, y) {
if (typeof snap === 'function')
return snap(x, y);
if (typeof snap === 'number' && snap > 0) {
return [Math.round(x / snap) * snap, Math.round(y / snap) * snap];
}
return [x, y];
}
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas)
return;
const handleDown = (e) => {
if (isLocked)
return;
const coords = getCanvasCoords(e);
const index = items.findIndex((item) => item.draggable && isNear(coords, item));
if (index !== -1) {
dragIndexRef.current = index;
canvas.setPointerCapture(e.pointerId);
onDragStart === null || onDragStart === void 0 ? void 0 : onDragStart();
}
};
const handleMove = (e) => {
if (isLocked)
return;
if (dragIndexRef.current === null)
return;
const coords = getCanvasCoords(e);
const [x, y] = applySnap(coords.x, coords.y);
const updated = itemsRef.current.map((item, i) => i === dragIndexRef.current ? Object.assign(Object.assign({}, item), { x, y }) : item);
onChange(updated);
};
const handleUp = (e) => {
dragIndexRef.current = null;
canvas.releasePointerCapture(e.pointerId);
onDragEnd === null || onDragEnd === void 0 ? void 0 : onDragEnd();
};
canvas.addEventListener('pointerdown', handleDown);
canvas.addEventListener('pointermove', handleMove);
canvas.addEventListener('pointerup', handleUp);
canvas.addEventListener('pointercancel', handleUp);
return () => {
canvas.removeEventListener('pointerdown', handleDown);
canvas.removeEventListener('pointermove', handleMove);
canvas.removeEventListener('pointerup', handleUp);
canvas.removeEventListener('pointercancel', handleUp);
};
}, [canvasRef, items, onChange, origin, scale, snap, isLocked, hitRadius]);
}