UNPKG

@supunlakmal/hooks

Version:

A collection of reusable React hooks

118 lines 5.67 kB
import { useCallback, useEffect, useRef } from 'react'; // Default options const defaultOptions = { threshold: 400, // Default long press duration }; /** * Custom hook to detect long press events on an element. * * @param ref - A React ref attached to the target element. * @param callback - Function to call when a long press is detected. * @param options - Optional configuration for the long press behavior. * @param options.threshold - Duration in ms until the press is considered long (default: 400). * @param options.onStart - Callback fired when the press starts. * @param options.onEnd - Callback fired when the press ends. * @param options.onCancel - Callback fired if the press is cancelled before duration. */ export const useLongPress = (ref, callback, { threshold = defaultOptions.threshold, onStart, onEnd, onCancel, } = {}) => { const timeoutRef = useRef(null); const targetRef = useRef(null); // Store the target element // Clears any active timer and resets target const clearTimer = useCallback(() => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } targetRef.current = null; // Reset target }, []); // Function called when the threshold is met const onLongPress = useCallback((event) => { if (callback && targetRef.current === event.target) { // Ensure target hasn't changed callback(event); } clearTimer(); // Clear timer after long press is triggered }, [callback, clearTimer]); // Start the timer on press down const start = useCallback((event) => { // Prevent default behavior for touch events, like scrolling if (event.type === 'touchstart') { // event.preventDefault(); // Consider adding an option for this } // Ensure the ref is current and not already pressing if (ref.current && ref.current.contains(event.target)) { targetRef.current = event.target; // Store the initial target onStart === null || onStart === void 0 ? void 0 : onStart(event); // Fire onStart callback // Clear any existing timer clearTimer(); // Set a new timer timeoutRef.current = setTimeout(() => onLongPress(event), threshold); } }, [ref, threshold, onStart, onLongPress, clearTimer]); // Clear the timer on press up or leave const cancel = useCallback((event) => { // Fire onEnd if a press was in progress on the target if (targetRef.current === event.target) { onEnd === null || onEnd === void 0 ? void 0 : onEnd(event); } // Fire onCancel if a timer was active (press was ongoing) if (timeoutRef.current && targetRef.current === event.target) { onCancel === null || onCancel === void 0 ? void 0 : onCancel(event); } clearTimer(); // Always clear timer on cancel/end actions }, [onEnd, onCancel, clearTimer]); // Mouse event handlers const handleMouseDown = useCallback((event) => start(event), [start]); const handleMouseUp = useCallback((event) => cancel(event), [cancel]); const handleMouseLeave = useCallback((event) => { // Cancel only if the mouse truly leaves the original target element if (targetRef.current === event.target) { cancel(event); } }, [cancel]); // Touch event handlers const handleTouchStart = useCallback((event) => start(event), [start]); const handleTouchEnd = useCallback((event) => cancel(event), [cancel]); // Touch cancel might happen for various reasons (e.g., moving finger off screen) const handleTouchCancel = useCallback((event) => cancel(event), [cancel]); // Add event listeners to the ref element useEffect(() => { const element = ref.current; if (element) { element.addEventListener('mousedown', handleMouseDown); element.addEventListener('mouseup', handleMouseUp); element.addEventListener('mouseleave', handleMouseLeave); element.addEventListener('touchstart', handleTouchStart, { passive: true }); // Use passive for touchstart for performance element.addEventListener('touchend', handleTouchEnd); element.addEventListener('touchcancel', handleTouchCancel); // Cleanup listeners on component unmount or ref change return () => { element.removeEventListener('mousedown', handleMouseDown); element.removeEventListener('mouseup', handleMouseUp); element.removeEventListener('mouseleave', handleMouseLeave); element.removeEventListener('touchstart', handleTouchStart); element.removeEventListener('touchend', handleTouchEnd); element.removeEventListener('touchcancel', handleTouchCancel); // Also clear timer on unmount clearTimer(); }; } // Add explicit return for when the element doesn't exist return () => { // Clean up any timers if the element wasn't available clearTimer(); }; // Intentionally not including clearTimer in the dependency array, // as its definition is stable due to useCallback([]). }, [ ref, handleMouseDown, handleMouseUp, handleMouseLeave, handleTouchStart, handleTouchEnd, handleTouchCancel, ]); // This hook doesn't return anything directly, it attaches listeners }; //# sourceMappingURL=useLongPress.js.map