ahooks
Version:
react hooks library
207 lines (206 loc) • 8.95 kB
JavaScript
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;