75 lines
2.1 KiB
TypeScript
75 lines
2.1 KiB
TypeScript
import { useCallback, useRef, type TouchEvent } from "react";
|
|
|
|
interface LongPressHandlers {
|
|
onTouchStart: (e: TouchEvent) => void;
|
|
onTouchMove: (e: TouchEvent) => void;
|
|
onTouchEnd: (e: TouchEvent) => void;
|
|
onTouchCancel: (e: TouchEvent) => void;
|
|
}
|
|
|
|
interface Options {
|
|
ms?: number;
|
|
// Suppress the synthetic click that follows touchend when long-press fired.
|
|
suppressClickOnFire?: boolean;
|
|
}
|
|
|
|
// Hand-rolled long-press detector. Starts a timer on touchstart; cancels on
|
|
// touchmove or early touchend; fires the callback on timer expiry. Caller is
|
|
// expected to suppress text-selection callout via CSS (-webkit-touch-callout).
|
|
export function useLongPress(
|
|
callback: (touch: { clientX: number; clientY: number; target: EventTarget | null }) => void,
|
|
{ ms = 500, suppressClickOnFire = true }: Options = {},
|
|
): LongPressHandlers {
|
|
const timerRef = useRef<number | null>(null);
|
|
const firedRef = useRef(false);
|
|
|
|
const clear = useCallback(() => {
|
|
if (timerRef.current !== null) {
|
|
window.clearTimeout(timerRef.current);
|
|
timerRef.current = null;
|
|
}
|
|
}, []);
|
|
|
|
const onTouchStart = useCallback(
|
|
(e: TouchEvent) => {
|
|
firedRef.current = false;
|
|
const touch = e.touches[0];
|
|
if (!touch) return;
|
|
const x = touch.clientX;
|
|
const y = touch.clientY;
|
|
const target = e.target;
|
|
clear();
|
|
timerRef.current = window.setTimeout(() => {
|
|
firedRef.current = true;
|
|
callback({ clientX: x, clientY: y, target });
|
|
}, ms);
|
|
},
|
|
[callback, ms, clear],
|
|
);
|
|
|
|
const onTouchMove = useCallback(() => {
|
|
clear();
|
|
}, [clear]);
|
|
|
|
const onTouchEnd = useCallback(
|
|
(e: TouchEvent) => {
|
|
clear();
|
|
if (firedRef.current && suppressClickOnFire) {
|
|
// Block the synthetic click that follows touchend; the long-press
|
|
// already handled the gesture.
|
|
e.preventDefault();
|
|
}
|
|
},
|
|
[clear, suppressClickOnFire],
|
|
);
|
|
|
|
const onTouchCancel = useCallback(
|
|
(_e: TouchEvent) => {
|
|
clear();
|
|
},
|
|
[clear],
|
|
);
|
|
|
|
return { onTouchStart, onTouchMove, onTouchEnd, onTouchCancel };
|
|
}
|