// Tiny hash router - no dependency, matches the project's "no build step" // bias. Routes are registered with route(pattern, handler); handler gets // (root, params) where params come from ":name" segments in the pattern. const routes = []; let currentRoot = null; let listenerAttached = false; export function route(pattern, handler) { const keys = []; const regexSource = pattern .split('/') .map((segment) => { if (segment.startsWith(':')) { keys.push(segment.slice(1)); return '([^/]+)'; } return segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }) .join('/'); routes.push({ regex: new RegExp('^' + regexSource + '$'), keys, handler }); } export function navigate(path) { location.hash = '#' + path; } function render() { if (!currentRoot) return; const [path, query] = (location.hash.slice(1) || '/').split('?'); const params = Object.fromEntries(new URLSearchParams(query || '')); for (const r of routes) { const match = path.match(r.regex); if (match) { r.keys.forEach((key, i) => { params[key] = decodeURIComponent(match[i + 1]); }); currentRoot.innerHTML = ''; r.handler(currentRoot, params); return; } } currentRoot.innerHTML = '

Page introuvable.

'; } /** * (Re)targets the router at `root` and renders the current route into it. * Safe to call more than once (e.g. after a logout/login cycle re-renders * the app shell) - the hashchange listener is only ever attached once, so * repeated calls don't pile up duplicate listeners. */ export function start(root) { currentRoot = root; if (!listenerAttached) { window.addEventListener('hashchange', render); listenerAttached = true; } render(); }