@f-ewald/components 0.4.0 → 0.6.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 (87) hide show
  1. package/README.md +29 -0
  2. package/custom-elements.json +3488 -929
  3. package/dist/chat-message.d.ts +39 -0
  4. package/dist/chat-message.d.ts.map +1 -0
  5. package/dist/chat-message.js +155 -0
  6. package/dist/chat-message.js.map +1 -0
  7. package/dist/data-table.d.ts +39 -0
  8. package/dist/data-table.d.ts.map +1 -0
  9. package/dist/data-table.js +119 -0
  10. package/dist/data-table.js.map +1 -0
  11. package/dist/editable-text.d.ts +52 -0
  12. package/dist/editable-text.d.ts.map +1 -0
  13. package/dist/editable-text.js +224 -0
  14. package/dist/editable-text.js.map +1 -0
  15. package/dist/form-select.d.ts +40 -0
  16. package/dist/form-select.d.ts.map +1 -0
  17. package/dist/form-select.js +236 -0
  18. package/dist/form-select.js.map +1 -0
  19. package/dist/gallery-item-variant.d.ts +21 -0
  20. package/dist/gallery-item-variant.d.ts.map +1 -0
  21. package/dist/gallery-item-variant.js +44 -0
  22. package/dist/gallery-item-variant.js.map +1 -0
  23. package/dist/gallery-item.d.ts +28 -0
  24. package/dist/gallery-item.d.ts.map +1 -0
  25. package/dist/gallery-item.js +54 -0
  26. package/dist/gallery-item.js.map +1 -0
  27. package/dist/icons.d.ts +7 -0
  28. package/dist/icons.d.ts.map +1 -1
  29. package/dist/icons.js +7 -0
  30. package/dist/icons.js.map +1 -1
  31. package/dist/index.d.ts +14 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +14 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/live-timer.d.ts +31 -0
  36. package/dist/live-timer.d.ts.map +1 -0
  37. package/dist/live-timer.js +78 -0
  38. package/dist/live-timer.js.map +1 -0
  39. package/dist/photo-gallery.d.ts +117 -0
  40. package/dist/photo-gallery.d.ts.map +1 -0
  41. package/dist/photo-gallery.js +620 -0
  42. package/dist/photo-gallery.js.map +1 -0
  43. package/dist/popover-panel.d.ts +34 -0
  44. package/dist/popover-panel.d.ts.map +1 -0
  45. package/dist/popover-panel.js +173 -0
  46. package/dist/popover-panel.js.map +1 -0
  47. package/dist/stat-meter.d.ts +25 -0
  48. package/dist/stat-meter.d.ts.map +1 -0
  49. package/dist/stat-meter.js +106 -0
  50. package/dist/stat-meter.js.map +1 -0
  51. package/dist/status-pill.d.ts +24 -0
  52. package/dist/status-pill.d.ts.map +1 -0
  53. package/dist/status-pill.js +101 -0
  54. package/dist/status-pill.js.map +1 -0
  55. package/dist/tile-grid.d.ts +32 -0
  56. package/dist/tile-grid.d.ts.map +1 -0
  57. package/dist/tile-grid.js +102 -0
  58. package/dist/tile-grid.js.map +1 -0
  59. package/dist/tokens.css +39 -0
  60. package/dist/tokens.d.ts +28 -3
  61. package/dist/tokens.d.ts.map +1 -1
  62. package/dist/tokens.js +42 -43
  63. package/dist/tokens.js.map +1 -1
  64. package/dist/user-avatar.d.ts +5 -2
  65. package/dist/user-avatar.d.ts.map +1 -1
  66. package/dist/user-avatar.js +22 -6
  67. package/dist/user-avatar.js.map +1 -1
  68. package/dist/utils/duration.d.ts +12 -0
  69. package/dist/utils/duration.d.ts.map +1 -0
  70. package/dist/utils/duration.js +27 -0
  71. package/dist/utils/duration.js.map +1 -0
  72. package/docs/chat-message.md +59 -0
  73. package/docs/data-table.md +63 -0
  74. package/docs/editable-text.md +51 -0
  75. package/docs/form-select.md +64 -0
  76. package/docs/gallery-item-variant.md +37 -0
  77. package/docs/gallery-item.md +47 -0
  78. package/docs/live-timer.md +39 -0
  79. package/docs/map-point.md +2 -1
  80. package/docs/photo-gallery.md +77 -0
  81. package/docs/popover-panel.md +66 -0
  82. package/docs/stat-meter.md +48 -0
  83. package/docs/status-pill.md +43 -0
  84. package/docs/tile-grid.md +58 -0
  85. package/docs/user-avatar.md +1 -1
  86. package/llms.txt +303 -2
  87. package/package.json +1 -1
