@f-ewald/components 0.7.0 → 1.0.2

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 (193) hide show
  1. package/README.md +76 -47
  2. package/custom-elements.json +3049 -910
  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-entry.d.ts +33 -0
  16. package/dist/calendar-entry.d.ts.map +1 -0
  17. package/dist/calendar-entry.js +64 -0
  18. package/dist/calendar-entry.js.map +1 -0
  19. package/dist/calendar-month.d.ts +67 -0
  20. package/dist/calendar-month.d.ts.map +1 -0
  21. package/dist/calendar-month.js +469 -0
  22. package/dist/calendar-month.js.map +1 -0
  23. package/dist/calendar-year.d.ts +43 -0
  24. package/dist/calendar-year.d.ts.map +1 -0
  25. package/dist/calendar-year.js +145 -0
  26. package/dist/calendar-year.js.map +1 -0
  27. package/dist/chat-message.d.ts.map +1 -1
  28. package/dist/chat-message.js +77 -24
  29. package/dist/chat-message.js.map +1 -1
  30. package/dist/confirm-dialog.d.ts +8 -1
  31. package/dist/confirm-dialog.d.ts.map +1 -1
  32. package/dist/confirm-dialog.js +130 -18
  33. package/dist/confirm-dialog.js.map +1 -1
  34. package/dist/copy-link-button.d.ts +2 -0
  35. package/dist/copy-link-button.d.ts.map +1 -1
  36. package/dist/copy-link-button.js +35 -3
  37. package/dist/copy-link-button.js.map +1 -1
  38. package/dist/data-table.d.ts +2 -0
  39. package/dist/data-table.d.ts.map +1 -1
  40. package/dist/data-table.js +83 -11
  41. package/dist/data-table.js.map +1 -1
  42. package/dist/distance-value.d.ts +0 -1
  43. package/dist/distance-value.d.ts.map +1 -1
  44. package/dist/distance-value.js +3 -8
  45. package/dist/distance-value.js.map +1 -1
  46. package/dist/distribution-chart.d.ts.map +1 -1
  47. package/dist/distribution-chart.js +54 -17
  48. package/dist/distribution-chart.js.map +1 -1
  49. package/dist/dropdown-button.d.ts.map +1 -1
  50. package/dist/dropdown-button.js +60 -11
  51. package/dist/dropdown-button.js.map +1 -1
  52. package/dist/editable-text.d.ts.map +1 -1
  53. package/dist/editable-text.js +46 -13
  54. package/dist/editable-text.js.map +1 -1
  55. package/dist/form-select.d.ts +21 -2
  56. package/dist/form-select.d.ts.map +1 -1
  57. package/dist/form-select.js +489 -37
  58. package/dist/form-select.js.map +1 -1
  59. package/dist/frame-box.d.ts.map +1 -1
  60. package/dist/frame-box.js +20 -12
  61. package/dist/frame-box.js.map +1 -1
  62. package/dist/icon-button.d.ts.map +1 -1
  63. package/dist/icon-button.js +11 -0
  64. package/dist/icon-button.js.map +1 -1
  65. package/dist/index.d.ts +4 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +4 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/kbd-hint.d.ts +30 -0
  70. package/dist/kbd-hint.d.ts.map +1 -0
  71. package/dist/kbd-hint.js +184 -0
  72. package/dist/kbd-hint.js.map +1 -0
  73. package/dist/live-timer.d.ts +0 -1
  74. package/dist/live-timer.d.ts.map +1 -1
  75. package/dist/live-timer.js +1 -6
  76. package/dist/live-timer.js.map +1 -1
  77. package/dist/map-circle.d.ts +3 -0
  78. package/dist/map-circle.d.ts.map +1 -1
  79. package/dist/map-circle.js +19 -1
  80. package/dist/map-circle.js.map +1 -1
  81. package/dist/map-pin.d.ts.map +1 -1
  82. package/dist/map-pin.js +16 -1
  83. package/dist/map-pin.js.map +1 -1
  84. package/dist/percent-bar-chart.d.ts +3 -0
  85. package/dist/percent-bar-chart.d.ts.map +1 -1
  86. package/dist/percent-bar-chart.js +60 -4
  87. package/dist/percent-bar-chart.js.map +1 -1
  88. package/dist/photo-gallery.d.ts.map +1 -1
  89. package/dist/photo-gallery.js +12 -3
  90. package/dist/photo-gallery.js.map +1 -1
  91. package/dist/popover-panel.d.ts.map +1 -1
  92. package/dist/popover-panel.js +132 -23
  93. package/dist/popover-panel.js.map +1 -1
  94. package/dist/price-history-chart.d.ts.map +1 -1
  95. package/dist/price-history-chart.js +52 -18
  96. package/dist/price-history-chart.js.map +1 -1
  97. package/dist/radio-cards.d.ts +2 -0
  98. package/dist/radio-cards.d.ts.map +1 -1
  99. package/dist/radio-cards.js +51 -5
  100. package/dist/radio-cards.js.map +1 -1
  101. package/dist/radio-pills.d.ts +2 -0
  102. package/dist/radio-pills.d.ts.map +1 -1
  103. package/dist/radio-pills.js +46 -4
  104. package/dist/radio-pills.js.map +1 -1
  105. package/dist/relative-time.d.ts +0 -1
  106. package/dist/relative-time.d.ts.map +1 -1
  107. package/dist/relative-time.js +1 -6
  108. package/dist/relative-time.js.map +1 -1
  109. package/dist/reveal-button.d.ts +2 -0
  110. package/dist/reveal-button.d.ts.map +1 -1
  111. package/dist/reveal-button.js +42 -5
  112. package/dist/reveal-button.js.map +1 -1
  113. package/dist/roman-numeral.js +1 -1
  114. package/dist/roman-numeral.js.map +1 -1
  115. package/dist/slide-panel.d.ts +7 -3
  116. package/dist/slide-panel.d.ts.map +1 -1
  117. package/dist/slide-panel.js +95 -23
  118. package/dist/slide-panel.js.map +1 -1
  119. package/dist/stat-meter.d.ts.map +1 -1
  120. package/dist/stat-meter.js +17 -7
  121. package/dist/stat-meter.js.map +1 -1
  122. package/dist/status-pill.d.ts.map +1 -1
  123. package/dist/status-pill.js +26 -12
  124. package/dist/status-pill.js.map +1 -1
  125. package/dist/tile-grid.d.ts +8 -0
  126. package/dist/tile-grid.d.ts.map +1 -1
  127. package/dist/tile-grid.js +88 -12
  128. package/dist/tile-grid.js.map +1 -1
  129. package/dist/toast-notification.d.ts.map +1 -1
  130. package/dist/toast-notification.js +40 -11
  131. package/dist/toast-notification.js.map +1 -1
  132. package/dist/tokens.css +23 -0
  133. package/dist/tokens.d.ts.map +1 -1
  134. package/dist/tokens.js +16 -0
  135. package/dist/tokens.js.map +1 -1
  136. package/dist/ui-button.d.ts +2 -0
  137. package/dist/ui-button.d.ts.map +1 -1
  138. package/dist/ui-button.js +58 -9
  139. package/dist/ui-button.js.map +1 -1
  140. package/dist/user-avatar.d.ts.map +1 -1
  141. package/dist/user-avatar.js +18 -3
  142. package/dist/user-avatar.js.map +1 -1
  143. package/dist/utils/calendar.d.ts +77 -0
  144. package/dist/utils/calendar.d.ts.map +1 -0
  145. package/dist/utils/calendar.js +135 -0
  146. package/dist/utils/calendar.js.map +1 -0
  147. package/dist/utils/form.d.ts +3 -0
  148. package/dist/utils/form.d.ts.map +1 -0
  149. package/dist/utils/form.js +14 -0
  150. package/dist/utils/form.js.map +1 -0
  151. package/dist/utils/layer-stack.d.ts +9 -0
  152. package/dist/utils/layer-stack.d.ts.map +1 -0
  153. package/dist/utils/layer-stack.js +35 -0
  154. package/dist/utils/layer-stack.js.map +1 -0
  155. package/dist/weight-bar-chart.d.ts.map +1 -1
  156. package/dist/weight-bar-chart.js +27 -8
  157. package/dist/weight-bar-chart.js.map +1 -1
  158. package/docs/address-autocomplete.md +5 -0
  159. package/docs/autocomplete-input.md +5 -0
  160. package/docs/calendar-entry.md +53 -0
  161. package/docs/calendar-month.md +79 -0
  162. package/docs/calendar-year.md +53 -0
  163. package/docs/chat-message.md +4 -0
  164. package/docs/confirm-dialog.md +2 -0
  165. package/docs/copy-link-button.md +2 -0
  166. package/docs/data-table.md +2 -0
  167. package/docs/design-language.md +149 -0
  168. package/docs/distribution-chart.md +4 -0
  169. package/docs/dropdown-button.md +1 -0
  170. package/docs/form-select.md +15 -3
  171. package/docs/frame-box.md +2 -0
  172. package/docs/kbd-hint.md +42 -0
  173. package/docs/map-circle.md +4 -0
  174. package/docs/percent-bar-chart.md +4 -1
  175. package/docs/photo-gallery.md +1 -0
  176. package/docs/popover-panel.md +2 -0
  177. package/docs/price-history-chart.md +8 -1
  178. package/docs/radio-cards.md +2 -0
  179. package/docs/radio-pills.md +2 -0
  180. package/docs/reveal-button.md +3 -0
  181. package/docs/slide-panel.md +4 -2
  182. package/docs/status-pill.md +3 -0
  183. package/docs/tile-grid.md +10 -1
  184. package/docs/toast-notification.md +4 -1
  185. package/docs/ui-button.md +2 -0
  186. package/docs/user-avatar.md +1 -0
  187. package/llms.txt +175 -61
  188. package/package.json +14 -2
  189. package/dist/map-point.d.ts +0 -31
  190. package/dist/map-point.d.ts.map +0 -1
  191. package/dist/map-point.js +0 -83
  192. package/dist/map-point.js.map +0 -1
  193. package/docs/map-point.md +0 -44
