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};