UNPKG

@mui/x-internal-gestures

Version:

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

284 lines (258 loc) 9.37 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.PressGesture = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _PointerGesture = require("../PointerGesture"); var _utils = require("../utils"); /** * PressGesture - Detects press and hold interactions * * This gesture tracks when users press and hold on an element for a specified duration, firing events when: * - The press begins and passes the holding threshold time (start, ongoing) * - The press ends (end) * - The press is canceled by movement beyond threshold (cancel) * * This gesture is commonly used for contextual menus, revealing additional options, or alternate actions. */ /** * Configuration options for PressGesture * Extends PointerGestureOptions with press-specific options */ /** * Event data specific to press gesture events * Contains information about the press location and duration */ /** * Type definition for the CustomEvent created by PressGesture */ /** * State tracking for the PressGesture */ /** * PressGesture class for handling press/hold interactions * * This gesture detects when users press and hold on an element for a specified duration, * and dispatches press-related events when the user holds long enough. * * The `start` and `ongoing` events are dispatched at the same time once the press threshold is reached. * If the press is canceled (event.g., by moving too far), a `cancel` event is dispatched before the `end` event. */ class PressGesture extends _PointerGesture.PointerGesture { constructor(options) { super(options); this.state = { startCentroid: null, lastPosition: null, timerId: null, startTime: 0, pressThresholdReached: false }; this.isSinglePhase = void 0; this.eventType = void 0; this.optionsType = void 0; this.mutableOptionsType = void 0; this.mutableStateType = void 0; /** * Duration in milliseconds required to hold before the press gesture is recognized */ this.duration = void 0; /** * Maximum distance a pointer can move for a gesture to still be considered a press */ this.maxDistance = void 0; this.duration = options.duration ?? 500; this.maxDistance = options.maxDistance ?? 10; } clone(overrides) { return new PressGesture((0, _extends2.default)({ name: this.name, preventDefault: this.preventDefault, stopPropagation: this.stopPropagation, minPointers: this.minPointers, maxPointers: this.maxPointers, duration: this.duration, maxDistance: this.maxDistance, requiredKeys: [...this.requiredKeys], pointerMode: [...this.pointerMode], preventIf: [...this.preventIf] }, overrides)); } destroy() { this.clearPressTimer(); this.resetState(); super.destroy(); } updateOptions(options) { super.updateOptions(options); this.duration = options.duration ?? this.duration; this.maxDistance = options.maxDistance ?? this.maxDistance; } resetState() { this.clearPressTimer(); this.isActive = false; this.state = (0, _extends2.default)({}, this.state, { startCentroid: null, lastPosition: null, timerId: null, startTime: 0, pressThresholdReached: false }); } /** * Clear the press timer if it's active */ clearPressTimer() { if (this.state.timerId !== null) { clearTimeout(this.state.timerId); this.state.timerId = null; } } /** * Handle pointer events for the press gesture */ handlePointerEvent(pointers, event) { const pointersArray = Array.from(pointers.values()); // Check for our forceCancel event to handle interrupted gestures (from contextmenu, blur) if (event.type === 'forceCancel') { // Reset all active press gestures when we get a force reset event this.cancelPress(event.target, pointersArray, event); return; } // Find which element (if any) is being targeted const targetElement = this.getTargetElement(event); if (!targetElement) { return; } // Check if this gesture should be prevented by active gestures if (this.shouldPreventGesture(targetElement)) { if (this.isActive) { // If the gesture was active but now should be prevented, cancel it gracefully this.cancelPress(targetElement, pointersArray, event); } return; } // Filter pointers to only include those targeting our element or its children const relevantPointers = this.getRelevantPointers(pointersArray, targetElement); // Check if we have enough pointers and not too many if (relevantPointers.length < this.minPointers || relevantPointers.length > this.maxPointers) { if (this.isActive) { // Cancel or end the gesture if it was active this.cancelPress(targetElement, relevantPointers, event); } return; } switch (event.type) { case 'pointerdown': if (!this.isActive && !this.state.startCentroid) { // Calculate and store the starting centroid this.state.startCentroid = (0, _utils.calculateCentroid)(relevantPointers); this.state.lastPosition = (0, _extends2.default)({}, this.state.startCentroid); this.state.startTime = event.timeStamp; this.isActive = true; // Store the original target element this.originalTarget = targetElement; // Start the timer for press recognition this.clearPressTimer(); // Clear any existing timer first this.state.timerId = setTimeout(() => { if (this.isActive && this.state.startCentroid) { this.state.pressThresholdReached = true; const lastPosition = this.state.lastPosition; // Emit press start event this.emitPressEvent(targetElement, 'start', relevantPointers, event, lastPosition); this.emitPressEvent(targetElement, 'ongoing', relevantPointers, event, lastPosition); } }, this.duration); } break; case 'pointermove': if (this.isActive && this.state.startCentroid) { // Calculate current position const currentPosition = (0, _utils.calculateCentroid)(relevantPointers); this.state.lastPosition = currentPosition; // Calculate distance from start position const deltaX = currentPosition.x - this.state.startCentroid.x; const deltaY = currentPosition.y - this.state.startCentroid.y; const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // If moved too far, cancel the press gesture if (distance > this.maxDistance) { this.cancelPress(targetElement, relevantPointers, event); } } break; case 'pointerup': if (this.isActive) { if (this.state.pressThresholdReached) { // Complete the press gesture if we've held long enough const position = this.state.lastPosition || this.state.startCentroid; this.emitPressEvent(targetElement, 'end', relevantPointers, event, position); } // Reset state this.resetState(); } break; case 'pointercancel': case 'forceCancel': // Cancel the gesture this.cancelPress(targetElement, relevantPointers, event); break; default: break; } } /** * Emit press-specific events with additional data */ emitPressEvent(element, phase, pointers, event, position) { // Get list of active gestures const activeGestures = this.gesturesRegistry.getActiveGestures(element); // Calculate current duration of the press const currentDuration = event.timeStamp - this.state.startTime; // Create custom event data const customEventData = { gestureName: this.name, centroid: position, target: event.target, srcEvent: event, phase, pointers, timeStamp: event.timeStamp, x: position.x, y: position.y, duration: currentDuration, activeGestures, customData: this.customData }; // Event names to trigger const eventName = (0, _utils.createEventName)(this.name, phase); // Dispatch custom events on the element const domEvent = new CustomEvent(eventName, { bubbles: true, cancelable: true, composed: true, detail: customEventData }); element.dispatchEvent(domEvent); // Apply preventDefault/stopPropagation if configured if (this.preventDefault) { event.preventDefault(); } if (this.stopPropagation) { event.stopPropagation(); } } /** * Cancel the current press gesture */ cancelPress(element, pointers, event) { if (this.isActive && this.state.pressThresholdReached) { const position = this.state.lastPosition || this.state.startCentroid; this.emitPressEvent(element ?? this.element, 'cancel', pointers, event, position); this.emitPressEvent(element ?? this.element, 'end', pointers, event, position); } this.resetState(); } } exports.PressGesture = PressGesture;