import { useRef, useCallback, useEffect } from 'react'; type LongPressOptions = { delay?: number; onLongPress: () => void; onTap?: () => void; enableHaptic?: boolean; }; export function useLongPress({ delay = 500, onLongPress, onTap, enableHaptic = true, }: LongPressOptions) { const timerRef = useRef | null>(null); const isLongPressRef = useRef(false); const startPosRef = useRef<{ x: number; y: number } | null>(null); const clear = useCallback(() => { if (timerRef.current) { clearTimeout(timerRef.current); timerRef.current = null; } startPosRef.current = null; }, []); const onPointerDown = useCallback((e: React.PointerEvent | React.TouchEvent) => { isLongPressRef.current = false; // Store start position to detect movement const clientX = 'touches' in e ? e.touches[0].clientX : (e as React.PointerEvent).clientX; const clientY = 'touches' in e ? e.touches[0].clientY : (e as React.PointerEvent).clientY; startPosRef.current = { x: clientX, y: clientY }; timerRef.current = setTimeout(() => { isLongPressRef.current = true; if (enableHaptic && typeof navigator !== 'undefined' && navigator.vibrate) { try { navigator.vibrate(15); } catch { // Ignore vibration errors } } onLongPress(); }, delay); }, [delay, onLongPress, enableHaptic]); const onPointerMove = useCallback((e: React.PointerEvent | React.TouchEvent) => { if (!startPosRef.current || !timerRef.current) return; const clientX = 'touches' in e ? e.touches[0].clientX : (e as React.PointerEvent).clientX; const clientY = 'touches' in e ? e.touches[0].clientY : (e as React.PointerEvent).clientY; // If moved more than 10px, cancel long press const dx = Math.abs(clientX - startPosRef.current.x); const dy = Math.abs(clientY - startPosRef.current.y); if (dx > 10 || dy > 10) { clear(); } }, [clear]); const onPointerUp = useCallback(() => { if (timerRef.current) { clearTimeout(timerRef.current); timerRef.current = null; if (!isLongPressRef.current && onTap) { // Only trigger tap if we didn't drag too far (checked in move) // and didn't trigger long press onTap(); } } clear(); }, [clear, onTap]); const onPointerLeave = useCallback(() => { clear(); }, [clear]); const onContextMenu = useCallback((e: React.MouseEvent) => { // Prevent default context menu on long press if (isLongPressRef.current) { e.preventDefault(); } }, []); useEffect(() => clear, [clear]); return { onPointerDown, onPointerMove, onPointerUp, onPointerLeave, onTouchStart: onPointerDown, // Add touch handlers for better mobile support onTouchMove: onPointerMove, onTouchEnd: onPointerUp, onContextMenu, }; }