UNPKG

@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
"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;