@adia-ai/web-components 0.5.4 → 0.5.6

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 (46) 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/agent-feedback-bar/class.js +9 -3
  6. package/components/avatar/avatar-group.a2ui.json +50 -0
  7. package/components/avatar/avatar-group.yaml +26 -0
  8. package/components/avatar/avatar.a2ui.json +4 -1
  9. package/components/avatar/avatar.yaml +7 -0
  10. package/components/button/class.js +39 -0
  11. package/components/chart/chart.a2ui.json +4 -2
  12. package/components/list/list-item.a2ui.json +53 -0
  13. package/components/list/list-item.yaml +29 -0
  14. package/components/segmented/segmented.d.ts +3 -3
  15. package/components/select/class.js +14 -0
  16. package/components/select/select.a2ui.json +5 -0
  17. package/components/select/select.css +10 -0
  18. package/components/select/select.d.ts +3 -3
  19. package/components/select/select.yaml +5 -0
  20. package/components/slider/class.js +58 -0
  21. package/components/slider/slider.a2ui.json +10 -0
  22. package/components/slider/slider.css +13 -0
  23. package/components/slider/slider.yaml +10 -0
  24. package/components/switch/class.js +18 -4
  25. package/components/switch/switch.css +10 -0
  26. package/components/switch/switch.d.ts +3 -3
  27. package/components/tabs/tab.a2ui.json +58 -0
  28. package/components/tabs/tab.yaml +33 -0
  29. package/components/timeline/timeline-item.a2ui.json +76 -0
  30. package/components/timeline/timeline-item.yaml +47 -0
  31. package/components/toast/toast.d.ts +35 -0
  32. package/components/tree/class.js +91 -0
  33. package/components/tree/tree-item.a2ui.json +65 -0
  34. package/components/tree/tree-item.yaml +41 -0
  35. package/components/tree/tree.a2ui.json +15 -0
  36. package/components/tree/tree.css +18 -0
  37. package/components/tree/tree.yaml +10 -0
  38. package/core/anchor.d.ts +71 -0
  39. package/core/controller.d.ts +171 -0
  40. package/core/markdown.d.ts +26 -0
  41. package/core/polyfills.d.ts +31 -0
  42. package/core/provider.d.ts +82 -0
  43. package/core/streams-bridge.d.ts +78 -0
  44. package/core/template.js +21 -3
  45. package/core/transport.d.ts +78 -0
  46. package/package.json +2 -2
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/AccordionItem.json",
4
+ "title": "AccordionItem",
5
+ "description": "Child of <accordion-ui>. One collapsible section with header + body. Use inside <accordion-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
+ "component": {
17
+ "const": "AccordionItem"
18
+ },
19
+ "open": {
20
+ "description": "Whether the section is expanded.",
21
+ "type": "boolean",
22
+ "default": false
23
+ },
24
+ "text": {
25
+ "description": "Header text — the clickable label that toggles the section.",
26
+ "type": "string"
27
+ }
28
+ },
29
+ "required": [
30
+ "component"
31
+ ],
32
+ "unevaluatedProperties": false,
33
+ "x-adiaui": {
34
+ "anti_patterns": [],
35
+ "category": "layout",
36
+ "composes": [],
37
+ "events": {},
38
+ "examples": [],
39
+ "keywords": [],
40
+ "name": "UIAccordionItem",
41
+ "related": [],
42
+ "slots": {},
43
+ "states": [],
44
+ "synonyms": {},
45
+ "tag": "accordion-item-ui",
46
+ "tokens": {},
47
+ "traits": [],
48
+ "version": 1
49
+ }
50
+ }
@@ -0,0 +1,27 @@
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 <accordion-ui>. Surface only inside that parent.
11
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
12
+ name: UIAccordionItem
13
+ tag: accordion-item-ui
14
+ component: AccordionItem
15
+ category: layout
16
+ version: 1
17
+ description: |-
18
+ Child of <accordion-ui>. One collapsible section with header + body. Use inside <accordion-ui> only.
19
+
20
+ props:
21
+ text:
22
+ description: Header text — the clickable label that toggles the section.
23
+ type: string
24
+ open:
25
+ description: Whether the section is expanded.
26
+ type: boolean
27
+ default: false
@@ -0,0 +1,63 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/ActionItem.json",
4
+ "title": "ActionItem",
5
+ "description": "Child of <action-list-ui>. One actionable row — icon + label, forwards activation to the parent action-list.",
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": "ActionItem"
18
+ },
19
+ "disabled": {
20
+ "description": "Disables click + keyboard activation; aria-disabled is set.",
21
+ "type": "boolean",
22
+ "default": false
23
+ },
24
+ "icon": {
25
+ "description": "Leading icon name (Phosphor).",
26
+ "type": "string"
27
+ },
28
+ "text": {
29
+ "description": "Action label.",
30
+ "type": "string"
31
+ },
32
+ "value": {
33
+ "description": "Identifier passed to the action-list change/activate handlers.",
34
+ "type": "string"
35
+ },
36
+ "variant": {
37
+ "description": "Visual variant (default | accent | danger).",
38
+ "type": "string",
39
+ "default": "default"
40
+ }
41
+ },
42
+ "required": [
43
+ "component"
44
+ ],
45
+ "unevaluatedProperties": false,
46
+ "x-adiaui": {
47
+ "anti_patterns": [],
48
+ "category": "navigation",
49
+ "composes": [],
50
+ "events": {},
51
+ "examples": [],
52
+ "keywords": [],
53
+ "name": "UIActionItem",
54
+ "related": [],
55
+ "slots": {},
56
+ "states": [],
57
+ "synonyms": {},
58
+ "tag": "action-item-ui",
59
+ "tokens": {},
60
+ "traits": [],
61
+ "version": 1
62
+ }
63
+ }
@@ -0,0 +1,37 @@
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 <action-list-ui>. Surface only inside that parent.
11
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
12
+ name: UIActionItem
13
+ tag: action-item-ui
14
+ component: ActionItem
15
+ category: navigation
16
+ version: 1
17
+ description: |-
18
+ Child of <action-list-ui>. One actionable row — icon + label, forwards activation to the parent action-list.
19
+
20
+ props:
21
+ icon:
22
+ description: Leading icon name (Phosphor).
23
+ type: string
24
+ text:
25
+ description: Action label.
26
+ type: string
27
+ value:
28
+ description: Identifier passed to the action-list change/activate handlers.
29
+ type: string
30
+ variant:
31
+ description: Visual variant (default | accent | danger).
32
+ type: string
33
+ default: 'default'
34
+ disabled:
35
+ description: Disables click + keyboard activation; aria-disabled is set.
36
+ type: boolean
37
+ default: false
@@ -45,12 +45,17 @@
45
45
 
