a tool for shared writing and social publishing
1import { useRef, useEffect, useCallback, useMemo } from "react";
2
3export const useLongPress = (cb: () => void, cancel?: boolean) => {
4 let longPressTimer = useRef<number>(undefined);
5 let isLongPress = useRef(false);
6 let startPosition = useRef<{
7 x: number;
8 y: number;
9 } | null>(null);
10 let mouseMoveListener = useRef<((e: MouseEvent) => void) | null>(null);
11 let touchMoveListener = useRef<((e: TouchEvent) => void) | null>(null);
12
13 let end = useCallback(() => {
14 // Clear the starting position
15 startPosition.current = null;
16 window.clearTimeout(longPressTimer.current);
17 longPressTimer.current = undefined;
18
19 // Remove event listeners
20 if (mouseMoveListener.current) {
21 window.removeEventListener("mousemove", mouseMoveListener.current);
22 mouseMoveListener.current = null;
23 }
24 if (touchMoveListener.current) {
25 window.removeEventListener("touchmove", touchMoveListener.current);
26 touchMoveListener.current = null;
27 }
28 }, []);
29
30 let onPointerDown = useCallback(
31 (e: React.MouseEvent) => {
32 let el = e.target as HTMLElement;
33 if (el.tagName === "SELECT") return;
34 if (e.button === 2) {
35 return;
36 }
37 // Set the starting position
38 startPosition.current = { x: e.clientX, y: e.clientY };
39
40 isLongPress.current = false;
41
42 longPressTimer.current = window.setTimeout(() => {
43 isLongPress.current = true;
44 cb();
45 end();
46 }, 500);
47
48 // Add mousemove and touchmove listeners
49 mouseMoveListener.current = (e: MouseEvent) => {
50 if (!startPosition.current) return;
51 // Calculate the distance moved
52 const distance = Math.sqrt(
53 Math.pow(e.clientX - startPosition.current.x, 2) +
54 Math.pow(e.clientY - startPosition.current.y, 2),
55 );
56 // Only end if the distance is greater than 16 pixels
57 if (distance > 16) {
58 end();
59 }
60 };
61
62 touchMoveListener.current = (e: TouchEvent) => {
63 if (!startPosition.current || !e.touches[0]) return;
64 const distance = Math.sqrt(
65 Math.pow(e.touches[0].clientX - startPosition.current.x, 2) +
66 Math.pow(e.touches[0].clientY - startPosition.current.y, 2),
67 );
68 if (distance > 16) {
69 end();
70 }
71 };
72
73 window.addEventListener("mousemove", mouseMoveListener.current);
74 window.addEventListener("touchmove", touchMoveListener.current);
75 },
76 [cb, end],
77 );
78
79 let click = useCallback((e: React.MouseEvent | React.PointerEvent) => {
80 if (isLongPress.current) e.preventDefault();
81 if (e.shiftKey) e.preventDefault();
82 }, []);
83
84 useEffect(() => {
85 if (cancel) {
86 end();
87 }
88 }, [cancel, end]);
89
90 return useMemo(
91 () => ({
92 isLongPress: isLongPress,
93 handlers: {
94 onPointerDown,
95 onPointerUp: end,
96 onClickCapture: click,
97 },
98 }),
99 [isLongPress, end, onPointerDown, click],
100 );
101};