@adia-ai/web-components 0.8.31 → 0.8.32

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 (108) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/components/accordion/accordion.d.ts +2 -2
  3. package/components/action-list/action-list.class.js +6 -2
  4. package/components/action-list/action-list.d.ts +1 -1
  5. package/components/agent-artifact/agent-artifact.d.ts +1 -1
  6. package/components/agent-feedback-bar/agent-feedback-bar.d.ts +2 -2
  7. package/components/agent-questions/agent-questions.d.ts +1 -1
  8. package/components/agent-reasoning/agent-reasoning.d.ts +3 -3
  9. package/components/agent-suggestions/agent-suggestions.d.ts +1 -1
  10. package/components/agent-trace/agent-trace.d.ts +1 -1
  11. package/components/alert/alert.d.ts +2 -2
  12. package/components/anchor-bar/anchor-bar.d.ts +2 -2
  13. package/components/avatar/avatar.class.js +0 -1
  14. package/components/avatar/avatar.css +16 -2
  15. package/components/badge/badge.css +30 -2
  16. package/components/breadcrumb/breadcrumb.css +15 -3
  17. package/components/button/button.d.ts +1 -1
  18. package/components/calendar-grid/calendar-grid.d.ts +2 -2
  19. package/components/card/card.css +21 -0
  20. package/components/card/card.yaml +9 -0
  21. package/components/chart/chart.d.ts +4 -4
  22. package/components/chart-legend/chart-legend.d.ts +1 -1
  23. package/components/chat-thread/chat-input.css +1 -1
  24. package/components/chat-thread/chat-thread.d.ts +2 -2
  25. package/components/check/check.css +3 -3
  26. package/components/command/command.d.ts +2 -2
  27. package/components/context-menu/context-menu.d.ts +3 -3
  28. package/components/datetime-picker/datetime-picker.d.ts +5 -5
  29. package/components/demo-toggle/demo-toggle.d.ts +1 -1
  30. package/components/drawer/drawer.d.ts +2 -2
  31. package/components/heatmap/heatmap.d.ts +5 -5
  32. package/components/index.js +1 -0
  33. package/components/inline-edit/inline-edit.d.ts +4 -4
  34. package/components/input/input.a2ui.json +4 -1
  35. package/components/input/input.css +18 -0
  36. package/components/input/input.yaml +11 -1
  37. package/components/integration-card/integration-card.d.ts +4 -4
  38. package/components/link/link.d.ts +1 -1
  39. package/components/list/list.class.js +22 -7
  40. package/components/list/list.d.ts +1 -1
  41. package/components/list-window/list-window.d.ts +5 -5
  42. package/components/menu/menu.css +2 -2
  43. package/components/menu/menu.d.ts +1 -1
  44. package/components/modal/modal.d.ts +1 -1
  45. package/components/nav/nav.css +8 -0
  46. package/components/nav/nav.d.ts +1 -1
  47. package/components/nav-group/nav-group.css +58 -9
  48. package/components/nav-group/nav-group.d.ts +1 -1
  49. package/components/nav-item/nav-item.css +7 -4
  50. package/components/nav-item/nav-item.d.ts +1 -1
  51. package/components/noodles/noodles.d.ts +3 -3
  52. package/components/option-card/option-card.css +1 -1
  53. package/components/pagination/pagination.d.ts +1 -1
  54. package/components/pane/pane.d.ts +1 -1
  55. package/components/password-strength/password-strength.d.ts +1 -1
  56. package/components/radio/radio.a2ui.json +4 -3
  57. package/components/radio/radio.css +2 -2
  58. package/components/radio/radio.yaml +7 -14
  59. package/components/radio-group/radio-group.a2ui.json +118 -0
  60. package/components/radio-group/radio-group.class.js +75 -0
  61. package/components/radio-group/radio-group.css +31 -0
  62. package/components/radio-group/radio-group.d.ts +20 -0
  63. package/components/radio-group/radio-group.examples.md +37 -0
  64. package/components/radio-group/radio-group.js +17 -0
  65. package/components/radio-group/radio-group.yaml +114 -0
  66. package/components/stat/stat.css +12 -8
  67. package/components/stat/stat.js +66 -1
  68. package/components/stat/stat.yaml +3 -0
  69. package/components/stepper/stepper-item.a2ui.json +5 -0
  70. package/components/stepper/stepper-item.yaml +11 -0
  71. package/components/stepper/stepper.a2ui.json +38 -3
  72. package/components/stepper/stepper.class.js +199 -2
  73. package/components/stepper/stepper.css +30 -0
  74. package/components/stepper/stepper.d.ts +28 -1
  75. package/components/stepper/stepper.yaml +76 -2
  76. package/components/stream/stream.d.ts +3 -3
  77. package/components/swatch/swatch.d.ts +1 -1
  78. package/components/swiper/swiper.d.ts +3 -3
  79. package/components/table/table.a2ui.json +2 -2
  80. package/components/table/table.d.ts +7 -7
  81. package/components/table/table.yaml +9 -1
  82. package/components/table-toolbar/table-toolbar.d.ts +4 -4
  83. package/components/tabs/tabs.d.ts +1 -1
  84. package/components/tag/tag.a2ui.json +1 -1
  85. package/components/tag/tag.css +78 -13
  86. package/components/tag/tag.d.ts +12 -8
  87. package/components/tag/tag.examples.md +4 -4
  88. package/components/tag/tag.yaml +18 -7
  89. package/components/tags-input/tags-input.d.ts +4 -4
  90. package/components/timeline/timeline.d.ts +1 -1
  91. package/components/toc/toc.d.ts +1 -1
  92. package/components/toggle-group/toggle-group.d.ts +1 -1
  93. package/components/toggle-scheme/toggle-scheme.d.ts +1 -1
  94. package/components/tour/tour.d.ts +4 -4
  95. package/components/tree/tree.d.ts +1 -1
  96. package/core/icons-phosphor.js +129 -71
  97. package/core/provider.js +84 -23
  98. package/dist/host.min.css +1 -1
  99. package/dist/host.sheet.js +1 -1
  100. package/dist/theme-provider.min.js +1 -1
  101. package/dist/web-components.min.css +1 -1
  102. package/dist/web-components.min.js +89 -87
  103. package/dist/web-components.sheet.js +1 -1
  104. package/package.json +1 -1
  105. package/styles/api/sizing-fallback.css +32 -0
  106. package/styles/api/sizing.css +7 -3
  107. package/styles/components.css +1 -0
  108. package/styles/tokens.css +1 -0
