/** * Hoover — router.js * * Hash-based SPA router with reactive state (triggers re-render on * navigation). Link component for client-side navigation. */ import { reactive } from './reactivity.js?v=7'; import { h } from './vdom.js?v=7'; /** * Hash-based router. * * const router = createRouter({ * '/dashboard': () => h('#comp', { component: DashboardPage, key: '/dashboard' }, []), * '/interfaces': () => h('#comp', { component: InterfacesPage, key: '/interfaces' }, []), * '*': () => h('#comp', { component: NotFoundPage, key: '*' }, []), * }); * * Reactive `router.state.path` updates trigger re-renders automatically. */ export function createRouter(routes) { const initialPath = location.hash.slice(1) || '/dashboard'; if (!location.hash) location.hash = initialPath; const state = reactive({ path: initialPath }); window.addEventListener('hashchange', () => { state.path = location.hash.slice(1) || '/dashboard'; }); const component = () => { const handler = routes[state.path] || routes['*']; if (!handler) { return h('div', { class: 'card' }, h('div', { class: 'text-muted' }, `404 — Not found: ${state.path}`)); } try { return handler(); } catch (e) { return h('div', { class: 'card' }, h('div', { class: 'text-muted' }, `Error: ${e.message || String(e)}`)); } }; return { state, navigate: (p) => { location.hash = p; }, component }; } /** * Client-side navigation link component. * Sets `location.hash` without full page navigation. */ export function Link(props) { const { path, class: cls, children, ...rest } = props || {}; return h('a', { href: '#' + path, class: cls || '', 'on:click': (e) => { e.preventDefault(); location.hash = path; }, ...rest, }, children || []); }