46
46
  import { UIElement } from '../../core/element.js';
47
47
 
48
- function makeButton({ icon, text = '' }) {
48
+ function makeButton({ icon, text = '', title = '' }) {
49
49
  const btn = document.createElement('button-ui');
50
50
  btn.setAttribute('icon', icon);
51
51
  btn.setAttribute('variant', 'ghost');
52
52
  btn.setAttribute('size', 'sm');
53
53
  if (text) btn.setAttribute('text', text);
54
+ // §190a (v0.5.6): supply title= for icon-only buttons. button-ui's §184
55
+ // FEEDBACK-08 §8 a11y safety-net mirrors title → aria-label automatically
56
+ // when the button is icon-only. Closes the per-render console.warn class
57
+ // captured 2026-05-14 ("Icon-only button is missing an accessible name").
58
+ if (!text && title) btn.setAttribute('title', title);
54
59
  return btn;
55
60
  }
56
61
 
@@ -142,11 +147,12 @@ export class UIAgentFeedbackBar extends UIElement {
142
147
  #build() {
143
148
  this.innerHTML = '';
144
149
 
145
- this.#upEl = makeButton({ icon: 'thumbs-up' });
146
- this.#downEl = makeButton({ icon: 'thumbs-down' });
150
+ this.#upEl = makeButton({ icon: 'thumbs-up', title: 'Rate this response positively' });
151
+ this.#downEl = makeButton({ icon: 'thumbs-down', title: 'Rate this response negatively' });
147
152
  this.#saveEl = makeButton({
148
153
  icon: this.saveIcon || 'bookmark-simple',
149
154
  text: this.saveLabel || '',
155
+ title: this.saveLabel ? '' : 'Save this response',
150
156
  });
151
157
  if (!this.saveLabel) this.#saveEl.hidden = true;
