UNPKG

wave-roll

Version:

JavaScript Library for Comparative MIDI Piano-Roll Visualization

1,189 lines 80.9 kB
import { T as O, U as Z, P as f, r as te, E as y, a as ie, w as _, e as P, C as V } from "./index-B5Sv2rNJ.js"; import "./webworkerAll-CL0K5VmR.js"; class q { constructor(e) { this._lastTransform = "", this._observer = null, this._tickerAttached = !1, this.updateTranslation = () => { if (!this._canvas) return; const t = this._canvas.getBoundingClientRect(), i = this._canvas.width, n = this._canvas.height, s = t.width / i * this._renderer.resolution, o = t.height / n * this._renderer.resolution, r = t.left, h = t.top, d = `translate(${r}px, ${h}px) scale(${s}, ${o})`; d !== this._lastTransform && (this._domElement.style.transform = d, this._lastTransform = d); }, this._domElement = e.domElement, this._renderer = e.renderer, !(globalThis.OffscreenCanvas && this._renderer.canvas instanceof OffscreenCanvas) && (this._canvas = this._renderer.canvas, this._attachObserver()); } /** The canvas element that this CanvasObserver is associated with. */ get canvas() { return this._canvas; } /** Attaches the DOM element to the canvas parent if it is not already attached. */ ensureAttached() { !this._domElement.parentNode && this._canvas.parentNode && (this._canvas.parentNode.appendChild(this._domElement), this.updateTranslation()); } /** Sets up a ResizeObserver if available. This ensures that the DOM element is kept in sync with the canvas size . */ _attachObserver() { "ResizeObserver" in globalThis ? (this._observer && (this._observer.disconnect(), this._observer = null), this._observer = new ResizeObserver((e) => { for (const t of e) { if (t.target !== this._canvas) continue; const i = this.canvas.width, n = this.canvas.height, s = t.contentRect.width / i * this._renderer.resolution, o = t.contentRect.height / n * this._renderer.resolution; (this._lastScaleX !== s || this._lastScaleY !== o) && (this.updateTranslation(), this._lastScaleX = s, this._lastScaleY = o); } }), this._observer.observe(this._canvas)) : this._tickerAttached || O.shared.add(this.updateTranslation, this, Z.HIGH); } /** Destroys the CanvasObserver instance, cleaning up observers and Ticker. */ destroy() { this._observer ? (this._observer.disconnect(), this._observer = null) : this._tickerAttached && O.shared.remove(this.updateTranslation), this._domElement = null, this._renderer = null, this._canvas = null, this._tickerAttached = !1, this._lastTransform = "", this._lastScaleX = null, this._lastScaleY = null; } } class w { /** * @param manager - The event boundary which manages this event. Propagation can only occur * within the boundary's jurisdiction. */ constructor(e) { this.bubbles = !0, this.cancelBubble = !0, this.cancelable = !1, this.composed = !1, this.defaultPrevented = !1, this.eventPhase = w.prototype.NONE, this.propagationStopped = !1, this.propagationImmediatelyStopped = !1, this.layer = new f(), this.page = new f(), this.NONE = 0, this.CAPTURING_PHASE = 1, this.AT_TARGET = 2, this.BUBBLING_PHASE = 3, this.manager = e; } /** @readonly */ get layerX() { return this.layer.x; } /** @readonly */ get layerY() { return this.layer.y; } /** @readonly */ get pageX() { return this.page.x; } /** @readonly */ get pageY() { return this.page.y; } /** * Fallback for the deprecated `InteractionEvent.data`. * @deprecated since 7.0.0 */ get data() { return this; } /** * The propagation path for this event. Alias for {@link EventBoundary.propagationPath}. * @advanced */ composedPath() { return this.manager && (!this.path || this.path[this.path.length - 1] !== this.target) && (this.path = this.target ? this.manager.propagationPath(this.target) : []), this.path; } /** * Unimplemented method included for implementing the DOM interface `Event`. It will throw an `Error`. * @deprecated * @ignore * @param _type * @param _bubbles * @param _cancelable */ initEvent(e, t, i) { throw new Error("initEvent() is a legacy DOM API. It is not implemented in the Federated Events API."); } /** * Unimplemented method included for implementing the DOM interface `UIEvent`. It will throw an `Error`. * @ignore * @deprecated * @param _typeArg * @param _bubblesArg * @param _cancelableArg * @param _viewArg * @param _detailArg */ initUIEvent(e, t, i, n, s) { throw new Error("initUIEvent() is a legacy DOM API. It is not implemented in the Federated Events API."); } /** * Prevent default behavior of both PixiJS and the user agent. * @example * ```ts * sprite.on('click', (event) => { * // Prevent both browser's default click behavior * // and PixiJS's default handling * event.preventDefault(); * * // Custom handling * customClickHandler(); * }); * ``` * @remarks * - Only works if the native event is cancelable * - Does not stop event propagation */ preventDefault() { this.nativeEvent instanceof Event && this.nativeEvent.cancelable && this.nativeEvent.preventDefault(), this.defaultPrevented = !0; } /** * Stop this event from propagating to any additional listeners, including those * on the current target and any following targets in the propagation path. * @example * ```ts * container.on('pointerdown', (event) => { * // Stop all further event handling * event.stopImmediatePropagation(); * * // These handlers won't be called: * // - Other pointerdown listeners on this container * // - Any pointerdown listeners on parent containers * }); * ``` * @remarks * - Immediately stops all event propagation * - Prevents other listeners on same target from being called * - More aggressive than stopPropagation() */ stopImmediatePropagation() { this.propagationImmediatelyStopped = !0; } /** * Stop this event from propagating to the next target in the propagation path. * The rest of the listeners on the current target will still be notified. * @example * ```ts * child.on('pointermove', (event) => { * // Handle event on child * updateChild(); * * // Prevent parent handlers from being called * event.stopPropagation(); * }); * * // This won't be called if child handles the event * parent.on('pointermove', (event) => { * updateParent(); * }); * ``` * @remarks * - Stops event bubbling to parent containers * - Does not prevent other listeners on same target * - Less aggressive than stopImmediatePropagation() */ stopPropagation() { this.propagationStopped = !0; } } var I = /iPhone/i, C = /iPod/i, L = /iPad/i, U = /\biOS-universal(?:.+)Mac\b/i, k = /\bAndroid(?:.+)Mobile\b/i, R = /Android/i, b = /(?:SD4930UR|\bSilk(?:.+)Mobile\b)/i, M = /Silk/i, v = /Windows Phone/i, X = /\bWindows(?:.+)ARM\b/i, Y = /BlackBerry/i, H = /BB10/i, F = /Opera Mini/i, N = /\b(CriOS|Chrome)(?:.+)Mobile/i, $ = /Mobile(?:.+)Firefox\b/i, K = function(a) { return typeof a < "u" && a.platform === "MacIntel" && typeof a.maxTouchPoints == "number" && a.maxTouchPoints > 1 && typeof MSStream > "u"; }; function ne(a) { return function(e) { return e.test(a); }; } function G(a) { var e = { userAgent: "", platform: "", maxTouchPoints: 0 }; !a && typeof navigator < "u" ? e = { userAgent: navigator.userAgent, platform: navigator.platform, maxTouchPoints: navigator.maxTouchPoints || 0 } : typeof a == "string" ? e.userAgent = a : a && a.userAgent && (e = { userAgent: a.userAgent, platform: a.platform, maxTouchPoints: a.maxTouchPoints || 0 }); var t = e.userAgent, i = t.split("[FBAN"); typeof i[1] < "u" && (t = i[0]), i = t.split("Twitter"), typeof i[1] < "u" && (t = i[0]); var n = ne(t), s = { apple: { phone: n(I) && !n(v), ipod: n(C), tablet: !n(I) && (n(L) || K(e)) && !n(v), universal: n(U), device: (n(I) || n(C) || n(L) || n(U) || K(e)) && !n(v) }, amazon: { phone: n(b), tablet: !n(b) && n(M), device: n(b) || n(M) }, android: { phone: !n(v) && n(b) || !n(v) && n(k), tablet: !n(v) && !n(b) && !n(k) && (n(M) || n(R)), device: !n(v) && (n(b) || n(M) || n(k) || n(R)) || n(/\bokhttp\b/i) }, windows: { phone: n(v), tablet: n(X), device: n(v) || n(X) }, other: { blackberry: n(Y), blackberry10: n(H), opera: n(F), firefox: n($), chrome: n(N), device: n(Y) || n(H) || n(F) || n($) || n(N) }, any: !1, phone: !1, tablet: !1 }; return s.any = s.apple.device || s.android.device || s.windows.device || s.other.device, s.phone = s.apple.phone || s.android.phone || s.windows.phone, s.tablet = s.apple.tablet || s.android.tablet || s.windows.tablet, s; } const se = G.default ?? G, oe = se(globalThis.navigator), re = 9, W = 100, ae = 0, he = 0, j = 2, z = 1, le = -1e3, ce = -1e3, de = 2, S = class J { // eslint-disable-next-line jsdoc/require-param /** * @param {WebGLRenderer|WebGPURenderer} renderer - A reference to the current renderer */ constructor(e, t = oe) { this._mobileInfo = t, this.debug = !1, this._activateOnTab = !0, this._deactivateOnMouseMove = !0, this._isActive = !1, this._isMobileAccessibility = !1, this._div = null, this._pool = [], this._renderId = 0, this._children = [], this._androidUpdateCount = 0, this._androidUpdateFrequency = 500, this._hookDiv = null, (t.tablet || t.phone) && this._createTouchHook(), this._renderer = e; } /** * Value of `true` if accessibility is currently active and accessibility layers are showing. * @type {boolean} * @readonly */ get isActive() { return this._isActive; } /** * Value of `true` if accessibility is enabled for touch devices. * @type {boolean} * @readonly */ get isMobileAccessibility() { return this._isMobileAccessibility; } /** * The DOM element that will sit over the PixiJS element. This is where the div overlays will go. * @readonly */ get hookDiv() { return this._hookDiv; } /** * Creates the touch hooks. * @private */ _createTouchHook() { const e = document.createElement("button"); e.style.width = `${z}px`, e.style.height = `${z}px`, e.style.position = "absolute", e.style.top = `${le}px`, e.style.left = `${ce}px`, e.style.zIndex = de.toString(), e.style.backgroundColor = "#FF0000", e.title = "select to enable accessibility for this content", e.addEventListener("focus", () => { this._isMobileAccessibility = !0, this._activate(), this._destroyTouchHook(); }), document.body.appendChild(e), this._hookDiv = e; } /** * Destroys the touch hooks. * @private */ _destroyTouchHook() { this._hookDiv && (document.body.removeChild(this._hookDiv), this._hookDiv = null); } /** * Activating will cause the Accessibility layer to be shown. * This is called when a user presses the tab key. * @private */ _activate() { if (this._isActive) return; this._isActive = !0, this._div || (this._div = document.createElement("div"), this._div.style.position = "absolute", this._div.style.top = `${ae}px`, this._div.style.left = `${he}px`, this._div.style.pointerEvents = "none", this._div.style.zIndex = j.toString(), this._canvasObserver = new q({ domElement: this._div, renderer: this._renderer })), this._activateOnTab && (this._onKeyDown = this._onKeyDown.bind(this), globalThis.addEventListener("keydown", this._onKeyDown, !1)), this._deactivateOnMouseMove && (this._onMouseMove = this._onMouseMove.bind(this), globalThis.document.addEventListener("mousemove", this._onMouseMove, !0)); const e = this._renderer.view.canvas; if (e.parentNode) this._canvasObserver.ensureAttached(), this._initAccessibilitySetup(); else { const t = new MutationObserver(() => { e.parentNode && (t.disconnect(), this._canvasObserver.ensureAttached(), this._initAccessibilitySetup()); }); t.observe(document.body, { childList: !0, subtree: !0 }); } } // New method to handle initialization after div is ready _initAccessibilitySetup() { this._renderer.runners.postrender.add(this), this._renderer.lastObjectRendered && this._updateAccessibleObjects(this._renderer.lastObjectRendered); } /** * Deactivates the accessibility system. Removes listeners and accessibility elements. * @private */ _deactivate() { if (!(!this._isActive || this._isMobileAccessibility)) { this._isActive = !1, globalThis.document.removeEventListener("mousemove", this._onMouseMove, !0), this._activateOnTab && globalThis.addEventListener("keydown", this._onKeyDown, !1), this._renderer.runners.postrender.remove(this); for (const e of this._children) e._accessibleDiv && e._accessibleDiv.parentNode && (e._accessibleDiv.parentNode.removeChild(e._accessibleDiv), e._accessibleDiv = null), e._accessibleActive = !1; this._pool.forEach((e) => { e.parentNode && e.parentNode.removeChild(e); }), this._div && this._div.parentNode && this._div.parentNode.removeChild(this._div), this._pool = [], this._children = []; } } /** * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. * @private * @param {Container} container - The Container to check. */ _updateAccessibleObjects(e) { if (!e.visible || !e.accessibleChildren) return; e.accessible && (e._accessibleActive || this._addChild(e), e._renderId = this._renderId); const t = e.children; if (t) for (let i = 0; i < t.length; i++) this._updateAccessibleObjects(t[i]); } /** * Runner init called, view is available at this point. * @ignore */ init(e) { const i = { accessibilityOptions: { ...J.defaultOptions, ...e?.accessibilityOptions || {} } }; this.debug = i.accessibilityOptions.debug, this._activateOnTab = i.accessibilityOptions.activateOnTab, this._deactivateOnMouseMove = i.accessibilityOptions.deactivateOnMouseMove, i.accessibilityOptions.enabledByDefault ? this._activate() : this._activateOnTab && (this._onKeyDown = this._onKeyDown.bind(this), globalThis.addEventListener("keydown", this._onKeyDown, !1)), this._renderer.runners.postrender.remove(this); } /** * Updates the accessibility layer during rendering. * - Removes divs for containers no longer in the scene * - Updates the position and dimensions of the root div * - Updates positions of active accessibility divs * Only fires while the accessibility system is active. * @ignore */ postrender() { const e = performance.now(); if (this._mobileInfo.android.device && e < this._androidUpdateCount || (this._androidUpdateCount = e + this._androidUpdateFrequency, !this._renderer.renderingToScreen || !this._renderer.view.canvas)) return; const t = /* @__PURE__ */ new Set(); if (this._renderer.lastObjectRendered) { this._updateAccessibleObjects(this._renderer.lastObjectRendered); for (const i of this._children) i._renderId === this._renderId && t.add(this._children.indexOf(i)); } for (let i = this._children.length - 1; i >= 0; i--) { const n = this._children[i]; t.has(i) || (n._accessibleDiv && n._accessibleDiv.parentNode && (n._accessibleDiv.parentNode.removeChild(n._accessibleDiv), this._pool.push(n._accessibleDiv), n._accessibleDiv = null), n._accessibleActive = !1, te(this._children, i, 1)); } this._renderer.renderingToScreen && this._canvasObserver.ensureAttached(); for (let i = 0; i < this._children.length; i++) { const n = this._children[i]; if (!n._accessibleActive || !n._accessibleDiv) continue; const s = n._accessibleDiv, o = n.hitArea || n.getBounds().rectangle; if (n.hitArea) { const r = n.worldTransform; s.style.left = `${r.tx + o.x * r.a}px`, s.style.top = `${r.ty + o.y * r.d}px`, s.style.width = `${o.width * r.a}px`, s.style.height = `${o.height * r.d}px`; } else this._capHitArea(o), s.style.left = `${o.x}px`, s.style.top = `${o.y}px`, s.style.width = `${o.width}px`, s.style.height = `${o.height}px`; } this._renderId++; } /** * private function that will visually add the information to the * accessibility div * @param {HTMLElement} div - */ _updateDebugHTML(e) { e.innerHTML = `type: ${e.type}</br> title : ${e.title}</br> tabIndex: ${e.tabIndex}`; } /** * Adjust the hit area based on the bounds of a display object * @param {Rectangle} hitArea - Bounds of the child */ _capHitArea(e) { e.x < 0 && (e.width += e.x, e.x = 0), e.y < 0 && (e.height += e.y, e.y = 0); const { width: t, height: i } = this._renderer; e.x + e.width > t && (e.width = t - e.x), e.y + e.height > i && (e.height = i - e.y); } /** * Creates or reuses a div element for a Container and adds it to the accessibility layer. * Sets up ARIA attributes, event listeners, and positioning based on the container's properties. * @private * @param {Container} container - The child to make accessible. */ _addChild(e) { let t = this._pool.pop(); t || (e.accessibleType === "button" ? t = document.createElement("button") : (t = document.createElement(e.accessibleType), t.style.cssText = ` color: transparent; pointer-events: none; padding: 0; margin: 0; border: 0; outline: 0; background: transparent; box-sizing: border-box; user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; `, e.accessibleText && (t.innerText = e.accessibleText)), t.style.width = `${W}px`, t.style.height = `${W}px`, t.style.backgroundColor = this.debug ? "rgba(255,255,255,0.5)" : "transparent", t.style.position = "absolute", t.style.zIndex = j.toString(), t.style.borderStyle = "none", navigator.userAgent.toLowerCase().includes("chrome") ? t.setAttribute("aria-live", "off") : t.setAttribute("aria-live", "polite"), navigator.userAgent.match(/rv:.*Gecko\//) ? t.setAttribute("aria-relevant", "additions") : t.setAttribute("aria-relevant", "text"), t.addEventListener("click", this._onClick.bind(this)), t.addEventListener("focus", this._onFocus.bind(this)), t.addEventListener("focusout", this._onFocusOut.bind(this))), t.style.pointerEvents = e.accessiblePointerEvents, t.type = e.accessibleType, e.accessibleTitle && e.accessibleTitle !== null ? t.title = e.accessibleTitle : (!e.accessibleHint || e.accessibleHint === null) && (t.title = `container ${e.tabIndex}`), e.accessibleHint && e.accessibleHint !== null && t.setAttribute("aria-label", e.accessibleHint), this.debug && this._updateDebugHTML(t), e._accessibleActive = !0, e._accessibleDiv = t, t.container = e, this._children.push(e), this._div.appendChild(e._accessibleDiv), e.interactive && (e._accessibleDiv.tabIndex = e.tabIndex); } /** * Dispatch events with the EventSystem. * @param e * @param type * @private */ _dispatchEvent(e, t) { const { container: i } = e.target, n = this._renderer.events.rootBoundary, s = Object.assign(new w(n), { target: i }); n.rootTarget = this._renderer.lastObjectRendered, t.forEach((o) => n.dispatchEvent(s, o)); } /** * Maps the div button press to pixi's EventSystem (click) * @private * @param {MouseEvent} e - The click event. */ _onClick(e) { this._dispatchEvent(e, ["click", "pointertap", "tap"]); } /** * Maps the div focus events to pixi's EventSystem (mouseover) * @private * @param {FocusEvent} e - The focus event. */ _onFocus(e) { e.target.getAttribute("aria-live") || e.target.setAttribute("aria-live", "assertive"), this._dispatchEvent(e, ["mouseover"]); } /** * Maps the div focus events to pixi's EventSystem (mouseout) * @private * @param {FocusEvent} e - The focusout event. */ _onFocusOut(e) { e.target.getAttribute("aria-live") || e.target.setAttribute("aria-live", "polite"), this._dispatchEvent(e, ["mouseout"]); } /** * Is called when a key is pressed * @private * @param {KeyboardEvent} e - The keydown event. */ _onKeyDown(e) { e.keyCode !== re || !this._activateOnTab || this._activate(); } /** * Is called when the mouse moves across the renderer element * @private * @param {MouseEvent} e - The mouse event. */ _onMouseMove(e) { e.movementX === 0 && e.movementY === 0 || this._deactivate(); } /** * Destroys the accessibility system. Removes all elements and listeners. * > [!IMPORTANT] This is typically called automatically when the {@link Application} is destroyed. * > A typically user should not need to call this method directly. */ destroy() { this._deactivate(), this._destroyTouchHook(), this._canvasObserver?.destroy(), this._canvasObserver = null, this._div = null, this._pool = null, this._children = null, this._renderer = null, this._activateOnTab && globalThis.removeEventListener("keydown", this._onKeyDown); } /** * Enables or disables the accessibility system. * @param enabled - Whether to enable or disable accessibility. * @example * ```js * app.renderer.accessibility.setAccessibilityEnabled(true); // Enable accessibility * app.renderer.accessibility.setAccessibilityEnabled(false); // Disable accessibility * ``` */ setAccessibilityEnabled(e) { e ? this._activate() : this._deactivate(); } }; S.extension = { type: [ y.WebGLSystem, y.WebGPUSystem ], name: "accessibility" }; S.defaultOptions = { /** * Whether to enable accessibility features on initialization * @default false */ enabledByDefault: !1, /** * Whether to visually show the accessibility divs for debugging * @default false */ debug: !1, /** * Whether to activate accessibility when tab key is pressed * @default true */ activateOnTab: !0, /** * Whether to deactivate accessibility when mouse moves * @default true */ deactivateOnMouseMove: !0 }; let ue = S; const pe = { accessible: !1, accessibleTitle: null, accessibleHint: null, tabIndex: 0, accessibleType: "button", accessibleText: null, accessiblePointerEvents: "auto", accessibleChildren: !0, _accessibleActive: !1, _accessibleDiv: null, _renderId: -1 }; class Q { /** * Constructor for the DOMPipe class. * @param renderer - The renderer instance that this DOMPipe will be associated with. */ constructor(e) { this._attachedDomElements = [], this._renderer = e, this._renderer.runners.postrender.add(this), this._renderer.runners.init.add(this), this._domElement = document.createElement("div"), this._domElement.style.position = "absolute", this._domElement.style.top = "0", this._domElement.style.left = "0", this._domElement.style.pointerEvents = "none", this._domElement.style.zIndex = "1000"; } /** Initializes the DOMPipe, setting up the main DOM element and adding it to the document body. */ init() { this._canvasObserver = new q({ domElement: this._domElement, renderer: this._renderer }); } /** * Adds a renderable DOM container to the list of attached elements. * @param domContainer - The DOM container to be added. * @param _instructionSet - The instruction set (unused). */ addRenderable(e, t) { this._attachedDomElements.includes(e) || this._attachedDomElements.push(e); } /** * Updates a renderable DOM container. * @param _domContainer - The DOM container to be updated (unused). */ updateRenderable(e) { } /** * Validates a renderable DOM container. * @param _domContainer - The DOM container to be validated (unused). * @returns Always returns true as validation is not required. */ validateRenderable(e) { return !0; } /** Handles the post-rendering process, ensuring DOM elements are correctly positioned and visible. */ postrender() { const e = this._attachedDomElements; if (e.length === 0) { this._domElement.remove(); return; } this._canvasObserver.ensureAttached(); for (let t = 0; t < e.length; t++) { const i = e[t], n = i.element; if (!i.parent || i.globalDisplayStatus < 7) n?.remove(), e.splice(t, 1), t--; else { this._domElement.contains(n) || (n.style.position = "absolute", n.style.pointerEvents = "auto", this._domElement.appendChild(n)); const s = i.worldTransform, o = i._anchor, r = i.width * o.x, h = i.height * o.y; n.style.transformOrigin = `${r}px ${h}px`, n.style.transform = `matrix(${s.a}, ${s.b}, ${s.c}, ${s.d}, ${s.tx - r}, ${s.ty - h})`, n.style.opacity = i.groupAlpha.toString(); } } } /** Destroys the DOMPipe, removing all attached DOM elements and cleaning up resources. */ destroy() { this._renderer.runners.postrender.remove(this); for (let e = 0; e < this._attachedDomElements.length; e++) this._attachedDomElements[e].element?.remove(); this._attachedDomElements.length = 0, this._domElement.remove(), this._canvasObserver.destroy(), this._renderer = null; } } Q.extension = { type: [ y.WebGLPipes, y.WebGPUPipes, y.CanvasPipes ], name: "dom" }; class ve { constructor() { this.interactionFrequency = 10, this._deltaTime = 0, this._didMove = !1, this._tickerAdded = !1, this._pauseUpdate = !0; } /** * Initializes the event ticker. * @param events - The event system. */ init(e) { this.removeTickerListener(), this.events = e, this.interactionFrequency = 10, this._deltaTime = 0, this._didMove = !1, this._tickerAdded = !1, this._pauseUpdate = !0; } /** Whether to pause the update checks or not. */ get pauseUpdate() { return this._pauseUpdate; } set pauseUpdate(e) { this._pauseUpdate = e; } /** Adds the ticker listener. */ addTickerListener() { this._tickerAdded || !this.domElement || (O.system.add(this._tickerUpdate, this, Z.INTERACTION), this._tickerAdded = !0); } /** Removes the ticker listener. */ removeTickerListener() { this._tickerAdded && (O.system.remove(this._tickerUpdate, this), this._tickerAdded = !1); } /** Sets flag to not fire extra events when the user has already moved there mouse */ pointerMoved() { this._didMove = !0; } /** Updates the state of interactive objects. */ _update() { if (!this.domElement || this._pauseUpdate) return; if (this._didMove) { this._didMove = !1; return; } const e = this.events._rootPointerEvent; this.events.supportsTouchEvents && e.pointerType === "touch" || globalThis.document.dispatchEvent(this.events.supportsPointerEvents ? new PointerEvent("pointermove", { clientX: e.clientX, clientY: e.clientY, pointerType: e.pointerType, pointerId: e.pointerId }) : new MouseEvent("mousemove", { clientX: e.clientX, clientY: e.clientY })); } /** * Updates the state of interactive objects if at least {@link interactionFrequency} * milliseconds have passed since the last invocation. * * Invoked by a throttled ticker update from {@link Ticker.system}. * @param ticker - The throttled ticker. */ _tickerUpdate(e) { this._deltaTime += e.deltaTime, !(this._deltaTime < this.interactionFrequency) && (this._deltaTime = 0, this._update()); } } const g = new ve(); class A extends w { constructor() { super(...arguments), this.client = new f(), this.movement = new f(), this.offset = new f(), this.global = new f(), this.screen = new f(); } /** @readonly */ get clientX() { return this.client.x; } /** @readonly */ get clientY() { return this.client.y; } /** * Alias for {@link FederatedMouseEvent.clientX this.clientX}. * @readonly */ get x() { return this.clientX; } /** * Alias for {@link FederatedMouseEvent.clientY this.clientY}. * @readonly */ get y() { return this.clientY; } /** @readonly */ get movementX() { return this.movement.x; } /** @readonly */ get movementY() { return this.movement.y; } /** @readonly */ get offsetX() { return this.offset.x; } /** @readonly */ get offsetY() { return this.offset.y; } /** @readonly */ get globalX() { return this.global.x; } /** @readonly */ get globalY() { return this.global.y; } /** * The pointer coordinates in the renderer's screen. Alias for `screen.x`. * @readonly */ get screenX() { return this.screen.x; } /** * The pointer coordinates in the renderer's screen. Alias for `screen.y`. * @readonly */ get screenY() { return this.screen.y; } /** * Converts global coordinates into container-local coordinates. * * This method transforms coordinates from world space to a container's local space, * useful for precise positioning and hit testing. * @param container - The Container to get local coordinates for * @param point - Optional Point object to store the result. If not provided, a new Point will be created * @param globalPos - Optional custom global coordinates. If not provided, the event's global position is used * @returns The local coordinates as a Point object * @example * ```ts * // Basic usage - get local coordinates relative to a container * sprite.on('pointermove', (event: FederatedMouseEvent) => { * // Get position relative to the sprite * const localPos = event.getLocalPosition(sprite); * console.log('Local position:', localPos.x, localPos.y); * }); * // Using custom global coordinates * const customGlobal = new Point(100, 100); * sprite.on('pointermove', (event: FederatedMouseEvent) => { * // Transform custom coordinates * const localPos = event.getLocalPosition(sprite, undefined, customGlobal); * console.log('Custom local position:', localPos.x, localPos.y); * }); * ``` * @see {@link Container.worldTransform} For the transformation matrix * @see {@link Point} For the point class used to store coordinates */ getLocalPosition(e, t, i) { return e.worldTransform.applyInverse(i || this.global, t); } /** * Whether the modifier key was pressed when this event natively occurred. * @param key - The modifier key. */ getModifierState(e) { return "getModifierState" in this.nativeEvent && this.nativeEvent.getModifierState(e); } /** * Not supported. * @param _typeArg * @param _canBubbleArg * @param _cancelableArg * @param _viewArg * @param _detailArg * @param _screenXArg * @param _screenYArg * @param _clientXArg * @param _clientYArg * @param _ctrlKeyArg * @param _altKeyArg * @param _shiftKeyArg * @param _metaKeyArg * @param _buttonArg * @param _relatedTargetArg * @deprecated since 7.0.0 * @ignore */ // eslint-disable-next-line max-params initMouseEvent(e, t, i, n, s, o, r, h, d, l, m, c, p, D, Ee) { throw new Error("Method not implemented."); } } class u extends A { constructor() { super(...arguments), this.width = 0, this.height = 0, this.isPrimary = !1; } /** * Only included for completeness for now * @ignore */ getCoalescedEvents() { return this.type === "pointermove" || this.type === "mousemove" || this.type === "touchmove" ? [this] : []; } /** * Only included for completeness for now * @ignore */ getPredictedEvents() { throw new Error("getPredictedEvents is not supported!"); } } class E extends A { constructor() { super(...arguments), this.DOM_DELTA_PIXEL = 0, this.DOM_DELTA_LINE = 1, this.DOM_DELTA_PAGE = 2; } } E.DOM_DELTA_PIXEL = 0; E.DOM_DELTA_LINE = 1; E.DOM_DELTA_PAGE = 2; const fe = 2048, me = new f(), T = new f(); class _e { /** * @param rootTarget - The holder of the event boundary. */ constructor(e) { this.dispatch = new ie(), this.moveOnAll = !1, this.enableGlobalMoveEvents = !0, this.mappingState = { trackingData: {} }, this.eventPool = /* @__PURE__ */ new Map(), this._allInteractiveElements = [], this._hitElements = [], this._isPointerMoveEvent = !1, this.rootTarget = e, this.hitPruneFn = this.hitPruneFn.bind(this), this.hitTestFn = this.hitTestFn.bind(this), this.mapPointerDown = this.mapPointerDown.bind(this), this.mapPointerMove = this.mapPointerMove.bind(this), this.mapPointerOut = this.mapPointerOut.bind(this), this.mapPointerOver = this.mapPointerOver.bind(this), this.mapPointerUp = this.mapPointerUp.bind(this), this.mapPointerUpOutside = this.mapPointerUpOutside.bind(this), this.mapWheel = this.mapWheel.bind(this), this.mappingTable = {}, this.addEventMapping("pointerdown", this.mapPointerDown), this.addEventMapping("pointermove", this.mapPointerMove), this.addEventMapping("pointerout", this.mapPointerOut), this.addEventMapping("pointerleave", this.mapPointerOut), this.addEventMapping("pointerover", this.mapPointerOver), this.addEventMapping("pointerup", this.mapPointerUp), this.addEventMapping("pointerupoutside", this.mapPointerUpOutside), this.addEventMapping("wheel", this.mapWheel); } /** * Adds an event mapping for the event `type` handled by `fn`. * * Event mappings can be used to implement additional or custom events. They take an event * coming from the upstream scene (or directly from the {@link EventSystem}) and dispatch new downstream events * generally trickling down and bubbling up to {@link EventBoundary.rootTarget this.rootTarget}. * * To modify the semantics of existing events, the built-in mapping methods of EventBoundary should be overridden * instead. * @param type - The type of upstream event to map. * @param fn - The mapping method. The context of this function must be bound manually, if desired. */ addEventMapping(e, t) { this.mappingTable[e] || (this.mappingTable[e] = []), this.mappingTable[e].push({ fn: t, priority: 0 }), this.mappingTable[e].sort((i, n) => i.priority - n.priority); } /** * Dispatches the given event * @param e - The event to dispatch. * @param type - The type of event to dispatch. Defaults to `e.type`. */ dispatchEvent(e, t) { e.propagationStopped = !1, e.propagationImmediatelyStopped = !1, this.propagate(e, t), this.dispatch.emit(t || e.type, e); } /** * Maps the given upstream event through the event boundary and propagates it downstream. * @param e - The event to map. */ mapEvent(e) { if (!this.rootTarget) return; const t = this.mappingTable[e.type]; if (t) for (let i = 0, n = t.length; i < n; i++) t[i].fn(e); else _(`[EventBoundary]: Event mapping not defined for ${e.type}`); } /** * Finds the Container that is the target of a event at the given coordinates. * * The passed (x,y) coordinates are in the world space above this event boundary. * @param x - The x coordinate of the event. * @param y - The y coordinate of the event. */ hitTest(e, t) { g.pauseUpdate = !0; const n = this._isPointerMoveEvent && this.enableGlobalMoveEvents ? "hitTestMoveRecursive" : "hitTestRecursive", s = this[n]( this.rootTarget, this.rootTarget.eventMode, me.set(e, t), this.hitTestFn, this.hitPruneFn ); return s && s[0]; } /** * Propagate the passed event from from {@link EventBoundary.rootTarget this.rootTarget} to its * target `e.target`. * @param e - The event to propagate. * @param type - The type of event to propagate. Defaults to `e.type`. */ propagate(e, t) { if (!e.target) return; const i = e.composedPath(); e.eventPhase = e.CAPTURING_PHASE; for (let n = 0, s = i.length - 1; n < s; n++) if (e.currentTarget = i[n], this.notifyTarget(e, t), e.propagationStopped || e.propagationImmediatelyStopped) return; if (e.eventPhase = e.AT_TARGET, e.currentTarget = e.target, this.notifyTarget(e, t), !(e.propagationStopped || e.propagationImmediatelyStopped)) { e.eventPhase = e.BUBBLING_PHASE; for (let n = i.length - 2; n >= 0; n--) if (e.currentTarget = i[n], this.notifyTarget(e, t), e.propagationStopped || e.propagationImmediatelyStopped) return; } } /** * Emits the event `e` to all interactive containers. The event is propagated in the bubbling phase always. * * This is used in the `globalpointermove` event. * @param e - The emitted event. * @param type - The listeners to notify. * @param targets - The targets to notify. */ all(e, t, i = this._allInteractiveElements) { if (i.length === 0) return; e.eventPhase = e.BUBBLING_PHASE; const n = Array.isArray(t) ? t : [t]; for (let s = i.length - 1; s >= 0; s--) n.forEach((o) => { e.currentTarget = i[s], this.notifyTarget(e, o); }); } /** * Finds the propagation path from {@link EventBoundary.rootTarget rootTarget} to the passed * `target`. The last element in the path is `target`. * @param target - The target to find the propagation path to. */ propagationPath(e) { const t = [e]; for (let i = 0; i < fe && e !== this.rootTarget && e.parent; i++) { if (!e.parent) throw new Error("Cannot find propagation path to disconnected target"); t.push(e.parent), e = e.parent; } return t.reverse(), t; } hitTestMoveRecursive(e, t, i, n, s, o = !1) { let r = !1; if (this._interactivePrune(e)) return null; if ((e.eventMode === "dynamic" || t === "dynamic") && (g.pauseUpdate = !1), e.interactiveChildren && e.children) { const l = e.children; for (let m = l.length - 1; m >= 0; m--) { const c = l[m], p = this.hitTestMoveRecursive( c, this._isInteractive(t) ? t : c.eventMode, i, n, s, o || s(e, i) ); if (p) { if (p.length > 0 && !p[p.length - 1].parent) continue; const D = e.isInteractive(); (p.length > 0 || D) && (D && this._allInteractiveElements.push(e), p.push(e)), this._hitElements.length === 0 && (this._hitElements = p), r = !0; } } } const h = this._isInteractive(t), d = e.isInteractive(); return d && d && this._allInteractiveElements.push(e), o || this._hitElements.length > 0 ? null : r ? this._hitElements : h && !s(e, i) && n(e, i) ? d ? [e] : [] : null; } /** * Recursive implementation for {@link EventBoundary.hitTest hitTest}. * @param currentTarget - The Container that is to be hit tested. * @param eventMode - The event mode for the `currentTarget` or one of its parents. * @param location - The location that is being tested for overlap. * @param testFn - Callback that determines whether the target passes hit testing. This callback * can assume that `pruneFn` failed to prune the container. * @param pruneFn - Callback that determiness whether the target and all of its children * cannot pass the hit test. It is used as a preliminary optimization to prune entire subtrees * of the scene graph. * @returns An array holding the hit testing target and all its ancestors in order. The first element * is the target itself and the last is {@link EventBoundary.rootTarget rootTarget}. This is the opposite * order w.r.t. the propagation path. If no hit testing target is found, null is returned. */ hitTestRecursive(e, t, i, n, s) { if (this._interactivePrune(e) || s(e, i)) return null; if ((e.eventMode === "dynamic" || t === "dynamic") && (g.pauseUpdate = !1), e.interactiveChildren && e.children) { const h = e.children, d = i; for (let l = h.length - 1; l >= 0; l--) { const m = h[l], c = this.hitTestRecursive( m, this._isInteractive(t) ? t : m.eventMode, d, n, s ); if (c) { if (c.length > 0 && !c[c.length - 1].parent) continue; const p = e.isInteractive(); return (c.length > 0 || p) && c.push(e), c; } } } const o = this._isInteractive(t), r = e.isInteractive(); return o && n(e, i) ? r ? [e] : [] : null; } _isInteractive(e) { return e === "static" || e === "dynamic"; } _interactivePrune(e) { return !e || !e.visible || !e.renderable || !e.measurable || e.eventMode === "none" || e.eventMode === "passive" && !e.interactiveChildren; } /** * Checks whether the container or any of its children cannot pass the hit test at all. * * {@link EventBoundary}'s implementation uses the {@link Container.hitArea hitArea} * and {@link Container._maskEffect} for pruning. * @param container - The container to prune. * @param location - The location to test for overlap. */ hitPruneFn(e, t) { if (e.hitArea && (e.worldTransform.applyInverse(t, T), !e.hitArea.contains(T.x, T.y))) return !0; if (e.effects && e.effects.length) for (let i = 0; i < e.effects.length; i++) { const n = e.effects[i]; if (n.containsPoint && !n.containsPoint(t, this.hitTestFn)) return !0; } return !1; } /** * Checks whether the container passes hit testing for the given location. * @param container - The container to test. * @param location - The location to test for overlap. * @returns - Whether `container` passes hit testing for `location`. */ hitTestFn(e, t) { return e.hitArea ? !0 : e?.containsPoint ? (e.worldTransform.applyInverse(t, T), e.containsPoint(T)) : !1; } /** * Notify all the listeners to the event's `currentTarget`. * * If the `currentTarget` contains the property `on<type>`, then it is called here, * simulating the behavior from version 6.x and prior. * @param e - The event passed to the target. * @param type - The type of event to notify. Defaults to `e.type`. */ notifyTarget(e, t) { if (!e.currentTarget.isInteractive()) return; t ?? (t = e.type); const i = `on${t}`; e.currentTarget[i]?.(e); const n = e.eventPhase === e.CAPTURING_PHASE || e.eventPhase === e.AT_TARGET ? `${t}capture` : t; this._notifyListeners(e, n), e.eventPhase === e.AT_TARGET && this._notifyListeners(e, t); } /** * Maps the upstream `pointerdown` events to a downstream `pointerdown` event. * * `touchstart`, `rightdown`, `mousedown` events are also dispatched for specific pointer types. * @param from - The upstream `pointerdown` event. */ mapPointerDown(e) { if (!(e instanceof u)) { _("EventBoundary cannot map a non-pointer event as a pointer event"); return; } const t = this.createPointerEvent(e); if (this.dispatchEvent(t, "pointerdown"), t.pointerType === "touch") this.dispatchEvent(t, "touchstart"); else if (t.pointerType === "mouse" || t.pointerType === "pen") { const n = t.button === 2; this.dispatchEvent(t, n ? "rightdown" : "mousedown"); } const i = this.trackingData(e.pointerId); i.pressTargetsByButton[e.button] = t.composedPath(), this.freeEvent(t); } /** * Maps the upstream `pointermove` to downstream `pointerout`, `pointerover`, and `pointermove` events, in that order. * * The tracking data for the specific pointer has an updated `overTarget`. `mouseout`, `mouseover`, * `mousemove`, and `touchmove` events are fired as well for specific pointer types. * @param from - The upstream `pointermove` event. */ mapPointerMove(e) { if (!(e instanceof u)) { _("EventBoundary cannot map a non-pointer event as a pointer event"); return; } this._allInteractiveElements.length = 0, this._hitElements.length = 0, this._isPointerMoveEvent = !0; const t = this.createPointerEvent(e); this._isPointerMoveEvent = !1; const i = t.pointerType === "mouse" || t.pointerType === "pen", n = this.trackingData(e.pointerId), s = this.findMountedTarget(n.overTargets); if (n.overTargets?.length > 0 && s !== t.target) { const h = e.type === "mousemove" ? "mouseout" : "pointerout", d = this.createPointerEvent(e, h, s); if (this.dispatchEvent(d, "pointerout"), i && this.dispatchEvent(d, "mouseout"), !t.composedPath().includes(s)) { const l = this.createPointerEvent(e, "pointerleave", s); for (l.eventPhase = l.AT_TARGET; l.target && !t.composedPath().includes(l.target); ) l.currentTarget = l.target, this.notifyTarget(l), i && this.notifyTarget(l, "mouseleave"), l.target = l.target.parent; this.freeEvent(l); } this.freeEvent(d); } if (s !== t.target) { const h = e.type === "mousemove" ? "mouseover" : "pointerover", d = this.clonePointerEvent(t, h); this.dispatchEvent(d, "pointerover"), i && this.dispatchEvent(d, "mouseover"); let l = s?.parent; for (; l && l !== this.rootTarget.parent && l !== t.target; ) l = l.parent; if (!l || l === this.rootTarget.parent) { const c = this.clonePointerEvent(t, "pointerenter"); for (c.eventPhase = c.AT_TARGET; c.target && c.target !== s && c.target !== this.rootTarget.parent; ) c.currentTarget = c.target, this.notifyTarget(c), i && this.notifyTarget(c, "mouseenter"), c.target = c.target.parent; this.freeEvent(c); } this.freeEvent(d); } const o = [], r = this.enableGlobalMoveEvents ?? !0; this.moveOnAll ? o.push("pointermove") : this.dispatchEvent(t, "pointermove"), r && o.push("globalpointermove"), t.pointerType === "touch" && (this.moveOnAll ? o.splice(1, 0, "touchmove") : this.dispatchEvent(t, "touchmove"), r && o.push("globaltouchmove")), i && (this.moveOnAll ? o.splice(1, 0, "mousemove") : this.dispatchEvent(t, "mousemove"), r && o.push("globalmousemove"), this.cursor = t.target?.cursor), o.length > 0 && this.all(t, o), this._allInteractiveElements.length = 0, this._hitElements.length = 0, n.overTargets = t.composedPath(), this.freeEvent(t); } /** * Maps the upstream `pointerover` to downstream `pointerover` and `pointerenter` events, in that order. * * The tracking data for the specific pointer gets a new `overTarget`. * @param from - The upstream `pointerover` event. */ mapPointerOver(e) { if (!(e instanceof u)) { _("EventBoundary cannot map a non-pointer event as a pointer event"); return; } const t = this.trackingData(e.pointerId), i = this.createPointerEvent(e), n = i.pointerType === "mouse" || i.pointerType === "pen"; this.dispatchEvent(i, "pointerover"), n && this.dispatchEvent(i, "mouseover"), i.pointerType === "mouse" && (this.cursor = i.target?.cursor); const s = this.clonePointerEvent(i, "pointerenter"); for (s.eventPhase = s.AT_TARGET; s.target && s.target !== this.rootTarget.parent; ) s.currentTarget = s.target, this.notifyTarget(s), n && this.notifyTarget(s, "mouseenter"), s.target = s.target.parent; t.overTargets = i.composedPath(), this.freeEvent(i), this.freeEvent(s); } /** * Maps the upstream `pointerout` to downstream `pointerout`, `pointerleave` events, in that order. * * The tracking data for the specific pointer is cleared of a `overTarget`. * @param from - The upstream `pointerout` event. */ mapPointerOut(e) { if (!(e instanceof u)) { _("EventBoundary cannot map a non-pointer event as a pointer event"); return; } const t = this.trackingData(e.pointerId); if (t.overTargets) { const i = e.pointerType === "mouse" || e.pointerType === "pen", n = this.findMountedTarget(t.overTargets), s = this.createPointerEvent(e, "pointerout", n); this.dispatchEvent(s), i && this.dispatchEvent(s, "mouseout"); const o = this.createPointerEvent(e, "pointerleave", n); for (o.eventPhase = o.AT_TARGET; o.target && o.target !== this.rootTarget.parent; ) o.currentTarget = o.target, this.notifyTarget(o), i && this.notifyTarget(o, "mouseleave"), o.target = o.target.parent; t.overTargets = null, this.freeEvent(s), this.freeEvent(o); } this.cursor = null; } /** * Maps the upstream `pointerup` event to downstream `pointerup`, `pointerupoutside`, * and `click`/`rightclick`/`pointertap` events, in that order. * * The `pointerupoutside` event bubbles from the original `pointerdown` target to the most specific * ancestor of the `pointerdown` and `pointerup` targets, which is also the `click` event's target. `touchend`, * `rightup`, `mouseup`, `touchendoutside`, `rightupoutside`, `mouseupoutside`, and `tap` are fired as well for * specific pointer types. * @param from - The upstream `pointerup` event. */ mapPointerUp(e) { if (!(e instanceof u)) { _("EventBoundary cannot map a non-pointer event as a pointer event"); return; } const t = performance.now(), i = this.createPointerEvent(e); if (this.dispatchEvent(i, "pointerup"), i.pointerType === "touch") this.dispatchEvent(i, "touchend"); else if (i.pointerType === "mouse" || i.pointerType === "pen") { const r = i.button === 2; this.dispatchEvent(i, r ? "rightup" : "mouseup"); } const n = this.trackingData(e.pointerId), s = this.findMountedTarget(n.pressTargetsByButton[e.button]); let o = s; if (s && !i.composedPath().includes(s)) { let r = s; for (; r && !i.composedPath().includes(r); ) { if (i.currentTarget = r, this.notifyTarget(i, "pointerupoutside"), i.pointerType === "touch") this.notifyTarget(i, "to