// on désactive une regle eslint pour ce fichier /* eslint no-async-promise-executor: 0 */ // --> OFF export type SearchableDomElement = HTMLElement | ShadowRoot; import { APIConfiguration } from "./api"; class HTML { /** * retourne la langue de la page courante telle que défini via l'attribut lang de la balise html */ static getLanguage(): string { const documentLanguage = document.documentElement.lang; const selectedLanguage = localStorage.getItem("SonicSelectedLanguage"); return selectedLanguage || (documentLanguage as string); } static getCookies() { return document.cookie .split(";") .reduce((previous: Record, current) => { const eqIdx = current.indexOf("="); previous[current.substring(0, eqIdx).trim()] = current.substring( eqIdx + 1 ); return previous; }, {}); } static everyAncestors( node: SearchableDomElement, callback: (node: SearchableDomElement) => /*continue ?*/ boolean ) { while (node) { const result = callback(node); if (!result) return; node = (node.parentNode || (node as ShadowRoot).host) as SearchableDomElement; } } static getScrollableAncestor(node: SearchableDomElement) { //Based on overflow-y: auto, overflow-y: scroll, overflow-y: hidden while (node) { const htmlNode = node as HTMLElement; if (htmlNode.nodeType === 1) { const style = window.getComputedStyle(htmlNode); if ( style?.overflowY === "auto" || style?.overflowY === "scroll" || style?.overflowY === "hidden" || style?.overflowX === "auto" || style?.overflowX === "scroll" || style?.overflowX === "hidden" ) { return node; } } node = (node.parentNode || (node as ShadowRoot).host) as SearchableDomElement; } return null; } /** * Va de parent en parent en partant de node pour trouver un attribut * @param attributeName nom de l'attribut * @returns valeur de l'attribut ou null si l'attribut n'est pas trouvé */ static getAncestorAttributeValue( node: SearchableDomElement | null, attributeName: string ): string | null { if (!node) return null; while (!("hasAttribute" in node && node.hasAttribute(attributeName))) { const newNode = node.parentNode || (node as ShadowRoot).host; if (!newNode) break; node = (node.parentNode || (node as ShadowRoot).host) as SearchableDomElement; } if (!("hasAttribute" in node)) { return null; } return node.getAttribute(attributeName); } /** * Petite fonction utilitaire pour retourner la configuration a passer à l'utilitaire API * Utilisée pour la configuration du wording / de la traduction ainsi que par le mixin fetcher par exemple */ static getApiConfiguration(node: SearchableDomElement): APIConfiguration { const token = HTML.getAncestorAttributeValue(node, "token") as string; const addHTTPResponse = HTML.getAncestorAttributeValue(node, "addHTTPResponse") != null; const serviceURL = HTML.getAncestorAttributeValue(node, "serviceURL"); let userName = null; let password = null; const tokenProvider = HTML.getAncestorAttributeValue(node, "tokenProvider"); const authToken = HTML.getAncestorAttributeValue(node, "eventsApiToken"); if (!token) { userName = HTML.getAncestorAttributeValue(node, "userName"); password = HTML.getAncestorAttributeValue(node, "password"); } const credentials = (HTML.getAncestorAttributeValue( node, "credentials" ) as RequestCredentials) || undefined; const cache = (node as Element).getAttribute("cache") as RequestCache; const blockUntilDone = (node as Element).hasAttribute("blockUntilDone"); const keepAlive = (node as Element).hasAttribute("keepAlive"); return { serviceURL, token, userName, password, authToken, tokenProvider, addHTTPResponse, credentials, cache, blockUntilDone, keepAlive, }; } /** * Va de parent en parent en partant de node pour trouver un attribut * @param attributeName nom de l'attribut * @returns valeur de l'attribut ou null si l'attribut n'est pas trouvé */ static getClosestElement(node: SearchableDomElement, selector: string) { while (!(node.nodeName && node.nodeName.toLowerCase() === selector)) { const newNode = node.parentNode || (node as ShadowRoot).host; if (!newNode) break; node = (node.parentNode || (node as ShadowRoot).host) as SearchableDomElement; } if (!node.nodeName) { return null; } return node; } /** * Va de parent en parent en partant de node pour trouver un attribut * @param attributeName nom de l'attribut * @returns valeur de l'attribut ou null si l'attribut n'est pas trouvé */ static getClosestForm(node: SearchableDomElement) { return HTML.getClosestElement(node, "form"); } /** * Parcourt les ancêtres (parentNode / shadow host) et collecte ceux dont le tagName * correspond à l'un des noms fournis (comparaison insensible à la casse). * @param node Élément de départ * @param tagNames Noms de balises à rechercher (ex: ['sonic-subscriber', 'sonic-sdui']) * @returns Tableau des ancêtres correspondants */ static getAncestorsByTagNames( node: SearchableDomElement, tagNames: string[] ): Element[] { const normalized = new Set(tagNames.map((t) => t.toLowerCase())); const result: Element[] = []; let current = (node.parentNode || (node as ShadowRoot).host) as SearchableDomElement; while (current) { if ( current instanceof Element && normalized.has(current.tagName.toLowerCase()) ) { result.push(current); } current = (current.parentNode || (current as ShadowRoot).host) as SearchableDomElement; } return result; } /** * Parcourt les ancêtres (parentNode / shadow host) et collecte ceux qui matchent * l'un des sélecteurs CSS fournis (element.matches(selector)). * @param node Élément de départ * @param selectors Sélecteurs CSS (ex: ['sonic-subscriber', 'sonic-sdui', 'div.container']) * @returns Tableau des ancêtres correspondants */ static getAncestorsBySelectors( node: SearchableDomElement, selectors: string[] ): Element[] { const result: Element[] = []; let current = (node.parentNode || (node as ShadowRoot).host) as SearchableDomElement; while (current) { if (current instanceof Element) { for (const selector of selectors) { try { if (current.matches(selector)) { result.push(current); break; } } catch { // Invalid selector, skip } } } current = (current.parentNode || (current as ShadowRoot).host) as SearchableDomElement; } return result; } /** * Lance le chargement d'un js et retourne une promise qui resoud à true lorsque le chargement à réussi et à false, sinon. * */ static async loadJS(src: string) { const p = new Promise(async (resolve) => { const script = document.createElement("script"); script.src = src; script.onload = () => resolve(true); script.onerror = () => resolve(true); document.head.appendChild(script); }); return p; } /** * Lance le chargement d'un css et retourne une promise qui resoud à true lorsque le chargement à réussi et à false, sinon. * */ static async loadCSS(src: string) { const p = new Promise(async (resolve) => { const cssnode = document.createElement("link"); cssnode.type = "text/css"; cssnode.rel = "stylesheet"; cssnode.href = src; cssnode.onload = () => resolve(true); cssnode.onerror = () => resolve(true); document.head.appendChild(cssnode); }); return p; } } export default HTML; export const detecHTMLLanguageChange = (handler: () => void) => { // Select the element const htmlElement = document.documentElement; // Create a new MutationObserver instance const observer = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { if (mutation.type === "attributes" && mutation.attributeName === "lang") { handler(); } } }); // Observer configuration: watch for changes to attributes const config = { attributes: true, // Watch for attribute changes attributeFilter: ["lang"], // Only watch for changes to the lang attribute }; // Start observing the element for changes observer.observe(htmlElement, config); };