@adia-ai/web-components 0.7.2 → 0.7.4

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 (145) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/components/accordion/accordion.css +2 -2
  3. package/components/action-list/action-list.css +2 -2
  4. package/components/agent-artifact/agent-artifact.css +31 -31
  5. package/components/agent-feedback-bar/agent-feedback-bar.css +10 -10
  6. package/components/agent-questions/agent-questions.css +59 -57
  7. package/components/agent-reasoning/agent-reasoning.css +54 -54
  8. package/components/agent-suggestions/agent-suggestions.css +4 -4
  9. package/components/agent-trace/agent-trace.css +53 -53
  10. package/components/alert/alert.css +53 -53
  11. package/components/avatar/avatar.css +27 -27
  12. package/components/badge/badge.css +56 -53
  13. package/components/block/block.css +16 -16
  14. package/components/blockquote/blockquote.css +16 -16
  15. package/components/breadcrumb/breadcrumb.css +23 -23
  16. package/components/button/button.css +99 -100
  17. package/components/calendar-grid/calendar-grid.css +95 -92
  18. package/components/calendar-picker/calendar-picker.css +141 -139
  19. package/components/canvas/canvas.css +12 -12
  20. package/components/card/card.css +83 -83
  21. package/components/chart/chart.css +218 -218
  22. package/components/chart-legend/chart-legend.css +26 -26
  23. package/components/check/check.css +40 -40
  24. package/components/code/code.css +125 -125
  25. package/components/col/col.a2ui.json +1 -1
  26. package/components/col/col.css +15 -15
  27. package/components/col/col.d.ts +1 -1
  28. package/components/col/col.yaml +6 -3
  29. package/components/color-picker/color-picker.css +55 -55
  30. package/components/combobox/combobox.css +64 -62
  31. package/components/command/command.css +91 -91
  32. package/components/context-menu/context-menu.css +1 -1
  33. package/components/date-range-picker/date-range-picker.css +59 -59
  34. package/components/datetime-picker/datetime-picker.css +25 -25
  35. package/components/demo-toggle/demo-toggle.css +27 -27
  36. package/components/description-list/description-list.css +18 -18
  37. package/components/divider/divider.css +24 -24
  38. package/components/embed/embed.css +6 -6
  39. package/components/empty-state/empty-state.css +29 -29
  40. package/components/feed/feed.css +12 -12
  41. package/components/field/field.css +28 -28
  42. package/components/field/field.test.js +2 -2
  43. package/components/fields/fields.css +5 -5
  44. package/components/grid/grid.a2ui.json +11 -1
  45. package/components/grid/grid.css +26 -7
  46. package/components/grid/grid.d.ts +5 -1
  47. package/components/grid/grid.yaml +18 -3
  48. package/components/heatmap/heatmap.css +61 -61
  49. package/components/icon/icon.css +12 -12
  50. package/components/image/image.css +14 -14
  51. package/components/inline-edit/inline-edit.css +16 -16
  52. package/components/inline-message/inline-message.css +16 -16
  53. package/components/input/input.css +69 -66
  54. package/components/inspector/inspector.css +6 -6
  55. package/components/integration-card/integration-card.css +41 -41
  56. package/components/integration-card/integration-card.test.js +4 -4
  57. package/components/kbd/kbd.css +47 -40
  58. package/components/link/link.css +12 -12
  59. package/components/list/list.css +8 -8
  60. package/components/list-window/list-window.css +10 -10
  61. package/components/loading-overlay/loading-overlay.css +17 -18
  62. package/components/loading-overlay/loading-overlay.test.js +8 -8
  63. package/components/mark/mark.css +16 -16
  64. package/components/menu/menu.css +9 -9
  65. package/components/modal/modal.class.js +5 -9
  66. package/components/modal/modal.css +43 -43
  67. package/components/nav/nav.css +40 -40
  68. package/components/nav-group/nav-group.css +54 -54
  69. package/components/nav-item/nav-item.css +44 -44
  70. package/components/noodles/noodles.css +31 -31
  71. package/components/number-format/number-format.css +4 -4
  72. package/components/option-card/option-card.css +70 -70
  73. package/components/otp-input/otp-input.css +29 -29
  74. package/components/page/page.a2ui.json +1 -1
  75. package/components/page/page.css +27 -27
  76. package/components/page/page.d.ts +1 -1
  77. package/components/page/page.yaml +1 -1
  78. package/components/pagination/pagination.css +6 -6
  79. package/components/pane/pane.css +57 -57
  80. package/components/password-strength/password-strength.css +32 -32
  81. package/components/pipeline-status/pipeline-status.css +67 -67
  82. package/components/popover/popover.css +11 -11
  83. package/components/preview/preview.css +21 -21
  84. package/components/progress/progress.css +23 -23
  85. package/components/progress-row/progress-row.css +17 -17
  86. package/components/qr-code/qr-code.css +4 -4
  87. package/components/radio/radio.css +39 -39
  88. package/components/range/range.css +58 -55
  89. package/components/rating/rating.css +28 -28
  90. package/components/relative-time/relative-time.css +6 -6
  91. package/components/richtext/richtext.css +133 -133
  92. package/components/row/row.a2ui.json +1 -1
  93. package/components/row/row.css +24 -22
  94. package/components/row/row.d.ts +1 -1
  95. package/components/row/row.yaml +4 -2
  96. package/components/search/search.css +5 -5
  97. package/components/segment/segment.css +29 -24
  98. package/components/segmented/segmented.css +30 -25
  99. package/components/select/select.css +95 -93
  100. package/components/skeleton/skeleton.css +14 -14
  101. package/components/skip-nav/skip-nav.css +4 -4
  102. package/components/slider/slider.css +61 -61
  103. package/components/spinner/spinner.css +40 -40
  104. package/components/spinner/spinner.test.js +10 -12
  105. package/components/stack/stack.css +11 -11
  106. package/components/stat/stat.css +27 -27
  107. package/components/step-progress/step-progress.css +20 -20
  108. package/components/stepper/stepper.css +29 -29
  109. package/components/stream/stream.css +12 -12
  110. package/components/swatch/swatch.css +68 -68
  111. package/components/swiper/swiper.class.js +5 -12
  112. package/components/swiper/swiper.css +57 -57
  113. package/components/switch/switch.css +53 -53
  114. package/components/table/table.css +166 -163
  115. package/components/table-toolbar/table-toolbar.css +33 -33
  116. package/components/tabs/tabs.css +54 -51
  117. package/components/tag/tag.css +74 -71
  118. package/components/tag/tag.test.js +14 -14
  119. package/components/tags-input/tags-input.css +51 -49
  120. package/components/text/text.css +44 -44
  121. package/components/textarea/textarea.css +49 -46
  122. package/components/time-picker/time-picker.css +47 -47
  123. package/components/timeline/timeline.css +54 -54
  124. package/components/toast/toast.css +58 -58
  125. package/components/toc/toc.css +28 -28
  126. package/components/toggle-group/toggle-group.css +9 -6
  127. package/components/toggle-scheme/toggle-scheme.css +2 -2
  128. package/components/toolbar/toolbar.css +18 -18
  129. package/components/tooltip/tooltip.css +2 -2
  130. package/components/tour/tour.css +4 -4
  131. package/components/tree/tree.class.js +89 -16
  132. package/components/tree/tree.css +37 -37
  133. package/components/tree/tree.test.js +49 -0
  134. package/components/upload/upload.css +49 -49
  135. package/dist/host.min.css +1 -0
  136. package/dist/web-components.min.css +1 -1
  137. package/dist/web-components.min.js +3 -3
  138. package/package.json +1 -1
  139. package/styles/api/sizing.css +52 -16
  140. package/styles/foundation/space.css +25 -8
  141. package/styles/host.css +48 -0
  142. package/styles/prose.css +187 -170
  143. package/styles/type/scale.css +6 -0
  144. package/styles/typography.css +10 -5
  145. package/styles/verse.css +122 -0
