@adia-ai/web-components 0.5.4 → 0.5.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 (34) hide show
  1. package/components/accordion/accordion-item.a2ui.json +50 -0
  2. package/components/accordion/accordion-item.yaml +27 -0
  3. package/components/action-list/action-item.a2ui.json +63 -0
  4. package/components/action-list/action-item.yaml +37 -0
  5. package/components/avatar/avatar-group.a2ui.json +50 -0
  6. package/components/avatar/avatar-group.yaml +26 -0
  7. package/components/avatar/avatar.a2ui.json +4 -1
  8. package/components/avatar/avatar.yaml +7 -0
  9. package/components/button/class.js +39 -0
  10. package/components/chart/chart.a2ui.json +4 -2
  11. package/components/list/list-item.a2ui.json +53 -0
  12. package/components/list/list-item.yaml +29 -0
  13. package/components/select/class.js +14 -0
  14. package/components/select/select.a2ui.json +5 -0
  15. package/components/select/select.css +10 -0
  16. package/components/select/select.yaml +5 -0
  17. package/components/slider/class.js +58 -0
  18. package/components/slider/slider.a2ui.json +10 -0
  19. package/components/slider/slider.css +13 -0
  20. package/components/slider/slider.yaml +10 -0
  21. package/components/switch/class.js +18 -4
  22. package/components/switch/switch.css +10 -0
  23. package/components/tabs/tab.a2ui.json +58 -0
  24. package/components/tabs/tab.yaml +33 -0
  25. package/components/timeline/timeline-item.a2ui.json +76 -0
  26. package/components/timeline/timeline-item.yaml +47 -0
  27. package/components/tree/class.js +91 -0
  28. package/components/tree/tree-item.a2ui.json +65 -0
  29. package/components/tree/tree-item.yaml +41 -0
  30. package/components/tree/tree.a2ui.json +15 -0
  31. package/components/tree/tree.css +18 -0
  32. package/components/tree/tree.yaml +10 -0
  33. package/core/template.js +21 -3
  34. package/package.json +2 -2
@@ -134,4 +134,17 @@
134
134
  :scope[disabled] [slot="fill"] { background: var(--slider-fill-bg-disabled); }
135
135
  :scope[disabled] [slot="thumb"] { cursor: not-allowed; background: var(--slider-thumb-bg-disabled); }
136
136
  :scope[disabled] [slot="thumb"]:hover { box-shadow: none; }
137
+
138
+ /* ── Hint (§184, v0.5.5, FEEDBACK-08 §7) ──
139
+ Small caption rendered beneath the track. aria-describedby is
140
+ wired on the host in class.js so screen readers announce this
141
+ as a description (distinct from aria-label, which comes from
142
+ [label]). Uses the same muted typography as field-ui's hint. */
143
+ [slot="hint"] {
144
+ display: block;
145
+ margin-top: var(--slider-hint-mt, var(--a-space-1));
146
+ font-size: var(--slider-hint-size, var(--a-fine-size));
147
+ color: var(--slider-hint-fg, var(--a-fg-muted));
148
+ line-height: var(--slider-hint-lh, 1.4);
149
+ }
137
150
  }
@@ -51,6 +51,16 @@ props:
51
51
  description: Current slider value
52
52
  type: number
53
53
  default: 50
54
+ throttle:
55
+ description: |-
56
+ §184 (v0.5.5, FEEDBACK-08 §4): when > 0, debounce the `input` event by this many milliseconds. Value updates + visual feedback remain immediate; only event dispatch accumulates. Pending input flushes BEFORE `change` so consumers always see input→…→input→change ordering. throttle="0" (default) preserves the pre-§184 every-pointer-move-fires-input behavior. Common values: 50-100ms for palette regen / shader compile / large list reflow.
57
+ type: number
58
+ default: 0
59
+ hint:
60
+ description: |-
61
+ §184 (v0.5.5, FEEDBACK-08 §7): small caption rendered beneath the slider track. Sets `aria-describedby` on the host so screen readers announce it as a description (distinct from `aria-label`, which comes from `label`). Does not conflict with the in-component `label`. Use for semantic clarifications a `<field-ui>` wrapper would be overkill for.
62
+ type: string
63
+ default: ""
54
64
  events:
