@f-ewald/components 1.0.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 (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
@@ -8,6 +8,7 @@ import { LitElement, css, html, nothing } from "lit";
8
8
  import { customElement, property } from "lit/decorators.js";
9
9
  import { iconArrowPath } from "./icons.js";
10
10
  import { tokens } from "./tokens.js";
11
+ import { activateLayer, claimEscape, deactivateLayer, isTopLayer, } from "./utils/layer-stack.js";
11
12
  /**
12
13
  * Reusable confirmation dialog: overlay + centered card with a slotted body,
13
14
  * an optional error line, and Cancel/Confirm actions. Instant `display:none`
@@ -34,6 +35,38 @@ let ConfirmDialog = class ConfirmDialog extends LitElement {
34
35
  this.busy = false;
35
36
  /** Inline error line shown below the body, or null for none. */
36
37
  this.error = null;
38
+ this._previousFocus = null;
39
+ this._onDocumentFocusIn = (event) => {
40
+ if (!this.open || !isTopLayer(this) || event.composedPath().includes(this))
41
+ return;
42
+ this._focusInitial();
43
+ };
44
+ this._onWindowKeydown = (event) => {
45
+ if (!this.open || !isTopLayer(this))
46
+ return;
47
+ if (claimEscape(this, event) && !this.busy) {
48
+ this._cancel();
49
+ return;
50
+ }
51
+ if (event.key !== "Tab")
52
+ return;
53
+ const focusable = this._getFocusableElements();
54
+ if (focusable.length === 0) {
55
+ event.preventDefault();
56
+ this._focusInitial();
57
+ return;
58
+ }
59
+ const active = (this.shadowRoot?.activeElement ?? document.activeElement);
60
+ const index = focusable.indexOf(active);
61
+ if (event.shiftKey && index <= 0) {
62
+ event.preventDefault();
63
+ focusable.at(-1)?.focus();
64
+ }
65
+ else if (!event.shiftKey && index === focusable.length - 1) {
66
+ event.preventDefault();
67
+ focusable[0]?.focus();
68
+ }
69
+ };
37
70
  }
