@f-ewald/components 1.0.0 → 1.0.3

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 (172) hide show
  1. package/README.md +6 -0
  2. package/custom-elements.json +891 -22
  3. package/dist/address-autocomplete.d.ts +8 -0
  4. package/dist/address-autocomplete.d.ts.map +1 -1
  5. package/dist/address-autocomplete.js +165 -15
  6. package/dist/address-autocomplete.js.map +1 -1
  7. package/dist/animate-confetti.d.ts +8 -5
  8. package/dist/animate-confetti.d.ts.map +1 -1
  9. package/dist/animate-confetti.js +46 -19
  10. package/dist/animate-confetti.js.map +1 -1
  11. package/dist/autocomplete-input.d.ts +8 -0
  12. package/dist/autocomplete-input.d.ts.map +1 -1
  13. package/dist/autocomplete-input.js +165 -15
  14. package/dist/autocomplete-input.js.map +1 -1
  15. package/dist/calendar-month.d.ts.map +1 -1
  16. package/dist/calendar-month.js +27 -13
  17. package/dist/calendar-month.js.map +1 -1
  18. package/dist/calendar-year.d.ts.map +1 -1
  19. package/dist/calendar-year.js +11 -2
  20. package/dist/calendar-year.js.map +1 -1
  21. package/dist/chat-message.d.ts.map +1 -1
  22. package/dist/chat-message.js +77 -24
  23. package/dist/chat-message.js.map +1 -1
  24. package/dist/confirm-dialog.d.ts +8 -1
  25. package/dist/confirm-dialog.d.ts.map +1 -1
  26. package/dist/confirm-dialog.js +130 -18
  27. package/dist/confirm-dialog.js.map +1 -1
  28. package/dist/copy-link-button.d.ts +2 -0
  29. package/dist/copy-link-button.d.ts.map +1 -1
  30. package/dist/copy-link-button.js +35 -3
  31. package/dist/copy-link-button.js.map +1 -1
  32. package/dist/data-table.d.ts +2 -0
  33. package/dist/data-table.d.ts.map +1 -1
  34. package/dist/data-table.js +83 -11
  35. package/dist/data-table.js.map +1 -1
  36. package/dist/distance-value.d.ts +0 -1
  37. package/dist/distance-value.d.ts.map +1 -1
  38. package/dist/distance-value.js +3 -8
  39. package/dist/distance-value.js.map +1 -1
  40. package/dist/distribution-chart.d.ts.map +1 -1
  41. package/dist/distribution-chart.js +54 -17
  42. package/dist/distribution-chart.js.map +1 -1
  43. package/dist/dropdown-button.d.ts.map +1 -1
  44. package/dist/dropdown-button.js +60 -11
  45. package/dist/dropdown-button.js.map +1 -1
  46. package/dist/editable-text.d.ts.map +1 -1
  47. package/dist/editable-text.js +46 -13
  48. package/dist/editable-text.js.map +1 -1
  49. package/dist/form-select.d.ts.map +1 -1
  50. package/dist/form-select.js +70 -22
  51. package/dist/form-select.js.map +1 -1
  52. package/dist/frame-box.d.ts.map +1 -1
  53. package/dist/frame-box.js +20 -12
  54. package/dist/frame-box.js.map +1 -1
  55. package/dist/icon-button.d.ts.map +1 -1
  56. package/dist/icon-button.js +11 -0
  57. package/dist/icon-button.js.map +1 -1
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/kbd-hint.d.ts +30 -0
  63. package/dist/kbd-hint.d.ts.map +1 -0
  64. package/dist/kbd-hint.js +184 -0
  65. package/dist/kbd-hint.js.map +1 -0
  66. package/dist/live-timer.d.ts +0 -1
  67. package/dist/live-timer.d.ts.map +1 -1
  68. package/dist/live-timer.js +1 -6
  69. package/dist/live-timer.js.map +1 -1
  70. package/dist/map-circle.d.ts.map +1 -1
  71. package/dist/map-circle.js +16 -1
  72. package/dist/map-circle.js.map +1 -1
  73. package/dist/map-pin.d.ts.map +1 -1
  74. package/dist/map-pin.js +16 -1
  75. package/dist/map-pin.js.map +1 -1
  76. package/dist/percent-bar-chart.d.ts +3 -0
  77. package/dist/percent-bar-chart.d.ts.map +1 -1
  78. package/dist/percent-bar-chart.js +60 -4
  79. package/dist/percent-bar-chart.js.map +1 -1
  80. package/dist/photo-gallery.d.ts.map +1 -1
  81. package/dist/photo-gallery.js +12 -3
  82. package/dist/photo-gallery.js.map +1 -1
  83. package/dist/popover-panel.d.ts.map +1 -1
  84. package/dist/popover-panel.js +132 -23
  85. package/dist/popover-panel.js.map +1 -1
  86. package/dist/price-history-chart.d.ts.map +1 -1
  87. package/dist/price-history-chart.js +52 -18
  88. package/dist/price-history-chart.js.map +1 -1
  89. package/dist/radio-cards.d.ts +2 -0
  90. package/dist/radio-cards.d.ts.map +1 -1
  91. package/dist/radio-cards.js +51 -5
  92. package/dist/radio-cards.js.map +1 -1
  93. package/dist/radio-pills.d.ts +2 -0
  94. package/dist/radio-pills.d.ts.map +1 -1
  95. package/dist/radio-pills.js +46 -4
  96. package/dist/radio-pills.js.map +1 -1
  97. package/dist/relative-time.d.ts +0 -1
  98. package/dist/relative-time.d.ts.map +1 -1
  99. package/dist/relative-time.js +1 -6
  100. package/dist/relative-time.js.map +1 -1
  101. package/dist/reveal-button.d.ts +2 -0
  102. package/dist/reveal-button.d.ts.map +1 -1
  103. package/dist/reveal-button.js +42 -5
  104. package/dist/reveal-button.js.map +1 -1
  105. package/dist/roman-numeral.js +1 -1
  106. package/dist/roman-numeral.js.map +1 -1
  107. package/dist/slide-panel.d.ts +7 -3
  108. package/dist/slide-panel.d.ts.map +1 -1
  109. package/dist/slide-panel.js +95 -23
  110. package/dist/slide-panel.js.map +1 -1
  111. package/dist/stat-meter.d.ts.map +1 -1
  112. package/dist/stat-meter.js +17 -7
  113. package/dist/stat-meter.js.map +1 -1
  114. package/dist/status-pill.d.ts.map +1 -1
  115. package/dist/status-pill.js +26 -12
  116. package/dist/status-pill.js.map +1 -1
  117. package/dist/tile-grid.d.ts +8 -0
  118. package/dist/tile-grid.d.ts.map +1 -1
  119. package/dist/tile-grid.js +88 -12
  120. package/dist/tile-grid.js.map +1 -1
  121. package/dist/toast-notification.d.ts.map +1 -1
  122. package/dist/toast-notification.js +40 -11
  123. package/dist/toast-notification.js.map +1 -1
  124. package/dist/tokens.css +19 -0
  125. package/dist/tokens.d.ts.map +1 -1
  126. package/dist/tokens.js +13 -0
  127. package/dist/tokens.js.map +1 -1
  128. package/dist/ui-button.d.ts +2 -0
  129. package/dist/ui-button.d.ts.map +1 -1
  130. package/dist/ui-button.js +58 -9
  131. package/dist/ui-button.js.map +1 -1
  132. package/dist/user-avatar.d.ts.map +1 -1
  133. package/dist/user-avatar.js +18 -3
  134. package/dist/user-avatar.js.map +1 -1
  135. package/dist/utils/form.d.ts +3 -0
  136. package/dist/utils/form.d.ts.map +1 -0
  137. package/dist/utils/form.js +14 -0
  138. package/dist/utils/form.js.map +1 -0
  139. package/dist/utils/layer-stack.d.ts +9 -0
  140. package/dist/utils/layer-stack.d.ts.map +1 -0
  141. package/dist/utils/layer-stack.js +35 -0
  142. package/dist/utils/layer-stack.js.map +1 -0
  143. package/dist/weight-bar-chart.d.ts.map +1 -1
  144. package/dist/weight-bar-chart.js +27 -8
  145. package/dist/weight-bar-chart.js.map +1 -1
  146. package/docs/address-autocomplete.md +5 -0
  147. package/docs/autocomplete-input.md +5 -0
  148. package/docs/calendar-month.md +2 -0
  149. package/docs/chat-message.md +4 -0
  150. package/docs/confirm-dialog.md +2 -0
  151. package/docs/copy-link-button.md +2 -0
  152. package/docs/data-table.md +2 -0
  153. package/docs/design-language.md +149 -0
  154. package/docs/distribution-chart.md +4 -0
  155. package/docs/dropdown-button.md +1 -0
  156. package/docs/frame-box.md +2 -0
  157. package/docs/kbd-hint.md +42 -0
  158. package/docs/percent-bar-chart.md +4 -1
  159. package/docs/photo-gallery.md +1 -0
  160. package/docs/popover-panel.md +2 -0
  161. package/docs/price-history-chart.md +8 -1
  162. package/docs/radio-cards.md +2 -0
  163. package/docs/radio-pills.md +2 -0
  164. package/docs/reveal-button.md +3 -0
  165. package/docs/slide-panel.md +4 -2
  166. package/docs/status-pill.md +3 -0
  167. package/docs/tile-grid.md +10 -1
  168. package/docs/toast-notification.md +4 -1
  169. package/docs/ui-button.md +2 -0
  170. package/docs/user-avatar.md +1 -0
  171. package/llms.txt +59 -34
  172. package/package.json +5 -1
