UNPKG

domtranslator

Version:
81 lines (79 loc) 3.54 kB
import { isElementNode } from '../utils/nodes'; import { XMutationObserver } from '../utils/XMutationObserver'; /** * Translates DOM tree persistently. When nodes in tree is updates, it will be automatically translated. */ export class PersistentDOMTranslator { translator; constructor(translator) { this.translator = translator; } // Stores nodes mutated as a result of translation // used to prevent handling mutation events triggered by our own translations mutatedNodes = new WeakSet(); observedNodesStorage = new Map(); translate(node) { if (this.observedNodesStorage.has(node)) { throw new Error('Node already under observe'); } // Observe node and children changes const observer = new XMutationObserver(); this.observedNodesStorage.set(node, observer); observer.addHandler('elementAdded', ({ target }) => { if (this.translator.has(target)) return; this.translator.translate(target, (node) => this.mutatedNodes.add(node)); }); observer.addHandler('elementRemoved', ({ target }) => { this.translator.restore(target); }); observer.addHandler('characterData', ({ target }) => { // skip this update if it was triggered by the translation itself if (this.mutatedNodes.has(target)) { this.mutatedNodes.delete(target); return; } // Translate/update translation depends on state if (this.translator.has(target)) { this.translator.update(target, (node) => this.mutatedNodes.add(node)); } else { this.translator.translate(target, (node) => this.mutatedNodes.add(node)); } }); observer.addHandler('changeAttribute', ({ target, attributeName }) => { if (!attributeName || !isElementNode(target)) return; const attribute = target.attributes.getNamedItem(attributeName); if (attribute === null) return; // skip this update if it was triggered by the translation itself if (this.mutatedNodes.has(attribute)) { this.mutatedNodes.delete(attribute); return; } // NOTE: If need delete untracked nodes, we should keep relates like Element -> attributes if (this.translator.has(attribute)) { this.translator.update(attribute, (node) => this.mutatedNodes.add(node)); } else { this.translator.translate(attribute, (node) => this.mutatedNodes.add(node)); } }); observer.observe(node); this.translator.translate(node, (node) => this.mutatedNodes.add(node)); } restore(node) { if (!this.observedNodesStorage.has(node)) { throw new Error('Node is not under observe'); } // mutatedNodes may include nodes from multiple observed tree elements — remove only those belonging to the unobserved // restoreNode calls the callback after restoring each node; the callback removes that node from mutatedNodes this.translator.restore(node, (node) => { this.mutatedNodes.delete(node); }); this.observedNodesStorage.get(node)?.disconnect(); this.observedNodesStorage.delete(node); } } //# sourceMappingURL=../sourcemaps/PersistentDOMTranslator/index.js.map