55
65
  change:
56
66
  description: "Fired when the value changes (on blur for inputs, on selection for pickers)."
@@ -26,12 +26,26 @@ export class UISwitch extends UIFormElement {
26
26
  checked: { type: Boolean, default: false, reflect: true },
27
27
  label: { type: String, default: '', reflect: true },
28
28
  size: { type: String, default: '', reflect: true },
29
+ // §184 (v0.5.5, FEEDBACK-08 §7): caption beneath the toggle.
30
+ // switch.yaml has declared `hint` since §170 but the template
31
+ // never rendered it — this arc closes the spec-vs-impl gap +
32
+ // wires aria-describedby on the host for screen readers.
33
+ hint: { type: String, default: '', reflect: true },
29
34
  };
30
35
 
31
- static template = (el) => html`
32
- <span slot="track"><span slot="thumb"></span></span>
33
- ${el.label ? html`<span slot="label">${el.label}</span>` : null}
34
- `;
36
+ // §184: per-instance hint id counter for aria-describedby wiring.
37
+ static #hintSeq = 0;
38
+
39
+ static template = (el) => {
40
+ const hintId = el.hint ? `switch-hint-${++UISwitch.#hintSeq}` : '';
41
+ if (hintId) el.setAttribute('aria-describedby', hintId);
42
+ else el.removeAttribute('aria-describedby');
43
+ return html`
44
+ <span slot="track"><span slot="thumb"></span></span>
45
+ ${el.label ? html`<span slot="label">${el.label}</span>` : null}
46
+ ${el.hint ? html`<span slot="hint" id=${hintId}>${el.hint}</span>` : null}
47
+ `;
48
+ };
35
49
 