152
158
 
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/AvatarGroup.json",
4
+ "title": "AvatarGroup",
5
+ "description": "Cluster of overlapping <avatar-ui> children. Stacks the first `max` avatars with a negative inline-start margin; if more children are present, renders a +N overflow indicator.",
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": "AvatarGroup"
18
+ },
19
+ "max": {
20
+ "description": "Maximum avatars to show before the +N overflow indicator.",
21
+ "type": "number",
22
+ "default": 5
23
+ },
24
+ "size": {
25
+ "description": "Forwards to each child <avatar-ui> (xs|sm|md|lg|xl). Empty uses the per-avatar size.",
26
+ "type": "string"
27
+ }
28
+ },
29
+ "required": [
30
+ "component"
31
+ ],
32
+ "unevaluatedProperties": false,
33
+ "x-adiaui": {
34
+ "anti_patterns": [],
35
+ "category": "feedback",
36
+ "composes": [],
37
+ "events": {},
38
+ "examples": [],
39
+ "keywords": [],
40
+ "name": "UIAvatarGroup",
41
+ "related": [],
42
+ "slots": {},
43
+ "states": [],
44
+ "synonyms": {},
45
+ "tag": "avatar-group-ui",
46
+ "tokens": {},
47
+ "traits": [],
48
+ "version": 1
49
+ }
50
+ }
@@ -0,0 +1,26 @@
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
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
11
+ name: UIAvatarGroup
12
+ tag: avatar-group-ui
13
+ component: AvatarGroup
14
+ category: feedback
15
+ version: 1
16
+ description: |-
17
+ Cluster of overlapping <avatar-ui> children. Stacks the first `max` avatars with a negative inline-start margin; if more children are present, renders a +N overflow indicator.
18
+
19
+ props:
20
+ max:
21
+ description: Maximum avatars to show before the +N overflow indicator.
22
+ type: number
23
+ default: 5
24
+ size:
25
+ description: Forwards to each child <avatar-ui> (xs|sm|md|lg|xl). Empty uses the per-avatar size.
26
+ type: string
@@ -24,7 +24,10 @@
24
24
  "name": {
25
25
  "description": "Deprecated alias for `text` (logs a one-shot console warning; will be removed in a future release). New code should use `text=` instead.",
26
26
  "type": "string",
27
- "default": ""
27
+ "default": "",
28
+ "deprecated": true,
29
+ "deprecated_reason": "Use `text` instead. Will be removed in v0.6.0.",
30
+ "deprecated_since": "0.4.x"
28
31
  },
29
32
  "shape": {
30
33
  "description": "Avatar shape",
@@ -19,6 +19,13 @@ props:
19
19
  name:
20
20
  description: "Deprecated alias for `text` (logs a one-shot console warning; will be removed in a future release). New code should use `text=` instead."
21
21
  type: string
22
+ # §179 (v0.5.5): principled deprecation metadata. The build pipeline
23
+ # propagates this into the sidecar + catalog; the LLM prompt builder
24
+ # surfaces "(deprecated, do not use)" based on this field rather than
25
+ # substring-matching the description.
26
+ deprecated: true
27
+ deprecated_since: "0.4.x"
28
+ deprecated_reason: "Use `text` instead. Will be removed in v0.6.0."
22
29
  default: ""
23
30
  icon:
24
31
  description: Phosphor icon name shown instead of initials when `src`/`text` are empty.
@@ -52,8 +52,47 @@ export class UIButton extends UIElement {
52
52
  this.prepend(iconEl);
53
53
  }
54
54
  }
55
+
56
+ // §184 (v0.5.5, FEEDBACK-08 §8): icon-only a11y warning + title→aria-label
57
+ // auto-derive. Two complementary mechanisms (consumer chooses):
58
+ // (a) When [title="Undo"] is set on an icon-only button without an
59
+ // explicit aria-label, mirror title → aria-label. Matches the
60
+ // common icon-only-button-with-tooltip pattern (zero authoring
61
+ // cost — already had to write the tooltip text).
62
+ // (b) When the button is icon-only AND has no aria-label AND no
63
+ // aria-labelledby AND no title, console.warn ONCE per element
64
+ // so the bug class is diagnosable in dev without firing every
65
+ // render tick. The warning is suppressed via a one-shot
66
+ // WeakSet so subsequent re-renders stay quiet.
67
+ if (this.icon && !this.text) {
68
+ const ariaLabel = this.getAttribute('aria-label');
69
+ const ariaLabelledBy = this.getAttribute('aria-labelledby');
70
+ const titleAttr = this.getAttribute('title');
71
+ if (!ariaLabel && !ariaLabelledBy && titleAttr) {
72
+ this.setAttribute('aria-label', titleAttr);
73
+ } else if (!ariaLabel && !ariaLabelledBy && !titleAttr) {
74
+ if (!UIButton.#a11yWarned.has(this)) {
75
+ UIButton.#a11yWarned.add(this);
76
+ // eslint-disable-next-line no-console
77
+ console.warn(
78
+ `[button-ui] Icon-only button is missing an accessible name.\n` +
79
+ ` Element: <button-ui icon="${this.icon}">\n` +
80
+ ` Add one of:\n` +
81
+ ` aria-label="Undo" ← explicit accessible name\n` +
82
+ ` title="Undo" ← auto-mirrored to aria-label + tooltip\n` +
83
+ ` aria-labelledby="..." ← reference an existing label element\n` +
84
+ ` Without an accessible name screen readers announce nothing meaningful.`
85
+ );
86
+ }
87
+ }
88
+ }
55
89
  }
