75 lines
2.3 KiB
TypeScript
75 lines
2.3 KiB
TypeScript
import { useCallback, useRef } from 'react';
|
|
|
|
/**
|
|
* Long-Press-Erkennung über Pointer-Events.
|
|
*
|
|
* Liefert Handler die direkt auf `<div>` etc. gespreaded werden können.
|
|
* Ein "Long-Press" feuert nach `thresholdMs` (Default 500 ms) wenn der Pointer
|
|
* sich nicht weiter als `moveTolerance` Pixel bewegt hat.
|
|
*/
|
|
|
|
interface LongPressOptions {
|
|
thresholdMs?: number;
|
|
moveTolerance?: number;
|
|
/** Wenn ``true``, werden auch Maus-Events behandelt (für Desktop-Smoke-Tests). */
|
|
includeMouse?: boolean;
|
|
}
|
|
|
|
interface LongPressHandlers {
|
|
onPointerDown: (e: React.PointerEvent) => void;
|
|
onPointerMove: (e: React.PointerEvent) => void;
|
|
onPointerUp: (e: React.PointerEvent) => void;
|
|
onPointerCancel: (e: React.PointerEvent) => void;
|
|
onPointerLeave: (e: React.PointerEvent) => void;
|
|
}
|
|
|
|
export function usePointerLongPress(
|
|
callback: (e: React.PointerEvent) => void,
|
|
options: LongPressOptions = {},
|
|
): LongPressHandlers {
|
|
const { thresholdMs = 500, moveTolerance = 8, includeMouse = false } = options;
|
|
const timerRef = useRef<number | null>(null);
|
|
const startPosRef = useRef<{ x: number; y: number } | null>(null);
|
|
const firedRef = useRef(false);
|
|
|
|
const _clear = useCallback(() => {
|
|
if (timerRef.current !== null) {
|
|
window.clearTimeout(timerRef.current);
|
|
timerRef.current = null;
|
|
}
|
|
startPosRef.current = null;
|
|
firedRef.current = false;
|
|
}, []);
|
|
|
|
const onPointerDown = useCallback(
|
|
(e: React.PointerEvent) => {
|
|
if (!includeMouse && e.pointerType === 'mouse') return;
|
|
_clear();
|
|
startPosRef.current = { x: e.clientX, y: e.clientY };
|
|
firedRef.current = false;
|
|
timerRef.current = window.setTimeout(() => {
|
|
firedRef.current = true;
|
|
callback(e);
|
|
}, thresholdMs);
|
|
},
|
|
[callback, includeMouse, thresholdMs, _clear],
|
|
);
|
|
|
|
const onPointerMove = useCallback(
|
|
(e: React.PointerEvent) => {
|
|
if (timerRef.current === null || !startPosRef.current) return;
|
|
const dx = e.clientX - startPosRef.current.x;
|
|
const dy = e.clientY - startPosRef.current.y;
|
|
if (Math.abs(dx) > moveTolerance || Math.abs(dy) > moveTolerance) _clear();
|
|
},
|
|
[moveTolerance, _clear],
|
|
);
|
|
|
|
return {
|
|
onPointerDown,
|
|
onPointerMove,
|
|
onPointerUp: _clear,
|
|
onPointerCancel: _clear,
|
|
onPointerLeave: _clear,
|
|
};
|
|
}
|