import HTML from "../utils/HTML"; /** Event dispatched when a component's connectedCallback has completed. Can be listened to from anywhere (bubbles). */ export const CONNECTED = "sonic-connected"; const READY_TIMEOUT_MS = 5000; const CONNECTED_SYMBOL = Symbol.for("sonic-connected"); function isWebComponent(element: Element): boolean { return element.tagName.includes("-"); } function waitForAncestorReady( ancestor: Element, timeoutMs = READY_TIMEOUT_MS, ): Promise { if (!isWebComponent(ancestor)) return Promise.resolve(); if ((ancestor as any)[CONNECTED_SYMBOL]) return Promise.resolve(); return new Promise((resolve) => { const handler = () => { (ancestor as any)[CONNECTED_SYMBOL] = true; ancestor.removeEventListener(CONNECTED, handler); resolve(); }; ancestor.addEventListener(CONNECTED, handler); setTimeout(() => { ancestor.removeEventListener(CONNECTED, handler); resolve(); }, timeoutMs); }); } /** * Décorateur de classe qui retarde le connectedCallback jusqu'à ce que tous les ancêtres * correspondant aux sélecteurs CSS fournis aient exécuté leur connectedCallback. * Les nœuds qui ne sont pas des web components sont considérés comme connectés par défaut. * S'applique uniquement aux web components (classes étendant HTMLElement). */ export function awaitConnectedAncestors(...selectors: string[]) { return function ( constructor: T, ): T { if (!selectors.length) return constructor; const originalConnectedCallback = constructor.prototype.connectedCallback; constructor.prototype.connectedCallback = function (this: HTMLElement) { const ancestors = HTML.getAncestorsBySelectors(this, selectors); const webComponentAncestors = ancestors.filter(isWebComponent); const whenDefinedPromises = [ ...new Set(webComponentAncestors.map((a) => a.tagName.toLowerCase())), ].map((tag) => customElements.whenDefined(tag)); const readyPromises = ancestors.map((a) => waitForAncestorReady(a)); Promise.all([...whenDefinedPromises, ...readyPromises]).then(() => { originalConnectedCallback?.call(this); }); }; return constructor; }; } /** * Décorateur de classe pour les ancêtres qui veulent signaler qu'ils sont prêts. * Dispatche l'événement "sonic-connected" à la fin du connectedCallback (bubbles). */ export function dispatchConnectedEvent() { return function ( constructor: T, ): T { const original = constructor.prototype.connectedCallback; constructor.prototype.connectedCallback = function (this: HTMLElement) { original?.call(this); (this as any)[CONNECTED_SYMBOL] = true; this.dispatchEvent(new CustomEvent(CONNECTED, { bubbles: true })); }; return constructor; }; }