@v4fire/client
Version:
V4Fire client core library
208 lines (174 loc) • 4.78 kB
text/typescript
/* 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();
}
}