@yandex/ui
Version:
Yandex UI components
92 lines (91 loc) • 4.45 kB
JavaScript
import { useCallback, useLayoutEffect, useRef } from 'react';
import { noop } from './noop';
/**
* Опции для подписки на события
*/
var listenerOptions = {
passive: false,
capture: false,
};
/**
* Предоставляет унифицированный интерфейс для работы с
* простыми тачевыми событиями (где используется один палец)
*/
export var useDrag = function (elementRef, onStateChange) {
var touchIdentifierRef = useRef();
var gestureStateRef = useRef();
var onStateChangeRef = useRef();
var handler = useCallback(function (event) {
var onStateChange = onStateChangeRef.current || noop;
var state = gestureStateRef.current;
var touch = Array.from(event.changedTouches).find(function (item) { return item.identifier === touchIdentifierRef.current; });
if (!state && event.type === 'touchstart' && event.changedTouches.length === 1) {
touch = event.changedTouches[0];
touchIdentifierRef.current = touch.identifier;
gestureStateRef.current = state = {
first: true,
last: false,
startTime: event.timeStamp,
initialPosition: { x: touch.clientX, y: touch.clientY },
data: {},
};
}
if (state && touch) {
// всегда обновляем ссылку на объект исходного события
state.event = event;
// сохраняет координаты предыдущего вызова функции
if (event.type === 'touchmove') {
state.first = false;
state.previousPosition = state.currentPosition;
}
if (event.type === 'touchstart' || event.type === 'touchmove') {
state.currentPosition = {
x: touch.clientX,
y: touch.clientY,
};
state.movement = {
x: state.currentPosition.x - state.initialPosition.x,
y: state.currentPosition.y - state.initialPosition.y,
};
state.delta = {
x: state.currentPosition.x - (state.previousPosition || state.initialPosition).x,
y: state.currentPosition.y - (state.previousPosition || state.initialPosition).y,
};
state.velocity = {
x: state.delta.x / (event.timeStamp - state.startTime - state.elapsedTime) || 0,
y: state.delta.y / (event.timeStamp - state.startTime - state.elapsedTime) || 0,
};
state.elapsedTime = event.timeStamp - state.startTime;
}
// жест завершен пользователем или был прекращен системой
if (event.type === 'touchend' || event.type === 'touchcancel') {
state.first = false;
state.last = true;
gestureStateRef.current = undefined;
touchIdentifierRef.current = undefined;
}
onStateChange(state);
}
}, [onStateChangeRef]);
// обновляем колбэк при каждом рендере, так нам не нужно будет использовать useCallback
onStateChangeRef.current = onStateChange;
/**
* Управляет подписками на события
*/
useLayoutEffect(function () {
if (!elementRef.current)
return;
var elem = elementRef.current;
elem.addEventListener('touchstart', handler, listenerOptions);
elem.addEventListener('touchmove', handler, listenerOptions);
elem.addEventListener('touchend', handler, listenerOptions);
elem.addEventListener('touchcancel', handler, listenerOptions);
return function () {
elem.removeEventListener('touchstart', handler, listenerOptions);
elem.removeEventListener('touchmove', handler, listenerOptions);
elem.removeEventListener('touchend', handler, listenerOptions);
elem.removeEventListener('touchcancel', handler, listenerOptions);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [elementRef, handler, elementRef.current]);
};