@f-ewald/components 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +15 -2
  2. package/custom-elements.json +1019 -93
  3. package/dist/action-bar.d.ts +24 -0
  4. package/dist/action-bar.d.ts.map +1 -0
  5. package/dist/action-bar.js +67 -0
  6. package/dist/action-bar.js.map +1 -0
  7. package/dist/app-shell.d.ts +86 -0
  8. package/dist/app-shell.d.ts.map +1 -0
  9. package/dist/app-shell.js +460 -0
  10. package/dist/app-shell.js.map +1 -0
  11. package/dist/app-sidebar.d.ts +44 -0
  12. package/dist/app-sidebar.d.ts.map +1 -0
  13. package/dist/app-sidebar.js +211 -0
  14. package/dist/app-sidebar.js.map +1 -0
  15. package/dist/form-actions.d.ts +27 -0
  16. package/dist/form-actions.d.ts.map +1 -0
  17. package/dist/form-actions.js +65 -0
  18. package/dist/form-actions.js.map +1 -0
  19. package/dist/index.d.ts +8 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +8 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp-server.js +82 -5
  24. package/dist/mcp-server.js.map +1 -1
  25. package/dist/page-header.d.ts +27 -0
  26. package/dist/page-header.d.ts.map +1 -0
  27. package/dist/page-header.js +106 -0
  28. package/dist/page-header.js.map +1 -0
  29. package/dist/pagination-nav.d.ts +34 -0
  30. package/dist/pagination-nav.d.ts.map +1 -0
  31. package/dist/pagination-nav.js +144 -0
  32. package/dist/pagination-nav.js.map +1 -0
  33. package/dist/timeline-container.d.ts +22 -0
  34. package/dist/timeline-container.d.ts.map +1 -0
  35. package/dist/timeline-container.js +42 -0
  36. package/dist/timeline-container.js.map +1 -0
  37. package/dist/timeline-entry.d.ts +31 -0
  38. package/dist/timeline-entry.d.ts.map +1 -0
  39. package/dist/timeline-entry.js +170 -0
  40. package/dist/timeline-entry.js.map +1 -0
  41. package/docs/action-bar.md +41 -0
  42. package/docs/app-shell.md +85 -0
  43. package/docs/app-sidebar.md +69 -0
  44. package/docs/design-language.md +74 -1
  45. package/docs/form-actions.md +43 -0
  46. package/docs/layouts/detail-only.md +52 -0
  47. package/docs/layouts/form-page.md +52 -0
  48. package/docs/layouts/list-detail.md +52 -0
  49. package/docs/layouts/list-only.md +50 -0
  50. package/docs/layouts/record-detail.md +88 -0
  51. package/docs/mcp-evaluation.md +21 -0
  52. package/docs/page-header.md +47 -0
  53. package/docs/pagination-nav.md +54 -0
  54. package/docs/timeline-container.md +44 -0
  55. package/docs/timeline-entry.md +52 -0
  56. package/llms.txt +229 -0
  57. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,2EAA2E;AAC3E,sEAAsE;AACtE,kEAAkE;AAClE,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AAQ/C,gFAAgF;AAChF,KAAK,UAAU,kBAAkB;IAC/B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5E,OAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAyB,CAAC,OAAO,CAAC;AAC3D,CAAC;AAED,gFAAgF;AAChF,KAAK,UAAU,cAAc;IAC3B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,sBAAsB,CAAC,EAAE,MAAM,CAAC,CAAC;IACpF,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAE/B,CAAC;IACF,MAAM,UAAU,GAAuB,EAAE,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC,IAAI,CAAC,aAAa;gBAAE,SAAS;YAClC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;YACtF,UAAU,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,oBAAoB;IAC1B,OAAO,EAAE,MAAM,kBAAkB,EAAE;CACpC,CAAC,CAAC;AAEH,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;IACE,KAAK,EAAE,iBAAiB;IACxB,WAAW,EACT,uEAAuE;QACvE,wEAAwE;QACxE,uEAAuE;QACvE,eAAe;CAClB,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;CACnF,CAAC,CACH,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;IACE,KAAK,EAAE,oBAAoB;IAC3B,WAAW,EACT,sEAAsE;QACtE,kEAAkE;QAClE,mEAAmE;QACnE,2BAA2B;IAC7B,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC,EAAE;CACnF,EACD,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;IAChB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;QACrE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,SAAS,GAAG,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxE,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,GAAG,kBAAkB,SAAS,EAAE,EAAE,CAAC;YAC7F,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;AACH,CAAC,CACF,CAAC;AAEF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\n/**\n * Stdio MCP server exposing the `@f-ewald/components` catalog to AI coding\n * assistants (Claude Code, etc.) working in a consuming project.\n *\n * Two tools, backed by the same `custom-elements.json` and `docs/*.md` this\n * package already generates and ships via `npm run docs`:\n * - `list_components`: every tag name + one-line description.\n * - `get_component_docs`: the full generated Markdown doc for one tag.\n *\n * No new data source — this is just a different transport over data that\n * already exists. See `docs/mcp-evaluation.md` for the design rationale.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport path from \"node:path\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n// This file compiles to dist/mcp-server.js; custom-elements.json and docs/\n// ship alongside dist/ at the package root, whether run from the repo\n// checkout or from an installed node_modules/@f-ewald/components.\nconst packageRoot = path.join(__dirname, \"..\");\nconst docsDir = path.join(packageRoot, \"docs\");\n\n/** One row of the `list_components` result. */\ninterface ComponentSummary {\n tag: string;\n summary: string;\n}\n\n/** Reads the package's own version out of package.json, for server metadata. */\nasync function readPackageVersion(): Promise<string> {\n const text = await readFile(path.join(packageRoot, \"package.json\"), \"utf8\");\n return (JSON.parse(text) as { version: string }).version;\n}\n\n/** Extracts {tag, one-line summary} for every custom element, sorted by tag. */\nasync function listComponents(): Promise<ComponentSummary[]> {\n const text = await readFile(path.join(packageRoot, \"custom-elements.json\"), \"utf8\");\n const manifest = JSON.parse(text) as {\n modules: { declarations?: { customElement?: boolean; tagName: string; description?: string }[] }[];\n };\n const components: ComponentSummary[] = [];\n for (const mod of manifest.modules) {\n for (const decl of mod.declarations ?? []) {\n if (!decl.customElement) continue;\n const summary = (decl.description ?? \"\").split(\"\\n\\n\")[0]!.replace(/\\n/g, \" \").trim();\n components.push({ tag: decl.tagName, summary });\n }\n }\n components.sort((a, b) => a.tag.localeCompare(b.tag));\n return components;\n}\n\nconst server = new McpServer({\n name: \"f-ewald-components\",\n version: await readPackageVersion(),\n});\n\nserver.registerTool(\n \"list_components\",\n {\n title: \"List components\",\n description:\n \"Lists every web component tag in @f-ewald/components with a one-line \" +\n \"description. Call get_component_docs(tag) for the full usage example, \" +\n \"attributes/properties, events, slots, and CSS custom properties of a \" +\n \"specific tag.\",\n },\n async () => ({\n content: [{ type: \"text\", text: JSON.stringify(await listComponents(), null, 2) }],\n }),\n);\n\nserver.registerTool(\n \"get_component_docs\",\n {\n title: \"Get component docs\",\n description:\n \"Returns the full generated Markdown documentation (install snippet, \" +\n \"usage example, attributes/properties, events, slots, CSS custom \" +\n \"properties) for one @f-ewald/components tag. Use list_components \" +\n \"first to find valid tags.\",\n inputSchema: { tag: z.string().describe('Component tag name, e.g. \"ui-button\".') },\n },\n async ({ tag }) => {\n try {\n const text = await readFile(path.join(docsDir, `${tag}.md`), \"utf8\");\n return { content: [{ type: \"text\", text }] };\n } catch {\n const validTags = (await listComponents()).map((c) => c.tag).join(\", \");\n return {\n content: [{ type: \"text\", text: `No docs found for tag \"${tag}\". Valid tags: ${validTags}` }],\n isError: true,\n };\n }\n },\n);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n"]}
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * Stdio MCP server exposing the `@f-ewald/components` catalog to AI coding\n * assistants (Claude Code, etc.) working in a consuming project.\n *\n * Tools, backed by the same `custom-elements.json` and `docs/*.md` this\n * package already generates and ships via `npm run docs`, plus the authored\n * `docs/layouts/*.md` recipes:\n * - `list_components`: every tag name + one-line description.\n * - `get_component_docs`: the full generated Markdown doc for one tag.\n * - `list_layouts`: every dashboard page template + one-line summary.\n * - `get_layout`: the full recipe for one page template.\n *\n * No new data source — this is just a different transport over docs that\n * already exist. See `docs/mcp-evaluation.md` for the design rationale.\n */\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport path from \"node:path\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { z } from \"zod\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n// This file compiles to dist/mcp-server.js; custom-elements.json and docs/\n// ship alongside dist/ at the package root, whether run from the repo\n// checkout or from an installed node_modules/@f-ewald/components.\nconst packageRoot = path.join(__dirname, \"..\");\nconst docsDir = path.join(packageRoot, \"docs\");\nconst layoutsDir = path.join(docsDir, \"layouts\");\n\n/** One row of the `list_components` result. */\ninterface ComponentSummary {\n tag: string;\n summary: string;\n}\n\n/** One row of the `list_layouts` result. */\ninterface LayoutSummary {\n name: string;\n title: string;\n summary: string;\n}\n\n/** Reads the package's own version out of package.json, for server metadata. */\nasync function readPackageVersion(): Promise<string> {\n const text = await readFile(path.join(packageRoot, \"package.json\"), \"utf8\");\n return (JSON.parse(text) as { version: string }).version;\n}\n\n/** Extracts {tag, one-line summary} for every custom element, sorted by tag. */\nasync function listComponents(): Promise<ComponentSummary[]> {\n const text = await readFile(path.join(packageRoot, \"custom-elements.json\"), \"utf8\");\n const manifest = JSON.parse(text) as {\n modules: { declarations?: { customElement?: boolean; tagName: string; description?: string }[] }[];\n };\n const components: ComponentSummary[] = [];\n for (const mod of manifest.modules) {\n for (const decl of mod.declarations ?? []) {\n if (!decl.customElement) continue;\n const summary = (decl.description ?? \"\").split(\"\\n\\n\")[0]!.replace(/\\n/g, \" \").trim();\n components.push({ tag: decl.tagName, summary });\n }\n }\n components.sort((a, b) => a.tag.localeCompare(b.tag));\n return components;\n}\n\n/**\n * Extracts {name, title, one-line summary} for every `docs/layouts/*.md`\n * recipe, sorted by name. `name` is the filename without `.md`; `summary` is\n * the recipe's first prose paragraph.\n */\nasync function listLayouts(): Promise<LayoutSummary[]> {\n let files: string[];\n try {\n files = await readdir(layoutsDir);\n } catch {\n return [];\n }\n const layouts: LayoutSummary[] = [];\n for (const file of files) {\n if (!file.endsWith(\".md\")) continue;\n const name = file.slice(0, -3);\n const lines = (await readFile(path.join(layoutsDir, file), \"utf8\")).split(\"\\n\");\n const title = lines.find((line) => line.startsWith(\"# \"))?.slice(2).trim() ?? name;\n let seenTitle = false;\n let summary = \"\";\n for (const line of lines) {\n const text = line.trim();\n if (!seenTitle) {\n if (text.startsWith(\"# \")) seenTitle = true;\n continue;\n }\n if (text === \"\" || text.startsWith(\"#\")) continue;\n summary = text;\n break;\n }\n layouts.push({ name, title, summary });\n }\n layouts.sort((a, b) => a.name.localeCompare(b.name));\n return layouts;\n}\n\nconst server = new McpServer({\n name: \"f-ewald-components\",\n version: await readPackageVersion(),\n});\n\nserver.registerTool(\n \"list_components\",\n {\n title: \"List components\",\n description:\n \"Lists every web component tag in @f-ewald/components with a one-line \" +\n \"description. Call get_component_docs(tag) for the full usage example, \" +\n \"attributes/properties, events, slots, and CSS custom properties of a \" +\n \"specific tag.\",\n },\n async () => ({\n content: [{ type: \"text\", text: JSON.stringify(await listComponents(), null, 2) }],\n }),\n);\n\nserver.registerTool(\n \"get_component_docs\",\n {\n title: \"Get component docs\",\n description:\n \"Returns the full generated Markdown documentation (install snippet, \" +\n \"usage example, attributes/properties, events, slots, CSS custom \" +\n \"properties) for one @f-ewald/components tag. Use list_components \" +\n \"first to find valid tags.\",\n inputSchema: { tag: z.string().describe('Component tag name, e.g. \"ui-button\".') },\n },\n async ({ tag }) => {\n try {\n const text = await readFile(path.join(docsDir, `${tag}.md`), \"utf8\");\n return { content: [{ type: \"text\", text }] };\n } catch {\n const validTags = (await listComponents()).map((c) => c.tag).join(\", \");\n return {\n content: [{ type: \"text\", text: `No docs found for tag \"${tag}\". Valid tags: ${validTags}` }],\n isError: true,\n };\n }\n },\n);\n\nserver.registerTool(\n \"list_layouts\",\n {\n title: \"List layouts\",\n description:\n \"Lists the dashboard page templates (layout recipes) in \" +\n \"@f-ewald/components — list-only, list+detail, detail-only, and form — \" +\n \"with a one-line summary each. Call get_layout(name) for the full recipe \" +\n \"(which components fill which app-shell slots, example markup, and notes). \" +\n \"Use this to learn the preferred internal-dashboard layout before \" +\n \"composing app-shell, app-sidebar, action-bar, pagination-nav, \" +\n \"form-actions, and page-header.\",\n },\n async () => ({\n content: [{ type: \"text\", text: JSON.stringify(await listLayouts(), null, 2) }],\n }),\n);\n\nserver.registerTool(\n \"get_layout\",\n {\n title: \"Get layout recipe\",\n description:\n \"Returns the full Markdown recipe for one dashboard page template: which \" +\n \"components go in which app-shell slots, copy-paste markup, and layout \" +\n \"notes. Use list_layouts first to find valid names.\",\n inputSchema: { name: z.string().describe('Layout name, e.g. \"list-detail\".') },\n },\n async ({ name }) => {\n if (/^[a-z0-9-]+$/.test(name)) {\n try {\n const text = await readFile(path.join(layoutsDir, `${name}.md`), \"utf8\");\n return { content: [{ type: \"text\", text }] };\n } catch {\n // Fall through to the not-found response with valid names.\n }\n }\n const validNames = (await listLayouts()).map((layout) => layout.name).join(\", \");\n return {\n content: [{ type: \"text\", text: `No layout named \"${name}\". Valid layouts: ${validNames}` }],\n isError: true,\n };\n },\n);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n"]}
@@ -0,0 +1,27 @@
1
+ import { LitElement } from "lit";
2
+ /**
3
+ * Page title block for the top of a dashboard view: an optional breadcrumb
4
+ * trail, the page heading, and a right-aligned cluster of page-level actions.
5
+ * It only lays these out — the breadcrumb links and action buttons are entirely
6
+ * the consumer's, so it stays framework- and router-agnostic.
7
+ *
8
+ * @element page-header
9
+ * @slot breadcrumb - Optional breadcrumb trail rendered above the heading.
10
+ * @slot actions - Optional right-aligned page actions (e.g. a primary button).
11
+ */
12
+ export declare class PageHeader extends LitElement {
13
+ /** The page heading text. */
14
+ heading: string;
15
+ /** Whether the breadcrumb slot currently has assigned content. */
16
+ private _hasBreadcrumb;
17
+ static styles: import("lit").CSSResult[];
18
+ /** Tracks whether the breadcrumb slot has content so its row can collapse when empty. */
19
+ private _onBreadcrumbSlotChange;
20
+ render(): import("lit-html").TemplateResult<1>;
21
+ }
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ "page-header": PageHeader;
25
+ }
26
+ }
27
+ //# sourceMappingURL=page-header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-header.d.ts","sourceRoot":"","sources":["../src/page-header.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAIrD;;;;;;;;;GASG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,6BAA6B;IACjB,OAAO,SAAM;IAEzB,kEAAkE;IACzD,OAAO,CAAC,cAAc,CAAS;IAExC,OAAgB,MAAM,4BAgDpB;IAEF,yFAAyF;IACzF,OAAO,CAAC,uBAAuB;IAKtB,MAAM,yCAYd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
@@ -0,0 +1,106 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html, nothing } from "lit";
8
+ import { customElement, property, state } from "lit/decorators.js";
9
+ import { tokens } from "./tokens.js";
10
+ /**
11
+ * Page title block for the top of a dashboard view: an optional breadcrumb
12
+ * trail, the page heading, and a right-aligned cluster of page-level actions.
13
+ * It only lays these out — the breadcrumb links and action buttons are entirely
14
+ * the consumer's, so it stays framework- and router-agnostic.
15
+ *
16
+ * @element page-header
17
+ * @slot breadcrumb - Optional breadcrumb trail rendered above the heading.
18
+ * @slot actions - Optional right-aligned page actions (e.g. a primary button).
19
+ */
20
+ let PageHeader = class PageHeader extends LitElement {
21
+ constructor() {
22
+ super(...arguments);
23
+ /** The page heading text. */
24
+ this.heading = "";
25
+ /** Whether the breadcrumb slot currently has assigned content. */
26
+ this._hasBreadcrumb = false;
27
+ }
28
+ static { this.styles = [
29
+ tokens,
30
+ css `
31
+ :host {
32
+ display: block;
33
+ font-family: var(
34
+ --ui-font,
35
+ ui-sans-serif,
36
+ system-ui,
37
+ sans-serif,
38
+ "Apple Color Emoji",
39
+ "Segoe UI Emoji",
40
+ "Segoe UI Symbol",
41
+ "Noto Color Emoji"
42
+ );
43
+ }
44
+ .header {
45
+ display: flex;
46
+ flex-wrap: wrap;
47
+ align-items: flex-end;
48
+ justify-content: space-between;
49
+ gap: 0.5rem;
50
+ }
51
+ .lead {
52
+ min-width: 0;
53
+ }
54
+ .breadcrumb {
55
+ margin-bottom: 0.25rem;
56
+ font-size: var(--ui-font-size-sm, 0.75rem);
57
+ color: var(--ui-text-muted, #64748b);
58
+ }
59
+ .breadcrumb.empty {
60
+ display: none;
61
+ }
62
+ .title {
63
+ margin: 0;
64
+ font-size: var(--ui-font-size-lg, 1rem);
65
+ font-weight: var(--ui-font-weight-semibold, 600);
66
+ line-height: var(--ui-line-height-tight, 1.25);
67
+ color: var(--ui-text, #0f172a);
68
+ }
69
+ .actions {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 0.5rem;
73
+ flex: 0 0 auto;
74
+ }
75
+ `,
76
+ ]; }
77
+ /** Tracks whether the breadcrumb slot has content so its row can collapse when empty. */
78
+ _onBreadcrumbSlotChange(event) {
79
+ const slot = event.target;
80
+ this._hasBreadcrumb = slot.assignedNodes({ flatten: true }).length > 0;
81
+ }
82
+ render() {
83
+ return html `
84
+ <div class="header">
85
+ <div class="lead">
86
+ <div class="breadcrumb ${this._hasBreadcrumb ? "" : "empty"}">
87
+ <slot name="breadcrumb" @slotchange=${this._onBreadcrumbSlotChange}></slot>
88
+ </div>
89
+ ${this.heading ? html `<h2 class="title">${this.heading}</h2>` : nothing}
90
+ </div>
91
+ <div class="actions"><slot name="actions"></slot></div>
92
+ </div>
93
+ `;
94
+ }
95
+ };
96
+ __decorate([
97
+ property()
98
+ ], PageHeader.prototype, "heading", void 0);
99
+ __decorate([
100
+ state()
101
+ ], PageHeader.prototype, "_hasBreadcrumb", void 0);
102
+ PageHeader = __decorate([
103
+ customElement("page-header")
104
+ ], PageHeader);
105
+ export { PageHeader };
106
+ //# sourceMappingURL=page-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-header.js","sourceRoot":"","sources":["../src/page-header.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;GASG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QACL,6BAA6B;QACjB,YAAO,GAAG,EAAE,CAAC;QAEzB,kEAAkE;QACjD,mBAAc,GAAG,KAAK,CAAC;IAuE1C,CAAC;aArEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6CF;KACF,AAhDqB,CAgDpB;IAEF,yFAAyF;IACjF,uBAAuB,CAAC,KAAY;QAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,MAAyB,CAAC;QAC7C,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACzE,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;;mCAGoB,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;kDACnB,IAAI,CAAC,uBAAuB;;YAElE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,qBAAqB,IAAI,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO;;;;KAI5E,CAAC;IACJ,CAAC;CACF,CAAA;AA1Ea;IAAX,QAAQ,EAAE;2CAAc;AAGR;IAAhB,KAAK,EAAE;kDAAgC;AAL7B,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CA4EtB","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Page title block for the top of a dashboard view: an optional breadcrumb\n * trail, the page heading, and a right-aligned cluster of page-level actions.\n * It only lays these out — the breadcrumb links and action buttons are entirely\n * the consumer's, so it stays framework- and router-agnostic.\n *\n * @element page-header\n * @slot breadcrumb - Optional breadcrumb trail rendered above the heading.\n * @slot actions - Optional right-aligned page actions (e.g. a primary button).\n */\n@customElement(\"page-header\")\nexport class PageHeader extends LitElement {\n /** The page heading text. */\n @property() heading = \"\";\n\n /** Whether the breadcrumb slot currently has assigned content. */\n @state() private _hasBreadcrumb = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n }\n .header {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-end;\n justify-content: space-between;\n gap: 0.5rem;\n }\n .lead {\n min-width: 0;\n }\n .breadcrumb {\n margin-bottom: 0.25rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n color: var(--ui-text-muted, #64748b);\n }\n .breadcrumb.empty {\n display: none;\n }\n .title {\n margin: 0;\n font-size: var(--ui-font-size-lg, 1rem);\n font-weight: var(--ui-font-weight-semibold, 600);\n line-height: var(--ui-line-height-tight, 1.25);\n color: var(--ui-text, #0f172a);\n }\n .actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 0 0 auto;\n }\n `,\n ];\n\n /** Tracks whether the breadcrumb slot has content so its row can collapse when empty. */\n private _onBreadcrumbSlotChange(event: Event): void {\n const slot = event.target as HTMLSlotElement;\n this._hasBreadcrumb = slot.assignedNodes({ flatten: true }).length > 0;\n }\n\n override render() {\n return html`\n <div class=\"header\">\n <div class=\"lead\">\n <div class=\"breadcrumb ${this._hasBreadcrumb ? \"\" : \"empty\"}\">\n <slot name=\"breadcrumb\" @slotchange=${this._onBreadcrumbSlotChange}></slot>\n </div>\n ${this.heading ? html`<h2 class=\"title\">${this.heading}</h2>` : nothing}\n </div>\n <div class=\"actions\"><slot name=\"actions\"></slot></div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"page-header\": PageHeader;\n }\n}\n"]}
@@ -0,0 +1,34 @@
1
+ import { LitElement } from "lit";
2
+ /** Fired when the user requests a different page. `detail.page` is the 1-based target page. */
3
+ export interface PageChangeDetail {
4
+ page: number;
5
+ }
6
+ /**
7
+ * Minimal, controlled pager for list/table views: a previous/next control pair
8
+ * around a "Page N of M" status. It owns no data — the consumer sets
9
+ * `current-page` / `total-pages` and moves the page in response to the
10
+ * `page-change` event (typically alongside its own data fetch), exactly like
11
+ * `data-table` leaves the row data to the caller.
12
+ *
13
+ * Previous is disabled on the first page and next on the last; neither fires an
14
+ * event when there is nowhere to go.
15
+ *
16
+ * @element pagination-nav
17
+ * @fires page-change - The user picked a new page (`detail: { page }`).
18
+ */
19
+ export declare class PaginationNav extends LitElement {
20
+ /** The 1-based current page. */
21
+ currentPage: number;
22
+ /** The total number of pages (minimum 1). */
23
+ totalPages: number;
24
+ static styles: import("lit").CSSResult[];
25
+ /** Clamps `page` to the valid range and fires `page-change` when it actually moves. */
26
+ private _go;
27
+ render(): import("lit-html").TemplateResult<1>;
28
+ }
29
+ declare global {
30
+ interface HTMLElementTagNameMap {
31
+ "pagination-nav": PaginationNav;
32
+ }
33
+ }
34
+ //# sourceMappingURL=pagination-nav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination-nav.d.ts","sourceRoot":"","sources":["../src/pagination-nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C,+FAA+F;AAC/F,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;GAYG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC3C,gCAAgC;IACuB,WAAW,SAAK;IACvE,6CAA6C;IACS,UAAU,SAAK;IAErE,OAAgB,MAAM,4BAiEpB;IAEF,uFAAuF;IACvF,OAAO,CAAC,GAAG;IAaF,MAAM,yCAsBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,aAAa,CAAC;KACjC;CACF"}
@@ -0,0 +1,144 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html } from "lit";
8
+ import { customElement, property } from "lit/decorators.js";
9
+ import { iconChevronLeft, iconChevronRight } from "./icons.js";
10
+ import { tokens } from "./tokens.js";
11
+ /**
12
+ * Minimal, controlled pager for list/table views: a previous/next control pair
13
+ * around a "Page N of M" status. It owns no data — the consumer sets
14
+ * `current-page` / `total-pages` and moves the page in response to the
15
+ * `page-change` event (typically alongside its own data fetch), exactly like
16
+ * `data-table` leaves the row data to the caller.
17
+ *
18
+ * Previous is disabled on the first page and next on the last; neither fires an
19
+ * event when there is nowhere to go.
20
+ *
21
+ * @element pagination-nav
22
+ * @fires page-change - The user picked a new page (`detail: { page }`).
23
+ */
24
+ let PaginationNav = class PaginationNav extends LitElement {
25
+ constructor() {
26
+ super(...arguments);
27
+ /** The 1-based current page. */
28
+ this.currentPage = 1;
29
+ /** The total number of pages (minimum 1). */
30
+ this.totalPages = 1;
31
+ }
32
+ static { this.styles = [
33
+ tokens,
34
+ css `
35
+ :host {
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ gap: 0.5rem;
40
+ font-family: var(
41
+ --ui-font,
42
+ ui-sans-serif,
43
+ system-ui,
44
+ sans-serif,
45
+ "Apple Color Emoji",
46
+ "Segoe UI Emoji",
47
+ "Segoe UI Symbol",
48
+ "Noto Color Emoji"
49
+ );
50
+ }
51
+ button {
52
+ display: inline-flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ width: 2rem;
56
+ height: 2rem;
57
+ padding: 0;
58
+ background: none;
59
+ border: 1px solid var(--ui-border, #e2e8f0);
60
+ border-radius: var(--ui-radius-sm, 0.25rem);
61
+ color: var(--ui-text, #0f172a);
62
+ cursor: pointer;
63
+ }
64
+ button:hover:not(:disabled) {
65
+ background: var(--ui-surface-muted, #f8fafc);
66
+ border-color: var(--ui-text-muted, #64748b);
67
+ }
68
+ button:disabled {
69
+ opacity: 0.5;
70
+ cursor: default;
71
+ }
72
+ button:focus-visible {
73
+ outline: none;
74
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
75
+ }
76
+ .status {
77
+ font-size: var(--ui-font-size-sm, 0.75rem);
78
+ font-weight: var(--ui-font-weight-medium, 500);
79
+ line-height: var(--ui-line-height-tight, 1.25);
80
+ color: var(--ui-text-muted, #64748b);
81
+ min-width: 6rem;
82
+ text-align: center;
83
+ }
84
+ @media (forced-colors: active) {
85
+ button:focus-visible {
86
+ outline: 2px solid CanvasText;
87
+ outline-offset: 2px;
88
+ box-shadow: none;
89
+ }
90
+ button:disabled {
91
+ color: GrayText;
92
+ border-color: GrayText;
93
+ opacity: 1;
94
+ }
95
+ }
96
+ `,
97
+ ]; }
98
+ /** Clamps `page` to the valid range and fires `page-change` when it actually moves. */
99
+ _go(page) {
100
+ const total = Math.max(1, Math.floor(this.totalPages));
101
+ const target = Math.min(total, Math.max(1, Math.floor(page)));
102
+ if (target === this.currentPage)
103
+ return;
104
+ this.dispatchEvent(new CustomEvent("page-change", {
105
+ detail: { page: target },
106
+ bubbles: true,
107
+ composed: true,
108
+ }));
109
+ }
110
+ render() {
111
+ const total = Math.max(1, Math.floor(this.totalPages));
112
+ const current = Math.min(total, Math.max(1, Math.floor(this.currentPage)));
113
+ return html `
114
+ <button
115
+ type="button"
116
+ aria-label="Previous page"
117
+ ?disabled=${current <= 1}
118
+ @click=${() => this._go(current - 1)}
119
+ >
120
+ ${iconChevronLeft(18)}
121
+ </button>
122
+ <span class="status" aria-live="polite">Page ${current} of ${total}</span>
123
+ <button
124
+ type="button"
125
+ aria-label="Next page"
126
+ ?disabled=${current >= total}
127
+ @click=${() => this._go(current + 1)}
128
+ >
129
+ ${iconChevronRight(18)}
130
+ </button>
131
+ `;
132
+ }
133
+ };
134
+ __decorate([
135
+ property({ type: Number, attribute: "current-page" })
136
+ ], PaginationNav.prototype, "currentPage", void 0);
137
+ __decorate([
138
+ property({ type: Number, attribute: "total-pages" })
139
+ ], PaginationNav.prototype, "totalPages", void 0);
140
+ PaginationNav = __decorate([
141
+ customElement("pagination-nav")
142
+ ], PaginationNav);
143
+ export { PaginationNav };
144
+ //# sourceMappingURL=pagination-nav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination-nav.js","sourceRoot":"","sources":["../src/pagination-nav.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAOrC;;;;;;;;;;;;GAYG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QACL,gCAAgC;QACuB,gBAAW,GAAG,CAAC,CAAC;QACvE,6CAA6C;QACS,eAAU,GAAG,CAAC,CAAC;IA0GvE,CAAC;aAxGiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8DF;KACF,AAjEqB,CAiEpB;IAEF,uFAAuF;IAC/E,GAAG,CAAC,IAAY;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,MAAM,KAAK,IAAI,CAAC,WAAW;YAAE,OAAO;QACxC,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAmB,aAAa,EAAE;YAC/C,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;YACxB,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,IAAI;SACf,CAAC,CACH,CAAC;IACJ,CAAC;IAEQ,MAAM;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAC3E,OAAO,IAAI,CAAA;;;;oBAIK,OAAO,IAAI,CAAC;iBACf,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC;;UAElC,eAAe,CAAC,EAAE,CAAC;;qDAEwB,OAAO,OAAO,KAAK;;;;oBAIpD,OAAO,IAAI,KAAK;iBACnB,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC;;UAElC,gBAAgB,CAAC,EAAE,CAAC;;KAEzB,CAAC;IACJ,CAAC;CACF,CAAA;AA5GwD;IAAtD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;kDAAiB;AAEjB;IAArD,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;iDAAgB;AAJ1D,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CA8GzB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconChevronLeft, iconChevronRight } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/** Fired when the user requests a different page. `detail.page` is the 1-based target page. */\nexport interface PageChangeDetail {\n page: number;\n}\n\n/**\n * Minimal, controlled pager for list/table views: a previous/next control pair\n * around a \"Page N of M\" status. It owns no data — the consumer sets\n * `current-page` / `total-pages` and moves the page in response to the\n * `page-change` event (typically alongside its own data fetch), exactly like\n * `data-table` leaves the row data to the caller.\n *\n * Previous is disabled on the first page and next on the last; neither fires an\n * event when there is nowhere to go.\n *\n * @element pagination-nav\n * @fires page-change - The user picked a new page (`detail: { page }`).\n */\n@customElement(\"pagination-nav\")\nexport class PaginationNav extends LitElement {\n /** The 1-based current page. */\n @property({ type: Number, attribute: \"current-page\" }) currentPage = 1;\n /** The total number of pages (minimum 1). */\n @property({ type: Number, attribute: \"total-pages\" }) totalPages = 1;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n }\n button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 2rem;\n height: 2rem;\n padding: 0;\n background: none;\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n color: var(--ui-text, #0f172a);\n cursor: pointer;\n }\n button:hover:not(:disabled) {\n background: var(--ui-surface-muted, #f8fafc);\n border-color: var(--ui-text-muted, #64748b);\n }\n button:disabled {\n opacity: 0.5;\n cursor: default;\n }\n button:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .status {\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: var(--ui-font-weight-medium, 500);\n line-height: var(--ui-line-height-tight, 1.25);\n color: var(--ui-text-muted, #64748b);\n min-width: 6rem;\n text-align: center;\n }\n @media (forced-colors: active) {\n button:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n button:disabled {\n color: GrayText;\n border-color: GrayText;\n opacity: 1;\n }\n }\n `,\n ];\n\n /** Clamps `page` to the valid range and fires `page-change` when it actually moves. */\n private _go(page: number): void {\n const total = Math.max(1, Math.floor(this.totalPages));\n const target = Math.min(total, Math.max(1, Math.floor(page)));\n if (target === this.currentPage) return;\n this.dispatchEvent(\n new CustomEvent<PageChangeDetail>(\"page-change\", {\n detail: { page: target },\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n override render() {\n const total = Math.max(1, Math.floor(this.totalPages));\n const current = Math.min(total, Math.max(1, Math.floor(this.currentPage)));\n return html`\n <button\n type=\"button\"\n aria-label=\"Previous page\"\n ?disabled=${current <= 1}\n @click=${() => this._go(current - 1)}\n >\n ${iconChevronLeft(18)}\n </button>\n <span class=\"status\" aria-live=\"polite\">Page ${current} of ${total}</span>\n <button\n type=\"button\"\n aria-label=\"Next page\"\n ?disabled=${current >= total}\n @click=${() => this._go(current + 1)}\n >\n ${iconChevronRight(18)}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"pagination-nav\": PaginationNav;\n }\n}\n"]}
@@ -0,0 +1,22 @@
1
+ import { LitElement } from "lit";
2
+ /**
3
+ * Vertical timeline: a single connecting line runs down the left edge and each
4
+ * slotted `timeline-entry` places a dot on it. This is a thin layout and
5
+ * semantics wrapper — the entries draw the line segments and dots themselves,
6
+ * so the container adds no gap between them (a gap would break the line).
7
+ * Exposed to assistive technology as a list of events.
8
+ *
9
+ * @element timeline-container
10
+ * @slot - `timeline-entry` elements, in chronological order.
11
+ */
12
+ export declare class TimelineContainer extends LitElement {
13
+ static styles: import("lit").CSSResult[];
14
+ connectedCallback(): void;
15
+ render(): import("lit-html").TemplateResult<1>;
16
+ }
17
+ declare global {
18
+ interface HTMLElementTagNameMap {
19
+ "timeline-container": TimelineContainer;
20
+ }
21
+ }
22
+ //# sourceMappingURL=timeline-container.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-container.d.ts","sourceRoot":"","sources":["../src/timeline-container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;;;;;;GASG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IAC/C,OAAgB,MAAM,4BAOpB;IAEO,iBAAiB,IAAI,IAAI,CAGjC;IAEQ,MAAM,yCAEd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF"}
@@ -0,0 +1,42 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html } from "lit";
8
+ import { customElement } from "lit/decorators.js";
9
+ import { tokens } from "./tokens.js";
10
+ /**
11
+ * Vertical timeline: a single connecting line runs down the left edge and each
12
+ * slotted `timeline-entry` places a dot on it. This is a thin layout and
13
+ * semantics wrapper — the entries draw the line segments and dots themselves,
14
+ * so the container adds no gap between them (a gap would break the line).
15
+ * Exposed to assistive technology as a list of events.
16
+ *
17
+ * @element timeline-container
18
+ * @slot - `timeline-entry` elements, in chronological order.
19
+ */
20
+ let TimelineContainer = class TimelineContainer extends LitElement {
21
+ static { this.styles = [
22
+ tokens,
23
+ css `
24
+ :host {
25
+ display: block;
26
+ }
27
+ `,
28
+ ]; }
29
+ connectedCallback() {
30
+ super.connectedCallback();
31
+ if (!this.hasAttribute("role"))
32
+ this.setAttribute("role", "list");
33
+ }
34
+ render() {
35
+ return html `<slot></slot>`;
36
+ }
37
+ };
38
+ TimelineContainer = __decorate([
39
+ customElement("timeline-container")
40
+ ], TimelineContainer);
41
+ export { TimelineContainer };
42
+ //# sourceMappingURL=timeline-container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-container.js","sourceRoot":"","sources":["../src/timeline-container.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;GASG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,UAAU;aAC/B,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;KAIF;KACF,AAPqB,CAOpB;IAEO,iBAAiB;QACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpE,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA,eAAe,CAAC;IAC7B,CAAC;CACF,CAAA;AAlBY,iBAAiB;IAD7B,aAAa,CAAC,oBAAoB,CAAC;GACvB,iBAAiB,CAkB7B","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Vertical timeline: a single connecting line runs down the left edge and each\n * slotted `timeline-entry` places a dot on it. This is a thin layout and\n * semantics wrapper — the entries draw the line segments and dots themselves,\n * so the container adds no gap between them (a gap would break the line).\n * Exposed to assistive technology as a list of events.\n *\n * @element timeline-container\n * @slot - `timeline-entry` elements, in chronological order.\n */\n@customElement(\"timeline-container\")\nexport class TimelineContainer extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n `,\n ];\n\n override connectedCallback(): void {\n super.connectedCallback();\n if (!this.hasAttribute(\"role\")) this.setAttribute(\"role\", \"list\");\n }\n\n override render() {\n return html`<slot></slot>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"timeline-container\": TimelineContainer;\n }\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { LitElement } from "lit";
2
+ import "./relative-time.js";
3
+ /**
4
+ * One event on a `timeline-container`: a dot on the vertical line, an optional
5
+ * headline, a relative timestamp ("3 hours ago"), and freely nested content.
6
+ * The connecting line is drawn here — its segment above the dot is hidden on
7
+ * the first entry and the segment below is hidden on the last, so the line caps
8
+ * exactly at the first and last dots. Only meaningful inside a
9
+ * `timeline-container`; demonstrated through it.
10
+ *
11
+ * @element timeline-entry
12
+ * @slot headline - Optional headline/title for the event.
13
+ * @slot - The event content; nest any elements here.
14
+ */
15
+ export declare class TimelineEntry extends LitElement {
16
+ /** ISO 8601 or SQLite datetime string, rendered as a relative time. */
17
+ datetime: string | null;
18
+ /** Whether the headline slot currently has assigned content. */
19
+ private _hasHeadline;
20
+ static styles: import("lit").CSSResult[];
21
+ connectedCallback(): void;
22
+ /** Collapses the headline when nothing is slotted so the time sits alone. */
23
+ private _onHeadlineSlotChange;
24
+ render(): import("lit-html").TemplateResult<1>;
25
+ }
26
+ declare global {
27
+ interface HTMLElementTagNameMap {
28
+ "timeline-entry": TimelineEntry;
29
+ }
30
+ }
31
+ //# sourceMappingURL=timeline-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-entry.d.ts","sourceRoot":"","sources":["../src/timeline-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAErD,OAAO,oBAAoB,CAAC;AAG5B;;;;;;;;;;;GAWG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC3C,uEAAuE;IAC3D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE3C,gEAAgE;IACvD,OAAO,CAAC,YAAY,CAAS;IAEtC,OAAgB,MAAM,4BAgGpB;IAEO,iBAAiB,IAAI,IAAI,CAGjC;IAED,6EAA6E;IAC7E,OAAO,CAAC,qBAAqB;IAIpB,MAAM,yCAqBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,aAAa,CAAC;KACjC;CACF"}