@adia-ai/web-components 0.6.49 → 0.6.50

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog — @adia-ai/web-components
2
2
 
3
+ ## [0.6.50] — 2026-05-30
4
+
5
+ ### Changed — `slot="caret"` disclosure-indicator convention + FEEDBACK-89 (ADR-0036)
6
+
7
+ - **`components/tree/` (`tree-item-ui`) + `components/pane/` (`pane-ui`)** — renamed the expand/collapse indicator slot `slot="chevron"` → **`slot="caret"`** (and tokens `--*-chevron-*` → `--*-caret-*`, e.g. `--tree-caret-size`, `--pane-caret-fg`), standardizing on the slot name already used by `accordion-item-ui` / `nav-group-ui` / `select-ui` and matching the Phosphor `caret-*` icon vocabulary. **No backward-compat alias** (hard rename; the caret is auto-stamped, so blast radius is small). See [ADR-0036](../../.brain/adrs/0036-caret-slot-disclosure-convention.md).
8
+ - **`tree-item-ui` adopt-or-stamp (FEEDBACK-89)** — `#stamp()` now adopts a consumer's declarative `slot="caret"` and `slot="actions"` children into the auto-stamped row instead of leaving them as siblings below it. A `<button-ui slot="actions">` (the natural way to add a per-row action — add / rename / overflow) now lands inline in the styled, hover-revealed actions area. The host `#onClick` already excludes `[slot="actions"] *` from row selection, so adoption is click-safe. +3 unit tests.
9
+ - **`dist/` CDN bundles** (`web-components.min.css` + `web-components.min.js`) regenerated to carry the renamed `--*-caret-*` tokens + caret stamp logic.
10
+
3
11
  ## [0.6.49] — 2026-05-30
4
12
 
5
13
  ### Changed — card-ui header slot sweep + `nav-group-ui` fixes (demos)
