import { useCallback, useRef } from 'react'; import type { TouchEvent } from 'react'; interface LongPressHandlers { onTouchStart: (e: TouchEvent) => void; onTouchMove: (e: TouchEvent) => void; onTouchEnd: (e: TouchEvent) => void; onTouchCancel: (e: TouchEvent) => void; } interface Options { ms?: number; // Suppress the synthetic click that follows touchend when long-press fired. suppressClickOnFire?: boolean; } // Hand-rolled long-press detector. Starts a timer on touchstart; cancels on // touchmove or early touchend; fires the callback on timer expiry. Caller is // expected to suppress text-selection callout via CSS (-webkit-touch-callout). export function useLongPress( callback: (touch: { clientX: number; clientY: number; target: EventTarget | null }) => void, { ms = 500, suppressClickOnFire = true }: Options = {}, ): LongPressHandlers { const timerRef = useRef(null); const firedRef = useRef(false); const clear = useCallback(() => { if (timerRef.current !== null) { window.clearTimeout(timerRef.current); timerRef.current = null; } }, []); const onTouchStart = useCallback( (e: TouchEvent) => { firedRef.current = false; const touch = e.touches[0]; if (!touch) return; const x = touch.clientX; const y = touch.clientY; const target = e.target; clear(); timerRef.current = window.setTimeout(() => { firedRef.current = true; callback({ clientX: x, clientY: y, target }); }, ms); }, [callback, ms, clear], ); const onTouchMove = useCallback(() => { clear(); }, [clear]); const onTouchEnd = useCallback( (e: TouchEvent) => { clear(); if (firedRef.current && suppressClickOnFire) { // Block the synthetic click that follows touchend; the long-press // already handled the gesture. e.preventDefault(); } }, [clear, suppressClickOnFire], ); const onTouchCancel = useCallback( (_e: TouchEvent) => { clear(); }, [clear], ); return { onTouchStart, onTouchMove, onTouchEnd, onTouchCancel }; }