UNPKG

@mui/x-internal-gestures

Version:

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

249 lines (226 loc) 7.98 kB
import _extends from "@babel/runtime/helpers/esm/extends"; /** * MoveGesture - Detects when a pointer enters, moves within, and leaves an element * * This gesture tracks pointer movements over an element, firing events when: * - A pointer enters the element (start) * - A pointer moves within the element (ongoing) * - A pointer leaves the element (end) * * Unlike other gestures which often require specific actions to trigger, * the move gesture fires automatically when pointers interact with the target element. * * This gesture only works with mouse pointers, not touch or pen. */ import { PointerGesture } from "../PointerGesture.js"; import { calculateCentroid, createEventName } from "../utils/index.js"; /** * Configuration options for the MoveGesture * Extends the base PointerGestureOptions */ /** * Event data specific to move gesture events * Includes the source pointer event and standard gesture data */ /** * Type definition for the CustomEvent created by MoveGesture */ /** * State tracking for the MoveGesture */ /** * MoveGesture class for handling pointer movement over elements * * This gesture detects when pointers enter, move within, or leave target elements, * and dispatches corresponding custom events. * * This gesture only works with hovering mouse pointers, not touch. */ export class MoveGesture extends PointerGesture { constructor(options) { super(options); // Pre-bind handlers to this instance to maintain reference equality this.state = { lastPosition: null }; this.isSinglePhase = void 0; this.eventType = void 0; this.optionsType = void 0; this.mutableOptionsType = void 0; this.mutableStateType = void 0; /** * Movement threshold in pixels that must be exceeded before the gesture activates. * Higher values reduce false positive gesture detection for small movements. */ this.threshold = void 0; // Store bound event handlers to properly remove them this.handleElementEnterBound = void 0; this.handleElementLeaveBound = void 0; this.handleElementEnterBound = this.handleElementEnter.bind(this); this.handleElementLeaveBound = this.handleElementLeave.bind(this); this.threshold = options.threshold || 0; } clone(overrides) { return new MoveGesture(_extends({ name: this.name, preventDefault: this.preventDefault, stopPropagation: this.stopPropagation, threshold: this.threshold, minPointers: this.minPointers, maxPointers: this.maxPointers, requiredKeys: [...this.requiredKeys], pointerMode: [...this.pointerMode], preventIf: [...this.preventIf] }, overrides)); } init(element, pointerManager, gestureRegistry, keyboardManager) { super.init(element, pointerManager, gestureRegistry, keyboardManager); // Add event listeners for entering and leaving elements // These are different from pointer events handled by PointerManager // @ts-expect-error, PointerEvent is correct. this.element.addEventListener('pointerenter', this.handleElementEnterBound); // @ts-expect-error, PointerEvent is correct. this.element.addEventListener('pointerleave', this.handleElementLeaveBound); } destroy() { // Remove event listeners using the same function references // @ts-expect-error, PointerEvent is correct. this.element.removeEventListener('pointerenter', this.handleElementEnterBound); // @ts-expect-error, PointerEvent is correct. this.element.removeEventListener('pointerleave', this.handleElementLeaveBound); this.resetState(); super.destroy(); } updateOptions(options) { // Call parent method to handle common options super.updateOptions(options); } resetState() { this.isActive = false; this.state = { lastPosition: null }; } /** * Handle pointer enter events for a specific element * @param event The original pointer event */ handleElementEnter(event) { if (event.pointerType !== 'mouse' && event.pointerType !== 'pen') { return; } // Get pointers from the PointerManager const pointers = this.pointerManager.getPointers() || new Map(); const pointersArray = Array.from(pointers.values()); // Only activate if we're within pointer count constraints if (pointersArray.length >= this.minPointers && pointersArray.length <= this.maxPointers) { this.isActive = true; const currentPosition = { x: event.clientX, y: event.clientY }; this.state.lastPosition = currentPosition; // Emit start event this.emitMoveEvent(this.element, 'start', pointersArray, event); this.emitMoveEvent(this.element, 'ongoing', pointersArray, event); } } /** * Handle pointer leave events for a specific element * @param event The original pointer event */ handleElementLeave(event) { if (event.pointerType !== 'mouse' && event.pointerType !== 'pen') { return; } if (!this.isActive) { return; } // Get pointers from the PointerManager const pointers = this.pointerManager.getPointers() || new Map(); const pointersArray = Array.from(pointers.values()); // Emit end event and reset state this.emitMoveEvent(this.element, 'end', pointersArray, event); this.resetState(); } /** * Handle pointer events for the move gesture (only handles move events now) * @param pointers Map of active pointers * @param event The original pointer event */ handlePointerEvent(pointers, event) { if (event.type !== 'pointermove' || event.pointerType !== 'mouse' && event.pointerType !== 'pen') { return; } if (this.preventDefault) { event.preventDefault(); } if (this.stopPropagation) { event.stopPropagation(); } const pointersArray = Array.from(pointers.values()); // Find which element (if any) is being targeted const targetElement = this.getTargetElement(event); if (!targetElement) { return; } // Make sure we're still within pointer count constraints if (pointersArray.length < this.minPointers || pointersArray.length > this.maxPointers) { return; } if (this.shouldPreventGesture(targetElement)) { if (!this.isActive) { return; } this.resetState(); this.emitMoveEvent(targetElement, 'end', pointersArray, event); return; } // Update position const currentPosition = { x: event.clientX, y: event.clientY }; this.state.lastPosition = currentPosition; if (!this.isActive) { this.isActive = true; this.emitMoveEvent(targetElement, 'start', pointersArray, event); } // Emit ongoing event this.emitMoveEvent(targetElement, 'ongoing', pointersArray, event); } /** * Emit move-specific events * @param element The DOM element the event is related to * @param phase The current phase of the gesture (start, ongoing, end) * @param pointers Array of active pointers * @param event The original pointer event */ emitMoveEvent(element, phase, pointers, event) { const currentPosition = this.state.lastPosition || calculateCentroid(pointers); // Get list of active gestures const activeGestures = this.gesturesRegistry.getActiveGestures(element); // Create custom event data const customEventData = { gestureName: this.name, centroid: currentPosition, target: event.target, srcEvent: event, phase, pointers, timeStamp: event.timeStamp, activeGestures, customData: this.customData }; // Event names to trigger const eventName = 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); } }