92 lines
3.0 KiB
JavaScript
92 lines
3.0 KiB
JavaScript
/**
|
||
* 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 = `<p style="color:var(--accent-red);padding:24px">
|
||
Failed to load section: ${e.message}</p>`;
|
||
}
|
||
|
||
// 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;
|