UNPKG

@mui/x-internal-gestures

Version:

The core engine of GestureEvents, a modern and robust multi-pointer gesture detection library for JavaScript.

280 lines (257 loc) 9.86 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.PressAndDragGesture = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _PointerGesture = require("../PointerGesture"); var _utils = require("../utils"); var _PanGesture = require("./PanGesture"); var _PressGesture = require("./PressGesture"); /** * PressAndDragGesture - Detects press followed by drag gestures using composition * * This gesture uses internal PressGesture and PanGesture instances to: * 1. First, detect a press (hold for specified duration without movement) * 2. Then, track drag movements from the press position * * The gesture fires events when: * - A press is completed (press phase) * - Drag movement begins and passes threshold (dragStart) * - Drag movement continues (drag) * - Drag movement ends (dragEnd) * - The gesture is canceled at any point * * This is ideal for panning operations where you want to hold first, then drag. */ /** * Configuration options for PressAndDragGesture * Extends PointerGestureOptions with press and drag specific settings */ /** * Event data specific to press and drag gesture events * Contains information about the gesture state, position, and movement */ /** * Type definition for the CustomEvent created by PressAndDragGesture */ /** * Represents the current phase of the PressAndDrag gesture */ /** * State tracking for the PressAndDragGesture */ /** * PressAndDragGesture class for handling press followed by drag interactions * * This gesture composes press and drag logic patterns from PressGesture and PanGesture * into a single coordinated gesture that handles press-then-drag interactions. */ class PressAndDragGesture extends _PointerGesture.PointerGesture { state = { phase: 'waitingForPress', dragTimeoutId: null }; /** * Duration required for press recognition */ /** * Maximum distance a pointer can move during press for it to still be considered a press */ /** * Maximum time between press completion and drag start */ /** * Movement threshold for drag activation */ /** * Allowed directions for the drag gesture */ constructor(options) { super(options); this.pressDuration = options.pressDuration ?? 500; this.pressMaxDistance = options.pressMaxDistance ?? 10; this.dragTimeout = options.dragTimeout ?? 1000; this.dragThreshold = options.dragThreshold ?? 0; this.dragDirection = options.dragDirection || ['up', 'down', 'left', 'right']; this.pressGesture = new _PressGesture.PressGesture({ name: `${this.name}-press`, duration: this.pressDuration, maxDistance: this.pressMaxDistance, 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 PressAndDragGesture((0, _extends2.default)({ name: this.name, preventDefault: this.preventDefault, stopPropagation: this.stopPropagation, minPointers: this.minPointers, maxPointers: this.maxPointers, pressDuration: this.pressDuration, pressMaxDistance: this.pressMaxDistance, 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.pressGesture.init(element, pointerManager, gestureRegistry, keyboardManager); this.panGesture.init(element, pointerManager, gestureRegistry, keyboardManager); // Listen to press gesture events this.element.addEventListener(this.pressGesture.name, this.pressHandler); // Listen to pan gesture events for dragging // @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.pressGesture.destroy(); this.panGesture.destroy(); this.element.removeEventListener(this.pressGesture.name, this.pressHandler); // @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.pressDuration = options.pressDuration ?? this.pressDuration; this.pressMaxDistance = options.pressMaxDistance ?? this.pressMaxDistance; this.dragTimeout = options.dragTimeout ?? this.dragTimeout; this.dragThreshold = options.dragThreshold ?? this.dragThreshold; this.dragDirection = options.dragDirection || this.dragDirection; // Update internal gesture options 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.pressGesture.name}ChangeOptions`, { detail: { duration: this.pressDuration, maxDistance: this.pressMaxDistance, 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: 'waitingForPress', dragTimeoutId: null }; } /** * This can be empty because the PressAndDragGesture relies on PressGesture and PanGesture to handle pointer events * The internal gestures will manage their own state and events, while this class coordinates between them */ handlePointerEvent() {} pressHandler = () => { if (this.state.phase !== 'waitingForPress') { return; } this.state.phase = 'pressDetected'; 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 !== 'pressDetected') { return; } // Clear the drag timeout as drag has started if (this.state.dragTimeoutId !== null) { clearTimeout(this.state.dragTimeoutId); this.state.dragTimeoutId = null; } // Restore touch action since we're now dragging 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 }); this.element.addEventListener('touchmove', _utils.preventDefault, { passive: false }); this.element.addEventListener('touchend', _utils.preventDefault, { passive: false }); } restoreTouchAction() { this.element.removeEventListener('touchstart', _utils.preventDefault); this.element.removeEventListener('touchmove', _utils.preventDefault); this.element.removeEventListener('touchend', _utils.preventDefault); } } exports.PressAndDragGesture = PressAndDragGesture;