@adia-ai/web-components 0.8.3 → 0.8.5

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 (95) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/USAGE.md +60 -25
  3. package/components/accordion/accordion.css +5 -0
  4. package/components/adia-mark/adia-mark.a2ui.json +123 -0
  5. package/components/adia-mark/adia-mark.class.js +64 -0
  6. package/components/adia-mark/adia-mark.css +48 -0
  7. package/components/adia-mark/adia-mark.d.ts +20 -0
  8. package/components/adia-mark/adia-mark.examples.md +26 -0
  9. package/components/adia-mark/adia-mark.js +17 -0
  10. package/components/adia-mark/adia-mark.test.js +69 -0
  11. package/components/adia-mark/adia-mark.yaml +121 -0
  12. package/components/agent-artifact/agent-artifact.css +1 -0
  13. package/components/agent-questions/agent-questions.a2ui.json +2 -1
  14. package/components/agent-questions/agent-questions.class.js +96 -53
  15. package/components/agent-questions/agent-questions.css +27 -106
  16. package/components/agent-questions/agent-questions.yaml +1 -0
  17. package/components/agent-reasoning/agent-reasoning.css +9 -5
  18. package/components/button/button.class.js +9 -2
  19. package/components/button/button.css +1 -1
  20. package/components/calendar-grid/calendar-grid.a2ui.json +3 -1
  21. package/components/calendar-grid/calendar-grid.class.js +11 -6
  22. package/components/calendar-grid/calendar-grid.css +42 -20
  23. package/components/calendar-grid/calendar-grid.yaml +5 -0
  24. package/components/calendar-picker/calendar-picker.a2ui.json +3 -1
  25. package/components/calendar-picker/calendar-picker.class.js +11 -6
  26. package/components/calendar-picker/calendar-picker.css +41 -19
  27. package/components/calendar-picker/calendar-picker.yaml +5 -0
  28. package/components/chart/chart.class.js +17 -14
  29. package/components/chat-thread/chat-input.a2ui.json +3 -1
  30. package/components/chat-thread/chat-input.css +12 -13
  31. package/components/chat-thread/chat-input.js +6 -2
  32. package/components/chat-thread/chat-input.yaml +3 -0
  33. package/components/check/check.css +1 -1
  34. package/components/color-input/color-input.class.js +8 -5
  35. package/components/color-picker/color-picker.class.js +6 -3
  36. package/components/combobox/combobox.a2ui.json +3 -1
  37. package/components/combobox/combobox.class.js +1 -3
  38. package/components/combobox/combobox.css +9 -12
  39. package/components/combobox/combobox.yaml +2 -0
  40. package/components/date-range-picker/date-range-picker.css +1 -0
  41. package/components/field/field.class.js +10 -7
  42. package/components/fields/fields.class.js +15 -12
  43. package/components/icon/icon.class.js +9 -5
  44. package/components/index.js +1 -0
  45. package/components/input/input.css +6 -2
  46. package/components/nav/nav.class.js +2 -1
  47. package/components/nav-group/nav-group.css +5 -6
  48. package/components/noodles/noodles.class.js +14 -10
  49. package/components/option-card/option-card.css +1 -0
  50. package/components/page/page.class.js +3 -2
  51. package/components/radio/radio.css +1 -1
  52. package/components/range/range.css +2 -0
  53. package/components/select/select.a2ui.json +2 -1
  54. package/components/select/select.class.js +12 -11
  55. package/components/select/select.css +29 -40
  56. package/components/select/select.test.js +6 -2
  57. package/components/select/select.yaml +1 -0
  58. package/components/swatch/swatch.a2ui.json +3 -1
  59. package/components/swatch/swatch.class.js +41 -32
  60. package/components/swatch/swatch.css +13 -18
  61. package/components/swatch/swatch.yaml +2 -0
  62. package/components/swiper/swiper.class.js +4 -1
  63. package/components/swiper/swiper.css +13 -4
  64. package/components/switch/switch.css +1 -1
  65. package/components/tag/tag.a2ui.json +4 -1
  66. package/components/tag/tag.class.js +3 -1
  67. package/components/tag/tag.css +13 -15
  68. package/components/tag/tag.yaml +3 -0
  69. package/components/tags-input/tags-input.css +1 -0
  70. package/components/textarea/textarea.css +2 -0
  71. package/components/time-picker/time-picker.css +2 -0
  72. package/components/timeline/timeline.a2ui.json +2 -1
  73. package/components/timeline/timeline.class.js +7 -8
  74. package/components/timeline/timeline.css +14 -26
  75. package/components/timeline/timeline.yaml +1 -0
  76. package/components/toggle-group/toggle-group.css +1 -0
  77. package/components/toolbar/toolbar.class.js +11 -6
  78. package/components/tree/tree.class.js +5 -2
  79. package/components/tree/tree.css +5 -2
  80. package/components/upload/upload.css +1 -0
  81. package/core/data-stream.js +21 -15
  82. package/core/element.js +36 -1
  83. package/core/element.test.js +67 -0
  84. package/core/icons-phosphor.js +86 -22
  85. package/core/icons.js +22 -8
  86. package/dist/theme-provider.min.js +2 -2
  87. package/dist/web-components.min.css +1 -1
  88. package/dist/web-components.min.js +109 -108
  89. package/dist/web-components.sheet.js +1 -1
  90. package/package.json +1 -5
  91. package/styles/colors/semantics/features.css +6 -1
  92. package/styles/components.css +1 -0
  93. package/traits/error-shake/error-shake.js +23 -17
  94. package/traits/fade-presence/fade-presence.js +19 -15
  95. package/traits/success-checkmark/success-checkmark.js +21 -16
