diff --git a/README.md b/README.md index f0666ea..ef5383a 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ npm install texivia-router | Svelte | – | – | ✓ | – | **✓** | | React | ✓ | – | ✓ | ✓ | **✓** | | Vue | – | ✓ | ✓ | – | **✓** | +| Lit | – | – | – | – | **✓** | | Angular | – | – | ✓ | – | **✓** | | Vanilla | – | – | ✓ | – | **✓** | @@ -131,6 +132,102 @@ Texivia doesn't need a nested routing concept. Use your framework's composition Layouts are components, not router config. This keeps the router simple and your layouts flexible. +## Lit 3 + +Type the router with a render-function `View` so each route's `view` is a `(params) => TemplateResult`. A single `` LitElement owns the navigation listener and re-renders by swapping the current view: + +```typescript +// src/router.ts +import { Router } from 'texivia-router'; +import type { TemplateResult } from 'lit'; +import { renderLanding } from './pages/landing'; +import { renderUserProfile } from './pages/user-profile'; +import { renderNotFound } from './pages/not-found'; + +export type View = (params: Record) => TemplateResult; + +export const router = new Router([ + { path: '/', view: renderLanding }, + { path: '/users/{id:\\d+}', view: renderUserProfile }, + { path: '*', view: renderNotFound }, +]); +``` + +```typescript +// src/app-shell.ts +import { LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { router, type View } from './router'; +import { renderLanding } from './pages/landing'; + +@customElement('app-shell') +export class AppShell extends LitElement { + // Light DOM keeps the clicked as event.target so the router's + // document-level closest('a') resolves it; Shadow DOM would retarget + // target to the host. Also lets global CSS apply. + createRenderRoot() { return this; } + + @state() private view: View = renderLanding; + @state() private params: Record = { + locale: navigator.language.split('-')[0], + }; + + private onNavigate = (e: Event) => { + const detail = (e as CustomEvent).detail; + if (detail?.view) this.view = detail.view; + this.params = detail?.params ?? {}; + }; + + connectedCallback() { + super.connectedCallback(); + // Attach listener BEFORE start() — routes without a handler dispatch + // synchronously, so a late listener misses the initial event on deep links. + document.addEventListener('texivia', this.onNavigate); + router.start(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener('texivia', this.onNavigate); + router.stop(); + } + + render() { + return this.view(this.params); + } +} +``` + +Each page exports a LitElement and a render fn that the router map references: + +```typescript +// src/pages/landing.ts +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('page-landing') +export class PageLanding extends LitElement { + createRenderRoot() { return this; } + @property() locale = 'en'; + + render() { + return html`

Welcome

Locale: ${this.locale}

