import { useCallback, useRef } from 'react'; /** * Long-Press-Erkennung über Pointer-Events. * * Liefert Handler die direkt auf `
` etc. gespreaded werden können. * Ein "Long-Press" feuert nach `thresholdMs` (Default 500 ms) wenn der Pointer * sich nicht weiter als `moveTolerance` Pixel bewegt hat. */ interface LongPressOptions { thresholdMs?: number; moveTolerance?: number; /** Wenn ``true``, werden auch Maus-Events behandelt (für Desktop-Smoke-Tests). */ includeMouse?: boolean; } interface LongPressHandlers { onPointerDown: (e: React.PointerEvent) => void; onPointerMove: (e: React.PointerEvent) => void; onPointerUp: (e: React.PointerEvent) => void; onPointerCancel: (e: React.PointerEvent) => void; onPointerLeave: (e: React.PointerEvent) => void; } export function usePointerLongPress( callback: (e: React.PointerEvent) => void, options: LongPressOptions = {}, ): LongPressHandlers { const { thresholdMs = 500, moveTolerance = 8, includeMouse = false } = options; const timerRef = useRef(null); const startPosRef = useRef<{ x: number; y: number } | null>(null); const firedRef = useRef(false); const _clear = useCallback(() => { if (timerRef.current !== null) { window.clearTimeout(timerRef.current); timerRef.current = null; } startPosRef.current = null; firedRef.current = false; }, []); const onPointerDown = useCallback( (e: React.PointerEvent) => { if (!includeMouse && e.pointerType === 'mouse') return; _clear(); startPosRef.current = { x: e.clientX, y: e.clientY }; firedRef.current = false; timerRef.current = window.setTimeout(() => { firedRef.current = true; callback(e); }, thresholdMs); }, [callback, includeMouse, thresholdMs, _clear], ); const onPointerMove = useCallback( (e: React.PointerEvent) => { if (timerRef.current === null || !startPosRef.current) return; const dx = e.clientX - startPosRef.current.x; const dy = e.clientY - startPosRef.current.y; if (Math.abs(dx) > moveTolerance || Math.abs(dy) > moveTolerance) _clear(); }, [moveTolerance, _clear], ); return { onPointerDown, onPointerMove, onPointerUp: _clear, onPointerCancel: _clear, onPointerLeave: _clear, }; }