@vueuse/gesture
Version:
🕹 Vue Composables making your app interactive
1,590 lines (1,571 loc) • 64.4 kB
JavaScript
import { unref, nextTick, getCurrentInstance, onMounted, onUnmounted, ref } from 'vue-demi';
function supportsGestureEvents() {
try {
return "constructor" in GestureEvent;
} catch (e) {
return false;
}
}
function supportsTouchEvents() {
return typeof window !== "undefined" && "ontouchstart" in window;
}
function getEventTouches(event) {
if ("pointerId" in event)
return null;
return event.type === "touchend" ? event.changedTouches : event.targetTouches;
}
function getTouchIds(event) {
return Array.from(getEventTouches(event)).map((t) => t.identifier);
}
function getGenericEventData(event) {
const buttons = "buttons" in event ? event.buttons : 0;
const { shiftKey, altKey, metaKey, ctrlKey } = event;
return { buttons, shiftKey, altKey, metaKey, ctrlKey };
}
const identity$1 = (xy) => xy;
function getPointerEventValues(event, transform = identity$1) {
const touchEvents = getEventTouches(event);
const { clientX, clientY } = touchEvents ? touchEvents[0] : event;
return transform([clientX, clientY]);
}
function getTwoTouchesEventValues(event, pointerIds, transform = identity$1) {
const [A, B] = Array.from(event.touches).filter(
(t) => pointerIds.includes(t.identifier)
);
if (!A || !B)
throw Error(`The event doesn't have two pointers matching the pointerIds`);
const dx = B.clientX - A.clientX;
const dy = B.clientY - A.clientY;
const cx = (B.clientX + A.clientX) / 2;
const cy = (B.clientY + A.clientY) / 2;
const distance = Math.hypot(dx, dy);
const angle = -(Math.atan2(dx, dy) * 180) / Math.PI;
const values = transform([distance, angle]);
const origin = transform([cx, cy]);
return { values, origin };
}
function getScrollEventValues(event, transform = identity$1) {
const {
scrollX,
scrollY,
scrollLeft,
scrollTop
} = event.currentTarget;
return transform([scrollX || scrollLeft || 0, scrollY || scrollTop || 0]);
}
const LINE_HEIGHT = 40;
const PAGE_HEIGHT = 800;
function getWheelEventValues(event, transform = identity$1) {
let { deltaX, deltaY, deltaMode } = event;
if (deltaMode === 1) {
deltaX *= LINE_HEIGHT;
deltaY *= LINE_HEIGHT;
} else if (deltaMode === 2) {
deltaX *= PAGE_HEIGHT;
deltaY *= PAGE_HEIGHT;
}
return transform([deltaX, deltaY]);
}
function getWebkitGestureEventValues(event, transform = identity$1) {
return transform([event.scale, event.rotation]);
}
function noop() {
}
function chainFns(...fns) {
if (fns.length === 0)
return noop;
if (fns.length === 1)
return fns[0];
return function() {
var result;
for (let fn of fns) {
result = fn.apply(this, arguments) || result;
}
return result;
};
}
function ensureVector(value, fallback) {
if (value === void 0) {
if (fallback === void 0) {
throw new Error("Must define fallback value if undefined is expected");
}
value = fallback;
}
if (Array.isArray(value))
return value;
return [value, value];
}
function assignDefault(value, fallback) {
return Object.assign({}, fallback, value || {});
}
function valueFn(v, ...args) {
if (typeof v === "function") {
return v(...args);
} else {
return v;
}
}
function getInitial(mixed) {
return {
_active: false,
_blocked: false,
_intentional: [false, false],
_movement: [0, 0],
_initial: [0, 0],
_bounds: [
[-Infinity, Infinity],
[-Infinity, Infinity]
],
_lastEventType: void 0,
_dragStarted: false,
_dragPreventScroll: false,
_dragIsTap: true,
_dragDelayed: false,
event: void 0,
intentional: false,
values: [0, 0],
velocities: [0, 0],
delta: [0, 0],
movement: [0, 0],
offset: [0, 0],
lastOffset: [0, 0],
direction: [0, 0],
initial: [0, 0],
previous: [0, 0],
first: false,
last: false,
active: false,
timeStamp: 0,
startTime: 0,
elapsedTime: 0,
cancel: noop,
canceled: false,
memo: void 0,
args: void 0,
...mixed
};
}
function getInitialState() {
const shared = {
hovering: false,
scrolling: false,
wheeling: false,
dragging: false,
moving: false,
pinching: false,
touches: 0,
buttons: 0,
down: false,
shiftKey: false,
altKey: false,
metaKey: false,
ctrlKey: false,
locked: false
};
const drag = getInitial({
_pointerId: void 0,
axis: void 0,
xy: [0, 0],
vxvy: [0, 0],
velocity: 0,
distance: 0,
tap: false,
swipe: [0, 0]
});
const pinch = getInitial({
// @ts-expect-error when used _pointerIds we can assert its type will be [number, number]
_pointerIds: [],
da: [0, 0],
vdva: [0, 0],
// @ts-expect-error origin can never be passed as undefined in userland
origin: void 0,
turns: 0
});
const wheel = getInitial({
axis: void 0,
xy: [0, 0],
vxvy: [0, 0],
velocity: 0,
distance: 0
});
const move = getInitial({
axis: void 0,
xy: [0, 0],
vxvy: [0, 0],
velocity: 0,
distance: 0
});
const scroll = getInitial({
axis: void 0,
xy: [0, 0],
vxvy: [0, 0],
velocity: 0,
distance: 0
});
return { shared, drag, pinch, wheel, move, scroll };
}
var __defProp$6 = Object.defineProperty;
var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$6 = (obj, key, value) => {
__defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
class Controller {
constructor(classes) {
this.classes = classes;
__publicField$6(this, "nativeRefs");
__publicField$6(this, "config");
__publicField$6(this, "handlers");
__publicField$6(this, "state");
// state for all gestures
__publicField$6(this, "timeouts");
// tracks timeouts of debounced gestures
__publicField$6(this, "domListeners");
// when config.domTarget is set, we attach events directly to the dom
__publicField$6(this, "windowListeners");
// keeps track of window listeners added by gestures (drag only at the moment)
__publicField$6(this, "pointerIds", /* @__PURE__ */ new Set());
// register Pointer Events pointerIds
__publicField$6(this, "touchIds", /* @__PURE__ */ new Set());
// register Touch Events identifiers
__publicField$6(this, "supportsTouchEvents", supportsTouchEvents());
__publicField$6(this, "supportsGestureEvents", supportsGestureEvents());
__publicField$6(this, "bind", (...args) => {
const bindings = {};
for (let RecognizerClass2 of this.classes)
new RecognizerClass2(this, args).addBindings(bindings);
for (let eventKey in this.nativeRefs) {
addBindings(
bindings,
eventKey,
(event) => this.nativeRefs[eventKey]({ ...this.state.shared, event, args })
);
}
if (this.config.domTarget) {
return updateDomListeners(this, bindings);
} else {
return getPropsListener(this, bindings);
}
});
/**
* Function ran on component unmount: cleans timeouts and removes dom listeners set by the bind function.
*/
__publicField$6(this, "clean", () => {
const { eventOptions, domTarget } = this.config;
const _target = unref(domTarget);
if (_target)
removeListeners(_target, takeAll(this.domListeners), eventOptions);
Object.values(this.timeouts).forEach(clearTimeout);
clearAllWindowListeners(this);
});
/**
* Resets state to the initial value.
*/
__publicField$6(this, "reset", () => {
this.state = getInitialState();
});
this.classes = classes;
this.state = getInitialState();
this.timeouts = {};
this.domListeners = [];
this.windowListeners = {};
}
}
function addEventIds(controller, event) {
if ("pointerId" in event) {
controller.pointerIds.add(event.pointerId);
} else {
controller.touchIds = new Set(getTouchIds(event));
}
}
function removeEventIds(controller, event) {
if ("pointerId" in event) {
controller.pointerIds.delete(event.pointerId);
} else {
getTouchIds(event).forEach((id) => controller.touchIds.delete(id));
}
}
function clearAllWindowListeners(controller) {
const {
config: { window: el, eventOptions },
windowListeners
} = controller;
const _el = unref(el);
if (!_el)
return;
for (let stateKey in windowListeners) {
const handlers = windowListeners[stateKey];
removeListeners(_el, handlers, eventOptions);
}
controller.windowListeners = {};
}
function clearWindowListeners({ config, windowListeners }, stateKey, options = config.eventOptions) {
const _window = unref(config.window);
if (!_window)
return;
removeListeners(_window, windowListeners[stateKey], options);
delete windowListeners[stateKey];
}
function updateWindowListeners({ config, windowListeners }, stateKey, listeners = [], options = config.eventOptions) {
const _window = unref(config.window);
if (!_window)
return;
removeListeners(_window, windowListeners[stateKey], options);
addListeners(_window, windowListeners[stateKey] = listeners, options);
}
function updateDomListeners({ config, domListeners }, bindings) {
const { eventOptions, domTarget } = config;
const _target = unref(domTarget);
if (!_target)
throw new Error("domTarget must be defined");
removeListeners(_target, takeAll(domListeners), eventOptions);
for (let [key, fns] of Object.entries(bindings)) {
const name = key.slice(2).toLowerCase();
domListeners.push([name, chainFns(...fns)]);
}
addListeners(_target, domListeners, eventOptions);
}
function getPropsListener({ config }, bindings) {
const props = {};
const captureString = config.eventOptions.capture ? "Capture" : "";
for (let [event, fns] of Object.entries(bindings)) {
const fnsArray = Array.isArray(fns) ? fns : [fns];
const key = event + captureString;
props[key] = chainFns(...fnsArray);
}
return props;
}
function takeAll(array = []) {
return array.splice(0, array.length);
}
function addBindings(bindings, name, fn) {
if (!bindings[name])
bindings[name] = [];
bindings[name].push(fn);
}
function addListeners(el, listeners = [], options = {}) {
if (!el)
return;
for (let [eventName, eventHandler] of listeners) {
el.addEventListener(eventName, eventHandler, options);
}
}
function removeListeners(el, listeners = [], options = {}) {
if (!el)
return;
for (let [eventName, eventHandler] of listeners) {
el.removeEventListener(eventName, eventHandler, options);
}
}
function addV(v1, v2) {
return v1.map((v, i) => v + v2[i]);
}
function subV(v1, v2) {
return v1.map((v, i) => v - v2[i]);
}
function calculateDistance(movement) {
return Math.hypot(...movement);
}
function calculateAllGeometry(movement, delta = movement) {
const dl = calculateDistance(delta);
const alpha = dl === 0 ? 0 : 1 / dl;
const direction = delta.map((v) => alpha * v);
const distance = calculateDistance(movement);
return { distance, direction };
}
function calculateAllKinematics(movement, delta, dt) {
const dl = calculateDistance(delta);
const alpha = dl === 0 ? 0 : 1 / dl;
const beta = dt === 0 ? 0 : 1 / dt;
const velocity = beta * dl;
const velocities = delta.map((v) => beta * v);
const direction = delta.map((v) => alpha * v);
const distance = calculateDistance(movement);
return { velocities, velocity, distance, direction };
}
function sign(x) {
if (Math.sign)
return Math.sign(x);
return Number(x > 0) - Number(x < 0) || +x;
}
function minMax(value, min, max) {
return Math.max(min, Math.min(value, max));
}
function rubberband2(distance, constant) {
return Math.pow(distance, constant * 5);
}
function rubberband(distance, dimension, constant) {
if (dimension === 0 || Math.abs(dimension) === Infinity)
return rubberband2(distance, constant);
return distance * dimension * constant / (dimension + constant * distance);
}
function rubberbandIfOutOfBounds(position, min, max, constant = 0.15) {
if (constant === 0)
return minMax(position, min, max);
if (position < min)
return -rubberband(min - position, max - min, constant) + min;
if (position > max)
return +rubberband(position - max, max - min, constant) + max;
return position;
}
var __defProp$5 = Object.defineProperty;
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$5 = (obj, key, value) => {
__defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
const RecognizersMap = /* @__PURE__ */ new Map();
const identity = (xy) => xy;
class Recognizer {
/**
* Creates an instance of a gesture recognizer.
* @param stateKey drag, move, pinch, etc.
* @param controller the controller attached to the gesture
* @param [args] the args that should be passed to the gesture handler
*/
constructor(controller, args = []) {
this.controller = controller;
this.args = args;
// dragging, scrolling, etc.
__publicField$5(this, "debounced", true);
// Convenience method to set a timeout for a given gesture
__publicField$5(this, "setTimeout", (callback, ms = 140, ...args) => {
clearTimeout(this.controller.timeouts[this.stateKey]);
this.controller.timeouts[this.stateKey] = window.setTimeout(
callback,
ms,
...args
);
});
// Convenience method to clear a timeout for a given gesture
__publicField$5(this, "clearTimeout", () => {
clearTimeout(this.controller.timeouts[this.stateKey]);
});
/**
* Fires the gesture handler
*/
__publicField$5(this, "fireGestureHandler", (forceFlag = false) => {
if (this.state._blocked) {
if (!this.debounced) {
this.state._active = false;
this.clean();
}
return null;
}
if (!forceFlag && !this.state.intentional && !this.config.triggerAllEvents)
return null;
if (this.state.intentional) {
const prev_active = this.state.active;
const next_active = this.state._active;
this.state.active = next_active;
this.state.first = next_active && !prev_active;
this.state.last = prev_active && !next_active;
this.controller.state.shared[this.ingKey] = next_active;
}
const touches = this.controller.pointerIds.size || this.controller.touchIds.size;
const down = this.controller.state.shared.buttons > 0 || touches > 0;
const state = {
...this.controller.state.shared,
...this.state,
...this.mapStateValues(this.state),
// Sets xy or da to the gesture state values
locked: !!document.pointerLockElement,
touches,
down
};
const newMemo = this.handler(state);
this.state.memo = newMemo !== void 0 ? newMemo : this.state.memo;
return state;
});
this.controller = controller;
this.args = args;
}
// Returns the gesture config
get config() {
return this.controller.config[this.stateKey];
}
// Is the gesture enabled
get enabled() {
return this.controller.config.enabled && this.config.enabled;
}
// Returns the controller state for a given gesture
get state() {
return this.controller.state[this.stateKey];
}
// Returns the gesture handler
get handler() {
return this.controller.handlers[this.stateKey];
}
get transform() {
return this.config.transform || this.controller.config.transform || identity;
}
// Convenience method to update the shared state
updateSharedState(sharedState) {
Object.assign(this.controller.state.shared, sharedState);
}
// Convenience method to update the gesture state
updateGestureState(gestureState) {
Object.assign(this.state, gestureState);
}
/**
* Returns state properties depending on the movement and state.
*
* Should be overriden for custom behavior, doesn't do anything in the implementation
* below.
*/
checkIntentionality(_intentional, _movement) {
return { _intentional, _blocked: false };
}
/**
* Returns basic movement properties for the gesture based on the next values and current state.
*/
getMovement(values) {
const { rubberband, threshold: T } = this.config;
const {
_bounds,
_initial,
_active,
_intentional: wasIntentional,
lastOffset,
movement: prevMovement
} = this.state;
const M = this.getInternalMovement(values, this.state);
const _T = this.transform(T).map(Math.abs);
const i0 = wasIntentional[0] === false ? getIntentionalDisplacement(M[0], _T[0]) : wasIntentional[0];
const i1 = wasIntentional[1] === false ? getIntentionalDisplacement(M[1], _T[1]) : wasIntentional[1];
const intentionalityCheck = this.checkIntentionality([i0, i1], M);
if (intentionalityCheck._blocked) {
return { ...intentionalityCheck, _movement: M, delta: [0, 0] };
}
const _intentional = intentionalityCheck._intentional;
const _movement = M;
let movement = [
_intentional[0] !== false ? M[0] - _intentional[0] : 0,
_intentional[1] !== false ? M[1] - _intentional[1] : 0
];
const offset = addV(movement, lastOffset);
const _rubberband = _active ? rubberband : [0, 0];
movement = computeRubberband(_bounds, addV(movement, _initial), _rubberband);
return {
...intentionalityCheck,
intentional: _intentional[0] !== false || _intentional[1] !== false,
_initial,
_movement,
movement,
values,
offset: computeRubberband(_bounds, offset, _rubberband),
delta: subV(movement, prevMovement)
};
}
// Cleans the gesture. Can be overriden by gestures.
clean() {
this.clearTimeout();
}
}
function getIntentionalDisplacement(movement, threshold) {
if (Math.abs(movement) >= threshold) {
return sign(movement) * threshold;
} else {
return false;
}
}
function computeRubberband(bounds, [Vx, Vy], [Rx, Ry]) {
const [[X1, X2], [Y1, Y2]] = bounds;
return [
rubberbandIfOutOfBounds(Vx, X1, X2, Rx),
rubberbandIfOutOfBounds(Vy, Y1, Y2, Ry)
];
}
function getGenericPayload({ state }, event, isStartEvent) {
const { timeStamp, type: _lastEventType } = event;
const previous = state.values;
const elapsedTime = isStartEvent ? 0 : timeStamp - state.startTime;
return { _lastEventType, event, timeStamp, elapsedTime, previous };
}
function getStartGestureState({ state, config, stateKey, args }, values, event) {
const offset = state.offset;
const startTime = event.timeStamp;
const { initial, bounds } = config;
const _state = {
...getInitialState()[stateKey],
_active: true,
args,
values,
initial: values,
offset,
lastOffset: offset,
startTime
};
return {
..._state,
_initial: valueFn(initial, _state),
_bounds: valueFn(bounds, _state)
};
}
class CoordinatesRecognizer extends Recognizer {
/**
* Returns the real movement (without taking intentionality into account)
*/
getInternalMovement(values, state) {
return subV(values, state.initial);
}
/**
* In coordinates-based gesture, this function will detect the first intentional axis,
* lock the gesture axis if lockDirection is specified in the config, block the gesture
* if the first intentional axis doesn't match the specified axis in config.
*/
checkIntentionality(_intentional, _movement) {
if (_intentional[0] === false && _intentional[1] === false) {
return { _intentional, axis: this.state.axis };
}
const [absX, absY] = _movement.map(Math.abs);
const axis = this.state.axis || (absX > absY ? "x" : absX < absY ? "y" : void 0);
if (!this.config.axis && !this.config.lockDirection)
return { _intentional, _blocked: false, axis };
if (!axis)
return { _intentional: [false, false], _blocked: false, axis };
if (!!this.config.axis && axis !== this.config.axis)
return { _intentional, _blocked: true, axis };
_intentional[axis === "x" ? 1 : 0] = false;
return { _intentional, _blocked: false, axis };
}
getKinematics(values, event) {
const state = this.getMovement(values);
if (!state._blocked) {
const dt = event.timeStamp - this.state.timeStamp;
Object.assign(
state,
calculateAllKinematics(state.movement, state.delta, dt)
);
}
return state;
}
mapStateValues(state) {
return { xy: state.values, vxvy: state.velocities };
}
}
var __defProp$4 = Object.defineProperty;
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$4 = (obj, key, value) => {
__defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
const TAP_DISTANCE_THRESHOLD = 3;
function persistEvent(event) {
"persist" in event && typeof event.persist === "function" && event.persist();
}
class DragRecognizer extends CoordinatesRecognizer {
constructor() {
super(...arguments);
__publicField$4(this, "ingKey", "dragging");
__publicField$4(this, "stateKey", "drag");
// TODO: add back when setPointerCapture is widely supported
// https://caniuse.com/#search=setPointerCapture
__publicField$4(this, "setPointerCapture", (event) => {
if (this.config.useTouch || document.pointerLockElement)
return;
const { target, pointerId } = event;
if (target && "setPointerCapture" in target) {
target.setPointerCapture(pointerId);
}
this.updateGestureState({
_dragTarget: target,
_dragPointerId: pointerId
});
});
__publicField$4(this, "releasePointerCapture", () => {
if (this.config.useTouch || document.pointerLockElement)
return;
const { _dragTarget, _dragPointerId } = this.state;
if (_dragPointerId && _dragTarget && "releasePointerCapture" in _dragTarget) {
if (!("hasPointerCapture" in _dragTarget) || _dragTarget.hasPointerCapture(_dragPointerId))
try {
_dragTarget.releasePointerCapture(_dragPointerId);
} catch (e) {
}
}
});
__publicField$4(this, "preventScroll", (event) => {
if (this.state._dragPreventScroll && event.cancelable) {
event.preventDefault();
}
});
__publicField$4(this, "getEventId", (event) => {
if (this.config.useTouch)
return event.changedTouches[0].identifier;
return event.pointerId;
});
__publicField$4(this, "isValidEvent", (event) => {
return this.state._pointerId === this.getEventId(event);
});
__publicField$4(this, "shouldPreventWindowScrollY", this.config.preventWindowScrollY && this.controller.supportsTouchEvents);
__publicField$4(this, "setUpWindowScrollDetection", (event) => {
persistEvent(event);
updateWindowListeners(
this.controller,
this.stateKey,
[
["touchmove", this.preventScroll],
["touchend", this.clean.bind(this)],
["touchcancel", this.clean.bind(this)]
],
{ passive: false }
);
this.setTimeout(this.startDrag.bind(this), 250, event);
});
__publicField$4(this, "setUpDelayedDragTrigger", (event) => {
this.state._dragDelayed = true;
persistEvent(event);
this.setTimeout(this.startDrag.bind(this), this.config.delay, event);
});
__publicField$4(this, "setStartState", (event) => {
const values = getPointerEventValues(event, this.transform);
this.updateSharedState(getGenericEventData(event));
this.updateGestureState({
...getStartGestureState(this, values, event),
...getGenericPayload(this, event, true),
_pointerId: this.getEventId(event)
// setting pointerId locks the gesture to this specific event
});
this.updateGestureState(this.getMovement(values));
});
__publicField$4(this, "onDragStart", (event) => {
addEventIds(this.controller, event);
if (!this.enabled || this.state._active)
return;
this.setStartState(event);
this.setPointerCapture(event);
if (this.shouldPreventWindowScrollY)
this.setUpWindowScrollDetection(event);
else if (this.config.delay > 0)
this.setUpDelayedDragTrigger(event);
else
this.startDrag(event, true);
});
__publicField$4(this, "onDragChange", (event) => {
if (
// if the gesture was canceled or
this.state.canceled || // if onDragStart wasn't fired or
!this.state._active || // if the event pointerId doesn't match the one that initiated the drag
!this.isValidEvent(event) || // if the event has the same timestamp as the previous event
// note that checking type equality is ONLY for tests ¯\_(ツ)_/¯
this.state._lastEventType === event.type && event.timeStamp === this.state.timeStamp
)
return;
let values;
if (document.pointerLockElement) {
const { movementX, movementY } = event;
values = addV(this.transform([movementX, movementY]), this.state.values);
} else
values = getPointerEventValues(event, this.transform);
const kinematics = this.getKinematics(values, event);
if (!this.state._dragStarted) {
if (this.state._dragDelayed) {
this.startDrag(event);
return;
}
if (this.shouldPreventWindowScrollY) {
if (!this.state._dragPreventScroll && kinematics.axis) {
if (kinematics.axis === "x") {
this.startDrag(event);
} else {
this.state._active = false;
return;
}
} else
return;
} else
return;
}
const genericEventData = getGenericEventData(event);
this.updateSharedState(genericEventData);
const genericPayload = getGenericPayload(this, event);
const realDistance = calculateDistance(kinematics._movement);
let { _dragIsTap } = this.state;
if (_dragIsTap && realDistance >= TAP_DISTANCE_THRESHOLD)
_dragIsTap = false;
this.updateGestureState({ ...genericPayload, ...kinematics, _dragIsTap });
this.fireGestureHandler();
});
__publicField$4(this, "onDragEnd", (event) => {
removeEventIds(this.controller, event);
if (!this.isValidEvent(event))
return;
this.clean();
if (!this.state._active)
return;
this.state._active = false;
const tap = this.state._dragIsTap;
const [vx, vy] = this.state.velocities;
const [mx, my] = this.state.movement;
const [ix, iy] = this.state._intentional;
const [svx, svy] = this.config.swipeVelocity;
const [sx, sy] = this.config.swipeDistance;
const sd = this.config.swipeDuration;
const endState = {
...getGenericPayload(this, event),
...this.getMovement(this.state.values)
};
const swipe = [0, 0];
if (endState.elapsedTime < sd) {
if (ix !== false && Math.abs(vx) > svx && Math.abs(mx) > sx)
swipe[0] = sign(vx);
if (iy !== false && Math.abs(vy) > svy && Math.abs(my) > sy)
swipe[1] = sign(vy);
}
this.updateSharedState({ buttons: 0 });
this.updateGestureState({ ...endState, tap, swipe });
this.fireGestureHandler(this.config.filterTaps && tap === true);
});
__publicField$4(this, "clean", () => {
super.clean();
this.state._dragStarted = false;
this.releasePointerCapture();
clearWindowListeners(this.controller, this.stateKey);
});
__publicField$4(this, "onCancel", () => {
if (this.state.canceled)
return;
this.updateGestureState({ canceled: true, _active: false });
this.updateSharedState({ buttons: 0 });
nextTick(this.fireGestureHandler);
});
__publicField$4(this, "onClick", (event) => {
if (!this.state._dragIsTap)
event.stopPropagation();
});
}
startDrag(event, onDragIsStart = false) {
if (
// if the gesture isn't active (probably means)
!this.state._active || // if the drag has already started we should ignore subsequent attempts
this.state._dragStarted
)
return;
if (!onDragIsStart)
this.setStartState(event);
this.updateGestureState({
_dragStarted: true,
_dragPreventScroll: true,
cancel: this.onCancel
});
this.clearTimeout();
this.fireGestureHandler();
}
addBindings(bindings) {
if (this.config.useTouch) {
addBindings(bindings, "onTouchStart", this.onDragStart);
addBindings(bindings, "onTouchMove", this.onDragChange);
addBindings(bindings, "onTouchEnd", this.onDragEnd);
addBindings(bindings, "onTouchCancel", this.onDragEnd);
} else {
addBindings(bindings, "onPointerDown", this.onDragStart);
addBindings(bindings, "onPointerMove", this.onDragChange);
addBindings(bindings, "onPointerUp", this.onDragEnd);
addBindings(bindings, "onPointerCancel", this.onDragEnd);
}
if (this.config.filterTaps) {
const handler = this.controller.config.eventOptions.capture ? "onClick" : "onClickCapture";
addBindings(bindings, handler, this.onClick);
}
}
}
function memoizeOne(resultFn, isEqual) {
let lastThis;
let lastArgs = [];
let lastResult;
let calledOnce = false;
function memoized(...newArgs) {
if (calledOnce && lastThis === this && isEqual(newArgs, lastArgs)) {
return lastResult;
}
lastResult = resultFn.apply(this, newArgs);
calledOnce = true;
lastThis = this;
lastArgs = newArgs;
return lastResult;
}
return memoized;
}
function equal(a, b) {
if (a === b)
return true;
if (a && b && typeof a == "object" && typeof b == "object") {
if (a.constructor !== b.constructor)
return false;
let length, i, keys;
if (Array.isArray(a)) {
length = a.length;
if (length !== b.length)
return false;
for (i = length; i-- !== 0; )
if (!equal(a[i], b[i]))
return false;
return true;
}
let it;
if (typeof Map === "function" && a instanceof Map && b instanceof Map) {
if (a.size !== b.size)
return false;
it = a.entries();
while (!(i = it.next()).done)
if (!b.has(i.value[0]))
return false;
it = a.entries();
while (!(i = it.next()).done)
if (!equal(i.value[1], b.get(i.value[0])))
return false;
return true;
}
if (typeof Set === "function" && a instanceof Set && b instanceof Set) {
if (a.size !== b.size)
return false;
it = a.entries();
while (!(i = it.next()).done)
if (!b.has(i.value[0]))
return false;
return true;
}
if (a.constructor === RegExp)
return a.source === b.source && a.flags === b.flags;
if (a.valueOf !== Object.prototype.valueOf)
return a.valueOf() === b.valueOf();
if (a.toString !== Object.prototype.toString)
return a.toString() === b.toString();
keys = Object.keys(a);
length = keys.length;
if (length !== Object.keys(b).length)
return false;
for (i = length; i-- !== 0; )
if (!Object.prototype.hasOwnProperty.call(b, keys[i]))
return false;
if (typeof Element !== "undefined" && a instanceof Element)
return false;
for (i = length; i-- !== 0; ) {
if (keys[i] === "_owner" && a.$$typeof)
continue;
if (!equal(a[keys[i]], b[keys[i]]))
return false;
}
return true;
}
return a !== a && b !== b;
}
function isEqual(a, b) {
try {
return equal(a, b);
} catch (error) {
if ((error.message || "").match(/stack|recursion/i)) {
console.warn("react-fast-compare cannot handle circular refs");
return false;
}
throw error;
}
}
function resolveWith(config = {}, resolvers) {
const result = {};
for (const [key, resolver] of Object.entries(resolvers))
switch (typeof resolver) {
case "function":
result[key] = resolver.call(result, config[key], key, config);
break;
case "object":
result[key] = resolveWith(config[key], resolver);
break;
case "boolean":
if (resolver)
result[key] = config[key];
break;
}
return result;
}
const DEFAULT_DRAG_DELAY = 180;
const DEFAULT_RUBBERBAND = 0.15;
const DEFAULT_SWIPE_VELOCITY = 0.5;
const DEFAULT_SWIPE_DISTANCE = 50;
const DEFAULT_SWIPE_DURATION = 250;
const InternalGestureOptionsNormalizers = {
threshold(value = 0) {
return ensureVector(value);
},
rubberband(value = 0) {
switch (value) {
case true:
return ensureVector(DEFAULT_RUBBERBAND);
case false:
return ensureVector(0);
default:
return ensureVector(value);
}
},
enabled(value = true) {
return value;
},
triggerAllEvents(value = false) {
return value;
},
initial(value = 0) {
if (typeof value === "function")
return value;
return ensureVector(value);
},
transform: true
};
const InternalCoordinatesOptionsNormalizers = {
...InternalGestureOptionsNormalizers,
axis: true,
lockDirection(value = false) {
return value;
},
bounds(value = {}) {
if (typeof value === "function")
return (state) => InternalCoordinatesOptionsNormalizers.bounds(value(state));
const {
left = -Infinity,
right = Infinity,
top = -Infinity,
bottom = Infinity
} = value;
return [
[left, right],
[top, bottom]
];
}
};
const isBrowser = typeof window !== "undefined" && window.document && window.document.createElement;
const InternalGenericOptionsNormalizers = {
enabled(value = true) {
return value;
},
domTarget: true,
window(value = isBrowser ? window : void 0) {
return value;
},
eventOptions({ passive = true, capture = false } = {}) {
return { passive, capture };
},
transform: true
};
const InternalDistanceAngleOptionsNormalizers = {
...InternalGestureOptionsNormalizers,
bounds(_value, _key, { distanceBounds = {}, angleBounds = {} }) {
const _distanceBounds = (state) => {
const D = assignDefault(valueFn(distanceBounds, state), {
min: -Infinity,
max: Infinity
});
return [D.min, D.max];
};
const _angleBounds = (state) => {
const A = assignDefault(valueFn(angleBounds, state), {
min: -Infinity,
max: Infinity
});
return [A.min, A.max];
};
if (typeof distanceBounds !== "function" && typeof angleBounds !== "function")
return [_distanceBounds(), _angleBounds()];
return (state) => [_distanceBounds(state), _angleBounds(state)];
}
};
const InternalDragOptionsNormalizers = {
...InternalCoordinatesOptionsNormalizers,
useTouch(value = true) {
return value && supportsTouchEvents();
},
preventWindowScrollY(value = false) {
return value;
},
threshold(v, _k, { filterTaps = false, lockDirection = false, axis = void 0 }) {
const A = ensureVector(
v,
filterTaps ? 3 : lockDirection ? 1 : axis ? 1 : 0
);
this.filterTaps = filterTaps;
return A;
},
swipeVelocity(v = DEFAULT_SWIPE_VELOCITY) {
return ensureVector(v);
},
swipeDistance(v = DEFAULT_SWIPE_DISTANCE) {
return ensureVector(v);
},
swipeDuration(value = DEFAULT_SWIPE_DURATION) {
return value;
},
delay(value = 0) {
switch (value) {
case true:
return DEFAULT_DRAG_DELAY;
case false:
return 0;
default:
return value;
}
}
};
function getInternalGenericOptions(config) {
return resolveWith(
config,
InternalGenericOptionsNormalizers
);
}
function getInternalCoordinatesOptions(config = {}) {
return resolveWith(
config,
InternalCoordinatesOptionsNormalizers
);
}
function getInternalDistanceAngleOptions(config = {}) {
return resolveWith(
config,
InternalDistanceAngleOptionsNormalizers
);
}
function getInternalDragOptions(config = {}) {
return resolveWith(
config,
InternalDragOptionsNormalizers
);
}
function _buildMoveConfig({
domTarget,
eventOptions,
window,
enabled,
...rest
}) {
const opts = getInternalGenericOptions({
domTarget,
eventOptions,
window,
enabled
});
opts.move = getInternalCoordinatesOptions(rest);
return opts;
}
function _buildHoverConfig({
domTarget,
eventOptions,
window,
enabled,
...rest
}) {
const opts = getInternalGenericOptions({
domTarget,
eventOptions,
window,
enabled
});
opts.hover = { enabled: true, ...rest };
return opts;
}
function _buildDragConfig({
domTarget,
eventOptions,
window,
enabled,
...rest
}) {
const opts = getInternalGenericOptions({
domTarget,
eventOptions,
window,
enabled
});
opts.drag = getInternalDragOptions(rest);
return opts;
}
function _buildPinchConfig({
domTarget,
eventOptions,
window,
enabled,
...rest
}) {
const opts = getInternalGenericOptions({
domTarget,
eventOptions,
window,
enabled
});
opts.pinch = getInternalDistanceAngleOptions(rest);
return opts;
}
function _buildScrollConfig({
domTarget,
eventOptions,
window,
enabled,
...rest
}) {
const opts = getInternalGenericOptions({
domTarget,
eventOptions,
window,
enabled
});
opts.scroll = getInternalCoordinatesOptions(rest);
return opts;
}
function _buildWheelConfig({
domTarget,
eventOptions,
window,
enabled,
...rest
}) {
const opts = getInternalGenericOptions({
domTarget,
eventOptions,
window,
enabled
});
opts.wheel = getInternalCoordinatesOptions(rest);
return opts;
}
function buildComplexConfig(config, actions = /* @__PURE__ */ new Set()) {
const {
drag,
wheel,
move,
scroll,
pinch,
hover,
eventOptions,
window,
transform,
domTarget,
enabled
} = config;
const mergedConfig = getInternalGenericOptions({
domTarget,
eventOptions,
transform,
window,
enabled
});
if (actions.has("onDrag"))
mergedConfig.drag = getInternalDragOptions(drag);
if (actions.has("onWheel"))
mergedConfig.wheel = getInternalCoordinatesOptions(wheel);
if (actions.has("onScroll"))
mergedConfig.scroll = getInternalCoordinatesOptions(scroll);
if (actions.has("onMove"))
mergedConfig.move = getInternalCoordinatesOptions(move);
if (actions.has("onPinch"))
mergedConfig.pinch = getInternalDistanceAngleOptions(pinch);
if (actions.has("onHover"))
mergedConfig.hover = { enabled: true, ...hover };
return mergedConfig;
}
function useRecognizers(handlers, config, nativeHandlers = {}) {
const classes = resolveClasses(handlers);
const controller = new Controller(classes);
controller.config = config;
controller.handlers = handlers;
controller.nativeRefs = nativeHandlers;
if (getCurrentInstance() && !config.manual) {
onMounted(controller.bind);
onUnmounted(controller.clean);
}
return controller;
}
function resolveClasses(internalHandlers) {
const classes = /* @__PURE__ */ new Set();
if (internalHandlers.drag)
classes.add(RecognizersMap.get("drag"));
if (internalHandlers.wheel)
classes.add(RecognizersMap.get("wheel"));
if (internalHandlers.scroll)
classes.add(RecognizersMap.get("scroll"));
if (internalHandlers.move)
classes.add(RecognizersMap.get("move"));
if (internalHandlers.pinch)
classes.add(RecognizersMap.get("pinch"));
if (internalHandlers.hover)
classes.add(RecognizersMap.get("hover"));
return classes;
}
function useDrag(handler, config = {}) {
RecognizersMap.set("drag", DragRecognizer);
const buildDragConfig = ref();
if (!buildDragConfig.value) {
buildDragConfig.value = memoizeOne(_buildDragConfig, isEqual);
}
return useRecognizers({ drag: handler }, buildDragConfig.value(config));
}
var __defProp$3 = Object.defineProperty;
var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$3 = (obj, key, value) => {
__defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
class MoveRecognizer extends CoordinatesRecognizer {
constructor() {
super(...arguments);
__publicField$3(this, "ingKey", "moving");
__publicField$3(this, "stateKey", "move");
__publicField$3(this, "debounced", true);
__publicField$3(this, "onMove", (event) => {
if (!this.enabled)
return;
this.setTimeout(this.onMoveEnd);
if (!this.state._active)
this.onMoveStart(event);
else
this.onMoveChange(event);
});
__publicField$3(this, "onMoveStart", (event) => {
this.updateSharedState(getGenericEventData(event));
const values = getPointerEventValues(event, this.transform);
this.updateGestureState({
...getStartGestureState(this, values, event),
...getGenericPayload(this, event, true)
});
this.updateGestureState(this.getMovement(values));
this.fireGestureHandler();
});
__publicField$3(this, "onMoveChange", (event) => {
this.updateSharedState(getGenericEventData(event));
const values = getPointerEventValues(event, this.transform);
this.updateGestureState({
...getGenericPayload(this, event),
...this.getKinematics(values, event)
});
this.fireGestureHandler();
});
__publicField$3(this, "onMoveEnd", () => {
this.clean();
if (!this.state._active)
return;
const values = this.state.values;
this.updateGestureState(this.getMovement(values));
this.updateGestureState({ velocities: [0, 0], velocity: 0, _active: false });
this.fireGestureHandler();
});
__publicField$3(this, "hoverTransform", () => {
return this.controller.config.hover.transform || this.controller.config.transform;
});
__publicField$3(this, "onPointerEnter", (event) => {
this.controller.state.shared.hovering = true;
if (!this.controller.config.enabled)
return;
if (this.controller.config.hover.enabled) {
const values = getPointerEventValues(event, this.hoverTransform());
const state = {
...this.controller.state.shared,
...this.state,
...getGenericPayload(this, event, true),
args: this.args,
values,
active: true,
hovering: true
};
this.controller.handlers.hover({
...state,
...this.mapStateValues(state)
});
}
if ("move" in this.controller.handlers)
this.onMoveStart(event);
});
__publicField$3(this, "onPointerLeave", (event) => {
this.controller.state.shared.hovering = false;
if ("move" in this.controller.handlers)
this.onMoveEnd();
if (!this.controller.config.hover.enabled)
return;
const values = getPointerEventValues(event, this.hoverTransform());
const state = {
...this.controller.state.shared,
...this.state,
...getGenericPayload(this, event),
args: this.args,
values,
active: false
};
this.controller.handlers.hover({ ...state, ...this.mapStateValues(state) });
});
}
addBindings(bindings) {
if ("move" in this.controller.handlers) {
addBindings(bindings, "onPointerMove", this.onMove);
}
if ("hover" in this.controller.handlers) {
addBindings(bindings, "onPointerEnter", this.onPointerEnter);
addBindings(bindings, "onPointerLeave", this.onPointerLeave);
}
}
}
class DistanceAngleRecognizer extends Recognizer {
getInternalMovement(values, state) {
const prev_a = state.values[1];
let [d, a = prev_a] = values;
let delta_a = a - prev_a;
let next_turns = state.turns;
if (Math.abs(delta_a) > 270)
next_turns += sign(delta_a);
return subV([d, a - 360 * next_turns], state.initial);
}
getKinematics(values, event) {
const state = this.getMovement(values);
const turns = (values[1] - state._movement[1] - this.state.initial[1]) / 360;
const dt = event.timeStamp - this.state.timeStamp;
const { distance, velocity, ...kinematics } = calculateAllKinematics(
state.movement,
state.delta,
dt
);
return { turns, ...state, ...kinematics };
}
mapStateValues(state) {
return { da: state.values, vdva: state.velocities };
}
}
var __defProp$2 = Object.defineProperty;
var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField$2 = (obj, key, value) => {
__defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
const ZOOM_CONSTANT = 7;
const WEBKIT_DISTANCE_SCALE_FACTOR = 260;
class PinchRecognizer extends DistanceAngleRecognizer {
constructor() {
super(...arguments);
__publicField$2(this, "ingKey", "pinching");
__publicField$2(this, "stateKey", "pinch");
__publicField$2(this, "onPinchStart", (event) => {
addEventIds(this.controller, event);
const touchIds = this.controller.touchIds;
if (!this.enabled)
return;
if (this.state._active) {
if (this.state._pointerIds.every((id) => touchIds.has(id)))
return;
}
if (touchIds.size < 2)
return;
const _pointerIds = Array.from(touchIds).slice(0, 2);
const { values, origin } = getTwoTouchesEventValues(
event,
_pointerIds,
this.transform
);
this.updateSharedState(getGenericEventData(event));
this.updateGestureState({
...getStartGestureState(this, values, event),
...getGenericPayload(this, event, true),
_pointerIds,
cancel: this.onCancel,
origin
});
this.updateGestureState(this.getMovement(values));
this.fireGestureHandler();
});
__publicField$2(this, "onPinchChange", (event) => {
const { canceled, _active } = this.state;
if (canceled || !_active || // if the event has the same timestamp as the previous event
event.timeStamp === this.state.timeStamp)
return;
const genericEventData = getGenericEventData(event);
this.updateSharedState(genericEventData);
try {
const { values, origin } = getTwoTouchesEventValues(
event,
this.state._pointerIds,
this.transform
);
const kinematics = this.getKinematics(values, event);
this.updateGestureState({
...getGenericPayload(this, event),
...kinematics,
origin
});
this.fireGestureHandler();
} catch (e) {
this.onPinchEnd(event);
}
});
__publicField$2(this, "onPinchEnd", (event) => {
removeEventIds(this.controller, event);
const pointerIds = getTouchIds(event);
if (this.state._pointerIds.every((id) => !pointerIds.includes(id)))
return;
this.clean();
if (!this.state._active)
return;
this.updateGestureState({
...getGenericPayload(this, event),
...this.getMovement(this.state.values),
_active: false
});
this.fireGestureHandler();
});
__publicField$2(this, "onCancel", () => {
if (this.state.canceled)
return;
this.updateGestureState({ _active: false, canceled: true });
this.fireGestureHandler();
});
/**
* PINCH WITH WEBKIT GESTURES
*/
__publicField$2(this, "onGestureStart", (event) => {
if (!this.enabled)
return;
event.preventDefault();
const values = getWebkitGestureEventValues(event, this.transform);
this.updateSharedState(getGenericEventData(event));
this.updateGestureState({
...getStartGestureState(this, values, event),
...getGenericPayload(this, event, true),
origin: [event.clientX, event.clientY],
// only used on dekstop
cancel: this.onCancel
});
this.updateGestureState(this.getMovement(values));
this.fireGestureHandler();
});
__publicField$2(this, "onGestureChange", (event) => {
const { canceled, _active } = this.state;
if (canceled || !_active)
return;
event.preventDefault();
const genericEventData = getGenericEventData(event);
this.updateSharedState(genericEventData);
const values = getWebkitGestureEventValues(event, this.transform);
values[0] = (values[0] - this.state.event.scale) * WEBKIT_DISTANCE_SCALE_FACTOR + this.state.values[0];
const kinematics = this.getKinematics(values, event);
this.updateGestureState({
...getGenericPayload(this, event),
...kinematics,
origin: [event.clientX, event.clientY]
// only used on dekstop
});
this.fireGestureHandler();
});
__publicField$2(this, "onGestureEnd", (event) => {
this.clean();
if (!this.state._active)
return;
this.updateGestureState({
...getGenericPayload(this, event),
...this.getMovement(this.state.values),
_active: false,
origin: [event.clientX, event.clientY]
// only used on dekstop
});
this.fireGestureHandler();
});
/**
* PINCH WITH WHEEL
*/
__publicField$2(this, "wheelShouldRun", (event) => {
return this.enabled && event.ctrlKey;
});
__publicField$2(this, "getWheelValuesFromEvent", (event) => {
const [, delta_d] = getWheelEventValues(event, this.transform);
const {
v