`; + } +} + +export const renderLanding = (p: Record) => + html``; +``` + +For programmatic navigation, import the router from anywhere: + +```typescript +import { router } from './router'; +router.navigate('/dashboard'); +``` + +Plain `
` tags inside Lit templates are intercepted automatically — no `` wrapper needed, **provided the host renders in Light DOM** (`createRenderRoot() { return this; }`). Shadow DOM retargets click events to the host, so `` inside a shadow root is invisible to the document-level click handler. See [`examples/lit/micro`](examples/lit/micro) for a complete example with layouts, parameterized routes, and a 404 fallback. + ## Vue 3 ```typescript diff --git a/examples/lit/micro/.gitignore b/examples/lit/micro/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/examples/lit/micro/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/examples/lit/micro/README.md b/examples/lit/micro/README.md new file mode 100644 index 0000000..e3fb140 --- /dev/null +++ b/examples/lit/micro/README.md @@ -0,0 +1,87 @@ +# Micro — Lit + Texivia Router + +Minimal Lit 3 example using `texivia-router` for client-side routing. Demonstrates locale-prefixed routes, parameterized paths, redirects, and a catch-all 404 page. + +## Routes + +| Path | View | +| ------------------------------ | ------------------------------------------------------- | +| `/` | Redirects to `/{locale}/` based on `navigator.language` | +| `/{locale}/` | Landing | +| `/{locale}/login` | Login | +| `/{locale}/users/{id}/profile` | UserProfile | +| `/{locale}/about` | About | +| `/{locale}/imprint` | Imprint | +| `/{locale}/contact` | Contact | +| `*` | NotFound | + +## How It Works + +`Router` is parameterized over a render function — `View = (params) => TemplateResult`. Each page exports a LitElement plus a `renderXxx` function that the route map references: + +```ts +// src/router.ts +export type View = (params: Record) => TemplateResult; + +export const router = new Router([ + { path: '/', handler: () => `/${navigator.language.split('-')[0]}/` }, + { path: '/{locale}/', view: renderLanding }, + { path: '/{locale}/users/{id:\\d+}/profile', view: renderUserProfile }, + // ... + { path: '*', view: renderNotFound }, +]); +``` + +`` is the root LitElement. It listens for the `texivia` event, swaps the current view, and re-renders: + +```ts +// src/app-shell.ts +@customElement('app-shell') +export class AppShell extends LitElement { + createRenderRoot() { + return this; + } // light DOM + + @state() private view: View = renderLanding; + @state() private params: Record = { + locale: navigator.language.split('-')[0], + }; + + private onNavigate = (e: Event) => { + const detail = (e as CustomEvent).detail; + if (detail?.view) this.view = detail.view; + this.params = detail?.params ?? {}; + }; + + connectedCallback() { + super.connectedCallback(); + document.addEventListener('texivia', this.onNavigate); + router.start(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener('texivia', this.onNavigate); + router.stop(); + } + + render() { + return this.view(this.params); + } +} +``` + +## Lit-specific notes + +**Light DOM (`createRenderRoot() { return this }`).** Composed click events bubble out of shadow DOM fine — but `event.target` is retargeted to the shadow host. Texivia's interceptor calls `event.target.closest('a')`, which then walks up from `` instead of from the actual anchor and finds nothing. Light DOM keeps the clicked `` as the literal `event.target`, so `closest('a')` resolves it. Light DOM also lets global `app.css` apply without `static styles`. Shadow DOM is workable only if you fork the router to read `event.composedPath()`. + +**Listener before `start()`.** Routes without a handler complete `_navigate` synchronously inside `router.start()` and dispatch the `texivia` event before control returns. Attach the listener first, then call `router.start()`, otherwise the initial event is missed on direct deep-link loads. + +**Layouts as plain template functions.** `mainLayout(locale, body)` is a function returning a `TemplateResult`, not a custom element. Layouts have no state or behavior, so wrapping them in a `LitElement` only adds ceremony — and forcing children through a custom element means dealing with `` (which doesn't project in light DOM). A function is shorter, has no render root, and composes naturally inside `html\`...\``. + +## Setup + +```sh +npm install +npm run dev +``` diff --git a/examples/lit/micro/index.html b/examples/lit/micro/index.html new file mode 100644 index 0000000..a17f1db --- /dev/null +++ b/examples/lit/micro/index.html @@ -0,0 +1,12 @@ + + + + + + Micro Texivia Lit Example + + +
+ + + diff --git a/examples/lit/micro/package-lock.json b/examples/lit/micro/package-lock.json new file mode 100644 index 0000000..5a0f5c3 --- /dev/null +++ b/examples/lit/micro/package-lock.json @@ -0,0 +1,1233 @@ +{ + "name": "micro", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "micro", + "version": "0.0.0", + "dependencies": { + "lit": "^3.3.2", + "texivia-router": "file:../../.." + }, + "devDependencies": { + "typescript": "~5.7.2", + "vite": "^6.3.1" + } + }, + "../../..": { + "name": "texivia-router", + "version": "1.0.0", + "license": "Apache-2.0", + "devDependencies": { + "@rollup/plugin-terser": "^0.4.4", + "@types/node": "^22.7.4", + "eslint": "^8.57.0", + "jsdom": "^26.1.0", + "terser": "^5.39.0", + "typescript": "^5.6.3", + "vite": "^5.4.8", + "vitest": "^3.1.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.5.1.tgz", + "integrity": "sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", + "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", + "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", + "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", + "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", + "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", + "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", + "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", + "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", + "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", + "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", + "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", + "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", + "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", + "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", + "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", + "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", + "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", + "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", + "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", + "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", + "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", + "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", + "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", + "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", + "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lit": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.2.tgz", + "integrity": "sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.2.tgz", + "integrity": "sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.60.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.3.tgz", + "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.3", + "@rollup/rollup-android-arm64": "4.60.3", + "@rollup/rollup-darwin-arm64": "4.60.3", + "@rollup/rollup-darwin-x64": "4.60.3", + "@rollup/rollup-freebsd-arm64": "4.60.3", + "@rollup/rollup-freebsd-x64": "4.60.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", + "@rollup/rollup-linux-arm-musleabihf": "4.60.3", + "@rollup/rollup-linux-arm64-gnu": "4.60.3", + "@rollup/rollup-linux-arm64-musl": "4.60.3", + "@rollup/rollup-linux-loong64-gnu": "4.60.3", + "@rollup/rollup-linux-loong64-musl": "4.60.3", + "@rollup/rollup-linux-ppc64-gnu": "4.60.3", + "@rollup/rollup-linux-ppc64-musl": "4.60.3", + "@rollup/rollup-linux-riscv64-gnu": "4.60.3", + "@rollup/rollup-linux-riscv64-musl": "4.60.3", + "@rollup/rollup-linux-s390x-gnu": "4.60.3", + "@rollup/rollup-linux-x64-gnu": "4.60.3", + "@rollup/rollup-linux-x64-musl": "4.60.3", + "@rollup/rollup-openbsd-x64": "4.60.3", + "@rollup/rollup-openharmony-arm64": "4.60.3", + "@rollup/rollup-win32-arm64-msvc": "4.60.3", + "@rollup/rollup-win32-ia32-msvc": "4.60.3", + "@rollup/rollup-win32-x64-gnu": "4.60.3", + "@rollup/rollup-win32-x64-msvc": "4.60.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/texivia-router": { + "resolved": "../../..", + "link": true + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/examples/lit/micro/package.json b/examples/lit/micro/package.json new file mode 100644 index 0000000..ff3509e --- /dev/null +++ b/examples/lit/micro/package.json @@ -0,0 +1,20 @@ +{ + "name": "micro", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -p tsconfig.app.json && vite build", + "preview": "vite preview", + "type-check": "tsc -p tsconfig.app.json" + }, + "dependencies": { + "lit": "^3.3.2", + "texivia-router": "file:../../.." + }, + "devDependencies": { + "typescript": "~5.7.2", + "vite": "^6.3.1" + } +} diff --git a/examples/lit/micro/public/favicon.ico b/examples/lit/micro/public/favicon.ico new file mode 100644 index 0000000..17c8917 Binary files /dev/null and b/examples/lit/micro/public/favicon.ico differ diff --git a/examples/lit/micro/src/app-shell.ts b/examples/lit/micro/src/app-shell.ts new file mode 100644 index 0000000..c4a928b --- /dev/null +++ b/examples/lit/micro/src/app-shell.ts @@ -0,0 +1,44 @@ +import { LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { router, type View } from './router'; +import { renderLanding } from './pages/landing'; + +@customElement('app-shell') +export class AppShell extends LitElement { + // Light DOM keeps the clicked
as event.target so the router's + // document-level closest('a') resolves it; Shadow DOM would retarget + // target to . Also lets global app.css apply. + createRenderRoot() { + return this; + } + + @state() private view: View = renderLanding; + @state() private params: Record = { + locale: navigator.language.split('-')[0], + }; + + private onNavigate = (event: Event) => { + const detail = (event as CustomEvent).detail; + if (detail?.view) this.view = detail.view; + this.params = detail?.params ?? {}; + }; + + connectedCallback() { + super.connectedCallback(); + // Listener must precede start(): for routes with no handler, _navigate + // dispatches the texivia event synchronously inside start(), so attaching + // afterwards loses the initial event on deep-link loads. + document.addEventListener('texivia', this.onNavigate); + router.start(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + document.removeEventListener('texivia', this.onNavigate); + router.stop(); + } + + render() { + return this.view(this.params); + } +} diff --git a/examples/lit/micro/src/app.css b/examples/lit/micro/src/app.css new file mode 100644 index 0000000..4903a14 --- /dev/null +++ b/examples/lit/micro/src/app.css @@ -0,0 +1,218 @@ +:root { + --background: #FFFFF0; + --code-bg: #fff0d2; + --border-color: #d4c5b9; + --border: 1px solid var(--border-color); + --primary: #590000; + --active: #6b5737; + --secondary: #8b7355; + --text: #2d2a26; + --muted: #666666; + --xs: 0.25rem; + --md: 0.5rem; + --lg: 1rem; + --xl: 2rem; +} + +html { + box-sizing: border-box; + font-size: 100%; + -webkit-text-size-adjust: 100%; +} + +*, *::before, *::after { + box-sizing: inherit; +} + +body { + background-color: var(--background); + color: var(--text); + font-family: 'Courier New', Courier, monospace; + line-height: 1.4; + margin: 0; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#app { + display: flex; + flex-direction: column; + margin: 0 auto; + min-height: 100vh; + width: clamp(360px, 100% - var(--xl), 50rem); +} + +h1, h2, h3 { + color: var(--primary); +} + +h1 { + border-bottom: 2px solid var(--primary); + font-size: 1.5rem; + margin: var(--xl) 0 var(--lg); + text-transform: uppercase; +} + +h2 { + font-size: 1.25rem; + margin: var(--lg) 0 0; +} + +p { + margin: var(--xs) 0 0; +} + +p + p { + margin: var(--md) 0 0; +} + +a { + color: var(--primary); + text-decoration: none; +} + +a:hover { + color: var(--active); + text-decoration: underline; +} + +a:focus-visible { + outline: 2px solid var(--primary); + outline-offset: 2px; + border-radius: 2px; +} + +main { + flex: 1; +} + +main a:visited { + color: var(--secondary); +} + +header { + display: flex; + align-items: center; + padding: var(--md) 0; + border-bottom: 1px solid var(--primary); +} + +header .brand { + font-size: 1.25rem; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +header nav { + align-items: center; + display: flex; + font-size: 1.125rem; + gap: var(--lg); + margin-left: auto; +} + +header a { + font-weight: bold; +} + +footer { + border-top: var(--border); + color: var(--muted); + display: flex; + flex-direction: column; + align-items: center; + font-size: 0.9em; + padding: var(--md) 0; + margin-top: auto; +} + +footer nav { + display: flex; + gap: var(--lg); +} + +@media (min-width: 480px) { + footer { + flex-direction: row; + } + footer nav { + margin-left: auto; + } +} + +label { + display: block; + margin-top: var(--md); + color: var(--muted); + font-size: 0.9em; +} + +input[type="text"], +input[type="password"] { + background: var(--code-bg); + border: var(--border); + color: var(--text); + font-family: inherit; + font-size: 1rem; + padding: var(--md); + width: 100%; + max-width: 20rem; + margin-top: var(--xs); +} + +input:focus { + outline: 2px solid var(--primary); + outline-offset: 1px; +} + +button { + background: var(--primary); + border: none; + color: var(--background); + cursor: pointer; + font-family: inherit; + font-size: 1rem; + font-weight: bold; + margin-top: var(--lg); + padding: var(--md) var(--lg); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +button:hover { + background: var(--active); +} + +select { + background: var(--code-bg); + border: var(--border); + color: var(--text); + font-family: inherit; + font-size: 0.9rem; + padding: var(--xs) var(--md); +} + +.login-form { + max-width: 24rem; + margin: 0 auto; + text-align: center; +} + +.login-form label, +.login-form input, +.login-form button, +.login-form p { + text-align: left; +} + +.login-form input { + max-width: none; + width: 100%; +} + +.login-form button { + display: block; + width: 100%; +} diff --git a/examples/lit/micro/src/components/app-footer.ts b/examples/lit/micro/src/components/app-footer.ts new file mode 100644 index 0000000..e8a89f6 --- /dev/null +++ b/examples/lit/micro/src/components/app-footer.ts @@ -0,0 +1,24 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('app-footer') +export class AppFooter extends LitElement { + createRenderRoot() { + return this; + } + + @property() locale = 'en'; + + render() { + return html` + + `; + } +} diff --git a/examples/lit/micro/src/components/app-header.ts b/examples/lit/micro/src/components/app-header.ts new file mode 100644 index 0000000..ae0d140 --- /dev/null +++ b/examples/lit/micro/src/components/app-header.ts @@ -0,0 +1,24 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; + +@customElement('app-header') +export class AppHeader extends LitElement { + createRenderRoot() { + return this; + } + + @property() locale = 'en'; + + render() { + return html` +
+ Texivia + +
+ `; + } +} diff --git a/examples/lit/micro/src/layouts/main-layout.ts b/examples/lit/micro/src/layouts/main-layout.ts new file mode 100644 index 0000000..df97cd8 --- /dev/null +++ b/examples/lit/micro/src/layouts/main-layout.ts @@ -0,0 +1,9 @@ +import { html, type TemplateResult } from 'lit'; +import '../components/app-header'; +import '../components/app-footer'; + +export const mainLayout = (locale: string, body: TemplateResult) => html` + +
${body}
+ +`; diff --git a/examples/lit/micro/src/main.ts b/examples/lit/micro/src/main.ts new file mode 100644 index 0000000..e5d5e70 --- /dev/null +++ b/examples/lit/micro/src/main.ts @@ -0,0 +1,4 @@ +import './app.css'; +import './app-shell'; + +document.getElementById('app')!.appendChild(document.createElement('app-shell')); diff --git a/examples/lit/micro/src/pages/about.ts b/examples/lit/micro/src/pages/about.ts new file mode 100644 index 0000000..8293465 --- /dev/null +++ b/examples/lit/micro/src/pages/about.ts @@ -0,0 +1,25 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; + +@customElement('page-about') +export class PageAbout extends LitElement { + createRenderRoot() { + return this; + } + + @property() locale = 'en'; + + render() { + return mainLayout( + this.locale, + html` +

About

+

Texivia is a lightweight, framework-agnostic router for single-page applications.

+

Built with TypeScript, it compiles all routes into a single regex for fast O(1) matching.

+ ` + ); + } +} + +export const renderAbout = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/contact.ts b/examples/lit/micro/src/pages/contact.ts new file mode 100644 index 0000000..f8a8a80 --- /dev/null +++ b/examples/lit/micro/src/pages/contact.ts @@ -0,0 +1,25 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; + +@customElement('page-contact') +export class PageContact extends LitElement { + createRenderRoot() { + return this; + } + + @property() locale = 'en'; + + render() { + return mainLayout( + this.locale, + html` +

Contact

+

Have questions or feedback? Reach out to us.

+

Email: example@texivia.dev

+ ` + ); + } +} + +export const renderContact = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/imprint.ts b/examples/lit/micro/src/pages/imprint.ts new file mode 100644 index 0000000..b6f8e71 --- /dev/null +++ b/examples/lit/micro/src/pages/imprint.ts @@ -0,0 +1,27 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; + +@customElement('page-imprint') +export class PageImprint extends LitElement { + createRenderRoot() { + return this; + } + + @property() locale = 'en'; + + render() { + return mainLayout( + this.locale, + html` +

Imprint

+

Responsible

+

Texivia Example App
123 Example Street
12345 Example City

+

Contact

+

Email: example@texivia.dev

+ ` + ); + } +} + +export const renderImprint = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/landing.ts b/examples/lit/micro/src/pages/landing.ts new file mode 100644 index 0000000..fbfa304 --- /dev/null +++ b/examples/lit/micro/src/pages/landing.ts @@ -0,0 +1,28 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; + +@customElement('page-landing') +export class PageLanding extends LitElement { + createRenderRoot() { + return this; + } + + @property() locale = 'en'; + + render() { + return mainLayout( + this.locale, + html` +

Welcome to Texivia

+

Your one-stop solution for all your text processing needs.

+

Locale: ${this.locale}

+

Explore our features and services tailored just for you.

+

Get started by signing up or logging in!

+

Already have an account? Login here

+ ` + ); + } +} + +export const renderLanding = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/login.ts b/examples/lit/micro/src/pages/login.ts new file mode 100644 index 0000000..225b08f --- /dev/null +++ b/examples/lit/micro/src/pages/login.ts @@ -0,0 +1,31 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; + +@customElement('page-login') +export class PageLogin extends LitElement { + createRenderRoot() { + return this; + } + + @property() locale = 'en'; + + render() { + return mainLayout( + this.locale, + html` +

Login

+ + ` + ); + } +} + +export const renderLogin = (p: Record) => html``; diff --git a/examples/lit/micro/src/pages/not-found.ts b/examples/lit/micro/src/pages/not-found.ts new file mode 100644 index 0000000..e642598 --- /dev/null +++ b/examples/lit/micro/src/pages/not-found.ts @@ -0,0 +1,21 @@ +import { LitElement, html } from 'lit'; +import { customElement } from 'lit/decorators.js'; + +@customElement('page-not-found') +export class PageNotFound extends LitElement { + createRenderRoot() { + return this; + } + + render() { + return html` +
+

404 Not Found

+

The page you are looking for does not exist.

+

Return to the home page.

+
+ `; + } +} + +export const renderNotFound = () => html``; diff --git a/examples/lit/micro/src/pages/user-profile.ts b/examples/lit/micro/src/pages/user-profile.ts new file mode 100644 index 0000000..f37bbf3 --- /dev/null +++ b/examples/lit/micro/src/pages/user-profile.ts @@ -0,0 +1,27 @@ +import { LitElement, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { mainLayout } from '../layouts/main-layout'; + +@customElement('page-user-profile') +export class PageUserProfile extends LitElement { + createRenderRoot() { + return this; + } + + @property() locale = 'en'; + @property() id = ''; + + render() { + return mainLayout( + this.locale, + html` +

User Profile

+

Locale: ${this.locale}

+

User ID: ${this.id}

+ ` + ); + } +} + +export const renderUserProfile = (p: Record) => + html``; diff --git a/examples/lit/micro/src/router.ts b/examples/lit/micro/src/router.ts new file mode 100644 index 0000000..c9d33c5 --- /dev/null +++ b/examples/lit/micro/src/router.ts @@ -0,0 +1,22 @@ +import { Router } from 'texivia-router'; +import type { TemplateResult } from 'lit'; +import { renderLanding } from './pages/landing'; +import { renderLogin } from './pages/login'; +import { renderUserProfile } from './pages/user-profile'; +import { renderAbout } from './pages/about'; +import { renderImprint } from './pages/imprint'; +import { renderContact } from './pages/contact'; +import { renderNotFound } from './pages/not-found'; + +export type View = (params: Record) => TemplateResult; + +export const router = new Router([ + { path: '/', handler: () => `/${navigator.language.split('-')[0]}/` }, + { path: '/{locale}/', view: renderLanding }, + { path: '/{locale}/login', view: renderLogin }, + { path: '/{locale}/users/{id:\\d+}/profile', view: renderUserProfile }, + { path: '/{locale}/about', view: renderAbout }, + { path: '/{locale}/imprint', view: renderImprint }, + { path: '/{locale}/contact', view: renderContact }, + { path: '*', view: renderNotFound }, +]); diff --git a/examples/lit/micro/tsconfig.app.json b/examples/lit/micro/tsconfig.app.json new file mode 100644 index 0000000..21e9121 --- /dev/null +++ b/examples/lit/micro/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "useDefineForClassFields": false, + "experimentalDecorators": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "skipLibCheck": true, + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"] +} diff --git a/examples/lit/micro/tsconfig.json b/examples/lit/micro/tsconfig.json new file mode 100644 index 0000000..d32ff68 --- /dev/null +++ b/examples/lit/micro/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] +} diff --git a/examples/lit/micro/tsconfig.node.json b/examples/lit/micro/tsconfig.node.json new file mode 100644 index 0000000..c921d97 --- /dev/null +++ b/examples/lit/micro/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/examples/lit/micro/vite.config.ts b/examples/lit/micro/vite.config.ts new file mode 100644 index 0000000..c049f46 --- /dev/null +++ b/examples/lit/micro/vite.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({}); diff --git a/package.json b/package.json index 1eaa38d..f11b59e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ } }, "scripts": { - "build": "vite build", + "build": "vite build && tsc --emitDeclarationOnly", "dev": "vite", "test": "vitest", "lint": "eslint src --ext .ts", @@ -43,7 +43,9 @@ "single-page-app", "svelte", "vue", - "react" + "react", + "lit", + "web-components" ], "devDependencies": { "@rollup/plugin-terser": "^0.4.4",