36
50
  connected() {
37
51
  super.connected();
@@ -105,6 +105,16 @@ switch-ui[checked] [slot="thumb"] {
105
105
 
106
106
  [slot="label"] { font-size: var(--switch-font-size); }
107
107
 
108
+ /* ── Hint (§184, v0.5.5, FEEDBACK-08 §7) ──
109
+ Caption beneath the toggle row; wired to aria-describedby on host. */
110
+ [slot="hint"] {
111
+ display: block;
112
+ margin-top: var(--switch-hint-mt, var(--a-space-1));
113
+ font-size: var(--switch-hint-size, var(--a-fine-size));
114
+ color: var(--switch-hint-fg, var(--a-fg-muted));
115
+ line-height: var(--switch-hint-lh, 1.4);
116
+ }
117
+
108
118
  :scope:focus-visible { outline: none; }
109
119
  :scope:focus-visible [slot="track"] { box-shadow: var(--switch-focus-ring); }
110
120
 
@@ -0,0 +1,58 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/Tab.json",
4
+ "title": "Tab",
5
+ "description": "Child of <tabs-ui>. One tab panel — the tab BUTTON is rendered by the parent from this child's text/icon. Wraps panel content as light DOM.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "component": {
17
+ "const": "Tab"
18
+ },
19
+ "disabled": {
20
+ "description": "Whether the tab is selectable.",
21
+ "type": "boolean",
22
+ "default": false
23
+ },
24
+ "icon": {
25
+ "description": "Optional leading icon name (Phosphor) for the tab button.",
26
+ "type": "string"
27
+ },
28
+ "text": {
29
+ "description": "Tab button label (rendered by parent <tabs-ui>).",
30
+ "type": "string"
31
+ },
32
+ "value": {
33
+ "description": "Stable id for the tab. Parent uses this to coordinate active state.",
34
+ "type": "string"
35
+ }
36
+ },
37
+ "required": [
38
+ "component"
39
+ ],
40
+ "unevaluatedProperties": false,
41
+ "x-adiaui": {
42
+ "anti_patterns": [],
43
+ "category": "navigation",
44
+ "composes": [],
45
+ "events": {},
46
+ "examples": [],
47
+ "keywords": [],
48
+ "name": "UITab",
49
+ "related": [],
50
+ "slots": {},
51
+ "states": [],
52
+ "synonyms": {},
53
+ "tag": "tab-ui",
54
+ "tokens": {},
55
+ "traits": [],
56
+ "version": 1
57
+ }
58
+ }
@@ -0,0 +1,33 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ #
3
+ # §176 (v0.5.5): authored to close the §175 baseline-orphan class. The
4
+ # component already existed as a sibling class in the parent's class.js
5
+ # + was registered alongside the parent (e.g. UIList + UIListItem both
6
+ # from list/class.js). The catalog just lacked its own entry. With the
7
+ # §172 sibling-yaml scanner, this file gets picked up next to the parent
8
+ # yaml.
9
+
10
+ # Child component of <tabs-ui>. Surface only inside that parent.
11
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
12
+ name: UITab
13
+ tag: tab-ui
14
+ component: Tab
15
+ category: navigation
16
+ version: 1
17
+ description: |-
18
+ Child of <tabs-ui>. One tab panel — the tab BUTTON is rendered by the parent from this child's text/icon. Wraps panel content as light DOM.
19
+
20
+ props:
21
+ text:
22
+ description: Tab button label (rendered by parent <tabs-ui>).
23
+ type: string
24
+ value:
25
+ description: Stable id for the tab. Parent uses this to coordinate active state.
26
+ type: string
27
+ icon:
28
+ description: Optional leading icon name (Phosphor) for the tab button.
29
+ type: string
30
+ disabled:
31
+ description: Whether the tab is selectable.
32
+ type: boolean
33
+ default: false
@@ -0,0 +1,76 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/TimelineItem.json",
4
+ "title": "TimelineItem",
5
+ "description": "Child of <timeline-ui>. One step in a sequenced reasoning/process timeline. Used heavily by <agent-reasoning-ui> + <agent-trace-ui>.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "description": {
17
+ "description": "Subtitle below the label.",
18
+ "type": "string"
19
+ },
20
+ "component": {
21
+ "const": "TimelineItem"
22
+ },
23
+ "duration": {
24
+ "description": "Elapsed time (e.g. \"1.2s\", \"340ms\").",
25
+ "type": "string"
26
+ },
27
+ "icon": {
28
+ "description": "Optional leading icon name (Phosphor).",
29
+ "type": "string"
30
+ },
31
+ "spinner": {
32
+ "description": "Show a spinner instead of the icon while status=pending.",
33
+ "type": "boolean",
34
+ "default": false
35
+ },
36
+ "status": {
37
+ "description": "Lifecycle state (idle | pending | done | failed).",
38
+ "type": "string",
39
+ "default": "idle"
40
+ },
41
+ "text": {
42
+ "description": "Primary step label.",
43
+ "type": "string"
44
+ },
45
+ "time": {
46
+ "description": "Absolute timestamp (HH:MM or ISO).",
47
+ "type": "string"
48
+ },
49
+ "variant": {
50
+ "description": "Visual variant (default | accent | success | warning | danger).",
51
+ "type": "string",
52
+ "default": "default"
53
+ }
54
+ },
55
+ "required": [
56
+ "component"
57
+ ],
58
+ "unevaluatedProperties": false,
59
+ "x-adiaui": {
60
+ "anti_patterns": [],
61
+ "category": "feedback",
62
+ "composes": [],
63
+ "events": {},
64
+ "examples": [],
65
+ "keywords": [],
66
+ "name": "UITimelineItem",
67
+ "related": [],
68
+ "slots": {},
69
+ "states": [],
70
+ "synonyms": {},
71
+ "tag": "timeline-item-ui",
72
+ "tokens": {},
73
+ "traits": [],
74
+ "version": 1
75
+ }
76
+ }
@@ -0,0 +1,47 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ #
3
+ # §176 (v0.5.5): authored to close the §175 baseline-orphan class. The
4
+ # component already existed as a sibling class in the parent's class.js
5
+ # + was registered alongside the parent (e.g. UIList + UIListItem both
6
+ # from list/class.js). The catalog just lacked its own entry. With the
7
+ # §172 sibling-yaml scanner, this file gets picked up next to the parent
8
+ # yaml.
9
+
10
+ # Child component of <timeline-ui>. Surface only inside that parent.
11
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
12
+ name: UITimelineItem
13
+ tag: timeline-item-ui
14
+ component: TimelineItem
15
+ category: feedback
16
+ version: 1
17
+ description: |-
18
+ Child of <timeline-ui>. One step in a sequenced reasoning/process timeline. Used heavily by <agent-reasoning-ui> + <agent-trace-ui>.
19
+
20
+ props:
21
+ text:
22
+ description: Primary step label.
23
+ type: string
24
+ description:
25
+ description: Subtitle below the label.
26
+ type: string
27
+ time:
28
+ description: Absolute timestamp (HH:MM or ISO).
29
+ type: string
30
+ duration:
31
+ description: Elapsed time (e.g. "1.2s", "340ms").
32
+ type: string
33
+ icon:
34
+ description: Optional leading icon name (Phosphor).
35
+ type: string
36
+ variant:
37
+ description: Visual variant (default | accent | success | warning | danger).
38
+ type: string
39
+ default: 'default'
40
+ status:
41
+ description: Lifecycle state (idle | pending | done | failed).
42
+ type: string
43
+ default: 'idle'
44
+ spinner:
45
+ description: Show a spinner instead of the icon while status=pending.
46
+ type: boolean
47
+ default: false
@@ -63,6 +63,82 @@ export class UITree extends UIElement {
63
63
  }));
