UNPKG

@v4fire/client

Version:

V4Fire client core library

208 lines (174 loc) 4.78 kB
/* eslint-disable @typescript-eslint/no-unused-vars-experimental */ /*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ /** * [[include:traits/i-observe-dom/README.md]] * @packageDocumentation */ import symbolGenerator from 'core/symbol'; import type iBlock from 'super/i-block/i-block'; import type { ObserveOptions, Observer, Observers, ObserverMutationRecord, ChangedNodes } from 'traits/i-observe-dom/interface'; export * from 'traits/i-observe-dom/interface'; export const $$ = symbolGenerator(); export default abstract class iObserveDOM { /** * Starts to observe DOM changes of the specified node * * @param component * @param opts */ static observe<T extends iBlock>(component: T & iObserveDOM, opts: ObserveOptions): void { const {node} = opts; const observers = this.getObserversMap(component); if (!opts.reInit && this.isNodeBeingObserved(component, opts.node)) { return; } if (observers.has(node)) { this.unobserve(component, node); } observers.set(node, iObserveDOM.createObserver(component, opts)); } /** * Stops to observe DOM changes of the specified node * * @param component * @param node */ static unobserve<T extends iBlock>(component: T & iObserveDOM, node: Element): void { const observers = this.getObserversMap(component), observer = observers.get(node); if (!observer) { return; } observers.delete(node); component.unsafe.async.clearAll({label: observer.key}); } /** * Filters the added and removed nodes * * @param records * @param filter */ static filterNodes(records: MutationRecord[], filter: (node: Node) => boolean): ObserverMutationRecord[] { return records.map((r) => ({ ...r, addedNodes: Array.from(r.addedNodes).filter(filter), removedNodes: Array.from(r.removedNodes).filter(filter) })); } /** * Returns changed nodes * @param records */ static getChangedNodes(records: MutationRecord[] | ObserverMutationRecord[]): ChangedNodes { const res = { addedNodes: <ChangedNodes['addedNodes']>[], removedNodes: <ChangedNodes['removedNodes']>[] }; for (let i = 0; i < records.length; i++) { res.addedNodes = res.addedNodes.concat(Array.from(records[i].addedNodes)); res.removedNodes = res.removedNodes.concat(Array.from(records[i].removedNodes)); } res.addedNodes = [].union(res.addedNodes); res.removedNodes = [].union(res.removedNodes); return res; } /** @see [[iObserveDOM.onDOMChange]] */ static onDOMChange<T extends iBlock>( component: T & iObserveDOM, records?: MutationRecord[], opts?: ObserveOptions ): void { this.emitDOMChange(component, records, opts); } /** * Fires an event that the DOM tree has been changed * * @param component * @param [records] * @param [opts] * * @emits `localEmitter:DOMChange(records?: MutationRecord[], options?: ObserverOptions)` * @emits `DOMChange(records?: MutationRecord[], options?: ObserverOptions)` */ static emitDOMChange<T extends iBlock>( component: T & iObserveDOM, records?: MutationRecord[], opts?: ObserveOptions ): void { component.unsafe.localEmitter.emit('DOMChange', records, opts); component.emit('DOMChange', records, opts); } /** * Returns true if `MutationObserver` is already observing the specified node * * @param component * @param node */ static isNodeBeingObserved<T extends iBlock>(component: T & iObserveDOM, node: Element): boolean { return this.getObserversMap(component).has(node); } /** * Returns a map of component observers * @param component */ protected static getObserversMap<T extends iBlock>(component: T & iObserveDOM): Observers { return component[$$.DOMObservers] ?? (component[$$.DOMObservers] = new Map()); } /** * Creates an observer * * @param component * @param opts */ protected static createObserver<T extends iBlock>(component: T & iObserveDOM, opts: ObserveOptions): Observer { const {async: $a} = component.unsafe, {node} = opts; const label = this.getObserverKey(); const observer = new MutationObserver((records) => { component.onDOMChange(records, opts); }); observer.observe(node, opts); $a.worker(observer, {label}); return { key: label, observer }; } /** * Generates the unique key and returns it */ protected static getObserverKey(): string { return String(Math.random()); } /** * Initializes observers */ abstract initDOMObservers(): void; /** * Handler: the DOM tree has been changed * * @param records * @param options */ onDOMChange(records: MutationRecord[], options: ObserveOptions): void { return Object.throw(); } }