@@ -379,7 +379,7 @@ export class UIAgentReasoning extends UIElement {
379
379
  <span data-reasoning-meta>
380
380
  ${total ? `<span data-reasoning-counter>${done}/${total}</span>` : ''}
381
381
  <span data-reasoning-time>${elapsed}s</span>
382
- <icon-ui name="${this.collapsed ? 'caret-right' : 'caret-up'}" color="muted" data-reasoning-chevron></icon-ui>
382
+ <icon-ui name="${this.collapsed ? 'caret-right' : 'caret-up'}" color="muted" data-reasoning-caret></icon-ui>
383
383
  </span>
384
384
  `;
385
385
 
@@ -27,7 +27,7 @@
27
27
  --agent-trace-detail-label-col-default: 9rem;
28
28
 
29
29
  /* ── Motion ── */
30
- --agent-trace-chevron-dur-default: var(--a-duration-fast);
30
+ --agent-trace-caret-dur-default: var(--a-duration-fast);
31
31
  }
32
32
 
33
33
  :scope {
@@ -50,7 +50,7 @@
50
50
  border-radius: var(--a-radius-sm);
51
51
  padding: var(--a-space-0-5) var(--a-space-1);
52
52
  margin: calc(var(--a-space-0-5) * -1) calc(var(--a-space-1) * -1);
53
- transition: background var(--agent-trace-chevron-dur, var(--agent-trace-chevron-dur-default));
53
+ transition: background var(--agent-trace-caret-dur, var(--agent-trace-caret-dur-default));
54
54
  }
55
55
 
56
56
  [data-trace-root] > summary:hover {
@@ -94,12 +94,12 @@
94
94
  margin-inline: 2px;
95
95
  }
96
96
 
97
- [data-trace-chevron] {
97
+ [data-trace-caret] {
98
98
  flex-shrink: 0;
99
- transition: transform var(--agent-trace-chevron-dur, var(--agent-trace-chevron-dur-default));
99
+ transition: transform var(--agent-trace-caret-dur, var(--agent-trace-caret-dur-default));
100
100
  }
101
101
 
102
- [data-trace-root][open] [data-trace-chevron] {
102
+ [data-trace-root][open] [data-trace-caret] {
103
103
  transform: rotate(90deg);
104
104
  }
105
105
 
@@ -225,20 +225,20 @@
225
225
  text-align: end;
226
226
  }
227
227
 
228
- /* Chevron in the trailing column */
229
- [data-trace-row-chevron] {
228
+ /* Caret in the trailing column */
229
+ [data-trace-row-caret] {
230
230
  --a-icon-size: 0.875em;
231
231
  justify-self: end;
232
232
  align-self: center;
233
- transition: transform var(--a-duration-fast) var(--agent-trace-chevron-dur, var(--a-duration-fast));
233
+ transition: transform var(--a-duration-fast) var(--agent-trace-caret-dur, var(--a-duration-fast));
234
234
  color: var(--agent-trace-fg-muted, var(--agent-trace-fg-muted-default));
235
235
  }
236
236
 
237
- details[data-trace-row][open] > summary > [data-trace-row-chevron] {
237
+ details[data-trace-row][open] > summary > [data-trace-row-caret] {
238
238
  transform: rotate(90deg);
239
239
  }
240
240
 
241
- [data-trace-row-chevron-spacer] {
241
+ [data-trace-row-caret-spacer] {
242
242
  width: 0.875em;
243
243
  justify-self: end;
244
244
  }
@@ -128,7 +128,7 @@ class UIAgentTrace extends UIElement {
128
128
  this.#rootEl.innerHTML = `
129
129
  <summary>
130
130
  <span data-trace-status>${pillsHTML}</span>
131
- <icon-ui name="caret-right" color="muted" size="sm" data-trace-chevron></icon-ui>
131
+ <icon-ui name="caret-right" color="muted" size="sm" data-trace-caret></icon-ui>
132
132
  </summary>
133
133
  <div data-trace-body>
134
134
  ${this.#metrics.length ? `<div data-trace-rows${hasAnyDetails ? ' data-has-details' : ''}>${headerRow}${rowsHTML}</div>` : ''}
@@ -156,8 +156,8 @@ class UIAgentTrace extends UIElement {
156
156
  <span data-trace-aux>${aux}</span>
157
157
  ${hasAnyDetails
158
158
  ? (hasDetails
159
- ? '<icon-ui name="caret-right" color="muted" data-trace-row-chevron></icon-ui>'
160
- : '<span data-trace-row-chevron-spacer aria-hidden="true"></span>')
159
+ ? '<icon-ui name="caret-right" color="muted" data-trace-row-caret></icon-ui>'
160
+ : '<span data-trace-row-caret-spacer aria-hidden="true"></span>')
161
161
  : ''}
162
162
  `;
163
163
 
@@ -82,6 +82,9 @@
82
82
  "text-area"
83
83
  ],
84
84
  "slots": {
85
+ "caret": {
86
+ "description": "Collapse caret inside the header. Auto-stamped as `<icon-ui slot=\"caret\" name=\"caret-right\">` (rotates on `[collapsed]`). Supply your own `slot=\"caret\"` child in the header to customize — adopt-or-stamp honors a declarative caret instead of stamping."
87
+ },
85
88
  "header": {
86
89
  "description": "Auto-created header element with label text and toggle arrow"
87
90
  }
@@ -100,12 +100,13 @@ export class UIPane extends UIElement {
100
100
  header.setAttribute('tabindex', '0');
101
101
  header.setAttribute('aria-expanded', String(!this.collapsed));
102
102
 
103
- // Stamp chevron icon if not present
104
- if (!header.querySelector('[slot="chevron"]')) {
105
- const chevron = document.createElement('icon-ui');
106
- chevron.setAttribute('slot', 'chevron');
107
- chevron.setAttribute('name', 'caret-right');
108
- header.append(chevron);
103
+ // Stamp the caret icon if not present (adopt-or-stamp: a declarative
104
+ // [slot="caret"] child is honored, else we stamp the default).
105
+ if (!header.querySelector('[slot="caret"]')) {
106
+ const caret = document.createElement('icon-ui');
107
+ caret.setAttribute('slot', 'caret');
108
+ caret.setAttribute('name', 'caret-right');
109
+ header.append(caret);
109
110
  }
110
111
  }
111
112
  }
@@ -29,7 +29,7 @@
29
29
 
30
30
  /* ── Header interaction ── */
31
31
  --pane-header-bg-hover-default: var(--a-bg-subtle);
32
- --pane-chevron-fg-default: var(--a-fg-muted);
32
+ --pane-caret-fg-default: var(--a-fg-muted);
33
33
 
34
34
  /* ── Section header ── */
35
35
  --pane-section-header-weight-default: var(--a-weight-medium);
@@ -107,17 +107,17 @@
107
107
  box-shadow: var(--a-focus-ring) inset;
108
108
  }
109
109
 
