UNPKG

ahooks

Version:
207 lines (206 loc) 8.95 kB
import { useRef } from 'react'; import useLatest from '../useLatest'; import { getTargetElement } from '../utils/domTarget'; import useEffectWithTarget from '../utils/useEffectWithTarget'; function useLongPress(onLongPress, target, { delay = 300, moveThreshold, onClick, onLongPressEnd } = {}) { const onLongPressRef = useLatest(onLongPress); const onClickRef = useLatest(onClick); const onLongPressEndRef = useLatest(onLongPressEnd); const timerRef = useRef(undefined); const isTriggeredRef = useRef(false); const pervPositionRef = useRef({ x: 0, y: 0 }); const activePointerIdRef = useRef(undefined); const mousePressed = useRef(false); const touchPressed = useRef(false); const hasMoveThreshold = !!(((moveThreshold === null || moveThreshold === void 0 ? void 0 : moveThreshold.x) && moveThreshold.x > 0) || ((moveThreshold === null || moveThreshold === void 0 ? void 0 : moveThreshold.y) && moveThreshold.y > 0)); useEffectWithTarget(() => { const targetElement = getTargetElement(target); if (!(targetElement === null || targetElement === void 0 ? void 0 : targetElement.addEventListener)) { return; } const supportPointerEvent = typeof window !== 'undefined' && typeof window.PointerEvent === 'function'; const overThreshold = (event) => { const { clientX, clientY } = getClientPosition(event); const offsetX = Math.abs(clientX - pervPositionRef.current.x); const offsetY = Math.abs(clientY - pervPositionRef.current.y); return !!(((moveThreshold === null || moveThreshold === void 0 ? void 0 : moveThreshold.x) && offsetX > moveThreshold.x) || ((moveThreshold === null || moveThreshold === void 0 ? void 0 : moveThreshold.y) && offsetY > moveThreshold.y)); }; function getClientPosition(event) { if ('TouchEvent' in window && event instanceof TouchEvent) { return { clientX: event.touches[0].clientX, clientY: event.touches[0].clientY, }; } if (event instanceof MouseEvent) { return { clientX: event.clientX, clientY: event.clientY, }; } return { clientX: 0, clientY: 0 }; } const createTimer = (event) => { timerRef.current = setTimeout(() => { onLongPressRef.current(event); isTriggeredRef.current = true; }, delay); }; const clearTimer = () => { if (timerRef.current) { clearTimeout(timerRef.current); timerRef.current = undefined; } }; const finishPress = (event, triggerClick = true) => { var _a; clearTimer(); if (isTriggeredRef.current) { (_a = onLongPressEndRef.current) === null || _a === void 0 ? void 0 : _a.call(onLongPressEndRef, event); } else if (triggerClick && onClickRef.current) { onClickRef.current(event); } isTriggeredRef.current = false; }; const onPointerDown = (event) => { if (activePointerIdRef.current !== undefined || event.isPrimary === false) { return; } activePointerIdRef.current = event.pointerId; if (hasMoveThreshold) { pervPositionRef.current.x = event.clientX; pervPositionRef.current.y = event.clientY; } createTimer(event); }; const onPointerUp = (event) => { if (activePointerIdRef.current !== event.pointerId) { return; } activePointerIdRef.current = undefined; finishPress(event); }; const onPointerCancel = (event) => { if (activePointerIdRef.current !== event.pointerId) { return; } activePointerIdRef.current = undefined; finishPress(event, false); }; const onPointerMove = (event) => { if (activePointerIdRef.current === event.pointerId) { onMove(event); } }; const onTouchStart = (event) => { if (touchPressed.current) { return; } touchPressed.current = true; if (hasMoveThreshold) { const { clientX, clientY } = getClientPosition(event); pervPositionRef.current.x = clientX; pervPositionRef.current.y = clientY; } createTimer(event); }; const onMouseDown = (event) => { var _a; if ((_a = event === null || event === void 0 ? void 0 : event.sourceCapabilities) === null || _a === void 0 ? void 0 : _a.firesTouchEvents) { return; } mousePressed.current = true; if (hasMoveThreshold) { pervPositionRef.current.x = event.clientX; pervPositionRef.current.y = event.clientY; } createTimer(event); }; const onMove = (event) => { if (timerRef.current && overThreshold(event)) { clearTimer(); } }; const onTouchEnd = (event) => { if (!touchPressed.current) { return; } touchPressed.current = false; finishPress(event); }; const onMouseUp = (event) => { var _a; if ((_a = event === null || event === void 0 ? void 0 : event.sourceCapabilities) === null || _a === void 0 ? void 0 : _a.firesTouchEvents) { return; } if (!mousePressed.current) { return; } mousePressed.current = false; finishPress(event); }; const onMouseLeave = (event) => { if (!mousePressed.current) { return; } mousePressed.current = false; finishPress(event, false); }; if (supportPointerEvent) { targetElement.addEventListener('pointerdown', onPointerDown); targetElement.addEventListener('pointerup', onPointerUp); targetElement.addEventListener('pointerleave', onPointerCancel); targetElement.addEventListener('pointercancel', onPointerCancel); if (hasMoveThreshold) { targetElement.addEventListener('pointermove', onPointerMove); } return () => { clearTimer(); isTriggeredRef.current = false; activePointerIdRef.current = undefined; targetElement.removeEventListener('pointerdown', onPointerDown); targetElement.removeEventListener('pointerup', onPointerUp); targetElement.removeEventListener('pointerleave', onPointerCancel); targetElement.removeEventListener('pointercancel', onPointerCancel); if (hasMoveThreshold) { targetElement.removeEventListener('pointermove', onPointerMove); } }; } const onTouchCancel = (event) => { if (!touchPressed.current) { return; } touchPressed.current = false; finishPress(event, false); }; targetElement.addEventListener('mousedown', onMouseDown); targetElement.addEventListener('mouseup', onMouseUp); targetElement.addEventListener('mouseleave', onMouseLeave); targetElement.addEventListener('touchstart', onTouchStart); targetElement.addEventListener('touchend', onTouchEnd); targetElement.addEventListener('touchcancel', onTouchCancel); if (hasMoveThreshold) { targetElement.addEventListener('mousemove', onMove); targetElement.addEventListener('touchmove', onMove); } return () => { clearTimer(); isTriggeredRef.current = false; targetElement.removeEventListener('mousedown', onMouseDown); targetElement.removeEventListener('mouseup', onMouseUp); targetElement.removeEventListener('mouseleave', onMouseLeave); targetElement.removeEventListener('touchstart', onTouchStart); targetElement.removeEventListener('touchend', onTouchEnd); targetElement.removeEventListener('touchcancel', onTouchCancel); if (hasMoveThreshold) { targetElement.removeEventListener('mousemove', onMove); targetElement.removeEventListener('touchmove', onMove); } }; }, [], target); } export default useLongPress;