router.ts (1295B)
1 type RouteHandler = (params: Record<string, string>) => void; 2 3 interface Route { 4 pattern: RegExp; 5 handler: RouteHandler; 6 keys: string[]; 7 } 8 9 const routes: Route[] = []; 10 11 export function route(path: string, handler: RouteHandler) { 12 const keys: string[] = []; 13 const pattern = new RegExp( 14 '^' + path.replace(/:(\w+)/g, (_, k) => { keys.push(k); return '([^/]+)'; }) + '$' 15 ); 16 routes.push({ pattern, handler, keys }); 17 } 18 19 export function navigate(hash: string) { 20 window.location.hash = hash; 21 } 22 23 function resolve() { 24 const hash = window.location.hash.slice(1) || '/'; 25 for (const r of routes) { 26 const m = hash.match(r.pattern); 27 if (m) { 28 const params: Record<string, string> = {}; 29 r.keys.forEach((k, i) => params[k] = decodeURIComponent(m[i + 1])); 30 r.handler(params); 31 updateNav(hash); 32 return; 33 } 34 } 35 // Default to dashboard 36 routes[0]?.handler({}); 37 updateNav('/'); 38 } 39 40 function updateNav(hash: string) { 41 document.querySelectorAll('#nav a').forEach(a => { 42 const href = (a as HTMLAnchorElement).getAttribute('href')?.slice(1) || '/'; 43 a.classList.toggle('active', hash === href || (href !== '/' && hash.startsWith(href))); 44 }); 45 } 46 47 export function startRouter() { 48 window.addEventListener('hashchange', resolve); 49 resolve(); 50 }