64
64
  }
65
65
 
66
+ // ──────────────────────────────────────────────────────────────────
67
+ // §184 (v0.5.5, FEEDBACK-08 §2): programmatic expand/collapse API.
68
+ // Pre-§184 consumers had to maintain local `_expanded[]` state +
69
+ // manually sync `tree-item.open` on every `tree-select` event. These
70
+ // five methods + the `auto-expand-selected` reflection let consumers
71
+ // declare the open state directly. All methods accept either a
72
+ // tree-item-ui element OR a value string (matched against [value]
73
+ // first, then [text] for ergonomics).
74
+ // ──────────────────────────────────────────────────────────────────
75
+
76
+ /**
77
+ * Resolve an `item` argument to a tree-item-ui element, or `null` if
78
+ * no match. Accepts: HTMLElement (used as-is when it matches `tree-item-ui`)
79
+ * OR a string (matched against [value] first, then [text]).
80
+ */
81
+ #resolveItem(arg) {
82
+ if (!arg) return null;
83
+ if (arg instanceof Element) {
84
+ return arg.matches?.('tree-item-ui') ? arg : null;
85
+ }
86
+ if (typeof arg === 'string') {
87
+ const escaped = arg.replace(/"/g, '\\"');
88
+ return (
89
+ this.querySelector(`tree-item-ui[value="${escaped}"]`) ||
90
+ this.querySelector(`tree-item-ui[text="${escaped}"]`) ||
91
+ null
92
+ );
93
+ }
94
+ return null;
95
+ }
96
+
97
+ /** Expand the given item (no-op when already open or it has no children). */
98
+ expand(itemOrValue) {
99
+ const item = this.#resolveItem(itemOrValue);
100
+ if (item && item.hasChildren && !item.open) item.open = true;
101
+ }
102
+
103
+ /** Collapse the given item (no-op when already collapsed). */
104
+ collapse(itemOrValue) {
105
+ const item = this.#resolveItem(itemOrValue);
106
+ if (item && item.open) item.open = false;
107
+ }
108
+
109
+ /** Expand every item with children. */
110
+ expandAll() {
111
+ for (const it of this.querySelectorAll('tree-item-ui')) {
112
+ if (it.hasChildren && !it.open) it.open = true;
113
+ }
114
+ }
115
+
116
+ /** Collapse every item. */
117
+ collapseAll() {
118
+ for (const it of this.querySelectorAll('tree-item-ui')) {
119
+ if (it.open) it.open = false;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Expand every ancestor of the given item so it becomes visible, then
125
+ * scroll it into view. Useful for revealing a programmatically-selected
126
+ * item nested inside collapsed parents.
127
+ */
128
+ expandTo(itemOrValue) {
129
+ const item = this.#resolveItem(itemOrValue);
130
+ if (!item) return;
131
+ let parent = item.parentElement?.closest('tree-item-ui');
132
+ while (parent) {
133
+ if (parent.hasChildren && !parent.open) parent.open = true;
134
+ parent = parent.parentElement?.closest('tree-item-ui');
135
+ }
136
+ item.querySelector(':scope > [slot="row"]')?.scrollIntoView?.({
137
+ block: 'nearest',
138
+ inline: 'nearest',
139
+ });
140
+ }
141
+
66
142
  #onClick = (e) => {
67
143
  const item = e.target.closest('tree-item-ui');
68
144
  if (!item || !this.contains(item)) return;
@@ -171,6 +247,10 @@ export class UITreeItem extends UIElement {
171
247
  value: { type: String, default: '', reflect: true },
172
248
  open: { type: Boolean, default: false, reflect: true },
173
249
  selected: { type: Boolean, default: false, reflect: true },
250
+ // §184 (v0.5.5, FEEDBACK-08 §1): optional trailing badge for counts
251
+ // / labels (e.g. "Colors (7)" → text="Colors" badge="7"). Mirrors
252
+ // nav-item-ui badge API; styled muted-and-small via tree.css.
253
+ badge: { type: String, default: '', reflect: true },
174
254
  };
175
255
 
176
256
  static template = () => null;
@@ -222,6 +302,13 @@ export class UITreeItem extends UIElement {
222
302
  textEl.textContent = this.text;
223
303
  row.appendChild(textEl);
224
304
 
305
+ // §184: trailing badge (when [badge] is set). Stamped even when
306
+ // empty so render() can populate without re-stamping the DOM.
307
+ const badgeEl = document.createElement('span');
308
+ badgeEl.setAttribute('slot', 'badge');
309
+ if (this.badge) badgeEl.textContent = this.badge;
310
+ row.appendChild(badgeEl);
311
+
225
312
  // Actions slot placeholder
226
313
  const actions = document.createElement('span');
227
314
  actions.setAttribute('slot', 'actions');
@@ -247,5 +334,9 @@ export class UITreeItem extends UIElement {
247
334
  // Update text
248
335
  const textEl = row.querySelector('[slot="text"]');
249
336
  if (textEl && this.text) textEl.textContent = this.text;
337
+
338
+ // §184: keep badge slot synced with the [badge] attribute.
339
+ const badgeEl = row.querySelector('[slot="badge"]');
340
+ if (badgeEl) badgeEl.textContent = this.badge || '';
250
341
  }
251
342
  }
@@ -0,0 +1,65 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/TreeItem.json",
4
+ "title": "TreeItem",
5
+ "description": "Child of <tree-ui>. One tree row with optional icon + text + trailing badge, plus nested tree-item-ui children for the collapsible hierarchy. Use inside <tree-ui> only.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "badge": {
17
+ "description": "Optional trailing badge text (count, label, etc.). Renders muted + small + right-aligned alongside the row, mirroring the nav-item-ui badge API. When empty, the badge slot stays empty.\n\nAdded in §184 (v0.5.5, FEEDBACK-08 §1).",
18
+ "type": "string"
19
+ },
20
+ "component": {
21
+ "const": "TreeItem"
22
+ },
23
+ "icon": {
24
+ "description": "Optional leading icon name (Phosphor).",
25
+ "type": "string"
26
+ },
27
+ "open": {
28
+ "description": "When true, the item's children render expanded. Single-instance state; the parent `<tree-ui>` toggles this on click + Enter/Space + ArrowRight/ArrowLeft per tree-view APG.",
29
+ "type": "boolean"
30
+ },
31
+ "selected": {
32
+ "description": "Reflects the currently-selected item. Exactly one tree-item is selected per `<tree-ui>` parent (managed by the parent).",
33
+ "type": "boolean"
34
+ },
35
+ "text": {
36
+ "description": "Primary text rendered in the row.",
37
+ "type": "string"
38
+ },
39
+ "value": {
40
+ "description": "Stable value emitted in `tree-select` event detail.",
41
+ "type": "string"
42
+ }
43
+ },
44
+ "required": [
45
+ "component"
46
+ ],
47
+ "unevaluatedProperties": false,
48
+ "x-adiaui": {
49
+ "anti_patterns": [],
50
+ "category": "data",
51
+ "composes": [],
52
+ "events": {},
53
+ "examples": [],
54
+ "keywords": [],
55
+ "name": "UITreeItem",
56
+ "related": [],
57
+ "slots": {},
58
+ "states": [],
59
+ "synonyms": {},
60
+ "tag": "tree-item-ui",
61
+ "tokens": {},
62
+ "traits": [],
63
+ "version": 1
64
+ }
65
+ }
@@ -0,0 +1,41 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ #
3
+ # §184 (v0.5.5): authored to close FEEDBACK-08 §1 — tree-item-ui badge
4
+ # attribute + general orphan-class catalog gap. tree-item-ui has shipped
5
+ # since v0.0.x co-located in tree/class.js (UITreeItem) but never had its
6
+ # own catalog entry. With §172 sibling-yaml scanner, this file is picked
7
+ # up next to the parent yaml. Mirrors the v0.5.5 §176 sibling-yaml
8
+ # baseline-orphan closure pattern.
9
+
10
+ # Child component of <tree-ui>. Surface only inside that parent.
11
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
12
+ name: UITreeItem
13
+ tag: tree-item-ui
14
+ component: TreeItem
15
+ category: data
16
+ version: 1
17
+ description: |-
18
+ Child of <tree-ui>. One tree row with optional icon + text + trailing badge, plus nested tree-item-ui children for the collapsible hierarchy. Use inside <tree-ui> only.
19
+
20
+ props:
21
+ text:
22
+ description: Primary text rendered in the row.
23
+ type: string
24
+ icon:
25
+ description: Optional leading icon name (Phosphor).
26
+ type: string
27
+ value:
28
+ description: Stable value emitted in `tree-select` event detail.
29
+ type: string
30
+ open:
31
+ description: When true, the item's children render expanded. Single-instance state; the parent `<tree-ui>` toggles this on click + Enter/Space + ArrowRight/ArrowLeft per tree-view APG.
32
+ type: boolean
33
+ selected:
34
+ description: Reflects the currently-selected item. Exactly one tree-item is selected per `<tree-ui>` parent (managed by the parent).
35
+ type: boolean
36
+ badge:
37
+ description: |-
38
+ Optional trailing badge text (count, label, etc.). Renders muted + small + right-aligned alongside the row, mirroring the nav-item-ui badge API. When empty, the badge slot stays empty.
39
+
40
+ Added in §184 (v0.5.5, FEEDBACK-08 §1).
41
+ type: string
@@ -84,6 +84,21 @@
84
84
  "--tree-actions-gap": {
85
85
  "description": "Gap between action icons"
86
86
  },
