@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":"slide-panel.js","sourceRoot":"","sources":["../src/slide-panel.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,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;GAUG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QACL,8CAA8C;QACjB,SAAI,GAAG,KAAK,CAAC;QAC1C,2EAA2E;QAC/D,YAAO,GAAG,EAAE,CAAC;IAgH3B,CAAC;aA9GiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuFF;KACF,AA1FqB,CA0FpB;IAEM,MAAM;QACZ,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACxF,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;0BACW,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;cAInC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,6BAA6B,IAAI,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,IAAI;;sEAEpB,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC;;;;KAIzF,CAAC;IACJ,CAAC;CACF,CAAA;AAlH8B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;wCAAc;AAE9B;IAAX,QAAQ,EAAE;2CAAc;AAJd,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CAoHtB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Generic sliding panel shell. Handles positioning, open/close animation,\n * header chrome, and a close button. Body content is provided via the\n * default slot; the consumer controls its own padding and overflow.\n *\n * Desktop: 300 px fixed right-side panel that slides from the right.\n * Mobile (≤768px): bottom-sheet drawer (60vh) — reserved for future use.\n *\n * @element slide-panel\n * @fires panel-close - User clicked the close (✕) button.\n */\n@customElement(\"slide-panel\")\nexport class SlidePanel extends LitElement {\n /** Whether the panel is currently visible. */\n @property({ type: Boolean }) open = false;\n /** Title text shown in the panel header (overridable via slot=\"title\"). */\n @property() heading = \"\";\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 300px;\n z-index: 20;\n background: var(--ui-surface, #fff);\n 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));\n transform: translateX(110%);\n transition: transform 0.25s ease;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .panel.open {\n transform: translateX(0);\n }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 0.625rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n flex: 0 0 auto;\n }\n .panel-title {\n font-weight: 600;\n font-size: var(--ui-font-size, 0.875rem);\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n color: var(--ui-text, #0f172a);\n flex: 1 1 auto;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n padding-right: 0.5rem;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.25rem 0.5rem;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n .close-btn:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .handle {\n display: none;\n }\n @media (max-width: 768px) {\n .panel {\n top: auto;\n right: 0;\n bottom: 0;\n left: 0;\n width: auto;\n height: 60vh;\n border-radius: 14px 14px 0 0;\n box-shadow: 0 -3px 20px rgb(0 0 0 / 0.18);\n transform: translateY(110%);\n }\n .panel.open {\n transform: translateY(0);\n }\n .handle {\n display: block;\n width: 36px;\n height: 4px;\n background: var(--ui-border, #e2e8f0);\n border-radius: 2px;\n margin: 10px auto 0;\n flex: 0 0 auto;\n }\n .panel-header {\n padding: 4px 10px 8px;\n }\n }\n `,\n ];\n\n private _close() {\n this.dispatchEvent(new CustomEvent(\"panel-close\", { bubbles: true, composed: true }));\n }\n\n override render() {\n return html`\n <div class=\"panel ${this.open ? \"open\" : \"\"}\">\n <div class=\"handle\"></div>\n <div class=\"panel-header\">\n <slot name=\"title\">\n ${this.heading ? html`<span class=\"panel-title\">${this.heading}</span>` : null}\n </slot>\n <button class=\"close-btn\" aria-label=\"Close panel\" @click=${this._close}>${iconX(18)}</button>\n </div>\n <slot></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"slide-panel\": SlidePanel;\n }\n}\n"]}
1
+ {"version":3,"file":"slide-panel.js","sourceRoot":"","sources":["../src/slide-panel.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 { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\nimport { activateLayer, claimEscape, deactivateLayer } from \"./utils/layer-stack.js\";\n\n/**\n * Generic sliding panel shell. Handles positioning, open/close animation,\n * header chrome, and a close button. Body content is provided via the\n * default slot; the consumer controls its own padding and overflow.\n *\n * Desktop: fixed right-side panel that slides from the right.\n * Mobile (≤48rem): bottom-sheet drawer (60vh) — reserved for future use.\n *\n * @element slide-panel\n * @fires panel-close - User clicked the close (✕) button.\n */\n@customElement(\"slide-panel\")\nexport class SlidePanel extends LitElement {\n /** Whether the panel is currently visible. */\n @property({ type: Boolean }) open = false;\n /** Title text shown in the panel header (overridable via slot=\"title\"). */\n @property() heading = \"\";\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 18.75rem;\n z-index: var(--component-layer-z, 20);\n background: var(--ui-surface, #ffffff);\n 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));\n transform: translateX(110%);\n transition: transform 250ms ease;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .panel.open {\n transform: translateX(0);\n }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n flex: 0 0 auto;\n }\n .panel-title {\n font-weight: 600;\n font-size: var(--ui-font-size, 0.875rem);\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n color: var(--ui-text, #0f172a);\n flex: 1 1 auto;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n padding-right: 0.5rem;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.25rem 0.5rem;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n .close-btn:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .close-btn:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .handle {\n display: none;\n }\n @media (prefers-reduced-motion: reduce) {\n .panel {\n transition: none;\n }\n }\n @media (forced-colors: active) {\n .close-btn:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n @media (max-width: 48rem) {\n .panel {\n top: auto;\n right: 0;\n bottom: 0;\n left: 0;\n width: auto;\n height: 60vh;\n border-radius: var(--ui-radius, 0.5rem) var(--ui-radius, 0.5rem) 0 0;\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 transform: translateY(110%);\n }\n .panel.open {\n transform: translateY(0);\n }\n .handle {\n display: block;\n width: 2.25rem;\n height: 0.25rem;\n background: var(--ui-border, #e2e8f0);\n border-radius: 9999px;\n margin: 0.5rem auto 0;\n flex: 0 0 auto;\n }\n .panel-header {\n padding: 0.25rem 0.5rem 0.5rem;\n }\n }\n `,\n ];\n\n private _previousFocus: HTMLElement | null = null;\n\n private _close() {\n this.dispatchEvent(new CustomEvent(\"panel-close\", { bubbles: true, composed: true }));\n }\n\n private _onWindowKeydown = (event: KeyboardEvent): void => {\n if (!this.open || !claimEscape(this, event)) return;\n this._close();\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 window.addEventListener(\"keydown\", this._onWindowKeydown);\n this.shadowRoot?.querySelector<HTMLElement>(\".close-btn\")?.focus();\n return;\n }\n deactivateLayer(this);\n window.removeEventListener(\"keydown\", this._onWindowKeydown);\n if (this._previousFocus?.isConnected) this._previousFocus.focus();\n this._previousFocus = null;\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener(\"keydown\", this._onWindowKeydown);\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 explicitLabel = this.getAttribute(\"aria-label\");\n return html`\n <div\n class=\"panel ${this.open ? \"open\" : \"\"}\"\n role=\"dialog\"\n aria-modal=\"false\"\n aria-label=${explicitLabel ?? nothing}\n aria-labelledby=${explicitLabel ? nothing : \"panel-title\"}\n aria-hidden=${String(!this.open)}\n ?inert=${!this.open}\n >\n <div class=\"handle\" aria-hidden=\"true\"></div>\n <div class=\"panel-header\">\n <span id=\"panel-title\" class=\"panel-title\">\n <slot name=\"title\">${this.heading || \"Panel\"}</slot>\n </span>\n <button class=\"close-btn\" aria-label=\"Close panel\" @click=${this._close}>\n <span aria-hidden=\"true\">${iconX(18)}</span>\n </button>\n </div>\n <slot></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"slide-panel\": SlidePanel;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"stat-meter.d.ts","sourceRoot":"","sources":["../src/stat-meter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;;;;GAOG;AACH,qBACa,SAAU,SAAQ,UAAU;IACvC,OAAgB,MAAM,4BAmDpB;IAEF,6DAA6D;IACjD,KAAK,SAAM;IAEvB,qFAAqF;IACzD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1D,mEAAmE;IACvD,KAAK,SAAM;IAEd,MAAM,yCAWd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,YAAY,EAAE,SAAS,CAAC;KACzB;CACF"}
1
+ {"version":3,"file":"stat-meter.d.ts","sourceRoot":"","sources":["../src/stat-meter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;;;;GAOG;AACH,qBACa,SAAU,SAAQ,UAAU;IACvC,OAAgB,MAAM,4BA6DpB;IAEF,6DAA6D;IACjD,KAAK,SAAM;IAEvB,qFAAqF;IACzD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE1D,mEAAmE;IACvD,KAAK,SAAM;IAEd,MAAM,yCAWd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,YAAY,EAAE,SAAS,CAAC;KACzB;CACF"}
@@ -31,7 +31,7 @@ let StatMeter = class StatMeter extends LitElement {
31
31
  :host {
32
32
  display: inline-flex;
33
33
  align-items: center;
34
- gap: 8px;
34
+ gap: 0.5rem;
35
35
  font-family: var(
36
36
  --ui-font,
37
37
  ui-sans-serif,
@@ -53,9 +53,9 @@ let StatMeter = class StatMeter extends LitElement {
53
53
 
54
54
  .track {
55
55
  display: block;
56
- width: 64px;
57
- height: 6px;
58
- border-radius: 3px;
56
+ width: 4rem;
57
+ height: 0.375rem;
58
+ border-radius: 9999px;
59
59
  background: var(--ui-surface-muted, #f8fafc);
60
60
  overflow: hidden;
61
61
  }
@@ -63,18 +63,28 @@ let StatMeter = class StatMeter extends LitElement {
63
63
  .fill {
64
64
  display: block;
65
65
  height: 100%;
66
- background: var(--fill-color, var(--ui-success, #16a34a));
67
- transition: width 0.3s ease;
66
+ /* Match map-circle's 30% white/black vertical depth while preserving token/color overrides. */
67
+ background: linear-gradient(
68
+ to bottom,
69
+ color-mix(in srgb, var(--fill-color, var(--ui-success, #16a34a)) 70%, #ffffff) 0%,
70
+ color-mix(in srgb, var(--fill-color, var(--ui-success, #16a34a)) 70%, #000000) 100%
71
+ );
72
+ transition: width 200ms ease;
68
73
  }
69
74
 
70
75
  .value {
71
- min-width: 32px;
76
+ min-width: 2rem;
72
77
  font-size: var(--ui-font-size-sm, 0.75rem);
73
78
  font-weight: 500;
74
79
  color: var(--ui-text, #0f172a);
75
80
  font-variant-numeric: tabular-nums;
76
81
  text-align: right;
77
82
  }
83
+ @media (prefers-reduced-motion: reduce) {
84
+ .fill {
85
+ transition: none;
86
+ }
87
+ }
78
88
  `,
79
89
  ]; }
80
90
  render() {
@@ -1 +1 @@
1
- {"version":3,"file":"stat-meter.js","sourceRoot":"","sources":["../src/stat-meter.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;;;;;;;GAOG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,UAAU;IAAlC;;QAsDL,6DAA6D;QACjD,UAAK,GAAG,EAAE,CAAC;QAEvB,qFAAqF;QACzD,YAAO,GAAkB,IAAI,CAAC;QAE1D,mEAAmE;QACvD,UAAK,GAAG,EAAE,CAAC;IAczB,CAAC;aA1EiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgDF;KACF,AAnDqB,CAmDpB;IAWO,MAAM;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC;QACzB,MAAM,OAAO,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACnE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,OAAO,oBAAoB,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,UAAU,OAAO,GAAG,CAAC;QACxG,OAAO,IAAI,CAAA;4BACa,IAAI,CAAC,KAAK;mDACa,IAAI,CAAC,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG;mCACrE,SAAS;;4BAEhB,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG;KACrD,CAAC;IACJ,CAAC;CACF,CAAA;AApBa;IAAX,QAAQ,EAAE;wCAAY;AAGK;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;0CAA+B;AAG9C;IAAX,QAAQ,EAAE;wCAAY;AA7DZ,SAAS;IADrB,aAAa,CAAC,YAAY,CAAC;GACf,SAAS,CA2ErB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * A compact labeled meter for a single percentage reading — e.g. CPU or\n * memory usage in a dashboard header. `percent` may be `null` when no\n * reading is available yet (e.g. the first tick of a polling metric); the\n * bar then renders empty and the value shows an em dash instead of \"0%\".\n *\n * @element stat-meter\n */\n@customElement(\"stat-meter\")\nexport class StatMeter extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n }\n\n .label {\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 600;\n letter-spacing: 0.04em;\n color: var(--ui-text-muted, #64748b);\n }\n\n .track {\n display: block;\n width: 64px;\n height: 6px;\n border-radius: 3px;\n background: var(--ui-surface-muted, #f8fafc);\n overflow: hidden;\n }\n\n .fill {\n display: block;\n height: 100%;\n background: var(--fill-color, var(--ui-success, #16a34a));\n transition: width 0.3s ease;\n }\n\n .value {\n min-width: 32px;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 500;\n color: var(--ui-text, #0f172a);\n font-variant-numeric: tabular-nums;\n text-align: right;\n }\n `,\n ];\n\n /** Short label shown before the bar, e.g. \"CPU\" or \"MEM\". */\n @property() label = \"\";\n\n /** Percentage 0-100. `null` renders an empty bar and a \"—\" value instead of \"0%\". */\n @property({ type: Number }) percent: number | null = null;\n\n /** Fill color override; falls back to the `--ui-success` token. */\n @property() color = \"\";\n\n override render() {\n const pct = this.percent;\n const clamped = pct === null ? 0 : Math.min(100, Math.max(0, pct));\n const fillStyle = this.color ? `width: ${clamped}%; --fill-color: ${this.color}` : `width: ${clamped}%`;\n return html`\n <span class=\"label\">${this.label}</span>\n <span class=\"track\" role=\"img\" aria-label=\"${this.label} ${pct === null ? \"no reading\" : `${pct}%`}\">\n <span class=\"fill\" style=${fillStyle}></span>\n </span>\n <span class=\"value\">${pct === null ? \"—\" : `${pct}%`}</span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"stat-meter\": StatMeter;\n }\n}\n"]}
1
+ {"version":3,"file":"stat-meter.js","sourceRoot":"","sources":["../src/stat-meter.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;;;;;;;GAOG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,UAAU;IAAlC;;QAgEL,6DAA6D;QACjD,UAAK,GAAG,EAAE,CAAC;QAEvB,qFAAqF;QACzD,YAAO,GAAkB,IAAI,CAAC;QAE1D,mEAAmE;QACvD,UAAK,GAAG,EAAE,CAAC;IAczB,CAAC;aApFiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA0DF;KACF,AA7DqB,CA6DpB;IAWO,MAAM;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC;QACzB,MAAM,OAAO,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACnE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,OAAO,oBAAoB,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,UAAU,OAAO,GAAG,CAAC;QACxG,OAAO,IAAI,CAAA;4BACa,IAAI,CAAC,KAAK;mDACa,IAAI,CAAC,KAAK,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG;mCACrE,SAAS;;4BAEhB,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG;KACrD,CAAC;IACJ,CAAC;CACF,CAAA;AApBa;IAAX,QAAQ,EAAE;wCAAY;AAGK;IAA3B,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;0CAA+B;AAG9C;IAAX,QAAQ,EAAE;wCAAY;AAvEZ,SAAS;IADrB,aAAa,CAAC,YAAY,CAAC;GACf,SAAS,CAqFrB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * A compact labeled meter for a single percentage reading — e.g. CPU or\n * memory usage in a dashboard header. `percent` may be `null` when no\n * reading is available yet (e.g. the first tick of a polling metric); the\n * bar then renders empty and the value shows an em dash instead of \"0%\".\n *\n * @element stat-meter\n */\n@customElement(\"stat-meter\")\nexport class StatMeter extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n }\n\n .label {\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 600;\n letter-spacing: 0.04em;\n color: var(--ui-text-muted, #64748b);\n }\n\n .track {\n display: block;\n width: 4rem;\n height: 0.375rem;\n border-radius: 9999px;\n background: var(--ui-surface-muted, #f8fafc);\n overflow: hidden;\n }\n\n .fill {\n display: block;\n height: 100%;\n /* Match map-circle's 30% white/black vertical depth while preserving token/color overrides. */\n background: linear-gradient(\n to bottom,\n color-mix(in srgb, var(--fill-color, var(--ui-success, #16a34a)) 70%, #ffffff) 0%,\n color-mix(in srgb, var(--fill-color, var(--ui-success, #16a34a)) 70%, #000000) 100%\n );\n transition: width 200ms ease;\n }\n\n .value {\n min-width: 2rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 500;\n color: var(--ui-text, #0f172a);\n font-variant-numeric: tabular-nums;\n text-align: right;\n }\n @media (prefers-reduced-motion: reduce) {\n .fill {\n transition: none;\n }\n }\n `,\n ];\n\n /** Short label shown before the bar, e.g. \"CPU\" or \"MEM\". */\n @property() label = \"\";\n\n /** Percentage 0-100. `null` renders an empty bar and a \"—\" value instead of \"0%\". */\n @property({ type: Number }) percent: number | null = null;\n\n /** Fill color override; falls back to the `--ui-success` token. */\n @property() color = \"\";\n\n override render() {\n const pct = this.percent;\n const clamped = pct === null ? 0 : Math.min(100, Math.max(0, pct));\n const fillStyle = this.color ? `width: ${clamped}%; --fill-color: ${this.color}` : `width: ${clamped}%`;\n return html`\n <span class=\"label\">${this.label}</span>\n <span class=\"track\" role=\"img\" aria-label=\"${this.label} ${pct === null ? \"no reading\" : `${pct}%`}\">\n <span class=\"fill\" style=${fillStyle}></span>\n </span>\n <span class=\"value\">${pct === null ? \"—\" : `${pct}%`}</span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"stat-meter\": StatMeter;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"status-pill.d.ts","sourceRoot":"","sources":["../src/status-pill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEhG;;;;;GAKG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,OAAgB,MAAM,4BAmDpB;IAEF,kCAAkC;IACtB,KAAK,SAAM;IACvB,qBAAqB;IACT,KAAK,EAAE,eAAe,CAAa;IAC/C,sDAAsD;IACzB,OAAO,UAAS;IAEpC,MAAM,yCAOd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
1
+ {"version":3,"file":"status-pill.d.ts","sourceRoot":"","sources":["../src/status-pill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEhG;;;;;GAKG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,OAAgB,MAAM,4BAiEpB;IAEF,kCAAkC;IACtB,KAAK,SAAM;IACvB,qBAAqB;IACT,KAAK,EAAE,eAAe,CAAa;IAC/C,sDAAsD;IACzB,OAAO,UAAS;IAEpC,MAAM,yCAOd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
@@ -33,21 +33,30 @@ let StatusPill = class StatusPill extends LitElement {
33
33
  .pill {
34
34
  display: inline-flex;
35
35
  align-items: center;
36
- gap: 0.3rem;
37
- border-radius: 999px;
38
- padding: 0.15rem 0.55rem;
39
- font-size: 0.75rem;
36
+ gap: 0.25rem;
37
+ border-radius: 9999px;
38
+ padding: 0.25rem 0.5rem;
39
+ font-size: var(--ui-font-size-sm, 0.75rem);
40
40
  font-weight: 500;
41
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
41
+ font-family: var(
42
+ --ui-font,
43
+ ui-sans-serif,
44
+ system-ui,
45
+ sans-serif,
46
+ "Apple Color Emoji",
47
+ "Segoe UI Emoji",
48
+ "Segoe UI Symbol",
49
+ "Noto Color Emoji"
50
+ );
42
51
  white-space: nowrap;
43
52
  }
44
53
  .pill.neutral {
45
- background: color-mix(in srgb, var(--ui-text-muted, #71717a) 15%, transparent);
46
- color: var(--ui-text-muted, #71717a);
54
+ background: color-mix(in srgb, var(--ui-text-muted, #64748b) 15%, transparent);
55
+ color: var(--ui-text-muted, #64748b);
47
56
  }
48
57
  .pill.info {
49
- background: color-mix(in srgb, #0ea5e9 15%, transparent);
50
- color: #0ea5e9;
58
+ background: color-mix(in srgb, var(--ui-info, #0ea5e9) 15%, transparent);
59
+ color: var(--ui-info, #0ea5e9);
51
60
  }
52
61
  .pill.primary {
53
62
  background: color-mix(in srgb, var(--ui-primary, #4f46e5) 15%, transparent);
@@ -58,8 +67,8 @@ let StatusPill = class StatusPill extends LitElement {
58
67
  color: var(--ui-success, #16a34a);
59
68
  }
60
69
  .pill.warning {
61
- background: color-mix(in srgb, #d97706 15%, transparent);
62
- color: #d97706;
70
+ background: color-mix(in srgb, var(--ui-warning, #d97706) 15%, transparent);
71
+ color: var(--ui-warning, #d97706);
63
72
  }
64
73
  .pill.danger {
65
74
  background: color-mix(in srgb, var(--ui-danger, #dc2626) 15%, transparent);
@@ -74,12 +83,17 @@ let StatusPill = class StatusPill extends LitElement {
74
83
  transform: rotate(360deg);
75
84
  }
76
85
  }
86
+ @media (prefers-reduced-motion: reduce) {
87
+ .spin {
88
+ animation: none;
89
+ }
90
+ }
77
91
  `,
78
92
  ]; }
79
93
  render() {
80
94
  return html `
81
95
  <span class="pill ${this.color}">
82
- ${this.spinner ? html `<span class="spin">${iconArrowPath(11)}</span>` : nothing}
96
+ ${this.spinner ? html `<span class="spin" aria-hidden="true">${iconArrowPath(11)}</span>` : nothing}
83
97
  ${this.label}
84
98
  </span>
85
99
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"status-pill.js","sourceRoot":"","sources":["../src/status-pill.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;AAIrC;;;;;GAKG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QAsDL,kCAAkC;QACtB,UAAK,GAAG,EAAE,CAAC;QACvB,qBAAqB;QACT,UAAK,GAAoB,SAAS,CAAC;QAC/C,sDAAsD;QACzB,YAAO,GAAG,KAAK,CAAC;IAU/C,CAAC;aApEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgDF;KACF,AAnDqB,CAmDpB;IASO,MAAM;QACb,OAAO,IAAI,CAAA;0BACW,IAAI,CAAC,KAAK;UAC1B,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,sBAAsB,aAAa,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;UAC7E,IAAI,CAAC,KAAK;;KAEf,CAAC;IACJ,CAAC;CACF,CAAA;AAda;IAAX,QAAQ,EAAE;yCAAY;AAEX;IAAX,QAAQ,EAAE;yCAAoC;AAElB;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CAAiB;AA3DlC,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CAqEtB","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\nexport type StatusPillColor = \"neutral\" | \"info\" | \"primary\" | \"success\" | \"warning\" | \"danger\";\n\n/**\n * Small colored status pill, optionally with a spinning icon — for task/run\n * states (\"Open\", \"Blocked\", \"Done\") and live-activity indicators (\"Running\").\n *\n * @element status-pill\n */\n@customElement(\"status-pill\")\nexport class StatusPill extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n .pill {\n display: inline-flex;\n align-items: center;\n gap: 0.3rem;\n border-radius: 999px;\n padding: 0.15rem 0.55rem;\n font-size: 0.75rem;\n font-weight: 500;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n white-space: nowrap;\n }\n .pill.neutral {\n background: color-mix(in srgb, var(--ui-text-muted, #71717a) 15%, transparent);\n color: var(--ui-text-muted, #71717a);\n }\n .pill.info {\n background: color-mix(in srgb, #0ea5e9 15%, transparent);\n color: #0ea5e9;\n }\n .pill.primary {\n background: color-mix(in srgb, var(--ui-primary, #4f46e5) 15%, transparent);\n color: var(--ui-primary, #4f46e5);\n }\n .pill.success {\n background: color-mix(in srgb, var(--ui-success, #16a34a) 15%, transparent);\n color: var(--ui-success, #16a34a);\n }\n .pill.warning {\n background: color-mix(in srgb, #d97706 15%, transparent);\n color: #d97706;\n }\n .pill.danger {\n background: color-mix(in srgb, var(--ui-danger, #dc2626) 15%, transparent);\n color: var(--ui-danger, #dc2626);\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 `,\n ];\n\n /** Text shown inside the pill. */\n @property() label = \"\";\n /** Color variant. */\n @property() color: StatusPillColor = \"neutral\";\n /** Renders a small spinning icon before the label. */\n @property({ type: Boolean }) spinner = false;\n\n override render() {\n return html`\n <span class=\"pill ${this.color}\">\n ${this.spinner ? html`<span class=\"spin\">${iconArrowPath(11)}</span>` : nothing}\n ${this.label}\n </span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"status-pill\": StatusPill;\n }\n}\n"]}
1
+ {"version":3,"file":"status-pill.js","sourceRoot":"","sources":["../src/status-pill.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;AAIrC;;;;;GAKG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QAoEL,kCAAkC;QACtB,UAAK,GAAG,EAAE,CAAC;QACvB,qBAAqB;QACT,UAAK,GAAoB,SAAS,CAAC;QAC/C,sDAAsD;QACzB,YAAO,GAAG,KAAK,CAAC;IAU/C,CAAC;aAlFiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8DF;KACF,AAjEqB,CAiEpB;IASO,MAAM;QACb,OAAO,IAAI,CAAA;0BACW,IAAI,CAAC,KAAK;UAC1B,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,yCAAyC,aAAa,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;UAChG,IAAI,CAAC,KAAK;;KAEf,CAAC;IACJ,CAAC;CACF,CAAA;AAda;IAAX,QAAQ,EAAE;yCAAY;AAEX;IAAX,QAAQ,EAAE;yCAAoC;AAElB;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CAAiB;AAzElC,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CAmFtB","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\nexport type StatusPillColor = \"neutral\" | \"info\" | \"primary\" | \"success\" | \"warning\" | \"danger\";\n\n/**\n * Small colored status pill, optionally with a spinning icon — for task/run\n * states (\"Open\", \"Blocked\", \"Done\") and live-activity indicators (\"Running\").\n *\n * @element status-pill\n */\n@customElement(\"status-pill\")\nexport class StatusPill extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n .pill {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n border-radius: 9999px;\n padding: 0.25rem 0.5rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 500;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n white-space: nowrap;\n }\n .pill.neutral {\n background: color-mix(in srgb, var(--ui-text-muted, #64748b) 15%, transparent);\n color: var(--ui-text-muted, #64748b);\n }\n .pill.info {\n background: color-mix(in srgb, var(--ui-info, #0ea5e9) 15%, transparent);\n color: var(--ui-info, #0ea5e9);\n }\n .pill.primary {\n background: color-mix(in srgb, var(--ui-primary, #4f46e5) 15%, transparent);\n color: var(--ui-primary, #4f46e5);\n }\n .pill.success {\n background: color-mix(in srgb, var(--ui-success, #16a34a) 15%, transparent);\n color: var(--ui-success, #16a34a);\n }\n .pill.warning {\n background: color-mix(in srgb, var(--ui-warning, #d97706) 15%, transparent);\n color: var(--ui-warning, #d97706);\n }\n .pill.danger {\n background: color-mix(in srgb, var(--ui-danger, #dc2626) 15%, transparent);\n color: var(--ui-danger, #dc2626);\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 `,\n ];\n\n /** Text shown inside the pill. */\n @property() label = \"\";\n /** Color variant. */\n @property() color: StatusPillColor = \"neutral\";\n /** Renders a small spinning icon before the label. */\n @property({ type: Boolean }) spinner = false;\n\n override render() {\n return html`\n <span class=\"pill ${this.color}\">\n ${this.spinner ? html`<span class=\"spin\" aria-hidden=\"true\">${iconArrowPath(11)}</span>` : nothing}\n ${this.label}\n </span>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"status-pill\": StatusPill;\n }\n}\n"]}
@@ -9,6 +9,12 @@ import { LitElement } from "lit";
9
9
  * `location.hash`), without hijacking clicks on nested interactive elements
10
10
  * (links/buttons) a tile's rendered content might contain.
11
11
  *
12
+ * Optional `fileIcon` prefixes each tile with a decorative Heroicons
13
+ * "document" glyph, for grids whose items represent files — the icon never
14
+ * carries its own accessible name since the rendered tile content (e.g. a
15
+ * filename) already identifies the item. Off by default, so it consumes no
16
+ * layout space or markup for grids of non-file items.
17
+ *
12
18
  * @element tile-grid
13
19
  */
14
20
  export declare class TileGrid extends LitElement {
@@ -22,6 +28,8 @@ export declare class TileGrid extends LitElement {
22
28
  renderTile: (item: unknown) => unknown;
23
29
  /** When set, clicking a tile (outside any nested link/button) navigates to this hash. */
24
30
  itemHref: ((item: unknown) => string | null) | null;
31
+ /** Prefixes each tile with a decorative "document" icon, for grids of file-like items. Off by default. */
32
+ fileIcon: boolean;
25
33
  render(): import("lit-html").TemplateResult<1>;
26
34
  }
27
35
  declare global {
@@ -1 +1 @@
1
- {"version":3,"file":"tile-grid.d.ts","sourceRoot":"","sources":["../src/tile-grid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C;;;;;;;;;;;GAWG;AACH,qBACa,QAAS,SAAQ,UAAU;;IACtC,OAAgB,MAAM,4BA0BpB;IAEF,iFAAiF;IACjD,KAAK,EAAE,OAAO,EAAE,CAAM;IACtD,sGAAsG;IACtE,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAG/E;IACX,yEAAyE;IACzC,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAA0B;IAChG,yFAAyF;IACzD,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAQ;IASlF,MAAM,yCAiBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,WAAW,EAAE,QAAQ,CAAC;KACvB;CACF"}
1
+ {"version":3,"file":"tile-grid.d.ts","sourceRoot":"","sources":["../src/tile-grid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAMrD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,QAAS,SAAQ,UAAU;;IACtC,OAAgB,MAAM,4BA6DpB;IAEF,iFAAiF;IACjD,KAAK,EAAE,OAAO,EAAE,CAAM;IACtD,sGAAsG;IACtE,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAG/E;IACX,yEAAyE;IACzC,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAA0B;IAChG,yFAAyF;IACzD,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC3F,0GAA0G;IACrD,QAAQ,UAAS;IAyB7D,MAAM,yCA8Bd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,WAAW,EAAE,QAAQ,CAAC;KACvB;CACF"}
package/dist/tile-grid.js CHANGED
@@ -4,9 +4,10 @@ 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 { repeat } from "lit/directives/repeat.js";
10
+ import { iconDocument } from "./icons.js";
10
11
  import { tokens } from "./tokens.js";
11
12
  /**
12
13
  * A generic, presentational grid shell: renders one bordered card per entry
@@ -18,6 +19,12 @@ import { tokens } from "./tokens.js";
18
19
  * `location.hash`), without hijacking clicks on nested interactive elements
19
20
  * (links/buttons) a tile's rendered content might contain.
20
21
  *
22
+ * Optional `fileIcon` prefixes each tile with a decorative Heroicons
23
+ * "document" glyph, for grids whose items represent files — the icon never
24
+ * carries its own accessible name since the rendered tile content (e.g. a
25
+ * filename) already identifies the item. Off by default, so it consumes no
26
+ * layout space or markup for grids of non-file items.
27
+ *
21
28
  * @element tile-grid
22
29
  */
23
30
  let TileGrid = class TileGrid extends LitElement {
@@ -31,18 +38,29 @@ let TileGrid = class TileGrid extends LitElement {
31
38
  this.renderTile = (item) => String(item);
32
39
  /** When set, clicking a tile (outside any nested link/button) navigates to this hash. */
33
40
  this.itemHref = null;
41
+ /** Prefixes each tile with a decorative "document" icon, for grids of file-like items. Off by default. */
42
+ this.fileIcon = false;
34
43
  }
35
44
  static { this.styles = [
36
45
  tokens,
37
46
  css `
38
47
  :host {
39
48
  display: block;
40
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
49
+ font-family: var(
50
+ --ui-font,
51
+ ui-sans-serif,
52
+ system-ui,
53
+ sans-serif,
54
+ "Apple Color Emoji",
55
+ "Segoe UI Emoji",
56
+ "Segoe UI Symbol",
57
+ "Noto Color Emoji"
58
+ );
41
59
  font-size: var(--ui-font-size-sm, 0.75rem);
42
60
  }
43
61
  .grid {
44
62
  display: grid;
45
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
63
+ grid-template-columns: repeat(auto-fill, minmax(8.75rem, 1fr));
46
64
  gap: 0.75rem;
47
65
  }
48
66
  .tile {
@@ -57,28 +75,83 @@ let TileGrid = class TileGrid extends LitElement {
57
75
  .tile.clickable:hover {
58
76
  background: var(--ui-surface-muted, #f8fafc);
59
77
  }
78
+ .tile.clickable:focus-visible {
79
+ outline: none;
80
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
81
+ }
82
+ .tile-body {
83
+ display: flex;
84
+ align-items: flex-start;
85
+ gap: 0.5rem;
86
+ }
87
+ .tile-icon {
88
+ display: inline-flex;
89
+ flex-shrink: 0;
90
+ margin-top: 0.125rem;
91
+ color: var(--ui-text-muted, #64748b);
92
+ }
93
+ .tile-content {
94
+ min-width: 0;
95
+ flex: 1;
96
+ }
97
+ @media (forced-colors: active) {
98
+ .tile.clickable:focus-visible {
99
+ outline: 2px solid CanvasText;
100
+ outline-offset: 2px;
101
+ box-shadow: none;
102
+ }
103
+ }
60
104
  `,
61
105
  ]; }
62
106
  #onTileClick(item, e) {
63
107
  if (!this.itemHref)
64
108
  return;
65
- if (e.target.closest("a, button"))
109
+ if (this.#isNestedInteractive(e.composedPath(), e.currentTarget))
66
110
  return;
67
111
  const href = this.itemHref(item);
68
112
  if (href)
69
113
  location.hash = href;
70
114
  }
115
+ #onTileKeydown(href, e) {
116
+ if (e.key !== "Enter" || e.target !== e.currentTarget)
117
+ return;
118
+ e.preventDefault();
119
+ location.hash = href;
120
+ }
121
+ #isNestedInteractive(path, tile) {
122
+ const selector = "a, button, input, select, textarea, summary, [contenteditable], [role='button'], [role='link'], [tabindex]";
123
+ for (const target of path) {
124
+ if (target === tile)
125
+ return false;
126
+ if (target instanceof HTMLElement && target.matches(selector))
127
+ return true;
128
+ }
129
+ return false;
130
+ }
71
131
  render() {
72
132
  return html `
73
133
  <div class="grid">
74
- ${repeat(this.items, (item, i) => this.itemKey(item, i), (item) => html `
75
- <div
76
- class=${this.itemHref ? "tile clickable" : "tile"}
77
- @click=${(e) => this.#onTileClick(item, e)}
78
- >
79
- ${this.renderTile(item)}
80
- </div>
81
- `)}
134
+ ${repeat(this.items, (item, i) => this.itemKey(item, i), (item) => {
135
+ const href = this.itemHref?.(item) ?? null;
136
+ return html `
137
+ <div
138
+ class=${href ? "tile clickable" : "tile"}
139
+ role=${href ? "link" : nothing}
140
+ tabindex=${href ? "0" : nothing}
141
+ @click=${(e) => this.#onTileClick(item, e)}
142
+ @keydown=${href ? (e) => this.#onTileKeydown(href, e) : nothing}
143
+ >
144
+ ${this.fileIcon
145
+ ? html `
146
+ <div class="tile-body">
147
+ <span class="tile-icon" aria-hidden="true">${iconDocument(16)}</span>
148
+ <div class="tile-content">${this.renderTile(item)}</div>
149
+ </div>
150
+ `
151
+ : this.renderTile(item)}
152
+ </div>
153
+ `;
154
+ })}
82
155
  </div>
83
156
  `;
84
157
  }
@@ -95,6 +168,9 @@ __decorate([
95
168
  __decorate([
96
169
  property({ attribute: false })
97
170
  ], TileGrid.prototype, "itemHref", void 0);
171
+ __decorate([
172
+ property({ type: Boolean, attribute: "file-icon" })
173
+ ], TileGrid.prototype, "fileIcon", void 0);
98
174
  TileGrid = __decorate([
99
175
  customElement("tile-grid")
100
176
  ], TileGrid);
@@ -1 +1 @@
1
- {"version":3,"file":"tile-grid.js","sourceRoot":"","sources":["../src/tile-grid.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;;GAWG;AAEI,IAAM,QAAQ,GAAd,MAAM,QAAS,SAAQ,UAAU;IAAjC;;QA6BL,iFAAiF;QACjD,UAAK,GAAc,EAAE,CAAC;QACtD,sGAAsG;QACtE,YAAO,GAAsD,CAC3F,KAAK,EACL,KAAK,EACL,EAAE,CAAC,KAAK,CAAC;QACX,yEAAyE;QACzC,eAAU,GAA+B,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChG,yFAAyF;QACzD,aAAQ,GAA8C,IAAI,CAAC;IA2B7F,CAAC;aAjEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;KAuBF;KACF,AA1BqB,CA0BpB;IAcF,YAAY,CAAC,IAAa,EAAE,CAAa;QACvC,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC3B,IAAK,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,WAAW,CAAC;YAAE,OAAO;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;IACjC,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;UAEL,MAAM,CACN,IAAI,CAAC,KAAK,EACV,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,EAClC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAA;;sBAEF,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,MAAM;uBACxC,CAAC,CAAa,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;;gBAEpD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;;WAE1B,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AApCiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;uCAAuB;AAEtB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;yCAGpB;AAEqB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;4CAAiE;AAEhE;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;0CAA4D;AAvChF,QAAQ;IADpB,aAAa,CAAC,WAAW,CAAC;GACd,QAAQ,CAkEpB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * A generic, presentational grid shell: renders one bordered card per entry\n * in `items`, with each tile's content produced by `renderTile` (default:\n * stringify). Knows nothing about what an \"item\" means — callers own the\n * data shape entirely. Modeled directly on `data-table`'s headless pattern.\n *\n * Optional `itemHref` makes whole tiles clickable (navigating via\n * `location.hash`), without hijacking clicks on nested interactive elements\n * (links/buttons) a tile's rendered content might contain.\n *\n * @element tile-grid\n */\n@customElement(\"tile-grid\")\nexport class TileGrid extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size-sm, 0.75rem);\n }\n .grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\n gap: 0.75rem;\n }\n .tile {\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius, 0.5rem);\n padding: 0.75rem;\n color: var(--ui-text, #0f172a);\n }\n .tile.clickable {\n cursor: pointer;\n }\n .tile.clickable:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n `,\n ];\n\n /** Tile data; opaque to this component beyond what `renderTile` does with it. */\n @property({ attribute: false }) items: unknown[] = [];\n /** Stable identity for `items[i]`, used as the repeat-directive key. Defaults to the item's index. */\n @property({ attribute: false }) itemKey: (item: unknown, index: number) => string | number = (\n _item,\n index,\n ) => index;\n /** Produces a tile's rendered content for `item`. Default: stringify. */\n @property({ attribute: false }) renderTile: (item: unknown) => unknown = (item) => String(item);\n /** When set, clicking a tile (outside any nested link/button) navigates to this hash. */\n @property({ attribute: false }) itemHref: ((item: unknown) => string | null) | null = null;\n\n #onTileClick(item: unknown, e: MouseEvent): void {\n if (!this.itemHref) return;\n if ((e.target as HTMLElement).closest(\"a, button\")) return;\n const href = this.itemHref(item);\n if (href) location.hash = href;\n }\n\n override render() {\n return html`\n <div class=\"grid\">\n ${repeat(\n this.items,\n (item, i) => this.itemKey(item, i),\n (item) => html`\n <div\n class=${this.itemHref ? \"tile clickable\" : \"tile\"}\n @click=${(e: MouseEvent) => this.#onTileClick(item, e)}\n >\n ${this.renderTile(item)}\n </div>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"tile-grid\": TileGrid;\n }\n}\n"]}
1
+ {"version":3,"file":"tile-grid.js","sourceRoot":"","sources":["../src/tile-grid.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 { iconDocument } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * A generic, presentational grid shell: renders one bordered card per entry\n * in `items`, with each tile's content produced by `renderTile` (default:\n * stringify). Knows nothing about what an \"item\" means — callers own the\n * data shape entirely. Modeled directly on `data-table`'s headless pattern.\n *\n * Optional `itemHref` makes whole tiles clickable (navigating via\n * `location.hash`), without hijacking clicks on nested interactive elements\n * (links/buttons) a tile's rendered content might contain.\n *\n * Optional `fileIcon` prefixes each tile with a decorative Heroicons\n * \"document\" glyph, for grids whose items represent files — the icon never\n * carries its own accessible name since the rendered tile content (e.g. a\n * filename) already identifies the item. Off by default, so it consumes no\n * layout space or markup for grids of non-file items.\n *\n * @element tile-grid\n */\n@customElement(\"tile-grid\")\nexport class TileGrid extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: var(\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 .grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(8.75rem, 1fr));\n gap: 0.75rem;\n }\n .tile {\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius, 0.5rem);\n padding: 0.75rem;\n color: var(--ui-text, #0f172a);\n }\n .tile.clickable {\n cursor: pointer;\n }\n .tile.clickable:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .tile.clickable:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .tile-body {\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n }\n .tile-icon {\n display: inline-flex;\n flex-shrink: 0;\n margin-top: 0.125rem;\n color: var(--ui-text-muted, #64748b);\n }\n .tile-content {\n min-width: 0;\n flex: 1;\n }\n @media (forced-colors: active) {\n .tile.clickable:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n /** Tile data; opaque to this component beyond what `renderTile` does with it. */\n @property({ attribute: false }) items: unknown[] = [];\n /** Stable identity for `items[i]`, used as the repeat-directive key. Defaults to the item's index. */\n @property({ attribute: false }) itemKey: (item: unknown, index: number) => string | number = (\n _item,\n index,\n ) => index;\n /** Produces a tile's rendered content for `item`. Default: stringify. */\n @property({ attribute: false }) renderTile: (item: unknown) => unknown = (item) => String(item);\n /** When set, clicking a tile (outside any nested link/button) navigates to this hash. */\n @property({ attribute: false }) itemHref: ((item: unknown) => string | null) | null = null;\n /** Prefixes each tile with a decorative \"document\" icon, for grids of file-like items. Off by default. */\n @property({ type: Boolean, attribute: \"file-icon\" }) fileIcon = false;\n\n #onTileClick(item: unknown, e: MouseEvent): void {\n if (!this.itemHref) return;\n if (this.#isNestedInteractive(e.composedPath(), e.currentTarget)) return;\n const href = this.itemHref(item);\n if (href) location.hash = href;\n }\n\n #onTileKeydown(href: string, e: KeyboardEvent): void {\n if (e.key !== \"Enter\" || e.target !== e.currentTarget) return;\n e.preventDefault();\n location.hash = href;\n }\n\n #isNestedInteractive(path: EventTarget[], tile: 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 === tile) return false;\n if (target instanceof HTMLElement && target.matches(selector)) return true;\n }\n return false;\n }\n\n override render() {\n return html`\n <div class=\"grid\">\n ${repeat(\n this.items,\n (item, i) => this.itemKey(item, i),\n (item) => {\n const href = this.itemHref?.(item) ?? null;\n return html`\n <div\n class=${href ? \"tile clickable\" : \"tile\"}\n role=${href ? \"link\" : nothing}\n tabindex=${href ? \"0\" : nothing}\n @click=${(e: MouseEvent) => this.#onTileClick(item, e)}\n @keydown=${href ? (e: KeyboardEvent) => this.#onTileKeydown(href, e) : nothing}\n >\n ${this.fileIcon\n ? html`\n <div class=\"tile-body\">\n <span class=\"tile-icon\" aria-hidden=\"true\">${iconDocument(16)}</span>\n <div class=\"tile-content\">${this.renderTile(item)}</div>\n </div>\n `\n : this.renderTile(item)}\n </div>\n `;\n },\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"tile-grid\": TileGrid;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"toast-notification.d.ts","sourceRoot":"","sources":["../src/toast-notification.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAE,MAAM,KAAK,CAAC;AAKrD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAExD,UAAU,YAAY;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAUD;;;;;;;;;;;GAWG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IACtC,OAAO,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAAoD;IAEnE,OAAgB,MAAM,4BAsEpB;IAEF;;;;;OAKG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAYxD;IAED;;;OAGG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,QAOjB;IAEQ,oBAAoB,SAI5B;IAEQ,MAAM,0DAcd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF;AAMD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,QAE1C;AAED,6BAA6B;AAC7B,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,QAE5C;AAED,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,QAEzC"}
1
+ {"version":3,"file":"toast-notification.d.ts","sourceRoot":"","sources":["../src/toast-notification.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAE,MAAM,KAAK,CAAC;AAKrD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAExD,UAAU,YAAY;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAUD;;;;;;;;;;;GAWG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IACtC,OAAO,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAAoD;IAEnE,OAAgB,MAAM,4BA+FpB;IAEF;;;;;OAKG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAYxD;IAED;;;OAGG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,QAOjB;IAEQ,oBAAoB,SAI5B;IAEQ,MAAM,0DAkBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF;AAMD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,QAE1C;AAED,6BAA6B;AAC7B,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,QAE5C;AAED,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,QAEzC"}
@@ -39,7 +39,7 @@ let ToastNotification = class ToastNotification extends LitElement {
39
39
  display: flex;
40
40
  flex-direction: column;
41
41
  gap: 0.5rem;
42
- max-width: 360px;
42
+ max-width: 22.5rem;
43
43
  pointer-events: none;
44
44
  }
45
45
  .toast {
@@ -48,16 +48,25 @@ let ToastNotification = class ToastNotification extends LitElement {
48
48
  align-items: flex-start;
49
49
  gap: 0.5rem;
50
50
  border-radius: var(--ui-radius, 0.5rem);
51
- padding: 0.625rem 0.75rem;
51
+ padding: 0.5rem 0.75rem;
52
52
  box-shadow: var(
53
53
  --ui-shadow-lg,
54
54
  0 20px 25px -5px rgb(0 0 0 / 0.1),
55
55
  0 8px 10px -6px rgb(0 0 0 / 0.1)
56
56
  );
57
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
57
+ font-family: var(
58
+ --ui-font,
59
+ ui-sans-serif,
60
+ system-ui,
61
+ sans-serif,
62
+ "Apple Color Emoji",
63
+ "Segoe UI Emoji",
64
+ "Segoe UI Symbol",
65
+ "Noto Color Emoji"
66
+ );
58
67
  font-size: var(--ui-font-size, 0.875rem);
59
68
  line-height: 1.4;
60
- color: #fff;
69
+ color: var(--ui-on-accent, #ffffff);
61
70
  background: var(--ui-text, #0f172a);
62
71
  }
63
72
  .toast.error {
@@ -67,7 +76,7 @@ let ToastNotification = class ToastNotification extends LitElement {
67
76
  background: var(--ui-success, #16a34a);
68
77
  }
69
78
  .toast.info {
70
- background: var(--ui-primary, #4f46e5);
79
+ background: var(--ui-info, #0ea5e9);
71
80
  }
72
81
  .message {
73
82
  flex: 1 1 auto;
@@ -77,8 +86,8 @@ let ToastNotification = class ToastNotification extends LitElement {
77
86
  flex: 0 0 auto;
78
87
  background: none;
79
88
  border: none;
80
- padding: 2px;
81
- margin: -2px -2px -2px 0;
89
+ padding: 0.25rem;
90
+ margin: -0.25rem -0.25rem -0.25rem 0;
82
91
  color: inherit;
83
92
  opacity: 0.8;
84
93
  cursor: pointer;
@@ -87,9 +96,25 @@ let ToastNotification = class ToastNotification extends LitElement {
87
96
  }
88
97
  .close:hover {
89
98
  opacity: 1;
90
- background: rgb(255 255 255 / 0.15);
99
+ background: var(--ui-hover-overlay, rgb(255 255 255 / 0.32));
100
+ }
101
+ .close:focus-visible {
102
+ outline: none;
103
+ opacity: 1;
104
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
105
+ }
106
+ @media (forced-colors: active) {
107
+ .toast {
108
+ border: 1px solid CanvasText;
109
+ forced-color-adjust: auto;
110
+ }
111
+ .close:focus-visible {
112
+ outline: 2px solid CanvasText;
113
+ outline-offset: 2px;
114
+ box-shadow: none;
115
+ }
91
116
  }
92
- @media (max-width: 768px) {
117
+ @media (max-width: 48rem) {
93
118
  :host {
94
119
  top: 0.75rem;
95
120
  right: 0.75rem;
@@ -138,10 +163,14 @@ let ToastNotification = class ToastNotification extends LitElement {
138
163
  return nothing;
139
164
  return html `
140
165
  ${this._toasts.map((t) => html `
141
- <div class="toast ${t.variant}" role="status">
166
+ <div
167
+ class="toast ${t.variant}"
168
+ role=${t.variant === "error" ? "alert" : "status"}
169
+ aria-atomic="true"
170
+ >
142
171
  <span class="message">${t.message}</span>
143
172
  <button class="close" aria-label="Dismiss notification" @click=${() => this.dismiss(t.id)}>
144
- ${iconX(14)}
173
+ <span aria-hidden="true">${iconX(14)}</span>
145
174
  </button>
146
175
  </div>
147
176
  `)}
@@ -1 +1 @@
1
- {"version":3,"file":"toast-notification.js","sourceRoot":"","sources":["../src/toast-notification.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAiBrC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC;;;;;;;;;;;GAWG;AAEI,IAAM,iBAAiB,GAAvB,MAAM,iBAAkB,SAAQ,UAAU;IAA1C;;QACY,YAAO,GAAY,EAAE,CAAC;QAE/B,YAAO,GAAG,CAAC,CAAC;QACZ,YAAO,GAAG,IAAI,GAAG,EAAyC,CAAC;IAgIrE,CAAC;aA9HiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAmEF;KACF,AAtEqB,CAsEpB;IAEF;;;;;OAKG;IACH,IAAI,CAAC,OAAe,EAAE,OAAO,GAAiB,EAAE;QAC9C,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC;QAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,mBAAmB,CAAC;QACzD,IAAI,CAAC,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3D,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,GAAG,CACd,EAAE,EACF,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAC7C,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,EAAU;QAChB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1B,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,CAAC;IAEQ,oBAAoB;QAC3B,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QAC/D,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAEQ,MAAM;QACb,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QAC9C,OAAO,IAAI,CAAA;QACP,IAAI,CAAC,OAAO,CAAC,GAAG,CAChB,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAA;8BACW,CAAC,CAAC,OAAO;oCACH,CAAC,CAAC,OAAO;6EACgC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrF,KAAK,CAAC,EAAE,CAAC;;;SAGhB,CACF;KACF,CAAC;IACJ,CAAC;CACF,CAAA;AAnIkB;IAAhB,KAAK,EAAE;kDAA+B;AAD5B,iBAAiB;IAD7B,aAAa,CAAC,oBAAoB,CAAC;GACvB,iBAAiB,CAoI7B;;AAQD,SAAS,QAAQ;IACf,OAAO,QAAQ,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC;AACtD,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,QAAQ,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,6BAA6B;AAC7B,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,QAAQ,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,2BAA2B;AAC3B,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,QAAQ,EAAE,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AACjD,CAAC","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, state } from \"lit/decorators.js\";\nimport { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\nexport type ToastVariant = \"error\" | \"info\" | \"success\";\n\ninterface ToastOptions {\n /** Visual style; also selects the accent color. Defaults to \"info\". */\n variant?: ToastVariant;\n /** Auto-dismiss delay in ms. Pass 0 to require a manual close. Defaults to 5000. */\n duration?: number;\n}\n\ninterface Toast {\n id: number;\n message: string;\n variant: ToastVariant;\n}\n\nconst DEFAULT_DURATION_MS = 5000;\n\n/**\n * Fixed-position stack of dismissible notifications, anchored top-right\n * (top-full-width on mobile). Not wired to any app state yet — callers add\n * toasts imperatively via the `show()` method on a live element reference,\n * e.g. `document.querySelector('toast-notification')?.show('Offline', { variant: 'error' })`,\n * or via the `notifySuccess`/`notifyError`/`notifyInfo` module-level helpers\n * exported from this file. Each toast auto-dismisses after `duration` ms and\n * can also be dismissed via its ✕ button. Appears/disappears instantly — no\n * slide/fade transitions.\n *\n * @element toast-notification\n */\n@customElement(\"toast-notification\")\nexport class ToastNotification extends LitElement {\n @state() private _toasts: Toast[] = [];\n\n private _nextId = 0;\n private _timers = new Map<number, ReturnType<typeof setTimeout>>();\n\n static override styles = [\n tokens,\n css`\n :host {\n position: fixed;\n top: 1rem;\n right: 1rem;\n z-index: 200;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n max-width: 360px;\n pointer-events: none;\n }\n .toast {\n pointer-events: auto;\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n border-radius: var(--ui-radius, 0.5rem);\n padding: 0.625rem 0.75rem;\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 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.4;\n color: #fff;\n background: var(--ui-text, #0f172a);\n }\n .toast.error {\n background: var(--ui-danger, #dc2626);\n }\n .toast.success {\n background: var(--ui-success, #16a34a);\n }\n .toast.info {\n background: var(--ui-primary, #4f46e5);\n }\n .message {\n flex: 1 1 auto;\n word-break: break-word;\n }\n .close {\n flex: 0 0 auto;\n background: none;\n border: none;\n padding: 2px;\n margin: -2px -2px -2px 0;\n color: inherit;\n opacity: 0.8;\n cursor: pointer;\n line-height: 0;\n border-radius: var(--ui-radius-sm, 0.25rem);\n }\n .close:hover {\n opacity: 1;\n background: rgb(255 255 255 / 0.15);\n }\n @media (max-width: 768px) {\n :host {\n top: 0.75rem;\n right: 0.75rem;\n left: 0.75rem;\n max-width: none;\n }\n }\n `,\n ];\n\n /**\n * Queues a toast for display.\n * @param message Text to show.\n * @param options Variant + auto-dismiss duration (see {@link ToastOptions}).\n * @returns The toast's id — pass to `dismiss()` to remove it early.\n */\n show(message: string, options: ToastOptions = {}): number {\n const id = this._nextId++;\n const variant = options.variant ?? \"info\";\n const duration = options.duration ?? DEFAULT_DURATION_MS;\n this._toasts = [...this._toasts, { id, message, variant }];\n if (duration > 0) {\n this._timers.set(\n id,\n setTimeout(() => this.dismiss(id), duration),\n );\n }\n return id;\n }\n\n /**\n * Removes a toast immediately, cancelling its pending auto-dismiss timer if any.\n * @param id The id returned by `show()`.\n */\n dismiss(id: number) {\n const timer = this._timers.get(id);\n if (timer) {\n clearTimeout(timer);\n this._timers.delete(id);\n }\n this._toasts = this._toasts.filter((t) => t.id !== id);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n for (const timer of this._timers.values()) clearTimeout(timer);\n this._timers.clear();\n }\n\n override render() {\n if (this._toasts.length === 0) return nothing;\n return html`\n ${this._toasts.map(\n (t) => html`\n <div class=\"toast ${t.variant}\" role=\"status\">\n <span class=\"message\">${t.message}</span>\n <button class=\"close\" aria-label=\"Dismiss notification\" @click=${() => this.dismiss(t.id)}>\n ${iconX(14)}\n </button>\n </div>\n `,\n )}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"toast-notification\": ToastNotification;\n }\n}\n\nfunction getToast(): ToastNotification | null {\n return document.querySelector(\"toast-notification\");\n}\n\n/** Shows an error toast. Use for connection/network failures, not validation errors. */\nexport function notifyError(message: string) {\n getToast()?.show(message, { variant: \"error\" });\n}\n\n/** Shows a success toast. */\nexport function notifySuccess(message: string) {\n getToast()?.show(message, { variant: \"success\" });\n}\n\n/** Shows an info toast. */\nexport function notifyInfo(message: string) {\n getToast()?.show(message, { variant: \"info\" });\n}\n"]}
1
+ {"version":3,"file":"toast-notification.js","sourceRoot":"","sources":["../src/toast-notification.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, state } from \"lit/decorators.js\";\nimport { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\nexport type ToastVariant = \"error\" | \"info\" | \"success\";\n\ninterface ToastOptions {\n /** Visual style; also selects the accent color. Defaults to \"info\". */\n variant?: ToastVariant;\n /** Auto-dismiss delay in ms. Pass 0 to require a manual close. Defaults to 5000. */\n duration?: number;\n}\n\ninterface Toast {\n id: number;\n message: string;\n variant: ToastVariant;\n}\n\nconst DEFAULT_DURATION_MS = 5000;\n\n/**\n * Fixed-position stack of dismissible notifications, anchored top-right\n * (top-full-width on mobile). Not wired to any app state yet — callers add\n * toasts imperatively via the `show()` method on a live element reference,\n * e.g. `document.querySelector('toast-notification')?.show('Offline', { variant: 'error' })`,\n * or via the `notifySuccess`/`notifyError`/`notifyInfo` module-level helpers\n * exported from this file. Each toast auto-dismisses after `duration` ms and\n * can also be dismissed via its ✕ button. Appears/disappears instantly — no\n * slide/fade transitions.\n *\n * @element toast-notification\n */\n@customElement(\"toast-notification\")\nexport class ToastNotification extends LitElement {\n @state() private _toasts: Toast[] = [];\n\n private _nextId = 0;\n private _timers = new Map<number, ReturnType<typeof setTimeout>>();\n\n static override styles = [\n tokens,\n css`\n :host {\n position: fixed;\n top: 1rem;\n right: 1rem;\n z-index: 200;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n max-width: 22.5rem;\n pointer-events: none;\n }\n .toast {\n pointer-events: auto;\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n border-radius: var(--ui-radius, 0.5rem);\n padding: 0.5rem 0.75rem;\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 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.4;\n color: var(--ui-on-accent, #ffffff);\n background: var(--ui-text, #0f172a);\n }\n .toast.error {\n background: var(--ui-danger, #dc2626);\n }\n .toast.success {\n background: var(--ui-success, #16a34a);\n }\n .toast.info {\n background: var(--ui-info, #0ea5e9);\n }\n .message {\n flex: 1 1 auto;\n word-break: break-word;\n }\n .close {\n flex: 0 0 auto;\n background: none;\n border: none;\n padding: 0.25rem;\n margin: -0.25rem -0.25rem -0.25rem 0;\n color: inherit;\n opacity: 0.8;\n cursor: pointer;\n line-height: 0;\n border-radius: var(--ui-radius-sm, 0.25rem);\n }\n .close:hover {\n opacity: 1;\n background: var(--ui-hover-overlay, rgb(255 255 255 / 0.32));\n }\n .close:focus-visible {\n outline: none;\n opacity: 1;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n @media (forced-colors: active) {\n .toast {\n border: 1px solid CanvasText;\n forced-color-adjust: auto;\n }\n .close:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n @media (max-width: 48rem) {\n :host {\n top: 0.75rem;\n right: 0.75rem;\n left: 0.75rem;\n max-width: none;\n }\n }\n `,\n ];\n\n /**\n * Queues a toast for display.\n * @param message Text to show.\n * @param options Variant + auto-dismiss duration (see {@link ToastOptions}).\n * @returns The toast's id — pass to `dismiss()` to remove it early.\n */\n show(message: string, options: ToastOptions = {}): number {\n const id = this._nextId++;\n const variant = options.variant ?? \"info\";\n const duration = options.duration ?? DEFAULT_DURATION_MS;\n this._toasts = [...this._toasts, { id, message, variant }];\n if (duration > 0) {\n this._timers.set(\n id,\n setTimeout(() => this.dismiss(id), duration),\n );\n }\n return id;\n }\n\n /**\n * Removes a toast immediately, cancelling its pending auto-dismiss timer if any.\n * @param id The id returned by `show()`.\n */\n dismiss(id: number) {\n const timer = this._timers.get(id);\n if (timer) {\n clearTimeout(timer);\n this._timers.delete(id);\n }\n this._toasts = this._toasts.filter((t) => t.id !== id);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n for (const timer of this._timers.values()) clearTimeout(timer);\n this._timers.clear();\n }\n\n override render() {\n if (this._toasts.length === 0) return nothing;\n return html`\n ${this._toasts.map(\n (t) => html`\n <div\n class=\"toast ${t.variant}\"\n role=${t.variant === \"error\" ? \"alert\" : \"status\"}\n aria-atomic=\"true\"\n >\n <span class=\"message\">${t.message}</span>\n <button class=\"close\" aria-label=\"Dismiss notification\" @click=${() => this.dismiss(t.id)}>\n <span aria-hidden=\"true\">${iconX(14)}</span>\n </button>\n </div>\n `,\n )}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"toast-notification\": ToastNotification;\n }\n}\n\nfunction getToast(): ToastNotification | null {\n return document.querySelector(\"toast-notification\");\n}\n\n/** Shows an error toast. Use for connection/network failures, not validation errors. */\nexport function notifyError(message: string) {\n getToast()?.show(message, { variant: \"error\" });\n}\n\n/** Shows a success toast. */\nexport function notifySuccess(message: string) {\n getToast()?.show(message, { variant: \"success\" });\n}\n\n/** Shows an info toast. */\nexport function notifyInfo(message: string) {\n getToast()?.show(message, { variant: \"info\" });\n}\n"]}