"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import type { Position } from "./use-position-persistence"; import { clampPosition } from "./use-position-persistence"; type DragState = { active: boolean; moved: boolean; startX: number; startY: number; originX: number; originY: number; pointerId: number; }; type DragCallbacks = { /** 拖拽开始时触发(pointer down 后) */ onDragStart: () => void; /** 拖拽释放时触发,moved 表示是否发生了实际移动 */ onRelease: (moved: boolean) => void; }; /** * 拖拽位置 Hook * * 处理 pointer 事件,跟踪拖拽状态与位置变化。 * 不处理边缘吸附、持久化等业务逻辑,通过回调委托给调用方。 */ export function useDragPosition( position: Position, setPosition: React.Dispatch>, callbacks: DragCallbacks, ): { dragging: boolean; handlers: { onPointerDown: (e: React.PointerEvent) => void; onPointerMove: (e: React.PointerEvent) => void; onPointerUp: (e: React.PointerEvent) => void; onPointerCancel: () => void; }; } { const [dragging, setDragging] = useState(false); const dragStateRef = useRef({ active: false, moved: false, startX: 0, startY: 0, originX: 0, originY: 0, pointerId: -1, }); const callbacksRef = useRef(callbacks); useEffect(() => { callbacksRef.current = callbacks; }, [callbacks]); const onPointerDown = useCallback( (e: React.PointerEvent): void => { // 仅主键响应拖拽 if (e.button !== 0 && e.pointerType === "mouse") return; const s = dragStateRef.current; s.active = true; s.moved = false; s.startX = e.clientX; s.startY = e.clientY; s.originX = position.x; s.originY = position.y; s.pointerId = e.pointerId; try { e.currentTarget.setPointerCapture(e.pointerId); } catch { // ignore } setDragging(true); callbacksRef.current.onDragStart(); }, [position], ); const onPointerMove = useCallback( (e: React.PointerEvent): void => { const s = dragStateRef.current; if (!s.active || e.pointerId !== s.pointerId) return; const dx = e.clientX - s.startX; const dy = e.clientY - s.startY; // 阈值过滤微抖动 if (!s.moved && Math.abs(dx) + Math.abs(dy) < 4) return; s.moved = true; const next = clampPosition({ x: s.originX + dx, y: s.originY + dy, }); setPosition(next); }, [setPosition], ); const onPointerUp = useCallback( (e: React.PointerEvent): void => { const s = dragStateRef.current; if (!s.active || e.pointerId !== s.pointerId) return; s.active = false; setDragging(false); try { e.currentTarget.releasePointerCapture(e.pointerId); } catch { // ignore } callbacksRef.current.onRelease(s.moved); }, [], ); const onPointerCancel = useCallback((): void => { dragStateRef.current.active = false; setDragging(false); }, []); return { dragging, handlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel, }, }; }