@@ -0,0 +1,102 @@
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 { repeat } from "lit/directives/repeat.js";
10
+ import { tokens } from "./tokens.js";
11
+ /**
12
+ * A generic, presentational grid shell: renders one bordered card per entry
13
+ * in `items`, with each tile's content produced by `renderTile` (default:
14
+ * stringify). Knows nothing about what an "item" means — callers own the
15
+ * data shape entirely. Modeled directly on `data-table`'s headless pattern.
16
+ *
17
+ * Optional `itemHref` makes whole tiles clickable (navigating via
18
+ * `location.hash`), without hijacking clicks on nested interactive elements
19
+ * (links/buttons) a tile's rendered content might contain.
20
+ *
21
+ * @element tile-grid
22
+ */
23
+ let TileGrid = class TileGrid extends LitElement {
24
+ constructor() {
25
+ super(...arguments);
26
+ /** Tile data; opaque to this component beyond what `renderTile` does with it. */
27
+ this.items = [];
28
+ /** Stable identity for `items[i]`, used as the repeat-directive key. Defaults to the item's index. */
29
+ this.itemKey = (_item, index) => index;
30
+ /** Produces a tile's rendered content for `item`. Default: stringify. */
31
+ this.renderTile = (item) => String(item);
32
+ /** When set, clicking a tile (outside any nested link/button) navigates to this hash. */
33
+ this.itemHref = null;
34
+ }
35
+ static { this.styles = [
36
+ tokens,
37
+ css `
38
+ :host {
39
+ display: block;
40
+ font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
41
+ font-size: var(--ui-font-size-sm, 0.75rem);
42
+ }
43
+ .grid {
44
+ display: grid;
45
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
46
+ gap: 0.75rem;
47
+ }
48
+ .tile {
49
+ border: 1px solid var(--ui-border, #e2e8f0);
50
+ border-radius: var(--ui-radius, 0.5rem);
51
+ padding: 0.75rem;
52
+ color: var(--ui-text, #0f172a);
53
+ }
54
+ .tile.clickable {
55
+ cursor: pointer;
56
+ }
57
+ .tile.clickable:hover {
58
+ background: var(--ui-surface-muted, #f8fafc);
59
+ }
60
+ `,
61
+ ]; }
62
+ #onTileClick(item, e) {
63
+ if (!this.itemHref)
64
+ return;
65
+ if (e.target.closest("a, button"))
66
+ return;
67
+ const href = this.itemHref(item);
68
+ if (href)
69
+ location.hash = href;
70
+ }
71
+ render() {
72
+ return html `
73
+ <div class="grid">
74
+ ${repeat(this.items, (item, i) => this.itemKey(item, i), (item) => html `
75
+ <div
76
+ class=${this.itemHref ? "tile clickable" : "tile"}
77
+ @click=${(e) => this.#onTileClick(item, e)}
78
+ >
79
+ ${this.renderTile(item)}
80
+ </div>
81
+ `)}
82
+ </div>
83
+ `;
84
+ }
85
+ };
86
+ __decorate([
87
+ property({ attribute: false })
88
+ ], TileGrid.prototype, "items", void 0);
89
+ __decorate([
90
+ property({ attribute: false })
91
+ ], TileGrid.prototype, "itemKey", void 0);
92
+ __decorate([
93
+ property({ attribute: false })
94
+ ], TileGrid.prototype, "renderTile", void 0);
95
+ __decorate([
96
+ property({ attribute: false })
97
+ ], TileGrid.prototype, "itemHref", void 0);
98
+ TileGrid = __decorate([
99
+ customElement("tile-grid")
100
+ ], TileGrid);
101
+ export { TileGrid };
102
+ //# sourceMappingURL=tile-grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tile-grid.js","sourceRoot":"","sources":["../src/tile-grid.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,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;;GAWG;AAEI,IAAM,QAAQ,GAAd,MAAM,QAAS,SAAQ,UAAU;IAAjC;;QA6BL,iFAAiF;QACjD,UAAK,GAAc,EAAE,CAAC;QACtD,sGAAsG;QACtE,YAAO,GAAsD,CAC3F,KAAK,EACL,KAAK,EACL,EAAE,CAAC,KAAK,CAAC;QACX,yEAAyE;QACzC,eAAU,GAA+B,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChG,yFAAyF;QACzD,aAAQ,GAA8C,IAAI,CAAC;IA2B7F,CAAC;aAjEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;KAuBF;KACF,AA1BqB,CA0BpB;IAcF,YAAY,CAAC,IAAa,EAAE,CAAa;QACvC,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC3B,IAAK,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,WAAW,CAAC;YAAE,OAAO;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;IACjC,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;UAEL,MAAM,CACN,IAAI,CAAC,KAAK,EACV,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,EAClC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAA;;sBAEF,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM;uBACxC,CAAC,CAAa,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;;gBAEpD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;;WAE1B,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AApCiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;uCAAuB;AAEtB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;yCAGpB;AAEqB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;4CAAiE;AAEhE;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;0CAA4D;AAvChF,QAAQ;IADpB,aAAa,CAAC,WAAW,CAAC;GACd,QAAQ,CAkEpB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * A generic, presentational grid shell: renders one bordered card per entry\n * in `items`, with each tile's content produced by `renderTile` (default:\n * stringify). Knows nothing about what an \"item\" means — callers own the\n * data shape entirely. Modeled directly on `data-table`'s headless pattern.\n *\n * Optional `itemHref` makes whole tiles clickable (navigating via\n * `location.hash`), without hijacking clicks on nested interactive elements\n * (links/buttons) a tile's rendered content might contain.\n *\n * @element tile-grid\n */\n@customElement(\"tile-grid\")\nexport class TileGrid extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size-sm, 0.75rem);\n }\n .grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\n gap: 0.75rem;\n }\n .tile {\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius, 0.5rem);\n padding: 0.75rem;\n color: var(--ui-text, #0f172a);\n }\n .tile.clickable {\n cursor: pointer;\n }\n .tile.clickable:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n `,\n ];\n\n /** Tile data; opaque to this component beyond what `renderTile` does with it. */\n @property({ attribute: false }) items: unknown[] = [];\n /** Stable identity for `items[i]`, used as the repeat-directive key. Defaults to the item's index. */\n @property({ attribute: false }) itemKey: (item: unknown, index: number) => string | number = (\n _item,\n index,\n ) => index;\n /** Produces a tile's rendered content for `item`. Default: stringify. */\n @property({ attribute: false }) renderTile: (item: unknown) => unknown = (item) => String(item);\n /** When set, clicking a tile (outside any nested link/button) navigates to this hash. */\n @property({ attribute: false }) itemHref: ((item: unknown) => string | null) | null = null;\n\n #onTileClick(item: unknown, e: MouseEvent): void {\n if (!this.itemHref) return;\n if ((e.target as HTMLElement).closest(\"a, button\")) return;\n const href = this.itemHref(item);\n if (href) location.hash = href;\n }\n\n override render() {\n return html`\n <div class=\"grid\">\n ${repeat(\n this.items,\n (item, i) => this.itemKey(item, i),\n (item) => html`\n <div\n class=${this.itemHref ? \"tile clickable\" : \"tile\"}\n @click=${(e: MouseEvent) => this.#onTileClick(item, e)}\n >\n ${this.renderTile(item)}\n </div>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"tile-grid\": TileGrid;\n }\n}\n"]}
package/dist/tokens.css CHANGED
@@ -1,4 +1,5 @@
1
1
  :root {
2
+ color-scheme: light;
2
3
  --ui-primary: #4f46e5;
3
4
  --ui-primary-hover: #4338ca;
4
5
  --ui-danger: #dc2626;
@@ -19,3 +20,41 @@
19
20
  --ui-font-size: 0.875rem;
20
21
  --ui-font-size-sm: 0.75rem;
21
22
  }
23
+
24
+ @media (prefers-color-scheme: dark) {
25
+ :root:not([data-theme="light"]) {
26
+ color-scheme: dark;
27
+ --ui-primary: #6366f1;
28
+ --ui-primary-hover: #818cf8;
29
+ --ui-danger: #ef4444;
30
+ --ui-danger-hover: #f87171;
31
+ --ui-success: #22c55e;
32
+ --ui-text: #f1f5f9;
33
+ --ui-text-muted: #94a3b8;
34
+ --ui-border: #334155;
35
+ --ui-surface: #0f172a;
36
+ --ui-surface-muted: #1e293b;
37
+ --ui-overlay: rgb(2 6 23 / 0.6);
38
+ --ui-focus-ring: 0 0 0 3px rgb(99 102 241 / 0.45);
39
+ }
40
+ }
41
+
42
+ :root[data-theme="dark"] {
43
+ color-scheme: dark;
44
+ --ui-primary: #6366f1;
45
+ --ui-primary-hover: #818cf8;
46
+ --ui-danger: #ef4444;
47
+ --ui-danger-hover: #f87171;
48
+ --ui-success: #22c55e;
49
+ --ui-text: #f1f5f9;
50
+ --ui-text-muted: #94a3b8;
51
+ --ui-border: #334155;
52
+ --ui-surface: #0f172a;
53
+ --ui-surface-muted: #1e293b;
54
+ --ui-overlay: rgb(2 6 23 / 0.6);
55
+ --ui-focus-ring: 0 0 0 3px rgb(99 102 241 / 0.45);
56
+ }
57
+
58
+ :root[data-theme="light"] {
59
+ color-scheme: light;
60
+ }
package/dist/tokens.d.ts CHANGED
@@ -8,9 +8,34 @@
8
8
  */
