@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.
- package/CHANGELOG.md +39 -0
- package/components/accordion/accordion.d.ts +2 -2
- package/components/action-list/action-list.class.js +6 -2
- package/components/action-list/action-list.d.ts +1 -1
- package/components/agent-artifact/agent-artifact.d.ts +1 -1
- package/components/agent-feedback-bar/agent-feedback-bar.d.ts +2 -2
- package/components/agent-questions/agent-questions.d.ts +1 -1
- package/components/agent-reasoning/agent-reasoning.d.ts +3 -3
- package/components/agent-suggestions/agent-suggestions.d.ts +1 -1
- package/components/agent-trace/agent-trace.d.ts +1 -1
- package/components/alert/alert.d.ts +2 -2
- package/components/anchor-bar/anchor-bar.d.ts +2 -2
- package/components/avatar/avatar.class.js +0 -1
- package/components/avatar/avatar.css +16 -2
- package/components/badge/badge.css +30 -2
- package/components/breadcrumb/breadcrumb.css +15 -3
- package/components/button/button.d.ts +1 -1
- package/components/calendar-grid/calendar-grid.d.ts +2 -2
- package/components/card/card.css +21 -0
- package/components/card/card.yaml +9 -0
- package/components/chart/chart.d.ts +4 -4
- package/components/chart-legend/chart-legend.d.ts +1 -1
- package/components/chat-thread/chat-input.css +1 -1
- package/components/chat-thread/chat-thread.d.ts +2 -2
- package/components/check/check.css +3 -3
- package/components/command/command.d.ts +2 -2
- package/components/context-menu/context-menu.d.ts +3 -3
- package/components/datetime-picker/datetime-picker.d.ts +5 -5
- package/components/demo-toggle/demo-toggle.d.ts +1 -1
- package/components/drawer/drawer.d.ts +2 -2
- package/components/heatmap/heatmap.d.ts +5 -5
- package/components/index.js +1 -0
- package/components/inline-edit/inline-edit.d.ts +4 -4
- package/components/input/input.a2ui.json +4 -1
- package/components/input/input.css +18 -0
- package/components/input/input.yaml +11 -1
- package/components/integration-card/integration-card.d.ts +4 -4
- package/components/link/link.d.ts +1 -1
- package/components/list/list.class.js +22 -7
- package/components/list/list.d.ts +1 -1
- package/components/list-window/list-window.d.ts +5 -5
- package/components/menu/menu.css +2 -2
- package/components/menu/menu.d.ts +1 -1
- package/components/modal/modal.d.ts +1 -1
- package/components/nav/nav.css +8 -0
- package/components/nav/nav.d.ts +1 -1
- package/components/nav-group/nav-group.css +58 -9
- package/components/nav-group/nav-group.d.ts +1 -1
- package/components/nav-item/nav-item.css +7 -4
- package/components/nav-item/nav-item.d.ts +1 -1
- package/components/noodles/noodles.d.ts +3 -3
- package/components/option-card/option-card.css +1 -1
- package/components/pagination/pagination.d.ts +1 -1
- package/components/pane/pane.d.ts +1 -1
- package/components/password-strength/password-strength.d.ts +1 -1
- package/components/radio/radio.a2ui.json +4 -3
- package/components/radio/radio.css +2 -2
- package/components/radio/radio.yaml +7 -14
- package/components/radio-group/radio-group.a2ui.json +118 -0
- package/components/radio-group/radio-group.class.js +75 -0
- package/components/radio-group/radio-group.css +31 -0
- package/components/radio-group/radio-group.d.ts +20 -0
- package/components/radio-group/radio-group.examples.md +37 -0
- package/components/radio-group/radio-group.js +17 -0
- package/components/radio-group/radio-group.yaml +114 -0
- package/components/stat/stat.css +12 -8
- package/components/stat/stat.js +66 -1
- package/components/stat/stat.yaml +3 -0
- package/components/stepper/stepper-item.a2ui.json +5 -0
- package/components/stepper/stepper-item.yaml +11 -0
- package/components/stepper/stepper.a2ui.json +38 -3
- package/components/stepper/stepper.class.js +199 -2
- package/components/stepper/stepper.css +30 -0
- package/components/stepper/stepper.d.ts +28 -1
- package/components/stepper/stepper.yaml +76 -2
- package/components/stream/stream.d.ts +3 -3
- package/components/swatch/swatch.d.ts +1 -1
- package/components/swiper/swiper.d.ts +3 -3
- package/components/table/table.a2ui.json +2 -2
- package/components/table/table.d.ts +7 -7
- package/components/table/table.yaml +9 -1
- package/components/table-toolbar/table-toolbar.d.ts +4 -4
- package/components/tabs/tabs.d.ts +1 -1
- package/components/tag/tag.a2ui.json +1 -1
- package/components/tag/tag.css +78 -13
- package/components/tag/tag.d.ts +12 -8
- package/components/tag/tag.examples.md +4 -4
- package/components/tag/tag.yaml +18 -7
- package/components/tags-input/tags-input.d.ts +4 -4
- package/components/timeline/timeline.d.ts +1 -1
- package/components/toc/toc.d.ts +1 -1
- package/components/toggle-group/toggle-group.d.ts +1 -1
- package/components/toggle-scheme/toggle-scheme.d.ts +1 -1
- package/components/tour/tour.d.ts +4 -4
- package/components/tree/tree.d.ts +1 -1
- package/core/icons-phosphor.js +129 -71
- package/core/provider.js +84 -23
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +89 -87
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/styles/api/sizing-fallback.css +32 -0
- package/styles/api/sizing.css +7 -3
- package/styles/components.css +1 -0
- 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\"
|
|
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
|
|
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": "
|
|
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
|
package/components/stat/stat.css
CHANGED
|
@@ -202,19 +202,23 @@
|
|
|
202
202
|
white-space: nowrap;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
/* Trend
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
}
|