@adia-ai/web-components 0.8.30 → 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 +59 -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-input.js +9 -1
- package/components/chat-thread/chat-thread.class.js +6 -0
- 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-item.a2ui.json +6 -0
- package/components/list/list-item.yaml +13 -0
- package/components/list/list.class.js +28 -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/pipeline-status/pipeline-status.a2ui.json +14 -8
- package/components/pipeline-status/pipeline-status.d.ts +5 -4
- package/components/pipeline-status/pipeline-status.yaml +17 -11
- 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/segmented/segmented.a2ui.json +5 -0
- package/components/segmented/segmented.class.js +20 -2
- package/components/segmented/segmented.yaml +5 -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.class.js +1 -1
- 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.css +11 -3
- 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
|
@@ -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
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
|
+
"allowEmpty": {
|
|
17
|
+
"description": "Suppresses the connect-time self-select of the first enabled segment when value is falsy. Without it, an empty value silently selects the first segment; with it, a falsy value renders every segment unselected until a real value arrives — for path-bound consumers whose bound value can be a genuine empty state.",
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false
|
|
20
|
+
},
|
|
16
21
|
"component": {
|
|
17
22
|
"const": "Segmented"
|
|
18
23
|
},
|
|
@@ -29,6 +29,14 @@ export class UISegmented extends UIFormElement {
|
|
|
29
29
|
static properties = {
|
|
30
30
|
...UIFormElement.properties,
|
|
31
31
|
value: { type: String, default: '', reflect: true },
|
|
32
|
+
// gh#693: absent (default false) preserves the original self-select
|
|
33
|
+
// behavior below. A path-bound consumer whose bound value can be a
|
|
34
|
+
// real empty state (e.g. "no disposition recorded yet") sets this to
|
|
35
|
+
// suppress the connect-time self-select, so a falsy `value` renders
|
|
36
|
+
// every segment unselected instead of silently picking one — a
|
|
37
|
+
// display-truth defect where the UI claimed a state the data never
|
|
38
|
+
// recorded.
|
|
39
|
+
allowEmpty: { type: Boolean, default: false, reflect: true, attribute: 'allow-empty' },
|
|
32
40
|
};
|
|
33
41
|
|
|
34
42
|
static template = () => null;
|
|
@@ -55,8 +63,10 @@ export class UISegmented extends UIFormElement {
|
|
|
55
63
|
this.addEventListener('keydown', this.#handleKeydown);
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
// Default to first segment if no value
|
|
59
|
-
|
|
66
|
+
// Default to first segment if no value — unless allow-empty opts out
|
|
67
|
+
// (a falsy path-bound value must render as truly empty, not silently
|
|
68
|
+
// pick one, gh#693).
|
|
69
|
+
if (!this.value && !this.allowEmpty) {
|
|
60
70
|
const first = this.querySelector('segment-ui:not([disabled])');
|
|
61
71
|
if (first) this.value = first.value || first.getAttribute('value') || '';
|
|
62
72
|
}
|
|
@@ -128,16 +138,24 @@ export class UISegmented extends UIFormElement {
|
|
|
128
138
|
if (!segs.length) return;
|
|
129
139
|
|
|
130
140
|
// Sync selection state
|
|
141
|
+
let anyActive = false;
|
|
131
142
|
for (const seg of segs) {
|
|
132
143
|
const segVal = seg.value || seg.getAttribute('value') || '';
|
|
133
144
|
const isActive = segVal === this.value;
|
|
134
145
|
if (isActive) {
|
|
135
146
|
seg.setAttribute('selected', '');
|
|
147
|
+
anyActive = true;
|
|
136
148
|
} else {
|
|
137
149
|
seg.removeAttribute('selected');
|
|
138
150
|
}
|
|
139
151
|
seg.setAttribute('tabindex', isActive ? '0' : '-1');
|
|
140
152
|
}
|
|
153
|
+
// allow-empty's "nothing selected" state must still be keyboard
|
|
154
|
+
// reachable — mirrors list-ui's roving-tabindex fallback (list.class.js).
|
|
155
|
+
if (!anyActive) {
|
|
156
|
+
const firstEnabled = segs.find((s) => !s.disabled);
|
|
157
|
+
firstEnabled?.setAttribute('tabindex', '0');
|
|
158
|
+
}
|
|
141
159
|
|
|
142
160
|
if (this.label) this.setAttribute('aria-label', this.label);
|
|
143
161
|
|
|
@@ -11,6 +11,11 @@ props:
|
|
|
11
11
|
description: Value of the currently selected segment.
|
|
12
12
|
type: string
|
|
13
13
|
default: ''
|
|
14
|
+
allowEmpty:
|
|
15
|
+
description: Suppresses the connect-time self-select of the first enabled segment when value is falsy. Without it, an empty value silently selects the first segment; with it, a falsy value renders every segment unselected until a real value arrives — for path-bound consumers whose bound value can be a genuine empty state.
|
|
16
|
+
type: boolean
|
|
17
|
+
default: false
|
|
18
|
+
attribute: allow-empty
|
|
14
19
|
events:
|
|
15
20
|
change:
|
|
16
21
|
description: Fired when the selected segment changes. detail contains { value }.
|
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
|
}
|
package/components/stat/stat.js
CHANGED
|
@@ -32,9 +32,15 @@ class UIStat extends UIElement {
|
|
|
32
32
|
|
|
33
33
|
static template = () => null;
|
|
34
34
|
|
|
35
|
+
// Trend arrows the class stamps directly (issue 805) — consumers calling
|
|
36
|
+
// installIconLoaders() manually need these two names present.
|
|
37
|
+
static requiredIcons = ['arrow-up', 'arrow-down'];
|
|
38
|
+
|
|
35
39
|
#valueEl = null;
|
|
36
40
|
#labelEl = null;
|
|
37
41
|
#changeEl = null;
|
|
42
|
+
#changeIconEl = null;
|
|
43
|
+
#changeTextEl = null;
|
|
38
44
|
#iconEl = null;
|
|
39
45
|
|
|
40
46
|
connected() {
|
|
@@ -59,6 +65,28 @@ class UIStat extends UIElement {
|
|
|
59
65
|
this.appendChild(this.#changeEl);
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
// Trend arrow (issue 805): a real icon-ui child instead of a CSS ::before
|
|
69
|
+
// unicode triangle, so the delta indicator rides the Phosphor icon
|
|
70
|
+
// system (contrast, weight, RTL) like every other icon on the page.
|
|
71
|
+
// No `slot="icon"` attribute on this child — that value is reserved for
|
|
72
|
+
// stat's own outer `[slot="icon"]` region, and an ancestor's `[slot="icon"]`
|
|
73
|
+
// descendant selector would otherwise leak margin/positioning onto it
|
|
74
|
+
// (issue 809, PR 810 shape). `[data-trend-icon]` is scoped to stat's own CSS.
|
|
75
|
+
this.#changeIconEl = this.#changeEl.querySelector(':scope > icon-ui[data-trend-icon]');
|
|
76
|
+
if (!this.#changeIconEl) {
|
|
77
|
+
this.#changeIconEl = document.createElement('icon-ui');
|
|
78
|
+
this.#changeIconEl.setAttribute('data-trend-icon', '');
|
|
79
|
+
this.#changeIconEl.hidden = true;
|
|
80
|
+
this.#changeEl.appendChild(this.#changeIconEl);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.#changeTextEl = this.#changeEl.querySelector(':scope > [data-trend-text]');
|
|
84
|
+
if (!this.#changeTextEl) {
|
|
85
|
+
this.#changeTextEl = document.createElement('span');
|
|
86
|
+
this.#changeTextEl.setAttribute('data-trend-text', '');
|
|
87
|
+
this.#changeEl.appendChild(this.#changeTextEl);
|
|
88
|
+
}
|
|
89
|
+
|
|
62
90
|
this.#iconEl = this.querySelector(':scope > [slot="icon"]');
|
|
63
91
|
if (!this.#iconEl) {
|
|
64
92
|
this.#iconEl = document.createElement('icon-ui');
|
|
@@ -83,6 +111,9 @@ class UIStat extends UIElement {
|
|
|
83
111
|
// value+change visual mass without being so wide as to look like text.
|
|
84
112
|
this.#valueEl.textContent = '';
|
|
85
113
|
this.#valueEl.innerHTML = '<skeleton-ui width="60%" height="2em" radius="sm"></skeleton-ui>';
|
|
114
|
+
// Skeleton replaces the trend icon + text children wholesale; the
|
|
115
|
+
// non-loading branch below detects the detached refs (parentNode
|
|
116
|
+
// check) and re-inserts them once real data arrives.
|
|
86
117
|
this.#changeEl.textContent = '';
|
|
87
118
|
this.#changeEl.innerHTML = '<skeleton-ui width="40%" height="1em" radius="sm"></skeleton-ui>';
|
|
88
119
|
this.#changeEl.hidden = false;
|
|
@@ -103,7 +134,39 @@ class UIStat extends UIElement {
|
|
|
103
134
|
|
|
104
135
|
// Change badge
|
|
105
136
|
if (this.change) {
|
|
106
|
-
|
|
137
|
+
// Contract: the generated icon+text pair owns [slot="change"]'s
|
|
138
|
+
// content — same "consumer content is overwritten" rule #valueEl /
|
|
139
|
+
// #labelEl already apply (their textContent is replaced every render
|
|
140
|
+
// regardless of what markup a consumer authored there). #changeEl
|
|
141
|
+
// additionally needs a specific two-node SHAPE (icon then text), not
|
|
142
|
+
// just overwritten text, so any deviation from that exact shape is
|
|
143
|
+
// normalized before writing: a consumer-authored `<stat-ui
|
|
144
|
+
// change="…"><span slot="change">stale</span></stat-ui>` gets its
|
|
145
|
+
// stale content cleared and the generated pair installed; loading's
|
|
146
|
+
// skeleton wipe (above) detaches the pair the same way and is
|
|
147
|
+
// recovered by the same branch; a disconnect/reconnect cycle that
|
|
148
|
+
// leaves an orphaned skeleton behind is also caught here (fresh
|
|
149
|
+
// #changeIconEl/#changeTextEl instances from the new connected()
|
|
150
|
+
// never match old stray children).
|
|
151
|
+
const hasOnlyTrendChildren =
|
|
152
|
+
this.#changeEl.childNodes.length === 2 &&
|
|
153
|
+
this.#changeEl.firstChild === this.#changeIconEl &&
|
|
154
|
+
this.#changeEl.lastChild === this.#changeTextEl;
|
|
155
|
+
if (!hasOnlyTrendChildren) {
|
|
156
|
+
this.#changeEl.textContent = '';
|
|
157
|
+
this.#changeEl.appendChild(this.#changeIconEl);
|
|
158
|
+
this.#changeEl.appendChild(this.#changeTextEl);
|
|
159
|
+
}
|
|
160
|
+
this.#changeTextEl.textContent = this.change;
|
|
161
|
+
if (this.trend === 'up') {
|
|
162
|
+
this.#changeIconEl.setAttribute('name', 'arrow-up');
|
|
163
|
+
this.#changeIconEl.hidden = false;
|
|
164
|
+
} else if (this.trend === 'down') {
|
|
165
|
+
this.#changeIconEl.setAttribute('name', 'arrow-down');
|
|
166
|
+
this.#changeIconEl.hidden = false;
|
|
167
|
+
} else {
|
|
168
|
+
this.#changeIconEl.hidden = true;
|
|
169
|
+
}
|
|
107
170
|
this.#changeEl.hidden = false;
|
|
108
171
|
} else {
|
|
109
172
|
this.#changeEl.hidden = true;
|
|
@@ -122,6 +185,8 @@ class UIStat extends UIElement {
|
|
|
122
185
|
this.#valueEl = null;
|
|
123
186
|
this.#labelEl = null;
|
|
124
187
|
this.#changeEl = null;
|
|
188
|
+
this.#changeIconEl = null;
|
|
189
|
+
this.#changeTextEl = null;
|
|
125
190
|
this.#iconEl = null;
|
|
126
191
|
}
|
|
127
192
|
}
|
|
@@ -112,6 +112,9 @@ tokens:
|
|
|
112
112
|
description: Font size for the primary value
|
|
113
113
|
--stat-value-weight:
|
|
114
114
|
description: Font weight for the primary value
|
|
115
|
+
requiredIcons:
|
|
116
|
+
- arrow-up
|
|
117
|
+
- arrow-down
|
|
115
118
|
a2ui:
|
|
116
119
|
rules:
|
|
117
120
|
- rule: 'Use for prominent metric/KPI displays inside dashboard cards. Value + label + optional delta indicator.'
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
"component": {
|
|
22
22
|
"const": "StepperItem"
|
|
23
23
|
},
|
|
24
|
+
"disabled": {
|
|
25
|
+
"description": "S2's phase-gating mechanism (gh#711 S1 review). Skipped in the parent's [interactive] roving tabindex/arrow order, unclickable, carries aria-disabled. Independent of [status] — a completed or upcoming step can still be disabled for a different reason (e.g. the visit phase hasn't unlocked it yet).",
|
|
26
|
+
"type": "boolean",
|
|
27
|
+
"default": false
|
|
28
|
+
},
|
|
24
29
|
"icon": {
|
|
25
30
|
"description": "Optional leading Phosphor icon name; overrides the auto-stamped numeric badge.",
|
|
26
31
|
"type": "string",
|
|
@@ -39,6 +39,15 @@ props:
|
|
|
39
39
|
- active
|
|
40
40
|
- completed
|
|
41
41
|
- error
|
|
42
|
+
disabled:
|
|
43
|
+
description: >-
|
|
44
|
+
S2's phase-gating mechanism (gh#711 S1 review). Skipped in the
|
|
45
|
+
parent's [interactive] roving tabindex/arrow order, unclickable,
|
|
46
|
+
carries aria-disabled. Independent of [status] — a completed or
|
|
47
|
+
upcoming step can still be disabled for a different reason (e.g.
|
|
48
|
+
the visit phase hasn't unlocked it yet).
|
|
49
|
+
type: boolean
|
|
50
|
+
default: false
|
|
42
51
|
slots:
|
|
43
52
|
label:
|
|
44
53
|
description: Custom label content; falls through to `[text]` prop if not slotted.
|
|
@@ -73,3 +82,5 @@ a2ui:
|
|
|
73
82
|
reason: 'Parent owns step progression.'
|
|
74
83
|
- rule: 'For chronological events (no completion semantics) use <timeline-item-ui> instead.'
|
|
75
84
|
reason: 'Stepper is process; timeline is history.'
|
|
85
|
+
- rule: 'Set [disabled] to phase-gate a step out of click/keyboard reach without changing [status].'
|
|
86
|
+
reason: 'gh#711 S1 review — S2 uses this to lock steps the current visit phase hasn''t unlocked.'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://adiaui.dev/a2ui/v0_9/components/Stepper.json",
|
|
4
4
|
"title": "Stepper",
|
|
5
|
-
"description": "Wizard / process stepper — parent step index drives numbered children's complete/current/upcoming states automatically. Renders horizontally as a labeled step bar with connectors. Use for multi-step forms, onboarding, or pipelines; for read-only event history use <timeline-ui> instead.",
|
|
5
|
+
"description": "Wizard / process stepper — parent step index drives numbered children's complete/current/upcoming states automatically. Renders horizontally as a labeled step bar with connectors. Use for multi-step forms, onboarding, or pipelines; for read-only event history use <timeline-ui> instead. Add [interactive] to opt into click-to-navigate + roving- tabindex keyboard (arrow keys / Home / End) — the host still owns whether navigation is permitted: listen for the cancelable [step-request] event and call preventDefault() to veto a step before it lands, or set [disabled] on a stepper-item-ui to phase-gate it out of reach entirely.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"allOf": [
|
|
8
8
|
{
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
"component": {
|
|
17
17
|
"const": "Stepper"
|
|
18
18
|
},
|
|
19
|
+
"interactive": {
|
|
20
|
+
"description": "Opt into click-to-navigate + roving-tabindex keyboard nav (arrow keys / Home / End / Enter / Space). Absent by default — a bare stepper is a read-only progress display, not a nav control (gh#711 S1).",
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": false
|
|
23
|
+
},
|
|
19
24
|
"orientation": {
|
|
20
25
|
"description": "horizontal | vertical",
|
|
21
26
|
"type": "string",
|
|
@@ -37,12 +42,40 @@
|
|
|
37
42
|
"composes": [
|
|
38
43
|
"icon-ui"
|
|
39
44
|
],
|
|
40
|
-
"events": {
|
|
45
|
+
"events": {
|
|
46
|
+
"change": {
|
|
47
|
+
"description": "Fired after a click or keyboard navigation changes [step] (i.e. only when the preceding [step-request] wasn't vetoed). Only fires when [interactive] is set.",
|
|
48
|
+
"detail": {
|
|
49
|
+
"step": {
|
|
50
|
+
"description": "New current step index.",
|
|
51
|
+
"type": "number"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"step-request": {
|
|
56
|
+
"description": "Cancelable — fires BEFORE [step] mutates, on a click or keyboard navigation. call preventDefault() to veto: the step does not change, [change] does not fire, and focus/roving-tabindex stay exactly where they were. A host that never listens for this event keeps the pre-veto uncontrolled behavior unchanged (gh#711 S1 review).",
|
|
57
|
+
"detail": {
|
|
58
|
+
"previous": {
|
|
59
|
+
"description": "The current step index before this navigation.",
|
|
60
|
+
"type": "number"
|
|
61
|
+
},
|
|
62
|
+
"step": {
|
|
63
|
+
"description": "The step index the navigation would land on if not vetoed.",
|
|
64
|
+
"type": "number"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
41
69
|
"examples": [
|
|
42
70
|
{
|
|
43
71
|
"description": "Three-step wizard",
|
|
44
72
|
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Stepper\",\n \"step\": 1,\n \"children\": [\n \"s1\",\n \"s2\",\n \"s3\"\n ]\n },\n {\n \"id\": \"s1\",\n \"component\": \"StepperItem\",\n \"text\": \"Account\"\n },\n {\n \"id\": \"s2\",\n \"component\": \"StepperItem\",\n \"text\": \"Details\"\n },\n {\n \"id\": \"s3\",\n \"component\": \"StepperItem\",\n \"text\": \"Review\"\n }\n]",
|
|
45
73
|
"name": "basic"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"description": "Click/keyboard-navigable stepper — host allows jumping between steps.",
|
|
77
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Stepper\",\n \"step\": 1,\n \"interactive\": true,\n \"children\": [\n \"s1\",\n \"s2\",\n \"s3\"\n ]\n },\n {\n \"id\": \"s1\",\n \"component\": \"StepperItem\",\n \"text\": \"Account\"\n },\n {\n \"id\": \"s2\",\n \"component\": \"StepperItem\",\n \"text\": \"Details\"\n },\n {\n \"id\": \"s3\",\n \"component\": \"StepperItem\",\n \"text\": \"Review\"\n }\n]",
|
|
78
|
+
"name": "interactive"
|
|
46
79
|
}
|
|
47
80
|
],
|
|
48
81
|
"keywords": [
|
|
@@ -51,7 +84,9 @@
|
|
|
51
84
|
"steps",
|
|
52
85
|
"process",
|
|
53
86
|
"onboarding",
|
|
54
|
-
"multi-step"
|
|
87
|
+
"multi-step",
|
|
88
|
+
"interactive",
|
|
89
|
+
"click-to-navigate"
|
|
55
90
|
],
|
|
56
91
|
"name": "UIStepper",
|
|
57
92
|
"related": [
|