9
9
  export declare const tokenValues: Record<string, string>;
10
10
  /**
11
- * A shared `:host` stylesheet that establishes the token custom properties
12
- * with their fallback values. Import and include this in every component's
13
- * `static styles` array alongside its component-specific `css` block.
11
+ * Dark-mode overrides for the same `--ui-*` custom properties, applied by
12
+ * `tokens.css` via `@media (prefers-color-scheme: dark)` and `[data-theme]`
13
+ * (see generate-tokens-css.mjs). Same Tailwind slate scale as the light
14
+ * palette, inverted, with primary/danger/success lightened one step (500
15
+ * instead of 600) for sufficient contrast against dark surfaces. Shape/type
16
+ * tokens are theme-independent and not repeated here.
17
+ */
18
+ export declare const darkTokenValues: Record<string, string>;
19
+ /**
20
+ * Historically a shared `:host { --ui-x: var(--ui-x, fallback); } ` block,
21
+ * re-declaring every token on each component's own host element as a
22
+ * "materialize the inherited value, or this fallback" trick. That pattern
23
+ * turned out to be a genuine bug, not a working default: browsers treat
24
+ * `--ui-x: var(--ui-x, ...)` on `:host` as a self-referencing declaration
25
+ * that computes to the guaranteed-invalid value REGARDLESS of what an
26
+ * ancestor (e.g. `:root`) set — the host's own `:host` rule wins the cascade
27
+ * over the ancestor's, so this dropped whatever the app actually set and
28
+ * silently substituted the component's own fallback everywhere. It went
29
+ * unnoticed because every component's fallback happened to equal the
30
+ * intended light-mode value — dark mode (a real, different value at
31
+ * `:root`) exposed it immediately (colors rendering as if no theme were
32
+ * applied at all). Fix: don't redeclare tokens on `:host` — plain
33
+ * inheritance already carries `:root`'s values into every shadow root
34
+ * correctly, and every component already has its own `var(--ui-x, fallback)`
35
+ * at each point of use, so nothing here is needed for the "zero external
36
+ * CSS" default to keep working. Kept as an empty export so the existing
37
+ * `static override styles = [tokens, css\`...\`]` import in every component
38
+ * keeps compiling.
14
39
  */
15
40
  export declare const tokens: import("lit").CSSResult;
16
41
  //# sourceMappingURL=tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA4B9C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,MAAM,yBAuClB,CAAC"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA4B9C,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAalD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,MAAM,yBAAQ,CAAC"}
package/dist/tokens.js CHANGED
@@ -32,48 +32,47 @@ export const tokenValues = {
32
32
  "--ui-font-size-sm": "0.75rem",
33
33
  };
34
34
  /**
35
- * A shared `:host` stylesheet that establishes the token custom properties
36
- * with their fallback values. Import and include this in every component's
37
- * `static styles` array alongside its component-specific `css` block.
35
+ * Dark-mode overrides for the same `--ui-*` custom properties, applied by
36
+ * `tokens.css` via `@media (prefers-color-scheme: dark)` and `[data-theme]`
37
+ * (see generate-tokens-css.mjs). Same Tailwind slate scale as the light
38
+ * palette, inverted, with primary/danger/success lightened one step (500
39
+ * instead of 600) for sufficient contrast against dark surfaces. Shape/type
40
+ * tokens are theme-independent and not repeated here.
38
41
  */
