import { RefProps, VNode } from 'lupine.web'; export type HtmlVarValueProps = string | null | undefined | VNode | (() => Promise>); export type HtmlVarResult = { value: HtmlVarValueProps; ref: RefProps; node: VNode }; export class HtmlVar implements HtmlVarResult { private _value: string | VNode | (() => Promise>); private _dirty = false; private _ref: RefProps; private resolve!: () => void; private promise: Promise; private updatePromise: Promise | null = null; constructor(initial?: HtmlVarValueProps) { this.promise = new Promise((res) => { this.resolve = res; }); this._value = initial || ''; this._ref = { onLoad: async (el: Element) => { // in case new resolve is created while updating const res = this.resolve; if (this._dirty) { await this.update(); } res(); }, }; } private async update(): Promise { if (this.updatePromise) { return this.updatePromise; } this.updatePromise = (async () => { while (this._dirty) { this._dirty = false; const v = typeof this._value === 'function' ? await this._value() : this._value; await this._ref.mountInnerComponent!(v); if (!this._dirty) { this._value = ''; } } })(); await this.updatePromise; this.updatePromise = null; } // need to wait before use ref.current async waitUpdate() { await this.promise; } set value(value: HtmlVarValueProps) { this._value = value || ''; if (this._dirty) { return; } this._dirty = true; if (!this._ref.current) { return; } this.promise = new Promise(async (res) => { this.resolve = res; await this.update(); this.resolve(); }); } get value(): HtmlVarValueProps { return this._ref.current ? this._ref.current.innerHTML : this._value; } get ref(): RefProps { return this._ref; } get node(): VNode { // if value is a function, it will be loaded later in onLoad const delayLoad = typeof this._value === 'function'; this._dirty = delayLoad ? true : false; // the Fragment Tag will be present in the html if ref is assigned return { type: 'Fragment', props: { ref: this._ref, children: delayLoad ? '' : this._value, }, html: [], }; } }