38
71
  static { this.styles = [
39
72
  tokens,
@@ -42,18 +75,18 @@ let ConfirmDialog = class ConfirmDialog extends LitElement {
42
75
  position: fixed;
43
76
  inset: 0;
44
77
  background: var(--ui-overlay, rgb(15 23 42 / 0.45));
45
- z-index: 50;
78
+ z-index: var(--component-layer-z, 50);
46
79
  display: none;
47
80
  align-items: flex-start;
48
81
  justify-content: center;
49
- padding: 10vh 20px 20px;
82
+ padding: 10vh 1.25rem 1.25rem;
50
83
  box-sizing: border-box;
51
84
  }
52
85
  .overlay.open {
53
86
  display: flex;
54
87
  }
55
88
  .dialog {
56
- background: var(--ui-surface, #fff);
89
+ background: var(--ui-surface, #ffffff);
57
90
  border-radius: var(--ui-radius, 0.5rem);
58
91
  box-shadow: var(
59
92
  --ui-shadow-lg,
@@ -61,28 +94,38 @@ let ConfirmDialog = class ConfirmDialog extends LitElement {
61
94
  0 8px 10px -6px rgb(0 0 0 / 0.1)
62
95
  );
63
96
  width: 100%;
64
- max-width: 420px;
97
+ max-width: 26.25rem;
65
98
  padding: 1.25rem;
66
99
  box-sizing: border-box;
100
+ outline: none;
67
101
  }
68
102
  .dialog-text {
69
103
  margin: 0 0 1rem;
70
104
  color: var(--ui-text, #0f172a);
71
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
105
+ font-family: var(
106
+ --ui-font,
107
+ ui-sans-serif,
108
+ system-ui,
109
+ sans-serif,
110
+ "Apple Color Emoji",
111
+ "Segoe UI Emoji",
112
+ "Segoe UI Symbol",
113
+ "Noto Color Emoji"
114
+ );
72
115
  font-size: var(--ui-font-size, 0.875rem);
73
116
  line-height: 1.45;
74
117
  }
75
118
  .dialog-actions {
76
119
  display: flex;
77
120
  justify-content: flex-end;
78
- gap: 0.6rem;
121
+ gap: 0.5rem;
79
122
  }
80
123
  .btn-cancel {
81
124
  background: none;
82
125
  border: 1px solid var(--ui-border, #e2e8f0);
83
126
  color: var(--ui-text, #0f172a);
84
127
  border-radius: var(--ui-radius-sm, 0.25rem);
85
- padding: 0.5rem 0.9rem;
128
+ padding: 0.5rem 1rem;
86
129
  font-size: var(--ui-font-size-sm, 0.75rem);
87
130
  cursor: pointer;
88
131
  font-family: inherit;
@@ -94,25 +137,25 @@ let ConfirmDialog = class ConfirmDialog extends LitElement {
94
137
  .btn-primary {
95
138
  border: none;
96
139
  border-radius: var(--ui-radius-sm, 0.25rem);
97
- padding: 0.5rem 0.9rem;
140
+ padding: 0.5rem 1rem;
98
141
  font-size: var(--ui-font-size-sm, 0.75rem);
99
142
  font-weight: 500;
100
143
  cursor: pointer;
101
144
  display: inline-flex;
102
145
  align-items: center;
103
- gap: 0.35rem;
146
+ gap: 0.25rem;
104
147
  font-family: inherit;
105
148
  }
106
149
  .btn-danger {
107
150
  background: var(--ui-danger, #dc2626);
108
- color: #fff;
151
+ color: var(--ui-on-accent, #ffffff);
109
152
  }
110
153
  .btn-danger:hover:not(:disabled) {
111
154
  background: var(--ui-danger-hover, #b91c1c);
112
155
  }
113
156
  .btn-primary {
114
157
  background: var(--ui-primary, #4f46e5);
115
- color: #fff;
158
+ color: var(--ui-on-accent, #ffffff);
116
159
  }
117
160
  .btn-primary:hover:not(:disabled) {
118
161
  background: var(--ui-primary-hover, #4338ca);
@@ -121,10 +164,14 @@ let ConfirmDialog = class ConfirmDialog extends LitElement {
121
164
  opacity: 0.6;
122
165
  cursor: default;
123
166
  }
167
+ button:focus-visible {
168
+ outline: none;
169
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
170
+ }
124
171
  .error-text {
125
172
  color: var(--ui-danger, #dc2626);
126
173
  font-size: var(--ui-font-size-sm, 0.75rem);
127
- margin: 0.4rem 0 0.8rem;
174
+ margin: 0.5rem 0 0.75rem;
128
175
  }
129
176
  .spin {
130
177
  display: inline-flex;
@@ -135,9 +182,21 @@ let ConfirmDialog = class ConfirmDialog extends LitElement {
135
182
  transform: rotate(360deg);
136
183
  }
137
184
  }
138
- @media (max-width: 768px) {
185
+ @media (prefers-reduced-motion: reduce) {
186
+ .spin {
187
+ animation: none;
188
+ }
189
+ }
190
+ @media (forced-colors: active) {
191
+ button:focus-visible {
192
+ outline: 2px solid CanvasText;
193
+ outline-offset: 2px;
194
+ box-shadow: none;
195
+ }
196
+ }
197
+ @media (max-width: 48rem) {
139
198
  .overlay {
140
- padding: 12px;
199
+ padding: 0.75rem;
141
200
  overflow-y: auto;
142
201
  }
143
202
  }
@@ -149,16 +208,69 @@ let ConfirmDialog = class ConfirmDialog extends LitElement {
149
208
  _cancel() {
150
209
  this.dispatchEvent(new CustomEvent("cancel", { bubbles: true, composed: true }));
151
210
  }
211
+ _getFocusableElements() {
212
+ const selector = "a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])";
213
+ const slotted = Array.from(this.querySelectorAll(selector));
214
+ const internal = Array.from(this.shadowRoot?.querySelectorAll(selector) ?? []).filter((element) => element.closest(".dialog") !== null);
215
+ return [...slotted, ...internal].filter((element) => !element.hasAttribute("hidden"));
216
+ }
217
+ _focusInitial() {
218
+ const autofocus = this.querySelector("[autofocus]");
219
+ const cancel = this.shadowRoot?.querySelector(".btn-cancel:not(:disabled)");
220
+ const dialog = this.shadowRoot?.querySelector(".dialog");
221
+ (autofocus ?? cancel ?? this._getFocusableElements()[0] ?? dialog)?.focus();
222
+ }
223
+ updated(changed) {
224
+ if (!changed.has("open"))
225
+ return;
226
+ if (this.open) {
227
+ activateLayer(this);
228
+ this._previousFocus = document.activeElement;
229
+ document.addEventListener("focusin", this._onDocumentFocusIn, true);
230
+ window.addEventListener("keydown", this._onWindowKeydown, true);
231
+ this._focusInitial();
232
+ return;
233
+ }
234
+ deactivateLayer(this);
235
+ document.removeEventListener("focusin", this._onDocumentFocusIn, true);
236
+ window.removeEventListener("keydown", this._onWindowKeydown, true);
237
+ if (this._previousFocus?.isConnected)
238
+ this._previousFocus.focus();
239
+ this._previousFocus = null;
240
+ }
241
+ disconnectedCallback() {
242
+ super.disconnectedCallback();
243
+ document.removeEventListener("focusin", this._onDocumentFocusIn, true);
244
+ window.removeEventListener("keydown", this._onWindowKeydown, true);
245
+ deactivateLayer(this);
246
+ if (this._previousFocus?.isConnected)
247
+ this._previousFocus.focus();
248
+ this._previousFocus = null;
249
+ this.open = false;
250
+ }
152
251
  render() {
252
+ const accessibleName = this.getAttribute("aria-label") ?? "Confirmation";
153
253
  return html `
154
254
  <div class="overlay ${this.open ? "open" : ""}">
155
- <div class="dialog">
156
- <p class="dialog-text"><slot></slot></p>
157
- ${this.error ? html `<p class="error-text">${this.error}</p>` : nothing}
255
+ <div
256
+ class="dialog"
257
+ role="dialog"
258
+ aria-modal="true"
259
+ aria-label=${accessibleName}
260
+ aria-describedby=${this.error ? "dialog-text dialog-error" : "dialog-text"}
261
+ tabindex="-1"
262
+ >
263
+ <p id="dialog-text" class="dialog-text"><slot></slot></p>
264
+ ${this.error
265
+ ? html `<p id="dialog-error" class="error-text" role="alert">${this.error}</p>`
266
+ : nothing}
158
267
  <div class="dialog-actions">
159
268
  <button class="btn-cancel" ?disabled=${this.busy} @click=${this._cancel}>${this.cancelLabel}</button>
160
269
  <button class=${this.danger ? "btn-danger" : "btn-primary"} ?disabled=${this.busy} @click=${this._confirm}>
161
- ${this.busy ? html `<span class="spin">${iconArrowPath(14)}</span>` : nothing} ${this.confirmLabel}
270
+ ${this.busy
271
+ ? html `<span class="spin" aria-hidden="true">${iconArrowPath(14)}</span>`
272
+ : nothing}
273
+ ${this.confirmLabel}
162
274
  </button>
163
275
  </div>
164
276
  </div>
@@ -1 +1 @@
1
- {"version":3,"file":"confirm-dialog.js","sourceRoot":"","sources":["../src/confirm-dialog.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;GAUG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QA8GL,qCAAqC;QACR,SAAI,GAAG,KAAK,CAAC;QAC1C,oCAAoC;QACM,iBAAY,GAAG,QAAQ,CAAC;QAClE,mCAAmC;QACM,gBAAW,GAAG,QAAQ,CAAC;QAChE,wEAAwE;QAC3C,WAAM,GAAG,IAAI,CAAC;QAC3C,8EAA8E;QACjD,SAAI,GAAG,KAAK,CAAC;QAC1C,gEAAgE;QACpD,UAAK,GAAkB,IAAI,CAAC;IA0B1C,CAAC;aAlJiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAwGF;KACF,AA3GqB,CA2GpB;IAeM,QAAQ;QACd,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACpF,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;4BACa,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;YAGvC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,yBAAyB,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO;;mDAE7B,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,WAAW;4BAC3E,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,cAAc,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC,QAAQ;gBACrG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA,sBAAsB,aAAa,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC,YAAY;;;;;KAK1G,CAAC;IACJ,CAAC;CACF,CAAA;AApC8B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CAAc;AAEA;IAAzC,QAAQ,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC;mDAAyB;AAEzB;IAAxC,QAAQ,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;kDAAwB;AAEnC;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;6CAAe;AAEd;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CAAc;AAE9B;IAAX,QAAQ,EAAE;4CAA6B;AAzH7B,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CAmJzB","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconArrowPath } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Reusable confirmation dialog: overlay + centered card with a slotted body,\n * an optional error line, and Cancel/Confirm actions. Instant `display:none`\n * → `display:flex` toggle (no transitions). Fires `confirm`/`cancel`\n * (bubbling, composed) instead of owning any deletion logic itself —\n * callers stay in charge of the request.\n *\n * @element confirm-dialog\n * @fires confirm - User clicked the confirm button.\n * @fires cancel - User clicked the cancel button.\n */\n@customElement(\"confirm-dialog\")\nexport class ConfirmDialog extends LitElement {\n static override styles = [\n tokens,\n css`\n .overlay {\n position: fixed;\n inset: 0;\n background: var(--ui-overlay, rgb(15 23 42 / 0.45));\n z-index: 50;\n display: none;\n align-items: flex-start;\n justify-content: center;\n padding: 10vh 20px 20px;\n box-sizing: border-box;\n }\n .overlay.open {\n display: flex;\n }\n .dialog {\n background: var(--ui-surface, #fff);\n border-radius: var(--ui-radius, 0.5rem);\n box-shadow: var(\n --ui-shadow-lg,\n 0 20px 25px -5px rgb(0 0 0 / 0.1),\n 0 8px 10px -6px rgb(0 0 0 / 0.1)\n );\n width: 100%;\n max-width: 420px;\n padding: 1.25rem;\n box-sizing: border-box;\n }\n .dialog-text {\n margin: 0 0 1rem;\n color: var(--ui-text, #0f172a);\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size, 0.875rem);\n line-height: 1.45;\n }\n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.6rem;\n }\n .btn-cancel {\n background: none;\n border: 1px solid var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 0.9rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n cursor: pointer;\n font-family: inherit;\n }\n .btn-cancel:hover:not(:disabled) {\n border-color: var(--ui-text-muted, #64748b);\n }\n .btn-danger,\n .btn-primary {\n border: none;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 0.9rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 0.35rem;\n font-family: inherit;\n }\n .btn-danger {\n background: var(--ui-danger, #dc2626);\n color: #fff;\n }\n .btn-danger:hover:not(:disabled) {\n background: var(--ui-danger-hover, #b91c1c);\n }\n .btn-primary {\n background: var(--ui-primary, #4f46e5);\n color: #fff;\n }\n .btn-primary:hover:not(:disabled) {\n background: var(--ui-primary-hover, #4338ca);\n }\n button:disabled {\n opacity: 0.6;\n cursor: default;\n }\n .error-text {\n color: var(--ui-danger, #dc2626);\n font-size: var(--ui-font-size-sm, 0.75rem);\n margin: 0.4rem 0 0.8rem;\n }\n .spin {\n display: inline-flex;\n animation: spin 0.8s linear infinite;\n }\n @keyframes spin {\n to {\n transform: rotate(360deg);\n }\n }\n @media (max-width: 768px) {\n .overlay {\n padding: 12px;\n overflow-y: auto;\n }\n }\n `,\n ];\n\n /** Whether the dialog is visible. */\n @property({ type: Boolean }) open = false;\n /** Label for the confirm button. */\n @property({ attribute: \"confirm-label\" }) confirmLabel = \"Delete\";\n /** Label for the cancel button. */\n @property({ attribute: \"cancel-label\" }) cancelLabel = \"Cancel\";\n /** Danger (red) vs. primary (indigo) styling for the confirm button. */\n @property({ type: Boolean }) danger = true;\n /** Shows a spinner and disables both buttons while a request is in flight. */\n @property({ type: Boolean }) busy = false;\n /** Inline error line shown below the body, or null for none. */\n @property() error: string | null = null;\n\n private _confirm() {\n this.dispatchEvent(new CustomEvent(\"confirm\", { bubbles: true, composed: true }));\n }\n\n private _cancel() {\n this.dispatchEvent(new CustomEvent(\"cancel\", { bubbles: true, composed: true }));\n }\n\n override render() {\n return html`\n <div class=\"overlay ${this.open ? \"open\" : \"\"}\">\n <div class=\"dialog\">\n <p class=\"dialog-text\"><slot></slot></p>\n ${this.error ? html`<p class=\"error-text\">${this.error}</p>` : nothing}\n <div class=\"dialog-actions\">\n <button class=\"btn-cancel\" ?disabled=${this.busy} @click=${this._cancel}>${this.cancelLabel}</button>\n <button class=${this.danger ? \"btn-danger\" : \"btn-primary\"} ?disabled=${this.busy} @click=${this._confirm}>\n ${this.busy ? html`<span class=\"spin\">${iconArrowPath(14)}</span>` : nothing} ${this.confirmLabel}\n </button>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"confirm-dialog\": ConfirmDialog;\n }\n}\n"]}
1
+ {"version":3,"file":"confirm-dialog.js","sourceRoot":"","sources":["../src/confirm-dialog.ts"],"names":[],"mappings":";;;;;;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,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,aAAa,EACb,WAAW,EACX,eAAe,EACf,UAAU,GACX,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;GAUG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,UAAU;IAAtC;;QAwIL,qCAAqC;QACR,SAAI,GAAG,KAAK,CAAC;QAC1C,oCAAoC;QACM,iBAAY,GAAG,QAAQ,CAAC;QAClE,mCAAmC;QACM,gBAAW,GAAG,QAAQ,CAAC;QAChE,wEAAwE;QAC3C,WAAM,GAAG,IAAI,CAAC;QAC3C,8EAA8E;QACjD,SAAI,GAAG,KAAK,CAAC;QAC1C,gEAAgE;QACpD,UAAK,GAAkB,IAAI,CAAC;QAEhC,mBAAc,GAAuB,IAAI,CAAC;QA2B1C,uBAAkB,GAAG,CAAC,KAAiB,EAAQ,EAAE;YACvD,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,OAAO;YACnF,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,CAAC,CAAC;QAEM,qBAAgB,GAAG,CAAC,KAAoB,EAAQ,EAAE;YACxD,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO;YAC5C,IAAI,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,OAAO;YACT,CAAC;YACD,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK;gBAAE,OAAO;YAEhC,MAAM,SAAS,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC/C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC3B,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,IAAI,CAAC,aAAa,EAAE,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,IAAI,QAAQ,CAAC,aAAa,CAAuB,CAAC;YAChG,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,MAAqB,CAAC,CAAC;YACvD,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBACjC,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;YAC5B,CAAC;iBAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,KAAK,KAAK,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7D,KAAK,CAAC,cAAc,EAAE,CAAC;gBACvB,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;YACxB,CAAC;QACH,CAAC,CAAC;IA0DJ,CAAC;aArQiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkIF;KACF,AArIqB,CAqIpB;IAiBM,QAAQ;QACd,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACpF,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;IAEO,qBAAqB;QAC3B,MAAM,QAAQ,GACZ,uIAAuI,CAAC;QAC1I,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAc,QAAQ,CAAC,CAAC,CAAC;QACzE,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CACzB,IAAI,CAAC,UAAU,EAAE,gBAAgB,CAAc,QAAQ,CAAC,IAAI,EAAE,CAC/D,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,CAAC;QAC3D,OAAO,CAAC,GAAG,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxF,CAAC;IAEO,aAAa;QACnB,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAc,aAAa,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAc,4BAA4B,CAAC,CAAC;QACzF,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAc,SAAS,CAAC,CAAC;QACtE,CAAC,SAAS,IAAI,MAAM,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;IAC9E,CAAC;IAgCkB,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,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;YACpE,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;YAChE,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QACD,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;QACvE,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;QACnE,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,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;QACvE,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;QACnE,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,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,IAAI,cAAc,CAAC;QACzE,OAAO,IAAI,CAAA;4BACa,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;;uBAK5B,cAAc;6BACR,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,aAAa;;;;YAIxE,IAAI,CAAC,KAAK;YACV,CAAC,CAAC,IAAI,CAAA,wDAAwD,IAAI,CAAC,KAAK,MAAM;YAC9E,CAAC,CAAC,OAAO;;mDAE8B,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,WAAW;4BAC3E,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,cAAc,IAAI,CAAC,IAAI,WAAW,IAAI,CAAC,QAAQ;gBACrG,IAAI,CAAC,IAAI;YACT,CAAC,CAAC,IAAI,CAAA,yCAAyC,aAAa,CAAC,EAAE,CAAC,SAAS;YACzE,CAAC,CAAC,OAAO;gBACT,IAAI,CAAC,YAAY;;;;;KAK5B,CAAC;IACJ,CAAC;CACF,CAAA;AA7H8B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CAAc;AAEA;IAAzC,QAAQ,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,CAAC;mDAAyB;AAEzB;IAAxC,QAAQ,CAAC,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;kDAAwB;AAEnC;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;6CAAe;AAEd;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;2CAAc;AAE9B;IAAX,QAAQ,EAAE;4CAA6B;AAnJ7B,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CAsQzB","sourcesContent":["import { LitElement, css, html, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconArrowPath } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\nimport {\n activateLayer,\n claimEscape,\n deactivateLayer,\n isTopLayer,\n} from \"./utils/layer-stack.js\";\n\n/**\n * Reusable confirmation dialog: overlay + centered card with a slotted body,\n * an optional error line, and Cancel/Confirm actions. Instant `display:none`\n * → `display:flex` toggle (no transitions). Fires `confirm`/`cancel`\n * (bubbling, composed) instead of owning any deletion logic itself —\n * callers stay in charge of the request.\n *\n * @element confirm-dialog\n * @fires confirm - User clicked the confirm button.\n * @fires cancel - User clicked the cancel button.\n */\n@customElement(\"confirm-dialog\")\nexport class ConfirmDialog extends LitElement {\n static override styles = [\n tokens,\n css`\n .overlay {\n position: fixed;\n inset: 0;\n background: var(--ui-overlay, rgb(15 23 42 / 0.45));\n z-index: var(--component-layer-z, 50);\n display: none;\n align-items: flex-start;\n justify-content: center;\n padding: 10vh 1.25rem 1.25rem;\n box-sizing: border-box;\n }\n .overlay.open {\n display: flex;\n }\n .dialog {\n background: var(--ui-surface, #ffffff);\n border-radius: var(--ui-radius, 0.5rem);\n box-shadow: var(\n --ui-shadow-lg,\n 0 20px 25px -5px rgb(0 0 0 / 0.1),\n 0 8px 10px -6px rgb(0 0 0 / 0.1)\n );\n width: 100%;\n max-width: 26.25rem;\n padding: 1.25rem;\n box-sizing: border-box;\n outline: none;\n }\n .dialog-text {\n margin: 0 0 1rem;\n color: var(--ui-text, #0f172a);\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n font-size: var(--ui-font-size, 0.875rem);\n line-height: 1.45;\n }\n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: 0.5rem;\n }\n .btn-cancel {\n background: none;\n border: 1px solid var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 1rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n cursor: pointer;\n font-family: inherit;\n }\n .btn-cancel:hover:not(:disabled) {\n border-color: var(--ui-text-muted, #64748b);\n }\n .btn-danger,\n .btn-primary {\n border: none;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 1rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 500;\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n font-family: inherit;\n }\n .btn-danger {\n background: var(--ui-danger, #dc2626);\n color: var(--ui-on-accent, #ffffff);\n }\n .btn-danger:hover:not(:disabled) {\n background: var(--ui-danger-hover, #b91c1c);\n }\n .btn-primary {\n background: var(--ui-primary, #4f46e5);\n color: var(--ui-on-accent, #ffffff);\n }\n .btn-primary:hover:not(:disabled) {\n background: var(--ui-primary-hover, #4338ca);\n }\n button:disabled {\n opacity: 0.6;\n cursor: default;\n }\n button:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .error-text {\n color: var(--ui-danger, #dc2626);\n font-size: var(--ui-font-size-sm, 0.75rem);\n margin: 0.5rem 0 0.75rem;\n }\n .spin {\n display: inline-flex;\n animation: spin 0.8s linear infinite;\n }\n @keyframes spin {\n to {\n transform: rotate(360deg);\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .spin {\n animation: none;\n }\n }\n @media (forced-colors: active) {\n button:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n @media (max-width: 48rem) {\n .overlay {\n padding: 0.75rem;\n overflow-y: auto;\n }\n }\n `,\n ];\n\n /** Whether the dialog is visible. */\n @property({ type: Boolean }) open = false;\n /** Label for the confirm button. */\n @property({ attribute: \"confirm-label\" }) confirmLabel = \"Delete\";\n /** Label for the cancel button. */\n @property({ attribute: \"cancel-label\" }) cancelLabel = \"Cancel\";\n /** Danger (red) vs. primary (indigo) styling for the confirm button. */\n @property({ type: Boolean }) danger = true;\n /** Shows a spinner and disables both buttons while a request is in flight. */\n @property({ type: Boolean }) busy = false;\n /** Inline error line shown below the body, or null for none. */\n @property() error: string | null = null;\n\n private _previousFocus: HTMLElement | null = null;\n\n private _confirm() {\n this.dispatchEvent(new CustomEvent(\"confirm\", { bubbles: true, composed: true }));\n }\n\n private _cancel() {\n this.dispatchEvent(new CustomEvent(\"cancel\", { bubbles: true, composed: true }));\n }\n\n private _getFocusableElements(): HTMLElement[] {\n const selector =\n \"a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])\";\n const slotted = Array.from(this.querySelectorAll<HTMLElement>(selector));\n const internal = Array.from(\n this.shadowRoot?.querySelectorAll<HTMLElement>(selector) ?? [],\n ).filter((element) => element.closest(\".dialog\") !== null);\n return [...slotted, ...internal].filter((element) => !element.hasAttribute(\"hidden\"));\n }\n\n private _focusInitial(): void {\n const autofocus = this.querySelector<HTMLElement>(\"[autofocus]\");\n const cancel = this.shadowRoot?.querySelector<HTMLElement>(\".btn-cancel:not(:disabled)\");\n const dialog = this.shadowRoot?.querySelector<HTMLElement>(\".dialog\");\n (autofocus ?? cancel ?? this._getFocusableElements()[0] ?? dialog)?.focus();\n }\n\n private _onDocumentFocusIn = (event: FocusEvent): void => {\n if (!this.open || !isTopLayer(this) || event.composedPath().includes(this)) return;\n this._focusInitial();\n };\n\n private _onWindowKeydown = (event: KeyboardEvent): void => {\n if (!this.open || !isTopLayer(this)) return;\n if (claimEscape(this, event) && !this.busy) {\n this._cancel();\n return;\n }\n if (event.key !== \"Tab\") return;\n\n const focusable = this._getFocusableElements();\n if (focusable.length === 0) {\n event.preventDefault();\n this._focusInitial();\n return;\n }\n const active = (this.shadowRoot?.activeElement ?? document.activeElement) as HTMLElement | null;\n const index = focusable.indexOf(active as HTMLElement);\n if (event.shiftKey && index <= 0) {\n event.preventDefault();\n focusable.at(-1)?.focus();\n } else if (!event.shiftKey && index === focusable.length - 1) {\n event.preventDefault();\n focusable[0]?.focus();\n }\n };\n\n protected override updated(changed: PropertyValues): void {\n if (!changed.has(\"open\")) return;\n if (this.open) {\n activateLayer(this);\n this._previousFocus = document.activeElement as HTMLElement | null;\n document.addEventListener(\"focusin\", this._onDocumentFocusIn, true);\n window.addEventListener(\"keydown\", this._onWindowKeydown, true);\n this._focusInitial();\n return;\n }\n deactivateLayer(this);\n document.removeEventListener(\"focusin\", this._onDocumentFocusIn, true);\n window.removeEventListener(\"keydown\", this._onWindowKeydown, true);\n if (this._previousFocus?.isConnected) this._previousFocus.focus();\n this._previousFocus = null;\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n document.removeEventListener(\"focusin\", this._onDocumentFocusIn, true);\n window.removeEventListener(\"keydown\", this._onWindowKeydown, true);\n deactivateLayer(this);\n if (this._previousFocus?.isConnected) this._previousFocus.focus();\n this._previousFocus = null;\n this.open = false;\n }\n\n override render() {\n const accessibleName = this.getAttribute(\"aria-label\") ?? \"Confirmation\";\n return html`\n <div class=\"overlay ${this.open ? \"open\" : \"\"}\">\n <div\n class=\"dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=${accessibleName}\n aria-describedby=${this.error ? \"dialog-text dialog-error\" : \"dialog-text\"}\n tabindex=\"-1\"\n >\n <p id=\"dialog-text\" class=\"dialog-text\"><slot></slot></p>\n ${this.error\n ? html`<p id=\"dialog-error\" class=\"error-text\" role=\"alert\">${this.error}</p>`\n : nothing}\n <div class=\"dialog-actions\">\n <button class=\"btn-cancel\" ?disabled=${this.busy} @click=${this._cancel}>${this.cancelLabel}</button>\n <button class=${this.danger ? \"btn-danger\" : \"btn-primary\"} ?disabled=${this.busy} @click=${this._confirm}>\n ${this.busy\n ? html`<span class=\"spin\" aria-hidden=\"true\">${iconArrowPath(14)}</span>`\n : nothing}\n ${this.confirmLabel}\n </button>\n </div>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"confirm-dialog\": ConfirmDialog;\n }\n}\n"]}
@@ -15,6 +15,8 @@ export declare class CopyLinkButton extends LitElement {
15
15
  value: string;
16
16
  /** Accessible label / tooltip text. */
17
17
  label: string;
18
+ /** Disables clipboard copying. */
19
+ disabled: boolean;
18
20
  static styles: import("lit").CSSResult[];
19
21
  private _onClick;
20
22
  render(): import("lit-html").TemplateResult<1>;
@@ -1 +1 @@
1
- {"version":3,"file":"copy-link-button.d.ts","sourceRoot":"","sources":["../src/copy-link-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAM5C;;;;;;;;;;GAUG;AACH,qBACa,cAAe,SAAQ,UAAU;IAC5C,sEAAsE;IAC1D,KAAK,SAAM;IACvB,uCAAuC;IAC3B,KAAK,SAAe;IAEhC,OAAgB,MAAM,4BAsBpB;YAEY,QAAQ;IAYb,MAAM,yCAMd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
1
+ {"version":3,"file":"copy-link-button.d.ts","sourceRoot":"","sources":["../src/copy-link-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAM5C;;;;;;;;;;GAUG;AACH,qBACa,cAAe,SAAQ,UAAU;IAC5C,sEAAsE;IAC1D,KAAK,SAAM;IACvB,uCAAuC;IAC3B,KAAK,SAAe;IAChC,kCAAkC;IACL,QAAQ,UAAS;IAE9C,OAAgB,MAAM,4BAyCpB;YAEY,QAAQ;IAab,MAAM,yCAYd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
@@ -27,6 +27,8 @@ let CopyLinkButton = class CopyLinkButton extends LitElement {
27
27
  this.value = "";
28
28
  /** Accessible label / tooltip text. */
29
29
  this.label = "Copy link";
30
+ /** Disables clipboard copying. */
31
+ this.disabled = false;
30
32
  }
31
33
  static { this.styles = [
32
34
  tokens,
@@ -38,20 +40,41 @@ let CopyLinkButton = class CopyLinkButton extends LitElement {
38
40
  background: none;
39
41
  border: none;
40
42
  cursor: pointer;
41
- padding: 4px;
43
+ padding: 0.25rem;
42
44
  color: var(--ui-text-muted, #64748b);
43
45
  border-radius: var(--ui-radius-sm, 0.25rem);
44
46
  display: inline-flex;
45
47
  align-items: center;
46
48
  justify-content: center;
47
49
  }
48
- button:hover {
50
+ button:hover:not(:disabled) {
49
51
  background: var(--ui-surface-muted, #f8fafc);
50
52
  color: var(--ui-text, #0f172a);
51
53
  }
54
+ button:disabled {
55
+ cursor: not-allowed;
56
+ opacity: 0.6;
57
+ }
58
+ button:focus-visible {
59
+ outline: none;
60
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
61
+ }
62
+ @media (forced-colors: active) {
63
+ button:focus-visible {
64
+ outline: 2px solid CanvasText;
65
+ outline-offset: 2px;
66
+ box-shadow: none;
67
+ }
68
+ button:disabled {
69
+ color: GrayText;
70
+ opacity: 1;
71
+ }
72
+ }
52
73
  `,
53
74
  ]; }
54
75
  async _onClick() {
76
+ if (this.disabled)
77
+ return;
55
78
  const text = this.value || window.location.href;
56
79
  try {
57
80
  await navigator.clipboard.writeText(text);
@@ -65,7 +88,13 @@ let CopyLinkButton = class CopyLinkButton extends LitElement {
65
88
  }
66
89
  render() {
67
90
  return html `
68
- <button type="button" aria-label=${this.label} title=${this.label} @click=${this._onClick}>
91
+ <button
92
+ type="button"
93
+ aria-label=${this.label}
94
+ title=${this.label}
95
+ ?disabled=${this.disabled}
96
+ @click=${this._onClick}
97
+ >
69
98
  ${iconLink(16)}
70
99
  </button>
71
100
  `;
@@ -77,6 +106,9 @@ __decorate([
77
106
  __decorate([
78
107
  property()
79
108
  ], CopyLinkButton.prototype, "label", void 0);
109
+ __decorate([
110
+ property({ type: Boolean })
111
+ ], CopyLinkButton.prototype, "disabled", void 0);
80
112
  CopyLinkButton = __decorate([
81
113
  customElement("copy-link-button")
82
114
  ], CopyLinkButton);
@@ -1 +1 @@
1
- {"version":3,"file":"copy-link-button.js","sourceRoot":"","sources":["../src/copy-link-button.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;GAUG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QACL,sEAAsE;QAC1D,UAAK,GAAG,EAAE,CAAC;QACvB,uCAAuC;QAC3B,UAAK,GAAG,WAAW,CAAC;IA6ClC,CAAC;aA3CiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;KAmBF;KACF,AAtBqB,CAsBpB;IAEM,KAAK,CAAC,QAAQ;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC1C,aAAa,CAAC,0BAA0B,CAAC,CAAC;YAC1C,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACzF,CAAC;QAAC,MAAM,CAAC;YACP,WAAW,CAAC,wBAAwB,CAAC,CAAC;YACtC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;yCAC0B,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC,QAAQ;UACrF,QAAQ,CAAC,EAAE,CAAC;;KAEjB,CAAC;IACJ,CAAC;CACF,CAAA;AA/Ca;IAAX,QAAQ,EAAE;6CAAY;AAEX;IAAX,QAAQ,EAAE;6CAAqB;AAJrB,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CAiD1B","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconLink } from \"./icons.js\";\nimport { notifyError, notifySuccess } from \"./toast-notification.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Small icon button that copies `value` to the clipboard and shows a toast\n * on success/failure (if a `<toast-notification>` element is present), and\n * always dispatches a `copy-success`/`copy-error` CustomEvent so consumers\n * without a toast element can react. Defaults to the current page URL if\n * `value` is unset.\n *\n * @element copy-link-button\n * @fires copy-success - The value was copied to the clipboard.\n * @fires copy-error - Copying to the clipboard failed.\n */\n@customElement(\"copy-link-button\")\nexport class CopyLinkButton extends LitElement {\n /** Text to copy. Defaults to `window.location.href` at click time. */\n @property() value = \"\";\n /** Accessible label / tooltip text. */\n @property() label = \"Copy link\";\n\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n button {\n background: none;\n border: none;\n cursor: pointer;\n padding: 4px;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n button:hover {\n background: var(--ui-surface-muted, #f8fafc);\n color: var(--ui-text, #0f172a);\n }\n `,\n ];\n\n private async _onClick() {\n const text = this.value || window.location.href;\n try {\n await navigator.clipboard.writeText(text);\n notifySuccess(\"Link copied to clipboard\");\n this.dispatchEvent(new CustomEvent(\"copy-success\", { bubbles: true, composed: true }));\n } catch {\n notifyError(\"Couldn't copy the link\");\n this.dispatchEvent(new CustomEvent(\"copy-error\", { bubbles: true, composed: true }));\n }\n }\n\n override render() {\n return html`\n <button type=\"button\" aria-label=${this.label} title=${this.label} @click=${this._onClick}>\n ${iconLink(16)}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"copy-link-button\": CopyLinkButton;\n }\n}\n"]}
1
+ {"version":3,"file":"copy-link-button.js","sourceRoot":"","sources":["../src/copy-link-button.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;GAUG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QACL,sEAAsE;QAC1D,UAAK,GAAG,EAAE,CAAC;QACvB,uCAAuC;QAC3B,UAAK,GAAG,WAAW,CAAC;QAChC,kCAAkC;QACL,aAAQ,GAAG,KAAK,CAAC;IAuEhD,CAAC;aArEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAsCF;KACF,AAzCqB,CAyCpB;IAEM,KAAK,CAAC,QAAQ;QACpB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC1C,aAAa,CAAC,0BAA0B,CAAC,CAAC;YAC1C,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACzF,CAAC;QAAC,MAAM,CAAC;YACP,WAAW,CAAC,wBAAwB,CAAC,CAAC;YACtC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;;qBAGM,IAAI,CAAC,KAAK;gBACf,IAAI,CAAC,KAAK;oBACN,IAAI,CAAC,QAAQ;iBAChB,IAAI,CAAC,QAAQ;;UAEpB,QAAQ,CAAC,EAAE,CAAC;;KAEjB,CAAC;IACJ,CAAC;CACF,CAAA;AA3Ea;IAAX,QAAQ,EAAE;6CAAY;AAEX;IAAX,QAAQ,EAAE;6CAAqB;AAEH;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;gDAAkB;AANnC,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CA6E1B","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconLink } from \"./icons.js\";\nimport { notifyError, notifySuccess } from \"./toast-notification.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Small icon button that copies `value` to the clipboard and shows a toast\n * on success/failure (if a `<toast-notification>` element is present), and\n * always dispatches a `copy-success`/`copy-error` CustomEvent so consumers\n * without a toast element can react. Defaults to the current page URL if\n * `value` is unset.\n *\n * @element copy-link-button\n * @fires copy-success - The value was copied to the clipboard.\n * @fires copy-error - Copying to the clipboard failed.\n */\n@customElement(\"copy-link-button\")\nexport class CopyLinkButton extends LitElement {\n /** Text to copy. Defaults to `window.location.href` at click time. */\n @property() value = \"\";\n /** Accessible label / tooltip text. */\n @property() label = \"Copy link\";\n /** Disables clipboard copying. */\n @property({ type: Boolean }) disabled = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-flex;\n }\n button {\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.25rem;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n button:hover:not(:disabled) {\n background: var(--ui-surface-muted, #f8fafc);\n color: var(--ui-text, #0f172a);\n }\n button:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n }\n button:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n @media (forced-colors: active) {\n button:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n button:disabled {\n color: GrayText;\n opacity: 1;\n }\n }\n `,\n ];\n\n private async _onClick() {\n if (this.disabled) return;\n const text = this.value || window.location.href;\n try {\n await navigator.clipboard.writeText(text);\n notifySuccess(\"Link copied to clipboard\");\n this.dispatchEvent(new CustomEvent(\"copy-success\", { bubbles: true, composed: true }));\n } catch {\n notifyError(\"Couldn't copy the link\");\n this.dispatchEvent(new CustomEvent(\"copy-error\", { bubbles: true, composed: true }));\n }\n }\n\n override render() {\n return html`\n <button\n type=\"button\"\n aria-label=${this.label}\n title=${this.label}\n ?disabled=${this.disabled}\n @click=${this._onClick}\n >\n ${iconLink(16)}\n </button>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"copy-link-button\": CopyLinkButton;\n }\n}\n"]}
@@ -29,6 +29,8 @@ export declare class DataTable extends LitElement {
29
29
  renderCell: (row: unknown, key: string) => unknown;
30
30
  /** When set, clicking a row (outside any nested link/button) navigates to this hash. */
31
31
  rowHref: ((row: unknown) => string | null) | null;
32
+ /** Accessible label for a row's keyboard link; defaults to primitive cell values. */
33
+ rowLabel: ((row: unknown) => string) | null;
32
34
  render(): import("lit-html").TemplateResult<1>;
33
35
  }
34
36
  declare global {
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,yGAAyG;AACzG,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,qBACa,SAAU,SAAQ,UAAU;;IACvC,OAAgB,MAAM,4BA+BpB;IAEF,wCAAwC;IACR,OAAO,EAAE,eAAe,EAAE,CAAM;IAChE,gFAAgF;IAChD,IAAI,EAAE,OAAO,EAAE,CAAM;IACrD,oGAAoG;IACpE,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAG7E;IACX,sFAAsF;IACtD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAGvC;IAC3C,wFAAwF;IACxD,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAQ;IAShF,MAAM,yCAwBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,YAAY,EAAE,SAAS,CAAC;KACzB;CACF"}
1
+ {"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,yGAAyG;AACzG,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,qBACa,SAAU,SAAQ,UAAU;;IACvC,OAAgB,MAAM,4BA+DpB;IAEF,wCAAwC;IACR,OAAO,EAAE,eAAe,EAAE,CAAM;IAChE,gFAAgF;IAChD,IAAI,EAAE,OAAO,EAAE,CAAM;IACrD,oGAAoG;IACpE,MAAM,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAG7E;IACX,sFAAsF;IACtD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAGvC;IAC3C,wFAAwF;IACxD,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAQ;IACzF,qFAAqF;IACrD,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC,GAAG,IAAI,CAAQ;IA4B1E,MAAM,yCA0Cd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,YAAY,EAAE,SAAS,CAAC;KACzB;CACF"}
@@ -33,13 +33,24 @@ let DataTable = class DataTable extends LitElement {
33
33
  this.renderCell = (row, key) => row[key];
34
34
  /** When set, clicking a row (outside any nested link/button) navigates to this hash. */
35
35
  this.rowHref = null;
36
+ /** Accessible label for a row's keyboard link; defaults to primitive cell values. */
37
+ this.rowLabel = null;
36
38
  }
37
39
  static { this.styles = [
38
40
  tokens,
39
41
  css `
40
42
  :host {
41
43
  display: block;
42
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
44
+ font-family: var(
45
+ --ui-font,
46
+ ui-sans-serif,
47
+ system-ui,
48
+ sans-serif,
49
+ "Apple Color Emoji",
50
+ "Segoe UI Emoji",
51
+ "Segoe UI Symbol",
52
+ "Noto Color Emoji"
53
+ );
43
54
  font-size: var(--ui-font-size-sm, 0.75rem);
44
55
  }
45
56
  table {
@@ -54,27 +65,69 @@ let DataTable = class DataTable extends LitElement {
54
65
  border-bottom: 1px solid var(--ui-border, #e2e8f0);
55
66
  }
56
67
  td {
57
- padding: 0.6rem 0.75rem;
68
+ padding: 0.5rem 0.75rem;
58
69
  border-bottom: 1px solid var(--ui-border, #e2e8f0);
59
70
  color: var(--ui-text, #0f172a);
60
71
  }
72
+ td:first-child {
73
+ position: relative;
74
+ }
75
+ .row-link {
76
+ position: absolute;
77
+ width: 1px;
78
+ height: 1px;
79
+ overflow: hidden;
80
+ clip: rect(0 0 0 0);
81
+ clip-path: inset(50%);
82
+ white-space: nowrap;
83
+ }
61
84
  tbody tr.clickable {
62
85
  cursor: pointer;
63
86
  }
64
87
  tbody tr.clickable:hover {
65
88
  background: var(--ui-surface-muted, #f8fafc);
66
89
  }
90
+ tbody tr.clickable:has(.row-link:focus-visible) {
91
+ outline: none;
92
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35)) inset;
93
+ }
94
+ @media (forced-colors: active) {
95
+ tbody tr.clickable:has(.row-link:focus-visible) {
96
+ outline: 2px solid CanvasText;
97
+ outline-offset: -2px;
98
+ box-shadow: none;
99
+ }
100
+ }
67
101
  `,
68
102
  ]; }
69
103
  #onRowClick(row, e) {
70
104
  if (!this.rowHref)
71
105
  return;
72
- if (e.target.closest("a, button"))
106
+ if (this.#isNestedInteractive(e.composedPath(), e.currentTarget))
73
107
  return;
74
108
  const href = this.rowHref(row);
75
109
  if (href)
76
110
  location.hash = href;
77
111
  }
112
+ #isNestedInteractive(path, row) {
113
+ const selector = "a, button, input, select, textarea, summary, [contenteditable], [role='button'], [role='link'], [tabindex]";
114
+ for (const target of path) {
115
+ if (target === row)
116
+ return false;
117
+ if (target instanceof HTMLElement && target.matches(selector))
118
+ return true;
119
+ }
120
+ return false;
121
+ }
122
+ #rowLinkLabel(row) {
123
+ const explicitLabel = this.rowLabel?.(row).trim();
124
+ if (explicitLabel)
125
+ return explicitLabel;
126
+ const values = this.columns
127
+ .map((column) => row[column.key])
128
+ .filter((value) => typeof value === "string" || typeof value === "number");
129
+ return values.length > 0 ? `Open ${values.join(", ")}` : "Open row";
130
+ }
78
131
  render() {
79
132
  return html `
80
133
  <table>
@@ -84,14 +137,30 @@ let DataTable = class DataTable extends LitElement {
84
137
  </tr>
85
138
  </thead>
86
139
  <tbody>
87
- ${repeat(this.rows, (row, i) => this.rowKey(row, i), (row) => html `
88
- <tr
89
- class=${this.rowHref ? "clickable" : ""}
90
- @click=${(e) => this.#onRowClick(row, e)}
91
- >
92
- ${this.columns.map((c) => html `<td>${this.renderCell(row, c.key) ?? nothing}</td>`)}
93
- </tr>
94
- `)}
140
+ ${repeat(this.rows, (row, i) => this.rowKey(row, i), (row) => {
141
+ const href = this.rowHref?.(row) ?? null;
142
+ return html `
143
+ <tr
144
+ class=${href ? "clickable" : ""}
145
+ @click=${(e) => this.#onRowClick(row, e)}
146
+ >
147
+ ${this.columns.map((c, columnIndex) => html `
148
+ <td>
149
+ ${href && columnIndex === 0
150
+ ? html `
151
+ <a
152
+ class="row-link"
153
+ href=${href}
154
+ aria-label=${this.#rowLinkLabel(row)}
155
+ ></a>
156
+ `
157
+ : nothing}
158
+ ${this.renderCell(row, c.key) ?? nothing}
159
+ </td>
160
+ `)}
161
+ </tr>
162
+ `;
163
+ })}
95
164
  </tbody>
96
165
  </table>
97
166
  `;
@@ -112,6 +181,9 @@ __decorate([
112
181
  __decorate([
113
182
  property({ attribute: false })
114
183
  ], DataTable.prototype, "rowHref", void 0);
184
+ __decorate([
185
+ property({ attribute: false })
186
+ ], DataTable.prototype, "rowLabel", void 0);
115
187
  DataTable = __decorate([
116
188
  customElement("data-table")
117
189
  ], DataTable);
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.js","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC;;;;;;;;;;;GAWG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,UAAU;IAAlC;;QAkCL,wCAAwC;QACR,YAAO,GAAsB,EAAE,CAAC;QAChE,gFAAgF;QAChD,SAAI,GAAc,EAAE,CAAC;QACrD,oGAAoG;QACpE,WAAM,GAAqD,CACzF,IAAI,EACJ,KAAK,EACL,EAAE,CAAC,KAAK,CAAC;QACX,sFAAsF;QACtD,eAAU,GAA2C,CACnF,GAAG,EACH,GAAG,EACH,EAAE,CAAE,GAA+B,CAAC,GAAG,CAAC,CAAC;QAC3C,wFAAwF;QACxD,YAAO,GAA6C,IAAI,CAAC;IAkC3F,CAAC;aAlFiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA4BF;KACF,AA/BqB,CA+BpB;IAmBF,WAAW,CAAC,GAAY,EAAE,CAAa;QACrC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAK,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,WAAW,CAAC;YAAE,OAAO;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,IAAI;YAAE,QAAQ,CAAC,IAAI,GAAG,IAAI,CAAC;IACjC,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;;;cAID,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAA,OAAO,CAAC,CAAC,KAAK,OAAO,CAAC;;;;YAIpD,MAAM,CACN,IAAI,CAAC,IAAI,EACT,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,EAC/B,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;wBAED,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE;yBAC9B,CAAC,CAAa,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC;;kBAElD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAA,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,OAAO,OAAO,CAAC;;aAEtF,CACF;;;KAGN,CAAC;IACJ,CAAC;CACF,CAAA;AAhDiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;0CAAiC;AAEhC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;uCAAsB;AAErB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;yCAGpB;AAEqB;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;6CAGY;AAEX;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;0CAA0D;AAjD9E,SAAS;IADrB,aAAa,CAAC,YAAY,CAAC;GACf,SAAS,CAmFrB","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\n\n/** One column header: `key` is looked up via `renderCell`/`getCellValue`, `label` is the header text. */\nexport interface DataTableColumn {\n key: string;\n label: string;\n}\n\n/**\n * A generic, presentational table shell: renders a `<thead>` from `columns`\n * and one `<tr>` per entry in `rows`, with each cell's content produced by\n * `renderCell` (default: plain property lookup on the row object). Knows\n * nothing about what a \"row\" means — callers own the data shape entirely.\n *\n * Optional `rowHref` makes whole rows clickable (navigating via `location.hash`),\n * without hijacking clicks on nested interactive elements (links/buttons) a\n * cell's rendered content might contain.\n *\n * @element data-table\n */\n@customElement(\"data-table\")\nexport class DataTable extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size-sm, 0.75rem);\n }\n table {\n width: 100%;\n border-collapse: collapse;\n }\n th {\n text-align: left;\n font-weight: 600;\n color: var(--ui-text-muted, #64748b);\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n }\n td {\n padding: 0.6rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\n }\n tbody tr.clickable {\n cursor: pointer;\n }\n tbody tr.clickable:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n `,\n ];\n\n /** Column headers, in display order. */\n @property({ attribute: false }) columns: DataTableColumn[] = [];\n /** Row data; opaque to this component beyond what `renderCell` does with it. */\n @property({ attribute: false }) rows: unknown[] = [];\n /** Stable identity for `rows[i]`, used as the repeat-directive key. Defaults to the row's index. */\n @property({ attribute: false }) rowKey: (row: unknown, index: number) => string | number = (\n _row,\n index,\n ) => index;\n /** Produces a cell's rendered content for `row`/`column.key`. Default: `row[key]`. */\n @property({ attribute: false }) renderCell: (row: unknown, key: string) => unknown = (\n row,\n key,\n ) => (row as Record<string, unknown>)[key];\n /** When set, clicking a row (outside any nested link/button) navigates to this hash. */\n @property({ attribute: false }) rowHref: ((row: unknown) => string | null) | null = null;\n\n #onRowClick(row: unknown, e: MouseEvent): void {\n if (!this.rowHref) return;\n if ((e.target as HTMLElement).closest(\"a, button\")) return;\n const href = this.rowHref(row);\n if (href) location.hash = href;\n }\n\n override render() {\n return html`\n <table>\n <thead>\n <tr>\n ${this.columns.map((c) => html`<th>${c.label}</th>`)}\n </tr>\n </thead>\n <tbody>\n ${repeat(\n this.rows,\n (row, i) => this.rowKey(row, i),\n (row) => html`\n <tr\n class=${this.rowHref ? \"clickable\" : \"\"}\n @click=${(e: MouseEvent) => this.#onRowClick(row, e)}\n >\n ${this.columns.map((c) => html`<td>${this.renderCell(row, c.key) ?? nothing}</td>`)}\n </tr>\n `,\n )}\n </tbody>\n </table>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"data-table\": DataTable;\n }\n}\n"]}
1
+ {"version":3,"file":"data-table.js","sourceRoot":"","sources":["../src/data-table.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\n\n/** One column header: `key` is looked up via `renderCell`/`getCellValue`, `label` is the header text. */\nexport interface DataTableColumn {\n key: string;\n label: string;\n}\n\n/**\n * A generic, presentational table shell: renders a `<thead>` from `columns`\n * and one `<tr>` per entry in `rows`, with each cell's content produced by\n * `renderCell` (default: plain property lookup on the row object). Knows\n * nothing about what a \"row\" means — callers own the data shape entirely.\n *\n * Optional `rowHref` makes whole rows clickable (navigating via `location.hash`),\n * without hijacking clicks on nested interactive elements (links/buttons) a\n * cell's rendered content might contain.\n *\n * @element data-table\n */\n@customElement(\"data-table\")\nexport class DataTable extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n font-size: var(--ui-font-size-sm, 0.75rem);\n }\n table {\n width: 100%;\n border-collapse: collapse;\n }\n th {\n text-align: left;\n font-weight: 600;\n color: var(--ui-text-muted, #64748b);\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n }\n td {\n padding: 0.5rem 0.75rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n color: var(--ui-text, #0f172a);\n }\n td:first-child {\n position: relative;\n }\n .row-link {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n }\n tbody tr.clickable {\n cursor: pointer;\n }\n tbody tr.clickable:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n tbody tr.clickable:has(.row-link:focus-visible) {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35)) inset;\n }\n @media (forced-colors: active) {\n tbody tr.clickable:has(.row-link:focus-visible) {\n outline: 2px solid CanvasText;\n outline-offset: -2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n /** Column headers, in display order. */\n @property({ attribute: false }) columns: DataTableColumn[] = [];\n /** Row data; opaque to this component beyond what `renderCell` does with it. */\n @property({ attribute: false }) rows: unknown[] = [];\n /** Stable identity for `rows[i]`, used as the repeat-directive key. Defaults to the row's index. */\n @property({ attribute: false }) rowKey: (row: unknown, index: number) => string | number = (\n _row,\n index,\n ) => index;\n /** Produces a cell's rendered content for `row`/`column.key`. Default: `row[key]`. */\n @property({ attribute: false }) renderCell: (row: unknown, key: string) => unknown = (\n row,\n key,\n ) => (row as Record<string, unknown>)[key];\n /** When set, clicking a row (outside any nested link/button) navigates to this hash. */\n @property({ attribute: false }) rowHref: ((row: unknown) => string | null) | null = null;\n /** Accessible label for a row's keyboard link; defaults to primitive cell values. */\n @property({ attribute: false }) rowLabel: ((row: unknown) => string) | null = null;\n\n #onRowClick(row: unknown, e: MouseEvent): void {\n if (!this.rowHref) return;\n if (this.#isNestedInteractive(e.composedPath(), e.currentTarget)) return;\n const href = this.rowHref(row);\n if (href) location.hash = href;\n }\n\n #isNestedInteractive(path: EventTarget[], row: EventTarget | null): boolean {\n const selector =\n \"a, button, input, select, textarea, summary, [contenteditable], [role='button'], [role='link'], [tabindex]\";\n for (const target of path) {\n if (target === row) return false;\n if (target instanceof HTMLElement && target.matches(selector)) return true;\n }\n return false;\n }\n\n #rowLinkLabel(row: unknown): string {\n const explicitLabel = this.rowLabel?.(row).trim();\n if (explicitLabel) return explicitLabel;\n const values = this.columns\n .map((column) => (row as Record<string, unknown>)[column.key])\n .filter((value) => typeof value === \"string\" || typeof value === \"number\");\n return values.length > 0 ? `Open ${values.join(\", \")}` : \"Open row\";\n }\n\n override render() {\n return html`\n <table>\n <thead>\n <tr>\n ${this.columns.map((c) => html`<th>${c.label}</th>`)}\n </tr>\n </thead>\n <tbody>\n ${repeat(\n this.rows,\n (row, i) => this.rowKey(row, i),\n (row) => {\n const href = this.rowHref?.(row) ?? null;\n return html`\n <tr\n class=${href ? \"clickable\" : \"\"}\n @click=${(e: MouseEvent) => this.#onRowClick(row, e)}\n >\n ${this.columns.map(\n (c, columnIndex) => html`\n <td>\n ${href && columnIndex === 0\n ? html`\n <a\n class=\"row-link\"\n href=${href}\n aria-label=${this.#rowLinkLabel(row)}\n ></a>\n `\n : nothing}\n ${this.renderCell(row, c.key) ?? nothing}\n </td>\n `,\n )}\n </tr>\n `;\n },\n )}\n </tbody>\n </table>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"data-table\": DataTable;\n }\n}\n"]}
@@ -9,7 +9,6 @@ import { LitElement, nothing } from "lit";
9
9
  * @element distance-value
10
10
  */
11
11
  export declare class DistanceValue extends LitElement {
12
- static styles: import("lit").CSSResult;
13
12
  /** Distance in miles (imperial). Switches to feet below 0.25 mi. */
14
13
  miles: number | null;
15
14
  /** Distance in kilometers (metric, future). Switches to meters below 0.5 km. */
@@ -1 +1 @@
1
- {"version":3,"file":"distance-value.d.ts","sourceRoot":"","sources":["../src/distance-value.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAE,MAAM,KAAK,CAAC;AAIrD;;;;;;;;GAQG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC3C,OAAgB,MAAM,0BAIpB;IAEF,oEAAoE;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IAExD,gFAAgF;IACpD,EAAE,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE5C,MAAM,0DAId;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,aAAa,CAAC;KACjC;CACF"}
1
+ {"version":3,"file":"distance-value.d.ts","sourceRoot":"","sources":["../src/distance-value.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,OAAO,EAAE,MAAM,KAAK,CAAC;AAIhD;;;;;;;;GAQG;AACH,qBACa,aAAc,SAAQ,UAAU;IAC3C,oEAAoE;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAQ;IAExD,gFAAgF;IACpD,EAAE,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE5C,MAAM,0DAId;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,gBAAgB,EAAE,aAAa,CAAC;KACjC;CACF"}