39
- export const tokens = css `
40
- :host {
41
- --ui-primary: var(--ui-primary, #4f46e5);
42
- --ui-primary-hover: var(--ui-primary-hover, #4338ca);
43
- --ui-danger: var(--ui-danger, #dc2626);
44
- --ui-danger-hover: var(--ui-danger-hover, #b91c1c);
45
- --ui-success: var(--ui-success, #16a34a);
46
- --ui-text: var(--ui-text, #0f172a);
47
- --ui-text-muted: var(--ui-text-muted, #64748b);
48
- --ui-border: var(--ui-border, #e2e8f0);
49
- --ui-surface: var(--ui-surface, #ffffff);
50
- --ui-surface-muted: var(--ui-surface-muted, #f8fafc);
51
- --ui-overlay: var(--ui-overlay, rgb(15 23 42 / 0.45));
52
- --ui-radius: var(--ui-radius, 0.5rem);
53
- --ui-radius-sm: var(--ui-radius-sm, 0.25rem);
54
- --ui-shadow: var(
55
- --ui-shadow,
56
- 0 4px 6px -1px rgb(0 0 0 / 0.1),
57
- 0 2px 4px -2px rgb(0 0 0 / 0.1)
58
- );
59
- --ui-shadow-lg: var(
60
- --ui-shadow-lg,
61
- 0 20px 25px -5px rgb(0 0 0 / 0.1),
62
- 0 8px 10px -6px rgb(0 0 0 / 0.1)
63
- );
64
- --ui-focus-ring: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
65
- --ui-font: var(
66
- --ui-font,
67
- ui-sans-serif,
68
- system-ui,
69
- sans-serif,
70
- "Apple Color Emoji",
71
- "Segoe UI Emoji",
72
- "Segoe UI Symbol",
73
- "Noto Color Emoji"
74
- );
75
- --ui-font-size: var(--ui-font-size, 0.875rem);
76
- --ui-font-size-sm: var(--ui-font-size-sm, 0.75rem);
77
- }
78
- `;
42
+ export const darkTokenValues = {
43
+ "--ui-primary": "#6366f1", // indigo-500
44
+ "--ui-primary-hover": "#818cf8", // indigo-400
45
+ "--ui-danger": "#ef4444", // red-500
46
+ "--ui-danger-hover": "#f87171", // red-400
47
+ "--ui-success": "#22c55e", // green-500
48
+ "--ui-text": "#f1f5f9", // slate-100
49
+ "--ui-text-muted": "#94a3b8", // slate-400
50
+ "--ui-border": "#334155", // slate-700
51
+ "--ui-surface": "#0f172a", // slate-900
52
+ "--ui-surface-muted": "#1e293b", // slate-800
53
+ "--ui-overlay": "rgb(2 6 23 / 0.6)", // slate-950 / 60%
54
+ "--ui-focus-ring": "0 0 0 3px rgb(99 102 241 / 0.45)",
55
+ };
56
+ /**
57
+ * Historically a shared `:host { --ui-x: var(--ui-x, fallback); } ` block,
58
+ * re-declaring every token on each component's own host element as a
59
+ * "materialize the inherited value, or this fallback" trick. That pattern
60
+ * turned out to be a genuine bug, not a working default: browsers treat
61
+ * `--ui-x: var(--ui-x, ...)` on `:host` as a self-referencing declaration
62
+ * that computes to the guaranteed-invalid value REGARDLESS of what an
63
+ * ancestor (e.g. `:root`) set — the host's own `:host` rule wins the cascade
64
+ * over the ancestor's, so this dropped whatever the app actually set and
65
+ * silently substituted the component's own fallback everywhere. It went
66
+ * unnoticed because every component's fallback happened to equal the
67
+ * intended light-mode value dark mode (a real, different value at
68
+ * `:root`) exposed it immediately (colors rendering as if no theme were
69
+ * applied at all). Fix: don't redeclare tokens on `:host` — plain
70
+ * inheritance already carries `:root`'s values into every shadow root
71
+ * correctly, and every component already has its own `var(--ui-x, fallback)`
72
+ * at each point of use, so nothing here is needed for the "zero external
73
+ * CSS" default to keep working. Kept as an empty export so the existing
74
+ * `static override styles = [tokens, css\`...\`]` import in every component
75
+ * keeps compiling.
76
+ */
77
+ export const tokens = css ``;
79
78
  //# sourceMappingURL=tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAA2B;IACjD,SAAS;IACT,cAAc,EAAE,SAAS,EAAE,aAAa;IACxC,oBAAoB,EAAE,SAAS,EAAE,aAAa;IAC9C,aAAa,EAAE,SAAS,EAAE,UAAU;IACpC,mBAAmB,EAAE,SAAS,EAAE,UAAU;IAC1C,cAAc,EAAE,SAAS,EAAE,YAAY;IACvC,WAAW,EAAE,SAAS,EAAE,YAAY;IACpC,iBAAiB,EAAE,SAAS,EAAE,YAAY;IAC1C,aAAa,EAAE,SAAS,EAAE,YAAY;IACtC,cAAc,EAAE,SAAS,EAAE,QAAQ;IACnC,oBAAoB,EAAE,SAAS,EAAE,WAAW;IAC5C,cAAc,EAAE,sBAAsB,EAAE,kBAAkB;IAE1D,gBAAgB;IAChB,aAAa,EAAE,QAAQ,EAAE,aAAa;IACtC,gBAAgB,EAAE,SAAS,EAAE,UAAU;IACvC,aAAa,EACX,kEAAkE,EAAE,YAAY;IAClF,gBAAgB,EACd,qEAAqE,EAAE,YAAY;IACrF,iBAAiB,EAAE,iCAAiC;IAEpD,OAAO;IACP,WAAW,EACT,oHAAoH;IACtH,gBAAgB,EAAE,UAAU;IAC5B,mBAAmB,EAAE,SAAS;CAC/B,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCxB,CAAC","sourcesContent":["import { css } from \"lit\";\n\n/**\n * Design token values, sourced from Tailwind v4's default theme palette.\n * Each entry maps a `--ui-*` custom property name to its default value.\n * These are the same values baked in as `var(--ui-*, <fallback>)` fallbacks\n * throughout component styles, so components render correctly with zero\n * external CSS. Consumers may override any of these by setting the custom\n * property on `:root` or an ancestor element (see `tokens.css`).\n */\nexport const tokenValues: Record<string, string> = {\n // Colors\n \"--ui-primary\": \"#4f46e5\", // indigo-600\n \"--ui-primary-hover\": \"#4338ca\", // indigo-700\n \"--ui-danger\": \"#dc2626\", // red-600\n \"--ui-danger-hover\": \"#b91c1c\", // red-700\n \"--ui-success\": \"#16a34a\", // green-600\n \"--ui-text\": \"#0f172a\", // slate-900\n \"--ui-text-muted\": \"#64748b\", // slate-500\n \"--ui-border\": \"#e2e8f0\", // slate-200\n \"--ui-surface\": \"#ffffff\", // white\n \"--ui-surface-muted\": \"#f8fafc\", // slate-50\n \"--ui-overlay\": \"rgb(15 23 42 / 0.45)\", // slate-900 / 45%\n\n // Shape / depth\n \"--ui-radius\": \"0.5rem\", // rounded-lg\n \"--ui-radius-sm\": \"0.25rem\", // rounded\n \"--ui-shadow\":\n \"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)\", // shadow-md\n \"--ui-shadow-lg\":\n \"0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)\", // shadow-xl\n \"--ui-focus-ring\": \"0 0 0 3px rgb(79 70 229 / 0.35)\",\n\n // Type\n \"--ui-font\":\n 'ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"',\n \"--ui-font-size\": \"0.875rem\",\n \"--ui-font-size-sm\": \"0.75rem\",\n};\n\n/**\n * A shared `:host` stylesheet that establishes the token custom properties\n * with their fallback values. Import and include this in every component's\n * `static styles` array alongside its component-specific `css` block.\n */\nexport const tokens = css`\n :host {\n --ui-primary: var(--ui-primary, #4f46e5);\n --ui-primary-hover: var(--ui-primary-hover, #4338ca);\n --ui-danger: var(--ui-danger, #dc2626);\n --ui-danger-hover: var(--ui-danger-hover, #b91c1c);\n --ui-success: var(--ui-success, #16a34a);\n --ui-text: var(--ui-text, #0f172a);\n --ui-text-muted: var(--ui-text-muted, #64748b);\n --ui-border: var(--ui-border, #e2e8f0);\n --ui-surface: var(--ui-surface, #ffffff);\n --ui-surface-muted: var(--ui-surface-muted, #f8fafc);\n --ui-overlay: var(--ui-overlay, rgb(15 23 42 / 0.45));\n --ui-radius: var(--ui-radius, 0.5rem);\n --ui-radius-sm: var(--ui-radius-sm, 0.25rem);\n --ui-shadow: var(\n --ui-shadow,\n 0 4px 6px -1px rgb(0 0 0 / 0.1),\n 0 2px 4px -2px rgb(0 0 0 / 0.1)\n );\n --ui-shadow-lg: var(\n --ui-shadow-lg,\n 0 20px 25px -5px rgb(0 0 0 / 0.1),\n 0 8px 10px -6px rgb(0 0 0 / 0.1)\n );\n --ui-focus-ring: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n --ui-font: 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 --ui-font-size: var(--ui-font-size, 0.875rem);\n --ui-font-size-sm: var(--ui-font-size-sm, 0.75rem);\n }\n`;\n"]}
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAA2B;IACjD,SAAS;IACT,cAAc,EAAE,SAAS,EAAE,aAAa;IACxC,oBAAoB,EAAE,SAAS,EAAE,aAAa;IAC9C,aAAa,EAAE,SAAS,EAAE,UAAU;IACpC,mBAAmB,EAAE,SAAS,EAAE,UAAU;IAC1C,cAAc,EAAE,SAAS,EAAE,YAAY;IACvC,WAAW,EAAE,SAAS,EAAE,YAAY;IACpC,iBAAiB,EAAE,SAAS,EAAE,YAAY;IAC1C,aAAa,EAAE,SAAS,EAAE,YAAY;IACtC,cAAc,EAAE,SAAS,EAAE,QAAQ;IACnC,oBAAoB,EAAE,SAAS,EAAE,WAAW;IAC5C,cAAc,EAAE,sBAAsB,EAAE,kBAAkB;IAE1D,gBAAgB;IAChB,aAAa,EAAE,QAAQ,EAAE,aAAa;IACtC,gBAAgB,EAAE,SAAS,EAAE,UAAU;IACvC,aAAa,EACX,kEAAkE,EAAE,YAAY;IAClF,gBAAgB,EACd,qEAAqE,EAAE,YAAY;IACrF,iBAAiB,EAAE,iCAAiC;IAEpD,OAAO;IACP,WAAW,EACT,oHAAoH;IACtH,gBAAgB,EAAE,UAAU;IAC5B,mBAAmB,EAAE,SAAS;CAC/B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAA2B;IACrD,cAAc,EAAE,SAAS,EAAE,aAAa;IACxC,oBAAoB,EAAE,SAAS,EAAE,aAAa;IAC9C,aAAa,EAAE,SAAS,EAAE,UAAU;IACpC,mBAAmB,EAAE,SAAS,EAAE,UAAU;IAC1C,cAAc,EAAE,SAAS,EAAE,YAAY;IACvC,WAAW,EAAE,SAAS,EAAE,YAAY;IACpC,iBAAiB,EAAE,SAAS,EAAE,YAAY;IAC1C,aAAa,EAAE,SAAS,EAAE,YAAY;IACtC,cAAc,EAAE,SAAS,EAAE,YAAY;IACvC,oBAAoB,EAAE,SAAS,EAAE,YAAY;IAC7C,cAAc,EAAE,mBAAmB,EAAE,kBAAkB;IACvD,iBAAiB,EAAE,kCAAkC;CACtD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA,EAAE,CAAC","sourcesContent":["import { css } from \"lit\";\n\n/**\n * Design token values, sourced from Tailwind v4's default theme palette.\n * Each entry maps a `--ui-*` custom property name to its default value.\n * These are the same values baked in as `var(--ui-*, <fallback>)` fallbacks\n * throughout component styles, so components render correctly with zero\n * external CSS. Consumers may override any of these by setting the custom\n * property on `:root` or an ancestor element (see `tokens.css`).\n */\nexport const tokenValues: Record<string, string> = {\n // Colors\n \"--ui-primary\": \"#4f46e5\", // indigo-600\n \"--ui-primary-hover\": \"#4338ca\", // indigo-700\n \"--ui-danger\": \"#dc2626\", // red-600\n \"--ui-danger-hover\": \"#b91c1c\", // red-700\n \"--ui-success\": \"#16a34a\", // green-600\n \"--ui-text\": \"#0f172a\", // slate-900\n \"--ui-text-muted\": \"#64748b\", // slate-500\n \"--ui-border\": \"#e2e8f0\", // slate-200\n \"--ui-surface\": \"#ffffff\", // white\n \"--ui-surface-muted\": \"#f8fafc\", // slate-50\n \"--ui-overlay\": \"rgb(15 23 42 / 0.45)\", // slate-900 / 45%\n\n // Shape / depth\n \"--ui-radius\": \"0.5rem\", // rounded-lg\n \"--ui-radius-sm\": \"0.25rem\", // rounded\n \"--ui-shadow\":\n \"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)\", // shadow-md\n \"--ui-shadow-lg\":\n \"0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)\", // shadow-xl\n \"--ui-focus-ring\": \"0 0 0 3px rgb(79 70 229 / 0.35)\",\n\n // Type\n \"--ui-font\":\n 'ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"',\n \"--ui-font-size\": \"0.875rem\",\n \"--ui-font-size-sm\": \"0.75rem\",\n};\n\n/**\n * Dark-mode overrides for the same `--ui-*` custom properties, applied by\n * `tokens.css` via `@media (prefers-color-scheme: dark)` and `[data-theme]`\n * (see generate-tokens-css.mjs). Same Tailwind slate scale as the light\n * palette, inverted, with primary/danger/success lightened one step (500\n * instead of 600) for sufficient contrast against dark surfaces. Shape/type\n * tokens are theme-independent and not repeated here.\n */\nexport const darkTokenValues: Record<string, string> = {\n \"--ui-primary\": \"#6366f1\", // indigo-500\n \"--ui-primary-hover\": \"#818cf8\", // indigo-400\n \"--ui-danger\": \"#ef4444\", // red-500\n \"--ui-danger-hover\": \"#f87171\", // red-400\n \"--ui-success\": \"#22c55e\", // green-500\n \"--ui-text\": \"#f1f5f9\", // slate-100\n \"--ui-text-muted\": \"#94a3b8\", // slate-400\n \"--ui-border\": \"#334155\", // slate-700\n \"--ui-surface\": \"#0f172a\", // slate-900\n \"--ui-surface-muted\": \"#1e293b\", // slate-800\n \"--ui-overlay\": \"rgb(2 6 23 / 0.6)\", // slate-950 / 60%\n \"--ui-focus-ring\": \"0 0 0 3px rgb(99 102 241 / 0.45)\",\n};\n\n/**\n * Historically a shared `:host { --ui-x: var(--ui-x, fallback); } ` block,\n * re-declaring every token on each component's own host element as a\n * \"materialize the inherited value, or this fallback\" trick. That pattern\n * turned out to be a genuine bug, not a working default: browsers treat\n * `--ui-x: var(--ui-x, ...)` on `:host` as a self-referencing declaration\n * that computes to the guaranteed-invalid value REGARDLESS of what an\n * ancestor (e.g. `:root`) set the host's own `:host` rule wins the cascade\n * over the ancestor's, so this dropped whatever the app actually set and\n * silently substituted the component's own fallback everywhere. It went\n * unnoticed because every component's fallback happened to equal the\n * intended light-mode value — dark mode (a real, different value at\n * `:root`) exposed it immediately (colors rendering as if no theme were\n * applied at all). Fix: don't redeclare tokens on `:host` — plain\n * inheritance already carries `:root`'s values into every shadow root\n * correctly, and every component already has its own `var(--ui-x, fallback)`\n * at each point of use, so nothing here is needed for the \"zero external\n * CSS\" default to keep working. Kept as an empty export so the existing\n * `static override styles = [tokens, css\\`...\\`]` import in every component\n * keeps compiling.\n */\nexport const tokens = css``;\n"]}
@@ -14,9 +14,12 @@ export declare class UserAvatar extends LitElement {
14
14
  src: string | null;
15
15
  /** Source string for the fallback initial (e.g. a display name or email) — first character, uppercased. */
16
16
  name: string | null;
17
- /** Diameter in CSS pixels. */
18
- size: number;
17
+ /** Diameter in CSS pixels, or a preset name (`xs`=18, `sm`=24, `md`=32, `lg`=48). */
18
+ size: string | number;
19
19
  private _imgError;
20
+ private static readonly SIZE_PRESETS;
21
+ /** Resolves the `size` property (number or preset name) to a pixel diameter. */
22
+ private resolveSize;
20
23
  protected willUpdate(changed: Map<string, unknown>): void;
21
24
  render(): import("lit-html").TemplateResult<1>;
22
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"user-avatar.d.ts","sourceRoot":"","sources":["../src/user-avatar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C;;;;;;;;GAQG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,OAAgB,MAAM,4BAyBpB;IAEF,mFAAmF;IACvE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAQ;IACtC,2GAA2G;IAC/F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAQ;IACvC,8BAA8B;IACF,IAAI,SAAM;IAE7B,OAAO,CAAC,SAAS,CAAS;IAEnC,UAAmB,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,QAE1D;IAEQ,MAAM,yCAgBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
1
+ {"version":3,"file":"user-avatar.d.ts","sourceRoot":"","sources":["../src/user-avatar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C;;;;;;;;GAQG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,OAAgB,MAAM,4BAyBpB;IAEF,mFAAmF;IACvE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAQ;IACtC,2GAA2G;IAC/F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAQ;IACvC,qFAAqF;IACzE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAM;IAE9B,OAAO,CAAC,SAAS,CAAS;IAEnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAKlC;IAEF,gFAAgF;IAChF,OAAO,CAAC,WAAW;IAMnB,UAAmB,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,QAE1D;IAEQ,MAAM,yCAiBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
@@ -4,6 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
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
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
+ var UserAvatar_1;
7
8
  import { LitElement, css, html } from "lit";
8
9
  import { customElement, property, state } from "lit/decorators.js";
9
10
  import { iconUserCircle } from "./icons.js";
@@ -24,10 +25,11 @@ let UserAvatar = class UserAvatar extends LitElement {
24
25
  this.src = null;
25
26
  /** Source string for the fallback initial (e.g. a display name or email) — first character, uppercased. */
26
27
  this.name = null;
27
- /** Diameter in CSS pixels. */
28
+ /** Diameter in CSS pixels, or a preset name (`xs`=18, `sm`=24, `md`=32, `lg`=48). */
28
29
  this.size = 32;
29
30
  this._imgError = false;
30
31
  }
32
+ static { UserAvatar_1 = this; }
31
33
  static { this.styles = [
32
34
  tokens,
33
35
  css `
@@ -54,16 +56,30 @@ let UserAvatar = class UserAvatar extends LitElement {
54
56
  }
55
57
  `,
56
58
  ]; }
59
+ static { this.SIZE_PRESETS = {
60
+ xs: 18,
61
+ sm: 24,
62
+ md: 32,
63
+ lg: 48,
64
+ }; }
65
+ /** Resolves the `size` property (number or preset name) to a pixel diameter. */
66
+ resolveSize() {
67
+ const numeric = Number(this.size);
68
+ if (!Number.isNaN(numeric))
69
+ return numeric;
70
+ return UserAvatar_1.SIZE_PRESETS[String(this.size)] ?? 32;
71
+ }
57
72
  willUpdate(changed) {
58
73
  if (changed.has("src"))
59
74
  this._imgError = false;
60
75
  }
61
76
  render() {
62
- const dim = `${this.size}px`;
77
+ const size = this.resolveSize();
78
+ const dim = `${size}px`;
63
79
  const showImage = !!this.src && !this._imgError;
64
80
  const initial = this.name?.trim()[0]?.toUpperCase();
65
81
  return html `
66
- <span class="avatar" style="width:${dim};height:${dim};font-size:${this.size * 0.42}px">
82
+ <span class="avatar" style="width:${dim};height:${dim};font-size:${size * 0.42}px">
67
83
  ${showImage
68
84
  ? html `<img
69
85
  src=${this.src}
@@ -71,7 +87,7 @@ let UserAvatar = class UserAvatar extends LitElement {
71
87
  referrerpolicy="no-referrer"
72
88
  @error=${() => (this._imgError = true)}
73
89
  />`
74
- : initial || iconUserCircle(Math.round(this.size * 0.65))}
90
+ : initial || iconUserCircle(Math.round(size * 0.65))}
75
91
  </span>
76
92
  `;
77
93
  }
@@ -83,12 +99,12 @@ __decorate([
83
99
  property()
84
100
  ], UserAvatar.prototype, "name", void 0);
85
101
  __decorate([
86
- property({ type: Number })
102
+ property()
87
103
  ], UserAvatar.prototype, "size", void 0);
88
104
  __decorate([
89
105
  state()
90
106
  ], UserAvatar.prototype, "_imgError", void 0);
91
- UserAvatar = __decorate([
107
+ UserAvatar = UserAvatar_1 = __decorate([
92
108
  customElement("user-avatar")
93
109
  ], UserAvatar);
94
110
  export { UserAvatar };
@@ -1 +1 @@
1
- {"version":3,"file":"user-avatar.js","sourceRoot":"","sources":["../src/user-avatar.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;GAQG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QA4BL,mFAAmF;QACvE,QAAG,GAAkB,IAAI,CAAC;QACtC,2GAA2G;QAC/F,SAAI,GAAkB,IAAI,CAAC;QACvC,8BAA8B;QACF,SAAI,GAAG,EAAE,CAAC;QAErB,cAAS,GAAG,KAAK,CAAC;IAuBrC,CAAC;aAzDiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;KAsBF;KACF,AAzBqB,CAyBpB;IAWiB,UAAU,CAAC,OAA6B;QACzD,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACjD,CAAC;IAEQ,MAAM;QACb,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,CAAC;QAC7B,MAAM,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACpD,OAAO,IAAI,CAAA;0CAC2B,GAAG,WAAW,GAAG,cAAc,IAAI,CAAC,IAAI,GAAG,IAAI;UAC/E,SAAS;YACT,CAAC,CAAC,IAAI,CAAA;oBACI,IAAI,CAAC,GAAI;;;uBAGN,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;eACrC;YACL,CAAC,CAAC,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;;KAE9D,CAAC;IACJ,CAAC;CACF,CAAA;AA7Ba;IAAX,QAAQ,EAAE;uCAA2B;AAE1B;IAAX,QAAQ,EAAE;wCAA4B;AAEX;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;wCAAW;AAErB;IAAhB,KAAK,EAAE;6CAA2B;AAnCxB,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CA0DtB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { iconUserCircle } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Circular avatar. Shows `src` when it loads successfully; falls back to the\n * first letter of `name` (uppercased) if `src` is unset or fails to load\n * (e.g. an expired OAuth profile-photo URL); falls back further to a generic\n * person icon if `name` is also unset. A broken image never leaves a blank\n * circle.\n *\n * @element user-avatar\n */\n@customElement(\"user-avatar\")\nexport class UserAvatar extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n .avatar {\n border-radius: 50%;\n background: var(--ui-primary, #4f46e5);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-weight: 600;\n overflow: hidden;\n flex: 0 0 auto;\n }\n img {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n object-fit: cover;\n }\n `,\n ];\n\n /** Image URL to show. Falls back to initials/icon if unset or it fails to load. */\n @property() src: string | null = null;\n /** Source string for the fallback initial (e.g. a display name or email) — first character, uppercased. */\n @property() name: string | null = null;\n /** Diameter in CSS pixels. */\n @property({ type: Number }) size = 32;\n\n @state() private _imgError = false;\n\n protected override willUpdate(changed: Map<string, unknown>) {\n if (changed.has(\"src\")) this._imgError = false;\n }\n\n override render() {\n const dim = `${this.size}px`;\n const showImage = !!this.src && !this._imgError;\n const initial = this.name?.trim()[0]?.toUpperCase();\n return html`\n <span class=\"avatar\" style=\"width:${dim};height:${dim};font-size:${this.size * 0.42}px\">\n ${showImage\n ? html`<img\n src=${this.src!}\n alt=\"\"\n referrerpolicy=\"no-referrer\"\n @error=${() => (this._imgError = true)}\n />`\n : initial || iconUserCircle(Math.round(this.size * 0.65))}\n </span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"user-avatar\": UserAvatar;\n }\n}\n"]}
1
+ {"version":3,"file":"user-avatar.js","sourceRoot":"","sources":["../src/user-avatar.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;GAQG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QA4BL,mFAAmF;QACvE,QAAG,GAAkB,IAAI,CAAC;QACtC,2GAA2G;QAC/F,SAAI,GAAkB,IAAI,CAAC;QACvC,qFAAqF;QACzE,SAAI,GAAoB,EAAE,CAAC;QAEtB,cAAS,GAAG,KAAK,CAAC;IAsCrC,CAAC;;aAxEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;KAsBF;KACF,AAzBqB,CAyBpB;aAWsB,iBAAY,GAA2B;QAC7D,EAAE,EAAE,EAAE;QACN,EAAE,EAAE,EAAE;QACN,EAAE,EAAE,EAAE;QACN,EAAE,EAAE,EAAE;KACP,AALmC,CAKlC;IAEF,gFAAgF;IACxE,WAAW;QACjB,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC;QAC3C,OAAO,aAAW,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1D,CAAC;IAEkB,UAAU,CAAC,OAA6B;QACzD,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACjD,CAAC;IAEQ,MAAM;QACb,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,CAAC;QACxB,MAAM,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;QAChD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACpD,OAAO,IAAI,CAAA;0CAC2B,GAAG,WAAW,GAAG,cAAc,IAAI,GAAG,IAAI;UAC1E,SAAS;YACT,CAAC,CAAC,IAAI,CAAA;oBACI,IAAI,CAAC,GAAI;;;uBAGN,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;eACrC;YACL,CAAC,CAAC,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;;KAEzD,CAAC;IACJ,CAAC;CACF,CAAA;AA5Ca;IAAX,QAAQ,EAAE;uCAA2B;AAE1B;IAAX,QAAQ,EAAE;wCAA4B;AAE3B;IAAX,QAAQ,EAAE;wCAA4B;AAEtB;IAAhB,KAAK,EAAE;6CAA2B;AAnCxB,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CAyEtB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { iconUserCircle } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Circular avatar. Shows `src` when it loads successfully; falls back to the\n * first letter of `name` (uppercased) if `src` is unset or fails to load\n * (e.g. an expired OAuth profile-photo URL); falls back further to a generic\n * person icon if `name` is also unset. A broken image never leaves a blank\n * circle.\n *\n * @element user-avatar\n */\n@customElement(\"user-avatar\")\nexport class UserAvatar extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n .avatar {\n border-radius: 50%;\n background: var(--ui-primary, #4f46e5);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-weight: 600;\n overflow: hidden;\n flex: 0 0 auto;\n }\n img {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n object-fit: cover;\n }\n `,\n ];\n\n /** Image URL to show. Falls back to initials/icon if unset or it fails to load. */\n @property() src: string | null = null;\n /** Source string for the fallback initial (e.g. a display name or email) — first character, uppercased. */\n @property() name: string | null = null;\n /** Diameter in CSS pixels, or a preset name (`xs`=18, `sm`=24, `md`=32, `lg`=48). */\n @property() size: string | number = 32;\n\n @state() private _imgError = false;\n\n private static readonly SIZE_PRESETS: Record<string, number> = {\n xs: 18,\n sm: 24,\n md: 32,\n lg: 48,\n };\n\n /** Resolves the `size` property (number or preset name) to a pixel diameter. */\n private resolveSize(): number {\n const numeric = Number(this.size);\n if (!Number.isNaN(numeric)) return numeric;\n return UserAvatar.SIZE_PRESETS[String(this.size)] ?? 32;\n }\n\n protected override willUpdate(changed: Map<string, unknown>) {\n if (changed.has(\"src\")) this._imgError = false;\n }\n\n override render() {\n const size = this.resolveSize();\n const dim = `${size}px`;\n const showImage = !!this.src && !this._imgError;\n const initial = this.name?.trim()[0]?.toUpperCase();\n return html`\n <span class=\"avatar\" style=\"width:${dim};height:${dim};font-size:${size * 0.42}px\">\n ${showImage\n ? html`<img\n src=${this.src!}\n alt=\"\"\n referrerpolicy=\"no-referrer\"\n @error=${() => (this._imgError = true)}\n />`\n : initial || iconUserCircle(Math.round(size * 0.65))}\n </span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"user-avatar\": UserAvatar;\n }\n}\n"]}
@@ -0,0 +1,12 @@
1
+ export type DurationFormat = "seconds" | "compact";
2
+ /**
3
+ * Formats a millisecond duration for display.
4
+ *
5
+ * `"seconds"` renders a whole-second count with a pluralized unit word (e.g.
6
+ * "1 second", "12 seconds"). `"compact"` renders the shortest sensible
7
+ * `Xh Ym Zs` form, dropping leading-zero units (`"12s"`, `"3m 12s"`,
8
+ * `"1h 03m 12s"`) — minutes/seconds are zero-padded only once a larger unit
9
+ * is present.
10
+ */
11
+ export declare function formatDuration(ms: number, format: DurationFormat): string;
12
+ //# sourceMappingURL=duration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../../src/utils/duration.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,SAAS,CAAC;AAMnD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAWzE"}
@@ -0,0 +1,27 @@
1
+ function pad2(n) {
2
+ return n < 10 ? `0${n}` : `${n}`;
3
+ }
4
+ /**
5
+ * Formats a millisecond duration for display.
6
+ *
7
+ * `"seconds"` renders a whole-second count with a pluralized unit word (e.g.
8
+ * "1 second", "12 seconds"). `"compact"` renders the shortest sensible
9
+ * `Xh Ym Zs` form, dropping leading-zero units (`"12s"`, `"3m 12s"`,
10
+ * `"1h 03m 12s"`) — minutes/seconds are zero-padded only once a larger unit
11
+ * is present.
12
+ */
13
+ export function formatDuration(ms, format) {
14
+ const totalSeconds = Math.max(0, Math.floor(ms / 1000));
15
+ if (format === "seconds") {
16
+ return totalSeconds === 1 ? "1 second" : `${totalSeconds} seconds`;
17
+ }
18
+ const hours = Math.floor(totalSeconds / 3600);
19
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
20
+ const seconds = totalSeconds % 60;
21
+ if (hours > 0)
22
+ return `${hours}h ${pad2(minutes)}m ${pad2(seconds)}s`;
23
+ if (minutes > 0)
24
+ return `${minutes}m ${pad2(seconds)}s`;
25
+ return `${seconds}s`;
26
+ }
27
+ //# sourceMappingURL=duration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.js","sourceRoot":"","sources":["../../src/utils/duration.ts"],"names":[],"mappings":"AAEA,SAAS,IAAI,CAAC,CAAS;IACrB,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;AACnC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,EAAU,EAAE,MAAsB;IAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;IACxD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,YAAY,UAAU,CAAC;IACrE,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;IAClC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,GAAG,KAAK,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;IACtE,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,GAAG,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC;IACxD,OAAO,GAAG,OAAO,GAAG,CAAC;AACvB,CAAC","sourcesContent":["export type DurationFormat = \"seconds\" | \"compact\";\n\nfunction pad2(n: number): string {\n return n < 10 ? `0${n}` : `${n}`;\n}\n\n/**\n * Formats a millisecond duration for display.\n *\n * `\"seconds\"` renders a whole-second count with a pluralized unit word (e.g.\n * \"1 second\", \"12 seconds\"). `\"compact\"` renders the shortest sensible\n * `Xh Ym Zs` form, dropping leading-zero units (`\"12s\"`, `\"3m 12s\"`,\n * `\"1h 03m 12s\"`) — minutes/seconds are zero-padded only once a larger unit\n * is present.\n */\nexport function formatDuration(ms: number, format: DurationFormat): string {\n const totalSeconds = Math.max(0, Math.floor(ms / 1000));\n if (format === \"seconds\") {\n return totalSeconds === 1 ? \"1 second\" : `${totalSeconds} seconds`;\n }\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) return `${hours}h ${pad2(minutes)}m ${pad2(seconds)}s`;\n if (minutes > 0) return `${minutes}m ${pad2(seconds)}s`;\n return `${seconds}s`;\n}\n"]}
@@ -0,0 +1,59 @@
1
+ # `<chat-message>`
2
+
3
+ One conversation entry in a chat-style activity feed. Tool calls and
4
+ "thinking" traces are variants of this component rather than separate
5
+ ones — they share the same header, collapse behavior, and body card as a
6
+ normal message, just dimmed and collapsible with an always-visible summary.
7
+
8
+ ## Install
9
+
10
+ ```js
11
+ import "@f-ewald/components/chat-message.js";
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```html
17
+ <chat-message role="user" author="Freddy" timestamp="2026-07-19T12:00:00Z">
18
+ Write notes.md containing a haiku.
19
+ </chat-message>
20
+ <chat-message role="agent" variant="tool" collapsible collapsed summary='file_write · {"filename": "notes.md"}'>
21
+ directory: .
22
+ filename: notes.md
23
+ </chat-message>
24
+ ```
25
+
26
+ ## Attributes / properties
27
+
28
+ | Property | Attribute | Type | Default | Description |
29
+ | --- | --- | --- | --- | --- |
30
+ | `role` | `role` | `ChatMessageRole` | `"agent"` | Whose message this is; drives the card background/border. |
31
+ | `variant` | `variant` | `ChatMessageVariant` | `"normal"` | `tool`/`thinking` render dimmed and smaller, with `tool` using monospace for its body. |
32
+ | `author` | `author` | `string` | `""` | Header label, e.g. "Freddy" or "Architect". |
33
+ | `timestamp` | `timestamp` | `string | null` | `null` | ISO-8601 timestamp, rendered via `relative-time` in the header. |
34
+ | `summary` | `summary` | `string` | `""` | Always-visible one-liner for `tool`/`thinking` variants (e.g. a truncated args preview). |
35
+ | `collapsible` | `collapsible` | `boolean` | `false` | Whether clicking the header/summary toggles the body slot. |
36
+ | `collapsed` | `collapsed` | `boolean` | `false` | Current collapse state (reflected as an attribute). |
37
+
38
+ ## Events
39
+
40
+ | Event | Description |
41
+ | --- | --- |
42
+ | `toggle` | Fired with `{ collapsed: boolean }` when the header/summary is clicked in collapsible mode. |
43
+
44
+ ## Slots
45
+
46
+ _None._
47
+
48
+ ## CSS custom properties
49
+
50
+ | Custom property |
51
+ | --- |
52
+ | `--ui-border` |
53
+ | `--ui-font` |
54
+ | `--ui-font-size` |
55
+ | `--ui-primary` |
56
+ | `--ui-radius` |
57
+ | `--ui-surface` |
58
+ | `--ui-text` |
59
+ | `--ui-text-muted` |
@@ -0,0 +1,63 @@
1
+ # `<data-table>`
2
+
3
+ A generic, presentational table shell: renders a `<thead>` from `columns`
4
+ and one `<tr>` per entry in `rows`, with each cell's content produced by
5
+ `renderCell` (default: plain property lookup on the row object). Knows
6
+ nothing about what a "row" means — callers own the data shape entirely.
7
+
8
+ Optional `rowHref` makes whole rows clickable (navigating via `location.hash`),
9
+ without hijacking clicks on nested interactive elements (links/buttons) a
10
+ cell's rendered content might contain.
11
+
12
+ ## Install
13
+
14
+ ```js
15
+ import "@f-ewald/components/data-table.js";
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```html
21
+ <data-table></data-table>
22
+ <script type="module">
23
+ const table = document.querySelector("data-table");
24
+ table.columns = [
25
+ { key: "title", label: "Title" },
26
+ { key: "state", label: "State" },
27
+ ];
28
+ table.rows = [
29
+ { id: "tsk_1", title: "Write onboarding docs", state: "Backlog" },
30
+ { id: "tsk_2", title: "Fix the login bug", state: "Done" },
31
+ ];
32
+ table.rowHref = (row) => `#/tasks/${row.id}`;
33
+ </script>
34
+ ```
35
+
36
+ ## Attributes / properties
37
+
38
+ | Property | Attribute | Type | Default | Description |
39
+ | --- | --- | --- | --- | --- |
40
+ | `columns` | _(JS property only)_ | `DataTableColumn[]` | `[]` | Column headers, in display order. |
41
+ | `rows` | _(JS property only)_ | `unknown[]` | `[]` | Row data; opaque to this component beyond what `renderCell` does with it. |
42
+ | `rowKey` | _(JS property only)_ | `(row: unknown, index: number) => string | number` | `—` | Stable identity for `rows[i]`, used as the repeat-directive key. Defaults to the row's index. |
43
+ | `renderCell` | _(JS property only)_ | `(row: unknown, key: string) => unknown` | `—` | Produces a cell's rendered content for `row`/`column.key`. Default: `row[key]`. |
44
+ | `rowHref` | _(JS property only)_ | `((row: unknown) => string | null) | null` | `null` | When set, clicking a row (outside any nested link/button) navigates to this hash. |
45
+
46
+ ## Events
47
+
48
+ _None._
49
+
50
+ ## Slots
51
+
52
+ _None._
53
+
54
+ ## CSS custom properties
55
+
56
+ | Custom property |
57
+ | --- |
58
+ | `--ui-border` |
59
+ | `--ui-font` |
60
+ | `--ui-font-size-sm` |
61
+ | `--ui-surface-muted` |
62
+ | `--ui-text` |
63
+ | `--ui-text-muted` |