import { id as mid } from "module"; import { TraceSource } from "@implab/core-amd/log/TraceSource"; import { argumentNotNull } from "@implab/core-amd/safe"; import { getScope, render } from "./render"; import { RenditionBase } from "./RenditionBase"; import { Scope } from "./Scope"; import { Subscribable } from "../observable"; import { Cancellation } from "@implab/core-amd/Cancellation"; import { collectNodes, destroy, isDocumentFragmentNode, isMounted, placeAt, startupWidgets } from "./traits"; const trace = TraceSource.get(mid); export class WatchRendition extends RenditionBase { private readonly _component: (arg: T) => unknown; private readonly _node: Node; private readonly _scope = new Scope(); private readonly _subject: Subscribable; private _renderJob?: { value: T }; private _ct = Cancellation.none; constructor(component: (arg: T) => unknown, subject: Subscribable) { super(); argumentNotNull(component, "component"); this._component = component; this._subject = subject; this._node = document.createComment("[Watch]"); } protected _create() { const scope = getScope(); scope.own(() => { this._scope.destroy(); destroy(this._node); }); scope.own(this._subject.subscribe({ next: this._onValue })); this._ct = new Cancellation(cancel => scope.own(cancel)); } private readonly _onValue = (value: T) => { if (!this._renderJob) { // schedule a new job this._renderJob = { value }; this._render().catch(e => trace.error(e)); } else { // update existing job this._renderJob = { value }; } }; private async _render() { // fork await Promise.resolve(); // don't render destroyed rendition if (this._ct.isRequested()) return; // remove all previous content this._scope.clean(); // render the new node const node = render( this._renderJob ? this._component(this._renderJob.value) : undefined, this._scope ); // get actual content const pending = isDocumentFragmentNode(node) ? collectNodes(node.childNodes) : [node]; placeAt(node, this._node, "after"); if (isMounted(this._node)) pending.forEach(n => startupWidgets(n)); if (pending.length) this._scope.own(() => pending.forEach(destroy)); this._renderJob = undefined; } protected _getDomNode() { if (!this._node) throw new Error("The instance of the widget isn't created"); return this._node; } }