1import { useCallback, useRef, useState } from "react";
2
3export function useLongPress(
4 onLongPress: () => void,
5 onClick: () => void,
6 { shouldPreventDefault = true, delay = 300 } = {}
7) {
8 const [longPressTriggered, setLongPressTriggered] = useState(false);
9 const timeout = useRef<number>();
10 const target = useRef<EventTarget>();
11
12 const start = useCallback(
13 (event: React.MouseEvent | React.TouchEvent) => {
14 if (shouldPreventDefault && event.target) {
15 event.target.addEventListener("touchend", preventDefault, {
16 passive: false,
17 });
18 target.current = event.target;
19 }
20 timeout.current = window.setTimeout(() => {
21 onLongPress();
22 setLongPressTriggered(true);
23 }, delay);
24 },
25 [onLongPress, delay, shouldPreventDefault]
26 );
27
28 const clear = useCallback(
29 (event: React.MouseEvent | React.TouchEvent, shouldTriggerClick = true) => {
30 timeout.current && clearTimeout(timeout.current);
31 shouldTriggerClick && !longPressTriggered && onClick();
32 setLongPressTriggered(false);
33 if (shouldPreventDefault && target.current) {
34 target.current.removeEventListener("touchend", preventDefault);
35 }
36 },
37 [shouldPreventDefault, onClick, longPressTriggered]
38 );
39
40 return {
41 onMouseDown: (e: React.MouseEvent) => start(e),
42 onTouchStart: (e: React.TouchEvent) => start(e),
43 onMouseUp: (e: React.MouseEvent) => clear(e),
44 onMouseLeave: (e: React.MouseEvent) => clear(e, false),
45 onTouchEnd: (e: React.TouchEvent) => clear(e),
46 };
47}
48
49const preventDefault = (e: Event) => {
50 if (!isTouchEvent(e)) return;
51 if (e.touches.length < 2 && e.preventDefault) {
52 e.preventDefault();
53 }
54};
55
56const isTouchEvent = (e: Event): e is TouchEvent => {
57 return "touches" in e;
58};