87
+ "--tree-badge-bg": {
88
+ "description": "Background color for the trailing badge (§184)."
89
+ },
90
+ "--tree-badge-fg": {
91
+ "description": "Foreground color for the trailing badge (§184)."
92
+ },
93
+ "--tree-badge-px": {
94
+ "description": "Inline padding for the trailing badge (§184)."
95
+ },
96
+ "--tree-badge-radius": {
97
+ "description": "Border radius for the trailing badge (§184)."
98
+ },
99
+ "--tree-badge-size": {
100
+ "description": "Font size for the trailing badge (§184)."
101
+ },
87
102
  "--tree-bg-hover": {
88
103
  "description": "Background color on hover"
89
104
  },
@@ -127,6 +127,24 @@
127
127
  text-overflow: ellipsis;
128
128
  }
129
129
 
130
+ /* ── Badge (§184, v0.5.5, FEEDBACK-08 §1) ──
131
+ Optional trailing badge for counts/labels (e.g. "Colors (7)" →
132
+ text="Colors" badge="7"). Empty span renders nothing (no padding,
133
+ no chrome). Sits before the [slot="actions"] hover-revealed slot. */
134
+ [slot="badge"] {
135
+ flex-shrink: 0;
136
+ font-size: var(--tree-badge-size, var(--a-fine-size));
137
+ color: var(--tree-badge-fg, var(--tree-fg-muted));
138
+ background: var(--tree-badge-bg, transparent);
139
+ padding: 0 var(--tree-badge-px, var(--a-space-1));
140
+ border-radius: var(--tree-badge-radius, var(--a-radius-sm));
141
+ line-height: 1;
142
+ font-variant-numeric: tabular-nums;
143
+ }
144
+ [slot="badge"]:empty {
145
+ display: none;
146
+ }
147
+
130
148
  /* ── Actions (right side) ── */