package/dist/ui-button.js CHANGED
@@ -52,12 +52,21 @@ let UiButton = class UiButton extends LitElement {
52
52
  .btn {
53
53
  display: inline-flex;
54
54
  align-items: center;
55
- gap: 0.35rem;
55
+ gap: 0.25rem;
56
56
  border-radius: var(--ui-radius-sm, 0.25rem);
57
- padding: 0.5rem 0.9rem;
57
+ padding: 0.5rem 1rem;
58
58
  font-size: var(--ui-font-size-sm, 0.75rem);
59
59
  font-weight: 500;
60
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
60
+ font-family: var(
61
+ --ui-font,
62
+ ui-sans-serif,
63
+ system-ui,
64
+ sans-serif,
65
+ "Apple Color Emoji",
66
+ "Segoe UI Emoji",
67
+ "Segoe UI Symbol",
68
+ "Noto Color Emoji"
69
+ );
61
70
  cursor: pointer;
62
71
  border: 1px solid transparent;
63
72
  text-decoration: none;
@@ -65,7 +74,7 @@ let UiButton = class UiButton extends LitElement {
65
74
  }
66
75
  .btn.primary {
67
76
  background: var(--ui-primary, #4f46e5);
68
- color: #fff;
77
+ color: var(--ui-on-accent, #ffffff);
69
78
  }
70
79
  .btn.primary:hover:not(:disabled) {
71
80
  background: var(--ui-primary-hover, #4338ca);
@@ -80,7 +89,7 @@ let UiButton = class UiButton extends LitElement {
80
89
  }
81
90
  .btn.danger {
82
91
  background: var(--ui-danger, #dc2626);
83
- color: #fff;
92
+ color: var(--ui-on-accent, #ffffff);
84
93
  }
85
94
  .btn.danger:hover:not(:disabled) {
86
95
  background: var(--ui-danger-hover, #b91c1c);
@@ -91,6 +100,10 @@ let UiButton = class UiButton extends LitElement {
91
100
  cursor: default;
92
101
  pointer-events: none;
93
102
  }
103
+ .btn:focus-visible {
104
+ outline: none;
105
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
106
+ }
94
107
  .spin {
95
108
  display: inline-flex;
96
109
  animation: spin 0.8s linear infinite;
@@ -103,6 +116,24 @@ let UiButton = class UiButton extends LitElement {
103
116
  transform: rotate(360deg);
104
117
  }
105
118
  }
119
+ @media (prefers-reduced-motion: reduce) {
120
+ .spin {
121
+ animation: none;
122
+ }
123
+ }
124
+ @media (forced-colors: active) {
125
+ .btn:focus-visible {
126
+ outline: 2px solid CanvasText;
127
+ outline-offset: 2px;
128
+ box-shadow: none;
129
+ }
130
+ .btn:disabled,
131
+ .btn[aria-disabled="true"] {
132
+ color: GrayText;
133
+ border-color: GrayText;
134
+ opacity: 1;
135
+ }
136
+ }
106
137
  `,
107
138
  ]; }
108
139
  /** Drives submit/reset on the ancestor form via ElementInternals, since a shadow-DOM button can't do it natively. */
@@ -112,21 +143,39 @@ let UiButton = class UiButton extends LitElement {
112
143
  else if (this.type === "reset")
113
144
  this.#internals.form?.reset();
114
145
  }
146
+ /** Suppresses navigation while a link-styled button is disabled or busy. */
147
+ _onLinkClick(e) {
148
+ if (!this.disabled && !this.busy)
149
+ return;
150
+ e.preventDefault();
151
+ }
115
152
  render() {
116
153
  const classes = `btn ${this.variant}`;
117
154
  const isDisabled = this.disabled || this.busy;
118
155
  if (this.href) {
119
156
  return html `
120
- <a class=${classes} href=${this.href} aria-disabled=${isDisabled ? "true" : "false"}>
121
- <span class="spin" ?hidden=${!this.busy}>${iconArrowPath(14)}</span>
157
+ <a
158
+ class=${classes}
159
+ href=${this.href}
160
+ aria-disabled=${isDisabled ? "true" : "false"}
161
+ aria-busy=${this.busy ? "true" : "false"}
162
+ @click=${this._onLinkClick}
163
+ >
164
+ <span class="spin" aria-hidden="true" ?hidden=${!this.busy}>${iconArrowPath(14)}</span>
122
165
  <slot name="icon" ?hidden=${this.busy}></slot>
123
166
  <slot></slot>
124
167
  </a>
125
168
  `;
126
169
  }
127
170
  return html `
128
- <button class=${classes} type="button" ?disabled=${isDisabled} @click=${this._onClick}>
129
- <span class="spin" ?hidden=${!this.busy}>${iconArrowPath(14)}</span>
171
+ <button
172
+ class=${classes}
173
+ type="button"
174
+ ?disabled=${isDisabled}
175
+ aria-busy=${this.busy ? "true" : "false"}
176
+ @click=${this._onClick}
177
+ >
178
+ <span class="spin" aria-hidden="true" ?hidden=${!this.busy}>${iconArrowPath(14)}</span>
130
179
  <slot name="icon" ?hidden=${this.busy}></slot>
131
180
  <slot></slot>
132
181
  </button>
@@ -1 +1 @@
1
- {"version":3,"file":"ui-button.js","sourceRoot":"","sources":["../src/ui-button.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,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIrC;;;;;;;;;;;;;;;;;GAiBG;AAEI,IAAM,QAAQ,GAAd,MAAM,QAAS,SAAQ,UAAU;IAAjC;;QAGL,eAAU,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QAgEpC,qBAAqB;QACT,YAAO,GAAkB,SAAS,CAAC;QAC/C,oEAAoE;QACxD,SAAI,GAAkB,IAAI,CAAC;QACvC,wDAAwD;QAC5C,SAAI,GAAkC,QAAQ,CAAC;QAC3D,wCAAwC;QACX,aAAQ,GAAG,KAAK,CAAC;QAC9C,0EAA0E;QAC7C,SAAI,GAAG,KAAK,CAAC;IA4B5C,CAAC;aAvGQ,mBAAc,GAAG,IAAI,AAAP,CAAQ;IAE7B,UAAU,CAA0B;aACpB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA0DF;KACF,AA7DqB,CA6DpB;IAaF,qHAAqH;IAC7G,QAAQ;QACd,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,CAAC;aAC7D,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAChE,CAAC;IAEQ,MAAM;QACb,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QACtC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC;QAC9C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,OAAO,IAAI,CAAA;mBACE,OAAO,SAAS,IAAI,CAAC,IAAI,kBAAkB,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;uCACpD,CAAC,IAAI,CAAC,IAAI,IAAI,aAAa,CAAC,EAAE,CAAC;sCAChC,IAAI,CAAC,IAAI;;;OAGxC,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAA;sBACO,OAAO,4BAA4B,UAAU,WAAW,IAAI,CAAC,QAAQ;qCACtD,CAAC,IAAI,CAAC,IAAI,IAAI,aAAa,CAAC,EAAE,CAAC;oCAChC,IAAI,CAAC,IAAI;;;KAGxC,CAAC;IACJ,CAAC;CACF,CAAA;AApCa;IAAX,QAAQ,EAAE;yCAAoC;AAEnC;IAAX,QAAQ,EAAE;sCAA4B;AAE3B;IAAX,QAAQ,EAAE;sCAAgD;AAE9B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;0CAAkB;AAEjB;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;sCAAc;AA5E/B,QAAQ;IADpB,aAAa,CAAC,WAAW,CAAC;GACd,QAAQ,CAwGpB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconArrowPath } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\nexport type ButtonVariant = \"primary\" | \"secondary\" | \"danger\";\n\n/**\n * Button (or link styled as one) with an optional leading icon, in three\n * visual weights. Set `href` to render an `<a>` instead of a `<button>` —\n * same styling either way — for cross-page navigation that should look like\n * an action button; a disabled/busy link stays a real `<a>` with\n * `aria-disabled` + `pointer-events: none` rather than losing its href.\n * Put the icon in the `icon` slot and the label in the default slot.\n *\n * Form-associated (`type=\"submit\"`/`\"reset\"`): the actual `<button>` lives in\n * this element's shadow root, which native HTML form association does not\n * cross into from an ancestor light-DOM `<form>`. `type=\"submit\"`/`\"reset\"`\n * is instead wired through `ElementInternals.form` — the same mechanism\n * `address-autocomplete` uses to associate with an ancestor form.\n *\n * @element ui-button\n * @slot icon - Optional leading icon (e.g. an inline SVG).\n * @slot - Button label.\n */\n@customElement(\"ui-button\")\nexport class UiButton extends LitElement {\n static formAssociated = true;\n\n #internals = this.attachInternals();\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n .btn {\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 0.9rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 500;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n cursor: pointer;\n border: 1px solid transparent;\n text-decoration: none;\n box-sizing: border-box;\n }\n .btn.primary {\n background: var(--ui-primary, #4f46e5);\n color: #fff;\n }\n .btn.primary:hover:not(:disabled) {\n background: var(--ui-primary-hover, #4338ca);\n }\n .btn.secondary {\n background: none;\n border-color: var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\n }\n .btn.secondary:hover:not(:disabled) {\n border-color: var(--ui-text-muted, #64748b);\n }\n .btn.danger {\n background: var(--ui-danger, #dc2626);\n color: #fff;\n }\n .btn.danger:hover:not(:disabled) {\n background: var(--ui-danger-hover, #b91c1c);\n }\n .btn:disabled,\n .btn[aria-disabled=\"true\"] {\n opacity: 0.6;\n cursor: default;\n pointer-events: none;\n }\n .spin {\n display: inline-flex;\n animation: spin 0.8s linear infinite;\n }\n .spin[hidden] {\n display: none;\n }\n @keyframes spin {\n to {\n transform: rotate(360deg);\n }\n }\n `,\n ];\n\n /** Visual weight. */\n @property() variant: ButtonVariant = \"primary\";\n /** Renders an `<a href=\"...\">` instead of a `<button>` when set. */\n @property() href: string | null = null;\n /** Native button `type`. Ignored when `href` is set. */\n @property() type: \"button\" | \"submit\" | \"reset\" = \"button\";\n /** Disables the control and dims it. */\n @property({ type: Boolean }) disabled = false;\n /** Shows a spinner in place of the icon slot and disables the control. */\n @property({ type: Boolean }) busy = false;\n\n /** Drives submit/reset on the ancestor form via ElementInternals, since a shadow-DOM button can't do it natively. */\n private _onClick() {\n if (this.type === \"submit\") this.#internals.form?.requestSubmit();\n else if (this.type === \"reset\") this.#internals.form?.reset();\n }\n\n override render() {\n const classes = `btn ${this.variant}`;\n const isDisabled = this.disabled || this.busy;\n if (this.href) {\n return html`\n <a class=${classes} href=${this.href} aria-disabled=${isDisabled ? \"true\" : \"false\"}>\n <span class=\"spin\" ?hidden=${!this.busy}>${iconArrowPath(14)}</span>\n <slot name=\"icon\" ?hidden=${this.busy}></slot>\n <slot></slot>\n </a>\n `;\n }\n return html`\n <button class=${classes} type=\"button\" ?disabled=${isDisabled} @click=${this._onClick}>\n <span class=\"spin\" ?hidden=${!this.busy}>${iconArrowPath(14)}</span>\n <slot name=\"icon\" ?hidden=${this.busy}></slot>\n <slot></slot>\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ui-button\": UiButton;\n }\n}\n"]}
1
+ {"version":3,"file":"ui-button.js","sourceRoot":"","sources":["../src/ui-button.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,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAIrC;;;;;;;;;;;;;;;;;GAiBG;AAEI,IAAM,QAAQ,GAAd,MAAM,QAAS,SAAQ,UAAU;IAAjC;;QAGL,eAAU,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QA+FpC,qBAAqB;QACT,YAAO,GAAkB,SAAS,CAAC;QAC/C,oEAAoE;QACxD,SAAI,GAAkB,IAAI,CAAC;QACvC,wDAAwD;QAC5C,SAAI,GAAkC,QAAQ,CAAC;QAC3D,wCAAwC;QACX,aAAQ,GAAG,KAAK,CAAC;QAC9C,0EAA0E;QAC7C,SAAI,GAAG,KAAK,CAAC;IA8C5C,CAAC;aAxJQ,mBAAc,GAAG,IAAI,AAAP,CAAQ;IAE7B,UAAU,CAA0B;aACpB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAyFF;KACF,AA5FqB,CA4FpB;IAaF,qHAAqH;IAC7G,QAAQ;QACd,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,CAAC;aAC7D,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAChE,CAAC;IAED,4EAA4E;IACpE,YAAY,CAAC,CAAa;QAChC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO;QACzC,CAAC,CAAC,cAAc,EAAE,CAAC;IACrB,CAAC;IAEQ,MAAM;QACb,MAAM,OAAO,GAAG,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QACtC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC;QAC9C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,OAAO,IAAI,CAAA;;kBAEC,OAAO;iBACR,IAAI,CAAC,IAAI;0BACA,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;sBACjC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;mBAC/B,IAAI,CAAC,YAAY;;0DAEsB,CAAC,IAAI,CAAC,IAAI,IAAI,aAAa,CAAC,EAAE,CAAC;sCACnD,IAAI,CAAC,IAAI;;;OAGxC,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAA;;gBAEC,OAAO;;oBAEH,UAAU;oBACV,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;iBAC/B,IAAI,CAAC,QAAQ;;wDAE0B,CAAC,IAAI,CAAC,IAAI,IAAI,aAAa,CAAC,EAAE,CAAC;oCACnD,IAAI,CAAC,IAAI;;;KAGxC,CAAC;IACJ,CAAC;CACF,CAAA;AAtDa;IAAX,QAAQ,EAAE;yCAAoC;AAEnC;IAAX,QAAQ,EAAE;sCAA4B;AAE3B;IAAX,QAAQ,EAAE;sCAAgD;AAE9B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;0CAAkB;AAEjB;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;sCAAc;AA3G/B,QAAQ;IADpB,aAAa,CAAC,WAAW,CAAC;GACd,QAAQ,CAyJpB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconArrowPath } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\nexport type ButtonVariant = \"primary\" | \"secondary\" | \"danger\";\n\n/**\n * Button (or link styled as one) with an optional leading icon, in three\n * visual weights. Set `href` to render an `<a>` instead of a `<button>` —\n * same styling either way — for cross-page navigation that should look like\n * an action button; a disabled/busy link stays a real `<a>` with\n * `aria-disabled` + `pointer-events: none` rather than losing its href.\n * Put the icon in the `icon` slot and the label in the default slot.\n *\n * Form-associated (`type=\"submit\"`/`\"reset\"`): the actual `<button>` lives in\n * this element's shadow root, which native HTML form association does not\n * cross into from an ancestor light-DOM `<form>`. `type=\"submit\"`/`\"reset\"`\n * is instead wired through `ElementInternals.form` — the same mechanism\n * `address-autocomplete` uses to associate with an ancestor form.\n *\n * @element ui-button\n * @slot icon - Optional leading icon (e.g. an inline SVG).\n * @slot - Button label.\n */\n@customElement(\"ui-button\")\nexport class UiButton extends LitElement {\n static formAssociated = true;\n\n #internals = this.attachInternals();\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n .btn {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 1rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 500;\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 cursor: pointer;\n border: 1px solid transparent;\n text-decoration: none;\n box-sizing: border-box;\n }\n .btn.primary {\n background: var(--ui-primary, #4f46e5);\n color: var(--ui-on-accent, #ffffff);\n }\n .btn.primary:hover:not(:disabled) {\n background: var(--ui-primary-hover, #4338ca);\n }\n .btn.secondary {\n background: none;\n border-color: var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\n }\n .btn.secondary:hover:not(:disabled) {\n border-color: var(--ui-text-muted, #64748b);\n }\n .btn.danger {\n background: var(--ui-danger, #dc2626);\n color: var(--ui-on-accent, #ffffff);\n }\n .btn.danger:hover:not(:disabled) {\n background: var(--ui-danger-hover, #b91c1c);\n }\n .btn:disabled,\n .btn[aria-disabled=\"true\"] {\n opacity: 0.6;\n cursor: default;\n pointer-events: none;\n }\n .btn:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .spin {\n display: inline-flex;\n animation: spin 0.8s linear infinite;\n }\n .spin[hidden] {\n display: none;\n }\n @keyframes spin {\n to {\n transform: rotate(360deg);\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .spin {\n animation: none;\n }\n }\n @media (forced-colors: active) {\n .btn:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n .btn:disabled,\n .btn[aria-disabled=\"true\"] {\n color: GrayText;\n border-color: GrayText;\n opacity: 1;\n }\n }\n `,\n ];\n\n /** Visual weight. */\n @property() variant: ButtonVariant = \"primary\";\n /** Renders an `<a href=\"...\">` instead of a `<button>` when set. */\n @property() href: string | null = null;\n /** Native button `type`. Ignored when `href` is set. */\n @property() type: \"button\" | \"submit\" | \"reset\" = \"button\";\n /** Disables the control and dims it. */\n @property({ type: Boolean }) disabled = false;\n /** Shows a spinner in place of the icon slot and disables the control. */\n @property({ type: Boolean }) busy = false;\n\n /** Drives submit/reset on the ancestor form via ElementInternals, since a shadow-DOM button can't do it natively. */\n private _onClick() {\n if (this.type === \"submit\") this.#internals.form?.requestSubmit();\n else if (this.type === \"reset\") this.#internals.form?.reset();\n }\n\n /** Suppresses navigation while a link-styled button is disabled or busy. */\n private _onLinkClick(e: MouseEvent) {\n if (!this.disabled && !this.busy) return;\n e.preventDefault();\n }\n\n override render() {\n const classes = `btn ${this.variant}`;\n const isDisabled = this.disabled || this.busy;\n if (this.href) {\n return html`\n <a\n class=${classes}\n href=${this.href}\n aria-disabled=${isDisabled ? \"true\" : \"false\"}\n aria-busy=${this.busy ? \"true\" : \"false\"}\n @click=${this._onLinkClick}\n >\n <span class=\"spin\" aria-hidden=\"true\" ?hidden=${!this.busy}>${iconArrowPath(14)}</span>\n <slot name=\"icon\" ?hidden=${this.busy}></slot>\n <slot></slot>\n </a>\n `;\n }\n return html`\n <button\n class=${classes}\n type=\"button\"\n ?disabled=${isDisabled}\n aria-busy=${this.busy ? \"true\" : \"false\"}\n @click=${this._onClick}\n >\n <span class=\"spin\" aria-hidden=\"true\" ?hidden=${!this.busy}>${iconArrowPath(14)}</span>\n <slot name=\"icon\" ?hidden=${this.busy}></slot>\n <slot></slot>\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ui-button\": UiButton;\n }\n}\n"]}
@@ -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,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"}
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,4BAwCpB;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"}
@@ -38,12 +38,27 @@ let UserAvatar = class UserAvatar extends LitElement {
38
38
  }
39
39
  .avatar {
40
40
  border-radius: 50%;
41
- background: var(--ui-primary, #4f46e5);
42
- color: #fff;
41
+ /* Match map-circle's 30% white/black vertical depth without tinting images. */
42
+ background: linear-gradient(
43
+ to bottom,
44
+ color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #ffffff) 0%,
45
+ color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #000000) 100%
46
+ );
47
+ color: var(--ui-on-accent, #ffffff);
48
+ text-shadow: 0 1px 2px rgb(0 0 0 / 0.35);
43
49
  display: flex;
44
50
  align-items: center;
45
51
  justify-content: center;
46
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
52
+ font-family: var(
53
+ --ui-font,
54
+ ui-sans-serif,
55
+ system-ui,
56
+ sans-serif,
57
+ "Apple Color Emoji",
58
+ "Segoe UI Emoji",
59
+ "Segoe UI Symbol",
60
+ "Noto Color Emoji"
61
+ );
47
62
  font-weight: 600;
48
63
  overflow: hidden;
49
64
  flex: 0 0 auto;
@@ -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,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"]}
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;;QA2CL,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;;aAvFiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAqCF;KACF,AAxCqB,CAwCpB;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;AAlDxB,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CAwFtB","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 /* Match map-circle's 30% white/black vertical depth without tinting images. */\n background: linear-gradient(\n to bottom,\n color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #ffffff) 0%,\n color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #000000) 100%\n );\n color: var(--ui-on-accent, #ffffff);\n text-shadow: 0 1px 2px rgb(0 0 0 / 0.35);\n display: flex;\n align-items: center;\n justify-content: center;\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 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,3 @@
1
+ /** Submits a form through its default enabled native submit control when present. */
2
+ export declare function submitWithDefaultButton(form: HTMLFormElement): void;
3
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/utils/form.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAWnE"}
@@ -0,0 +1,14 @@
1
+ /** Submits a form through its default enabled native submit control when present. */
2
+ export function submitWithDefaultButton(form) {
3
+ const submitter = Array.from(form.elements).find((element) => (element instanceof HTMLButtonElement ||
4
+ element instanceof HTMLInputElement) &&
5
+ (element.type === "submit" ||
6
+ (element instanceof HTMLInputElement && element.type === "image")));
7
+ if (submitter?.matches(":disabled"))
8
+ return;
9
+ if (submitter)
10
+ submitter.click();
11
+ else
12
+ form.requestSubmit();
13
+ }
14
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/utils/form.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,UAAU,uBAAuB,CAAC,IAAqB;IAC3D,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAC9C,CAAC,OAAO,EAAmD,EAAE,CAC3D,CAAC,OAAO,YAAY,iBAAiB;QACnC,OAAO,YAAY,gBAAgB,CAAC;QACtC,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ;YACxB,CAAC,OAAO,YAAY,gBAAgB,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CACvE,CAAC;IACF,IAAI,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO;IAC5C,IAAI,SAAS;QAAE,SAAS,CAAC,KAAK,EAAE,CAAC;;QAC5B,IAAI,CAAC,aAAa,EAAE,CAAC;AAC5B,CAAC","sourcesContent":["/** Submits a form through its default enabled native submit control when present. */\nexport function submitWithDefaultButton(form: HTMLFormElement): void {\n const submitter = Array.from(form.elements).find(\n (element): element is HTMLButtonElement | HTMLInputElement =>\n (element instanceof HTMLButtonElement ||\n element instanceof HTMLInputElement) &&\n (element.type === \"submit\" ||\n (element instanceof HTMLInputElement && element.type === \"image\")),\n );\n if (submitter?.matches(\":disabled\")) return;\n if (submitter) submitter.click();\n else form.requestSubmit();\n}\n"]}
@@ -0,0 +1,9 @@
1
+ /** Registers an open overlay as the current topmost interactive layer. */
2
+ export declare function activateLayer(layer: HTMLElement): void;
3
+ /** Removes a closed or disconnected overlay from the layer stack. */
4
+ export declare function deactivateLayer(layer: HTMLElement): void;
5
+ /** Whether this overlay currently owns global Escape/focus behavior. */
6
+ export declare function isTopLayer(layer: HTMLElement): boolean;
7
+ /** Claims Escape for the topmost layer and marks it handled for other listeners. */
8
+ export declare function claimEscape(layer: HTMLElement, event: KeyboardEvent): boolean;
9
+ //# sourceMappingURL=layer-stack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer-stack.d.ts","sourceRoot":"","sources":["../../src/utils/layer-stack.ts"],"names":[],"mappings":"AAUA,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAItD;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAKxD;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAEtD;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAM7E"}
@@ -0,0 +1,35 @@
1
+ const layers = [];
2
+ const baseZIndex = 100;
3
+ /** Keeps visual z-order synchronized with interaction-stack order. */
4
+ function syncLayerOrder() {
5
+ layers.forEach((layer, index) => {
6
+ layer.style.setProperty("--component-layer-z", String(baseZIndex + index));
7
+ });
8
+ }
9
+ /** Registers an open overlay as the current topmost interactive layer. */
10
+ export function activateLayer(layer) {
11
+ deactivateLayer(layer);
12
+ layers.push(layer);
13
+ syncLayerOrder();
14
+ }
15
+ /** Removes a closed or disconnected overlay from the layer stack. */
16
+ export function deactivateLayer(layer) {
17
+ const index = layers.indexOf(layer);
18
+ if (index >= 0)
19
+ layers.splice(index, 1);
20
+ layer.style.removeProperty("--component-layer-z");
21
+ syncLayerOrder();
22
+ }
23
+ /** Whether this overlay currently owns global Escape/focus behavior. */
24
+ export function isTopLayer(layer) {
25
+ return layers.at(-1) === layer;
26
+ }
27
+ /** Claims Escape for the topmost layer and marks it handled for other listeners. */
28
+ export function claimEscape(layer, event) {
29
+ if (event.key !== "Escape" || event.defaultPrevented || !isTopLayer(layer)) {
30
+ return false;
31
+ }
32
+ event.preventDefault();
33
+ return true;
34
+ }
35
+ //# sourceMappingURL=layer-stack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layer-stack.js","sourceRoot":"","sources":["../../src/utils/layer-stack.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAkB,EAAE,CAAC;AACjC,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,sEAAsE;AACtE,SAAS,cAAc;IACrB,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC9B,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,qBAAqB,EAAE,MAAM,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;AACL,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAAC,KAAkB;IAC9C,eAAe,CAAC,KAAK,CAAC,CAAC;IACvB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnB,cAAc,EAAE,CAAC;AACnB,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,eAAe,CAAC,KAAkB;IAChD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,KAAK,IAAI,CAAC;QAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACxC,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,qBAAqB,CAAC,CAAC;IAClD,cAAc,EAAE,CAAC;AACnB,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,KAAkB;IAC3C,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC;AACjC,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,WAAW,CAAC,KAAkB,EAAE,KAAoB;IAClE,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3E,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,CAAC,cAAc,EAAE,CAAC;IACvB,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["const layers: HTMLElement[] = [];\nconst baseZIndex = 100;\n\n/** Keeps visual z-order synchronized with interaction-stack order. */\nfunction syncLayerOrder(): void {\n layers.forEach((layer, index) => {\n layer.style.setProperty(\"--component-layer-z\", String(baseZIndex + index));\n });\n}\n\n/** Registers an open overlay as the current topmost interactive layer. */\nexport function activateLayer(layer: HTMLElement): void {\n deactivateLayer(layer);\n layers.push(layer);\n syncLayerOrder();\n}\n\n/** Removes a closed or disconnected overlay from the layer stack. */\nexport function deactivateLayer(layer: HTMLElement): void {\n const index = layers.indexOf(layer);\n if (index >= 0) layers.splice(index, 1);\n layer.style.removeProperty(\"--component-layer-z\");\n syncLayerOrder();\n}\n\n/** Whether this overlay currently owns global Escape/focus behavior. */\nexport function isTopLayer(layer: HTMLElement): boolean {\n return layers.at(-1) === layer;\n}\n\n/** Claims Escape for the topmost layer and marks it handled for other listeners. */\nexport function claimEscape(layer: HTMLElement, event: KeyboardEvent): boolean {\n if (event.key !== \"Escape\" || event.defaultPrevented || !isTopLayer(layer)) {\n return false;\n }\n event.preventDefault();\n return true;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"weight-bar-chart.d.ts","sourceRoot":"","sources":["../src/weight-bar-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,qBACa,cAAe,SAAQ,UAAU;IAC5C,oEAAoE;IACpC,KAAK,EAAE,aAAa,EAAE,CAAM;IAE5D,OAAgB,MAAM,4BAuCpB;IAEO,MAAM,gDAuBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
1
+ {"version":3,"file":"weight-bar-chart.d.ts","sourceRoot":"","sources":["../src/weight-bar-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,qBACa,cAAe,SAAQ,UAAU;IAC5C,oEAAoE;IACpC,KAAK,EAAE,aAAa,EAAE,CAAM;IAE5D,OAAgB,MAAM,4BA0DpB;IAEO,MAAM,gDAuBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
@@ -33,35 +33,54 @@ let WeightBarChart = class WeightBarChart extends LitElement {
33
33
  .row {
34
34
  display: flex;
35
35
  align-items: center;
36
- gap: 8px;
37
- margin: 6px 0;
36
+ gap: 0.5rem;
37
+ margin: 0.5rem 0;
38
38
  }
39
39
  .label {
40
- flex: 0 0 90px;
40
+ flex: 0 0 5.5rem;
41
41
  font-size: var(--ui-font-size-sm, 0.75rem);
42
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
42
+ font-family: var(
43
+ --ui-font,
44
+ ui-sans-serif,
45
+ system-ui,
46
+ sans-serif,
47
+ "Apple Color Emoji",
48
+ "Segoe UI Emoji",
49
+ "Segoe UI Symbol",
50
+ "Noto Color Emoji"
51
+ );
43
52
  color: var(--ui-text, #0f172a);
44
53
  }
45
54
  .track {
46
55
  flex: 1 1 auto;
47
- height: 12px;
56
+ height: 0.75rem;
48
57
  background: var(--ui-surface-muted, #f8fafc);
49
58
  border-radius: var(--ui-radius-sm, 0.25rem);
50
59
  overflow: hidden;
51
60
  }
52
61
  .bar {
53
62
  height: 100%;
54
- background: var(--ui-primary, #4f46e5);
63
+ /* Match map-circle's 30% white/black vertical depth while preserving token overrides. */
64
+ background: linear-gradient(
65
+ to bottom,
66
+ color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #ffffff) 0%,
67
+ color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #000000) 100%
68
+ );
55
69
  border-radius: 0 var(--ui-radius-sm, 0.25rem) var(--ui-radius-sm, 0.25rem) 0;
56
- transition: width 0.15s ease-out;
70
+ transition: width 150ms ease-out;
57
71
  }
58
72
  .pct {
59
- flex: 0 0 40px;
73
+ flex: 0 0 2.5rem;
60
74
  text-align: right;
61
75
  font-size: var(--ui-font-size-sm, 0.75rem);
62
76
  font-variant-numeric: tabular-nums;
63
77
  color: var(--ui-text-muted, #64748b);
64
78
  }
79
+ @media (prefers-reduced-motion: reduce) {
80
+ .bar {
81
+ transition: none;
82
+ }
83
+ }
65
84
  `,
66
85
  ]; }
67
86
  render() {
@@ -1 +1 @@
1
- {"version":3,"file":"weight-bar-chart.js","sourceRoot":"","sources":["../src/weight-bar-chart.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;AASrC;;;;;;;;;GASG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QACL,oEAAoE;QACpC,UAAK,GAAoB,EAAE,CAAC;IAmE9D,CAAC;aAjEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoCF;KACF,AAvCqB,CAuCpB;IAEO,MAAM;QACb,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,+EAA+E;QAC/E,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAChE,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClG,OAAO,IAAI,CAAA;6CAC8B,OAAO;UAC1C,MAAM,CACN,MAAM,EACN,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,EACjB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAA;;oCAEY,IAAI,CAAC,KAAK;;iDAEG,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG;;kCAEvC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;;WAEnD,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AAnEiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;6CAA6B;AAFjD,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CAqE1B","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\nexport interface WeightBarItem {\n id: string;\n label: string;\n /** Normalized fraction (fractions across all items sum to ~1). */\n value: number;\n}\n\n/**\n * Sorted horizontal bar chart of labeled weights (normalized fractions\n * summing to ~1). Bars sort descending — the order IS the priority ranking.\n * Widths scale relative to the largest weight (which fills its track); the\n * percent labels carry the absolute values. Rows are keyed by item id\n * (repeat directive) so a re-render with new weights moves the existing\n * rows; bar widths animate via CSS, re-sorting is instant.\n *\n * @element weight-bar-chart\n */\n@customElement(\"weight-bar-chart\")\nexport class WeightBarChart extends LitElement {\n /** Items to render as weighted rows, sorted descending by value. */\n @property({ attribute: false }) items: WeightBarItem[] = [];\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .row {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 6px 0;\n }\n .label {\n flex: 0 0 90px;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n color: var(--ui-text, #0f172a);\n }\n .track {\n flex: 1 1 auto;\n height: 12px;\n background: var(--ui-surface-muted, #f8fafc);\n border-radius: var(--ui-radius-sm, 0.25rem);\n overflow: hidden;\n }\n .bar {\n height: 100%;\n background: var(--ui-primary, #4f46e5);\n border-radius: 0 var(--ui-radius-sm, 0.25rem) var(--ui-radius-sm, 0.25rem) 0;\n transition: width 0.15s ease-out;\n }\n .pct {\n flex: 0 0 40px;\n text-align: right;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-variant-numeric: tabular-nums;\n color: var(--ui-text-muted, #64748b);\n }\n `,\n ];\n\n override render() {\n if (this.items.length === 0) return null;\n // Stable sort: ties keep the incoming item order, so the chart never flickers.\n const sorted = [...this.items].sort((a, b) => b.value - a.value);\n const max = Math.max(0.01, ...sorted.map((item) => item.value));\n const summary = sorted.map((item) => `${item.label} ${Math.round(item.value * 100)}%`).join(\", \");\n return html`\n <div role=\"img\" aria-label=\"Weights: ${summary}\">\n ${repeat(\n sorted,\n (item) => item.id,\n (item) => html`\n <div class=\"row\">\n <span class=\"label\">${item.label}</span>\n <div class=\"track\">\n <div class=\"bar\" style=\"width: ${(item.value / max) * 100}%\"></div>\n </div>\n <span class=\"pct\">${Math.round(item.value * 100)}%</span>\n </div>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"weight-bar-chart\": WeightBarChart;\n }\n}\n"]}
1
+ {"version":3,"file":"weight-bar-chart.js","sourceRoot":"","sources":["../src/weight-bar-chart.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;AASrC;;;;;;;;;GASG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QACL,oEAAoE;QACpC,UAAK,GAAoB,EAAE,CAAC;IAsF9D,CAAC;aApFiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuDF;KACF,AA1DqB,CA0DpB;IAEO,MAAM;QACb,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,+EAA+E;QAC/E,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAChE,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClG,OAAO,IAAI,CAAA;6CAC8B,OAAO;UAC1C,MAAM,CACN,MAAM,EACN,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,EACjB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAA;;oCAEY,IAAI,CAAC,KAAK;;iDAEG,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG;;kCAEvC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;;WAEnD,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AAtFiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;6CAA6B;AAFjD,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CAwF1B","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\nexport interface WeightBarItem {\n id: string;\n label: string;\n /** Normalized fraction (fractions across all items sum to ~1). */\n value: number;\n}\n\n/**\n * Sorted horizontal bar chart of labeled weights (normalized fractions\n * summing to ~1). Bars sort descending — the order IS the priority ranking.\n * Widths scale relative to the largest weight (which fills its track); the\n * percent labels carry the absolute values. Rows are keyed by item id\n * (repeat directive) so a re-render with new weights moves the existing\n * rows; bar widths animate via CSS, re-sorting is instant.\n *\n * @element weight-bar-chart\n */\n@customElement(\"weight-bar-chart\")\nexport class WeightBarChart extends LitElement {\n /** Items to render as weighted rows, sorted descending by value. */\n @property({ attribute: false }) items: WeightBarItem[] = [];\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin: 0.5rem 0;\n }\n .label {\n flex: 0 0 5.5rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\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 color: var(--ui-text, #0f172a);\n }\n .track {\n flex: 1 1 auto;\n height: 0.75rem;\n background: var(--ui-surface-muted, #f8fafc);\n border-radius: var(--ui-radius-sm, 0.25rem);\n overflow: hidden;\n }\n .bar {\n height: 100%;\n /* Match map-circle's 30% white/black vertical depth while preserving token overrides. */\n background: linear-gradient(\n to bottom,\n color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #ffffff) 0%,\n color-mix(in srgb, var(--ui-primary, #4f46e5) 70%, #000000) 100%\n );\n border-radius: 0 var(--ui-radius-sm, 0.25rem) var(--ui-radius-sm, 0.25rem) 0;\n transition: width 150ms ease-out;\n }\n .pct {\n flex: 0 0 2.5rem;\n text-align: right;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-variant-numeric: tabular-nums;\n color: var(--ui-text-muted, #64748b);\n }\n @media (prefers-reduced-motion: reduce) {\n .bar {\n transition: none;\n }\n }\n `,\n ];\n\n override render() {\n if (this.items.length === 0) return null;\n // Stable sort: ties keep the incoming item order, so the chart never flickers.\n const sorted = [...this.items].sort((a, b) => b.value - a.value);\n const max = Math.max(0.01, ...sorted.map((item) => item.value));\n const summary = sorted.map((item) => `${item.label} ${Math.round(item.value * 100)}%`).join(\", \");\n return html`\n <div role=\"img\" aria-label=\"Weights: ${summary}\">\n ${repeat(\n sorted,\n (item) => item.id,\n (item) => html`\n <div class=\"row\">\n <span class=\"label\">${item.label}</span>\n <div class=\"track\">\n <div class=\"bar\" style=\"width: ${(item.value / max) * 100}%\"></div>\n </div>\n <span class=\"pct\">${Math.round(item.value * 100)}%</span>\n </div>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"weight-bar-chart\": WeightBarChart;\n }\n}\n"]}
@@ -35,6 +35,7 @@ import "@f-ewald/components/address-autocomplete.js";
35
35
  | `value` | `value` | `string` | `""` | Current input value; also the form-associated value submitted with the form. |
36
36
  | `placeholder` | `placeholder` | `string` | `""` | Placeholder text shown when the input is empty. |
37
37
  | `required` | `required` | `boolean` | `false` | Marks the input as required for native form validation. |
38
+ | `disabled` | `disabled` | `boolean` | `false` | Disables the input and closes its suggestion popup. |
38
39
  | `types` | `types` | `string` | `"address"` | Mapbox `types` param, e.g. "address" or "address,poi". |
39
40
  | `endpoint` | `endpoint` | `string` | `"https://api.mapbox.com/search/geocode/v6/forward"` | Geocoding endpoint URL. Defaults to the Mapbox Geocoding v6 forward URL. |
40
41
  | `accessToken` | `access-token` | `string` | `""` | Mapbox access token. Required for requests to succeed. |
@@ -60,9 +61,13 @@ _None._
60
61
  | Custom property |
61
62
  | --- |
62
63
  | `--ui-border` |
64
+ | `--ui-focus-ring` |
63
65
  | `--ui-font` |
64
66
  | `--ui-font-size-sm` |
67
+ | `--ui-primary` |
65
68
  | `--ui-radius-sm` |
66
69
  | `--ui-shadow` |
67
70
  | `--ui-surface` |
68
71
  | `--ui-surface-muted` |
72
+ | `--ui-text` |
73
+ | `--ui-text-muted` |
@@ -49,6 +49,7 @@ import "@f-ewald/components/autocomplete-input.js";
49
49
  | `value` | `value` | `string` | `""` | Current input value; also the form-associated value submitted with the form. |
50
50
  | `placeholder` | `placeholder` | `string` | `""` | Placeholder text shown when the input is empty. |
51
51
  | `required` | `required` | `boolean` | `false` | Marks the input as required for native form validation. |
52
+ | `disabled` | `disabled` | `boolean` | `false` | Disables the input and closes its suggestion popup. |
52
53
  | `endpoint` | `endpoint` | `string` | `""` | API endpoint queried in API mode. Ignored when `options` is set. |
53
54
  | `queryParam` | `query-param` | `string` | `"q"` | Query string parameter name the current input text is sent under. |
54
55
  | `debounce` | `debounce` | `number` | `300` | Debounce delay (ms) before firing a request/filter after typing. |
@@ -71,9 +72,13 @@ _None._
71
72
  | Custom property |
72
73
  | --- |
73
74
  | `--ui-border` |
75
+ | `--ui-focus-ring` |
74
76
  | `--ui-font` |
75
77
  | `--ui-font-size-sm` |
78
+ | `--ui-primary` |
76
79
  | `--ui-radius-sm` |
77
80
  | `--ui-shadow` |
78
81
  | `--ui-surface` |
79
82
  | `--ui-surface-muted` |
83
+ | `--ui-text` |
84
+ | `--ui-text-muted` |
@@ -68,6 +68,7 @@ _None._
68
68
  | `--ui-font-size-sm` |
69
69
  | `--ui-font-size-xs` |
70
70
  | `--ui-hover-overlay` |
71
+ | `--ui-info` |
71
72
  | `--ui-primary` |
72
73
  | `--ui-radius-sm` |
73
74
  | `--ui-success` |
@@ -75,3 +76,4 @@ _None._
75
76
  | `--ui-surface-muted` |
76
77
  | `--ui-text` |
77
78
  | `--ui-text-muted` |
79
+ | `--ui-warning` |
@@ -50,10 +50,14 @@ _None._
50
50
  | Custom property |
51
51
  | --- |
52
52
  | `--ui-border` |
53
+ | `--ui-focus-ring` |
53
54
  | `--ui-font` |
55
+ | `--ui-font-mono` |
54
56
  | `--ui-font-size` |
57
+ | `--ui-font-size-sm` |
55
58
  | `--ui-primary` |
56
59
  | `--ui-radius` |
60
+ | `--ui-radius-sm` |
57
61
  | `--ui-surface` |
58
62
  | `--ui-text` |
59
63
  | `--ui-text-muted` |
@@ -51,9 +51,11 @@ import "@f-ewald/components/confirm-dialog.js";
51
51
  | `--ui-border` |
52
52
  | `--ui-danger` |
53
53
  | `--ui-danger-hover` |
54
+ | `--ui-focus-ring` |
54
55
  | `--ui-font` |
55
56
  | `--ui-font-size` |
56
57
  | `--ui-font-size-sm` |
58
+ | `--ui-on-accent` |
57
59
  | `--ui-overlay` |
58
60
  | `--ui-primary` |
59
61
  | `--ui-primary-hover` |
@@ -24,6 +24,7 @@ import "@f-ewald/components/copy-link-button.js";
24
24
  | --- | --- | --- | --- | --- |
25
25
  | `value` | `value` | `string` | `""` | Text to copy. Defaults to `window.location.href` at click time. |
26
26
  | `label` | `label` | `string` | `"Copy link"` | Accessible label / tooltip text. |
27
+ | `disabled` | `disabled` | `boolean` | `false` | Disables clipboard copying. |
27
28
 
28
29
  ## Events
29
30
 
@@ -40,6 +41,7 @@ _None._
40
41
 
41
42
  | Custom property |
42
43
  | --- |
44
+ | `--ui-focus-ring` |
43
45
  | `--ui-radius-sm` |
44
46
  | `--ui-surface-muted` |
45
47
  | `--ui-text` |
@@ -42,6 +42,7 @@ import "@f-ewald/components/data-table.js";
42
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
43
  | `renderCell` | _(JS property only)_ | `(row: unknown, key: string) => unknown` | `—` | Produces a cell's rendered content for `row`/`column.key`. Default: `row[key]`. |
44
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
+ | `rowLabel` | _(JS property only)_ | `((row: unknown) => string) | null` | `null` | Accessible label for a row's keyboard link; defaults to primitive cell values. |
45
46
 
46
47
  ## Events
47
48
 
@@ -56,6 +57,7 @@ _None._
56
57
  | Custom property |
57
58
  | --- |
58
59
  | `--ui-border` |
60
+ | `--ui-focus-ring` |
59
61
  | `--ui-font` |
60
62
  | `--ui-font-size-sm` |
61
63
  | `--ui-surface-muted` |