56
90
 
91
+ // §184: WeakSet of elements we've already warned about. Prevents
92
+ // repeat warnings across re-renders. GC-friendly — when the element
93
+ // is gone the entry is collected.
94
+ static #a11yWarned = new WeakSet();
95
+
57
96
  #onClick = (e) => {
58
97
  if (this.disabled) { e.stopPropagation(); return; }
59
98
  if (this.type === 'submit') {
@@ -47,7 +47,8 @@
47
47
  "square",
48
48
  "tall"
49
49
  ],
50
- "default": "std"
50
+ "default": "std",
51
+ "deprecated": true
51
52
  },
52
53
  "color": {
53
54
  "description": "Color scheme",
@@ -78,7 +79,8 @@
78
79
  "heading": {
79
80
  "description": "DEPRECATED (OD-CHART-02). Place chart titles in an enclosing card-ui's `<header><span slot=\"heading\">...</span></header>` instead. Still honored for back-compat; emits a one-shot console.warn per instance when set. Used as the chart's `aria-label` when no explicit label is provided.",
80
81
  "type": "string",
81
- "default": ""
82
+ "default": "",
83
+ "deprecated": true
82
84
  },
83
85
  "hideAverage": {
84
86
  "description": "When true, suppress the overlaid average line",
@@ -0,0 +1,53 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/ListItem.json",
4
+ "title": "ListItem",
5
+ "description": "Child of <list-ui>. One list item with optional icon + text + description, plus arbitrary slotted content (cards, rows). Use inside <list-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
+ "description": {
17
+ "description": "Secondary line below the primary text. Subtle color.",
18
+ "type": "string"
19
+ },
20
+ "component": {
21
+ "const": "ListItem"
22
+ },
23
+ "icon": {
24
+ "description": "Optional leading icon name (Phosphor).",
25
+ "type": "string"
26
+ },
27
+ "text": {
28
+ "description": "Primary text. Renders as semibold inline body.",
29
+ "type": "string"
30
+ }
31
+ },
32
+ "required": [
33
+ "component"
34
+ ],
35
+ "unevaluatedProperties": false,
36
+ "x-adiaui": {
37
+ "anti_patterns": [],
38
+ "category": "layout",
39
+ "composes": [],
40
+ "events": {},
41
+ "examples": [],
42
+ "keywords": [],
43
+ "name": "UIListItem",
44
+ "related": [],
45
+ "slots": {},
46
+ "states": [],
47
+ "synonyms": {},
48
+ "tag": "list-item-ui",
49
+ "tokens": {},
50
+ "traits": [],
51
+ "version": 1
52
+ }
53
+ }
@@ -0,0 +1,29 @@
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 <list-ui>. Surface only inside that parent.
11
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
12
+ name: UIListItem
13
+ tag: list-item-ui
14
+ component: ListItem
15
+ category: layout
16
+ version: 1
17
+ description: |-
18
+ Child of <list-ui>. One list item with optional icon + text + description, plus arbitrary slotted content (cards, rows). Use inside <list-ui> only.
19
+
20
+ props:
21
+ icon:
22
+ description: Optional leading icon name (Phosphor).
23
+ type: string
24
+ text:
25
+ description: Primary text. Renders as semibold inline body.
26
+ type: string
27
+ description:
28
+ description: Secondary line below the primary text. Subtle color.
29
+ type: string
@@ -6,10 +6,10 @@
6
6
 
7
7
  import { UIFormElement } from '../../core/form.js';
8
8
 
