import { css, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; interface NavLink { readonly href: string; readonly label: string; } const LINKS: readonly NavLink[] = [ { href: '/', label: 'Home' }, { href: '/about', label: 'About' }, ]; /** * Site navigation. * * The router intercepts these clicks, so these are ordinary anchors. Keeping * them as real `` elements means middle-click, ctrl-click, "open in * new tab" and screen-reader link navigation all keep working. */ @customElement('app-nav') export class AppNav extends LitElement { static override styles = css` :host { display: block; margin-bottom: 2rem; } nav { display: flex; gap: 0.25rem; border-bottom: 1px solid var(--app-color-border, #e3e3e8); padding-bottom: 0.75rem; } a { padding: 0.35rem 0.75rem; border-radius: var(--app-radius, 0.5rem); color: var(--app-color-muted, #63636b); text-decoration: none; font-size: 0.95rem; } a:hover { color: var(--app-color-text, #18181b); background: color-mix(in srgb, currentColor 8%, transparent); } a:focus-visible { outline: 2px solid var(--app-color-accent, #3b5bdb); outline-offset: 2px; } /* aria-current is the accessible source of truth, so style from it. */ a[aria-current='page'] { color: var(--app-color-accent, #3b5bdb); background: color-mix(in srgb, var(--app-color-accent, #3b5bdb) 12%, transparent); font-weight: 600; } `; @state() private currentPath = window.location.pathname; override connectedCallback(): void { super.connectedCallback(); // The router pushes state on link clicks, which does not fire popstate, // so listen for both that and real back/forward navigation. window.addEventListener('popstate', this.#syncPath); window.addEventListener('click', this.#syncPathSoon); } override disconnectedCallback(): void { super.disconnectedCallback(); window.removeEventListener('popstate', this.#syncPath); window.removeEventListener('click', this.#syncPathSoon); } override render() { return html` `; } #syncPath = (): void => { this.currentPath = window.location.pathname; }; // Runs after the router's own click handler has pushed the new URL. #syncPathSoon = (): void => { queueMicrotask(this.#syncPath); }; } declare global { interface HTMLElementTagNameMap { 'app-nav': AppNav; } }