UNPKG

@mui/x-internal-gestures

Version:

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

268 lines (262 loc) 8.22 kB
import _extends from "@babel/runtime/helpers/esm/extends"; export class PointerManager { pointers = (() => new Map())(); count = 0; constructor(mode) { this.mode = mode; this.clearPointers(); } clearPointers() { this.pointers.clear(); if (this.mode === 'mouse') { this.pointers.set(1, { id: 1, x: NaN, y: NaN, target: document.body }); } } addPointers(pointer) { if (this.mode === 'mouse') { // Mouse mode only allows one pointer return; } if (Array.isArray(pointer)) { pointer.forEach(p => this.addPointers(p)); return; } if (this.pointers.has(pointer.id)) { return; } this.pointers.set(pointer.id, pointer); } removePointers(id) { if (this.mode === 'mouse') { // Mouse pointer cannot be removed return; } if (Array.isArray(id)) { id.forEach(pointerId => this.pointers.delete(pointerId)); return; } this.pointers.delete(id); } updatePointers(pointer) { if (Array.isArray(pointer)) { return pointer.map(p => this.updatePointers(p)); } const existingPointer = this.pointers.get(pointer.id); if (!existingPointer) { throw new Error(`Pointer with id ${pointer.id} does not exist`); } const newPointer = _extends({}, existingPointer, pointer); this.pointers.set(pointer.id, newPointer); if (newPointer.target === existingPointer.target) { return { pointer: newPointer }; } const oldTarget = existingPointer.target; return { oldTarget, pointer: newPointer }; } nextId() { this.count += 1; return 500 + this.count; } parseMousePointer(pointer, target) { if (this.mode !== 'mouse') { throw new Error('Mouse pointer can only be used in mouse mode'); } // Get the existing mouse pointer (always id: 1) const existingPointer = this.pointers.get(1); const existingX = Number.isNaN(existingPointer?.x) ? undefined : existingPointer?.x; const existingY = Number.isNaN(existingPointer?.y) ? undefined : existingPointer?.y; const finalTarget = pointer?.target ?? target; const targetRect = finalTarget.getBoundingClientRect(); // Use existing coordinates if available and not being overridden const x = pointer?.x ?? existingX ?? targetRect.left + targetRect.width / 2; const y = pointer?.y ?? existingY ?? targetRect.top + targetRect.height / 2; return { id: 1, x, y, target: finalTarget }; } parsePointers(pointers, target, defaultConfig) { const normalizedPointers = Array.isArray(pointers) ? pointers : _extends({}, defaultConfig, pointers); if (this.mode === 'mouse') { // If the mode is mouse, we only need one pointer if (Array.isArray(normalizedPointers) && normalizedPointers.length > 1 || !Array.isArray(normalizedPointers) && normalizedPointers.amount !== 1) { throw new Error('Mouse mode only supports one pointer'); } } // Normalize pointers to be an array let pointersArray = []; if (!Array.isArray(normalizedPointers)) { const { amount, distance: pointerDistance, ids } = normalizedPointers; // Get the target element's bounding rect const targetRect = target.getBoundingClientRect(); const centerX = targetRect.left + targetRect.width / 2; const centerY = targetRect.top + targetRect.height / 2; // Create pointers in a circle around the center of the target pointersArray = Array.from({ length: amount }).map((_, index) => { const pointerId = ids?.[index] ?? this.nextId(); const existingPointer = ids?.[index] ? this.pointers.get(ids[index]) : undefined; // Only calculate new positions if no existing position is available const angle = Math.PI * 2 * index / amount; const x = existingPointer?.x ?? centerX + Math.cos(angle) * pointerDistance / 2; const y = existingPointer?.y ?? centerY + Math.sin(angle) * pointerDistance / 2; return { id: pointerId, x, y, target }; }); } else { const allTargets = new Set(normalizedPointers.map(pointer => pointer.target ?? target)); const targetRectMap = new Map(Array.from(allTargets).map(currentTarget => { const rect = currentTarget.getBoundingClientRect(); return [currentTarget, { centerX: rect.left + rect.width / 2, centerY: rect.top + rect.height / 2 }]; })); pointersArray = normalizedPointers.map(pointer => { const pointerId = pointer.id ?? this.nextId(); const existingPointer = pointer.id ? this.pointers.get(pointer.id) : undefined; return { id: pointerId, target: pointer.target ?? target, // Use existing coordinates if available and not being overridden x: pointer.x ?? existingPointer?.x ?? targetRectMap.get(pointer.target ?? target).centerX, y: pointer.y ?? existingPointer?.y ?? targetRectMap.get(pointer.target ?? target).centerY }; }); } this.addPointers(pointersArray); return pointersArray; } pointerEnter(pointer) { const over = new PointerEvent('pointerover', { bubbles: true, cancelable: true, composed: true, clientX: pointer.x, clientY: pointer.y, pointerId: pointer.id, pointerType: this.mode }); const enter = new PointerEvent('pointerenter', { bubbles: false, cancelable: false, composed: true, clientX: pointer.x, clientY: pointer.y, pointerId: pointer.id, pointerType: this.mode }); pointer.target.dispatchEvent(over); pointer.target.dispatchEvent(enter); } pointerLeave(pointer, oldTarget) { const out = new PointerEvent('pointerout', { bubbles: true, cancelable: true, composed: true, clientX: pointer.x, clientY: pointer.y, pointerId: pointer.id, pointerType: this.mode }); const leave = new PointerEvent('pointerleave', { bubbles: false, cancelable: false, composed: true, clientX: pointer.x, clientY: pointer.y, pointerId: pointer.id, pointerType: this.mode }); oldTarget.dispatchEvent(out); oldTarget.dispatchEvent(leave); } pointerDown(pointer) { if (this.pointers.get(pointer.id)?.isDown === true) { return; } const change = this.updatePointers(_extends({}, pointer, { isDown: true })); if (change?.oldTarget) { const { oldTarget, pointer: currentPointer } = change; this.pointerLeave(currentPointer, oldTarget); this.pointerEnter(currentPointer); } const event = new PointerEvent('pointerdown', { bubbles: true, cancelable: true, composed: true, clientX: pointer.x, clientY: pointer.y, pointerId: pointer.id, pointerType: this.mode }); pointer.target.dispatchEvent(event); } pointerMove(pointer) { const change = this.updatePointers(pointer); if (change?.oldTarget) { const { oldTarget, pointer: currentPointer } = change; this.pointerLeave(currentPointer, oldTarget); this.pointerEnter(currentPointer); } const event = new PointerEvent('pointermove', { bubbles: true, cancelable: true, composed: true, clientX: pointer.x, clientY: pointer.y, pointerId: pointer.id, pointerType: this.mode }); pointer.target.dispatchEvent(event); } pointerUp(pointer) { // TODO: Only fire if all pointers are up const event = new PointerEvent('pointerup', { bubbles: true, cancelable: true, composed: true, clientX: pointer.x, clientY: pointer.y, pointerId: pointer.id, pointerType: this.mode }); if (this.mode === 'mouse') { this.updatePointers(_extends({}, pointer, { isDown: false })); } else { this.removePointers(pointer.id); } pointer.target.dispatchEvent(event); } }