9
- export interface SegmentedChangeEventDetail {
10
- value: string;
9
+ export interface SegmentedChangeEventDetail<V extends string = string> {
10
+ value: V;
11
11
  }
12
- export type SegmentedChangeEvent = CustomEvent<SegmentedChangeEventDetail>;
12
+ export type SegmentedChangeEvent<V extends string = string> = CustomEvent<SegmentedChangeEventDetail<V>>;
13
13
 
14
14
  export class UISegmented extends UIFormElement {
15
15
  /** Selected segment's value. */
@@ -37,8 +37,14 @@ export class UISelect extends UIFormElement {
37
37
  searchable: { type: Boolean, default: false, reflect: true },
38
38
  freeText: { type: Boolean, default: false, reflect: true, attribute: 'free-text' },
39
39
  divider: { type: Boolean, default: false, reflect: true },
40
+ // §184 (v0.5.5, FEEDBACK-08 §7): optional caption beneath the
41
+ // trigger, wired to aria-describedby on the host.
42
+ hint: { type: String, default: '', reflect: true },
40
43
  };
41
44
 
45
+ // §184: per-instance hint id counter for aria-describedby wiring.
46
+ static #hintSeq = 0;
47
+
42
48
  static template = () => null;
43
49
 
44
50
  #options = [];
@@ -97,13 +103,21 @@ export class UISelect extends UIFormElement {
97
103
  const displayMarkup = this.searchable
98
104
  ? `<input slot="display" type="text" role="combobox" aria-autocomplete="list" autocomplete="off" placeholder="${escapeHTML(this.placeholder || '')}" value="${escapeHTML(this.#displayText() === this.placeholder ? '' : this.#displayText())}" />`
99
105
  : `<span slot="display">${escapeHTML(this.#displayText())}</span>`;
106
+ // §184 (v0.5.5, FEEDBACK-08 §7): optional hint slot beneath the
107
+ // trigger. Mirrors slider-ui's hint pattern + matches the
108
+ // schema-declared `hint` prop on switch-ui (which had the spec
109
+ // but not the rendering until this arc).
110
+ const hintId = this.hint ? `select-hint-${++UISelect.#hintSeq}` : '';
111
+ const hintMarkup = this.hint ? `<span slot="hint" id="${hintId}">${escapeHTML(this.hint)}</span>` : '';
100
112
  this.innerHTML = `
101
113
  <span slot="trigger">
102
114
  ${leading}
103
115
  ${displayMarkup}
104
116
  <icon-ui name="caret-up-down" slot="caret"></icon-ui>
105
117
  </span>
118
+ ${hintMarkup}
106
119
  `;
120
+ if (this.hint) this.setAttribute('aria-describedby', hintId);
107
121
 
108
122
  if (this.searchable) {
109
123
  // Detach from previous search input if any
@@ -46,6 +46,11 @@
46
46
  "type": "boolean",
47
47
  "default": false
48
48
  },
49
+ "hint": {
50
+ "description": "§184 (v0.5.5, FEEDBACK-08 §7): small caption rendered beneath the select. 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`.",
51
+ "type": "string",
52
+ "default": ""
53
+ },
49
54
  "icon": {
50
55
  "description": "Leading icon name rendered inside the trigger",
51
56
  "type": "string",
@@ -308,3 +308,13 @@ select-ui[divider] [role="group"] + [role="group"] {
308
308
  margin-top: var(--a-space-1);
309
309
  padding-top: var(--a-space-1);
310
310
  }
311
+
312
+ /* ── Hint (§184, v0.5.5, FEEDBACK-08 §7) ──
313
+ Caption beneath the trigger; wired to aria-describedby on the host. */
314
+ select-ui > [slot="hint"] {
315
+ display: block;
316
+ margin-top: var(--select-hint-mt, var(--a-space-1));
317
+ font-size: var(--select-hint-size, var(--a-fine-size));
318
+ color: var(--select-hint-fg, var(--a-fg-muted));
319
+ line-height: var(--select-hint-lh, 1.4);
320
+ }
@@ -16,10 +16,10 @@ export interface SelectOption {
16
16
  divider?: boolean;
17
17
  }
18
18
 
19
- export interface SelectChangeEventDetail {
20
- value: string;
19
+ export interface SelectChangeEventDetail<V extends string = string> {
20
+ value: V;
21
21
  }
22
- export type SelectChangeEvent = CustomEvent<SelectChangeEventDetail>;
22
+ export type SelectChangeEvent<V extends string = string> = CustomEvent<SelectChangeEventDetail<V>>;
23
23
 
24
24
  export interface SelectActionEventDetail {
25
25
  action: string;
@@ -67,6 +67,11 @@ props:
67
67
  description: Label text above the trigger
68
68
  type: string
69
69
  default: ""
70
+ hint:
71
+ description: |-
72
+ §184 (v0.5.5, FEEDBACK-08 §7): small caption rendered beneath the select. 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`.
73
+ type: string
74
+ default: ""
70
75
  maxlength:
71
76
  description: Maximum character length for validation
72
77
  type: number