131
149
  [slot="actions"] {
132
150
  display: flex;
@@ -66,6 +66,16 @@ tokens:
66
66
  description: Inline-end padding of each row
67
67
  --tree-row-radius:
68
68
  description: Border radius of each row
69
+ --tree-badge-bg:
70
+ description: Background color for the trailing badge (§184).
71
+ --tree-badge-fg:
72
+ description: Foreground color for the trailing badge (§184).
73
+ --tree-badge-px:
74
+ description: Inline padding for the trailing badge (§184).
75
+ --tree-badge-radius:
76
+ description: Border radius for the trailing badge (§184).
77
+ --tree-badge-size:
78
+ description: Font size for the trailing badge (§184).
69
79
  requiredIcons:
70
80
  - caret-right
71
81
  a2ui:
package/core/template.js CHANGED
@@ -137,14 +137,32 @@ function scan(fragment, count) {
137
137
  // bug. Hot template paths fire many times, so warn (not throw):
138
138
  // a thrown error would crash render; warn surfaces the bug class.
139
139
  if (attr.value.includes('{{p:')) {
140
+ // §184 (v0.5.5, FEEDBACK-08 §5): `class` is special-cased
141
+ // in the hint because `.class=${expr}` sets a JS expando
142
+ // property (`element.class = "foo"`) — NOT the `class`
143
+ // attribute / className. Consumers following the original
144
+ // §152 generic hint silently lost CSS classes. For class,
145
+ // suggest `.className=${expr}`; for style suggest the
146
+ // canonical style-object path; for everything else keep
147
+ // the generic property-assignment recipe.
148
+ const a = attr.name;
149
+ const specific =
150
+ a === 'class'
151
+ ? ` .className=\${expression} ← write to the className property (NOT .class, which is an expando)\n` +
152
+ ` class="\${expression}" ← full replacement (whole class string is the expression)\n` +
153
+ ` .classList=\${{foo: true, bar: false}} ← if/when implemented; verify in USAGE.md\n`
154
+ : a === 'style'
155
+ ? ` .style.cssText=\${expression} ← write CSS text to .style.cssText\n` +
156
+ ` style="\${expression}" ← full replacement of the style string\n`
157
+ : ` ${a}="\${expression}" ← full replacement (whole attr is the placeholder)\n` +
158
+ ` .${a}=\${expression} ← property assignment (preferred for objects/functions)\n`;
140
159
  // eslint-disable-next-line no-console
141
160
  console.warn(
142
161
  `[template] Partial attribute interpolation is not supported.\n` +
143
162
  ` Element: <${n.tagName.toLowerCase()}>\n` +
144
- ` Attribute: ${attr.name}="${attr.value.slice(0, 80)}${attr.value.length > 80 ? '…' : ''}"\n` +
163
+ ` Attribute: ${a}="${attr.value.slice(0, 80)}${attr.value.length > 80 ? '…' : ''}"\n` +
145
164
  ` Use one of:\n` +
146
- ` ${attr.name}="\${expression}" ← full replacement (whole attr is the placeholder)\n` +
147
- ` .${attr.name}=\${expression} ← property assignment (preferred for objects/functions)\n` +
165
+ specific +
148
166
  ` Compute the full attr value as a single expression and interpolate the whole.`
149
167
  );
150
168
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adia-ai/web-components",
3
- "version": "0.5.4",
4
- "description": "AdiaUI web components vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-runtime.",
3
+ "version": "0.5.5",
4
+ "description": "AdiaUI web components \u2014 vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-runtime.",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",
7
7
  "exports": {