110
- /* Collapse indicator — stamped by JS as icon-ui */
111
- & > header > [slot="chevron"] {
110
+ /* Collapse indicator (caret) — stamped by JS as icon-ui */
111
+ & > header > [slot="caret"] {
112
112
  --a-icon-size: var(--a-caret-size);
113
113
  flex-shrink: 0;
114
114
  margin-inline-start: auto;
115
- color: var(--pane-chevron-fg, var(--pane-chevron-fg-default));
115
+ color: var(--pane-caret-fg, var(--pane-caret-fg-default));
116
116
  transition: transform var(--pane-duration, var(--pane-duration-default)) var(--pane-easing, var(--pane-easing-default));
117
117
  transform: rotate(90deg);
118
118
  }
119
119
 
120
- :scope[collapsed] > header > [slot="chevron"] {
120
+ :scope[collapsed] > header > [slot="caret"] {
121
121
  transform: rotate(0deg);
122
122
  }
123
123
 
@@ -62,6 +62,12 @@ events:
62
62
  slots:
63
63
  header:
64
64
  description: Auto-created header element with label text and toggle arrow
65
+ caret:
66
+ description: >-
67
+ Collapse caret inside the header. Auto-stamped as
68
+ `<icon-ui slot="caret" name="caret-right">` (rotates on `[collapsed]`).
69
+ Supply your own `slot="caret"` child in the header to customize —
70
+ adopt-or-stamp honors a declarative caret instead of stamping.
65
71
  states:
66
72
  - name: idle
67
73
  description: Default, ready for interaction.
@@ -65,6 +65,12 @@
65
65
  "Accordion"
66
66
  ],
67
67
  "slots": {
68
+ "actions": {
69
+ "description": "Per-row action buttons (rename / add / overflow menu), right-aligned and hover-revealed. A declarative `slot=\"actions\"` child is adopted into the auto-stamped row (FEEDBACK-89), so it sits inline in the row rather than wrapping below it."
70
+ },
71
+ "caret": {
72
+ "description": "Override slot for the expand/collapse caret. Auto-stamped as `<icon-ui slot=\"caret\" name=\"caret-right\">` (rotates on `[open]`, hidden for leaf nodes). Supply your own `slot=\"caret\"` child to customize — adopt-or-stamp moves a declarative caret into the row."
73
+ },
68
74
  "icon": {
69
75
  "description": "Override the leading [icon] glyph with a custom slotted element (custom icon-ui, folder open/closed glyph, file-type marker). Mutually exclusive with the [icon] attribute — slot child wins."
70
76
  }
@@ -47,6 +47,18 @@ slots:
47
47
  Override the leading [icon] glyph with a custom slotted element (custom
48
48
  icon-ui, folder open/closed glyph, file-type marker). Mutually exclusive
49
49
  with the [icon] attribute — slot child wins.
50
+ caret:
51
+ description: >-
52
+ Override slot for the expand/collapse caret. Auto-stamped as
53
+ `<icon-ui slot="caret" name="caret-right">` (rotates on `[open]`,
54
+ hidden for leaf nodes). Supply your own `slot="caret"` child to
55
+ customize — adopt-or-stamp moves a declarative caret into the row.
56
+ actions:
57
+ description: >-
58
+ Per-row action buttons (rename / add / overflow menu), right-aligned
59
+ and hover-revealed. A declarative `slot="actions"` child is adopted
60
+ into the auto-stamped row (FEEDBACK-89), so it sits inline in the row
61
+ rather than wrapping below it.
50
62
 
51
63
  a2ui:
52
64
  rules:
@@ -72,7 +84,7 @@ a2ui:
72
84
  - >-
73
85
  Nest further <tree-item-ui> in the default slot only — no
74
86
  <list-item-ui>, <nav-item-ui>, or arbitrary content inside a
75
- tree row. The chevron is auto-stamped when the row has nested
87
+ tree row. The caret is auto-stamped when the row has nested
76
88
  tree-item-ui children.
77
89
 
78
90
  keywords:
@@ -126,8 +126,8 @@
126
126
  "--tree-bg-selected": {
127
127
  "description": "Background color when selected"
128
128
  },
129
- "--tree-chevron-size": {
130
- "description": "Size of the collapse chevron icon"
129
+ "--tree-caret-size": {
130
+ "description": "Size of the collapse caret icon"
131
131
  },
132
132
  "--tree-duration": {
133
133
  "description": "Transition duration"
@@ -139,7 +139,7 @@
139
139
  "description": "Primary text color"
140
140
  },
141
141
  "--tree-fg-muted": {
142
- "description": "Muted text color (icons, chevrons)"
142
+ "description": "Muted text color (icons, carets)"
143
143
  },
144
144
  "--tree-focus-ring": {
145
145
  "description": "Focus ring box-shadow"
@@ -293,11 +293,17 @@ export class UITreeItem extends UIElement {
293
293
  row.setAttribute('slot', 'row');
294
294
  row.setAttribute('tabindex', '0');
295
295
 
296
- // Chevron
297
- const chevron = document.createElement('icon-ui');
298
- chevron.setAttribute('slot', 'chevron');
299
- chevron.setAttribute('name', 'caret-right');
300
- row.appendChild(chevron);
296
+ // Caret — adopt a declarative [slot="caret"] child if the consumer supplied
297
+ // one, else stamp the default. (Adopt-or-stamp; same pattern as actions.)
298
+ const declaredCaret = this.querySelector(':scope > [slot="caret"]');
299
+ if (declaredCaret) {
300
+ row.appendChild(declaredCaret);
301
+ } else {
302
+ const caret = document.createElement('icon-ui');
303
+ caret.setAttribute('slot', 'caret');
304
+ caret.setAttribute('name', 'caret-right');
305
+ row.appendChild(caret);
306
+ }
301
307
 
302
308
  // Icon
303
309
  if (this.icon) {
@@ -320,10 +326,19 @@ export class UITreeItem extends UIElement {
320
326
  if (this.badge) badgeEl.textContent = this.badge;
321
327
  row.appendChild(badgeEl);
322
328
 
323
- // Actions slot placeholder
324
- const actions = document.createElement('span');
325
- actions.setAttribute('slot', 'actions');
326
- row.appendChild(actions);
329
+ // Actions — adopt pre-existing declarative [slot="actions"] children into
330
+ // the row (FEEDBACK-89) so per-row action buttons land in the styled,
331
+ // hover-revealed actions area; else stamp an empty placeholder. The host
332
+ // #onClick already excludes [slot="actions"] * from row selection, so
333
+ // adoption is click-safe.
334
+ const declaredActions = this.querySelectorAll(':scope > [slot="actions"]');
335
+ if (declaredActions.length) {
336
+ for (const a of declaredActions) row.appendChild(a);
337
+ } else {
338
+ const actions = document.createElement('span');
339
+ actions.setAttribute('slot', 'actions');
340
+ row.appendChild(actions);
341
+ }
327
342
 
328
343
  this.prepend(row);
329
344
  }
@@ -7,7 +7,7 @@
7
7
  --tree-row-gap-default: var(--a-space-1);
8
8
  --tree-actions-gap-default: var(--a-space-0-5);
9
9
  --tree-indent-default: var(--a-space-4);
10
- --tree-chevron-size-default: var(--a-space-2);
10
+ --tree-caret-size-default: var(--a-space-2);
11
11
  --tree-icon-size-default: var(--a-space-3);
12
12
 
13
13
  /* ── Typography ── */
@@ -47,7 +47,7 @@
47
47
  --tree-item-row-gap: var(--tree-row-gap, var(--a-space-1));
48
48
  --tree-item-actions-gap: var(--tree-actions-gap, var(--a-space-0-5));
49
49
  --tree-item-indent: var(--tree-indent, var(--a-space-4));
50
- --tree-item-chevron-size: var(--tree-chevron-size, var(--a-space-2));
50
+ --tree-item-caret-size: var(--tree-caret-size, var(--a-space-2));
51
51
  --tree-item-icon-size: var(--tree-icon-size, var(--a-space-3));
52
52
 
53
53
  --tree-item-fg: var(--tree-fg, var(--a-fg));
@@ -92,21 +92,21 @@
92
92
 
93
93
  /* :scope[selected] rules moved outside @scope — see tree-ui Safari note at end of file. */
94
94
 
95
- /* ── Chevron ── */
96
- [slot="chevron"] {
97
- --a-icon-size: var(--tree-chevron-size, var(--tree-chevron-size-default));
95
+ /* ── Caret ── */
96
+ [slot="caret"] {
97
+ --a-icon-size: var(--tree-caret-size, var(--tree-caret-size-default));
98
98
  flex-shrink: 0;
99
99
  color: var(--tree-fg-muted, var(--tree-fg-muted-default));
100
100
  transition: transform var(--tree-duration, var(--tree-duration-default)) var(--tree-easing, var(--tree-easing-default));
101
101
  transform: rotate(90deg);
102
102
  }
103
103
 
104
- :scope:not([open]) > [slot="row"] > [slot="chevron"] {
104
+ :scope:not([open]) > [slot="row"] > [slot="caret"] {
105
105
  transform: rotate(0deg);
106
106
  }
107
107
 
108
- /* Hide chevron for leaf nodes */
109
- :scope:not(:has(> tree-item-ui)) > [slot="row"] > [slot="chevron"] {
108
+ /* Hide caret for leaf nodes */
109
+ :scope:not(:has(> tree-item-ui)) > [slot="row"] > [slot="caret"] {
110
110
  visibility: hidden;
111
111
  }
112
112
 
@@ -101,3 +101,55 @@ describe('<tree-ui> tree-select forwards modifier keys (FB-46)', () => {
101
101
  expect(received.shiftKey).toBe(false);
102
102
  });
103
103
  });
104
+
105
+ describe('<tree-item-ui> caret slot + actions adoption (caret convention + FB-89)', () => {
106
+ let host;
107
+ const settle = () => new Promise((r) => setTimeout(r, 30));
108
+
109
+ beforeEach(() => {
110
+ host = document.createElement('div');
111
+ document.body.appendChild(host);
112
+ });
113
+ afterEach(() => host.remove());
114
+
115
+ it('auto-stamps a slot="caret" icon (renamed from slot="chevron")', async () => {
116
+ const item = document.createElement('tree-item-ui');
117
+ item.setAttribute('text', 'Colors');
118
+ host.appendChild(item);
119
+ await settle();
120
+ const row = item.querySelector(':scope > [slot="row"]');
121
+ expect(row.querySelector('[slot="caret"]')).toBeTruthy();
122
+ expect(row.querySelector('[slot="chevron"]')).toBeNull();
123
+ });
124
+
125
+ it('adopts a declarative slot="actions" child into the stamped row (FB-89)', async () => {
126
+ const item = document.createElement('tree-item-ui');
127
+ item.setAttribute('text', 'Colors');
128
+ const btn = document.createElement('button-ui');
129
+ btn.setAttribute('slot', 'actions');
130
+ btn.setAttribute('icon', 'plus');
131
+ item.appendChild(btn);
132
+ host.appendChild(item);
133
+ await settle();
134
+ const row = item.querySelector(':scope > [slot="row"]');
135
+ // The action button is now INSIDE the row, not a sibling left below it.
136
+ expect(btn.parentElement).toBe(row);
137
+ expect(item.querySelector(':scope > [slot="actions"]')).toBeNull();
138
+ });
139
+
140
+ it('adopts a declarative slot="caret" child instead of stamping a default', async () => {
141
+ const item = document.createElement('tree-item-ui');
142
+ item.setAttribute('text', 'Colors');
143
+ const customCaret = document.createElement('icon-ui');
144
+ customCaret.setAttribute('slot', 'caret');
145
+ customCaret.setAttribute('name', 'folder');
146
+ item.appendChild(customCaret);
147
+ host.appendChild(item);
148
+ await settle();
149
+ const row = item.querySelector(':scope > [slot="row"]');
150
+ const carets = row.querySelectorAll('[slot="caret"]');
151
+ expect(carets.length).toBe(1); // no double-stamp
152
+ expect(carets[0]).toBe(customCaret); // consumer's caret used
153
+ expect(carets[0].getAttribute('name')).toBe('folder');
154
+ });
155
+ });
@@ -59,8 +59,8 @@ tokens:
59
59
  description: Background color on hover
60
60
  --tree-bg-selected:
61
61
  description: Background color when selected
62
- --tree-chevron-size:
63
- description: Size of the collapse chevron icon
62
+ --tree-caret-size:
63
+ description: Size of the collapse caret icon
64
64
  --tree-duration:
65
65
  description: Transition duration
66
66
  --tree-easing:
@@ -68,7 +68,7 @@ tokens:
68
68
  --tree-fg:
69
69
  description: Primary text color
70
70
  --tree-fg-muted:
71
- description: Muted text color (icons, chevrons)
71
+ description: Muted text color (icons, carets)
72
72
  --tree-focus-ring:
73
73
  description: Focus ring box-shadow
74
74
  --tree-font-size:
@@ -121,7 +121,7 @@ a2ui:
121
121
  rows — selection is managed by the parent and bubbles once.
122
122
  Detail = {item, text, value, ctrlKey, metaKey, shiftKey}.
123
123
  - >-
124
- Per ADR-0027, <tree-ui> composes <icon-ui> (for chevrons) but
124
+ Per ADR-0027, <tree-ui> composes <icon-ui> (for carets) but
125
125
  does NOT auto-import its children. Consumer pages must
126
126
  explicitly import both <tree-ui> and <tree-item-ui>.
127
127
  anti_patterns: []