import { Route } from "./route"; declare type Listener = { location: string; }; declare type ActivableLink = HTMLElement & { location?: string; href?: Route | string; goBack?: Route | string | null; autoActive?: "strict" | "partial" | "disabled" | "strict-path-only"; setAttribute(name: string, value: string): void; removeAttribute(name: string): void; }; /** * Gestionnaire d'écoute des modifications sur l'url courante. * On peut s'y abonner via la methode statique onChange() * Utilisé par sonic-bouton pour lm'affichage de l'état actif et par le router. */ export default class LocationHandler { static listeners: Array = []; static listening = false; static prevURL: string = document.location?.href.replace( document.location.origin, "" ); static listen() { if (!LocationHandler.listening) { return; } const newURL = document.location?.href.replace( document.location.origin, "" ); if (LocationHandler.prevURL && LocationHandler.prevURL != newURL) { LocationHandler.prevURL = newURL; LocationHandler.listeners.forEach((listener) => { listener.location = newURL; }); } window.requestAnimationFrame(LocationHandler.listen); } /** * Arrête l'écoute des changements de location pour le listener fournit */ static offChange(listener: Listener) { const idx = LocationHandler.listeners.indexOf(listener); if (idx == -1) return; LocationHandler.listeners.splice(idx, 1); if (LocationHandler.listeners.length == 0) LocationHandler.listening = false; } /** * Ecoute les changements de location et l'assigne à la propriété location de chaque listener */ static onChange(listener: Listener) { if (!LocationHandler.listening) { LocationHandler.listening = true; LocationHandler.listen(); } LocationHandler.listeners.push(listener); listener.location = this.prevURL; } /** * * @param component HTMLElement * Recupère la proprité to ou href de l'élément et lance la navigation * Si l'attribut pushState est présent la naviguation se fait via un pushState * Si l'attribut replaceState est présent la naviguation se fait via un replaceState * Voir link et button pour les exemples d'implémentation **/ static changeFromComponent(component: ActivableLink) { const goBack = component.goBack; const referrer = document.referrer; if (goBack !== null && goBack !== undefined) { const origin = document.location.origin; const urlDest = (goBack || origin).toString(); const isHTTP = referrer.indexOf("http") == 0; const isNotSameOrigin = isHTTP ? new URL(referrer).origin != origin : false; const isReferrerEmpty = referrer == ""; const isFirstPage = history.length < 3; // imparfait mais variabsle selon les situations const isFallbackNoReferer = isReferrerEmpty && isFirstPage; // fallback pour ff qui a parfois un referer nulle ex : drupal 9 const isNotSameURL = urlDest != document.location.href; const goToURLDest = (isNotSameOrigin && isNotSameURL) || isFallbackNoReferer; if (goToURLDest) { const state = history.state || {}; state.concorde = state.concorde || {}; state.concorde.hasDoneHistoryBack = true; history.pushState(state, document.title); history.back(); document.location.replace(urlDest); } else { history.back(); } return; } let to: string = component.getAttribute("to") || ""; if (!to) to = component.href?.toString() || ""; if (!to) return; if (to.indexOf("#") == 0) { document.location.hash = to.substring(1); return; } const url = new URL(to, document.location.href); const split = url.pathname.split("/"); const newPathName = []; let prevSp = ""; for (const sp of split) { if (sp != prevSp) newPathName.push(sp); prevSp = sp; } to = "/" + newPathName.join("/") + url.search + (url.hash ? +url.hash : ""); if (component.hasAttribute("pushState")) { history.pushState(null, "", to); } else if (component.hasAttribute("replaceState")) { history.replaceState(null, "", to); } else document.location.href = to; } /** * * @param component ActivableLink * Ajoute l'attribut "active" à l'élément si l'url correspond à la location en donction du mode d'activation */ static updateComponentActiveState(component: ActivableLink) { if (component.autoActive == "disabled") { return; } const href = component.href?.toString() || ""; if (component.href && href.indexOf("http") != 0) { const url1 = new URL(href, document.location.href); const url2 = new URL(component.location || "", document.location.origin); let isActive = false; if (component.autoActive == "strict") { isActive = url1.pathname == url2.pathname && url1.hash == url2.hash && url1.search == url2.search; } else if (component.autoActive == "strict-path-only") { isActive = url1.pathname == url2.pathname && url1.hash == url2.hash; } else { isActive = url2.href.indexOf(url1.href) == 0; } if (isActive) component.setAttribute("active", "true"); else component.removeAttribute("active"); } } }