@@ -115,14 +115,23 @@
115
115
  color: var(--calendar-grid-title-fg);
116
116
  }
117
117
 
118
- [data-cal-prev],
119
- [data-cal-next] {
118
+ /* Stamped <button-ui variant="ghost" size="xs" icon="caret-…"> (gh issue
119
+ 276 wave 4) — icon-only (no [text], no child text) so button-ui's own
120
+ [data-icon-only] rule already squares it to --button-height; pointing
121
+ --button-height at the nav size (not just literal width/height) avoids
122
+ the min-width/min-height floor trap. Literal background/color below
123
+ out-specify button.css's own (via the `button-ui` type-selector bump —
124
+ same technique as swiper's dots), so the button-ui type-selector prefix
125
+ is load-bearing, not decorative. */
126
+ button-ui[data-cal-prev],
127
+ button-ui[data-cal-next] {
128
+ --button-height: var(--calendar-grid-nav-size);
129
+ --a-icon-size: var(--calendar-grid-nav-icon-size);
130
+
120
131
  display: inline-flex;
121
132
  align-items: center;
122
133
  justify-content: center;
123
- width: var(--calendar-grid-nav-size);
124
134
  height: var(--calendar-grid-nav-size);
125
- padding: 0;
126
135
  border: none;
127
136
  border-radius: var(--calendar-grid-nav-radius);
128
137
  background: var(--calendar-grid-nav-bg);
@@ -131,10 +140,9 @@
131
140
  transition:
132
141
  background var(--calendar-grid-duration) var(--calendar-grid-easing),
133
142
  color var(--calendar-grid-duration) var(--calendar-grid-easing);
134
- --a-icon-size: var(--calendar-grid-nav-icon-size);
135
143
  }
136
- [data-cal-prev]:hover,
137
- [data-cal-next]:hover {
144
+ button-ui[data-cal-prev]:hover,
145
+ button-ui[data-cal-next]:hover {
138
146
  background: var(--calendar-grid-nav-bg-hover);
139
147
  color: var(--calendar-grid-nav-fg-hover);
140
148
  }
@@ -164,7 +172,18 @@
164
172
  gap: var(--calendar-grid-day-grid-gap);
165
173
  }
166
174
 
167
- [data-cal-day] {
175
+ /* Stamped <button-ui variant="ghost" text="…"> (gh issue 276 wave 4).
176
+ button-ui floors min-width/min-height at --button-height — point it at
177
+ the day-cell size (not just the literal width/height below) so the
178
+ floor never clamps a smaller [size] tier. The literal geometry/color
179
+ declarations below out-specify button-ui's own equivalents via the
180
+ `button-ui` type-selector bump (same technique as swiper's dots).
181
+ button-ui renders its label via `::after { content: attr(text) }` — the
182
+ today-indicator dot below therefore uses `::before` (kept free), not
183
+ `::after`, to avoid the two fighting over the same pseudo-element. */
184
+ button-ui[data-cal-day] {
185
+ --button-height: var(--calendar-grid-day-size);
186
+
168
187
  display: flex;
169
188
  align-items: center;
170
189
  justify-content: center;
@@ -188,25 +207,25 @@
188
207
  }
189
208
 
190
209
  /* ── State: hover ── */
191
- [data-cal-day]:not([disabled]):not([data-outside]):hover {
210
+ button-ui[data-cal-day]:not([disabled]):not([data-outside]):hover {
192
211
  background: var(--calendar-grid-day-bg-hover);
193
212
  color: var(--calendar-grid-day-fg-hover);
194
213
  }
195
214
 
196
215
  /* ── State: focus ── */
197
- [data-cal-day]:not([disabled]):not([data-outside]):focus-visible,
198
- [data-cal-day][data-focused] {
216
+ button-ui[data-cal-day]:not([disabled]):not([data-outside]):focus-visible,
217
+ button-ui[data-cal-day][data-focused] {
199
218
  outline: none;
200
219
  box-shadow: var(--calendar-grid-day-focus-ring);
201
220
  }
202
221
 
203
222
  /* ── State: selected ── */
204
- [data-cal-day][data-selected] {
223
+ button-ui[data-cal-day][data-selected] {
205
224
  background: var(--calendar-grid-day-bg-selected);
206
225
  color: var(--calendar-grid-day-fg-selected);
207
226
  font-weight: var(--calendar-grid-selected-weight);
208
227
  }
209
- [data-cal-day][data-selected]:not([disabled]):hover {
228
+ button-ui[data-cal-day][data-selected]:not([disabled]):hover {
210
229
  background: var(--calendar-grid-day-bg-selected);
211
230
  color: var(--calendar-grid-day-fg-selected);
212
231
  }
@@ -217,21 +236,21 @@
217
236
  fill); the in-range middle is visually subordinate. `:not([data-selected])`
218
237
  guard prevents the muted fill from overriding the endpoint cells in
219
238
  consumer code that sets both `value` AND `range-start`/`range-end`. */
220
- [data-cal-day][data-in-range]:not([data-selected]) {
239
+ button-ui[data-cal-day][data-in-range]:not([data-selected]) {
221
240
  background: var(--calendar-grid-day-bg-in-range);
222
241
  color: var(--calendar-grid-day-fg-in-range);
223
242
  }
224
- [data-cal-day][data-in-range]:not([data-selected]):not([disabled]):hover {
243
+ button-ui[data-cal-day][data-in-range]:not([data-selected]):not([disabled]):hover {
225
244
  background: var(--calendar-grid-day-bg-in-range);
226
245
  }
227
246
 
228
247
  /* ── State: today (unselected) ── */
229
- [data-cal-day][data-today]:not([data-selected]) {
248
+ button-ui[data-cal-day][data-today]:not([data-selected]) {
230
249
  position: relative;
231
250
  color: var(--calendar-grid-day-today-color);
232
251
  font-weight: var(--calendar-grid-today-weight);
233
252
  }
234
- [data-cal-day][data-today]:not([data-selected])::after {
253
+ button-ui[data-cal-day][data-today]:not([data-selected])::before {
235
254
  content: "";
236
255
  position: absolute;
237
256
  left: 50%;
@@ -245,13 +264,16 @@
245
264
  }
246
265
 
247
266
  /* ── State: outside month ── */
248
- [data-cal-day][data-outside] {
267
+ button-ui[data-cal-day][data-outside] {
249
268
  color: var(--calendar-grid-day-fg-outside);
250
269
  cursor: default;
251
270
  }
252
271
 
253
- /* ── State: disabled (excluding outside) ── */
254
- [data-cal-day]:disabled:not([data-outside]) {
272
+ /* ── State: disabled (excluding outside) ──
273
+ `[disabled]` — NOT the `:disabled` pseudo-class, which only matches
274
+ native form controls; button-ui is a custom element, so `:disabled`
275
+ silently never matches it. */
276
+ button-ui[data-cal-day][disabled]:not([data-outside]) {
255
277
  color: var(--calendar-grid-day-fg-disabled);
256
278
  cursor: not-allowed;
257
279
  }
@@ -16,6 +16,11 @@ description: >-
16
16
  (two side-by-side grids), and datetime-picker-ui (one grid + time pane).
17
17
  For a complete date-picker affordance (trigger + popover + form
18
18
  participation), use calendar-picker-ui.
19
+ # Per ADR-0027 — primitives that programmatically create other primitives
20
+ # in their render() do NOT auto-import them. Consumer (or demo shell) must
21
+ # explicitly import each composed primitive.
22
+ composes:
23
+ - button-ui # day cells + month nav (gh issue 276 wave 4)
19
24
  props:
20
25
  value:
21
26
  description: Selected date in ISO format (YYYY-MM-DD).
@@ -94,7 +94,9 @@
94
94
  "x-adiaui": {
95
95
  "anti_patterns": [],
96
96
  "category": "input",
97
- "composes": [],
97
+ "composes": [
98
+ "button-ui"
99
+ ],
98
100
  "events": {
99
101
  "change": {
100
102
  "description": "Fired when a date is selected"
@@ -191,10 +191,14 @@ export class UICalendarPicker extends UIFormElement {
191
191
  const maxDate = parseISO(this.max);
192
192
 
193
193
  // Header
194
+ // <button-ui variant="ghost" icon="…"> (gh issue 276 wave 4) — no
195
+ // tabindex here, matching the native version: these nav buttons stay in
196
+ // the default tab order (unlike calendar-grid-ui's, which are pulled out
197
+ // via tabindex="-1" for its inline roving-tabindex day grid).
194
198
  let h = `<div data-cal-header>
195
- <button data-cal-prev type="button" aria-label="Previous month"><icon-ui name="caret-left"></icon-ui></button>
199
+ <button-ui variant="ghost" size="xs" icon="caret-left" data-cal-prev aria-label="Previous month"></button-ui>
196
200
  <span data-cal-title>${MONTHS_LONG[month]} ${year}</span>
197
- <button data-cal-next type="button" aria-label="Next month"><icon-ui name="caret-right"></icon-ui></button>
201
+ <button-ui variant="ghost" size="xs" icon="caret-right" data-cal-next aria-label="Next month"></button-ui>
198
202
  </div>`;
199
203
 
200
204
  // Day-of-week header
@@ -210,7 +214,7 @@ export class UICalendarPicker extends UIFormElement {
210
214
  // Previous month trailing days
211
215
  for (let i = firstDay - 1; i >= 0; i--) {
212
216
  const day = daysInPrev - i;
213
- h += `<button type="button" data-cal-day data-outside disabled tabindex="-1">${day}</button>`;
217
+ h += `<button-ui variant="ghost" data-cal-day data-outside disabled tabindex="-1" text="${day}"></button-ui>`;
214
218
  }
215
219
 
216
220
  // Current month days
@@ -223,7 +227,6 @@ export class UICalendarPicker extends UIFormElement {
223
227
  const isFocused = this.#focusedDay === d;
224
228
 
225
229
  const attrs = [
226
- 'type="button"',
227
230
  'data-cal-day',
228
231
  `data-date="${iso}"`,
229
232
  isToday ? 'data-today' : '',
@@ -233,14 +236,16 @@ export class UICalendarPicker extends UIFormElement {
233
236
  `tabindex="${isFocused ? '0' : '-1'}"`,
234
237
  ].filter(Boolean).join(' ');
235
238
 
236
- h += `<button ${attrs}>${d}</button>`;
239
+ // <button-ui variant="ghost" text="…"> (gh issue 276 wave 4) — the day
240
+ // number moves from element text content to the `text` attribute.
241
+ h += `<button-ui variant="ghost" ${attrs} text="${d}"></button-ui>`;
237
242
  }
238
243
 
239
244
  // Next month leading days
240
245
  const totalCells = firstDay + daysInMonth;
241
246
  const remaining = (7 - (totalCells % 7)) % 7;
242
247
  for (let d = 1; d <= remaining; d++) {
243
- h += `<button type="button" data-cal-day data-outside disabled tabindex="-1">${d}</button>`;
248
+ h += `<button-ui variant="ghost" data-cal-day data-outside disabled tabindex="-1" text="${d}"></button-ui>`;
244
249
  }
245
250
 
246
251
  h += '</div>';
@@ -156,10 +156,12 @@
156
156
  :scope:focus-visible [slot="trigger"] {
157
157
  /* Canonical ring via L3 token (see semantics.css FOCUS block). */
158
158
  box-shadow: var(--calendar-picker-trigger-focus-ring);
159
+ border-color: transparent;
159
160
  }
160
161
  :scope[aria-invalid="true"]:focus-visible [slot="trigger"],
161
162
  :scope[error]:focus-visible [slot="trigger"] {
162
163
  box-shadow: var(--calendar-picker-trigger-focus-ring-invalid);
164
+ border-color: transparent;
163
165
  }
164
166
 
165
167
  [slot="display"] {
@@ -230,14 +232,22 @@ calendar-picker-ui [data-cal-title] {
230
232
  color: var(--calendar-picker-title-fg);
231
233
  }
232
234
 
233
- calendar-picker-ui [data-cal-prev],
234
- calendar-picker-ui [data-cal-next] {
235
+ /* Stamped <button-ui variant="ghost" size="xs" icon="caret-…"> (gh issue 276
236
+ wave 4) — icon-only (no [text], no child text) so button-ui's own
237
+ [data-icon-only] rule already squares it to --button-height; pointing
238
+ --button-height at the nav size (not just literal height below) avoids
239
+ the min-width/min-height floor trap. Literal background/color below
240
+ out-specify button.css's own via the `button-ui` type-selector bump
241
+ (same technique as swiper's dots). */
242
+ calendar-picker-ui button-ui[data-cal-prev],
243
+ calendar-picker-ui button-ui[data-cal-next] {
244
+ --button-height: var(--calendar-picker-nav-size);
245
+ --a-icon-size: var(--calendar-picker-nav-icon-size);
246
+
235
247
  display: inline-flex;
236
248
  align-items: center;
237
249
  justify-content: center;
238
- width: var(--calendar-picker-nav-size);
239
250
  height: var(--calendar-picker-nav-size);
240
- padding: 0;
241
251
  border: none;
242
252
  border-radius: var(--calendar-picker-nav-radius);
243
253
  background: var(--calendar-picker-nav-bg);
@@ -246,10 +256,9 @@ calendar-picker-ui [data-cal-next] {
246
256
  transition:
247
257
  background var(--calendar-picker-duration) var(--calendar-picker-easing),
248
258
  color var(--calendar-picker-duration) var(--calendar-picker-easing);
249
- --a-icon-size: var(--calendar-picker-nav-icon-size);
250
259
  }
251
- calendar-picker-ui [data-cal-prev]:hover,
252
- calendar-picker-ui [data-cal-next]:hover {
260
+ calendar-picker-ui button-ui[data-cal-prev]:hover,
261
+ calendar-picker-ui button-ui[data-cal-next]:hover {
253
262
  background: var(--calendar-picker-nav-bg-hover);
254
263
  color: var(--calendar-picker-nav-fg-hover);
255
264
  }
@@ -279,8 +288,18 @@ calendar-picker-ui [data-cal-grid] {
279
288
  gap: var(--calendar-picker-day-grid-gap);
280
289
  }
281
290
 
282
- /* Day buttons */
283
- calendar-picker-ui [data-cal-day] {
291
+ /* Day buttons — stamped <button-ui variant="ghost" text="…"> (gh issue 276
292
+ wave 4). button-ui floors min-width/min-height at --button-height — point
293
+ it at the day-cell size (not just the literal width/height below) so the
294
+ floor never clamps a smaller [size] tier. The literal geometry/color
295
+ declarations below out-specify button-ui's own equivalents via the
296
+ `button-ui` type-selector bump (same technique as swiper's dots).
297
+ button-ui renders its label via `::after { content: attr(text) }` — the
298
+ today-indicator dot below therefore uses `::before` (kept free), not
299
+ `::after`, to avoid the two fighting over the same pseudo-element. */
300
+ calendar-picker-ui button-ui[data-cal-day] {
301
+ --button-height: var(--calendar-picker-day-size);
302
+
284
303
  display: flex;
285
304
  align-items: center;
286
305
  justify-content: center;
@@ -302,25 +321,25 @@ calendar-picker-ui [data-cal-day] {
302
321
  }
303
322
 
304
323
  /* ── State: hover ── */
305
- calendar-picker-ui [data-cal-day]:not([disabled]):not([data-outside]):hover {
324
+ calendar-picker-ui button-ui[data-cal-day]:not([disabled]):not([data-outside]):hover {
306
325
  background: var(--calendar-picker-day-bg-hover);
307
326
  color: var(--calendar-picker-day-fg-hover);
308
327
  }
309
328
 
310
329
  /* ── State: focus ── */
311
- calendar-picker-ui [data-cal-day]:not([disabled]):not([data-outside]):focus-visible,
312
- calendar-picker-ui [data-cal-day][data-focused] {
330
+ calendar-picker-ui button-ui[data-cal-day]:not([disabled]):not([data-outside]):focus-visible,
331
+ calendar-picker-ui button-ui[data-cal-day][data-focused] {
313
332
  outline: none;
314
333
  box-shadow: var(--calendar-picker-day-focus-ring);
315
334
  }
316
335
 
317
336
  /* ── State: selected ── */
318
- calendar-picker-ui [data-cal-day][data-selected] {
337
+ calendar-picker-ui button-ui[data-cal-day][data-selected] {
319
338
  background: var(--calendar-picker-day-bg-selected);
320
339
  color: var(--calendar-picker-day-fg-selected);
321
340
  font-weight: var(--calendar-picker-selected-weight);
322
341
  }
323
- calendar-picker-ui [data-cal-day][data-selected]:not([disabled]):hover {
342
+ calendar-picker-ui button-ui[data-cal-day][data-selected]:not([disabled]):hover {
324
343
  background: var(--calendar-picker-day-bg-selected-hover);
325
344
  color: var(--calendar-picker-day-fg-selected);
326
345
  }
@@ -328,12 +347,12 @@ calendar-picker-ui [data-cal-day][data-selected]:not([disabled]):hover {
328
347
  /* ── State: today (unselected) ──
329
348
  Use a small bottom dot so it does not compete with the rounded
330
349
  focus-ring box-shadow (that looked like a double outline). */
331
- calendar-picker-ui [data-cal-day][data-today]:not([data-selected]) {
350
+ calendar-picker-ui button-ui[data-cal-day][data-today]:not([data-selected]) {
332
351
  position: relative;
333
352
  color: var(--calendar-picker-day-today-border);
334
353
  font-weight: var(--calendar-picker-today-weight);
335
354
  }
336
- calendar-picker-ui [data-cal-day][data-today]:not([data-selected])::after {
355
+ calendar-picker-ui button-ui[data-cal-day][data-today]:not([data-selected])::before {
337
356
  content: "";
338
357
  position: absolute;
339
358
  left: 50%;
@@ -347,13 +366,16 @@ calendar-picker-ui [data-cal-day][data-today]:not([data-selected])::after {
347
366
  }
348
367
 
349
368
  /* ── State: outside month ── */
350
- calendar-picker-ui [data-cal-day][data-outside] {
369
+ calendar-picker-ui button-ui[data-cal-day][data-outside] {
351
370
  color: var(--calendar-picker-day-fg-outside);
352
371
  cursor: default;
353
372
  }
354
373
 
355
- /* ── State: disabled ── */
356
- calendar-picker-ui [data-cal-day]:disabled:not([data-outside]) {
374
+ /* ── State: disabled ──
375
+ `[disabled]` — NOT the `:disabled` pseudo-class, which only matches
376
+ native form controls; button-ui is a custom element, so `:disabled`
377
+ silently never matches it. */
378
+ calendar-picker-ui button-ui[data-cal-day][disabled]:not([data-outside]) {
357
379
  color: var(--calendar-picker-day-fg-disabled);
358
380
  cursor: not-allowed;
359
381
  }
@@ -11,6 +11,11 @@ description: >-
11
11
  supports min/max and disabled-date masks. Use for single-date input in
12
12
  forms; for date ranges compose two pickers or use a dedicated range
13
13
  component.
14
+ # Per ADR-0027 — primitives that programmatically create other primitives
15
+ # in their render() do NOT auto-import them. Consumer (or demo shell) must
16
+ # explicitly import each composed primitive.
17
+ composes:
18
+ - button-ui # day cells + month nav (gh issue 276 wave 4)
14
19
  props:
15
20
  name:
16
21
  description: Form field name
@@ -341,21 +341,24 @@ export class UIChart extends UIElement {
341
341
  this.addEventListener('pointerdown', this.#onPointerDown);
342
342
  this.addEventListener('click', this.#onClick);
343
343
 
344
- this.#resizeObs = new ResizeObserver((entries) => {
345
- const { inlineSize: w, blockSize: h } = entries[0].contentBoxSize[0];
346
- if (!this.#data.length) return;
347
- const rw = Math.round(w);
348
- const rh = Math.round(h);
349
- if (rw === this.#lastW && (!this.hasAttribute('resize') || rh === this.#lastH)) return;
350
- this.#lastW = rw;
351
- this.#lastH = rh;
352
- if (this.#resizeRaf) return;
353
- this.#resizeRaf = requestAnimationFrame(() => {
354
- this.#resizeRaf = null;
355
- this.#renderChart();
344
+ // gh#285 SSR DOM shims (linkedom) have no ResizeObserver global.
345
+ if (typeof ResizeObserver !== 'undefined') {
346
+ this.#resizeObs = new ResizeObserver((entries) => {
347
+ const { inlineSize: w, blockSize: h } = entries[0].contentBoxSize[0];
348
+ if (!this.#data.length) return;
349
+ const rw = Math.round(w);
350
+ const rh = Math.round(h);
351
+ if (rw === this.#lastW && (!this.hasAttribute('resize') || rh === this.#lastH)) return;
352
+ this.#lastW = rw;
353
+ this.#lastH = rh;
354
+ if (this.#resizeRaf) return;
355
+ this.#resizeRaf = requestAnimationFrame(() => {
356
+ this.#resizeRaf = null;
357
+ this.#renderChart();
358
+ });
356
359
  });
357
- });
358
- this.#resizeObs.observe(this);
360
+ this.#resizeObs.observe(this);
361
+ }
359
362
  }
360
363
 
361
364
  render() {
@@ -59,7 +59,9 @@
59
59
  }
60
60
  ],
61
61
  "category": "agent",
62
- "composes": [],
62
+ "composes": [
63
+ "button-ui"
64
+ ],
63
65
  "events": {
64
66
  "submit": {
65
67
  "description": "Fires when the user presses Enter (without Shift) in the textarea OR clicks the built-in send button. Unconditional — there is no [submit-on-enter] opt-in or opt-out attribute; Enter→submit is delegated from the inner <textarea-ui> which always emits a `submit` event on Enter without Shift. The composer suppresses submission while `[loading]` is set.",
@@ -122,10 +122,12 @@ chat-input-ui textarea-ui:not([disabled]) [slot="text"]:hover {
122
122
  ─────────────────────────────────────────────────────────────── */
123
123
  :scope:focus-within {
124
124
  box-shadow: var(--chat-input-focus-ring);
125
+ border-color: transparent;
125
126
  }
126
127
  :scope[aria-invalid="true"]:focus-within,
127
128
  :scope[error]:focus-within {
128
129
  box-shadow: var(--chat-input-focus-ring-invalid);
130
+ border-color: transparent;
129
131
  }
130
132
 
131
133
  /* ── Nested-control color isolation ─────────────────────────────────
@@ -239,23 +241,20 @@ chat-input-ui textarea-ui:not([disabled]) [slot="text"]:hover {
239
241
  border: 1px solid var(--chat-input-image-border);
240
242
  }
241
243
 
244
+ /* Attachment-remove — a stamped <button-ui variant="ghost" size="xs"
245
+ icon="x"> (gh issue 276: no native <button> in stamped DOM); the
246
+ floating filled-circle look rides button token re-points. */
242
247
  [slot="preview"] [data-preview-remove] {
243
248
  position: absolute;
244
249
  top: calc(-1 * var(--chat-input-remove-offset));
245
250
  right: calc(-1 * var(--chat-input-remove-offset));
246
- width: var(--chat-input-remove-size);
247
- height: var(--chat-input-remove-size);
248
- border-radius: 50%;
249
- border: none;
250
- background: var(--chat-input-remove-bg);
251
- color: var(--chat-input-remove-fg);
252
- font-size: var(--chat-input-remove-font);
253
- line-height: 1;
254
- cursor: pointer;
255
- display: flex;
256
- align-items: center;
257
- justify-content: center;
258
- padding: 0;
251
+ --button-height: var(--chat-input-remove-size);
252
+ --button-radius: var(--a-radius-full);
253
+ --button-bg: var(--chat-input-remove-bg);
254
+ --button-fg: var(--chat-input-remove-fg);
255
+ --button-bg-ghost-hover: var(--chat-input-remove-bg);
256
+ --button-fg-ghost-hover: var(--chat-input-remove-fg);
257
+ --a-icon-size: var(--chat-input-remove-font);
259
258
  }
260
259
 
261
260
  /* Disabled / streaming — host-level chrome. `color` cascades to the
@@ -228,8 +228,12 @@ class UIChatInput extends UIElement {
228
228
  const img = document.createElement('img');
229
229
  img.src = a.dataUrl;
230
230
  img.alt = a.name || 'attachment';
231
- const btn = document.createElement('button');
232
- btn.textContent = '×';
231
+ // button-ui, not a native <button> (gh issue 276).
232
+ const btn = document.createElement('button-ui');
233
+ btn.setAttribute('variant', 'ghost');
234
+ btn.setAttribute('size', 'xs');
235
+ btn.setAttribute('icon', 'x');
236
+ btn.setAttribute('aria-label', `Remove ${a.name || 'attachment'}`);
233
237
  btn.setAttribute('data-preview-remove', '');
234
238
  btn.addEventListener('click', () => this.#removeAttachment(i));
235
239
  wrap.appendChild(img);
@@ -61,6 +61,9 @@ description: |
61
61
  autocomplete, trailing/leading controls), wrap inside
62
62
  `<chat-composer>` — see ChatComposer for the module-tier shape.
63
63
 
64
+ composes:
65
+ - button-ui # send button + attachment-remove (gh issue 276 — no native <button> in stamped DOM)
66
+
64
67
  props:
65
68
  disabled:
66
69
  description: |
@@ -123,7 +123,7 @@ check-ui[indeterminate] {
123
123
  :scope[label]::after { content: attr(label); }
124
124
 
125
125
  /* Focus */
126
- :scope:focus-visible [slot="box"] { box-shadow: var(--check-focus-ring); }
126
+ :scope:focus-visible [slot="box"] { box-shadow: var(--check-focus-ring); border-color: transparent; }
127
127
 
128
128
  /* Size handled by universal [size] attribute system. */
129
129
  }
@@ -169,11 +169,14 @@ export class UIColorInput extends UIFormElement {
169
169
  // Store the observer in a #field so disconnected() can clean it up
170
170
  // — without the explicit reference, the observer survives the host
171
171
  // element across mount/unmount cycles (audit-lifecycle-leak).
172
- this.#popoverObserver = new MutationObserver(sync);
173
- this.#popoverObserver.observe(this.#popover, {
174
- attributes: true,
175
- attributeFilter: ['open'],
176
- });
172
+ // gh#285 SSR DOM shims (linkedom) have no MutationObserver global.
173
+ if (typeof MutationObserver !== 'undefined') {
174
+ this.#popoverObserver = new MutationObserver(sync);
175
+ this.#popoverObserver.observe(this.#popover, {
176
+ attributes: true,
177
+ attributeFilter: ['open'],
178
+ });
179
+ }
177
180
  }
178
181
 
179
182
  #onPickerChange = (e) => {
@@ -362,9 +362,12 @@ export class UIColorPicker extends UIFormElement {
362
362
 
363
363
  this.#hueTrack.addEventListener('keydown', this.#onHueKeydown);
364
364
 
365
- // Observe area resize to redraw canvas
366
- this.#areaResizeObs = new ResizeObserver(() => this.#drawArea());
367
- this.#areaResizeObs.observe(this.#areaEl);
365
+ // Observe area resize to redraw canvas.
366
+ // gh#285 SSR DOM shims (linkedom) have no ResizeObserver global.
367
+ if (typeof ResizeObserver !== 'undefined') {
368
+ this.#areaResizeObs = new ResizeObserver(() => this.#drawArea());
369
+ this.#areaResizeObs.observe(this.#areaEl);
370
+ }
368
371
  }
369
372
 
370
373
  disconnected() {
@@ -150,7 +150,9 @@
150
150
  ],
151
151
  "category": "input",
152
152
  "composes": [
153
- "icon-ui"
153
+ "icon-ui",
154
+ "button-ui",
155
+ "spinner-ui"
154
156
  ],
155
157
  "events": {
156
158
  "change": {
@@ -241,9 +241,7 @@ export class UICombobox extends UIFormElement {
241
241
  aria-controls="${listboxId}"
242
242
  aria-labelledby="${labelId}"
243
243
  data-placeholder="${escapeHTML(this.placeholder || '')}"></span>
244
- <button type="button" data-clear aria-label="Clear" tabindex="-1" style="display:none">
245
- <icon-ui name="x-circle"></icon-ui>
246
- </button>
244
+ <button-ui variant="ghost" size="xs" data-clear aria-label="Clear" tabindex="-1" style="display:none"><icon-ui slot="icon" name="x-circle"></icon-ui></button-ui>
247
245
  <span data-suffix>
248
246
  <icon-ui name="caret-down"></icon-ui>
249
247
  </span>
@@ -102,10 +102,12 @@
102
102
 
103
103
  :scope:focus-within [data-field] {
104
104
  box-shadow: var(--combobox-focus-ring);
105
+ border-color: transparent;
105
106
  }
106
107
  :scope[aria-invalid="true"]:focus-within [data-field],
107
108
  :scope[error]:focus-within [data-field] {
108
109
  box-shadow: var(--combobox-focus-ring-invalid);
110
+ border-color: transparent;
109
111
  }
110
112
 
111
113
  [data-prefix],
@@ -159,20 +161,15 @@
159
161
  align-items: center;
160
162
  }
161
163
 
164
+ /* Clear x — a stamped <button-ui variant="ghost" size="xs"> kept
165
+ mouse-only via tabindex="-1" (gh issue 276: no native <button> in
166
+ stamped DOM); colors/geometry ride button token re-points. */
162
167
  [data-clear] {
163
- display: inline-flex;
164
- align-items: center;
165
- justify-content: center;
166
- padding: 0;
167
- margin: 0;
168
- border: 0;
169
- background: transparent;
170
- color: var(--combobox-fg-muted);
171
- cursor: pointer;
168
+ --button-fg: var(--combobox-fg-muted);
169
+ --button-fg-ghost-hover: var(--combobox-fg);
170
+ --button-height: calc(var(--combobox-font-size) + var(--a-space-1));
172
171
  --a-icon-size: var(--combobox-font-size);
173
- }
174
- [data-clear]:hover {
175
- color: var(--combobox-fg);
172
+ flex-shrink: 0;
176
173
  }
177
174
  }
178
175
 
@@ -20,6 +20,8 @@ description: |
20
20
  # do NOT auto-import them. Consumer (or demo shell) must explicitly import.
21
21
  composes:
22
22
  - icon-ui # caret-down / x / magnifying-glass affixes (created in render)
23
+ - button-ui # clear-x affordance, mouse-only via tabindex=-1 (gh issue 276 — no native <button> in stamped DOM)
24
+ - spinner-ui # loading row in the stamped listbox
23
25
  props:
24
26
  name:
25
27
  description: Form control name for form data submission
@@ -130,6 +130,7 @@
130
130
  :scope:focus-visible { outline: none; }
131
131
  :scope:focus-visible [slot="trigger"] {
132
132
  box-shadow: var(--date-range-picker-trigger-focus-ring);
133
+ border-color: transparent;
133
134
  }
134
135
 
135
136
  /* States */
@@ -95,13 +95,16 @@ export class UIField extends UIElement {
95
95
  this.#bindForToControl();
96
96
  this.#bindErrorListener();
97
97
  this.#labelEl?.addEventListener('click', this.#onLabelClick);
98
- this.#mo = new MutationObserver(() => {
99
- this.#bindForToControl();
100
- this.#bindErrorListener();
101
- this.#wireAriaDescribedBy();
102
- this.#syncErrorMessage();
103
- });
104
- this.#mo.observe(this, { childList: true });
98
+ // gh#285 SSR DOM shims (linkedom) have no MutationObserver global.
99
+ if (typeof MutationObserver !== 'undefined') {
100
+ this.#mo = new MutationObserver(() => {
101
+ this.#bindForToControl();
102
+ this.#bindErrorListener();
103
+ this.#wireAriaDescribedBy();
104
+ this.#syncErrorMessage();
105
+ });
106
+ this.#mo.observe(this, { childList: true });
107
+ }
105
108
  this.#syncErrorMessage();
106
109
  }
107
110