@@ -84,8 +84,8 @@
84
84
  },
85
85
  "examples": [
86
86
  {
87
- "description": "Card with radio group for plan selection: Basic, Pro, and Enterprise tiers.",
88
- "a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Card\",\n \"children\": [\n \"hdr\",\n \"sec\",\n \"ftr\"\n ]\n },\n {\n \"id\": \"hdr\",\n \"component\": \"Header\",\n \"children\": [\n \"title\",\n \"desc\"\n ]\n },\n {\n \"id\": \"title\",\n \"component\": \"Text\",\n \"slot\": \"heading\",\n \"textContent\": \"Choose a Plan\",\n \"variant\": \"section\"\n },\n {\n \"id\": \"desc\",\n \"component\": \"Text\",\n \"slot\": \"description\",\n \"variant\": \"caption\",\n \"textContent\": \"Select the plan that works best for you\"\n },\n {\n \"id\": \"sec\",\n \"component\": \"Section\",\n \"children\": [\n \"options\"\n ]\n },\n {\n \"id\": \"options\",\n \"component\": \"Column\",\n \"children\": [\n \"basic\",\n \"pro\",\n \"enterprise\"\n ],\n \"gap\": \"3\"\n },\n {\n \"id\": \"basic\",\n \"component\": \"Radio\",\n \"label\": \"Basic — $9/mo\",\n \"value\": \"basic\",\n \"name\": \"plan\"\n },\n {\n \"id\": \"pro\",\n \"component\": \"Radio\",\n \"label\": \"Pro — $29/mo\",\n \"value\": \"pro\",\n \"name\": \"plan\"\n },\n {\n \"id\": \"enterprise\",\n \"component\": \"Radio\",\n \"label\": \"Enterprise — $99/mo\",\n \"value\": \"enterprise\",\n \"name\": \"plan\"\n },\n {\n \"id\": \"ftr\",\n \"component\": \"Footer\",\n \"children\": [\n \"confirm-btn\"\n ]\n },\n {\n \"id\": \"confirm-btn\",\n \"component\": \"Button\",\n \"text\": \"Confirm Plan\",\n \"variant\": \"primary\",\n \"block\": true,\n \"slot\": \"action\"\n }\n]",
87
+ "description": "Card with radio group for plan selection: Basic, Pro, and Enterprise tiers. The options are wrapped in radio-group-ui (gh#729) — its own [label] IS the group's accessible name (role=radiogroup + aria-labelledby), not a sibling Text node.",
88
+ "a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Card\",\n \"children\": [\n \"hdr\",\n \"sec\",\n \"ftr\"\n ]\n },\n {\n \"id\": \"hdr\",\n \"component\": \"Header\",\n \"children\": [\n \"title\"\n ]\n },\n {\n \"id\": \"title\",\n \"component\": \"Text\",\n \"slot\": \"heading\",\n \"textContent\": \"Choose a Plan\",\n \"variant\": \"section\"\n },\n {\n \"id\": \"sec\",\n \"component\": \"Section\",\n \"children\": [\n \"options\"\n ]\n },\n {\n \"id\": \"options\",\n \"component\": \"RadioGroup\",\n \"label\": \"Select the plan that works best for you\",\n \"children\": [\n \"basic\",\n \"pro\",\n \"enterprise\"\n ]\n },\n {\n \"id\": \"basic\",\n \"component\": \"Radio\",\n \"label\": \"Basic — $9/mo\",\n \"value\": \"basic\",\n \"name\": \"plan\"\n },\n {\n \"id\": \"pro\",\n \"component\": \"Radio\",\n \"label\": \"Pro — $29/mo\",\n \"value\": \"pro\",\n \"name\": \"plan\"\n },\n {\n \"id\": \"enterprise\",\n \"component\": \"Radio\",\n \"label\": \"Enterprise — $99/mo\",\n \"value\": \"enterprise\",\n \"name\": \"plan\"\n },\n {\n \"id\": \"ftr\",\n \"component\": \"Footer\",\n \"children\": [\n \"confirm-btn\"\n ]\n },\n {\n \"id\": \"confirm-btn\",\n \"component\": \"Button\",\n \"text\": \"Confirm Plan\",\n \"variant\": \"primary\",\n \"block\": true,\n \"slot\": \"action\"\n }\n]",
89
89
  "name": "radio-group"
90
90
  }
91
91
  ],
