import { html } from "./html"; export interface Route { path: string; component: () => Promise | string; title?: string; showInNavigation?: boolean; } export class Router { private currentRoute: Route | null = null; private loadedRoutes: Set = new Set(); isLoading: boolean = false; routes: Route[] = []; constructor(private container: HTMLElement) { window.addEventListener("popstate", () => this.handleRoute()); document.addEventListener("click", (e) => this.handleClick(e)); } private async render() { if (!this.currentRoute) return; this.isLoading = true; const preloaded = this.loadedRoutes.has(this.currentRoute.path); if (!preloaded) { this.container.style.opacity = "0.5"; this.container.style.pointerEvents = "none"; } try { const result = this.currentRoute.component(); const content = result instanceof Promise ? await result : result; this.loadedRoutes.add(this.currentRoute.path); if (!preloaded) await new Promise((resolve) => setTimeout(resolve, 150)); this.container.innerHTML = content; if (!preloaded) { this.container.style.opacity = "1"; this.container.style.pointerEvents = "auto"; } } catch (error) { console.error("failed to load page:", error); this.container.innerHTML = this.getErrorContent(); this.container.style.opacity = "1"; this.container.style.pointerEvents = "auto"; } finally { this.isLoading = false; } } addRoute(route: Route) { this.routes.push(route); return this; } private handleClick(e: Event) { const target = e.target as HTMLElement; const link = target.closest("a[data-link]"); if (link) { e.preventDefault(); const href = link.getAttribute("href"); if (href) { this.navigate(href); } } } private handleRoute() { const path = window.location.pathname; const route = this.routes.find((r) => r.path === path) || this.routes.find((r) => r.path === "*"); if (route) { this.currentRoute = route; this.render(); if (route.title) { document.title = `${route.title} - willow!`; } this.updateNavigation(path); } } updateNavigation = (currentPath: string) => { const navLinks = document.querySelectorAll( ".navigation-bar__nav a[data-link]", ); navLinks.forEach((link) => { const href = link.getAttribute("href"); if (href === currentPath) { link.classList.add("active"); } else { link.classList.remove("active"); } }); }; navigate(path: string) { window.history.pushState({}, "", path); this.handleRoute(); } private getErrorContent() { return html`

error

failed to load page. please try again.

go home
`; } start() { this.handleRoute(); } }