@mui/x-internal-gestures
Version:
The core engine of GestureEvents, a modern and robust multi-pointer gesture detection library for JavaScript.
305 lines (283 loc) • 10.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.GestureManager = void 0;
var _ActiveGesturesRegistry = require("./ActiveGesturesRegistry");
var _KeyboardManager = require("./KeyboardManager");
var _PointerManager = require("./PointerManager");
/**
* Configuration options for initializing the GestureManager
*/
/**
* The primary class responsible for setting up and managing gestures across multiple elements.
*
* GestureManager maintains a collection of gesture templates that can be instantiated for
* specific DOM elements. It handles lifecycle management, event dispatching, and cleanup.
*
* @example
* ```typescript
* // Basic setup with default gestures
* const manager = new GestureManager({
* root: document.body,
* touchAction: 'none',
* gestures: [
* new PanGesture({ name: 'pan' }),
* ],
* });
*
* // Register pan gestures on an element
* const element = manager.registerElement('pan', document.querySelector('.draggable'));
*
* // Add event listeners with proper typing
* element.addEventListener('panStart', (event) => {
* console.log('Pan started');
* });
*
* element.addEventListener('pan', (event) => {
* console.log(`Pan delta: ${event.deltaX}, ${event.deltaY}`);
* });
*
* // Custom gesture types
* interface MyGestureEvents {
* custom: { x: number, y: number }
* }
* const customManager = new GestureManager<MyGestureEvents>({
* root: document.body
* gestures: [
* new CustomGesture({ name: 'custom' }),
* ],
* });
* ```
*/
class GestureManager {
/** Repository of gesture templates that can be cloned for specific elements */
gestureTemplates = new Map();
/** Maps DOM elements to their active gesture instances */
elementGestureMap = new Map();
activeGesturesRegistry = new _ActiveGesturesRegistry.ActiveGesturesRegistry();
keyboardManager = new _KeyboardManager.KeyboardManager();
/**
* Create a new GestureManager instance to coordinate gesture recognition
*
* @param options - Configuration options for the gesture manager
*/
constructor(options) {
// Initialize the PointerManager
this.pointerManager = new _PointerManager.PointerManager({
root: options.root,
touchAction: options.touchAction,
passive: options.passive
});
// Add initial gestures as templates if provided
if (options.gestures && options.gestures.length > 0) {
options.gestures.forEach(gesture => {
this.addGestureTemplate(gesture);
});
}
}
/**
* Add a gesture template to the manager's template registry.
* Templates serve as prototypes that can be cloned for individual elements.
*
* @param gesture - The gesture instance to use as a template
*/
addGestureTemplate(gesture) {
if (this.gestureTemplates.has(gesture.name)) {
console.warn(`Gesture template with name "${gesture.name}" already exists. It will be overwritten.`);
}
this.gestureTemplates.set(gesture.name, gesture);
}
/**
* Updates the options for a specific gesture on a given element and emits a change event.
*
* @param gestureName - Name of the gesture whose options should be updated
* @param element - The DOM element where the gesture is attached
* @param options - New options to apply to the gesture
* @returns True if the options were successfully updated, false if the gesture wasn't found
*
* @example
* ```typescript
* // Update pan gesture sensitivity on the fly
* manager.setGestureOptions('pan', element, { threshold: 5 });
* ```
*/
setGestureOptions(gestureName, element, options) {
const elementGestures = this.elementGestureMap.get(element);
if (!elementGestures || !elementGestures.has(gestureName)) {
console.error(`Gesture "${gestureName}" not found on the provided element.`);
return;
}
const event = new CustomEvent(`${gestureName}ChangeOptions`, {
detail: options,
bubbles: false,
cancelable: false,
composed: false
});
element.dispatchEvent(event);
}
/**
* Updates the state for a specific gesture on a given element and emits a change event.
*
* @param gestureName - Name of the gesture whose state should be updated
* @param element - The DOM element where the gesture is attached
* @param state - New state to apply to the gesture
* @returns True if the state was successfully updated, false if the gesture wasn't found
*
* @example
* ```typescript
* // Update total delta for a turnWheel gesture
* manager.setGestureState('turnWheel', element, { totalDeltaX: 10 });
* ```
*/
setGestureState(gestureName, element, state) {
const elementGestures = this.elementGestureMap.get(element);
if (!elementGestures || !elementGestures.has(gestureName)) {
console.error(`Gesture "${gestureName}" not found on the provided element.`);
return;
}
const event = new CustomEvent(`${gestureName}ChangeState`, {
detail: state,
bubbles: false,
cancelable: false,
composed: false
});
element.dispatchEvent(event);
}
/**
* Register an element to recognize one or more gestures.
*
* This method clones the specified gesture template(s) and creates
* gesture recognizer instance(s) specifically for the provided element.
* The element is returned with enhanced TypeScript typing for gesture events.
*
* @param gestureNames - Name(s) of the gesture(s) to register (must match template names)
* @param element - The DOM element to attach the gesture(s) to
* @param options - Optional map of gesture-specific options to override when registering
* @returns The same element with properly typed event listeners
*
* @example
* ```typescript
* // Register multiple gestures
* const element = manager.registerElement(['pan', 'pinch'], myDiv);
*
* // Register a single gesture
* const draggable = manager.registerElement('pan', dragHandle);
*
* // Register with customized options for each gesture
* const customElement = manager.registerElement(
* ['pan', 'pinch', 'rotate'],
* myElement,
* {
* pan: { threshold: 20, direction: ['left', 'right'] },
* pinch: { threshold: 0.1 }
* }
* );
* ```
*/
registerElement(gestureNames, element, options) {
// Handle array of gesture names
if (!Array.isArray(gestureNames)) {
gestureNames = [gestureNames];
}
gestureNames.forEach(name => {
const gestureOptions = options?.[name];
this.registerSingleGesture(name, element, gestureOptions);
});
return element;
}
/**
* Internal method to register a single gesture on an element.
*
* @param gestureName - Name of the gesture to register
* @param element - DOM element to attach the gesture to
* @param options - Optional options to override the gesture template configuration
* @returns True if the registration was successful, false otherwise
*/
registerSingleGesture(gestureName, element, options) {
// Find the gesture template
const gestureTemplate = this.gestureTemplates.get(gestureName);
if (!gestureTemplate) {
console.error(`Gesture template "${gestureName}" not found.`);
return false;
}
// Create element's gesture map if it doesn't exist
if (!this.elementGestureMap.has(element)) {
this.elementGestureMap.set(element, new Map());
}
// Check if this element already has this gesture registered
const elementGestures = this.elementGestureMap.get(element);
if (elementGestures.has(gestureName)) {
console.warn(`Element already has gesture "${gestureName}" registered. It will be replaced.`);
// Unregister the existing gesture first
this.unregisterElement(gestureName, element);
}
// Clone the gesture template and create a new instance with optional overrides
// This allows each element to have its own state, event listeners, and configuration
const gestureInstance = gestureTemplate.clone(options);
gestureInstance.init(element, this.pointerManager, this.activeGesturesRegistry, this.keyboardManager);
// Store the gesture in the element's gesture map
elementGestures.set(gestureName, gestureInstance);
return true;
}
/**
* Unregister a specific gesture from an element.
* This removes the gesture recognizer and stops event emission for that gesture.
*
* @param gestureName - Name of the gesture to unregister
* @param element - The DOM element to remove the gesture from
* @returns True if the gesture was found and removed, false otherwise
*/
unregisterElement(gestureName, element) {
const elementGestures = this.elementGestureMap.get(element);
if (!elementGestures || !elementGestures.has(gestureName)) {
return false;
}
// Destroy the gesture instance
const gesture = elementGestures.get(gestureName);
gesture.destroy();
// Remove from the map
elementGestures.delete(gestureName);
this.activeGesturesRegistry.unregisterElement(element);
// Remove the element from the map if it no longer has any gestures
if (elementGestures.size === 0) {
this.elementGestureMap.delete(element);
}
return true;
}
/**
* Unregister all gestures from an element.
* Completely removes the element from the gesture system.
*
* @param element - The DOM element to remove all gestures from
*/
unregisterAllGestures(element) {
const elementGestures = this.elementGestureMap.get(element);
if (elementGestures) {
// Unregister all gestures for this element
for (const [, gesture] of elementGestures) {
gesture.destroy();
this.activeGesturesRegistry.unregisterElement(element);
}
// Clear the map
this.elementGestureMap.delete(element);
}
}
/**
* Clean up all gestures and event listeners.
* Call this method when the GestureManager is no longer needed to prevent memory leaks.
*/
destroy() {
// Unregister all element gestures
for (const [element] of this.elementGestureMap) {
this.unregisterAllGestures(element);
}
// Clear all templates
this.gestureTemplates.clear();
this.elementGestureMap.clear();
this.activeGesturesRegistry.destroy();
this.keyboardManager.destroy();
this.pointerManager.destroy();
}
}
exports.GestureManager = GestureManager;