@@ -1 +1 @@
1
- {"version":3,"file":"confirm-dialog.js","sourceRoot":"","sources":["../src/confirm-dialog.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,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;AAErC;;;;;;;;;;GAUG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QA8GL,qCAAqC;QACR,SAAI,GAAG,KAAK,CAAC;QAC1C,oCAAoC;QACM,iBAAY,GAAG,QAAQ,CAAC;QAClE,mCAAmC;QACM,gBAAW,GAAG,QAAQ,CAAC;QAChE,wEAAwE;QAC3C,WAAM,GAAG,IAAI,CAAC;QAC3C,8EAA8E;QACjD,SAAI,GAAG,KAAK,CAAC;QAC1C,gEAAgE;QACpD,UAAK,GAAkB,IAAI,CAAC;IA0B1C,CAAC;aAlJiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAwGF;KACF,AA3GqB,CA2GpB;IAeM,QAAQ;QACd,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACpF,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;4BACa,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;YAGvC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,yBAAyB,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO;;mDAE7B,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,WAAW;4BAC3E,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,cAAc,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC,QAAQ;gBACrG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA,sBAAsB,aAAa,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC,YAAY;;;;;KAK1G,CAAC;IACJ,CAAC;CACF,CAAA;AApC8B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CAAc;AAEA;IAAzC,QAAQ,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC;mDAAyB;AAEzB;IAAxC,QAAQ,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;kDAAwB;AAEnC;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;6CAAe;AAEd;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CAAc;AAE9B;IAAX,QAAQ,EAAE;4CAA6B;AAzH7B,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CAmJzB","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconArrowPath } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Reusable confirmation dialog: overlay + centered card with a slotted body,\n * an optional error line, and Cancel/Confirm actions. Instant `display:none`\n * → `display:flex` toggle (no transitions). Fires `confirm`/`cancel`\n * (bubbling, composed) instead of owning any deletion logic itself —\n * callers stay in charge of the request.\n *\n * @element confirm-dialog\n * @fires confirm - User clicked the confirm button.\n * @fires cancel - User clicked the cancel button.\n */\n@customElement(\"confirm-dialog\")\nexport class ConfirmDialog extends LitElement {\n static override styles = [\n tokens,\n css`\n .overlay {\n position: fixed;\n inset: 0;\n background: var(--ui-overlay, rgb(15 23 42 / 0.45));\n z-index: 50;\n display: none;\n align-items: flex-start;\n justify-content: center;\n padding: 10vh 20px 20px;\n box-sizing: border-box;\n }\n .overlay.open {\n display: flex;\n }\n .dialog {\n background: var(--ui-surface, #fff);\n border-radius: var(--ui-radius, 0.5rem);\n box-shadow: 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 width: 100%;\n max-width: 420px;\n padding: 1.25rem;\n box-sizing: border-box;\n }\n .dialog-text {\n margin: 0 0 1rem;\n color: var(--ui-text, #0f172a);\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size, 0.875rem);\n line-height: 1.45;\n }\n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.6rem;\n }\n .btn-cancel {\n background: none;\n border: 1px solid var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\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 cursor: pointer;\n font-family: inherit;\n }\n .btn-cancel:hover:not(:disabled) {\n border-color: var(--ui-text-muted, #64748b);\n }\n .btn-danger,\n .btn-primary {\n border: none;\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 cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n font-family: inherit;\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-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 button:disabled {\n opacity: 0.6;\n cursor: default;\n }\n .error-text {\n color: var(--ui-danger, #dc2626);\n font-size: var(--ui-font-size-sm, 0.75rem);\n margin: 0.4rem 0 0.8rem;\n }\n .spin {\n display: inline-flex;\n animation: spin 0.8s linear infinite;\n }\n @keyframes spin {\n to {\n transform: rotate(360deg);\n }\n }\n @media (max-width: 768px) {\n .overlay {\n padding: 12px;\n overflow-y: auto;\n }\n }\n `,\n ];\n\n /** Whether the dialog is visible. */\n @property({ type: Boolean }) open = false;\n /** Label for the confirm button. */\n @property({ attribute: \"confirm-label\" }) confirmLabel = \"Delete\";\n /** Label for the cancel button. */\n @property({ attribute: \"cancel-label\" }) cancelLabel = \"Cancel\";\n /** Danger (red) vs. primary (indigo) styling for the confirm button. */\n @property({ type: Boolean }) danger = true;\n /** Shows a spinner and disables both buttons while a request is in flight. */\n @property({ type: Boolean }) busy = false;\n /** Inline error line shown below the body, or null for none. */\n @property() error: string | null = null;\n\n private _confirm() {\n this.dispatchEvent(new CustomEvent(\"confirm\", { bubbles: true, composed: true }));\n }\n\n private _cancel() {\n this.dispatchEvent(new CustomEvent(\"cancel\", { bubbles: true, composed: true }));\n }\n\n override render() {\n return html`\n <div class=\"overlay ${this.open ? \"open\" : \"\"}\">\n <div class=\"dialog\">\n <p class=\"dialog-text\"><slot></slot></p>\n ${this.error ? html`<p class=\"error-text\">${this.error}</p>` : nothing}\n <div class=\"dialog-actions\">\n <button class=\"btn-cancel\" ?disabled=${this.busy} @click=${this._cancel}>${this.cancelLabel}</button>\n <button class=${this.danger ? \"btn-danger\" : \"btn-primary\"} ?disabled=${this.busy} @click=${this._confirm}>\n ${this.busy ? html`<span class=\"spin\">${iconArrowPath(14)}</span>` : nothing} ${this.confirmLabel}\n </button>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"confirm-dialog\": ConfirmDialog;\n }\n}\n"]}
1
+ {"version":3,"file":"confirm-dialog.js","sourceRoot":"","sources":["../src/confirm-dialog.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconArrowPath } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\nimport {\n activateLayer,\n claimEscape,\n deactivateLayer,\n isTopLayer,\n} from \"./utils/layer-stack.js\";\n\n/**\n * Reusable confirmation dialog: overlay + centered card with a slotted body,\n * an optional error line, and Cancel/Confirm actions. Instant `display:none`\n * → `display:flex` toggle (no transitions). Fires `confirm`/`cancel`\n * (bubbling, composed) instead of owning any deletion logic itself —\n * callers stay in charge of the request.\n *\n * @element confirm-dialog\n * @fires confirm - User clicked the confirm button.\n * @fires cancel - User clicked the cancel button.\n */\n@customElement(\"confirm-dialog\")\nexport class ConfirmDialog extends LitElement {\n static override styles = [\n tokens,\n css`\n .overlay {\n position: fixed;\n inset: 0;\n background: var(--ui-overlay, rgb(15 23 42 / 0.45));\n z-index: var(--component-layer-z, 50);\n display: none;\n align-items: flex-start;\n justify-content: center;\n padding: 10vh 1.25rem 1.25rem;\n box-sizing: border-box;\n }\n .overlay.open {\n display: flex;\n }\n .dialog {\n background: var(--ui-surface, #ffffff);\n border-radius: var(--ui-radius, 0.5rem);\n box-shadow: 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 width: 100%;\n max-width: 26.25rem;\n padding: 1.25rem;\n box-sizing: border-box;\n outline: none;\n }\n .dialog-text {\n margin: 0 0 1rem;\n color: var(--ui-text, #0f172a);\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-size: var(--ui-font-size, 0.875rem);\n line-height: 1.45;\n }\n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.5rem;\n }\n .btn-cancel {\n background: none;\n border: 1px solid var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\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 cursor: pointer;\n font-family: inherit;\n }\n .btn-cancel:hover:not(:disabled) {\n border-color: var(--ui-text-muted, #64748b);\n }\n .btn-danger,\n .btn-primary {\n border: none;\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 cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n font-family: inherit;\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-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 button:disabled {\n opacity: 0.6;\n cursor: default;\n }\n button:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .error-text {\n color: var(--ui-danger, #dc2626);\n font-size: var(--ui-font-size-sm, 0.75rem);\n margin: 0.5rem 0 0.75rem;\n }\n .spin {\n display: inline-flex;\n animation: spin 0.8s linear infinite;\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 button:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n @media (max-width: 48rem) {\n .overlay {\n padding: 0.75rem;\n overflow-y: auto;\n }\n }\n `,\n ];\n\n /** Whether the dialog is visible. */\n @property({ type: Boolean }) open = false;\n /** Label for the confirm button. */\n @property({ attribute: \"confirm-label\" }) confirmLabel = \"Delete\";\n /** Label for the cancel button. */\n @property({ attribute: \"cancel-label\" }) cancelLabel = \"Cancel\";\n /** Danger (red) vs. primary (indigo) styling for the confirm button. */\n @property({ type: Boolean }) danger = true;\n /** Shows a spinner and disables both buttons while a request is in flight. */\n @property({ type: Boolean }) busy = false;\n /** Inline error line shown below the body, or null for none. */\n @property() error: string | null = null;\n\n private _previousFocus: HTMLElement | null = null;\n\n private _confirm() {\n this.dispatchEvent(new CustomEvent(\"confirm\", { bubbles: true, composed: true }));\n }\n\n private _cancel() {\n this.dispatchEvent(new CustomEvent(\"cancel\", { bubbles: true, composed: true }));\n }\n\n private _getFocusableElements(): HTMLElement[] {\n const selector =\n \"a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])\";\n const slotted = Array.from(this.querySelectorAll<HTMLElement>(selector));\n const internal = Array.from(\n this.shadowRoot?.querySelectorAll<HTMLElement>(selector) ?? [],\n ).filter((element) => element.closest(\".dialog\") !== null);\n return [...slotted, ...internal].filter((element) => !element.hasAttribute(\"hidden\"));\n }\n\n private _focusInitial(): void {\n const autofocus = this.querySelector<HTMLElement>(\"[autofocus]\");\n const cancel = this.shadowRoot?.querySelector<HTMLElement>(\".btn-cancel:not(:disabled)\");\n const dialog = this.shadowRoot?.querySelector<HTMLElement>(\".dialog\");\n (autofocus ?? cancel ?? this._getFocusableElements()[0] ?? dialog)?.focus();\n }\n\n private _onDocumentFocusIn = (event: FocusEvent): void => {\n if (!this.open || !isTopLayer(this) || event.composedPath().includes(this)) return;\n this._focusInitial();\n };\n\n private _onWindowKeydown = (event: KeyboardEvent): void => {\n if (!this.open || !isTopLayer(this)) return;\n if (claimEscape(this, event) && !this.busy) {\n this._cancel();\n return;\n }\n if (event.key !== \"Tab\") return;\n\n const focusable = this._getFocusableElements();\n if (focusable.length === 0) {\n event.preventDefault();\n this._focusInitial();\n return;\n }\n const active = (this.shadowRoot?.activeElement ?? document.activeElement) as HTMLElement | null;\n const index = focusable.indexOf(active as HTMLElement);\n if (event.shiftKey && index <= 0) {\n event.preventDefault();\n focusable.at(-1)?.focus();\n } else if (!event.shiftKey && index === focusable.length - 1) {\n event.preventDefault();\n focusable[0]?.focus();\n }\n };\n\n protected override updated(changed: PropertyValues): void {\n if (!changed.has(\"open\")) return;\n if (this.open) {\n activateLayer(this);\n this._previousFocus = document.activeElement as HTMLElement | null;\n document.addEventListener(\"focusin\", this._onDocumentFocusIn, true);\n window.addEventListener(\"keydown\", this._onWindowKeydown, true);\n this._focusInitial();\n return;\n }\n deactivateLayer(this);\n document.removeEventListener(\"focusin\", this._onDocumentFocusIn, true);\n window.removeEventListener(\"keydown\", this._onWindowKeydown, true);\n if (this._previousFocus?.isConnected) this._previousFocus.focus();\n this._previousFocus = null;\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n document.removeEventListener(\"focusin\", this._onDocumentFocusIn, true);\n window.removeEventListener(\"keydown\", this._onWindowKeydown, true);\n deactivateLayer(this);\n if (this._previousFocus?.isConnected) this._previousFocus.focus();\n this._previousFocus = null;\n this.open = false;\n }\n\n override render() {\n const accessibleName = this.getAttribute(\"aria-label\") ?? \"Confirmation\";\n return html`\n <div class=\"overlay ${this.open ? \"open\" : \"\"}\">\n <div\n class=\"dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=${accessibleName}\n aria-describedby=${this.error ? \"dialog-text dialog-error\" : \"dialog-text\"}\n tabindex=\"-1\"\n >\n <p id=\"dialog-text\" class=\"dialog-text\"><slot></slot></p>\n ${this.error\n ? html`<p id=\"dialog-error\" class=\"error-text\" role=\"alert\">${this.error}</p>`\n : nothing}\n <div class=\"dialog-actions\">\n <button class=\"btn-cancel\" ?disabled=${this.busy} @click=${this._cancel}>${this.cancelLabel}</button>\n <button class=${this.danger ? \"btn-danger\" : \"btn-primary\"} ?disabled=${this.busy} @click=${this._confirm}>\n ${this.busy\n ? html`<span class=\"spin\" aria-hidden=\"true\">${iconArrowPath(14)}</span>`\n : nothing}\n ${this.confirmLabel}\n </button>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"confirm-dialog\": ConfirmDialog;\n }\n}\n"]}
@@ -15,6 +15,8 @@ export declare class CopyLinkButton extends LitElement {
15
15
  value: string;
16
16
  /** Accessible label / tooltip text. */
17
17
  label: string;
18
+ /** Disables clipboard copying. */
19
+ disabled: boolean;
18
20
  static styles: import("lit").CSSResult[];
19
21
  private _onClick;
20
22
  render(): import("lit-html").TemplateResult<1>;
@@ -1 +1 @@
1
- {"version":3,"file":"copy-link-button.d.ts","sourceRoot":"","sources":["../src/copy-link-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAM5C;;;;;;;;;;GAUG;AACH,qBACa,cAAe,SAAQ,UAAU;IAC5C,sEAAsE;IAC1D,KAAK,SAAM;IACvB,uCAAuC;IAC3B,KAAK,SAAe;IAEhC,OAAgB,MAAM,4BAsBpB;YAEY,QAAQ;IAYb,MAAM,yCAMd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
1
+ {"version":3,"file":"copy-link-button.d.ts","sourceRoot":"","sources":["../src/copy-link-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAM5C;;;;;;;;;;GAUG;AACH,qBACa,cAAe,SAAQ,UAAU;IAC5C,sEAAsE;IAC1D,KAAK,SAAM;IACvB,uCAAuC;IAC3B,KAAK,SAAe;IAChC,kCAAkC;IACL,QAAQ,UAAS;IAE9C,OAAgB,MAAM,4BAyCpB;YAEY,QAAQ;IAab,MAAM,yCAYd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
@@ -27,6 +27,8 @@ let CopyLinkButton = class CopyLinkButton extends LitElement {
27
27
  this.value = "";
28
28
  /** Accessible label / tooltip text. */
29
29
  this.label = "Copy link";
30
+ /** Disables clipboard copying. */
31
+ this.disabled = false;
30
32
  }
31
33
  static { this.styles = [
32
34
  tokens,
@@ -38,20 +40,41 @@ let CopyLinkButton = class CopyLinkButton extends LitElement {
38
40
  background: none;
39
41
  border: none;
40
42
  cursor: pointer;
41
- padding: 4px;
43
+ padding: 0.25rem;
42
44
  color: var(--ui-text-muted, #64748b);
43
45
  border-radius: var(--ui-radius-sm, 0.25rem);
44
46
  display: inline-flex;
45
47
  align-items: center;
46
48
  justify-content: center;
47
49
  }
48
- button:hover {
50
+ button:hover:not(:disabled) {
49
51
  background: var(--ui-surface-muted, #f8fafc);
50
52
  color: var(--ui-text, #0f172a);
51
53
  }
54
+ button:disabled {
55
+ cursor: not-allowed;
56
+ opacity: 0.6;
57
+ }
58
+ button:focus-visible {
59
+ outline: none;
60
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
61
+ }
62
+ @media (forced-colors: active) {
63
+ button:focus-visible {
64
+ outline: 2px solid CanvasText;
65
+ outline-offset: 2px;
66
+ box-shadow: none;
67
+ }
68
+ button:disabled {
69
+ color: GrayText;
70
+ opacity: 1;
71
+ }
72
+ }
52
73
  `,
53
74
  ]; }
54
75
  async _onClick() {
76
+ if (this.disabled)
77
+ return;
55
78
  const text = this.value || window.location.href;
56
79
  try {
57
80
  await navigator.clipboard.writeText(text);
@@ -65,7 +88,13 @@ let CopyLinkButton = class CopyLinkButton extends LitElement {
65
88
  }
66
89
  render() {
67
90
  return html `
68
- <button type="button" aria-label=${this.label} title=${this.label} @click=${this._onClick}>
91
+ <button
92
+ type="button"
93
+ aria-label=${this.label}
94
+ title=${this.label}
95
+ ?disabled=${this.disabled}
96
+ @click=${this._onClick}
97
+ >
69
98
  ${iconLink(16)}
70
99
  </button>
71
100
  `;
@@ -77,6 +106,9 @@ __decorate([
77
106
  __decorate([
78
107
  property()
79
108
  ], CopyLinkButton.prototype, "label", void 0);
109
+ __decorate([
110
+ property({ type: Boolean })
111
+ ], CopyLinkButton.prototype, "disabled", void 0);
80
112
  CopyLinkButton = __decorate([
81
113
  customElement("copy-link-button")
82
114
  ], CopyLinkButton);
@@ -1 +1 @@
1
- {"version":3,"file":"copy-link-button.js","sourceRoot":"","sources":["../src/copy-link-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,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;GAUG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QACL,sEAAsE;QAC1D,UAAK,GAAG,EAAE,CAAC;QACvB,uCAAuC;QAC3B,UAAK,GAAG,WAAW,CAAC;IA6ClC,CAAC;aA3CiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;KAmBF;KACF,AAtBqB,CAsBpB;IAEM,KAAK,CAAC,QAAQ;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC1C,aAAa,CAAC,0BAA0B,CAAC,CAAC;YAC1C,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACzF,CAAC;QAAC,MAAM,CAAC;YACP,WAAW,CAAC,wBAAwB,CAAC,CAAC;YACtC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;yCAC0B,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,QAAQ;UACrF,QAAQ,CAAC,EAAE,CAAC;;KAEjB,CAAC;IACJ,CAAC;CACF,CAAA;AA/Ca;IAAX,QAAQ,EAAE;6CAAY;AAEX;IAAX,QAAQ,EAAE;6CAAqB;AAJrB,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CAiD1B","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconLink } from \"./icons.js\";\nimport { notifyError, notifySuccess } from \"./toast-notification.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Small icon button that copies `value` to the clipboard and shows a toast\n * on success/failure (if a `<toast-notification>` element is present), and\n * always dispatches a `copy-success`/`copy-error` CustomEvent so consumers\n * without a toast element can react. Defaults to the current page URL if\n * `value` is unset.\n *\n * @element copy-link-button\n * @fires copy-success - The value was copied to the clipboard.\n * @fires copy-error - Copying to the clipboard failed.\n */\n@customElement(\"copy-link-button\")\nexport class CopyLinkButton extends LitElement {\n /** Text to copy. Defaults to `window.location.href` at click time. */\n @property() value = \"\";\n /** Accessible label / tooltip text. */\n @property() label = \"Copy link\";\n\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n button {\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n button:hover {\n background: var(--ui-surface-muted, #f8fafc);\n color: var(--ui-text, #0f172a);\n }\n `,\n ];\n\n private async _onClick() {\n const text = this.value || window.location.href;\n try {\n await navigator.clipboard.writeText(text);\n notifySuccess(\"Link copied to clipboard\");\n this.dispatchEvent(new CustomEvent(\"copy-success\", { bubbles: true, composed: true }));\n } catch {\n notifyError(\"Couldn't copy the link\");\n this.dispatchEvent(new CustomEvent(\"copy-error\", { bubbles: true, composed: true }));\n }\n }\n\n override render() {\n return html`\n <button type=\"button\" aria-label=${this.label} title=${this.label} @click=${this._onClick}>\n ${iconLink(16)}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"copy-link-button\": CopyLinkButton;\n }\n}\n"]}
1
+ {"version":3,"file":"copy-link-button.js","sourceRoot":"","sources":["../src/copy-link-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,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;GAUG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QACL,sEAAsE;QAC1D,UAAK,GAAG,EAAE,CAAC;QACvB,uCAAuC;QAC3B,UAAK,GAAG,WAAW,CAAC;QAChC,kCAAkC;QACL,aAAQ,GAAG,KAAK,CAAC;IAuEhD,CAAC;aArEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAsCF;KACF,AAzCqB,CAyCpB;IAEM,KAAK,CAAC,QAAQ;QACpB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC1C,aAAa,CAAC,0BAA0B,CAAC,CAAC;YAC1C,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACzF,CAAC;QAAC,MAAM,CAAC;YACP,WAAW,CAAC,wBAAwB,CAAC,CAAC;YACtC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;;qBAGM,IAAI,CAAC,KAAK;gBACf,IAAI,CAAC,KAAK;oBACN,IAAI,CAAC,QAAQ;iBAChB,IAAI,CAAC,QAAQ;;UAEpB,QAAQ,CAAC,EAAE,CAAC;;KAEjB,CAAC;IACJ,CAAC;CACF,CAAA;AA3Ea;IAAX,QAAQ,EAAE;6CAAY;AAEX;IAAX,QAAQ,EAAE;6CAAqB;AAEH;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;gDAAkB;AANnC,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CA6E1B","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconLink } from \"./icons.js\";\nimport { notifyError, notifySuccess } from \"./toast-notification.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Small icon button that copies `value` to the clipboard and shows a toast\n * on success/failure (if a `<toast-notification>` element is present), and\n * always dispatches a `copy-success`/`copy-error` CustomEvent so consumers\n * without a toast element can react. Defaults to the current page URL if\n * `value` is unset.\n *\n * @element copy-link-button\n * @fires copy-success - The value was copied to the clipboard.\n * @fires copy-error - Copying to the clipboard failed.\n */\n@customElement(\"copy-link-button\")\nexport class CopyLinkButton extends LitElement {\n /** Text to copy. Defaults to `window.location.href` at click time. */\n @property() value = \"\";\n /** Accessible label / tooltip text. */\n @property() label = \"Copy link\";\n /** Disables clipboard copying. */\n @property({ type: Boolean }) disabled = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n button {\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.25rem;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n button:hover:not(:disabled) {\n background: var(--ui-surface-muted, #f8fafc);\n color: var(--ui-text, #0f172a);\n }\n button:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n }\n button:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n @media (forced-colors: active) {\n button:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n button:disabled {\n color: GrayText;\n opacity: 1;\n }\n }\n `,\n ];\n\n private async _onClick() {\n if (this.disabled) return;\n const text = this.value || window.location.href;\n try {\n await navigator.clipboard.writeText(text);\n notifySuccess(\"Link copied to clipboard\");\n this.dispatchEvent(new CustomEvent(\"copy-success\", { bubbles: true, composed: true }));\n } catch {\n notifyError(\"Couldn't copy the link\");\n this.dispatchEvent(new CustomEvent(\"copy-error\", { bubbles: true, composed: true }));\n }\n }\n\n override render() {\n return html`\n <button\n type=\"button\"\n aria-label=${this.label}\n title=${this.label}\n ?disabled=${this.disabled}\n @click=${this._onClick}\n >\n ${iconLink(16)}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"copy-link-button\": CopyLinkButton;\n }\n}\n"]}
@@ -29,6 +29,8 @@ export declare class DataTable extends LitElement {
29
29
  renderCell: (row: unknown, key: string) => unknown;
30
30
  /** When set, clicking a row (outside any nested link/button) navigates to this hash. */
31
31
  rowHref: ((row: unknown) => string | null) | null;
32
+ /** Accessible label for a row's keyboard link; defaults to primitive cell values. */
33
+ rowLabel: ((row: unknown) => string) | null;
32
34
  render(): import("lit-html").TemplateResult<1>;
33
35
  }
34
36
  declare global {
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,yGAAyG;AACzG,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,qBACa,SAAU,SAAQ,UAAU;;IACvC,OAAgB,MAAM,4BA+BpB;IAEF,wCAAwC;IACR,OAAO,EAAE,eAAe,EAAE,CAAM;IAChE,gFAAgF;IAChD,IAAI,EAAE,OAAO,EAAE,CAAM;IACrD,oGAAoG;IACpE,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAG7E;IACX,sFAAsF;IACtD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAGvC;IAC3C,wFAAwF;IACxD,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAQ;IAShF,MAAM,yCAwBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,YAAY,EAAE,SAAS,CAAC;KACzB;CACF"}
1
+ {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,yGAAyG;AACzG,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,qBACa,SAAU,SAAQ,UAAU;;IACvC,OAAgB,MAAM,4BA+DpB;IAEF,wCAAwC;IACR,OAAO,EAAE,eAAe,EAAE,CAAM;IAChE,gFAAgF;IAChD,IAAI,EAAE,OAAO,EAAE,CAAM;IACrD,oGAAoG;IACpE,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAG7E;IACX,sFAAsF;IACtD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAGvC;IAC3C,wFAAwF;IACxD,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAQ;IACzF,qFAAqF;IACrD,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC,GAAG,IAAI,CAAQ;IA4B1E,MAAM,yCA0Cd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,YAAY,EAAE,SAAS,CAAC;KACzB;CACF"}
@@ -33,13 +33,24 @@ let DataTable = class DataTable extends LitElement {
33
33
  this.renderCell = (row, key) => row[key];
34
34
  /** When set, clicking a row (outside any nested link/button) navigates to this hash. */
35
35
  this.rowHref = null;
36
+ /** Accessible label for a row's keyboard link; defaults to primitive cell values. */
37
+ this.rowLabel = null;
36
38
  }
37
39
  static { this.styles = [
38
40
  tokens,
39
41
  css `
40
42
  :host {
41
43
  display: block;
42
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
44
+ font-family: var(
45
+ --ui-font,
46
+ ui-sans-serif,
47
+ system-ui,
48
+ sans-serif,
49
+ "Apple Color Emoji",
50
+ "Segoe UI Emoji",
51
+ "Segoe UI Symbol",
52
+ "Noto Color Emoji"
53
+ );
43
54
  font-size: var(--ui-font-size-sm, 0.75rem);
44
55
  }
45
56
  table {
@@ -54,27 +65,69 @@ let DataTable = class DataTable extends LitElement {
54
65
  border-bottom: 1px solid var(--ui-border, #e2e8f0);
55
66
  }
56
67
  td {
57
- padding: 0.6rem 0.75rem;
68
+ padding: 0.5rem 0.75rem;
58
69
  border-bottom: 1px solid var(--ui-border, #e2e8f0);
59
70
  color: var(--ui-text, #0f172a);
60
71
  }
72
+ td:first-child {
73
+ position: relative;
74
+ }
75
+ .row-link {
76
+ position: absolute;
77
+ width: 1px;
78
+ height: 1px;
79
+ overflow: hidden;
80
+ clip: rect(0 0 0 0);
81
+ clip-path: inset(50%);
82
+ white-space: nowrap;
83
+ }
61
84
  tbody tr.clickable {
62
85
  cursor: pointer;
63
86
  }
64
87
  tbody tr.clickable:hover {
65
88
  background: var(--ui-surface-muted, #f8fafc);
66
89
  }
90
+ tbody tr.clickable:has(.row-link:focus-visible) {
91
+ outline: none;
92
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35)) inset;
93
+ }
94
+ @media (forced-colors: active) {
95
+ tbody tr.clickable:has(.row-link:focus-visible) {
96
+ outline: 2px solid CanvasText;
97
+ outline-offset: -2px;
98
+ box-shadow: none;
99
+ }
100
+ }
67
101
  `,
68
102
  ]; }
69
103
  #onRowClick(row, e) {
70
104
  if (!this.rowHref)
71
105
  return;
72
- if (e.target.closest("a, button"))
106
+ if (this.#isNestedInteractive(e.composedPath(), e.currentTarget))
73
107
  return;
74
108
  const href = this.rowHref(row);
75
109
  if (href)
76
110
  location.hash = href;
77
111
  }
112
+ #isNestedInteractive(path, row) {
113
+ const selector = "a, button, input, select, textarea, summary, [contenteditable], [role='button'], [role='link'], [tabindex]";
114
+ for (const target of path) {
115
+ if (target === row)
116
+ return false;
117
+ if (target instanceof HTMLElement && target.matches(selector))
118
+ return true;
119
+ }
120
+ return false;
121
+ }
122
+ #rowLinkLabel(row) {
123
+ const explicitLabel = this.rowLabel?.(row).trim();
124
+ if (explicitLabel)
125
+ return explicitLabel;
126
+ const values = this.columns
127
+ .map((column) => row[column.key])
128
+ .filter((value) => typeof value === "string" || typeof value === "number");
129
+ return values.length > 0 ? `Open ${values.join(", ")}` : "Open row";
130
+ }
78
131
  render() {
79
132
  return html `
80
133
  <table>
@@ -84,14 +137,30 @@ let DataTable = class DataTable extends LitElement {
84
137
  </tr>
85
138
  </thead>
86
139
  <tbody>
87
- ${repeat(this.rows, (row, i) => this.rowKey(row, i), (row) => html `
88
- <tr
89
- class=${this.rowHref ? "clickable" : ""}
90
- @click=${(e) => this.#onRowClick(row, e)}
91
- >
92
- ${this.columns.map((c) => html `<td>${this.renderCell(row, c.key) ?? nothing}</td>`)}
93
- </tr>
94
- `)}
140
+ ${repeat(this.rows, (row, i) => this.rowKey(row, i), (row) => {
141
+ const href = this.rowHref?.(row) ?? null;
142
+ return html `
143
+ <tr
144
+ class=${href ? "clickable" : ""}
145
+ @click=${(e) => this.#onRowClick(row, e)}
146
+ >
147
+ ${this.columns.map((c, columnIndex) => html `
148
+ <td>
149
+ ${href && columnIndex === 0
150
+ ? html `
151
+ <a
152
+ class="row-link"
153
+ href=${href}
154
+ aria-label=${this.#rowLinkLabel(row)}
155
+ ></a>
156
+ `
157
+ : nothing}
158
+ ${this.renderCell(row, c.key) ?? nothing}
159
+ </td>
160
+ `)}
161
+ </tr>
162
+ `;
163
+ })}
95
164
  </tbody>
96
165
  </table>
97
166
  `;
@@ -112,6 +181,9 @@ __decorate([
112
181
  __decorate([
113
182
  property({ attribute: false })
114
183
  ], DataTable.prototype, "rowHref", void 0);
184
+ __decorate([
185
+ property({ attribute: false })
186
+ ], DataTable.prototype, "rowLabel", void 0);
115
187
  DataTable = __decorate([
116
188
  customElement("data-table")
117
189
  ], DataTable);
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.js","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,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;AAQrC;;;;;;;;;;;GAWG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,UAAU;IAAlC;;QAkCL,wCAAwC;QACR,YAAO,GAAsB,EAAE,CAAC;QAChE,gFAAgF;QAChD,SAAI,GAAc,EAAE,CAAC;QACrD,oGAAoG;QACpE,WAAM,GAAqD,CACzF,IAAI,EACJ,KAAK,EACL,EAAE,CAAC,KAAK,CAAC;QACX,sFAAsF;QACtD,eAAU,GAA2C,CACnF,GAAG,EACH,GAAG,EACH,EAAE,CAAE,GAA+B,CAAC,GAAG,CAAC,CAAC;QAC3C,wFAAwF;QACxD,YAAO,GAA6C,IAAI,CAAC;IAkC3F,CAAC;aAlFiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA4BF;KACF,AA/BqB,CA+BpB;IAmBF,WAAW,CAAC,GAAY,EAAE,CAAa;QACrC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAK,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,WAAW,CAAC;YAAE,OAAO;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;IACjC,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;;;cAID,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAA,OAAO,CAAC,CAAC,KAAK,OAAO,CAAC;;;;YAIpD,MAAM,CACN,IAAI,CAAC,IAAI,EACT,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,EAC/B,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;wBAED,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE;yBAC9B,CAAC,CAAa,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC;;kBAElD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAA,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,OAAO,OAAO,CAAC;;aAEtF,CACF;;;KAGN,CAAC;IACJ,CAAC;CACF,CAAA;AAhDiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;0CAAiC;AAEhC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;uCAAsB;AAErB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;yCAGpB;AAEqB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;6CAGY;AAEX;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;0CAA0D;AAjD9E,SAAS;IADrB,aAAa,CAAC,YAAY,CAAC;GACf,SAAS,CAmFrB","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\n\n/** One column header: `key` is looked up via `renderCell`/`getCellValue`, `label` is the header text. */\nexport interface DataTableColumn {\n key: string;\n label: string;\n}\n\n/**\n * A generic, presentational table shell: renders a `<thead>` from `columns`\n * and one `<tr>` per entry in `rows`, with each cell's content produced by\n * `renderCell` (default: plain property lookup on the row object). Knows\n * nothing about what a \"row\" means — callers own the data shape entirely.\n *\n * Optional `rowHref` makes whole rows clickable (navigating via `location.hash`),\n * without hijacking clicks on nested interactive elements (links/buttons) a\n * cell's rendered content might contain.\n *\n * @element data-table\n */\n@customElement(\"data-table\")\nexport class DataTable 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 table {\n width: 100%;\n border-collapse: collapse;\n }\n th {\n text-align: left;\n font-weight: 600;\n color: var(--ui-text-muted, #64748b);\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n }\n td {\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\n }\n tbody tr.clickable {\n cursor: pointer;\n }\n tbody tr.clickable:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n `,\n ];\n\n /** Column headers, in display order. */\n @property({ attribute: false }) columns: DataTableColumn[] = [];\n /** Row data; opaque to this component beyond what `renderCell` does with it. */\n @property({ attribute: false }) rows: unknown[] = [];\n /** Stable identity for `rows[i]`, used as the repeat-directive key. Defaults to the row's index. */\n @property({ attribute: false }) rowKey: (row: unknown, index: number) => string | number = (\n _row,\n index,\n ) => index;\n /** Produces a cell's rendered content for `row`/`column.key`. Default: `row[key]`. */\n @property({ attribute: false }) renderCell: (row: unknown, key: string) => unknown = (\n row,\n key,\n ) => (row as Record<string, unknown>)[key];\n /** When set, clicking a row (outside any nested link/button) navigates to this hash. */\n @property({ attribute: false }) rowHref: ((row: unknown) => string | null) | null = null;\n\n #onRowClick(row: unknown, e: MouseEvent): void {\n if (!this.rowHref) return;\n if ((e.target as HTMLElement).closest(\"a, button\")) return;\n const href = this.rowHref(row);\n if (href) location.hash = href;\n }\n\n override render() {\n return html`\n <table>\n <thead>\n <tr>\n ${this.columns.map((c) => html`<th>${c.label}</th>`)}\n </tr>\n </thead>\n <tbody>\n ${repeat(\n this.rows,\n (row, i) => this.rowKey(row, i),\n (row) => html`\n <tr\n class=${this.rowHref ? \"clickable\" : \"\"}\n @click=${(e: MouseEvent) => this.#onRowClick(row, e)}\n >\n ${this.columns.map((c) => html`<td>${this.renderCell(row, c.key) ?? nothing}</td>`)}\n </tr>\n `,\n )}\n </tbody>\n </table>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"data-table\": DataTable;\n }\n}\n"]}
1
+ {"version":3,"file":"data-table.js","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\n\n/** One column header: `key` is looked up via `renderCell`/`getCellValue`, `label` is the header text. */\nexport interface DataTableColumn {\n key: string;\n label: string;\n}\n\n/**\n * A generic, presentational table shell: renders a `<thead>` from `columns`\n * and one `<tr>` per entry in `rows`, with each cell's content produced by\n * `renderCell` (default: plain property lookup on the row object). Knows\n * nothing about what a \"row\" means — callers own the data shape entirely.\n *\n * Optional `rowHref` makes whole rows clickable (navigating via `location.hash`),\n * without hijacking clicks on nested interactive elements (links/buttons) a\n * cell's rendered content might contain.\n *\n * @element data-table\n */\n@customElement(\"data-table\")\nexport class DataTable extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n font-size: var(--ui-font-size-sm, 0.75rem);\n }\n table {\n width: 100%;\n border-collapse: collapse;\n }\n th {\n text-align: left;\n font-weight: 600;\n color: var(--ui-text-muted, #64748b);\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n }\n td {\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\n }\n td:first-child {\n position: relative;\n }\n .row-link {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n }\n tbody tr.clickable {\n cursor: pointer;\n }\n tbody tr.clickable:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n tbody tr.clickable:has(.row-link:focus-visible) {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35)) inset;\n }\n @media (forced-colors: active) {\n tbody tr.clickable:has(.row-link:focus-visible) {\n outline: 2px solid CanvasText;\n outline-offset: -2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n /** Column headers, in display order. */\n @property({ attribute: false }) columns: DataTableColumn[] = [];\n /** Row data; opaque to this component beyond what `renderCell` does with it. */\n @property({ attribute: false }) rows: unknown[] = [];\n /** Stable identity for `rows[i]`, used as the repeat-directive key. Defaults to the row's index. */\n @property({ attribute: false }) rowKey: (row: unknown, index: number) => string | number = (\n _row,\n index,\n ) => index;\n /** Produces a cell's rendered content for `row`/`column.key`. Default: `row[key]`. */\n @property({ attribute: false }) renderCell: (row: unknown, key: string) => unknown = (\n row,\n key,\n ) => (row as Record<string, unknown>)[key];\n /** When set, clicking a row (outside any nested link/button) navigates to this hash. */\n @property({ attribute: false }) rowHref: ((row: unknown) => string | null) | null = null;\n /** Accessible label for a row's keyboard link; defaults to primitive cell values. */\n @property({ attribute: false }) rowLabel: ((row: unknown) => string) | null = null;\n\n #onRowClick(row: unknown, e: MouseEvent): void {\n if (!this.rowHref) return;\n if (this.#isNestedInteractive(e.composedPath(), e.currentTarget)) return;\n const href = this.rowHref(row);\n if (href) location.hash = href;\n }\n\n #isNestedInteractive(path: EventTarget[], row: EventTarget | null): boolean {\n const selector =\n \"a, button, input, select, textarea, summary, [contenteditable], [role='button'], [role='link'], [tabindex]\";\n for (const target of path) {\n if (target === row) return false;\n if (target instanceof HTMLElement && target.matches(selector)) return true;\n }\n return false;\n }\n\n #rowLinkLabel(row: unknown): string {\n const explicitLabel = this.rowLabel?.(row).trim();\n if (explicitLabel) return explicitLabel;\n const values = this.columns\n .map((column) => (row as Record<string, unknown>)[column.key])\n .filter((value) => typeof value === \"string\" || typeof value === \"number\");\n return values.length > 0 ? `Open ${values.join(\", \")}` : \"Open row\";\n }\n\n override render() {\n return html`\n <table>\n <thead>\n <tr>\n ${this.columns.map((c) => html`<th>${c.label}</th>`)}\n </tr>\n </thead>\n <tbody>\n ${repeat(\n this.rows,\n (row, i) => this.rowKey(row, i),\n (row) => {\n const href = this.rowHref?.(row) ?? null;\n return html`\n <tr\n class=${href ? \"clickable\" : \"\"}\n @click=${(e: MouseEvent) => this.#onRowClick(row, e)}\n >\n ${this.columns.map(\n (c, columnIndex) => html`\n <td>\n ${href && columnIndex === 0\n ? html`\n <a\n class=\"row-link\"\n href=${href}\n aria-label=${this.#rowLinkLabel(row)}\n ></a>\n `\n : nothing}\n ${this.renderCell(row, c.key) ?? nothing}\n </td>\n `,\n )}\n </tr>\n `;\n },\n )}\n </tbody>\n </table>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"data-table\": DataTable;\n }\n}\n"]}
@@ -9,7 +9,6 @@ import { LitElement, nothing } from "lit";
9
9
  * @element distance-value
10
10
  */
11
11
  export declare class DistanceValue extends LitElement {
12
- static styles: import("lit").CSSResult;
13
12
  /** Distance in miles (imperial). Switches to feet below 0.25 mi. */
14
13
  miles: number | null;
15
14
  /** Distance in kilometers (metric, future). Switches to meters below 0.5 km. */
@@ -1 +1 @@
1
- {"version":3,"file":"distance-value.d.ts","sourceRoot":"","sources":["../src/distance-value.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAE,MAAM,KAAK,CAAC;AAIrD;;;;;;;;GAQG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC3C,OAAgB,MAAM,0BAIpB;IAEF,oEAAoE;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IAExD,gFAAgF;IACpD,EAAE,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE5C,MAAM,0DAId;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,aAAa,CAAC;KACjC;CACF"}
1
+ {"version":3,"file":"distance-value.d.ts","sourceRoot":"","sources":["../src/distance-value.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,OAAO,EAAE,MAAM,KAAK,CAAC;AAIhD;;;;;;;;GAQG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC3C,oEAAoE;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IAExD,gFAAgF;IACpD,EAAE,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE5C,MAAM,0DAId;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,aAAa,CAAC;KACjC;CACF"}
@@ -4,7 +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
- import { LitElement, css, html, nothing } from "lit";
7
+ import { LitElement, html, nothing } from "lit";
8
8
  import { customElement, property } from "lit/decorators.js";
9
9
  import { formatMiles, formatKm } from "./utils/distance.js";
10
10
  /**
@@ -24,16 +24,11 @@ let DistanceValue = class DistanceValue extends LitElement {
24
24
  /** Distance in kilometers (metric, future). Switches to meters below 0.5 km. */
25
25
  this.km = null;
26
26
  }
27
- static { this.styles = css `
28
- :host {
29
- display: inline;
30
- }
31
- `; }
32
27
  render() {
33
28
  if (this.km != null)
34
- return html `${formatKm(this.km)}`;
29
+ return Number.isFinite(this.km) ? html `${formatKm(this.km)}` : nothing;
35
30
  if (this.miles != null)
36
- return html `${formatMiles(this.miles)}`;
31
+ return Number.isFinite(this.miles) ? html `${formatMiles(this.miles)}` : nothing;
37
32
  return nothing;
38
33
  }
39
34
  };
@@ -1 +1 @@
1
- {"version":3,"file":"distance-value.js","sourceRoot":"","sources":["../src/distance-value.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;;;;;;;GAQG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QAOL,oEAAoE;QACxC,UAAK,GAAkB,IAAI,CAAC;QAExD,gFAAgF;QACpD,OAAE,GAAkB,IAAI,CAAC;IAOvD,CAAC;aAjBiB,WAAM,GAAG,GAAG,CAAA;;;;GAI3B,AAJqB,CAIpB;IAQO,MAAM;QACb,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACvD,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,OAAO,OAAO,CAAC;IACjB,CAAC;CACF,CAAA;AAV6B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;4CAA6B;AAG5B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;yCAA0B;AAX1C,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CAkBzB","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { formatMiles, formatKm } from \"./utils/distance.js\";\n\n/**\n * Inline distance display. Renders miles/feet or km/m, switching units at\n * sensible thresholds (< 0.25 mi → ft; < 0.5 km → m).\n *\n * Supply exactly one of `miles` or `km`; the other stays null.\n * km support is present but reserved for future use.\n *\n * @element distance-value\n */\n@customElement(\"distance-value\")\nexport class DistanceValue extends LitElement {\n static override styles = css`\n :host {\n display: inline;\n }\n `;\n\n /** Distance in miles (imperial). Switches to feet below 0.25 mi. */\n @property({ type: Number }) miles: number | null = null;\n\n /** Distance in kilometers (metric, future). Switches to meters below 0.5 km. */\n @property({ type: Number }) km: number | null = null;\n\n override render() {\n if (this.km != null) return html`${formatKm(this.km)}`;\n if (this.miles != null) return html`${formatMiles(this.miles)}`;\n return nothing;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"distance-value\": DistanceValue;\n }\n}\n"]}
1
+ {"version":3,"file":"distance-value.js","sourceRoot":"","sources":["../src/distance-value.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;;;;;;;GAQG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QACL,oEAAoE;QACxC,UAAK,GAAkB,IAAI,CAAC;QAExD,gFAAgF;QACpD,OAAE,GAAkB,IAAI,CAAC;IAOvD,CAAC;IALU,MAAM;QACb,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI;YAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;QAC5F,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI;YAAE,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;QACxG,OAAO,OAAO,CAAC;IACjB,CAAC;CACF,CAAA;AAV6B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;4CAA6B;AAG5B;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;yCAA0B;AAL1C,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CAYzB","sourcesContent":["import { LitElement, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { formatMiles, formatKm } from \"./utils/distance.js\";\n\n/**\n * Inline distance display. Renders miles/feet or km/m, switching units at\n * sensible thresholds (< 0.25 mi → ft; < 0.5 km → m).\n *\n * Supply exactly one of `miles` or `km`; the other stays null.\n * km support is present but reserved for future use.\n *\n * @element distance-value\n */\n@customElement(\"distance-value\")\nexport class DistanceValue extends LitElement {\n /** Distance in miles (imperial). Switches to feet below 0.25 mi. */\n @property({ type: Number }) miles: number | null = null;\n\n /** Distance in kilometers (metric, future). Switches to meters below 0.5 km. */\n @property({ type: Number }) km: number | null = null;\n\n override render() {\n if (this.km != null) return Number.isFinite(this.km) ? html`${formatKm(this.km)}` : nothing;\n if (this.miles != null) return Number.isFinite(this.miles) ? html`${formatMiles(this.miles)}` : nothing;\n return nothing;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"distance-value\": DistanceValue;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"distribution-chart.d.ts","sourceRoot":"","sources":["../src/distribution-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAO,MAAM,KAAK,CAAC;AAqB1D,MAAM,WAAW,iBAAiB;IAChC,8FAA8F;IAC9F,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AASD;;;;;;;;;;;GAWG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IAC/C,OAAgB,MAAM,4BAgEpB;IAEF,wEAAwE;IAC5D,MAAM,SAAM;IACxB,4DAA4D;IAC5B,MAAM,EAAE,iBAAiB,EAAE,CAAM;IACjE,oFAAoF;IACpD,YAAY,EAAE,MAAM,EAAE,CAAyB;IAC/E,8GAA8G;IAC1D,QAAQ,SAAM;IAEzD,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAuB;IAC9C,0EAA0E;IACjE,OAAO,CAAC,MAAM,CAAK;IAC5B,wFAAwF;IAC/E,OAAO,CAAC,OAAO,CAAuB;IAE/C,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,GAAG,CAA+B;IAEjC,iBAAiB,SAGzB;IAEQ,YAAY,SAMpB;IAEQ,oBAAoB,SAI5B;IAEQ,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,QAE7C;IAED,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,UAAU;IAuFlB,OAAO,CAAC,aAAa;IAmBZ,MAAM,0DAUd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF"}
1
+ {"version":3,"file":"distribution-chart.d.ts","sourceRoot":"","sources":["../src/distribution-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAO,MAAM,KAAK,CAAC;AAqB1D,MAAM,WAAW,iBAAiB;IAChC,8FAA8F;IAC9F,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AASD;;;;;;;;;;;GAWG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IAC/C,OAAgB,MAAM,4BA6FpB;IAEF,wEAAwE;IAC5D,MAAM,SAAM;IACxB,4DAA4D;IAC5B,MAAM,EAAE,iBAAiB,EAAE,CAAM;IACjE,oFAAoF;IACpD,YAAY,EAAE,MAAM,EAAE,CAAyB;IAC/E,8GAA8G;IAC1D,QAAQ,SAAM;IAEzD,OAAO,CAAC,KAAK,CAAiC;IAC9C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAuB;IAC9C,0EAA0E;IACjE,OAAO,CAAC,MAAM,CAAK;IAC5B,wFAAwF;IAC/E,OAAO,CAAC,OAAO,CAAuB;IAE/C,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,GAAG,CAA+B;IAEjC,iBAAiB,SAGzB;IAEQ,YAAY,SAMpB;IAEQ,oBAAoB,SAI5B;IAEQ,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,QAE7C;IAED,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,UAAU;IAiGlB,OAAO,CAAC,aAAa;IAmBZ,MAAM,0DAUd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF"}
@@ -56,27 +56,51 @@ let DistributionChart = class DistributionChart extends LitElement {
56
56
  width: 100%;
57
57
  }
58
58
  .wrap {
59
- padding: 4px 0 8px;
59
+ padding: 0.25rem 0 0.5rem;
60
60
  }
61
61
  svg {
62
62
  display: block;
63
63
  overflow: visible;
64
+ font-family: var(
65
+ --ui-font,
66
+ ui-sans-serif,
67
+ system-ui,
68
+ sans-serif,
69
+ "Apple Color Emoji",
70
+ "Segoe UI Emoji",
71
+ "Segoe UI Symbol",
72
+ "Noto Color Emoji"
73
+ );
64
74
  }
65
75
  .curve {
66
76
  fill: none;
67
- stroke: #4f46e5;
77
+ stroke: var(--ui-primary, #4f46e5);
68
78
  stroke-width: 1.5;
69
79
  stroke-linejoin: round;
70
80
  }
71
81
  .fill {
72
82
  fill: url(#dist-fill-grad);
73
83
  }
84
+ .fill-start {
85
+ stop-color: var(--ui-primary, #4f46e5);
86
+ }
87
+ .crosshair {
88
+ stroke: var(--ui-text-muted, #64748b);
89
+ }
90
+ .chart-label {
91
+ fill: var(--ui-text-muted, #64748b);
92
+ }
74
93
  .skeleton {
75
- background: linear-gradient(90deg, #f8fafc 25%, #e2e8f0 50%, #f8fafc 75%);
94
+ background: linear-gradient(
95
+ 90deg,
96
+ var(--ui-surface-muted, #f8fafc) 25%,
97
+ var(--ui-border, #e2e8f0) 50%,
98
+ var(--ui-surface-muted, #f8fafc) 75%
99
+ );
76
100
  background-size: 200% 100%;
77
101
  animation: shimmer 1.4s infinite;
78
102
  border-radius: var(--ui-radius-sm, 0.25rem);
79
- height: 100px;
103
+ height: 6.25rem;
80
104
  }
81
105
  @keyframes shimmer {
82
106
  0% {
@@ -89,28 +113,33 @@ let DistributionChart = class DistributionChart extends LitElement {
89
113
  .error {
90
114
  font-size: var(--ui-font-size-sm, 0.75rem);
91
115
  color: var(--ui-danger, #dc2626);
92
- padding: 8px 0;
116
+ padding: 0.5rem 0;
93
117
  }
94
118
  .legend {
95
119
  display: flex;
96
120
  flex-wrap: wrap;
97
- gap: 6px 12px;
98
- padding: 2px 0 2px 8px;
121
+ gap: 0.5rem 0.75rem;
122
+ padding: 0.25rem 0 0.25rem 0.5rem;
99
123
  }
100
124
  .legend-item {
101
125
  display: inline-flex;
102
126
  align-items: center;
103
- gap: 4px;
127
+ gap: 0.25rem;
104
128
  font-size: var(--ui-font-size-sm, 0.75rem);
105
129
  color: var(--ui-text-muted, #64748b);
106
130
  }
107
131
  .legend-dot {
108
132
  display: inline-block;
109
- width: 8px;
110
- height: 8px;
133
+ width: 0.5rem;
134
+ height: 0.5rem;
111
135
  border-radius: 50%;
112
136
  flex: 0 0 auto;
113
137
  }
138
+ @media (prefers-reduced-motion: reduce) {
139
+ .skeleton {
140
+ animation: none;
141
+ }
142
+ }
114
143
  `,
115
144
  ]; }
116
145
  connectedCallback() {
@@ -200,13 +229,21 @@ let DistributionChart = class DistributionChart extends LitElement {
200
229
  const anchor = hx < innerW * 0.2 ? "start" : hx > innerW * 0.8 ? "end" : "middle";
201
230
  const lx = hx < innerW * 0.2 ? 0 : hx > innerW * 0.8 ? innerW : hx;
202
231
  return svg `
203
- <line stroke="#94a3b8" stroke-width="1" x1=${hx} y1=${0} x2=${hx} y2=${innerH} />
204
- <text font-size=${fs} fill="#64748b" font-weight="500"
232
+ <line class="crosshair" stroke-width="1" x1=${hx} y1=${0} x2=${hx} y2=${innerH} />
233
+ <text class="chart-label" font-size=${fs} font-weight="500"
205
234
  text-anchor=${anchor} x=${lx} y=${-fs * 0.4}>${hLabel}</text>
206
235
  `;
207
236
  })() : nothing;
237
+ const markerSummary = this.values.length > 0
238
+ ? ` Markers: ${this.values
239
+ .map((value) => `${value.label || "value"} ${value.value.toLocaleString("en-US")} ${data.unit}`)
240
+ .join(", ")}.`
241
+ : "";
242
+ const summary = `${data.label}: ${data.min.toLocaleString("en-US")} to ${data.max.toLocaleString("en-US")} ${data.unit}, ` +
243
+ `mean ${data.mean.toLocaleString("en-US")} ${data.unit}.${markerSummary}`;
208
244
  return svg `
209
245
  <svg viewBox="0 0 ${this._width} ${SVG_H}" width=${this._width} height=${SVG_H}
246
+ role="img" aria-label=${summary}
210
247
  style="cursor:crosshair"
211
248
  @mousemove=${(e) => {
212
249
  const innerX = e.offsetX - PAD.left;
@@ -215,8 +252,8 @@ let DistributionChart = class DistributionChart extends LitElement {
215
252
  @mouseleave=${() => { this._hoverX = null; }}>
216
253
  <defs>
217
254
  <linearGradient id="dist-fill-grad" x1="0" y1="0" x2="0" y2="1">
218
- <stop offset="0%" stop-color="#4f46e5" stop-opacity="0.28"/>
219
- <stop offset="100%" stop-color="#4f46e5" stop-opacity="0"/>
255
+ <stop class="fill-start" offset="0%" stop-opacity="0.28"/>
256
+ <stop class="fill-start" offset="100%" stop-opacity="0"/>
220
257
  </linearGradient>
221
258
  </defs>
222
259
  <g transform="translate(${PAD.left},${PAD.top})">
@@ -224,9 +261,9 @@ let DistributionChart = class DistributionChart extends LitElement {
224
261
  <path class="curve" d=${curvePath} />
225
262
  ${markers}
226
263
  ${crosshair}
227
- <text font-size=${fs} fill="#94a3b8" text-anchor="start" x=${0} y=${innerH + fs * 1.4}>${minLabel}</text>
228
- <text font-size=${fs} fill="#94a3b8" text-anchor="end" x=${innerW} y=${innerH + fs * 1.4}>${maxLabel}</text>
229
- <text font-size=${fs} fill="#94a3b8" text-anchor="middle" x=${innerW / 2} y=${innerH + fs * 1.4}>${data.unit}</text>
264
+ <text class="chart-label" font-size=${fs} text-anchor="start" x=${0} y=${innerH + fs * 1.4}>${minLabel}</text>
265
+ <text class="chart-label" font-size=${fs} text-anchor="end" x=${innerW} y=${innerH + fs * 1.4}>${maxLabel}</text>
266
+ <text class="chart-label" font-size=${fs} text-anchor="middle" x=${innerW / 2} y=${innerH + fs * 1.4}>${data.unit}</text>
230
267
  </g>
231
268
  </svg>
232
269
  `;