import { css, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { tailwind } from '../styles/tailwind.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 = [ tailwind, css` :host { display: block; } a { /* * Tailwind's preflight strips the default underline, but preflight is * document-level and is not adopted into shadow roots, so links reset * themselves here instead. */ text-decoration: none; color: inherit; } `, ]; @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; } }