UNPKG

@mui/x-internal-gestures

Version:

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

322 lines (276 loc) 10.6 kB
import _extends from "@babel/runtime/helpers/esm/extends"; /** * Base Gesture module that provides common functionality for all gesture implementations */ import { eventList } from "./utils/eventList.js"; /** * The possible phases of a gesture during its lifecycle. * * - 'start': The gesture has been recognized and is beginning * - 'ongoing': The gesture is in progress (e.g., a finger is moving) * - 'end': The gesture has completed successfully * - 'cancel': The gesture was interrupted or terminated abnormally */ /** * Core data structure passed to gesture event handlers. * Contains all relevant information about a gesture event. */ /** * Defines the types of pointers that can trigger a gesture. */ /** * Base configuration options that can be overridden per pointer mode. */ /** * Configuration options for creating a gesture instance. */ // eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention /** * Type for the state of a gesture recognizer. */ /** * Base abstract class for all gestures. This class provides the fundamental structure * and functionality for handling gestures, including registering and unregistering * gesture handlers, creating emitters, and managing gesture state. * * Gesture is designed as an extensible base for implementing specific gesture recognizers. * Concrete gesture implementations should extend this class or one of its subclasses. * * To implement: * - Non-pointer gestures (like wheel events): extend this Gesture class directly * - Pointer-based gestures: extend the PointerGesture class instead * * @example * ```ts * import { Gesture } from './Gesture'; * * class CustomGesture extends Gesture { * constructor(options) { * super(options); * } * * clone(overrides) { * return new CustomGesture({ * name: this.name, * // ... other options * ...overrides, * }); * } * } * ``` */ export class Gesture { /** Unique name identifying this gesture type */ /** Whether to prevent default browser action for gesture events */ /** Whether to stop propagation of gesture events */ /** * List of gesture names that should prevent this gesture from activating when they are active. */ /** * Array of keyboard keys that must be pressed for the gesture to be recognized. */ /** * KeyboardManager instance for tracking key presses */ /** * List of pointer types that can trigger this gesture. * If undefined, all pointer types are allowed. */ /** * Pointer mode-specific configuration overrides. */ /** * User-mutable data object for sharing state between gesture events * This object is included in all events emitted by this gesture */ customData = {}; /** Reference to the singleton PointerManager instance */ /** Reference to the singleton ActiveGesturesRegistry instance */ /** The DOM element this gesture is attached to */ /** Stores the active gesture state */ /** @internal For types. If false enables phases (xStart, x, xEnd) */ /** @internal For types. The event type this gesture is associated with */ /** @internal For types. The options type for this gesture */ /** @internal For types. The options that can be changed at runtime */ /** @internal For types. The state that can be changed at runtime */ /** * Create a new gesture instance with the specified options * * @param options - Configuration options for this gesture */ constructor(options) { if (!options || !options.name) { throw new Error('Gesture must be initialized with a valid name.'); } if (options.name in eventList) { throw new Error(`Gesture can't be created with a native event name. Tried to use "${options.name}". Please use a custom name instead.`); } this.name = options.name; this.preventDefault = options.preventDefault ?? false; this.stopPropagation = options.stopPropagation ?? false; this.preventIf = options.preventIf ?? []; this.requiredKeys = options.requiredKeys ?? []; this.pointerMode = options.pointerMode ?? []; this.pointerOptions = options.pointerOptions ?? {}; } /** * Initialize the gesture by acquiring the pointer manager and gestures registry * Must be called before the gesture can be used */ init(element, pointerManager, gestureRegistry, keyboardManager) { this.element = element; this.pointerManager = pointerManager; this.gesturesRegistry = gestureRegistry; this.keyboardManager = keyboardManager; const changeOptionsEventName = `${this.name}ChangeOptions`; this.element.addEventListener(changeOptionsEventName, this.handleOptionsChange); const changeStateEventName = `${this.name}ChangeState`; this.element.addEventListener(changeStateEventName, this.handleStateChange); } /** * Handle option change events * @param event Custom event with new options in the detail property */ handleOptionsChange = event => { if (event && event.detail) { this.updateOptions(event.detail); } }; /** * Update the gesture options with new values * @param options Object containing properties to update */ updateOptions(options) { // Update common options this.preventDefault = options.preventDefault ?? this.preventDefault; this.stopPropagation = options.stopPropagation ?? this.stopPropagation; this.preventIf = options.preventIf ?? this.preventIf; this.requiredKeys = options.requiredKeys ?? this.requiredKeys; this.pointerMode = options.pointerMode ?? this.pointerMode; this.pointerOptions = options.pointerOptions ?? this.pointerOptions; } /** * Get the default configuration for the pointer specific options. * Change this function in child classes to provide different defaults. */ getBaseConfig() { return { requiredKeys: this.requiredKeys }; } /** * Get the effective configuration for a specific pointer mode. * This merges the base configuration with pointer mode-specific overrides. * * @param pointerType - The pointer type to get configuration for * @returns The effective configuration object */ getEffectiveConfig(pointerType, baseConfig) { if (pointerType !== 'mouse' && pointerType !== 'touch' && pointerType !== 'pen') { // Unknown pointer type, return base config return baseConfig; } // Apply pointer mode-specific overrides const pointerModeOverrides = this.pointerOptions[pointerType]; if (pointerModeOverrides) { return _extends({}, baseConfig, pointerModeOverrides); } return baseConfig; } /** * Handle state change events * @param event Custom event with new state values in the detail property */ handleStateChange = event => { if (event && event.detail) { this.updateState(event.detail); } }; /** * Update the gesture state with new values * @param stateChanges Object containing state properties to update */ updateState(stateChanges) { // This is a base implementation - concrete gesture classes should override // to handle specific state updates based on their state structure Object.assign(this.state, stateChanges); } /** * Create a deep clone of this gesture for a new element * * @param overrides - Optional configuration options that override the defaults * @returns A new instance of this gesture with the same configuration and any overrides applied */ /** * Check if the event's target is or is contained within any of our registered elements * * @param event - The browser event to check * @returns The matching element or null if no match is found */ getTargetElement(event) { if (this.isActive || this.element === event.target || 'contains' in this.element && this.element.contains(event.target) || 'getRootNode' in this.element && this.element.getRootNode() instanceof ShadowRoot && event.composedPath().includes(this.element)) { return this.element; } return null; } /** Whether the gesture is currently active */ set isActive(isActive) { if (isActive) { this.gesturesRegistry.registerActiveGesture(this.element, this); } else { this.gesturesRegistry.unregisterActiveGesture(this.element, this); } } /** Whether the gesture is currently active */ get isActive() { return this.gesturesRegistry.isGestureActive(this.element, this) ?? false; } /** * Checks if this gesture should be prevented from activating. * * @param element - The DOM element to check against * @param pointerType - The type of pointer triggering the gesture * @returns true if the gesture should be prevented, false otherwise */ shouldPreventGesture(element, pointerType) { // Get effective configuration for this pointer type const effectiveConfig = this.getEffectiveConfig(pointerType, this.getBaseConfig()); // First check if required keyboard keys are pressed if (!this.keyboardManager.areKeysPressed(effectiveConfig.requiredKeys)) { return true; // Prevent the gesture if required keys are not pressed } if (this.preventIf.length === 0) { return false; // No prevention rules, allow the gesture } const activeGestures = this.gesturesRegistry.getActiveGestures(element); // Check if any of the gestures that would prevent this one are active return this.preventIf.some(gestureName => activeGestures[gestureName]); } /** * Checks if the given pointer type is allowed for this gesture based on the pointerMode setting. * * @param pointerType - The type of pointer to check. * @returns true if the pointer type is allowed, false otherwise. */ isPointerTypeAllowed(pointerType) { // If no pointer mode is specified, all pointer types are allowed if (!this.pointerMode || this.pointerMode.length === 0) { return true; } // Check if the pointer type is in the allowed types list return this.pointerMode.includes(pointerType); } /** * Clean up the gesture and unregister any listeners * Call this method when the gesture is no longer needed to prevent memory leaks */ destroy() { const changeOptionsEventName = `${this.name}ChangeOptions`; this.element.removeEventListener(changeOptionsEventName, this.handleOptionsChange); const changeStateEventName = `${this.name}ChangeState`; this.element.removeEventListener(changeStateEventName, this.handleStateChange); } /** * Reset the gesture state to its initial values */ }