/** * WijiBoard – Hash-based SPA Router * web/js/router.js * * Usage: * Router.register('home', homeModule); * Router.register('stepper-test', stepperModule); * Router.init('#view', '#home'); // outlet element id, default route * * Each section module must export: * { mount(containerEl), unmount() } */ const Router = (() => { const routes = new Map(); // hash → module let outlet = null; // DOM element to render into let current = null; // currently active route key let currentMod = null; // currently mounted module // ── Register a route ───────────────────────────────────────── function register(hash, module) { routes.set(hash, module); } // ── Navigate to a route ────────────────────────────────────── async function navigate(hash) { const key = hash.replace(/^#/, ''); if (!routes.has(key)) { console.warn(`[Router] Unknown route: "${key}"`); return; } if (key === current) return; // already there // Unmount old section if (currentMod?.unmount) { try { await currentMod.unmount(); } catch (e) { console.error('[Router] unmount error', e); } } // Clear outlet outlet.innerHTML = ''; // Mount new section const mod = routes.get(key); currentMod = mod; current = key; try { await mod.mount(outlet); } catch (e) { console.error('[Router] mount error', e); outlet.innerHTML = `
Failed to load section: ${e.message}
`; } // Update sidebar active state document.querySelectorAll('.sidebar-nav-item').forEach(el => { el.classList.toggle('active', el.dataset.route === key); }); // Update URL hash (without re-triggering hashchange) if (location.hash.replace('#','') !== key) { history.replaceState(null, '', `#${key}`); } } // ── Init: wire up outlet + default route ───────────────────── function init(outletSelector, defaultHash = '#home') { outlet = document.querySelector(outletSelector); if (!outlet) throw new Error(`[Router] Outlet "${outletSelector}" not found`); // Listen for hash changes window.addEventListener('hashchange', () => { navigate(location.hash || defaultHash); }); // Handle initial load const initial = location.hash && location.hash.length > 1 ? location.hash : defaultHash; // Small defer to let the page fully render first requestAnimationFrame(() => navigate(initial)); } // ── Active route getter ─────────────────────────────────────── function getActive() { return current; } return { register, navigate, init, getActive }; })(); export default Router;