@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":"radio-cards.js","sourceRoot":"","sources":["../src/radio-cards.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,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB;;;;;;;;;GASG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QA8CL,wCAAwC;QACR,YAAO,GAAsB,EAAE,CAAC;QAChE,gCAAgC;QACpB,UAAK,GAAG,EAAE,CAAC;QAEd,UAAK,GAAG,eAAe,EAAE,aAAa,EAAE,CAAC;IA+BpD,CAAC;aAjFiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAwCF;KACF,AA3CqB,CA2CpB;IAOO,KAAK,CAAoC;IAE1C,SAAS,CAAC,KAAa;QAC7B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACtG,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;UAEL,MAAM,CACN,IAAI,CAAC,OAAO,EACZ,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAClB,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;;;uBAIA,IAAI,CAAC,KAAK;2BACN,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK;0BACzB,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;;;2CAGd,GAAG,CAAC,KAAK;kBAClC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAA,kCAAkC,GAAG,CAAC,WAAW,SAAS,CAAC,CAAC,CAAC,OAAO;;;WAGjG,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AAnCiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;2CAAiC;AAEpD;IAAX,QAAQ,EAAE;yCAAY;AAjDZ,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CAkFtB","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\nexport interface RadioCardOption {\n value: string;\n label: string;\n description?: string;\n}\n\nlet instanceCount = 0;\n\n/**\n * Single-select group of full-width cards, each with a label and optional\n * description — for a handful of meaningfully different choices where the\n * description matters. For many short, same-shaped options (a color swatch,\n * a basemap style), use `radio-pills` instead. Wraps native radio inputs for\n * keyboard/a11y and fires `change` rather than relying on form submission.\n *\n * @element radio-cards\n * @fires change - A card was selected; detail: { value }.\n */\n@customElement(\"radio-cards\")\nexport class RadioCards extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .options {\n display: flex;\n gap: 0.6rem;\n flex-wrap: wrap;\n }\n .card {\n flex: 1 1 180px;\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.6rem 0.7rem;\n cursor: pointer;\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 .card:has(input:checked) {\n border-color: var(--ui-primary, #4f46e5);\n background: var(--ui-surface-muted, #f8fafc);\n }\n .card input {\n width: auto;\n margin-top: 0.15rem;\n accent-color: var(--ui-primary, #4f46e5);\n cursor: pointer;\n }\n .card-label {\n font-weight: 600;\n color: var(--ui-text, #0f172a);\n }\n .card-description {\n display: block;\n color: var(--ui-text-muted, #64748b);\n font-weight: 400;\n }\n `,\n ];\n\n /** Options to render, one card each. */\n @property({ attribute: false }) options: RadioCardOption[] = [];\n /** Currently selected value. */\n @property() value = \"\";\n\n readonly #name = `radio-cards-${++instanceCount}`;\n\n private _onChange(value: string) {\n this.value = value;\n this.dispatchEvent(new CustomEvent(\"change\", { detail: { value }, bubbles: true, composed: true }));\n }\n\n override render() {\n return html`\n <div class=\"options\">\n ${repeat(\n this.options,\n (opt) => opt.value,\n (opt) => html`\n <label class=\"card\">\n <input\n type=\"radio\"\n name=${this.#name}\n ?checked=${this.value === opt.value}\n @change=${() => this._onChange(opt.value)}\n />\n <span>\n <span class=\"card-label\">${opt.label}</span>\n ${opt.description ? html`<span class=\"card-description\">${opt.description}</span>` : nothing}\n </span>\n </label>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"radio-cards\": RadioCards;\n }\n}\n"]}
1
+ {"version":3,"file":"radio-cards.js","sourceRoot":"","sources":["../src/radio-cards.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,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB;;;;;;;;;GASG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QAoFL,wCAAwC;QACR,YAAO,GAAsB,EAAE,CAAC;QAChE,gCAAgC;QACpB,UAAK,GAAG,EAAE,CAAC;QACvB,gDAAgD;QACnB,aAAQ,GAAG,KAAK,CAAC;QAErC,UAAK,GAAG,eAAe,EAAE,aAAa,EAAE,CAAC;IAiCpD,CAAC;aA3HiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8EF;KACF,AAjFqB,CAiFpB;IASO,KAAK,CAAoC;IAE1C,SAAS,CAAC,KAAa;QAC7B,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACtG,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;UAEL,MAAM,CACN,IAAI,CAAC,OAAO,EACZ,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAClB,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;;;uBAIA,IAAI,CAAC,KAAK;2BACN,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK;4BACvB,IAAI,CAAC,QAAQ;0BACf,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;;;2CAGd,GAAG,CAAC,KAAK;kBAClC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAA,kCAAkC,GAAG,CAAC,WAAW,SAAS,CAAC,CAAC,CAAC,OAAO;;;WAGjG,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AAvCiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;2CAAiC;AAEpD;IAAX,QAAQ,EAAE;yCAAY;AAEM;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;4CAAkB;AAzFnC,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CA4HtB","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\nexport interface RadioCardOption {\n value: string;\n label: string;\n description?: string;\n}\n\nlet instanceCount = 0;\n\n/**\n * Single-select group of full-width cards, each with a label and optional\n * description — for a handful of meaningfully different choices where the\n * description matters. For many short, same-shaped options (a color swatch,\n * a basemap style), use `radio-pills` instead. Wraps native radio inputs for\n * keyboard/a11y and fires `change` rather than relying on form submission.\n *\n * @element radio-cards\n * @fires change - A card was selected; detail: { value }.\n */\n@customElement(\"radio-cards\")\nexport class RadioCards extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .options {\n display: flex;\n gap: 0.5rem;\n flex-wrap: wrap;\n }\n .card {\n flex: 1 1 11.25rem;\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 0.75rem;\n cursor: pointer;\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 .card:has(input:checked) {\n border-color: var(--ui-primary, #4f46e5);\n background: var(--ui-surface-muted, #f8fafc);\n }\n .card input {\n width: auto;\n margin-top: 0.25rem;\n accent-color: var(--ui-primary, #4f46e5);\n cursor: pointer;\n }\n .card-label {\n font-weight: 600;\n color: var(--ui-text, #0f172a);\n }\n .card-description {\n display: block;\n color: var(--ui-text-muted, #64748b);\n font-weight: 400;\n }\n .card:has(input:focus-visible) {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .card:has(input:disabled) {\n cursor: not-allowed;\n opacity: 0.6;\n }\n .card:has(input:disabled) input {\n cursor: not-allowed;\n }\n @media (forced-colors: active) {\n .card:has(input:focus-visible) {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n .card:has(input:checked) {\n border-color: Highlight;\n }\n .card:has(input:disabled) {\n color: GrayText;\n opacity: 1;\n }\n .card:has(input:disabled) .card-label,\n .card:has(input:disabled) .card-description {\n color: GrayText;\n }\n }\n `,\n ];\n\n /** Options to render, one card each. */\n @property({ attribute: false }) options: RadioCardOption[] = [];\n /** Currently selected value. */\n @property() value = \"\";\n /** Disables every native radio in the group. */\n @property({ type: Boolean }) disabled = false;\n\n readonly #name = `radio-cards-${++instanceCount}`;\n\n private _onChange(value: string) {\n if (this.disabled) return;\n this.value = value;\n this.dispatchEvent(new CustomEvent(\"change\", { detail: { value }, bubbles: true, composed: true }));\n }\n\n override render() {\n return html`\n <div class=\"options\">\n ${repeat(\n this.options,\n (opt) => opt.value,\n (opt) => html`\n <label class=\"card\">\n <input\n type=\"radio\"\n name=${this.#name}\n ?checked=${this.value === opt.value}\n ?disabled=${this.disabled}\n @change=${() => this._onChange(opt.value)}\n />\n <span>\n <span class=\"card-label\">${opt.label}</span>\n ${opt.description ? html`<span class=\"card-description\">${opt.description}</span>` : nothing}\n </span>\n </label>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"radio-cards\": RadioCards;\n }\n}\n"]}
@@ -20,6 +20,8 @@ export declare class RadioPills extends LitElement {
20
20
  options: RadioPillOption[];
21
21
  /** Currently selected value. */
22
22
  value: string;
23
+ /** Disables every native radio in the group. */
24
+ disabled: boolean;
23
25
  private _onChange;
24
26
  render(): import("lit-html").TemplateResult<1>;
25
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"radio-pills.d.ts","sourceRoot":"","sources":["../src/radio-pills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAID;;;;;;;;;GASG;AACH,qBACa,UAAW,SAAQ,UAAU;;IACxC,OAAgB,MAAM,4BAmCpB;IAEF,wCAAwC;IACR,OAAO,EAAE,eAAe,EAAE,CAAM;IAChE,gCAAgC;IACpB,KAAK,SAAM;IAIvB,OAAO,CAAC,SAAS;IAKR,MAAM,yCAoBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
1
+ {"version":3,"file":"radio-pills.d.ts","sourceRoot":"","sources":["../src/radio-pills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAID;;;;;;;;;GASG;AACH,qBACa,UAAW,SAAQ,UAAU;;IACxC,OAAgB,MAAM,4BAqEpB;IAEF,wCAAwC;IACR,OAAO,EAAE,eAAe,EAAE,CAAM;IAChE,gCAAgC;IACpB,KAAK,SAAM;IACvB,gDAAgD;IACnB,QAAQ,UAAS;IAI9C,OAAO,CAAC,SAAS;IAMR,MAAM,yCAqBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
@@ -26,6 +26,8 @@ let RadioPills = class RadioPills extends LitElement {
26
26
  this.options = [];
27
27
  /** Currently selected value. */
28
28
  this.value = "";
29
+ /** Disables every native radio in the group. */
30
+ this.disabled = false;
29
31
  this.#name = `radio-pills-${++instanceCount}`;
30
32
  }
31
33
  static { this.styles = [
@@ -37,17 +39,26 @@ let RadioPills = class RadioPills extends LitElement {
37
39
  .options {
38
40
  display: flex;
39
41
  flex-wrap: wrap;
40
- gap: 0.4rem;
42
+ gap: 0.5rem;
41
43
  }
42
44
  .pill {
43
45
  display: flex;
44
46
  align-items: center;
45
- gap: 0.3rem;
46
- padding: 0.3rem 0.6rem;
47
+ gap: 0.25rem;
48
+ padding: 0.25rem 0.75rem;
47
49
  border: 1px solid var(--ui-border, #e2e8f0);
48
50
  border-radius: 999px;
49
51
  cursor: pointer;
50
- 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
+ );
51
62
  font-size: var(--ui-font-size-sm, 0.75rem);
52
63
  color: var(--ui-text, #0f172a);
53
64
  }
@@ -62,10 +73,37 @@ let RadioPills = class RadioPills extends LitElement {
62
73
  cursor: pointer;
63
74
  margin: 0;
64
75
  }
76
+ .pill:has(input:focus-visible) {
77
+ outline: none;
78
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
79
+ }
80
+ .pill:has(input:disabled) {
81
+ cursor: not-allowed;
82
+ opacity: 0.6;
83
+ }
84
+ .pill:has(input:disabled) input {
85
+ cursor: not-allowed;
86
+ }
87
+ @media (forced-colors: active) {
88
+ .pill:has(input:focus-visible) {
89
+ outline: 2px solid CanvasText;
90
+ outline-offset: 2px;
91
+ box-shadow: none;
92
+ }
93
+ .pill:has(input:checked) {
94
+ border-color: Highlight;
95
+ }
96
+ .pill:has(input:disabled) {
97
+ color: GrayText;
98
+ opacity: 1;
99
+ }
100
+ }
65
101
  `,
66
102
  ]; }
67
103
  #name;
68
104
  _onChange(value) {
105
+ if (this.disabled)
106
+ return;
69
107
  this.value = value;
70
108
  this.dispatchEvent(new CustomEvent("change", { detail: { value }, bubbles: true, composed: true }));
71
109
  }
@@ -78,6 +116,7 @@ let RadioPills = class RadioPills extends LitElement {
78
116
  type="radio"
79
117
  name=${this.#name}
80
118
  ?checked=${this.value === opt.value}
119
+ ?disabled=${this.disabled}
81
120
  @change=${() => this._onChange(opt.value)}
82
121
  />
83
122
  <span>${opt.label}</span>
@@ -93,6 +132,9 @@ __decorate([
93
132
  __decorate([
94
133
  property()
95
134
  ], RadioPills.prototype, "value", void 0);
135
+ __decorate([
136
+ property({ type: Boolean })
137
+ ], RadioPills.prototype, "disabled", void 0);
96
138
  RadioPills = __decorate([
97
139
  customElement("radio-pills")
98
140
  ], RadioPills);
@@ -1 +1 @@
1
- {"version":3,"file":"radio-pills.js","sourceRoot":"","sources":["../src/radio-pills.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;AAOrC,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB;;;;;;;;;GASG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QAsCL,wCAAwC;QACR,YAAO,GAAsB,EAAE,CAAC;QAChE,gCAAgC;QACpB,UAAK,GAAG,EAAE,CAAC;QAEd,UAAK,GAAG,eAAe,EAAE,aAAa,EAAE,CAAC;IA4BpD,CAAC;aAtEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgCF;KACF,AAnCqB,CAmCpB;IAOO,KAAK,CAAoC;IAE1C,SAAS,CAAC,KAAa;QAC7B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACtG,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;UAEL,MAAM,CACN,IAAI,CAAC,OAAO,EACZ,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAClB,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;;;uBAIA,IAAI,CAAC,KAAK;2BACN,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK;0BACzB,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;;sBAEnC,GAAG,CAAC,KAAK;;WAEpB,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AAhCiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;2CAAiC;AAEpD;IAAX,QAAQ,EAAE;yCAAY;AAzCZ,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CAuEtB","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 RadioPillOption {\n value: string;\n label: string;\n}\n\nlet instanceCount = 0;\n\n/**\n * Single-select group of compact pill-shaped options — for many short,\n * same-shaped choices (a basemap style, a unit toggle). For a handful of\n * choices where a description matters, use `radio-cards` instead. Wraps\n * native radio inputs for keyboard/a11y and fires `change` rather than\n * relying on form submission.\n *\n * @element radio-pills\n * @fires change - A pill was selected; detail: { value }.\n */\n@customElement(\"radio-pills\")\nexport class RadioPills extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .options {\n display: flex;\n flex-wrap: wrap;\n gap: 0.4rem;\n }\n .pill {\n display: flex;\n align-items: center;\n gap: 0.3rem;\n padding: 0.3rem 0.6rem;\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: 999px;\n cursor: pointer;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size-sm, 0.75rem);\n color: var(--ui-text, #0f172a);\n }\n .pill:has(input:checked) {\n border-color: var(--ui-primary, #4f46e5);\n background: var(--ui-surface-muted, #f8fafc);\n }\n .pill input {\n width: 0.85rem;\n height: 0.85rem;\n accent-color: var(--ui-primary, #4f46e5);\n cursor: pointer;\n margin: 0;\n }\n `,\n ];\n\n /** Options to render, one pill each. */\n @property({ attribute: false }) options: RadioPillOption[] = [];\n /** Currently selected value. */\n @property() value = \"\";\n\n readonly #name = `radio-pills-${++instanceCount}`;\n\n private _onChange(value: string) {\n this.value = value;\n this.dispatchEvent(new CustomEvent(\"change\", { detail: { value }, bubbles: true, composed: true }));\n }\n\n override render() {\n return html`\n <div class=\"options\">\n ${repeat(\n this.options,\n (opt) => opt.value,\n (opt) => html`\n <label class=\"pill\">\n <input\n type=\"radio\"\n name=${this.#name}\n ?checked=${this.value === opt.value}\n @change=${() => this._onChange(opt.value)}\n />\n <span>${opt.label}</span>\n </label>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"radio-pills\": RadioPills;\n }\n}\n"]}
1
+ {"version":3,"file":"radio-pills.js","sourceRoot":"","sources":["../src/radio-pills.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;AAOrC,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB;;;;;;;;;GASG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QAwEL,wCAAwC;QACR,YAAO,GAAsB,EAAE,CAAC;QAChE,gCAAgC;QACpB,UAAK,GAAG,EAAE,CAAC;QACvB,gDAAgD;QACnB,aAAQ,GAAG,KAAK,CAAC;QAErC,UAAK,GAAG,eAAe,EAAE,aAAa,EAAE,CAAC;IA8BpD,CAAC;aA5GiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkEF;KACF,AArEqB,CAqEpB;IASO,KAAK,CAAoC;IAE1C,SAAS,CAAC,KAAa;QAC7B,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACtG,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;UAEL,MAAM,CACN,IAAI,CAAC,OAAO,EACZ,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAClB,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;;;uBAIA,IAAI,CAAC,KAAK;2BACN,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK;4BACvB,IAAI,CAAC,QAAQ;0BACf,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;;sBAEnC,GAAG,CAAC,KAAK;;WAEpB,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AApCiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;2CAAiC;AAEpD;IAAX,QAAQ,EAAE;yCAAY;AAEM;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;4CAAkB;AA7EnC,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CA6GtB","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 RadioPillOption {\n value: string;\n label: string;\n}\n\nlet instanceCount = 0;\n\n/**\n * Single-select group of compact pill-shaped options — for many short,\n * same-shaped choices (a basemap style, a unit toggle). For a handful of\n * choices where a description matters, use `radio-cards` instead. Wraps\n * native radio inputs for keyboard/a11y and fires `change` rather than\n * relying on form submission.\n *\n * @element radio-pills\n * @fires change - A pill was selected; detail: { value }.\n */\n@customElement(\"radio-pills\")\nexport class RadioPills extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .options {\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n }\n .pill {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.25rem 0.75rem;\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: 999px;\n cursor: pointer;\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 color: var(--ui-text, #0f172a);\n }\n .pill:has(input:checked) {\n border-color: var(--ui-primary, #4f46e5);\n background: var(--ui-surface-muted, #f8fafc);\n }\n .pill input {\n width: 0.85rem;\n height: 0.85rem;\n accent-color: var(--ui-primary, #4f46e5);\n cursor: pointer;\n margin: 0;\n }\n .pill:has(input:focus-visible) {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .pill:has(input:disabled) {\n cursor: not-allowed;\n opacity: 0.6;\n }\n .pill:has(input:disabled) input {\n cursor: not-allowed;\n }\n @media (forced-colors: active) {\n .pill:has(input:focus-visible) {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n .pill:has(input:checked) {\n border-color: Highlight;\n }\n .pill:has(input:disabled) {\n color: GrayText;\n opacity: 1;\n }\n }\n `,\n ];\n\n /** Options to render, one pill each. */\n @property({ attribute: false }) options: RadioPillOption[] = [];\n /** Currently selected value. */\n @property() value = \"\";\n /** Disables every native radio in the group. */\n @property({ type: Boolean }) disabled = false;\n\n readonly #name = `radio-pills-${++instanceCount}`;\n\n private _onChange(value: string) {\n if (this.disabled) return;\n this.value = value;\n this.dispatchEvent(new CustomEvent(\"change\", { detail: { value }, bubbles: true, composed: true }));\n }\n\n override render() {\n return html`\n <div class=\"options\">\n ${repeat(\n this.options,\n (opt) => opt.value,\n (opt) => html`\n <label class=\"pill\">\n <input\n type=\"radio\"\n name=${this.#name}\n ?checked=${this.value === opt.value}\n ?disabled=${this.disabled}\n @change=${() => this._onChange(opt.value)}\n />\n <span>${opt.label}</span>\n </label>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"radio-pills\": RadioPills;\n }\n}\n"]}
@@ -9,7 +9,6 @@ import { LitElement } from "lit";
9
9
  * @element relative-time
10
10
  */
11
11
  export declare class RelativeTime extends LitElement {
12
- static styles: import("lit").CSSResult;
13
12
  /** Timestamp to render, relative to now. */
14
13
  datetime: string | null;
15
14
  private _now;
@@ -1 +1 @@
1
- {"version":3,"file":"relative-time.d.ts","sourceRoot":"","sources":["../src/relative-time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAM5C;;;;;;;;GAQG;AACH,qBACa,YAAa,SAAQ,UAAU;IAC1C,OAAgB,MAAM,0BAIpB;IAEF,4CAA4C;IAChC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAElC,OAAO,CAAC,IAAI,CAAc;IAEnC,OAAO,CAAC,MAAM,CAA+C;IAEpD,iBAAiB,SAKzB;IAEQ,oBAAoB,SAI5B;IAEQ,MAAM,yCAKd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,YAAY,CAAC;KAC/B;CACF"}
1
+ {"version":3,"file":"relative-time.d.ts","sourceRoot":"","sources":["../src/relative-time.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,MAAM,KAAK,CAAC;AAMvC;;;;;;;;GAQG;AACH,qBACa,YAAa,SAAQ,UAAU;IAC1C,4CAA4C;IAChC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAElC,OAAO,CAAC,IAAI,CAAc;IAEnC,OAAO,CAAC,MAAM,CAA+C;IAEpD,iBAAiB,SAKzB;IAEQ,oBAAoB,SAI5B;IAEQ,MAAM,yCAKd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,YAAY,CAAC;KAC/B;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 } from "lit";
7
+ import { LitElement, html } from "lit";
8
8
  import { customElement, property, state } from "lit/decorators.js";
9
9
  import { parseTimestamp, formatRelativeTime } from "./utils/time.js";
10
10
  const REFRESH_INTERVAL_MS = 60_000;
@@ -25,11 +25,6 @@ let RelativeTime = class RelativeTime extends LitElement {
25
25
  this._now = Date.now();
26
26
  this._timer = null;
27
27
  }
28
- static { this.styles = css `
29
- :host {
30
- display: inline;
31
- }
32
- `; }
33
28
  connectedCallback() {
34
29
  super.connectedCallback();
35
30
  this._timer = setInterval(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"relative-time.js","sourceRoot":"","sources":["../src/relative-time.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,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErE,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEnC;;;;;;;;GAQG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAArC;;QAOL,4CAA4C;QAChC,aAAQ,GAAkB,IAAI,CAAC;QAE1B,SAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE3B,WAAM,GAA0C,IAAI,CAAC;IAqB/D,CAAC;aAhCiB,WAAM,GAAG,GAAG,CAAA;;;;GAI3B,AAJqB,CAIpB;IASO,iBAAiB;QACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;YAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,CAAC,EAAE,mBAAmB,CAAC,CAAC;IAC1B,CAAC;IAEQ,oBAAoB;QAC3B,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI;YAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAEQ,MAAM;QACb,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAA,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAAE,OAAO,IAAI,CAAA,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChE,OAAO,IAAI,CAAA,eAAe,IAAI,CAAC,cAAc,EAAE,IAAI,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;IAClG,CAAC;CACF,CAAA;AAzBa;IAAX,QAAQ,EAAE;8CAAgC;AAE1B;IAAhB,KAAK,EAAE;0CAA2B;AAVxB,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAiCxB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { parseTimestamp, formatRelativeTime } from \"./utils/time.js\";\n\nconst REFRESH_INTERVAL_MS = 60_000;\n\n/**\n * Inline relative-time display (e.g. \"3 hours ago\"). Accepts either a\n * standard ISO 8601 string or a SQLite `datetime('now')` string\n * (\"YYYY-MM-DD HH:MM:SS\", UTC, no zone marker) via `datetime`. Shows the\n * full date/time in the viewer's local timezone as a hover tooltip, and\n * re-renders on an interval so the text stays current while visible.\n *\n * @element relative-time\n */\n@customElement(\"relative-time\")\nexport class RelativeTime extends LitElement {\n static override styles = css`\n :host {\n display: inline;\n }\n `;\n\n /** Timestamp to render, relative to now. */\n @property() datetime: string | null = null;\n\n @state() private _now = Date.now();\n\n private _timer: ReturnType<typeof setInterval> | null = null;\n\n override connectedCallback() {\n super.connectedCallback();\n this._timer = setInterval(() => {\n this._now = Date.now();\n }, REFRESH_INTERVAL_MS);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n if (this._timer != null) clearInterval(this._timer);\n this._timer = null;\n }\n\n override render() {\n if (!this.datetime) return html``;\n const date = parseTimestamp(this.datetime);\n if (Number.isNaN(date.getTime())) return html`${this.datetime}`;\n return html`<span title=${date.toLocaleString()}>${formatRelativeTime(date, this._now)}</span>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"relative-time\": RelativeTime;\n }\n}\n"]}
1
+ {"version":3,"file":"relative-time.js","sourceRoot":"","sources":["../src/relative-time.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErE,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEnC;;;;;;;;GAQG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAArC;;QACL,4CAA4C;QAChC,aAAQ,GAAkB,IAAI,CAAC;QAE1B,SAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE3B,WAAM,GAA0C,IAAI,CAAC;IAqB/D,CAAC;IAnBU,iBAAiB;QACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;YAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,CAAC,EAAE,mBAAmB,CAAC,CAAC;IAC1B,CAAC;IAEQ,oBAAoB;QAC3B,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI;YAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAEQ,MAAM;QACb,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAA,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAAE,OAAO,IAAI,CAAA,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChE,OAAO,IAAI,CAAA,eAAe,IAAI,CAAC,cAAc,EAAE,IAAI,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;IAClG,CAAC;CACF,CAAA;AAzBa;IAAX,QAAQ,EAAE;8CAAgC;AAE1B;IAAhB,KAAK,EAAE;0CAA2B;AAJxB,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CA2BxB","sourcesContent":["import { LitElement, html } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { parseTimestamp, formatRelativeTime } from \"./utils/time.js\";\n\nconst REFRESH_INTERVAL_MS = 60_000;\n\n/**\n * Inline relative-time display (e.g. \"3 hours ago\"). Accepts either a\n * standard ISO 8601 string or a SQLite `datetime('now')` string\n * (\"YYYY-MM-DD HH:MM:SS\", UTC, no zone marker) via `datetime`. Shows the\n * full date/time in the viewer's local timezone as a hover tooltip, and\n * re-renders on an interval so the text stays current while visible.\n *\n * @element relative-time\n */\n@customElement(\"relative-time\")\nexport class RelativeTime extends LitElement {\n /** Timestamp to render, relative to now. */\n @property() datetime: string | null = null;\n\n @state() private _now = Date.now();\n\n private _timer: ReturnType<typeof setInterval> | null = null;\n\n override connectedCallback() {\n super.connectedCallback();\n this._timer = setInterval(() => {\n this._now = Date.now();\n }, REFRESH_INTERVAL_MS);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n if (this._timer != null) clearInterval(this._timer);\n this._timer = null;\n }\n\n override render() {\n if (!this.datetime) return html``;\n const date = parseTimestamp(this.datetime);\n if (Number.isNaN(date.getTime())) return html`${this.datetime}`;\n return html`<span title=${date.toLocaleString()}>${formatRelativeTime(date, this._now)}</span>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"relative-time\": RelativeTime;\n }\n}\n"]}
@@ -8,6 +8,8 @@ export declare class RevealButton extends LitElement {
8
8
  static styles: import("lit").CSSResult[];
9
9
  /** Label shown on the button before it's clicked. */
10
10
  label: string;
11
+ /** Disables revealing the slotted content. */
12
+ disabled: boolean;
11
13
  private _reveal;
12
14
  protected render(): import("lit-html").TemplateResult<1>;
13
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"reveal-button.d.ts","sourceRoot":"","sources":["../src/reveal-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;GAIG;AACH,qBACa,YAAa,SAAQ,UAAU;IAC1C,OAAgB,MAAM,4BAoBpB;IAEF,qDAAqD;IAErD,KAAK,EAAE,MAAM,CAA2B;IAExC,OAAO,CAAC,OAAO;IAOf,UAAmB,MAAM,yCAKxB;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,YAAY,CAAC;KAC/B;CACF"}
1
+ {"version":3,"file":"reveal-button.d.ts","sourceRoot":"","sources":["../src/reveal-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;GAIG;AACH,qBACa,YAAa,SAAQ,UAAU;IAC1C,OAAgB,MAAM,4BAgDpB;IAEF,qDAAqD;IAErD,KAAK,EAAE,MAAM,CAA2B;IACxC,8CAA8C;IACjB,QAAQ,UAAS;IAE9C,OAAO,CAAC,OAAO;IAQf,UAAmB,MAAM,yCAOxB;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,YAAY,CAAC;KAC/B;CACF"}
@@ -17,6 +17,8 @@ let RevealButton = class RevealButton extends LitElement {
17
17
  super(...arguments);
18
18
  /** Label shown on the button before it's clicked. */
19
19
  this.label = "Reveal hidden content";
20
+ /** Disables revealing the slotted content. */
21
+ this.disabled = false;
20
22
  }
21
23
  static { this.styles = [
22
24
  tokens,
@@ -25,21 +27,51 @@ let RevealButton = class RevealButton extends LitElement {
25
27
  display: none;
26
28
  }
27
29
  button {
28
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
30
+ font-family: var(
31
+ --ui-font,
32
+ ui-sans-serif,
33
+ system-ui,
34
+ sans-serif,
35
+ "Apple Color Emoji",
36
+ "Segoe UI Emoji",
37
+ "Segoe UI Symbol",
38
+ "Noto Color Emoji"
39
+ );
29
40
  font-size: var(--ui-font-size, 0.875rem);
30
41
  background: var(--ui-primary, #4f46e5);
31
- color: #fff;
42
+ color: var(--ui-on-accent, #ffffff);
32
43
  border: none;
33
44
  border-radius: var(--ui-radius-sm, 0.25rem);
34
- padding: 0.5rem 0.9rem;
45
+ padding: 0.5rem 1rem;
35
46
  cursor: pointer;
36
47
  }
37
- button:hover {
48
+ button:hover:not(:disabled) {
38
49
  background: var(--ui-primary-hover, #4338ca);
39
50
  }
51
+ button:disabled {
52
+ cursor: not-allowed;
53
+ opacity: 0.6;
54
+ }
55
+ button:focus-visible {
56
+ outline: none;
57
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
58
+ }
59
+ @media (forced-colors: active) {
60
+ button:focus-visible {
61
+ outline: 2px solid CanvasText;
62
+ outline-offset: 2px;
63
+ box-shadow: none;
64
+ }
65
+ button:disabled {
66
+ color: GrayText;
67
+ opacity: 1;
68
+ }
69
+ }
40
70
  `,
41
71
  ]; }
42
72
  _reveal() {
73
+ if (this.disabled)
74
+ return;
43
75
  const div = this.shadowRoot.querySelector("div");
44
76
  const button = this.shadowRoot.querySelector("button");
45
77
  div?.classList.toggle("hidden");
@@ -48,13 +80,18 @@ let RevealButton = class RevealButton extends LitElement {
48
80
  render() {
49
81
  return html `
50
82
  <div class="hidden"><slot></slot></div>
51
- <button @click="${this._reveal}">${this.label}</button>
83
+ <button type="button" ?disabled=${this.disabled} @click=${this._reveal}>
84
+ ${this.label}
85
+ </button>
52
86
  `;
53
87
  }
54
88
  };
55
89
  __decorate([
56
90
  property({ type: String })
57
91
  ], RevealButton.prototype, "label", void 0);
92
+ __decorate([
93
+ property({ type: Boolean })
94
+ ], RevealButton.prototype, "disabled", void 0);
58
95
  RevealButton = __decorate([
59
96
  customElement("reveal-button")
60
97
  ], RevealButton);
@@ -1 +1 @@
1
- {"version":3,"file":"reveal-button.js","sourceRoot":"","sources":["../src/reveal-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,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAArC;;QAuBL,qDAAqD;QAErD,UAAK,GAAW,uBAAuB,CAAC;IAe1C,CAAC;aAvCiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;KAiBF;KACF,AApBqB,CAoBpB;IAMM,OAAO;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,UAAW,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACxD,GAAG,EAAE,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAEkB,MAAM;QACvB,OAAO,IAAI,CAAA;;wBAES,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,KAAK;KAC9C,CAAC;IACJ,CAAC;CACF,CAAA;AAfC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;2CACa;AAzB7B,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAwCxB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Button that reveals hidden slotted content when clicked.\n *\n * @element reveal-button\n */\n@customElement(\"reveal-button\")\nexport class RevealButton extends LitElement {\n static override styles = [\n tokens,\n css`\n .hidden {\n display: none;\n }\n button {\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size, 0.875rem);\n background: var(--ui-primary, #4f46e5);\n color: #fff;\n border: none;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 0.9rem;\n cursor: pointer;\n }\n button:hover {\n background: var(--ui-primary-hover, #4338ca);\n }\n `,\n ];\n\n /** Label shown on the button before it's clicked. */\n @property({ type: String })\n label: string = \"Reveal hidden content\";\n\n private _reveal() {\n const div = this.shadowRoot!.querySelector(\"div\");\n const button = this.shadowRoot!.querySelector(\"button\");\n div?.classList.toggle(\"hidden\");\n button?.classList.toggle(\"hidden\");\n }\n\n protected override render() {\n return html`\n <div class=\"hidden\"><slot></slot></div>\n <button @click=\"${this._reveal}\">${this.label}</button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"reveal-button\": RevealButton;\n }\n}\n"]}
1
+ {"version":3,"file":"reveal-button.js","sourceRoot":"","sources":["../src/reveal-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,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAArC;;QAmDL,qDAAqD;QAErD,UAAK,GAAW,uBAAuB,CAAC;QACxC,8CAA8C;QACjB,aAAQ,GAAG,KAAK,CAAC;IAkBhD,CAAC;aAxEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6CF;KACF,AAhDqB,CAgDpB;IAQM,OAAO;QACb,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,UAAW,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAW,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACxD,GAAG,EAAE,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAEkB,MAAM;QACvB,OAAO,IAAI,CAAA;;wCAEyB,IAAI,CAAC,QAAQ,WAAW,IAAI,CAAC,OAAO;UAClE,IAAI,CAAC,KAAK;;KAEf,CAAC;IACJ,CAAC;CACF,CAAA;AApBC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;2CACa;AAEX;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;8CAAkB;AAvDnC,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAyExB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Button that reveals hidden slotted content when clicked.\n *\n * @element reveal-button\n */\n@customElement(\"reveal-button\")\nexport class RevealButton extends LitElement {\n static override styles = [\n tokens,\n css`\n .hidden {\n display: none;\n }\n button {\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 background: var(--ui-primary, #4f46e5);\n color: var(--ui-on-accent, #ffffff);\n border: none;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 1rem;\n cursor: pointer;\n }\n button:hover:not(:disabled) {\n background: var(--ui-primary-hover, #4338ca);\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 /** Label shown on the button before it's clicked. */\n @property({ type: String })\n label: string = \"Reveal hidden content\";\n /** Disables revealing the slotted content. */\n @property({ type: Boolean }) disabled = false;\n\n private _reveal() {\n if (this.disabled) return;\n const div = this.shadowRoot!.querySelector(\"div\");\n const button = this.shadowRoot!.querySelector(\"button\");\n div?.classList.toggle(\"hidden\");\n button?.classList.toggle(\"hidden\");\n }\n\n protected override render() {\n return html`\n <div class=\"hidden\"><slot></slot></div>\n <button type=\"button\" ?disabled=${this.disabled} @click=${this._reveal}>\n ${this.label}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"reveal-button\": RevealButton;\n }\n}\n"]}
@@ -31,7 +31,7 @@ let RomanNumeral = class RomanNumeral extends LitElement {
31
31
  return html `${this._convertToRoman(this.value)}`;
32
32
  }
33
33
  _convertToRoman(num) {
34
- if (!num) {
34
+ if (num == null || !Number.isInteger(num) || num <= 0) {
35
35
  return "NaN";
36
36
  }
37
37
  let remaining = num;
@@ -1 +1 @@
1
- {"version":3,"file":"roman-numeral.js","sourceRoot":"","sources":["../src/roman-numeral.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE5D,MAAM,cAAc,GAA2B;IAC7C,CAAC,EAAE,IAAI;IACP,EAAE,EAAE,GAAG;IACP,CAAC,EAAE,GAAG;IACN,EAAE,EAAE,GAAG;IACP,CAAC,EAAE,GAAG;IACN,EAAE,EAAE,EAAE;IACN,CAAC,EAAE,EAAE;IACL,EAAE,EAAE,EAAE;IACN,CAAC,EAAE,EAAE;IACL,EAAE,EAAE,CAAC;IACL,CAAC,EAAE,CAAC;IACJ,EAAE,EAAE,CAAC;IACL,CAAC,EAAE,CAAC;CACL,CAAC;AAEF;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAKvB,MAAM;QACvB,OAAO,IAAI,CAAA,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IACnD,CAAC;IAEO,eAAe,CAAC,GAAY;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,SAAS,GAAG,GAAG,CAAC;QACpB,IAAI,GAAG,GAAG,EAAE,CAAC;QAEb,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;YAC7D,SAAS,IAAI,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;YAC5C,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;QAED,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAA;AAtBC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;2CACZ;AAHJ,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAyBxB","sourcesContent":["import { LitElement, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nconst ROMAN_NUMERALS: Record<string, number> = {\n M: 1000,\n CM: 900,\n D: 500,\n CD: 400,\n C: 100,\n XC: 90,\n L: 50,\n XL: 40,\n X: 10,\n IX: 9,\n V: 5,\n IV: 4,\n I: 1,\n};\n\n/**\n * Converts an integer to a roman numeral inline.\n *\n * @element roman-numeral\n */\n@customElement(\"roman-numeral\")\nexport class RomanNumeral extends LitElement {\n /** Integer value to render as a roman numeral. */\n @property({ type: Number })\n value?: number;\n\n protected override render() {\n return html`${this._convertToRoman(this.value)}`;\n }\n\n private _convertToRoman(num?: number): string {\n if (!num) {\n return \"NaN\";\n }\n\n let remaining = num;\n let str = \"\";\n\n for (const symbol of Object.keys(ROMAN_NUMERALS)) {\n const count = Math.floor(remaining / ROMAN_NUMERALS[symbol]);\n remaining -= count * ROMAN_NUMERALS[symbol];\n str += symbol.repeat(count);\n }\n\n return str;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"roman-numeral\": RomanNumeral;\n }\n}\n"]}
1
+ {"version":3,"file":"roman-numeral.js","sourceRoot":"","sources":["../src/roman-numeral.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE5D,MAAM,cAAc,GAA2B;IAC7C,CAAC,EAAE,IAAI;IACP,EAAE,EAAE,GAAG;IACP,CAAC,EAAE,GAAG;IACN,EAAE,EAAE,GAAG;IACP,CAAC,EAAE,GAAG;IACN,EAAE,EAAE,EAAE;IACN,CAAC,EAAE,EAAE;IACL,EAAE,EAAE,EAAE;IACN,CAAC,EAAE,EAAE;IACL,EAAE,EAAE,CAAC;IACL,CAAC,EAAE,CAAC;IACJ,EAAE,EAAE,CAAC;IACL,CAAC,EAAE,CAAC;CACL,CAAC;AAEF;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAKvB,MAAM;QACvB,OAAO,IAAI,CAAA,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IACnD,CAAC;IAEO,eAAe,CAAC,GAAY;QAClC,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YACtD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,SAAS,GAAG,GAAG,CAAC;QACpB,IAAI,GAAG,GAAG,EAAE,CAAC;QAEb,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;YAC7D,SAAS,IAAI,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;YAC5C,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;QAED,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAA;AAtBC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;2CACZ;AAHJ,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAyBxB","sourcesContent":["import { LitElement, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nconst ROMAN_NUMERALS: Record<string, number> = {\n M: 1000,\n CM: 900,\n D: 500,\n CD: 400,\n C: 100,\n XC: 90,\n L: 50,\n XL: 40,\n X: 10,\n IX: 9,\n V: 5,\n IV: 4,\n I: 1,\n};\n\n/**\n * Converts an integer to a roman numeral inline.\n *\n * @element roman-numeral\n */\n@customElement(\"roman-numeral\")\nexport class RomanNumeral extends LitElement {\n /** Integer value to render as a roman numeral. */\n @property({ type: Number })\n value?: number;\n\n protected override render() {\n return html`${this._convertToRoman(this.value)}`;\n }\n\n private _convertToRoman(num?: number): string {\n if (num == null || !Number.isInteger(num) || num <= 0) {\n return \"NaN\";\n }\n\n let remaining = num;\n let str = \"\";\n\n for (const symbol of Object.keys(ROMAN_NUMERALS)) {\n const count = Math.floor(remaining / ROMAN_NUMERALS[symbol]);\n remaining -= count * ROMAN_NUMERALS[symbol];\n str += symbol.repeat(count);\n }\n\n return str;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"roman-numeral\": RomanNumeral;\n }\n}\n"]}
@@ -1,11 +1,11 @@
1
- import { LitElement } from "lit";
1
+ import { LitElement, type PropertyValues } from "lit";
2
2
  /**
3
3
  * Generic sliding panel shell. Handles positioning, open/close animation,
4
4
  * header chrome, and a close button. Body content is provided via the
5
5
  * default slot; the consumer controls its own padding and overflow.
6
6
  *
7
- * Desktop: 300 px fixed right-side panel that slides from the right.
8
- * Mobile (≤768px): bottom-sheet drawer (60vh) — reserved for future use.
7
+ * Desktop: fixed right-side panel that slides from the right.
8
+ * Mobile (≤48rem): bottom-sheet drawer (60vh) — reserved for future use.
9
9
  *
10
10
  * @element slide-panel
11
11
  * @fires panel-close - User clicked the close (✕) button.
@@ -16,7 +16,11 @@ export declare class SlidePanel extends LitElement {
16
16
  /** Title text shown in the panel header (overridable via slot="title"). */
17
17
  heading: string;
18
18
  static styles: import("lit").CSSResult[];
19
+ private _previousFocus;
19
20
  private _close;
21
+ private _onWindowKeydown;
22
+ protected updated(changed: PropertyValues): void;
23
+ disconnectedCallback(): void;
20
24
  render(): import("lit-html").TemplateResult<1>;
21
25
  }
22
26
  declare global {
@@ -1 +1 @@
1
- {"version":3,"file":"slide-panel.d.ts","sourceRoot":"","sources":["../src/slide-panel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C;;;;;;;;;;GAUG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,8CAA8C;IACjB,IAAI,UAAS;IAC1C,2EAA2E;IAC/D,OAAO,SAAM;IAEzB,OAAgB,MAAM,4BA0FpB;IAEF,OAAO,CAAC,MAAM;IAIL,MAAM,yCAad;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
1
+ {"version":3,"file":"slide-panel.d.ts","sourceRoot":"","sources":["../src/slide-panel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAM1E;;;;;;;;;;GAUG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,8CAA8C;IACjB,IAAI,UAAS;IAC1C,2EAA2E;IAC/D,OAAO,SAAM;IAEzB,OAAgB,MAAM,4BAuHpB;IAEF,OAAO,CAAC,cAAc,CAA4B;IAElD,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,gBAAgB,CAGtB;IAEF,UAAmB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAaxD;IAEQ,oBAAoB,IAAI,IAAI,CAOpC;IAEQ,MAAM,yCAwBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
@@ -4,17 +4,18 @@ 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 } from "lit";
7
+ import { LitElement, css, html, nothing } from "lit";
8
8
  import { customElement, property } from "lit/decorators.js";
9
9
  import { iconX } from "./icons.js";
10
10
  import { tokens } from "./tokens.js";
11
+ import { activateLayer, claimEscape, deactivateLayer } from "./utils/layer-stack.js";
11
12
  /**
12
13
  * Generic sliding panel shell. Handles positioning, open/close animation,
13
14
  * header chrome, and a close button. Body content is provided via the
14
15
  * default slot; the consumer controls its own padding and overflow.
15
16
  *
16
- * Desktop: 300 px fixed right-side panel that slides from the right.
17
- * Mobile (≤768px): bottom-sheet drawer (60vh) — reserved for future use.
17
+ * Desktop: fixed right-side panel that slides from the right.
18
+ * Mobile (≤48rem): bottom-sheet drawer (60vh) — reserved for future use.
18
19
  *
19
20
  * @element slide-panel
20
21
  * @fires panel-close - User clicked the close (✕) button.
@@ -26,6 +27,12 @@ let SlidePanel = class SlidePanel extends LitElement {
26
27
  this.open = false;
27
28
  /** Title text shown in the panel header (overridable via slot="title"). */
28
29
  this.heading = "";
30
+ this._previousFocus = null;
31
+ this._onWindowKeydown = (event) => {
32
+ if (!this.open || !claimEscape(this, event))
33
+ return;
34
+ this._close();
35
+ };
29
36
  }
30
37
  static { this.styles = [
31
38
  tokens,
@@ -38,12 +45,12 @@ let SlidePanel = class SlidePanel extends LitElement {
38
45
  top: 0;
39
46
  right: 0;
40
47
  bottom: 0;
41
- width: 300px;
42
- z-index: 20;
43
- background: var(--ui-surface, #fff);
48
+ width: 18.75rem;
49
+ z-index: var(--component-layer-z, 20);
50
+ background: var(--ui-surface, #ffffff);
44
51
  box-shadow: var(--ui-shadow-lg, 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1));
45
52
  transform: translateX(110%);
46
- transition: transform 0.25s ease;
53
+ transition: transform 250ms ease;
47
54
  display: flex;
48
55
  flex-direction: column;
49
56
  overflow: hidden;
@@ -55,14 +62,23 @@ let SlidePanel = class SlidePanel extends LitElement {
55
62
  display: flex;
56
63
  align-items: center;
57
64
  justify-content: space-between;
58
- padding: 0.5rem 0.625rem;
65
+ padding: 0.5rem;
59
66
  border-bottom: 1px solid var(--ui-border, #e2e8f0);
60
67
  flex: 0 0 auto;
61
68
  }
62
69
  .panel-title {
63
70
  font-weight: 600;
64
71
  font-size: var(--ui-font-size, 0.875rem);
65
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
72
+ font-family: var(
73
+ --ui-font,
74
+ ui-sans-serif,
75
+ system-ui,
76
+ sans-serif,
77
+ "Apple Color Emoji",
78
+ "Segoe UI Emoji",
79
+ "Segoe UI Symbol",
80
+ "Noto Color Emoji"
81
+ );
66
82
  color: var(--ui-text, #0f172a);
67
83
  flex: 1 1 auto;
68
84
  overflow: hidden;
@@ -85,10 +101,26 @@ let SlidePanel = class SlidePanel extends LitElement {
85
101
  .close-btn:hover {
86
102
  background: var(--ui-surface-muted, #f8fafc);
87
103
  }
104
+ .close-btn:focus-visible {
105
+ outline: none;
106
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
107
+ }
88
108
  .handle {
89
109
  display: none;
90
110
  }
91
- @media (max-width: 768px) {
111
+ @media (prefers-reduced-motion: reduce) {
112
+ .panel {
113
+ transition: none;
114
+ }
115
+ }
116
+ @media (forced-colors: active) {
117
+ .close-btn:focus-visible {
118
+ outline: 2px solid CanvasText;
119
+ outline-offset: 2px;
120
+ box-shadow: none;
121
+ }
122
+ }
123
+ @media (max-width: 48rem) {
92
124
  .panel {
93
125
  top: auto;
94
126
  right: 0;
@@ -96,8 +128,12 @@ let SlidePanel = class SlidePanel extends LitElement {
96
128
  left: 0;
97
129
  width: auto;
98
130
  height: 60vh;
99
- border-radius: 14px 14px 0 0;
100
- box-shadow: 0 -3px 20px rgb(0 0 0 / 0.18);
131
+ border-radius: var(--ui-radius, 0.5rem) var(--ui-radius, 0.5rem) 0 0;
132
+ box-shadow: var(
133
+ --ui-shadow-lg,
134
+ 0 20px 25px -5px rgb(0 0 0 / 0.1),
135
+ 0 8px 10px -6px rgb(0 0 0 / 0.1)
136
+ );
101
137
  transform: translateY(110%);
102
138
  }
103
139
  .panel.open {
@@ -105,15 +141,15 @@ let SlidePanel = class SlidePanel extends LitElement {
105
141
  }
106
142
  .handle {
107
143
  display: block;
108
- width: 36px;
109
- height: 4px;
144
+ width: 2.25rem;
145
+ height: 0.25rem;
110
146
  background: var(--ui-border, #e2e8f0);
111
- border-radius: 2px;
112
- margin: 10px auto 0;
147
+ border-radius: 9999px;
148
+ margin: 0.5rem auto 0;
113
149
  flex: 0 0 auto;
114
150
  }
115
151
  .panel-header {
116
- padding: 4px 10px 8px;
152
+ padding: 0.25rem 0.5rem 0.5rem;
117
153
  }
118
154
  }
119
155
  `,
@@ -121,15 +157,51 @@ let SlidePanel = class SlidePanel extends LitElement {
121
157
  _close() {
122
158
  this.dispatchEvent(new CustomEvent("panel-close", { bubbles: true, composed: true }));
123
159
  }
160
+ updated(changed) {
161
+ if (!changed.has("open"))
162
+ return;
163
+ if (this.open) {
164
+ activateLayer(this);
165
+ this._previousFocus = document.activeElement;
166
+ window.addEventListener("keydown", this._onWindowKeydown);
167
+ this.shadowRoot?.querySelector(".close-btn")?.focus();
168
+ return;
169
+ }
170
+ deactivateLayer(this);
171
+ window.removeEventListener("keydown", this._onWindowKeydown);
172
+ if (this._previousFocus?.isConnected)
173
+ this._previousFocus.focus();
174
+ this._previousFocus = null;
175
+ }
176
+ disconnectedCallback() {
177
+ super.disconnectedCallback();
178
+ window.removeEventListener("keydown", this._onWindowKeydown);
179
+ deactivateLayer(this);
180
+ if (this._previousFocus?.isConnected)
181
+ this._previousFocus.focus();
182
+ this._previousFocus = null;
183
+ this.open = false;
184
+ }
124
185
  render() {
186
+ const explicitLabel = this.getAttribute("aria-label");
125
187
  return html `
126
- <div class="panel ${this.open ? "open" : ""}">
127
- <div class="handle"></div>
188
+ <div
189
+ class="panel ${this.open ? "open" : ""}"
190
+ role="dialog"
191
+ aria-modal="false"
192
+ aria-label=${explicitLabel ?? nothing}
193
+ aria-labelledby=${explicitLabel ? nothing : "panel-title"}
194
+ aria-hidden=${String(!this.open)}
195
+ ?inert=${!this.open}
196
+ >
197
+ <div class="handle" aria-hidden="true"></div>
128
198
  <div class="panel-header">
129
- <slot name="title">
130
- ${this.heading ? html `<span class="panel-title">${this.heading}</span>` : null}
131
- </slot>
132
- <button class="close-btn" aria-label="Close panel" @click=${this._close}>${iconX(18)}</button>
199
+ <span id="panel-title" class="panel-title">
200
+ <slot name="title">${this.heading || "Panel"}</slot>
201
+ </span>
202
+ <button class="close-btn" aria-label="Close panel" @click=${this._close}>
203
+ <span aria-hidden="true">${iconX(18)}</span>
204
+ </button>
133
205
  </div>
134
206
  <slot></slot>
135
207
  </div>