@supunlakmal/hooks
Version:
A collection of reusable React hooks
118 lines • 5.67 kB
JavaScript
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