@f-ewald/components 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +6 -0
  2. package/custom-elements.json +891 -22
  3. package/dist/address-autocomplete.d.ts +8 -0
  4. package/dist/address-autocomplete.d.ts.map +1 -1
  5. package/dist/address-autocomplete.js +165 -15
  6. package/dist/address-autocomplete.js.map +1 -1
  7. package/dist/animate-confetti.d.ts +8 -5
  8. package/dist/animate-confetti.d.ts.map +1 -1
  9. package/dist/animate-confetti.js +46 -19
  10. package/dist/animate-confetti.js.map +1 -1
  11. package/dist/autocomplete-input.d.ts +8 -0
  12. package/dist/autocomplete-input.d.ts.map +1 -1
  13. package/dist/autocomplete-input.js +165 -15
  14. package/dist/autocomplete-input.js.map +1 -1
  15. package/dist/calendar-month.d.ts.map +1 -1
  16. package/dist/calendar-month.js +27 -13
  17. package/dist/calendar-month.js.map +1 -1
  18. package/dist/calendar-year.d.ts.map +1 -1
  19. package/dist/calendar-year.js +11 -2
  20. package/dist/calendar-year.js.map +1 -1
  21. package/dist/chat-message.d.ts.map +1 -1
  22. package/dist/chat-message.js +77 -24
  23. package/dist/chat-message.js.map +1 -1
  24. package/dist/confirm-dialog.d.ts +8 -1
  25. package/dist/confirm-dialog.d.ts.map +1 -1
  26. package/dist/confirm-dialog.js +130 -18
  27. package/dist/confirm-dialog.js.map +1 -1
  28. package/dist/copy-link-button.d.ts +2 -0
  29. package/dist/copy-link-button.d.ts.map +1 -1
  30. package/dist/copy-link-button.js +35 -3
  31. package/dist/copy-link-button.js.map +1 -1
  32. package/dist/data-table.d.ts +2 -0
  33. package/dist/data-table.d.ts.map +1 -1
  34. package/dist/data-table.js +83 -11
  35. package/dist/data-table.js.map +1 -1
  36. package/dist/distance-value.d.ts +0 -1
  37. package/dist/distance-value.d.ts.map +1 -1
  38. package/dist/distance-value.js +3 -8
  39. package/dist/distance-value.js.map +1 -1
  40. package/dist/distribution-chart.d.ts.map +1 -1
  41. package/dist/distribution-chart.js +54 -17
  42. package/dist/distribution-chart.js.map +1 -1
  43. package/dist/dropdown-button.d.ts.map +1 -1
  44. package/dist/dropdown-button.js +60 -11
  45. package/dist/dropdown-button.js.map +1 -1
  46. package/dist/editable-text.d.ts.map +1 -1
  47. package/dist/editable-text.js +46 -13
  48. package/dist/editable-text.js.map +1 -1
  49. package/dist/form-select.d.ts.map +1 -1
  50. package/dist/form-select.js +70 -22
  51. package/dist/form-select.js.map +1 -1
  52. package/dist/frame-box.d.ts.map +1 -1
  53. package/dist/frame-box.js +20 -12
  54. package/dist/frame-box.js.map +1 -1
  55. package/dist/icon-button.d.ts.map +1 -1
  56. package/dist/icon-button.js +11 -0
  57. package/dist/icon-button.js.map +1 -1
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/kbd-hint.d.ts +30 -0
  63. package/dist/kbd-hint.d.ts.map +1 -0
  64. package/dist/kbd-hint.js +184 -0
  65. package/dist/kbd-hint.js.map +1 -0
  66. package/dist/live-timer.d.ts +0 -1
  67. package/dist/live-timer.d.ts.map +1 -1
  68. package/dist/live-timer.js +1 -6
  69. package/dist/live-timer.js.map +1 -1
  70. package/dist/map-circle.d.ts.map +1 -1
  71. package/dist/map-circle.js +16 -1
  72. package/dist/map-circle.js.map +1 -1
  73. package/dist/map-pin.d.ts.map +1 -1
  74. package/dist/map-pin.js +16 -1
  75. package/dist/map-pin.js.map +1 -1
  76. package/dist/percent-bar-chart.d.ts +3 -0
  77. package/dist/percent-bar-chart.d.ts.map +1 -1
  78. package/dist/percent-bar-chart.js +60 -4
  79. package/dist/percent-bar-chart.js.map +1 -1
  80. package/dist/photo-gallery.d.ts.map +1 -1
  81. package/dist/photo-gallery.js +12 -3
  82. package/dist/photo-gallery.js.map +1 -1
  83. package/dist/popover-panel.d.ts.map +1 -1
  84. package/dist/popover-panel.js +132 -23
  85. package/dist/popover-panel.js.map +1 -1
  86. package/dist/price-history-chart.d.ts.map +1 -1
  87. package/dist/price-history-chart.js +52 -18
  88. package/dist/price-history-chart.js.map +1 -1
  89. package/dist/radio-cards.d.ts +2 -0
  90. package/dist/radio-cards.d.ts.map +1 -1
  91. package/dist/radio-cards.js +51 -5
  92. package/dist/radio-cards.js.map +1 -1
  93. package/dist/radio-pills.d.ts +2 -0
  94. package/dist/radio-pills.d.ts.map +1 -1
  95. package/dist/radio-pills.js +46 -4
  96. package/dist/radio-pills.js.map +1 -1
  97. package/dist/relative-time.d.ts +0 -1
  98. package/dist/relative-time.d.ts.map +1 -1
  99. package/dist/relative-time.js +1 -6
  100. package/dist/relative-time.js.map +1 -1
  101. package/dist/reveal-button.d.ts +2 -0
  102. package/dist/reveal-button.d.ts.map +1 -1
  103. package/dist/reveal-button.js +42 -5
  104. package/dist/reveal-button.js.map +1 -1
  105. package/dist/roman-numeral.js +1 -1
  106. package/dist/roman-numeral.js.map +1 -1
  107. package/dist/slide-panel.d.ts +7 -3
  108. package/dist/slide-panel.d.ts.map +1 -1
  109. package/dist/slide-panel.js +95 -23
  110. package/dist/slide-panel.js.map +1 -1
  111. package/dist/stat-meter.d.ts.map +1 -1
  112. package/dist/stat-meter.js +17 -7
  113. package/dist/stat-meter.js.map +1 -1
  114. package/dist/status-pill.d.ts.map +1 -1
  115. package/dist/status-pill.js +26 -12
  116. package/dist/status-pill.js.map +1 -1
  117. package/dist/tile-grid.d.ts +8 -0
  118. package/dist/tile-grid.d.ts.map +1 -1
  119. package/dist/tile-grid.js +88 -12
  120. package/dist/tile-grid.js.map +1 -1
  121. package/dist/toast-notification.d.ts.map +1 -1
  122. package/dist/toast-notification.js +40 -11
  123. package/dist/toast-notification.js.map +1 -1
  124. package/dist/tokens.css +19 -0
  125. package/dist/tokens.d.ts.map +1 -1
  126. package/dist/tokens.js +13 -0
  127. package/dist/tokens.js.map +1 -1
  128. package/dist/ui-button.d.ts +2 -0
  129. package/dist/ui-button.d.ts.map +1 -1
  130. package/dist/ui-button.js +58 -9
  131. package/dist/ui-button.js.map +1 -1
  132. package/dist/user-avatar.d.ts.map +1 -1
  133. package/dist/user-avatar.js +18 -3
  134. package/dist/user-avatar.js.map +1 -1
  135. package/dist/utils/form.d.ts +3 -0
  136. package/dist/utils/form.d.ts.map +1 -0
  137. package/dist/utils/form.js +14 -0
  138. package/dist/utils/form.js.map +1 -0
  139. package/dist/utils/layer-stack.d.ts +9 -0
  140. package/dist/utils/layer-stack.d.ts.map +1 -0
  141. package/dist/utils/layer-stack.js +35 -0
  142. package/dist/utils/layer-stack.js.map +1 -0
  143. package/dist/weight-bar-chart.d.ts.map +1 -1
  144. package/dist/weight-bar-chart.js +27 -8
  145. package/dist/weight-bar-chart.js.map +1 -1
  146. package/docs/address-autocomplete.md +5 -0
  147. package/docs/autocomplete-input.md +5 -0
  148. package/docs/calendar-month.md +2 -0
  149. package/docs/chat-message.md +4 -0
  150. package/docs/confirm-dialog.md +2 -0
  151. package/docs/copy-link-button.md +2 -0
  152. package/docs/data-table.md +2 -0
  153. package/docs/design-language.md +149 -0
  154. package/docs/distribution-chart.md +4 -0
  155. package/docs/dropdown-button.md +1 -0
  156. package/docs/frame-box.md +2 -0
  157. package/docs/kbd-hint.md +42 -0
  158. package/docs/percent-bar-chart.md +4 -1
  159. package/docs/photo-gallery.md +1 -0
  160. package/docs/popover-panel.md +2 -0
  161. package/docs/price-history-chart.md +8 -1
  162. package/docs/radio-cards.md +2 -0
  163. package/docs/radio-pills.md +2 -0
  164. package/docs/reveal-button.md +3 -0
  165. package/docs/slide-panel.md +4 -2
  166. package/docs/status-pill.md +3 -0
  167. package/docs/tile-grid.md +10 -1
  168. package/docs/toast-notification.md +4 -1
  169. package/docs/ui-button.md +2 -0
  170. package/docs/user-avatar.md +1 -0
  171. package/llms.txt +59 -34
  172. package/package.json +5 -1
