clicknhold
Version:
React hook for handling click and hold events
112 lines (90 loc) • 2.64 kB
text/typescript
import { useState, useRef, useCallback, useEffect } from 'react';
interface ClicknHoldOptions {
duration?: number;
onComplete?: () => void;
onStart?: () => void;
onCancel?: () => void;
onProgress?: (progress: number) => void;
disabled?: boolean;
}
type EventHandlers = {
onMouseDown: (event: React.MouseEvent) => void;
onMouseUp: () => void;
onMouseLeave: () => void;
onTouchStart: (event: React.TouchEvent) => void;
onTouchEnd: () => void;
onKeyDown: (event: React.KeyboardEvent) => void;
onKeyUp: (event: React.KeyboardEvent) => void;
}
export function useClicknHold({
duration = 2000,
onComplete,
onStart,
onCancel,
onProgress,
disabled = false
}: ClicknHoldOptions = {}) {
const [isHolding, setIsHolding] = useState(false);
const timerRef = useRef<number>();
const startTimeRef = useRef<number>();
const cleanup = useCallback(() => {
if (timerRef.current) {
window.clearInterval(timerRef.current);
timerRef.current = undefined;
}
startTimeRef.current = undefined;
setIsHolding(false);
}, []);
const startHolding = useCallback((event: React.MouseEvent | React.TouchEvent | React.KeyboardEvent) => {
if ('preventDefault' in event) {
event.preventDefault();
}
if (disabled) return;
startTimeRef.current = Date.now();
setIsHolding(true);
onStart?.();
timerRef.current = window.setInterval(() => {
const elapsedTime = Date.now() - startTimeRef.current!;
const progress = Math.min(elapsedTime / duration, 1);
onProgress?.(progress);
if (elapsedTime >= duration) {
cleanup();
onComplete?.();
}
}, 16);
}, [duration, onComplete, onStart, onProgress, cleanup, disabled]);
const stopHolding = useCallback(() => {
if (isHolding) {
cleanup();
onCancel?.();
}
}, [isHolding, cleanup, onCancel]);
const handleKeyDown = useCallback((event: React.KeyboardEvent) => {
if (event.key === 'Enter' || event.key === ' ') {
startHolding(event);
}
}, [startHolding]);
const handleKeyUp = useCallback((event: React.KeyboardEvent) => {
if (event.key === 'Enter' || event.key === ' ') {
stopHolding();
}
}, [stopHolding]);
useEffect(() => {
return cleanup;
}, [cleanup]);
const handlers: EventHandlers = {
onMouseDown: startHolding,
onMouseUp: stopHolding,
onMouseLeave: stopHolding,
onTouchStart: startHolding,
onTouchEnd: stopHolding,
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp,
};
return {
isHolding,
handlers,
startHolding,
stopHolding
};
}