@mui/x-internal-gestures
Version:
The core engine of GestureEvents, a modern and robust multi-pointer gesture detection library for JavaScript.
254 lines (236 loc) • 8.85 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TapAndDragGesture = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _PointerGesture = require("../PointerGesture");
var _utils = require("../utils");
var _PanGesture = require("./PanGesture");
var _TapGesture = require("./TapGesture");
/**
* TapAndDragGesture - Detects tap followed by drag gestures using composition
*
* This gesture uses internal TapGesture and PanGesture instances to:
* 1. First, detect a tap (quick touch without movement)
* 2. Then, track drag movements on the next pointer down
*
* The gesture fires events when:
* - A tap is completed (tap phase)
* - Drag movement begins and passes threshold (dragStart)
* - Drag movement continues (drag)
* - Drag movement ends (dragEnd)
* - The gesture is canceled at any point
*/
/**
* Configuration options for TapAndDragGesture
* Extends PointerGestureOptions with tap and drag specific settings
*/
/**
* Event data specific to tap and drag gesture events
* Contains information about the gesture state, position, and movement
*/
/**
* Type definition for the CustomEvent created by TapAndDragGesture
*/
/**
* Represents the current phase of the TapAndDrag gesture
*/
/**
* State tracking for the TapAndDragGesture
*/
/**
* TapAndDragGesture class for handling tap followed by drag interactions
*
* This gesture composes tap and drag logic patterns from TapGesture and PanGesture
* into a single coordinated gesture that handles tap-then-drag interactions.
*/
class TapAndDragGesture extends _PointerGesture.PointerGesture {
state = {
phase: 'waitingForTap',
dragTimeoutId: null
};
/**
* Maximum distance a pointer can move during tap for it to still be considered a tap
* (Following TapGesture pattern)
*/
/**
* Maximum time between tap completion and drag start
*/
/**
* Movement threshold for drag activation
*/
/**
* Allowed directions for the drag gesture
*/
constructor(options) {
super(options);
this.tapMaxDistance = options.tapMaxDistance ?? 10;
this.dragTimeout = options.dragTimeout ?? 1000;
this.dragThreshold = options.dragThreshold ?? 0;
this.dragDirection = options.dragDirection || ['up', 'down', 'left', 'right'];
this.tapGesture = new _TapGesture.TapGesture({
name: `${this.name}-tap`,
maxDistance: this.tapMaxDistance,
maxPointers: this.maxPointers,
pointerMode: this.pointerMode,
requiredKeys: this.requiredKeys,
preventIf: this.preventIf,
pointerOptions: structuredClone(this.pointerOptions)
});
this.panGesture = new _PanGesture.PanGesture({
name: `${this.name}-pan`,
minPointers: this.minPointers,
maxPointers: this.maxPointers,
threshold: this.dragThreshold,
direction: this.dragDirection,
pointerMode: this.pointerMode,
requiredKeys: this.requiredKeys,
preventIf: this.preventIf,
pointerOptions: structuredClone(this.pointerOptions)
});
}
clone(overrides) {
return new TapAndDragGesture((0, _extends2.default)({
name: this.name,
preventDefault: this.preventDefault,
stopPropagation: this.stopPropagation,
minPointers: this.minPointers,
maxPointers: this.maxPointers,
tapMaxDistance: this.tapMaxDistance,
dragTimeout: this.dragTimeout,
dragThreshold: this.dragThreshold,
dragDirection: [...this.dragDirection],
requiredKeys: [...this.requiredKeys],
pointerMode: [...this.pointerMode],
preventIf: [...this.preventIf],
pointerOptions: structuredClone(this.pointerOptions)
}, overrides));
}
init(element, pointerManager, gestureRegistry, keyboardManager) {
super.init(element, pointerManager, gestureRegistry, keyboardManager);
this.tapGesture.init(element, pointerManager, gestureRegistry, keyboardManager);
this.panGesture.init(element, pointerManager, gestureRegistry, keyboardManager);
this.element.addEventListener(this.tapGesture.name, this.tapHandler);
// @ts-expect-error, PointerEvent is correct.
this.element.addEventListener(`${this.panGesture.name}Start`, this.dragStartHandler);
// @ts-expect-error, PointerEvent is correct.
this.element.addEventListener(this.panGesture.name, this.dragMoveHandler);
// @ts-expect-error, PointerEvent is correct.
this.element.addEventListener(`${this.panGesture.name}End`, this.dragEndHandler);
// @ts-expect-error, PointerEvent is correct.
this.element.addEventListener(`${this.panGesture.name}Cancel`, this.dragEndHandler);
}
destroy() {
this.resetState();
this.tapGesture.destroy();
this.panGesture.destroy();
this.element.removeEventListener(this.tapGesture.name, this.tapHandler);
// @ts-expect-error, PointerEvent is correct.
this.element.removeEventListener(`${this.panGesture.name}Start`, this.dragStartHandler);
// @ts-expect-error, PointerEvent is correct.
this.element.removeEventListener(this.panGesture.name, this.dragMoveHandler);
// @ts-expect-error, PointerEvent is correct.
this.element.removeEventListener(`${this.panGesture.name}End`, this.dragEndHandler);
// @ts-expect-error, PointerEvent is correct.
this.element.removeEventListener(`${this.panGesture.name}Cancel`, this.dragEndHandler);
super.destroy();
}
updateOptions(options) {
super.updateOptions(options);
this.tapMaxDistance = options.tapMaxDistance ?? this.tapMaxDistance;
this.dragTimeout = options.dragTimeout ?? this.dragTimeout;
this.dragThreshold = options.dragThreshold ?? this.dragThreshold;
this.dragDirection = options.dragDirection || this.dragDirection;
this.element.dispatchEvent(new CustomEvent(`${this.panGesture.name}ChangeOptions`, {
detail: {
minPointers: this.minPointers,
maxPointers: this.maxPointers,
threshold: this.dragThreshold,
direction: this.dragDirection,
pointerMode: this.pointerMode,
requiredKeys: this.requiredKeys,
preventIf: this.preventIf,
pointerOptions: structuredClone(this.pointerOptions)
}
}));
this.element.dispatchEvent(new CustomEvent(`${this.tapGesture.name}ChangeOptions`, {
detail: {
maxDistance: this.tapMaxDistance,
maxPointers: this.maxPointers,
pointerMode: this.pointerMode,
requiredKeys: this.requiredKeys,
preventIf: this.preventIf,
pointerOptions: structuredClone(this.pointerOptions)
}
}));
}
resetState() {
if (this.state.dragTimeoutId !== null) {
clearTimeout(this.state.dragTimeoutId);
}
this.restoreTouchAction();
this.isActive = false;
this.state = {
phase: 'waitingForTap',
dragTimeoutId: null
};
}
/**
* This can be empty because the TapAndDragGesture relies on TapGesture and PanGesture to handle pointer events
* The internal gestures will manage their own state and events, while this class coordinates between them
*/
handlePointerEvent() {}
tapHandler = () => {
if (this.state.phase !== 'waitingForTap') {
return;
}
this.state.phase = 'tapDetected';
this.setTouchAction();
// Start timeout to wait for drag start
this.state.dragTimeoutId = setTimeout(() => {
// Timeout expired, reset gesture
this.resetState();
}, this.dragTimeout);
};
dragStartHandler = event => {
if (this.state.phase !== 'tapDetected') {
return;
}
// Clear the drag timeout as drag has started
if (this.state.dragTimeoutId !== null) {
clearTimeout(this.state.dragTimeoutId);
this.state.dragTimeoutId = null;
}
this.restoreTouchAction();
this.state.phase = 'dragging';
this.isActive = true;
// Fire start event
this.element.dispatchEvent(new CustomEvent((0, _utils.createEventName)(this.name, event.detail.phase), event));
};
dragMoveHandler = event => {
if (this.state.phase !== 'dragging') {
return;
}
// Fire move event
this.element.dispatchEvent(new CustomEvent((0, _utils.createEventName)(this.name, event.detail.phase), event));
};
dragEndHandler = event => {
if (this.state.phase !== 'dragging') {
return;
}
this.resetState();
// Fire end event
this.element.dispatchEvent(new CustomEvent((0, _utils.createEventName)(this.name, event.detail.phase), event));
};
setTouchAction() {
this.element.addEventListener('touchstart', _utils.preventDefault, {
passive: false
});
}
restoreTouchAction() {
this.element.removeEventListener('touchstart', _utils.preventDefault);
}
}
exports.TapAndDragGesture = TapAndDragGesture;