@@ -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>
@@ -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":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAuB,MAAM,KAAK,CAAC;AAC1E,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;AACrC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAErF;;;;;;;;;;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;QA2HjB,mBAAc,GAAuB,IAAI,CAAC;QAM1C,qBAAgB,GAAG,CAAC,KAAoB,EAAQ,EAAE;YACxD,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC;gBAAE,OAAO;YACpD,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,CAAC,CAAC;IAmDJ,CAAC;aArLiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoHF;KACF,AAvHqB,CAuHpB;IAIM,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;IAOkB,OAAO,CAAC,OAAuB;QAChD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO;QACjC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,aAAa,CAAC,IAAI,CAAC,CAAC;YACpB,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC,aAAmC,CAAC;YACnE,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC1D,IAAI,CAAC,UAAU,EAAE,aAAa,CAAc,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC;YACnE,OAAO;QACT,CAAC;QACD,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC7D,IAAI,IAAI,CAAC,cAAc,EAAE,WAAW;YAAE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAClE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;IAC7B,CAAC;IAEQ,oBAAoB;QAC3B,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC7B,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC7D,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,cAAc,EAAE,WAAW;YAAE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAClE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;IACpB,CAAC;IAEQ,MAAM;QACb,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QACtD,OAAO,IAAI,CAAA;;uBAEQ,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;qBAGzB,aAAa,IAAI,OAAO;0BACnB,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa;sBAC3C,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;iBACvB,CAAC,IAAI,CAAC,IAAI;;;;;iCAKM,IAAI,CAAC,OAAO,IAAI,OAAO;;sEAEc,IAAI,CAAC,MAAM;uCAC1C,KAAK,CAAC,EAAE,CAAC;;;;;KAK3C,CAAC;IACJ,CAAC;CACF,CAAA;AAzL8B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;wCAAc;AAE9B;IAAX,QAAQ,EAAE;2CAAc;AAJd,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CA2LtB","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
+ /* Keep the top highlight while softening the dark edge for compact meters. */
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)) 80%, #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 /* Keep the top highlight while softening the dark edge for compact meters. */\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)) 80%, #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"}