UNPKG

@stringsync/vexml

Version:

MusicXML to Vexflow

131 lines (130 loc) 5.88 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.NativeBridge = void 0; const util = __importStar(require("../util")); /** * This class is responsible for lazily managing the activation and deactivation of native events on a host element. * * - Activation is the process initializing the native event machinery needed for a given vexml event. * - Deactivation is the process of cleaning up the native event machinery when a given vexml event is no longer needed. * - Native events are only added to the host element when they are needed. */ class NativeBridge { root; mappings; nativeEventTopic; nativeEventOpts; // Handles for native event topic subscribers indexed by the vexml event name. handles = {}; constructor(root, mappings, nativeEventTopic, nativeEventOpts) { this.root = root; this.mappings = mappings; this.nativeEventTopic = nativeEventTopic; this.nativeEventOpts = nativeEventOpts; } /** Returns whether the vexml event is activated. */ isActivated(vexmlEventName) { return vexmlEventName in this.handles; } /** * Activates a vexml event, initializing the native event machinery if needed. * * NOTE: vexml events cannot be activated if they are already active. It is the caller's responsibility to ensure that * the event is not already active. */ activate(vexmlEventName) { util.assert(!this.isActivated(vexmlEventName), `vexml event is already active: ${vexmlEventName}`); const mapping = this.mappings.find((m) => m.vexml.includes(vexmlEventName)); if (!mapping) { return; } this.handles[vexmlEventName] ??= []; for (const native of Object.entries(mapping.native)) { const nativeEventName = native[0]; const nativeEventListener = native[1]; // Enforce only a single listener per native event. vexml is intended to consume the event through the // nativeEventTopic. That way, we only run the native callbacks that we need to run. if (!this.nativeEventTopic.hasSubscribers(nativeEventName)) { const srcElement = mapping.src === 'scroll' ? this.root.getScrollContainer() : this.root.getOverlay().getElement(); srcElement.addEventListener(nativeEventName, this.publishNativeEvent, this.nativeEventOpts[nativeEventName]); } const handle = this.nativeEventTopic.subscribe(nativeEventName, nativeEventListener); this.handles[vexmlEventName].push(handle); } } /** * Deactivates a vexml event, cleaning up the native event machinery if needed. * * NOTE: vexml events cannot be deactivated if they are already inactive. It is the caller's responsibility to ensure * that the event is not already inactive. */ deactivate(vexmlEventName) { util.assert(this.isActivated(vexmlEventName), `vexml event is already inactive: ${vexmlEventName}`); const mapping = this.mappings.find((m) => m.vexml.includes(vexmlEventName)); if (!mapping) { return; } for (const handle of this.handles[vexmlEventName]) { this.nativeEventTopic.unsubscribe(handle); } delete this.handles[vexmlEventName]; for (const native of Object.entries(mapping.native)) { const nativeEventName = native[0]; if (!this.nativeEventTopic.hasSubscribers(nativeEventName)) { const srcElement = mapping.src === 'scroll' ? this.root.getScrollContainer() : this.root.getOverlay().getElement(); srcElement.removeEventListener(nativeEventName, this.publishNativeEvent, this.nativeEventOpts[nativeEventName]); } } } /** Deactivates all vexml events. */ deactivateAll() { for (const vexmlEventName of Object.keys(this.handles)) { this.deactivate(vexmlEventName); } } /** * Forwards a native event to its respective topic. * * NOTE: This is done in this manner so we can have a reference to the function that is added as a native event * listener. This is necessary for unsubscribing from the native event listener when the dependent vexml events are * deactivated. */ publishNativeEvent = (event) => { this.nativeEventTopic.publish(event.type, event); return false; }; } exports.NativeBridge = NativeBridge;