@@ -102,7 +102,8 @@
102
102
  ],
103
103
  "name": "UIRadio",
104
104
  "related": [
105
- "button"
105
+ "button",
106
+ "radio-group"
106
107
  ],
107
108
  "slots": {
108
109
  "dot": {
@@ -2,11 +2,11 @@
2
2
  `:scope[checked] [descendant]` (Flavor B — attribute-removal restyle)
3
3
  both fail inside `@scope`. Selectors moved out. See
4
4
  docs/BROWSER-COMPAT.md §3a. */
5
- radio-ui:not([disabled]):hover [slot="dot"] {
5
+ radio-ui:not([disabled]):hover:not(:focus-visible) [slot="dot"] {
6
6
  border-color: var(--radio-border-hover);
7
7
  background: var(--radio-bg-hover);
8
8
  }
9
- radio-ui[checked]:not([disabled]):hover [slot="dot"] {
9
+ radio-ui[checked]:not([disabled]):hover:not(:focus-visible) [slot="dot"] {
10
10
  background: var(--radio-bg-checked-hover);
11
11
  border-color: var(--radio-bg-checked-hover);
12
12
  }
@@ -111,7 +111,7 @@ tokens:
111
111
  description: Override transition timing
112
112
  a2ui:
113
113
  rules:
114
- - "Self-labeling widget — use the [label] attribute directly; do NOT wrap in <field-ui>. The widget renders its own label inline via CSS attr() pattern. For radio groups, the canonical pattern is a column of bare <radio-ui label='…'> elements sharing a [name=] — no field-ui wrapper around each radio."
114
+ - "Self-labeling widget — use the [label] attribute directly; do NOT wrap in <field-ui>. The widget renders its own label inline via CSS attr() pattern. For radio groups, the canonical pattern is <radio-group-ui label='…'> wrapping bare <radio-ui label='…'> elements sharing a [name=] — radio-group-ui supplies the group's accessible name (role=radiogroup + aria-labelledby); a bare Column of radios cannot (gh#729)."
115
115
  - rule: 'Use the [name] attribute to group radios — exactly one is selected per name group; form-associated.'
116
116
  reason: 'Standard radio-group semantics; name= is the group key.'
117
117
  - rule: 'For button-style single-select clusters (visually richer) use <segmented-ui> + <segment-ui> instead.'
@@ -127,7 +127,7 @@ anti_patterns:
127
127
  rule: Use [label] on radio-ui directly; do not wrap in field-ui.
128
128
  examples:
129
129
  - name: radio-group
130
- description: "Card with radio group for plan selection: Basic, Pro, and Enterprise tiers."
130
+ description: "Card with radio group for plan selection: Basic, Pro, and Enterprise tiers. The options are wrapped in radio-group-ui (gh#729) — its own [label] IS the group's accessible name (role=radiogroup + aria-labelledby), not a sibling Text node."
131
131
  a2ui: >-
132
132
  [
133
133
  {
@@ -143,8 +143,7 @@ examples:
143
143
  "id": "hdr",
144
144
  "component": "Header",
145
145
  "children": [
146
- "title",
147
- "desc"
146
+ "title"
148
147
  ]
149
148
  },
150
149
  {
@@ -154,13 +153,6 @@ examples:
154
153
  "textContent": "Choose a Plan",
155
154
  "variant": "section"
156
155
  },
157
- {
158
- "id": "desc",
159
- "component": "Text",
160
- "slot": "description",
161
- "variant": "caption",
162
- "textContent": "Select the plan that works best for you"
163
- },
164
156
  {
165
157
  "id": "sec",
166
158
  "component": "Section",
@@ -170,13 +162,13 @@ examples:
170
162
  },
171
163
  {
172
164
  "id": "options",
173
- "component": "Column",
165
+ "component": "RadioGroup",
166
+ "label": "Select the plan that works best for you",
174
167
  "children": [
175
168
  "basic",
176
169
  "pro",
177
170
  "enterprise"
178
- ],
179
- "gap": "3"
171
+ ]
180
172
  },
181
173
  {
182
174
  "id": "basic",
@@ -262,3 +254,4 @@ synonyms:
262
254
  - settings
263
255
  related:
264
256
  - button
257
+ - radio-group
@@ -0,0 +1,118 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/RadioGroup.json",
4
+ "title": "RadioGroup",
5
+ "description": "Groups a set of bare <radio-ui label=\"…\"> children under one accessible name — role=\"radiogroup\" on the host plus aria-labelledby pointing at its own [label]. Radio-ui already supports ancestor fieldset/[role=radiogroup] grouping for exclusivity + arrow-key navigation (unchanged by this wrapper); a plain layout container (Column) can carry neither the role nor the label association, which left the canonical questionnaire shape (radio.yaml's own example) with no programmatic question↔group link (gh#729). Use this wherever a question/prompt introduces a set of mutually-exclusive radio-ui options — it is the ONLY primitive that carries the group's accessible name; radio-ui itself stays self-labeling per-option.",
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
+ "required": {
17
+ "description": "Marks the group as requiring a selection for form validation (visual + aria-required only — the constraint itself lives on each radio-ui's own [required]).",
18
+ "type": "boolean",
19
+ "default": false
20
+ },
21
+ "component": {
22
+ "const": "RadioGroup"
23
+ },
24
+ "label": {
25
+ "description": "The question or prompt text — rendered above the options and wired to the host via aria-labelledby. This is the group's accessible name; without it the group has none.",
26
+ "type": "string",
27
+ "default": ""
28
+ }
29
+ },
30
+ "required": [
31
+ "component",
32
+ "label"
33
+ ],
34
+ "unevaluatedProperties": false,
35
+ "x-adiaui": {
36
+ "anti_patterns": [
37
+ {
38
+ "description": "Wrapping radio-ui options in a bare Column/Section with the question as a sibling Text node instead of radio-group-ui's [label].",
39
+ "right": "<radio-group-ui label=\"How often do you exercise?\">\n <radio-ui label=\"Never\" name=\"exercise\"></radio-ui>\n <radio-ui label=\"Sometimes\" name=\"exercise\"></radio-ui>\n</radio-group-ui>\n",
40
+ "rule": "The question that introduces a radio-ui set belongs in radio-group-ui's [label], not a sibling Text node.",
41
+ "wrong": "<column>\n <text>How often do you exercise?</text>\n <radio-ui label=\"Never\" name=\"exercise\"></radio-ui>\n <radio-ui label=\"Sometimes\" name=\"exercise\"></radio-ui>\n</column>\n"
42
+ }
43
+ ],
44
+ "category": "input",
45
+ "composes": [],
46
+ "events": {},
47
+ "examples": [
48
+ {
49
+ "description": "A single scored-questionnaire item: the question as the group's accessible label, three bare radio-ui options sharing one [name=].",
50
+ "a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"RadioGroup\",\n \"label\": \"Little interest or pleasure in doing things?\",\n \"children\": [\"opt-0\", \"opt-1\", \"opt-2\"]\n },\n { \"id\": \"opt-0\", \"component\": \"Radio\", \"label\": \"Not at all\", \"name\": \"phq2-1\", \"value\": \"0\" },\n { \"id\": \"opt-1\", \"component\": \"Radio\", \"label\": \"Several days\", \"name\": \"phq2-1\", \"value\": \"1\" },\n { \"id\": \"opt-2\", \"component\": \"Radio\", \"label\": \"More than half the days\", \"name\": \"phq2-1\", \"value\": \"2\" }\n]",
51
+ "name": "questionnaire-item"
52
+ }
53
+ ],
54
+ "keywords": [
55
+ "radio",
56
+ "radiogroup",
57
+ "fieldset",
58
+ "legend",
59
+ "questionnaire",
60
+ "survey",
61
+ "poll",
62
+ "accessibility",
63
+ "aria-labelledby"
64
+ ],
65
+ "name": "UIRadioGroup",
66
+ "related": [
67
+ "radio",
68
+ "field"
69
+ ],
70
+ "slots": {
71
+ "default": {
72
+ "description": "radio-ui children sharing one [name=] — exclusivity and arrow-key navigation are unchanged; they still resolve via closest('fieldset, [role=radiogroup]'), which now matches this host."
73
+ },
74
+ "label": {
75
+ "description": "Auto-minted label element (mounted by radio-group-ui from the [label] prop) — do not author this slot directly."
76
+ }
77
+ },
78
+ "states": [
79
+ {
80
+ "description": "Default, ready for interaction.",
81
+ "name": "idle"
82
+ }
83
+ ],
84
+ "status": "stable",
85
+ "synonyms": {
86
+ "poll": [
87
+ "radio-group",
88
+ "questionnaire"
89
+ ],
90
+ "questionnaire": [
91
+ "radio-group",
92
+ "form",
93
+ "survey"
94
+ ],
95
+ "survey": [
96
+ "radio-group",
97
+ "questionnaire"
98
+ ]
99
+ },
100
+ "tag": "radio-group-ui",
101
+ "tokens": {
102
+ "--radio-group-gap": {
103
+ "description": "Gap between the label and the option stack, and between each option."
104
+ },
105
+ "--radio-group-label-color": {
106
+ "description": "Override the question/prompt label color."
107
+ },
108
+ "--radio-group-label-font-size": {
109
+ "description": "Override the question/prompt label font size."
110
+ },
111
+ "--radio-group-label-font-weight": {
112
+ "description": "Override the question/prompt label font weight."
113
+ }
114
+ },
115
+ "traits": [],
116
+ "version": 1
117
+ }
118
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Non-side-effect class export for `<radio-group-ui>`.
3
+ *
4
+ * Importing this file gives you the class(es) without auto-registering the tag.
5
+ * Useful for test isolation, subclassing with tag-name override, or selective
6
+ * composition.
7
+ *
8
+ * The auto-register path stays at `@adia-ai/web-components/components/radio-group`
9
+ * (which imports this file + calls `defineIfFree()`).
10
+ *
11
+ * @see ../../USAGE.md#registration--auto-vs-explicit
12
+ */
13
+
14
+ /**
15
+ * <radio-group-ui label="Choose a plan">
16
+ * <radio-ui label="Basic" name="plan" value="basic"></radio-ui>
17
+ * <radio-ui label="Pro" name="plan" value="pro"></radio-ui>
18
+ * </radio-group-ui>
19
+ *
20
+ * Programmatic question↔group association for a set of bare radio-ui
21
+ * options (gh#729). role="radiogroup" on the host + aria-labelledby
22
+ * pointing at an auto-minted label element rendered from [label] — the
23
+ * ONLY job this component does. Exclusivity + arrow-key navigation are
24
+ * unchanged: radio-ui's own #select/#onKey already resolve their group
25
+ * via `closest('fieldset, [role="radiogroup"]')` (radio.class.js:48),
26
+ * which now matches this host instead of falling through to
27
+ * `this.parentElement`.
28
+ *
29
+ * Not a form-associated element itself — it carries no value; each child
30
+ * radio-ui is its own form participant per the existing [name=] contract.
31
+ */
32
+ import { UIElement } from '../../core/element.js';
33
+
34
+ export class UIRadioGroup extends UIElement {
35
+ static properties = {
36
+ label: { type: String, default: '', reflect: true },
37
+ required: { type: Boolean, default: false, reflect: true },
38
+ };
39
+
40
+ static template = () => null;
41
+
42
+ #labelEl = null;
43
+
44
+ connected() {
45
+ this.setAttribute('role', 'radiogroup');
46
+ this.#ensureLabelElement();
47
+ if (this.#labelEl?.id) this.setAttribute('aria-labelledby', this.#labelEl.id);
48
+ }
49
+
50
+ disconnected() {
51
+ this.#labelEl = null;
52
+ }
53
+
54
+ render() {
55
+ if (this.#labelEl) this.#labelEl.textContent = this.label || '';
56
+ if (this.required) this.setAttribute('aria-required', 'true');
57
+ else this.removeAttribute('aria-required');
58
+ }
59
+
60
+ #ensureLabelElement() {
61
+ this.#labelEl = this.querySelector(':scope > [data-radio-group-label]');
62
+ if (!this.#labelEl) {
63
+ const el = document.createElement('div');
64
+ el.setAttribute('slot', 'label');
65
+ el.setAttribute('data-radio-group-label', '');
66
+ el.id = UIRadioGroup.#nextId();
67
+ el.textContent = this.label || '';
68
+ this.prepend(el);
69
+ this.#labelEl = el;
70
+ }
71
+ }
72
+
73
+ static #idSeq = 0;
74
+ static #nextId() { return `radio-group-label-${++UIRadioGroup.#idSeq}`; }
75
+ }
@@ -0,0 +1,31 @@
1
+ @scope (radio-group-ui) {
2
+ :where(:scope) {
3
+ /* ── Tokens ── */
4
+ --radio-group-gap: var(--a-space-2);
5
+ --radio-group-label-color: var(--md-sys-color-neutral-on-surface);
6
+ --radio-group-label-font-size: var(--a-ui-md);
7
+ --radio-group-label-font-weight: var(--a-weight-medium);
8
+ }
9
+
10
+ /* ── Base — vertical stack: label row, then each radio-ui option. ── */
11
+ :scope {
12
+ box-sizing: border-box;
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: var(--radio-group-gap);
16
+ }
17
+
18
+ :scope [slot="label"] {
19
+ color: var(--radio-group-label-color);
20
+ font-size: var(--radio-group-label-font-size);
21
+ font-weight: var(--radio-group-label-font-weight);
22
+ }
23
+
24
+ /* No accessible name set → hide the (empty) label row rather than
25
+ reserve blank space. label is required in practice (radio-group-ui's
26
+ entire purpose is carrying it); this only guards the transient/
27
+ misuse case. */
28
+ :scope:not([label]) [slot="label"] {
29
+ display: none;
30
+ }
31
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `<radio-group-ui>` — Groups a set of bare <radio-ui label="…"> children under one accessible name — role="radiogroup" on the host plus aria-labelledby pointing at its own [label]. Radio-ui already supports ancestor fieldset/[role=radiogroup] grouping for exclusivity + arrow-key navigation (unchanged by this wrapper); a plain layout container (Column) can carry neither the role nor the label association, which left the canonical questionnaire shape (radio.yaml's own example) with no programmatic question↔group link (gh#729). Use this wherever a question/prompt introduces a set of mutually-exclusive radio-ui options — it is the ONLY primitive that carries the group's accessible name; radio-ui itself stays self-labeling per-option.
3
+ *
4
+ * @see https://ui-kit.exe.xyz/site/components/radio-group
5
+ *
6
+ * Type declarations generated by scripts/build/dts-codegen.mjs from
7
+ * the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
8
+ * run `npm run build:components`, then `npm run codegen:dts` to
9
+ * regenerate; or hand-author this file fully if rich event types are
10
+ * needed beyond what the yaml `events:` block can express.
11
+ */
12
+
13
+ import { UIElement } from '../../core/element.js';
14
+
15
+ export class UIRadioGroup extends UIElement {
16
+ /** Marks the group as requiring a selection for form validation (visual + aria-required only — the constraint itself lives on each radio-ui's own [required]). */
17
+ required: boolean;
18
+ /** The question or prompt text — rendered above the options and wired to the host via aria-labelledby. This is the group's accessible name; without it the group has none. */
19
+ label: string;
20
+ }
@@ -0,0 +1,37 @@
1
+ # radio-group — Examples
2
+
3
+ ## label
4
+
5
+ ```html
6
+ <radio-group-ui label="Little interest or pleasure in doing things?">
7
+ <radio-ui label="Not at all" name="rg-demo-phq" value="0" checked></radio-ui>
8
+ <radio-ui label="Several days" name="rg-demo-phq" value="1"></radio-ui>
9
+ <radio-ui label="More than half the days" name="rg-demo-phq" value="2"></radio-ui>
10
+ </radio-group-ui>
11
+ ```
12
+
13
+ ## required
14
+
15
+ ```html
16
+ <radio-group-ui label="How would you rate your overall health?" required>
17
+ <radio-ui label="Excellent" name="rg-demo-health" value="excellent" required></radio-ui>
18
+ <radio-ui label="Good" name="rg-demo-health" value="good" required></radio-ui>
19
+ <radio-ui label="Fair" name="rg-demo-health" value="fair" required></radio-ui>
20
+ <radio-ui label="Poor" name="rg-demo-health" value="poor" required></radio-ui>
21
+ </radio-group-ui>
22
+ ```
23
+
24
+ ## Combinations
25
+
26
+ ```html
27
+ <col-ui gap="5">
28
+ <radio-group-ui label="Little interest or pleasure in doing things?">
29
+ <radio-ui label="Not at all" name="rg-demo-multi-1" value="0" checked></radio-ui>
30
+ <radio-ui label="Several days" name="rg-demo-multi-1" value="1"></radio-ui>
31
+ </radio-group-ui>
32
+ <radio-group-ui label="Feeling down, depressed, or hopeless?">
33
+ <radio-ui label="Not at all" name="rg-demo-multi-2" value="0"></radio-ui>
34
+ <radio-ui label="Several days" name="rg-demo-multi-2" value="1" checked></radio-ui>
35
+ </radio-group-ui>
36
+ </col-ui>
37
+ ```
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `<radio-group-ui>` — auto-registers the tag on import.
3
+ *
4
+ * For non-side-effect class import (test isolation, tag override), use
5
+ * the `class` subpath:
6
+ *
7
+ * import { UIRadioGroup } from '@adia-ai/web-components/components/radio-group/class';
8
+ *
9
+ * @see ../../USAGE.md#registration--auto-vs-explicit
10
+ */
11
+
12
+ import { defineIfFree } from '../../core/register.js';
13
+ import { UIRadioGroup } from './radio-group.class.js';
14
+
15
+ defineIfFree('radio-group-ui', UIRadioGroup);
16
+
17
+ export { UIRadioGroup };
@@ -0,0 +1,114 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
3
+ name: UIRadioGroup
4
+ tag: radio-group-ui
5
+ status: stable
6
+ component: RadioGroup
7
+ category: input
8
+ version: 1
9
+ description: >-
10
+ Groups a set of bare <radio-ui label="…"> children under one accessible
11
+ name — role="radiogroup" on the host plus aria-labelledby pointing at
12
+ its own [label]. Radio-ui already supports ancestor fieldset/[role=radiogroup]
13
+ grouping for exclusivity + arrow-key navigation (unchanged by this
14
+ wrapper); a plain layout container (Column) can carry neither the role
15
+ nor the label association, which left the canonical questionnaire shape
16
+ (radio.yaml's own example) with no programmatic question↔group link
17
+ (gh#729). Use this wherever a question/prompt introduces a set of
18
+ mutually-exclusive radio-ui options — it is the ONLY primitive that
19
+ carries the group's accessible name; radio-ui itself stays self-labeling
20
+ per-option.
21
+ props:
22
+ label:
23
+ description: >-
24
+ The question or prompt text — rendered above the options and wired
25
+ to the host via aria-labelledby. This is the group's accessible
26
+ name; without it the group has none.
27
+ required: true
28
+ type: string
29
+ default: ""
30
+ reflect: true
31
+ required:
32
+ description: Marks the group as requiring a selection for form validation (visual + aria-required only — the constraint itself lives on each radio-ui's own [required]).
33
+ type: boolean
34
+ default: false
35
+ reflect: true
36
+ events: {}
37
+ slots:
38
+ default:
39
+ description: "radio-ui children sharing one [name=] — exclusivity and arrow-key navigation are unchanged; they still resolve via closest('fieldset, [role=radiogroup]'), which now matches this host."
40
+ label:
41
+ description: Auto-minted label element (mounted by radio-group-ui from the [label] prop) — do not author this slot directly.
42
+ states:
43
+ - name: idle
44
+ description: Default, ready for interaction.
45
+ traits: []
46
+ tokens:
47
+ --radio-group-gap:
48
+ description: Gap between the label and the option stack, and between each option.
49
+ --radio-group-label-color:
50
+ description: Override the question/prompt label color.
51
+ --radio-group-label-font-size:
52
+ description: Override the question/prompt label font size.
53
+ --radio-group-label-font-weight:
54
+ description: Override the question/prompt label font weight.
55
+ a2ui:
56
+ rules:
57
+ - "The canonical radio-group shape is <radio-group-ui label=\"…\"> wrapping a set of bare <radio-ui label=\"…\" name=\"…\"> children — the wrapper supplies the group's accessible name (role=radiogroup + aria-labelledby); Column/Section cannot carry a role, so a bare Column of radios has no programmatic question↔group association."
58
+ - rule: "Do not put the question text as a sibling Text node before a Column of radios — put it in radio-group-ui's own [label] prop instead."
59
+ reason: "A sibling Text has no aria relationship to the radios; radio-group-ui's [label] is wired via aria-labelledby, which a sibling Text is not."
60
+ - rule: "radio-ui children keep their own [label] per-option; radio-group-ui's [label] is the shared question, not a per-option label."
61
+ reason: "Two different accessible names at two different levels — the group's name and each option's name — both need to reach an AT user."
62
+ anti_patterns:
63
+ - description: Wrapping radio-ui options in a bare Column/Section with the question as a sibling Text node instead of radio-group-ui's [label].
64
+ wrong: |
65
+ <column>
66
+ <text>How often do you exercise?</text>
67
+ <radio-ui label="Never" name="exercise"></radio-ui>
68
+ <radio-ui label="Sometimes" name="exercise"></radio-ui>
69
+ </column>
70
+ right: |
71
+ <radio-group-ui label="How often do you exercise?">
72
+ <radio-ui label="Never" name="exercise"></radio-ui>
73
+ <radio-ui label="Sometimes" name="exercise"></radio-ui>
74
+ </radio-group-ui>
75
+ rule: The question that introduces a radio-ui set belongs in radio-group-ui's [label], not a sibling Text node.
76
+ examples:
77
+ - name: questionnaire-item
78
+ description: "A single scored-questionnaire item: the question as the group's accessible label, three bare radio-ui options sharing one [name=]."
79
+ a2ui: >-
80
+ [
81
+ {
82
+ "id": "root",
83
+ "component": "RadioGroup",
84
+ "label": "Little interest or pleasure in doing things?",
85
+ "children": ["opt-0", "opt-1", "opt-2"]
86
+ },
87
+ { "id": "opt-0", "component": "Radio", "label": "Not at all", "name": "phq2-1", "value": "0" },
88
+ { "id": "opt-1", "component": "Radio", "label": "Several days", "name": "phq2-1", "value": "1" },
89
+ { "id": "opt-2", "component": "Radio", "label": "More than half the days", "name": "phq2-1", "value": "2" }
90
+ ]
91
+ keywords:
92
+ - radio
93
+ - radiogroup
94
+ - fieldset
95
+ - legend
96
+ - questionnaire
97
+ - survey
98
+ - poll
99
+ - accessibility
100
+ - aria-labelledby
101
+ synonyms:
102
+ questionnaire:
103
+ - radio-group
104
+ - form
105
+ - survey
106
+ survey:
107
+ - radio-group
108
+ - questionnaire
109
+ poll:
110
+ - radio-group
111
+ - questionnaire
112
+ related:
113
+ - radio
114
+ - field
@@ -202,19 +202,23 @@
202
202
  white-space: nowrap;
203
203
  }
204
204
 
205
- /* Trend arrows via ::before */
206
- :scope[trend="up"] [slot="change"]::before {
207
- content: "\25B2";
208
- color: var(--stat-up-fg);
205
+ /* Trend arrow — Phosphor icon-ui, colored via currentColor from the
206
+ [trend] color rules below (issue 805; was a CSS ::before unicode
207
+ triangle). Sized relative to the change text so it scales with
208
+ --stat-change-size instead of a fixed px value. No slot="icon"
209
+ attribute — that value is reserved for stat's own [slot="icon"]
210
+ region; an ancestor's `[slot="icon"]` descendant rule would otherwise
211
+ leak margin/positioning onto this nested icon-ui (issue 809, PR 810
212
+ shape). Spacing from the text comes from [slot="change"]'s own
213
+ `gap`, not margin, for the same reason. */
214
+ [slot="change"] icon-ui[data-trend-icon] {
215
+ --a-icon-size: 1em;
209
216
  }
217
+
210
218
  :scope[trend="up"] [slot="change"] {
211
219
  color: var(--stat-up-fg);
212
220
  }
213
221
 
214
- :scope[trend="down"] [slot="change"]::before {
215
- content: "\25BC";
216
- color: var(--stat-down-fg);
217
- }
218
222
  :scope[trend="down"] [slot="change"] {
219
223
  color: var(--stat-down-fg);
220
224
  }