@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
JavaScript
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
*/
}