export abstract class TesoftComponent extends HTMLElement { private ready: boolean; protected constructor() { super(); this.ready = false; } // noinspection JSUnusedGlobalSymbols async connectedCallback() { const promises = []; for (const child of findComponents(this.shadowRoot)) { promises.push(new Promise(resolve => window.customElements.whenDefined(child.tagName.toLowerCase()).then(() => { if (child.isConnected) { resolve(void {}); } else { child.addEventListener("ready", (e: any) => { e.preventDefault(); resolve(void {}); }, {once: true}); } }) )); } await Promise.all(promises); await this.populate(); this.ready = true; this.dispatchEvent(new CustomEvent("ready", { bubbles: true, cancelable: true, })); } async populate() { // Overwrite if necessary in components } get isReady(): boolean { return this.ready; } } function findComponents(root: any | null): TesoftComponent[] { const components = []; for (const child of root?.querySelectorAll("*") ?? []) { if (child.tagName.toLowerCase().startsWith("snz-")) { components.push(child); } } return components; } export function onReady(callback: () => void, ...components: TesoftComponent[]) { async function domLoaded() { if (components.length === 0) { components = findComponents(document); } const promises = []; for (const component of components) { if (!component.isReady) { promises.push(new Promise(resolve => { component.addEventListener("ready", resolve, {once: true}); })); } } await Promise.all(promises); callback(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", domLoaded, {once: true}); } else { // noinspection JSIgnoredPromiseFromCall domLoaded(); } }