@@ -10,18 +10,18 @@ toggle-option-ui:not([disabled]):hover {
10
10
  @scope (toggle-group-ui) {
11
11
  :where(:scope) {
12
12
  /* ── Tokens ── */
13
- --toggle-group-border-width-default: 1px;
14
- --toggle-group-border-color-default: var(--a-ui-border);
15
- --toggle-group-radius-default: var(--a-radius);
16
- --toggle-group-height-default: var(--a-size);
13
+ --toggle-group-border-width: 1px;
14
+ --toggle-group-border-color: var(--a-ui-border);
15
+ --toggle-group-radius: var(--a-radius);
16
+ --toggle-group-height: var(--a-size);
17
17
  text-align: start; /* §text-align-reset — blocks inheritance from centered ancestors */
18
18
  }
19
19
 
20
20
  :scope {
21
21
  box-sizing: border-box;
22
22
  display: flex;
23
- border: var(--toggle-group-border-width, var(--toggle-group-border-width-default)) solid var(--toggle-group-border-color, var(--toggle-group-border-color-default));
24
- border-radius: var(--toggle-group-radius, var(--toggle-group-radius-default));
23
+ border: var(--toggle-group-border-width) solid var(--toggle-group-border-color);
24
+ border-radius: var(--toggle-group-radius);
25
25
  overflow: hidden;
26
26
  }
27
27
 
@@ -70,6 +70,9 @@ toggle-option-ui:not([disabled]):hover {
70
70
  background: var(--toggle-option-bg);
71
71
  color: var(--toggle-option-fg);
72
72
  font: inherit;
73
+ /* font-family floor — see segment.css rationale (inline default; this
74
+ scope's tokens don't use the -default suffix). */
75
+ font-family: var(--toggle-option-font-family, var(--a-font-family-ui));
73
76
  font-size: var(--toggle-option-font-size);
74
77
  cursor: pointer;
75
78
  transition:
@@ -1,6 +1,6 @@
1
1
  @scope (toggle-scheme-ui) {
2
2
  :where(:scope) {
3
- --toggle-scheme-icon-transition-default: var(--a-duration-fast) var(--a-easing);
3
+ --toggle-scheme-icon-transition: var(--a-duration-fast) var(--a-easing);
4
4
  }
5
5
 
6
6
  :scope {
@@ -18,6 +18,6 @@
18
18
  }
19
19
 
20
20
  :scope > [part="button"] {
21
- transition: color var(--toggle-scheme-icon-transition, var(--toggle-scheme-icon-transition-default));
21
+ transition: color var(--toggle-scheme-icon-transition);
22
22
  }
23
23
  }
@@ -1,15 +1,15 @@
1
1
  @scope (toolbar-ui) {
2
2
  :where(:scope) {
3
3
  /* ── Layout ── */
4
- --toolbar-gap-default: var(--a-space-1);
5
- --toolbar-radius-default: var(--a-radius-md);
6
- --toolbar-px-default: var(--a-space-1);
7
- --toolbar-py-default: var(--a-space-1);
4
+ --toolbar-gap: var(--a-space-1);
5
+ --toolbar-radius: var(--a-radius-md);
6
+ --toolbar-px: var(--a-space-1);
7
+ --toolbar-py: var(--a-space-1);
8
8
 
9
9
  /* ── Colors ── */
10
- --toolbar-bg-default: transparent;
11
- --toolbar-fg-default: var(--a-fg);
12
- --toolbar-border-default: var(--a-border-subtle);
10
+ --toolbar-bg: transparent;
11
+ --toolbar-fg: var(--a-fg);
12
+ --toolbar-border: var(--a-border-subtle);
13
13
  }
14
14
 
15
15
  /* ── Base ── */
@@ -18,10 +18,10 @@
18
18
  display: flex;
19
19
  flex-direction: row;
20
20
  align-items: center;
21
- gap: var(--toolbar-gap, var(--toolbar-gap-default));
22
- padding: var(--toolbar-py, var(--toolbar-py-default)) var(--toolbar-px, var(--toolbar-px-default));
23
- border-radius: var(--toolbar-radius, var(--toolbar-radius-default));
24
- color: var(--toolbar-fg, var(--toolbar-fg-default));
21
+ gap: var(--toolbar-gap);
22
+ padding: var(--toolbar-py) var(--toolbar-px);
23
+ border-radius: var(--toolbar-radius);
24
+ color: var(--toolbar-fg);
25
25
  min-width: 0; /* allow shrinking within flex/grid parents */
26
26
  overflow: hidden; /* prevent pre-measurement flash before reflow */
27
27
  }
@@ -34,14 +34,14 @@
34
34
 
35
35
  /* ── Bordered ── */
36
36
  :scope[bordered] {
37
- border: 1px solid var(--toolbar-border, var(--toolbar-border-default));
38
- background: var(--toolbar-bg, var(--toolbar-bg-default));
37
+ border: 1px solid var(--toolbar-border);
38
+ background: var(--toolbar-bg);
39
39
  }
40
40
 
41
41
  /* ── Gap variants ── */
42
- :scope[gap="sm"] { --toolbar-gap-default: var(--a-space-1); }
43
- :scope[gap="md"] { --toolbar-gap-default: var(--a-space-2); }
44
- :scope[gap="lg"] { --toolbar-gap-default: var(--a-space-3); }
42
+ :scope[gap="sm"] { --toolbar-gap: var(--a-space-1); }
43
+ :scope[gap="md"] { --toolbar-gap: var(--a-space-2); }
44
+ :scope[gap="lg"] { --toolbar-gap: var(--a-space-3); }
45
45
 
46
46
  /* ── Align ── */
47
47
  :scope[align="start"] { justify-content: flex-start; }
@@ -102,9 +102,9 @@ toolbar-ui [data-toolbar-spillover-menu]:popover-open {
102
102
  background: var(--a-bg-subtle);
103
103
  box-shadow: var(--a-shadow-lg);
104
104
  min-width: 10rem;
105
- font-family: inherit;
105
+ font-family: var(--toolbar-font-family, var(--a-font-family-ui));
106
106
  font-size: var(--a-ui-size);
107
- color: var(--toolbar-fg, var(--toolbar-fg-default));
107
+ color: var(--toolbar-fg);
108
108
  /* Stack overflow items as rows (each row can be a group with its own gap). */
109
109
  display: flex;
110
110
  flex-direction: column;
@@ -3,12 +3,12 @@
3
3
  /* Host (anchor) tokens. The popover itself escapes to top-layer and
4
4
  cannot inherit these — top-layer rules below reference --a-*
5
5
  directly per documented exception. */
6
- --tooltip-host-display-default: inline-flex;
6
+ --tooltip-host-display: inline-flex;
7
7
  }
8
8
  :scope {
9
9
  /* ── Base ── */
10
10
  box-sizing: border-box;
11
- display: var(--tooltip-host-display, var(--tooltip-host-display-default));
11
+ display: var(--tooltip-host-display);
12
12
  position: relative;
13
13
  }
14
14
  }
@@ -1,8 +1,8 @@
1
1
  @scope (tour-ui) {
2
2
  :where(:scope) {
3
- --tour-scrim-default: var(--a-scrim-dialog);
4
- --tour-spotlight-padding-default: var(--a-space-2);
5
- --tour-spotlight-radius-default: var(--a-radius-md);
3
+ --tour-scrim: var(--a-scrim-dialog);
4
+ --tour-spotlight-padding: var(--a-space-2);
5
+ --tour-spotlight-radius: var(--a-radius-md);
6
6
  }
7
7
 
8
8
  :scope {
@@ -67,7 +67,7 @@
67
67
  box-shadow: var(--tour-popover-shadow, var(--a-shadow-lg));
68
68
  min-width: var(--tour-popover-min-width, 18rem);
69
69
  max-width: var(--tour-popover-max-width, 22rem);
70
- font-family: inherit;
70
+ font-family: var(--tour-font-family, var(--a-font-family-ui));
71
71
  font-size: var(--a-ui-size);
72
72
  color: var(--a-fg);
73
73
  /* Above the spotlight (1000) so the user can read + click it */
@@ -266,6 +266,11 @@ export class UITreeItem extends UIElement {
266
266
 
267
267
  static template = () => null;
268
268
 
269
+ // FB-96: late-adoption of interpolated [slot="actions"]/[slot="caret"]
270
+ // children — see connected() + #adoptSlotted().
271
+ #slotObserver = null;
272
+ #adopting = false;
273
+
269
274
  get hasChildren() {
270
275
  return this.querySelector(':scope > tree-item-ui') !== null;
271
276
  }
@@ -303,6 +308,17 @@ export class UITreeItem extends UIElement {
303
308
  row.setAttribute('role', 'treeitem');
304
309
  if (!row.hasAttribute('tabindex')) row.setAttribute('tabindex', '0');
305
310
  }
311
+
312
+ // FB-96: declarative [slot="actions"]/[slot="caret"] children rendered via
313
+ // the template engine (`.map()`/`repeat()`/conditional) arrive in a LATER
314
+ // reactive tick — AFTER #stamp() — nested in the engine's display:contents
315
+ // wrapper spans. So neither a connect-time read nor a `:scope >` direct-child
316
+ // query sees them, and e.g. a `<button-ui slot="actions">` is left as a bare
317
+ // role=button child of the group/tree (axe `aria-required-children`). Adopt
318
+ // what's present now, then observe for the late arrivals.
319
+ this.#adoptSlotted();
320
+ this.#slotObserver = new MutationObserver(() => this.#adoptSlotted());
321
+ this.#slotObserver.observe(this, { childList: true, subtree: true });
306
322
  }
307
323
 
308
324
  #stamp() {
@@ -310,15 +326,18 @@ export class UITreeItem extends UIElement {
310
326
  row.setAttribute('slot', 'row');
311
327
  row.setAttribute('tabindex', '0');
312
328
 
313
- // Caret — adopt a declarative [slot="caret"] child if the consumer supplied
314
- // one, else stamp the default. (Adopt-or-stamp; same pattern as actions.)
315
- const declaredCaret = this.querySelector(':scope > [slot="caret"]');
329
+ // Caret — adopt a declarative [slot="caret"] child if one is already present
330
+ // (incl. inside the engine's display:contents wrappers, FB-96), else stamp
331
+ // the default, marked so #adoptSlotted() can drop it if a declared caret
332
+ // arrives late.
333
+ const declaredCaret = this.#logicalSlotChildren('caret')[0];
316
334
  if (declaredCaret) {
317
335
  row.appendChild(declaredCaret);
318
336
  } else {
319
337
  const caret = document.createElement('icon-ui');
320
338
  caret.setAttribute('slot', 'caret');
321
339
  caret.setAttribute('name', 'caret-right');
340
+ caret.dataset.treeCaretDefault = '1';
322
341
  row.appendChild(caret);
323
342
  }
324
343
 
@@ -343,19 +362,16 @@ export class UITreeItem extends UIElement {
343
362
  if (this.badge) badgeEl.textContent = this.badge;
344
363
  row.appendChild(badgeEl);
345
364
 
346
- // Actions — adopt pre-existing declarative [slot="actions"] children into
347
- // the row (FEEDBACK-89) so per-row action buttons land in the styled,
348
- // hover-revealed actions area; else stamp an empty placeholder. The host
349
- // #onClick already excludes [slot="actions"] * from row selection, so
350
- // adoption is click-safe.
351
- const declaredActions = this.querySelectorAll(':scope > [slot="actions"]');
352
- if (declaredActions.length) {
353
- for (const a of declaredActions) row.appendChild(a);
354
- } else {
355
- const actions = document.createElement('span');
356
- actions.setAttribute('slot', 'actions');
357
- row.appendChild(actions);
358
- }
365
+ // Actions — stamp an empty placeholder; the real declarative [slot="actions"]
366
+ // children (FEEDBACK-89) are moved into the row by #adoptSlotted(), which
367
+ // runs in connected() AND on later mutations, so interpolated buttons that
368
+ // arrive after #stamp (nested in the engine's display:contents wrappers,
369
+ // FB-96) get adopted too. The placeholder is dropped once a real one lands.
370
+ // (#onClick excludes [slot="actions"] * from row selection — adoption-safe.)
371
+ const actions = document.createElement('span');
372
+ actions.setAttribute('slot', 'actions');
373
+ actions.dataset.treeActionsPlaceholder = '1';
374
+ row.appendChild(actions);
359
375
 
360
376
  this.prepend(row);
361
377
  }
@@ -385,4 +401,61 @@ export class UITreeItem extends UIElement {
385
401
  const badgeEl = row.querySelector('[slot="badge"]');
386
402
  if (badgeEl) badgeEl.textContent = this.badge || '';
387
403
  }
404
+
405
+ // ── FB-96: wrapper-piercing, late adoption of slotted row children ──
406
+
407
+ /**
408
+ * Collect this item's logical `[slot="<slot>"]` children — direct, or nested
409
+ * inside the template engine's `display:contents` wrapper spans (which carry
410
+ * `role="presentation"` since 0.7.2) — but NOT inside a nested `<tree-item-ui>`
411
+ * (a plain descendant query would wrongly grab a child item's actions/caret).
412
+ */
413
+ #logicalSlotChildren(slot) {
414
+ const out = [];
415
+ const walk = (parent) => {
416
+ for (const ch of parent.children) {
417
+ if (ch.matches('tree-item-ui')) continue; // nested-item boundary
418
+ if (ch.getAttribute('slot') === slot) { out.push(ch); continue; }
419
+ // pierce the engine's transparent wrapper spans
420
+ if (ch.matches('[role="presentation"]') || ch.style?.display === 'contents') walk(ch);
421
+ }
422
+ };
423
+ walk(this);
424
+ return out;
425
+ }
426
+
427
+ /**
428
+ * Move declarative `[slot="actions"]`/`[slot="caret"]` children into the
429
+ * stamped row. Idempotent + re-entrancy-guarded (it mutates `this`, which
430
+ * re-fires #slotObserver). Drops the auto-stamped placeholder/default once a
431
+ * real child is adopted. (FEEDBACK-89 + FB-96.)
432
+ */
433
+ #adoptSlotted() {
434
+ if (this.#adopting) return;
435
+ const row = this.querySelector(':scope > [slot="row"]');
436
+ if (!row) return;
437
+ this.#adopting = true;
438
+ try {
439
+ const actions = this.#logicalSlotChildren('actions')
440
+ .filter((el) => !el.dataset.treeActionsPlaceholder && el.parentElement !== row);
441
+ if (actions.length) {
442
+ row.querySelector(':scope > [slot="actions"][data-tree-actions-placeholder]')?.remove();
443
+ for (const el of actions) row.appendChild(el);
444
+ }
445
+ const caret = this.#logicalSlotChildren('caret')
446
+ .find((el) => !el.dataset.treeCaretDefault && el.parentElement !== row);
447
+ if (caret) {
448
+ row.querySelector(':scope > [slot="caret"][data-tree-caret-default]')?.remove();
449
+ row.prepend(caret);
450
+ }
451
+ } finally {
452
+ this.#slotObserver?.takeRecords(); // drain self-mutations — no re-entrant pass
453
+ this.#adopting = false;
454
+ }
455
+ }
456
+
457
+ disconnected() {
458
+ this.#slotObserver?.disconnect();
459
+ this.#slotObserver = null;
460
+ }
388
461
  }
@@ -1,30 +1,30 @@
1
1
  @scope (tree-ui) {
2
2
  :where(:scope) {
3
3
  /* ── Layout ── */
4
- --tree-row-height-default: var(--a-size);
5
- --tree-row-radius-default: var(--a-radius-sm);
6
- --tree-row-px-default: var(--a-space-1);
7
- --tree-row-gap-default: var(--a-space-1);
8
- --tree-actions-gap-default: var(--a-space-0-5);
9
- --tree-indent-default: var(--a-space-4);
10
- --tree-caret-size-default: var(--a-space-2);
11
- --tree-icon-size-default: var(--a-space-3);
4
+ --tree-row-height: var(--a-size);
5
+ --tree-row-radius: var(--a-radius-sm);
6
+ --tree-row-px: var(--a-space-1);
7
+ --tree-row-gap: var(--a-space-1);
8
+ --tree-actions-gap: var(--a-space-0-5);
9
+ --tree-indent: var(--a-space-4);
10
+ --tree-caret-size: var(--a-space-2);
11
+ --tree-icon-size: var(--a-space-3);
12
12
 
13
13
  /* ── Typography ── */
14
- --tree-font-size-default: var(--a-ui-size);
14
+ --tree-font-size: var(--a-ui-size);
15
15
 
16
16
  /* ── Colors ── */
17
- --tree-fg-default: var(--a-fg);
18
- --tree-fg-muted-default: var(--a-fg-muted);
19
- --tree-bg-hover-default: var(--a-bg-muted);
20
- --tree-bg-selected-default: var(--a-bg-hover);
17
+ --tree-fg: var(--a-fg);
18
+ --tree-fg-muted: var(--a-fg-muted);
19
+ --tree-bg-hover: var(--a-bg-muted);
20
+ --tree-bg-selected: var(--a-bg-hover);
21
21
 
22
22
  /* ── Transition ── */
23
- --tree-duration-default: var(--a-duration-fast);
24
- --tree-easing-default: var(--a-easing);
23
+ --tree-duration: var(--a-duration-fast);
24
+ --tree-easing: var(--a-easing);
25
25
 
26
26
  /* ── State ── */
27
- --tree-focus-ring-default: var(--a-focus-ring);
27
+ --tree-focus-ring: var(--a-focus-ring);
28
28
  }
29
29
 
30
30
  :scope {
@@ -32,8 +32,8 @@
32
32
  box-sizing: border-box;
33
33
  display: flex;
34
34
  flex-direction: column;
35
- font-size: var(--tree-font-size, var(--tree-font-size-default));
36
- color: var(--tree-fg, var(--tree-fg-default));
35
+ font-size: var(--tree-font-size);
36
+ color: var(--tree-fg);
37
37
  }
38
38
  }
39
39
 
@@ -69,35 +69,35 @@
69
69
  [slot="row"] {
70
70
  display: flex;
71
71
  align-items: center;
72
- gap: var(--tree-row-gap, var(--tree-row-gap-default));
73
- height: var(--tree-row-height, var(--tree-row-height-default));
74
- padding-inline-end: var(--tree-row-px, var(--tree-row-px-default));
75
- border-radius: var(--tree-row-radius, var(--tree-row-radius-default));
72
+ gap: var(--tree-row-gap);
73
+ height: var(--tree-row-height);
74
+ padding-inline-end: var(--tree-row-px);
75
+ border-radius: var(--tree-row-radius);
76
76
  cursor: pointer;
77
77
  user-select: none;
78
78
  white-space: nowrap;
79
79
  transition:
80
- background var(--tree-duration, var(--tree-duration-default)) var(--tree-easing, var(--tree-easing-default)),
81
- color var(--tree-duration, var(--tree-duration-default)) var(--tree-easing, var(--tree-easing-default));
80
+ background var(--tree-duration) var(--tree-easing),
81
+ color var(--tree-duration) var(--tree-easing);
82
82
  outline: none;
83
83
  }
84
84
 
85
85
  [slot="row"]:hover {
86
- background: var(--tree-bg-hover, var(--tree-bg-hover-default));
86
+ background: var(--tree-bg-hover);
87
87
  }
88
88
 
89
89
  [slot="row"]:focus-visible {
90
- box-shadow: var(--tree-focus-ring, var(--tree-focus-ring-default));
90
+ box-shadow: var(--tree-focus-ring);
91
91
  }
92
92
 
93
93
  /* :scope[selected] rules moved outside @scope — see tree-ui Safari note at end of file. */
94
94
 
95
95
  /* ── Caret ── */
96
96
  [slot="caret"] {
97
- --a-icon-size: var(--tree-caret-size, var(--tree-caret-size-default));
97
+ --a-icon-size: var(--tree-caret-size);
98
98
  flex-shrink: 0;
99
- color: var(--tree-fg-muted, var(--tree-fg-muted-default));
100
- transition: transform var(--tree-duration, var(--tree-duration-default)) var(--tree-easing, var(--tree-easing-default));
99
+ color: var(--tree-fg-muted);
100
+ transition: transform var(--tree-duration) var(--tree-easing);
101
101
  transform: rotate(90deg);
102
102
  }
103
103
 
@@ -112,8 +112,8 @@
112
112
 
113
113
  /* ── Icon ── */
114
114
  [slot="icon"] {
115
- --a-icon-size: var(--tree-icon-size, var(--tree-icon-size-default));
116
- color: var(--tree-fg-muted, var(--tree-fg-muted-default));
115
+ --a-icon-size: var(--tree-icon-size);
116
+ color: var(--tree-fg-muted);
117
117
  flex-shrink: 0;
118
118
  }
119
119
 
@@ -134,7 +134,7 @@
134
134
  [slot="badge"] {
135
135
  flex-shrink: 0;
136
136
  font-size: var(--tree-badge-size, var(--a-ui-xs));
137
- color: var(--tree-badge-fg, var(--tree-fg-muted, var(--tree-fg-muted-default)));
137
+ color: var(--tree-badge-fg, var(--tree-fg-muted));
138
138
  background: var(--tree-badge-bg, transparent);
139
139
  padding: 0 var(--tree-badge-px, var(--a-space-1));
140
140
  border-radius: var(--tree-badge-radius, var(--a-radius-sm));
@@ -149,10 +149,10 @@
149
149
  [slot="actions"] {
150
150
  display: flex;
151
151
  align-items: center;
152
- gap: var(--tree-actions-gap, var(--tree-actions-gap-default));
152
+ gap: var(--tree-actions-gap);
153
153
  margin-inline-start: auto;
154
154
  opacity: 0;
155
- transition: opacity var(--tree-duration, var(--tree-duration-default)) var(--tree-easing, var(--tree-easing-default));
155
+ transition: opacity var(--tree-duration) var(--tree-easing);
156
156
  }
157
157
 
158
158
  [slot="row"]:hover [slot="actions"] {
@@ -173,11 +173,11 @@
173
173
  `:where(:scope)` still resolve via inheritance. See
174
174
  docs/BROWSER-COMPAT.md §3a. */
175
175
  tree-ui[selected] > [slot="row"] {
176
- background: var(--tree-bg-selected, var(--tree-bg-selected-default));
177
- color: var(--tree-fg, var(--tree-fg-default));
176
+ background: var(--tree-bg-selected);
177
+ color: var(--tree-fg);
178
178
  }
179
179
  tree-ui[selected] > [slot="row"] [slot="icon"] {
180
- color: var(--tree-fg, var(--tree-fg-default));
180
+ color: var(--tree-fg);
181
181
  }
182
182
  tree-ui[selected] > [slot="row"] [slot="actions"] {
183
183
  opacity: 1;
@@ -11,6 +11,7 @@ import { html, stamp } from '../../core/template.js';
11
11
 
12
12
  beforeAll(async () => {
13
13
  await import('./tree.js');
14
+ await import('../button/button.js');
14
15
  });
15
16
 
16
17
  describe('<tree-ui> tree-select forwards modifier keys (FB-46)', () => {
@@ -261,3 +262,51 @@ describe('<tree-item-ui> ARIA tree containment (FEEDBACK-91)', () => {
261
262
  expect(parent.querySelector(':scope > [slot="row"]').hasAttribute('aria-selected')).toBe(false);
262
263
  });
263
264
  });
265
+
266
+ describe('<tree-item-ui> adopts interpolated [slot="actions"] into the row (FEEDBACK-96)', () => {
267
+ let host;
268
+ const settle = () => new Promise((r) => setTimeout(r, 40));
269
+ beforeEach(() => { host = document.createElement('div'); document.body.appendChild(host); });
270
+ afterEach(() => host.remove());
271
+
272
+ it('moves a `.map()`-rendered actions button into the treeitem row (not a stray child of the group)', async () => {
273
+ const PALS = [{ id: 'n', name: 'Neutral' }, { id: 'b', name: 'Brand' }];
274
+ stamp(html`
275
+ <tree-ui>
276
+ <tree-item-ui text="Colors" value="colors" open>
277
+ ${html`<button-ui slot="actions" icon="plus" title="Add"></button-ui>`}
278
+ ${PALS.map((p) => html`<tree-item-ui text="${p.name}" value="${p.id}"></tree-item-ui>`)}
279
+ </tree-item-ui>
280
+ </tree-ui>
281
+ `, host);
282
+ await settle();
283
+
284
+ const colors = host.querySelector('tree-item-ui[value="colors"]');
285
+ const row = colors.querySelector(':scope > [slot="row"]');
286
+ const btn = colors.querySelector('button-ui[slot="actions"]');
287
+ expect(btn).not.toBeNull();
288
+ // The fix: the interpolated button (wrapped in a display:contents span) is
289
+ // adopted INTO the row. Pre-fix, #stamp's `:scope >` query missed it (the
290
+ // button arrives after #stamp, inside a wrapper span) and it stayed a bare
291
+ // child of the group/tree → axe aria-required-children.
292
+ expect(btn.parentElement).toBe(row);
293
+ // Auto-stamped empty placeholder dropped — exactly one actions slot in the row.
294
+ expect(row.querySelectorAll(':scope > [slot="actions"]').length).toBe(1);
295
+ // Nested items NOT mis-adopted as actions (the tree-item-ui boundary holds).
296
+ expect(host.querySelectorAll('tree-item-ui').length).toBe(3);
297
+ });
298
+
299
+ it('is idempotent — no double-adopt across settles', async () => {
300
+ stamp(html`
301
+ <tree-ui>
302
+ <tree-item-ui text="Colors" value="colors" open>
303
+ ${html`<button-ui slot="actions" icon="plus"></button-ui>`}
304
+ </tree-item-ui>
305
+ </tree-ui>
306
+ `, host);
307
+ await settle();
308
+ await settle();
309
+ const row = host.querySelector('tree-item-ui[value="colors"] > [slot="row"]');
310
+ expect(row.querySelectorAll(':scope > [slot="actions"]').length).toBe(1);
311
+ });
312
+ });