@adia-ai/web-components 0.5.4 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/accordion/accordion-item.a2ui.json +50 -0
- package/components/accordion/accordion-item.yaml +27 -0
- package/components/action-list/action-item.a2ui.json +63 -0
- package/components/action-list/action-item.yaml +37 -0
- package/components/avatar/avatar-group.a2ui.json +50 -0
- package/components/avatar/avatar-group.yaml +26 -0
- package/components/avatar/avatar.a2ui.json +4 -1
- package/components/avatar/avatar.yaml +7 -0
- package/components/button/class.js +39 -0
- package/components/chart/chart.a2ui.json +4 -2
- package/components/list/list-item.a2ui.json +53 -0
- package/components/list/list-item.yaml +29 -0
- package/components/select/class.js +14 -0
- package/components/select/select.a2ui.json +5 -0
- package/components/select/select.css +10 -0
- package/components/select/select.yaml +5 -0
- package/components/slider/class.js +58 -0
- package/components/slider/slider.a2ui.json +10 -0
- package/components/slider/slider.css +13 -0
- package/components/slider/slider.yaml +10 -0
- package/components/switch/class.js +18 -4
- package/components/switch/switch.css +10 -0
- package/components/tabs/tab.a2ui.json +58 -0
- package/components/tabs/tab.yaml +33 -0
- package/components/timeline/timeline-item.a2ui.json +76 -0
- package/components/timeline/timeline-item.yaml +47 -0
- package/components/tree/class.js +91 -0
- package/components/tree/tree-item.a2ui.json +65 -0
- package/components/tree/tree-item.yaml +41 -0
- package/components/tree/tree.a2ui.json +15 -0
- package/components/tree/tree.css +18 -0
- package/components/tree/tree.yaml +10 -0
- package/core/template.js +21 -3
- package/package.json +2 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/AccordionItem.json",
|
|
4
|
+
"title": "AccordionItem",
|
|
5
|
+
"description": "Child of <accordion-ui>. One collapsible section with header + body. Use inside <accordion-ui> only.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"component": {
|
|
17
|
+
"const": "AccordionItem"
|
|
18
|
+
},
|
|
19
|
+
"open": {
|
|
20
|
+
"description": "Whether the section is expanded.",
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": false
|
|
23
|
+
},
|
|
24
|
+
"text": {
|
|
25
|
+
"description": "Header text — the clickable label that toggles the section.",
|
|
26
|
+
"type": "string"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"required": [
|
|
30
|
+
"component"
|
|
31
|
+
],
|
|
32
|
+
"unevaluatedProperties": false,
|
|
33
|
+
"x-adiaui": {
|
|
34
|
+
"anti_patterns": [],
|
|
35
|
+
"category": "layout",
|
|
36
|
+
"composes": [],
|
|
37
|
+
"events": {},
|
|
38
|
+
"examples": [],
|
|
39
|
+
"keywords": [],
|
|
40
|
+
"name": "UIAccordionItem",
|
|
41
|
+
"related": [],
|
|
42
|
+
"slots": {},
|
|
43
|
+
"states": [],
|
|
44
|
+
"synonyms": {},
|
|
45
|
+
"tag": "accordion-item-ui",
|
|
46
|
+
"tokens": {},
|
|
47
|
+
"traits": [],
|
|
48
|
+
"version": 1
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
#
|
|
3
|
+
# §176 (v0.5.5): authored to close the §175 baseline-orphan class. The
|
|
4
|
+
# component already existed as a sibling class in the parent's class.js
|
|
5
|
+
# + was registered alongside the parent (e.g. UIList + UIListItem both
|
|
6
|
+
# from list/class.js). The catalog just lacked its own entry. With the
|
|
7
|
+
# §172 sibling-yaml scanner, this file gets picked up next to the parent
|
|
8
|
+
# yaml.
|
|
9
|
+
|
|
10
|
+
# Child component of <accordion-ui>. Surface only inside that parent.
|
|
11
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
12
|
+
name: UIAccordionItem
|
|
13
|
+
tag: accordion-item-ui
|
|
14
|
+
component: AccordionItem
|
|
15
|
+
category: layout
|
|
16
|
+
version: 1
|
|
17
|
+
description: |-
|
|
18
|
+
Child of <accordion-ui>. One collapsible section with header + body. Use inside <accordion-ui> only.
|
|
19
|
+
|
|
20
|
+
props:
|
|
21
|
+
text:
|
|
22
|
+
description: Header text — the clickable label that toggles the section.
|
|
23
|
+
type: string
|
|
24
|
+
open:
|
|
25
|
+
description: Whether the section is expanded.
|
|
26
|
+
type: boolean
|
|
27
|
+
default: false
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/ActionItem.json",
|
|
4
|
+
"title": "ActionItem",
|
|
5
|
+
"description": "Child of <action-list-ui>. One actionable row — icon + label, forwards activation to the parent action-list.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"component": {
|
|
17
|
+
"const": "ActionItem"
|
|
18
|
+
},
|
|
19
|
+
"disabled": {
|
|
20
|
+
"description": "Disables click + keyboard activation; aria-disabled is set.",
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": false
|
|
23
|
+
},
|
|
24
|
+
"icon": {
|
|
25
|
+
"description": "Leading icon name (Phosphor).",
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
"text": {
|
|
29
|
+
"description": "Action label.",
|
|
30
|
+
"type": "string"
|
|
31
|
+
},
|
|
32
|
+
"value": {
|
|
33
|
+
"description": "Identifier passed to the action-list change/activate handlers.",
|
|
34
|
+
"type": "string"
|
|
35
|
+
},
|
|
36
|
+
"variant": {
|
|
37
|
+
"description": "Visual variant (default | accent | danger).",
|
|
38
|
+
"type": "string",
|
|
39
|
+
"default": "default"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"required": [
|
|
43
|
+
"component"
|
|
44
|
+
],
|
|
45
|
+
"unevaluatedProperties": false,
|
|
46
|
+
"x-adiaui": {
|
|
47
|
+
"anti_patterns": [],
|
|
48
|
+
"category": "navigation",
|
|
49
|
+
"composes": [],
|
|
50
|
+
"events": {},
|
|
51
|
+
"examples": [],
|
|
52
|
+
"keywords": [],
|
|
53
|
+
"name": "UIActionItem",
|
|
54
|
+
"related": [],
|
|
55
|
+
"slots": {},
|
|
56
|
+
"states": [],
|
|
57
|
+
"synonyms": {},
|
|
58
|
+
"tag": "action-item-ui",
|
|
59
|
+
"tokens": {},
|
|
60
|
+
"traits": [],
|
|
61
|
+
"version": 1
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
#
|
|
3
|
+
# §176 (v0.5.5): authored to close the §175 baseline-orphan class. The
|
|
4
|
+
# component already existed as a sibling class in the parent's class.js
|
|
5
|
+
# + was registered alongside the parent (e.g. UIList + UIListItem both
|
|
6
|
+
# from list/class.js). The catalog just lacked its own entry. With the
|
|
7
|
+
# §172 sibling-yaml scanner, this file gets picked up next to the parent
|
|
8
|
+
# yaml.
|
|
9
|
+
|
|
10
|
+
# Child component of <action-list-ui>. Surface only inside that parent.
|
|
11
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
12
|
+
name: UIActionItem
|
|
13
|
+
tag: action-item-ui
|
|
14
|
+
component: ActionItem
|
|
15
|
+
category: navigation
|
|
16
|
+
version: 1
|
|
17
|
+
description: |-
|
|
18
|
+
Child of <action-list-ui>. One actionable row — icon + label, forwards activation to the parent action-list.
|
|
19
|
+
|
|
20
|
+
props:
|
|
21
|
+
icon:
|
|
22
|
+
description: Leading icon name (Phosphor).
|
|
23
|
+
type: string
|
|
24
|
+
text:
|
|
25
|
+
description: Action label.
|
|
26
|
+
type: string
|
|
27
|
+
value:
|
|
28
|
+
description: Identifier passed to the action-list change/activate handlers.
|
|
29
|
+
type: string
|
|
30
|
+
variant:
|
|
31
|
+
description: Visual variant (default | accent | danger).
|
|
32
|
+
type: string
|
|
33
|
+
default: 'default'
|
|
34
|
+
disabled:
|
|
35
|
+
description: Disables click + keyboard activation; aria-disabled is set.
|
|
36
|
+
type: boolean
|
|
37
|
+
default: false
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/AvatarGroup.json",
|
|
4
|
+
"title": "AvatarGroup",
|
|
5
|
+
"description": "Cluster of overlapping <avatar-ui> children. Stacks the first `max` avatars with a negative inline-start margin; if more children are present, renders a +N overflow indicator.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"component": {
|
|
17
|
+
"const": "AvatarGroup"
|
|
18
|
+
},
|
|
19
|
+
"max": {
|
|
20
|
+
"description": "Maximum avatars to show before the +N overflow indicator.",
|
|
21
|
+
"type": "number",
|
|
22
|
+
"default": 5
|
|
23
|
+
},
|
|
24
|
+
"size": {
|
|
25
|
+
"description": "Forwards to each child <avatar-ui> (xs|sm|md|lg|xl). Empty uses the per-avatar size.",
|
|
26
|
+
"type": "string"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"required": [
|
|
30
|
+
"component"
|
|
31
|
+
],
|
|
32
|
+
"unevaluatedProperties": false,
|
|
33
|
+
"x-adiaui": {
|
|
34
|
+
"anti_patterns": [],
|
|
35
|
+
"category": "feedback",
|
|
36
|
+
"composes": [],
|
|
37
|
+
"events": {},
|
|
38
|
+
"examples": [],
|
|
39
|
+
"keywords": [],
|
|
40
|
+
"name": "UIAvatarGroup",
|
|
41
|
+
"related": [],
|
|
42
|
+
"slots": {},
|
|
43
|
+
"states": [],
|
|
44
|
+
"synonyms": {},
|
|
45
|
+
"tag": "avatar-group-ui",
|
|
46
|
+
"tokens": {},
|
|
47
|
+
"traits": [],
|
|
48
|
+
"version": 1
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
#
|
|
3
|
+
# §176 (v0.5.5): authored to close the §175 baseline-orphan class. The
|
|
4
|
+
# component already existed as a sibling class in the parent's class.js
|
|
5
|
+
# + was registered alongside the parent (e.g. UIList + UIListItem both
|
|
6
|
+
# from list/class.js). The catalog just lacked its own entry. With the
|
|
7
|
+
# §172 sibling-yaml scanner, this file gets picked up next to the parent
|
|
8
|
+
# yaml.
|
|
9
|
+
|
|
10
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
11
|
+
name: UIAvatarGroup
|
|
12
|
+
tag: avatar-group-ui
|
|
13
|
+
component: AvatarGroup
|
|
14
|
+
category: feedback
|
|
15
|
+
version: 1
|
|
16
|
+
description: |-
|
|
17
|
+
Cluster of overlapping <avatar-ui> children. Stacks the first `max` avatars with a negative inline-start margin; if more children are present, renders a +N overflow indicator.
|
|
18
|
+
|
|
19
|
+
props:
|
|
20
|
+
max:
|
|
21
|
+
description: Maximum avatars to show before the +N overflow indicator.
|
|
22
|
+
type: number
|
|
23
|
+
default: 5
|
|
24
|
+
size:
|
|
25
|
+
description: Forwards to each child <avatar-ui> (xs|sm|md|lg|xl). Empty uses the per-avatar size.
|
|
26
|
+
type: string
|
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
"name": {
|
|
25
25
|
"description": "Deprecated alias for `text` (logs a one-shot console warning; will be removed in a future release). New code should use `text=` instead.",
|
|
26
26
|
"type": "string",
|
|
27
|
-
"default": ""
|
|
27
|
+
"default": "",
|
|
28
|
+
"deprecated": true,
|
|
29
|
+
"deprecated_reason": "Use `text` instead. Will be removed in v0.6.0.",
|
|
30
|
+
"deprecated_since": "0.4.x"
|
|
28
31
|
},
|
|
29
32
|
"shape": {
|
|
30
33
|
"description": "Avatar shape",
|
|
@@ -19,6 +19,13 @@ props:
|
|
|
19
19
|
name:
|
|
20
20
|
description: "Deprecated alias for `text` (logs a one-shot console warning; will be removed in a future release). New code should use `text=` instead."
|
|
21
21
|
type: string
|
|
22
|
+
# §179 (v0.5.5): principled deprecation metadata. The build pipeline
|
|
23
|
+
# propagates this into the sidecar + catalog; the LLM prompt builder
|
|
24
|
+
# surfaces "(deprecated, do not use)" based on this field rather than
|
|
25
|
+
# substring-matching the description.
|
|
26
|
+
deprecated: true
|
|
27
|
+
deprecated_since: "0.4.x"
|
|
28
|
+
deprecated_reason: "Use `text` instead. Will be removed in v0.6.0."
|
|
22
29
|
default: ""
|
|
23
30
|
icon:
|
|
24
31
|
description: Phosphor icon name shown instead of initials when `src`/`text` are empty.
|
|
@@ -52,8 +52,47 @@ export class UIButton extends UIElement {
|
|
|
52
52
|
this.prepend(iconEl);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
// §184 (v0.5.5, FEEDBACK-08 §8): icon-only a11y warning + title→aria-label
|
|
57
|
+
// auto-derive. Two complementary mechanisms (consumer chooses):
|
|
58
|
+
// (a) When [title="Undo"] is set on an icon-only button without an
|
|
59
|
+
// explicit aria-label, mirror title → aria-label. Matches the
|
|
60
|
+
// common icon-only-button-with-tooltip pattern (zero authoring
|
|
61
|
+
// cost — already had to write the tooltip text).
|
|
62
|
+
// (b) When the button is icon-only AND has no aria-label AND no
|
|
63
|
+
// aria-labelledby AND no title, console.warn ONCE per element
|
|
64
|
+
// so the bug class is diagnosable in dev without firing every
|
|
65
|
+
// render tick. The warning is suppressed via a one-shot
|
|
66
|
+
// WeakSet so subsequent re-renders stay quiet.
|
|
67
|
+
if (this.icon && !this.text) {
|
|
68
|
+
const ariaLabel = this.getAttribute('aria-label');
|
|
69
|
+
const ariaLabelledBy = this.getAttribute('aria-labelledby');
|
|
70
|
+
const titleAttr = this.getAttribute('title');
|
|
71
|
+
if (!ariaLabel && !ariaLabelledBy && titleAttr) {
|
|
72
|
+
this.setAttribute('aria-label', titleAttr);
|
|
73
|
+
} else if (!ariaLabel && !ariaLabelledBy && !titleAttr) {
|
|
74
|
+
if (!UIButton.#a11yWarned.has(this)) {
|
|
75
|
+
UIButton.#a11yWarned.add(this);
|
|
76
|
+
// eslint-disable-next-line no-console
|
|
77
|
+
console.warn(
|
|
78
|
+
`[button-ui] Icon-only button is missing an accessible name.\n` +
|
|
79
|
+
` Element: <button-ui icon="${this.icon}">\n` +
|
|
80
|
+
` Add one of:\n` +
|
|
81
|
+
` aria-label="Undo" ← explicit accessible name\n` +
|
|
82
|
+
` title="Undo" ← auto-mirrored to aria-label + tooltip\n` +
|
|
83
|
+
` aria-labelledby="..." ← reference an existing label element\n` +
|
|
84
|
+
` Without an accessible name screen readers announce nothing meaningful.`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
55
89
|
}
|
|
56
90
|
|
|
91
|
+
// §184: WeakSet of elements we've already warned about. Prevents
|
|
92
|
+
// repeat warnings across re-renders. GC-friendly — when the element
|
|
93
|
+
// is gone the entry is collected.
|
|
94
|
+
static #a11yWarned = new WeakSet();
|
|
95
|
+
|
|
57
96
|
#onClick = (e) => {
|
|
58
97
|
if (this.disabled) { e.stopPropagation(); return; }
|
|
59
98
|
if (this.type === 'submit') {
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"square",
|
|
48
48
|
"tall"
|
|
49
49
|
],
|
|
50
|
-
"default": "std"
|
|
50
|
+
"default": "std",
|
|
51
|
+
"deprecated": true
|
|
51
52
|
},
|
|
52
53
|
"color": {
|
|
53
54
|
"description": "Color scheme",
|
|
@@ -78,7 +79,8 @@
|
|
|
78
79
|
"heading": {
|
|
79
80
|
"description": "DEPRECATED (OD-CHART-02). Place chart titles in an enclosing card-ui's `<header><span slot=\"heading\">...</span></header>` instead. Still honored for back-compat; emits a one-shot console.warn per instance when set. Used as the chart's `aria-label` when no explicit label is provided.",
|
|
80
81
|
"type": "string",
|
|
81
|
-
"default": ""
|
|
82
|
+
"default": "",
|
|
83
|
+
"deprecated": true
|
|
82
84
|
},
|
|
83
85
|
"hideAverage": {
|
|
84
86
|
"description": "When true, suppress the overlaid average line",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/ListItem.json",
|
|
4
|
+
"title": "ListItem",
|
|
5
|
+
"description": "Child of <list-ui>. One list item with optional icon + text + description, plus arbitrary slotted content (cards, rows). Use inside <list-ui> only.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"description": {
|
|
17
|
+
"description": "Secondary line below the primary text. Subtle color.",
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"component": {
|
|
21
|
+
"const": "ListItem"
|
|
22
|
+
},
|
|
23
|
+
"icon": {
|
|
24
|
+
"description": "Optional leading icon name (Phosphor).",
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"text": {
|
|
28
|
+
"description": "Primary text. Renders as semibold inline body.",
|
|
29
|
+
"type": "string"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"required": [
|
|
33
|
+
"component"
|
|
34
|
+
],
|
|
35
|
+
"unevaluatedProperties": false,
|
|
36
|
+
"x-adiaui": {
|
|
37
|
+
"anti_patterns": [],
|
|
38
|
+
"category": "layout",
|
|
39
|
+
"composes": [],
|
|
40
|
+
"events": {},
|
|
41
|
+
"examples": [],
|
|
42
|
+
"keywords": [],
|
|
43
|
+
"name": "UIListItem",
|
|
44
|
+
"related": [],
|
|
45
|
+
"slots": {},
|
|
46
|
+
"states": [],
|
|
47
|
+
"synonyms": {},
|
|
48
|
+
"tag": "list-item-ui",
|
|
49
|
+
"tokens": {},
|
|
50
|
+
"traits": [],
|
|
51
|
+
"version": 1
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Edit this file; run `npm run build:components` to regenerate a2ui.json.
|
|
2
|
+
#
|
|
3
|
+
# §176 (v0.5.5): authored to close the §175 baseline-orphan class. The
|
|
4
|
+
# component already existed as a sibling class in the parent's class.js
|
|
5
|
+
# + was registered alongside the parent (e.g. UIList + UIListItem both
|
|
6
|
+
# from list/class.js). The catalog just lacked its own entry. With the
|
|
7
|
+
# §172 sibling-yaml scanner, this file gets picked up next to the parent
|
|
8
|
+
# yaml.
|
|
9
|
+
|
|
10
|
+
# Child component of <list-ui>. Surface only inside that parent.
|
|
11
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
12
|
+
name: UIListItem
|
|
13
|
+
tag: list-item-ui
|
|
14
|
+
component: ListItem
|
|
15
|
+
category: layout
|
|
16
|
+
version: 1
|
|
17
|
+
description: |-
|
|
18
|
+
Child of <list-ui>. One list item with optional icon + text + description, plus arbitrary slotted content (cards, rows). Use inside <list-ui> only.
|
|
19
|
+
|
|
20
|
+
props:
|
|
21
|
+
icon:
|
|
22
|
+
description: Optional leading icon name (Phosphor).
|
|
23
|
+
type: string
|
|
24
|
+
text:
|
|
25
|
+
description: Primary text. Renders as semibold inline body.
|
|
26
|
+
type: string
|
|
27
|
+
description:
|
|
28
|
+
description: Secondary line below the primary text. Subtle color.
|
|
29
|
+
type: string
|
|
@@ -37,8 +37,14 @@ export class UISelect extends UIFormElement {
|
|
|
37
37
|
searchable: { type: Boolean, default: false, reflect: true },
|
|
38
38
|
freeText: { type: Boolean, default: false, reflect: true, attribute: 'free-text' },
|
|
39
39
|
divider: { type: Boolean, default: false, reflect: true },
|
|
40
|
+
// §184 (v0.5.5, FEEDBACK-08 §7): optional caption beneath the
|
|
41
|
+
// trigger, wired to aria-describedby on the host.
|
|
42
|
+
hint: { type: String, default: '', reflect: true },
|
|
40
43
|
};
|
|
41
44
|
|
|
45
|
+
// §184: per-instance hint id counter for aria-describedby wiring.
|
|
46
|
+
static #hintSeq = 0;
|
|
47
|
+
|
|
42
48
|
static template = () => null;
|
|
43
49
|
|
|
44
50
|
#options = [];
|
|
@@ -97,13 +103,21 @@ export class UISelect extends UIFormElement {
|
|
|
97
103
|
const displayMarkup = this.searchable
|
|
98
104
|
? `<input slot="display" type="text" role="combobox" aria-autocomplete="list" autocomplete="off" placeholder="${escapeHTML(this.placeholder || '')}" value="${escapeHTML(this.#displayText() === this.placeholder ? '' : this.#displayText())}" />`
|
|
99
105
|
: `<span slot="display">${escapeHTML(this.#displayText())}</span>`;
|
|
106
|
+
// §184 (v0.5.5, FEEDBACK-08 §7): optional hint slot beneath the
|
|
107
|
+
// trigger. Mirrors slider-ui's hint pattern + matches the
|
|
108
|
+
// schema-declared `hint` prop on switch-ui (which had the spec
|
|
109
|
+
// but not the rendering until this arc).
|
|
110
|
+
const hintId = this.hint ? `select-hint-${++UISelect.#hintSeq}` : '';
|
|
111
|
+
const hintMarkup = this.hint ? `<span slot="hint" id="${hintId}">${escapeHTML(this.hint)}</span>` : '';
|
|
100
112
|
this.innerHTML = `
|
|
101
113
|
<span slot="trigger">
|
|
102
114
|
${leading}
|
|
103
115
|
${displayMarkup}
|
|
104
116
|
<icon-ui name="caret-up-down" slot="caret"></icon-ui>
|
|
105
117
|
</span>
|
|
118
|
+
${hintMarkup}
|
|
106
119
|
`;
|
|
120
|
+
if (this.hint) this.setAttribute('aria-describedby', hintId);
|
|
107
121
|
|
|
108
122
|
if (this.searchable) {
|
|
109
123
|
// Detach from previous search input if any
|
|
@@ -46,6 +46,11 @@
|
|
|
46
46
|
"type": "boolean",
|
|
47
47
|
"default": false
|
|
48
48
|
},
|
|
49
|
+
"hint": {
|
|
50
|
+
"description": "§184 (v0.5.5, FEEDBACK-08 §7): small caption rendered beneath the select. Sets `aria-describedby` on the host so screen readers announce it as a description (distinct from `aria-label`, which comes from `label`). Does not conflict with the in-component `label`.",
|
|
51
|
+
"type": "string",
|
|
52
|
+
"default": ""
|
|
53
|
+
},
|
|
49
54
|
"icon": {
|
|
50
55
|
"description": "Leading icon name rendered inside the trigger",
|
|
51
56
|
"type": "string",
|
|
@@ -308,3 +308,13 @@ select-ui[divider] [role="group"] + [role="group"] {
|
|
|
308
308
|
margin-top: var(--a-space-1);
|
|
309
309
|
padding-top: var(--a-space-1);
|
|
310
310
|
}
|
|
311
|
+
|
|
312
|
+
/* ── Hint (§184, v0.5.5, FEEDBACK-08 §7) ──
|
|
313
|
+
Caption beneath the trigger; wired to aria-describedby on the host. */
|
|
314
|
+
select-ui > [slot="hint"] {
|
|
315
|
+
display: block;
|
|
316
|
+
margin-top: var(--select-hint-mt, var(--a-space-1));
|
|
317
|
+
font-size: var(--select-hint-size, var(--a-fine-size));
|
|
318
|
+
color: var(--select-hint-fg, var(--a-fg-muted));
|
|
319
|
+
line-height: var(--select-hint-lh, 1.4);
|
|
320
|
+
}
|
|
@@ -67,6 +67,11 @@ props:
|
|
|
67
67
|
description: Label text above the trigger
|
|
68
68
|
type: string
|
|
69
69
|
default: ""
|
|
70
|
+
hint:
|
|
71
|
+
description: |-
|
|
72
|
+
§184 (v0.5.5, FEEDBACK-08 §7): small caption rendered beneath the select. Sets `aria-describedby` on the host so screen readers announce it as a description (distinct from `aria-label`, which comes from `label`). Does not conflict with the in-component `label`.
|
|
73
|
+
type: string
|
|
74
|
+
default: ""
|
|
70
75
|
maxlength:
|
|
71
76
|
description: Maximum character length for validation
|
|
72
77
|
type: number
|
|
@@ -42,13 +42,31 @@ export class UISlider extends UIFormElement {
|
|
|
42
42
|
step: { type: Number, default: 1, reflect: true },
|
|
43
43
|
label: { type: String, default: '', reflect: true },
|
|
44
44
|
suffix: { type: String, default: '', reflect: true },
|
|
45
|
+
// §184 (v0.5.5, FEEDBACK-08 §4): declarative debounce for the
|
|
46
|
+
// `input` event when driving expensive computation (palette regen,
|
|
47
|
+
// shader compile, large list reflow). When > 0, value updates +
|
|
48
|
+
// visual feedback are immediate but `input` event emission is
|
|
49
|
+
// debounced — only the FINAL value in the throttle window dispatches.
|
|
50
|
+
// `change` fires unthrottled on pointerup / track click / keyboard;
|
|
51
|
+
// any pending `input` flushes BEFORE `change` so consumers always
|
|
52
|
+
// see input→input→…→input→change ordering. throttle="0" (default)
|
|
53
|
+
// preserves the pre-§184 every-pointer-move-fires-input behavior.
|
|
54
|
+
throttle: { type: Number, default: 0, reflect: true },
|
|
45
55
|
};
|
|
46
56
|
|
|
47
57
|
static template = () => null;
|
|
48
58
|
|
|
59
|
+
// §184: per-instance hint id counter for aria-describedby wiring.
|
|
60
|
+
static #hintSeq = 0;
|
|
61
|
+
|
|
49
62
|
#trackEl = null;
|
|
50
63
|
#thumbEl = null;
|
|
51
64
|
#dragging = false;
|
|
65
|
+
// §184 (v0.5.5, FEEDBACK-08 §4): debounce timer for the `input`
|
|
66
|
+
// event. When `throttle > 0`, #setValue stores a pending dispatch
|
|
67
|
+
// here + restarts the timer on every value change. Flushed before
|
|
68
|
+
// any `change` event so input always precedes change.
|
|
69
|
+
#inputTimer = null;
|
|
52
70
|
|
|
53
71
|
get #pct() {
|
|
54
72
|
const range = this.max - this.min;
|
|
@@ -69,6 +87,9 @@ export class UISlider extends UIFormElement {
|
|
|
69
87
|
if (this.label) this.setAttribute('aria-label', this.label);
|
|
70
88
|
|
|
71
89
|
if (!this.querySelector('[slot="track"]')) {
|
|
90
|
+
// §184 (v0.5.5, FEEDBACK-08 §7): hint slot stamped underneath
|
|
91
|
+
// the track when [hint] is set. Wired to aria-describedby below.
|
|
92
|
+
const hintId = this.hint ? `slider-hint-${++UISlider.#hintSeq}` : '';
|
|
72
93
|
this.innerHTML = `
|
|
73
94
|
<div slot="header">
|
|
74
95
|
${this.label ? `<span slot="label">${this.label}</span>` : ''}
|
|
@@ -81,7 +102,9 @@ export class UISlider extends UIFormElement {
|
|
|
81
102
|
<div slot="fill"></div>
|
|
82
103
|
<div slot="thumb" tabindex="0"></div>
|
|
83
104
|
</div>
|
|
105
|
+
${this.hint ? `<span slot="hint" id="${hintId}">${this.hint}</span>` : ''}
|
|
84
106
|
`;
|
|
107
|
+
if (this.hint) this.setAttribute('aria-describedby', hintId);
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
this.#trackEl = this.querySelector('[slot="track"]');
|
|
@@ -148,9 +171,36 @@ export class UISlider extends UIFormElement {
|
|
|
148
171
|
#setValue(v) {
|
|
149
172
|
if (v === this.value) return;
|
|
150
173
|
this.value = v;
|
|
174
|
+
// §184: when throttle > 0, debounce the `input` dispatch.
|
|
175
|
+
// The value update + UI is still immediate; only event emission is
|
|
176
|
+
// accumulated. Same value can dispatch input multiple times across
|
|
177
|
+
// pointer moves, but the throttle collapses them to one trailing
|
|
178
|
+
// emission at quiet+throttle ms.
|
|
179
|
+
const t = Number(this.throttle) || 0;
|
|
180
|
+
if (t > 0) {
|
|
181
|
+
if (this.#inputTimer != null) clearTimeout(this.#inputTimer);
|
|
182
|
+
this.#inputTimer = setTimeout(() => {
|
|
183
|
+
this.#inputTimer = null;
|
|
184
|
+
this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: this.value } }));
|
|
185
|
+
}, t);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
151
188
|
this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: this.value } }));
|
|
152
189
|
}
|
|
153
190
|
|
|
191
|
+
/**
|
|
192
|
+
* §184: flush any pending throttled `input` dispatch synchronously.
|
|
193
|
+
* Called before `change` so consumers see the trailing input event
|
|
194
|
+
* BEFORE the change commit. No-op when no timer is pending.
|
|
195
|
+
*/
|
|
196
|
+
#flushInput() {
|
|
197
|
+
if (this.#inputTimer != null) {
|
|
198
|
+
clearTimeout(this.#inputTimer);
|
|
199
|
+
this.#inputTimer = null;
|
|
200
|
+
this.dispatchEvent(new CustomEvent('input', { bubbles: true, detail: { value: this.value } }));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
154
204
|
#onPointerDown = (e) => {
|
|
155
205
|
if (this.disabled) return;
|
|
156
206
|
e.preventDefault();
|
|
@@ -170,12 +220,14 @@ export class UISlider extends UIFormElement {
|
|
|
170
220
|
this.#thumbEl.releasePointerCapture(e.pointerId);
|
|
171
221
|
this.#thumbEl.removeEventListener('pointermove', this.#onPointerMove);
|
|
172
222
|
this.#thumbEl.removeEventListener('pointerup', this.#onPointerUp);
|
|
223
|
+
this.#flushInput(); // §184: pending throttled input fires before change
|
|
173
224
|
this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: this.value } }));
|
|
174
225
|
};
|
|
175
226
|
|
|
176
227
|
#onTrackClick = (e) => {
|
|
177
228
|
if (this.disabled || e.target === this.#thumbEl) return;
|
|
178
229
|
this.#setValue(this.#valueFromX(e.clientX));
|
|
230
|
+
this.#flushInput(); // §184: ensure trailing input precedes change
|
|
179
231
|
this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: this.value } }));
|
|
180
232
|
};
|
|
181
233
|
|
|
@@ -193,6 +245,7 @@ export class UISlider extends UIFormElement {
|
|
|
193
245
|
}
|
|
194
246
|
e.preventDefault();
|
|
195
247
|
this.#setValue(this.#snap(v));
|
|
248
|
+
this.#flushInput(); // §184: trailing input fires before change
|
|
196
249
|
this.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { value: this.value } }));
|
|
197
250
|
};
|
|
198
251
|
|
|
@@ -203,6 +256,11 @@ export class UISlider extends UIFormElement {
|
|
|
203
256
|
this.#thumbEl?.removeEventListener('pointermove', this.#onPointerMove);
|
|
204
257
|
this.#thumbEl?.removeEventListener('pointerup', this.#onPointerUp);
|
|
205
258
|
this.removeEventListener('keydown', this.#onKey);
|
|
259
|
+
// §184: drop any pending throttle timer (no flush — element is gone)
|
|
260
|
+
if (this.#inputTimer != null) {
|
|
261
|
+
clearTimeout(this.#inputTimer);
|
|
262
|
+
this.#inputTimer = null;
|
|
263
|
+
}
|
|
206
264
|
this.#trackEl = null;
|
|
207
265
|
this.#thumbEl = null;
|
|
208
266
|
}
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
"type": "string",
|
|
32
32
|
"default": ""
|
|
33
33
|
},
|
|
34
|
+
"hint": {
|
|
35
|
+
"description": "§184 (v0.5.5, FEEDBACK-08 §7): small caption rendered beneath the slider track. Sets `aria-describedby` on the host so screen readers announce it as a description (distinct from `aria-label`, which comes from `label`). Does not conflict with the in-component `label`. Use for semantic clarifications a `<field-ui>` wrapper would be overkill for.",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"default": ""
|
|
38
|
+
},
|
|
34
39
|
"label": {
|
|
35
40
|
"description": "Label text above the slider",
|
|
36
41
|
"type": "string",
|
|
@@ -61,6 +66,11 @@
|
|
|
61
66
|
"type": "string",
|
|
62
67
|
"default": ""
|
|
63
68
|
},
|
|
69
|
+
"throttle": {
|
|
70
|
+
"description": "§184 (v0.5.5, FEEDBACK-08 §4): when > 0, debounce the `input` event by this many milliseconds. Value updates + visual feedback remain immediate; only event dispatch accumulates. Pending input flushes BEFORE `change` so consumers always see input→…→input→change ordering. throttle=\"0\" (default) preserves the pre-§184 every-pointer-move-fires-input behavior. Common values: 50-100ms for palette regen / shader compile / large list reflow.",
|
|
71
|
+
"type": "number",
|
|
72
|
+
"default": 0
|
|
73
|
+
},
|
|
64
74
|
"value": {
|
|
65
75
|
"description": "Current slider value",
|
|
66
76
|
"type": "number",
|