@adia-ai/web-components 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/USAGE.md +42 -0
  2. package/components/accordion/accordion.a2ui.json +8 -2
  3. package/components/accordion/accordion.d.ts +7 -2
  4. package/components/accordion/accordion.yaml +8 -2
  5. package/components/accordion/class.js +6 -0
  6. package/components/agent-questions/agent-questions.yaml +2 -0
  7. package/components/agent-questions/class.js +6 -0
  8. package/components/agent-reasoning/agent-reasoning.yaml +5 -0
  9. package/components/agent-reasoning/class.js +6 -0
  10. package/components/agent-trace/agent-trace.js +6 -0
  11. package/components/agent-trace/agent-trace.yaml +3 -0
  12. package/components/calendar-picker/calendar-picker.yaml +4 -0
  13. package/components/calendar-picker/class.js +7 -0
  14. package/components/canvas/canvas.a2ui.json +1 -10
  15. package/components/canvas/canvas.d.ts +0 -6
  16. package/components/canvas/canvas.yaml +1 -7
  17. package/components/card/card.a2ui.json +1 -5
  18. package/components/card/card.d.ts +0 -9
  19. package/components/card/card.yaml +1 -3
  20. package/components/chat-thread/chat-input.a2ui.json +158 -0
  21. package/components/chat-thread/chat-input.yaml +251 -0
  22. package/components/check/class.js +1 -0
  23. package/components/color-picker/class.js +6 -0
  24. package/components/color-picker/color-picker.yaml +2 -0
  25. package/components/command/class.js +6 -0
  26. package/components/command/command.yaml +2 -0
  27. package/components/drawer/class.js +25 -3
  28. package/components/drawer/drawer.a2ui.json +13 -1
  29. package/components/drawer/drawer.d.ts +6 -1
  30. package/components/drawer/drawer.yaml +11 -1
  31. package/components/feed/feed-item.a2ui.json +86 -0
  32. package/components/pane/class.js +6 -0
  33. package/components/pane/pane.yaml +2 -0
  34. package/components/radio/class.js +1 -0
  35. package/components/row/row.a2ui.json +1 -5
  36. package/components/row/row.d.ts +0 -9
  37. package/components/row/row.yaml +1 -3
  38. package/components/select/class.js +7 -0
  39. package/components/select/select.yaml +2 -0
  40. package/components/slider/class.js +25 -0
  41. package/components/switch/class.js +1 -0
  42. package/components/table/class.js +6 -0
  43. package/components/table/table.a2ui.json +13 -0
  44. package/components/table/table.d.ts +9 -0
  45. package/components/table/table.yaml +13 -0
  46. package/components/tag/class.js +6 -0
  47. package/components/tag/tag.yaml +2 -0
  48. package/components/textarea/class.js +1 -0
  49. package/components/tree/class.js +6 -0
  50. package/components/tree/tree.yaml +2 -0
  51. package/components/upload/class.js +1 -0
  52. package/core/icons.d.ts +148 -0
  53. package/core/icons.js +148 -5
  54. package/core/icons.test.js +187 -0
  55. package/core/template.js +59 -3
  56. package/package.json +16 -2
@@ -0,0 +1,251 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ #
3
+ # §172 (v0.5.4): authored to close the chat-input redundant-send-button
4
+ # regression diagnosed 2026-05-14. Pre-§172 the runtime registry declared
5
+ # `ChatInput → chat-input-ui` but the component had no yaml/sidecar/catalog
6
+ # entry — `<chat-input-ui>` lived as a sibling file inside `chat-thread/`
7
+ # rather than its own folder. The build scanner only looked for canonical
8
+ # `<name>/<name>.yaml`, so this component was invisible to the catalog +
9
+ # system prompt + audit pipeline.
10
+ #
11
+ # Result: the LLM saw `ChatInput` in the type registry but had no schema
12
+ # describing what the element stamps. When users asked for "chat interface
13
+ # with chat-input", the LLM defensively added a sibling Button (primary
14
+ # variant) for "send" — duplicating the built-in send button the
15
+ # component already stamps. The user's ticket
16
+ # (20260514045025-chatinput-training-data-unclea) flagged this.
17
+ #
18
+ # The scanner was extended in §172 to pick up sibling yamls in the same
19
+ # component dir. This file now flows through the build to the v0.9 catalog,
20
+ # the LLM system prompt's CORPUS CONTEXT block, and the audit-script family.
21
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
22
+ name: UIChatInput
23
+ tag: chat-input-ui
24
+ component: ChatInput
25
+ category: agent
26
+ version: 1
27
+ description: |
28
+ Composable chat input bar — a self-contained chat-message composer
29
+ that stamps its OWN inner structure (textarea + model picker + send
30
+ button) when authored as a bare `<chat-input-ui>` tag.
31
+
32
+ IMPORTANT (closes the 2026-05-14 redundant-send-button class): the
33
+ component stamps a built-in send button (paper-plane-right icon,
34
+ primary variant). DO NOT add a separate Button sibling for "send" —
35
+ the user gets two send buttons. The submit event fires on Enter or
36
+ send-button click; `detail` is `{ text, model }`.
37
+
38
+ Inner stamped structure (default):
39
+ <chat-input-ui>
40
+ <textarea-ui placeholder="Type a message..." rows="1"></textarea-ui>
41
+ <div slot="toolbar">
42
+ <select-ui slot="model" placeholder="Model">...</select-ui>
43
+ <button-ui icon="paper-plane-right" variant="primary" slot="send"></button-ui>
44
+ </div>
45
+ </chat-input-ui>
46
+
47
+ Layout:
48
+ ┌──────────────────────────────────┐
49
+ │ textarea (grows vertically) │
50
+ ├──────────────────────────────────┤
51
+ │ [model ▾] [⏎ send] │ ← toolbar (model picker + built-in send)
52
+ └──────────────────────────────────┘
53
+
54
+ Composite wrapper, not a form field itself. The inner textarea-ui
55
+ is form-associated via UIFormElement and submits through the parent
56
+ form. `chat-input-ui`'s `disabled` / `placeholder` props propagate
57
+ to the inner textarea.
58
+
59
+ For module-tier composer surfaces (slot vocabulary for file attach,
60
+ autocomplete, trailing/leading controls), wrap inside
61
+ `<chat-composer>` — see ChatComposer for the module-tier shape.
62
+
63
+ props:
64
+ disabled:
65
+ description: |
66
+ Disable the entire input. Textarea becomes contenteditable=false;
67
+ send button disabled.
68
+ type: boolean
69
+ default: false
70
+ reflect: true
71
+ loading:
72
+ description: |
73
+ In-flight / streaming state. Send button disabled, submit events
74
+ suppressed, but textarea stays editable so the user can draft a
75
+ follow-up while the model is still responding.
76
+ type: boolean
77
+ default: false
78
+ reflect: true
79
+ placeholder:
80
+ description: Textarea placeholder. Defaults to "Type a message...".
81
+ type: string
82
+ default: "Type a message..."
83
+ reflect: true
84
+ model:
85
+ description: |
86
+ Currently selected model value (reflected, two-way with inner
87
+ `<select-ui slot="model">`). Empty when no model picker is shown.
88
+ type: string
89
+ default: ""
90
+ reflect: true
91
+ models:
92
+ description: |
93
+ JSON array of model options for the inner model picker —
94
+ [{value, label}] or [{label, options: [...]}] groups. Empty array
95
+ hides the model picker.
96
+ type: array
97
+ default: []
98
+
99
+ events:
100
+ submit:
101
+ description: >-
102
+ Fires when the user presses Enter (without Shift) in the textarea
103
+ OR clicks the built-in send button. The composer suppresses
104
+ submission while `[loading]` is set.
105
+ detail:
106
+ text:
107
+ type: string
108
+ description: Submitted message text from the inner textarea.
109
+ model:
110
+ type: string
111
+ description: Currently selected model value (empty if no model picker).
112
+
113
+ slots:
114
+ toolbar:
115
+ description: >-
116
+ Override slot for the entire toolbar row (model picker + send
117
+ button). Most authors should NOT override this — the built-in
118
+ toolbar handles model selection + send. Use this only when
119
+ custom toolbar layout is required.
120
+ send:
121
+ description: >-
122
+ Override slot for the send button only. The default stamped send
123
+ button has `[icon="paper-plane-right"] [variant="primary"]`. Most
124
+ authors should NOT override this — the built-in send button IS
125
+ the send control. Adding a separate Button sibling for "send"
126
+ duplicates this functionality.
127
+ model:
128
+ description: >-
129
+ Override slot for the model picker. Most authors should NOT
130
+ override this — set the `models` prop instead.
131
+
132
+ states:
133
+ - name: idle
134
+ description: Default, accepting input. Send button enabled when textarea has content.
135
+ - name: disabled
136
+ attribute: disabled
137
+ description: Input fully disabled (typically during initial form setup).
138
+ - name: loading
139
+ attribute: loading
140
+ description: >-
141
+ LLM is responding. Send button disabled + submit events suppressed,
142
+ but textarea stays editable for follow-up drafts.
143
+
144
+ traits: []
145
+
146
+ a2ui:
147
+ rules:
148
+ - >-
149
+ ChatInput is a self-contained composer — it stamps its own
150
+ textarea + model picker + send button. DO NOT add a separate
151
+ Button sibling for "send" inside the same parent. The user
152
+ gets two send buttons (one built-in, one redundant).
153
+ - >-
154
+ For chat interfaces emit ChatInput as the sole input
155
+ component inside the chat shell. The submit event fires on
156
+ Enter or send-button click; `detail` is `{ text, model }`.
157
+ - >-
158
+ To customize models, set the `models` prop with an array of
159
+ {value, label} option objects. Do not stamp a separate Select
160
+ next to ChatInput for model selection — the built-in model
161
+ picker handles it.
162
+
163
+ anti_patterns:
164
+ - description: >-
165
+ Adding a separate Button(primary) sibling next to ChatInput for
166
+ "send". The component stamps its own send button (paper-plane-right
167
+ icon). Two send buttons render side-by-side, confusing users.
168
+ - description: >-
169
+ Stamping a Select next to ChatInput for model picker. The component
170
+ has a built-in model picker driven by the `models` prop. Set
171
+ `models=[{value, label}, ...]` instead of stamping a separate Select.
172
+ - description: >-
173
+ Wrapping ChatInput inside <field-ui label="..."> for label
174
+ association. ChatInput is a composite container, not a form
175
+ field. For a labeled composer surface, use <chat-composer> +
176
+ slot label markup.
177
+
178
+ examples:
179
+ - name: basic-chat-input
180
+ description: >-
181
+ Minimal chat input — placeholder, no model picker. Sits inside
182
+ a chat shell footer.
183
+ a2ui: |-
184
+ [
185
+ {"id": "root", "component": "ChatInput", "placeholder": "Ask me anything..."}
186
+ ]
187
+ - name: chat-input-with-models
188
+ description: >-
189
+ Chat input with model selection. The `models` prop drives the
190
+ built-in model picker — DO NOT add a separate Select sibling.
191
+ a2ui: |-
192
+ [
193
+ {
194
+ "id": "root",
195
+ "component": "ChatInput",
196
+ "placeholder": "Type a message...",
197
+ "model": "claude-opus-4-7",
198
+ "models": [
199
+ {"value": "claude-opus-4-7", "label": "Claude Opus 4.7"},
200
+ {"value": "claude-haiku-4-5", "label": "Claude Haiku 4.5"},
201
+ {"value": "claude-sonnet-4-5", "label": "Claude Sonnet 4.5"}
202
+ ]
203
+ }
204
+ ]
205
+ - name: chat-input-loading-state
206
+ description: >-
207
+ Streaming response state. `[loading]` reflects on the host;
208
+ send button disables; textarea stays editable so the user can
209
+ draft a follow-up while the LLM is responding.
210
+ a2ui: |-
211
+ [
212
+ {
213
+ "id": "root",
214
+ "component": "ChatInput",
215
+ "placeholder": "Drafting follow-up while streaming...",
216
+ "loading": true
217
+ }
218
+ ]
219
+ - name: chat-shell-with-chat-input
220
+ description: >-
221
+ Full chat shell — header + thread + ChatInput in the footer.
222
+ Note: ChatInput is the sole footer child; no separate send
223
+ button.
224
+ a2ui: |-
225
+ [
226
+ {"id": "root", "component": "ChatShell", "children": ["header", "thread", "input"]},
227
+ {"id": "header", "component": "ChatHeader", "title": "Chat"},
228
+ {"id": "thread", "component": "ChatThread"},
229
+ {"id": "input", "component": "ChatInput", "placeholder": "Send a message..."}
230
+ ]
231
+
232
+ keywords:
233
+ - chat-input
234
+ - chat
235
+ - message-input
236
+ - composer
237
+ - send-message
238
+ - conversation
239
+ - prompt
240
+ - submit
241
+
242
+ synonyms:
243
+ message-input: [conversation-input, prompt-input, send-bar]
244
+
245
+ related:
246
+ - ChatShell
247
+ - ChatThread
248
+ - ChatComposer
249
+ - ChatHeader
250
+ - TextArea
251
+ - Input
@@ -20,6 +20,7 @@ import { UIFormElement } from '../../core/form.js';
20
20
  import { html } from '../../core/element.js';
21
21
 
22
22
  export class UICheck extends UIFormElement {
23
+ static labelDeprecated = false; // §170 (v0.5.4): label is first-class per check.yaml
23
24
  static properties = {
24
25
  ...UIFormElement.properties,
25
26
  checked: { type: Boolean, default: false, reflect: true },
@@ -123,6 +123,12 @@ function gamutMapChroma(L, C, H) {
123
123
  // ── Component ────────────────────────────────────────────
124
124
 
125
125
  export class UIColorPicker extends UIFormElement {
126
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
127
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
128
+ // across all defined elements. Audited by check-required-icons.mjs
129
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
130
+ static requiredIcons = ['copy'];
131
+
126
132
  static properties = {
127
133
  ...UIFormElement.properties,
128
134
  value: { type: String, default: '#3b82f6', reflect: true },
@@ -95,6 +95,8 @@ states:
95
95
  attribute: disabled
96
96
  traits: []
97
97
  tokens: {}
98
+ requiredIcons:
99
+ - copy
98
100
  a2ui:
99
101
  rules: []
100
102
  anti_patterns: []
@@ -34,6 +34,12 @@ import { UIElement } from '../../core/element.js';
34
34
  * dismiss — Escape pressed
35
35
  */
36
36
  export class UICommand extends UIElement {
37
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
38
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
39
+ // across all defined elements. Audited by check-required-icons.mjs
40
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
41
+ static requiredIcons = ['magnifying-glass'];
42
+
37
43
  static properties = {
38
44
  placeholder: { type: String, default: 'Type a command...', reflect: true },
39
45
  open: { type: Boolean, default: false, reflect: true },
@@ -41,6 +41,8 @@ states:
41
41
  description: Default, ready for interaction.
42
42
  traits: []
43
43
  tokens: {}
44
+ requiredIcons:
45
+ - magnifying-glass
44
46
  a2ui:
45
47
  rules: []
46
48
  anti_patterns: []
@@ -50,6 +50,13 @@ export class UIDrawer extends UIElement {
50
50
  #previousFocus = null;
51
51
  #closeTimer = null;
52
52
  #dialogRef = null;
53
+ // §156 (v0.5.3): track the reason the drawer closed so the dispatched
54
+ // `close` CustomEvent carries `detail.reason`. Set at each entry point
55
+ // (escape → 'escape', backdrop → 'backdrop', close-button → 'close-button',
56
+ // any other path defaults to 'programmatic'). Reset to 'programmatic'
57
+ // after dispatch so a subsequent .open=false from consumer code
58
+ // doesn't carry a stale reason. Per FEEDBACK-06 §2.
59
+ #closeReason = 'programmatic';
53
60
 
54
61
  static properties = {
55
62
  text: { type: String, default: '', reflect: true },
@@ -95,11 +102,15 @@ export class UIDrawer extends UIElement {
95
102
  }
96
103
 
97
104
  #onPress = (e) => {
98
- if (e.target.closest('[slot="close"]')) this.open = false;
105
+ if (e.target.closest('[slot="close"]')) {
106
+ this.#closeReason = 'close-button';
107
+ this.open = false;
108
+ }
99
109
  };
100
110
 
101
111
  #onDialogCancel = (e) => {
102
112
  e.preventDefault();
113
+ this.#closeReason = 'escape';
103
114
  if (!this.permanent) this.open = false;
104
115
  };
105
116
 
@@ -107,11 +118,22 @@ export class UIDrawer extends UIElement {
107
118
  this.open = false;
108
119
  this.#previousFocus?.focus();
109
120
  this.#previousFocus = null;
110
- this.dispatchEvent(new Event('close', { bubbles: true }));
121
+ // §156 (v0.5.3): emit a CustomEvent with typed detail.reason. Capture
122
+ // and reset #closeReason so a subsequent programmatic close doesn't
123
+ // inherit the prior reason. Per FEEDBACK-06 §2.
124
+ const reason = this.#closeReason;
125
+ this.#closeReason = 'programmatic';
126
+ this.dispatchEvent(new CustomEvent('close', {
127
+ detail: { reason },
128
+ bubbles: true,
129
+ }));
111
130
  };
112
131
 
113
132
  #onDialogClick = (e) => {
114
- if (e.target === this.#dialogRef && !this.permanent) this.open = false;
133
+ if (e.target === this.#dialogRef && !this.permanent) {
134
+ this.#closeReason = 'backdrop';
135
+ this.open = false;
136
+ }
115
137
  };
116
138
 
117
139
  connected() {
@@ -63,7 +63,19 @@
63
63
  "composes": [],
64
64
  "events": {
65
65
  "close": {
66
- "description": "Fired when the drawer is dismissed via close button or backdrop click"
66
+ "description": "Fired when the drawer closes via any path (close button, backdrop, Escape key, or programmatic `.open = false`). The `detail.reason` field distinguishes which.",
67
+ "detail": {
68
+ "reason": {
69
+ "description": "`'escape'` (Escape key) / `'backdrop'` (backdrop click) / `'close-button'` ([slot=\"close\"] button click) / `'programmatic'` (consumer set `.open = false` from JS). Defaults to `'programmatic'` when no event-driven path captured a reason. Added v0.5.3 §156 per FEEDBACK-06 §2.",
70
+ "type": "string",
71
+ "enum": [
72
+ "escape",
73
+ "backdrop",
74
+ "close-button",
75
+ "programmatic"
76
+ ]
77
+ }
78
+ }
67
79
  }
68
80
  },
69
81
  "examples": [
@@ -12,7 +12,12 @@
12
12
 
13
13
  import { UIElement } from '../../core/element.js';
14
14
 
15
- export type DrawerCloseEvent = CustomEvent<unknown>;
15
+ export interface DrawerCloseEventDetail {
16
+ /** `'escape'` (Escape key) / `'backdrop'` (backdrop click) / `'close-button'` ([slot="close"] button click) / `'programmatic'` (consumer set `.open = false` from JS). Defaults to `'programmatic'` when no event-driven path captured a reason. Added v0.5.3 §156 per FEEDBACK-06 §2. */
17
+ reason: 'escape' | 'backdrop' | 'close-button' | 'programmatic';
18
+ }
19
+
20
+ export type DrawerCloseEvent = CustomEvent<DrawerCloseEventDetail>;
16
21
 
17
22
  export class UIDrawer extends UIElement {
18
23
  /** Controls visibility. When false, backdrop and panel are removed from DOM. */
@@ -49,7 +49,17 @@ props:
49
49
  default: ""
50
50
  events:
51
51
  close:
52
- description: Fired when the drawer is dismissed via close button or backdrop click
52
+ description: Fired when the drawer closes via any path (close button, backdrop, Escape key, or programmatic `.open = false`). The `detail.reason` field distinguishes which.
53
+ detail:
54
+ reason:
55
+ type: string
56
+ enum: [escape, backdrop, close-button, programmatic]
57
+ description: >-
58
+ `'escape'` (Escape key) / `'backdrop'` (backdrop click) /
59
+ `'close-button'` ([slot="close"] button click) /
60
+ `'programmatic'` (consumer set `.open = false` from JS).
61
+ Defaults to `'programmatic'` when no event-driven path
62
+ captured a reason. Added v0.5.3 §156 per FEEDBACK-06 §2.
53
63
  slots:
54
64
  backdrop:
55
65
  description: Scrim overlay behind the drawer (stamped by the component).
@@ -0,0 +1,86 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/FeedItem.json",
4
+ "title": "FeedItem",
5
+ "description": "Atomic feed entry inside a `<feed-ui>` lane. Three dismiss policies are inferred from the prop shape — auto-fade (duration > 0 + no action), sticky-dismissible (duration null/0), action-required (duration null/0 + action; future phase). Posted via `UIFeed.post()` rather than authored directly.",
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": "FeedItem"
18
+ },
19
+ "dismissible": {
20
+ "description": "Render an x close button (default true for sticky, false for auto-fade)",
21
+ "type": "boolean",
22
+ "default": false
23
+ },
24
+ "duration": {
25
+ "description": "Auto-fade timer in ms; null/0 = sticky (requires user input)",
26
+ "type": "number",
27
+ "default": 4000
28
+ },
29
+ "heading": {
30
+ "description": "Optional emphasis line above text",
31
+ "type": "string",
32
+ "default": ""
33
+ },
34
+ "icon": {
35
+ "description": "Optional leading icon name",
36
+ "type": "string",
37
+ "default": ""
38
+ },
39
+ "text": {
40
+ "description": "Body copy",
41
+ "type": "string",
42
+ "default": ""
43
+ },
44
+ "variant": {
45
+ "description": "Semantic variant",
46
+ "type": "string",
47
+ "enum": [
48
+ "default",
49
+ "info",
50
+ "success",
51
+ "warning",
52
+ "danger"
53
+ ],
54
+ "default": "default"
55
+ }
56
+ },
57
+ "required": [
58
+ "component"
59
+ ],
60
+ "unevaluatedProperties": false,
61
+ "x-adiaui": {
62
+ "anti_patterns": [],
63
+ "category": "feedback",
64
+ "composes": [],
65
+ "events": {
66
+ "close": {
67
+ "description": "Fired after the item finishes its exit animation"
68
+ }
69
+ },
70
+ "examples": [],
71
+ "keywords": [],
72
+ "name": "UIFeedItem",
73
+ "related": [],
74
+ "slots": {
75
+ "body": {
76
+ "description": "Default content slot (also accepts the `text` / `heading` props)"
77
+ }
78
+ },
79
+ "states": {},
80
+ "synonyms": {},
81
+ "tag": "feed-item-ui",
82
+ "tokens": {},
83
+ "traits": [],
84
+ "version": 1
85
+ }
86
+ }
@@ -47,6 +47,12 @@
47
47
  import { UIElement } from '../../core/element.js';
48
48
 
49
49
  export class UIPane extends UIElement {
50
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
51
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
52
+ // across all defined elements. Audited by check-required-icons.mjs
53
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
54
+ static requiredIcons = ['caret-right'];
55
+
50
56
  static properties = {
51
57
  collapsed: { type: Boolean, default: false, reflect: true },
52
58
  resizable: { type: Boolean, default: false, reflect: true },
@@ -60,6 +60,8 @@ states:
60
60
  description: Default, ready for interaction.
61
61
  traits: []
62
62
  tokens: {}
63
+ requiredIcons:
64
+ - caret-right
63
65
  a2ui:
64
66
  rules: []
65
67
  anti_patterns: []
@@ -20,6 +20,7 @@ import { UIFormElement } from '../../core/form.js';
20
20
  import { html } from '../../core/element.js';
21
21
 
22
22
  export class UIRadio extends UIFormElement {
23
+ static labelDeprecated = false; // §170 (v0.5.4): label is first-class per radio.yaml
23
24
  static properties = {
24
25
  ...UIFormElement.properties,
25
26
  checked: { type: Boolean, default: false, reflect: true },
@@ -55,11 +55,7 @@
55
55
  "anti_patterns": [],
56
56
  "category": "layout",
57
57
  "composes": [],
58
- "events": {
59
- "drag-end": {
60
- "description": "Fired when a drag completes."
61
- }
62
- },
58
+ "events": {},
63
59
  "examples": [
64
60
  {
65
61
  "description": "Chat interface with message bubbles containing avatar and text pairs, plus an input footer.",
@@ -12,8 +12,6 @@
12
12
 
13
13
  import { UIElement } from '../../core/element.js';
14
14
 
15
- export type RowDragEndEvent = CustomEvent<unknown>;
16
-
17
15
  export class UIRow extends UIElement {
18
16
  /** Align items */
19
17
  align: string;
@@ -27,11 +25,4 @@ export class UIRow extends UIElement {
27
25
  justify: string;
28
26
  /** Enable flex wrap */
29
27
  wrap: boolean;
30
-
31
- addEventListener<K extends keyof HTMLElementEventMap>(
32
- type: K,
33
- listener: (this: UIRow, ev: HTMLElementEventMap[K]) => unknown,
34
- options?: boolean | AddEventListenerOptions,
35
- ): void;
36
- addEventListener(type: 'drag-end', listener: (ev: RowDragEndEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
37
28
  }
@@ -36,9 +36,7 @@ props:
36
36
  description: Enable flex wrap
37
37
  type: boolean
38
38
  default: false
39
- events:
40
- drag-end:
41
- description: "Fired when a drag completes."
39
+ events: {}
42
40
  slots:
43
41
  default:
44
42
  description: "Default slot — primary child content."
@@ -19,6 +19,13 @@ function escapeHTML(s) {
19
19
  }
20
20
 
21
21
  export class UISelect extends UIFormElement {
22
+ static labelDeprecated = false; // §170 (v0.5.4): label is first-class per select.yaml
23
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
24
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
25
+ // across all defined elements. Audited by check-required-icons.mjs
26
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
27
+ static requiredIcons = ['caret-up-down'];
28
+
22
29
  static properties = {
23
30
  ...UIFormElement.properties,
24
31
  placeholder: { type: String, default: 'Select...', reflect: true },
@@ -127,6 +127,8 @@ states:
127
127
  attribute: disabled
128
128
  traits: []
129
129
  tokens: {}
130
+ requiredIcons:
131
+ - caret-up-down
130
132
  a2ui:
131
133
  rules: []
132
134
  anti_patterns: []
@@ -95,6 +95,31 @@ export class UISlider extends UIFormElement {
95
95
  render() {
96
96
  if (!this.#trackEl) return;
97
97
 
98
+ // §153 (v0.5.3): function-typed `.value` runtime guard. Per FEEDBACK-07
99
+ // §3, consumers sometimes pass `() => signal.value * 100` expecting
100
+ // auto-subscribe; AdiaUI's reactive system (template.js `isFn` branch)
101
+ // does wrap functions in effects + call them per dep change, but the
102
+ // result of `v()` must be a number for `#pct` / `#format` to work.
103
+ // When the function returns non-number (e.g. doesn't read a signal,
104
+ // returns object/string) OR when consumers bypass the template engine
105
+ // (manual `sliderEl.value = someFunction`), `this.value` ends up as
106
+ // the function itself — `#pct` does NaN math, thumb stays at 0%,
107
+ // silent fail. Warn loudly + skip render so the bug class is
108
+ // diagnosable in dev. See USAGE.md "Reactive binding" for the
109
+ // documented patterns.
110
+ if (typeof this.value === 'function') {
111
+ // eslint-disable-next-line no-console
112
+ console.warn(
113
+ '[slider-ui] .value received a function. Did you mean:\n' +
114
+ ' .value=${fn()} ← call the function to get the current value\n' +
115
+ ' .value=${signal.value} ← read the signal\'s current value\n' +
116
+ 'Functions are not auto-invoked at render time; the slider reads\n' +
117
+ '.value synchronously. See USAGE.md "Reactive binding" for the\n' +
118
+ 'parent-template-re-read pattern that works across frameworks.'
119
+ );
120
+ return;
121
+ }
122
+
98
123
  const pct = this.#pct;
99
124
  const fill = this.querySelector('[slot="fill"]');
100
125
  if (fill) fill.style.width = `${pct}%`;
@@ -20,6 +20,7 @@ import { UIFormElement } from '../../core/form.js';
20
20
  import { html } from '../../core/element.js';
21
21
 
22
22
  export class UISwitch extends UIFormElement {
23
+ static labelDeprecated = false; // §170 (v0.5.4): label is first-class per switch.yaml
23
24
  static properties = {
24
25
  ...UIFormElement.properties,
25
26
  checked: { type: Boolean, default: false, reflect: true },
@@ -91,6 +91,12 @@ function csvEscape(val) {
91
91
  // ── Component ────────────────────────────────────────────────────────────────
92
92
 
93
93
  export class UITable extends UIElement {
94
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
95
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
96
+ // across all defined elements. Audited by check-required-icons.mjs
97
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
98
+ static requiredIcons = ['caret-right', 'caret-up-down', 'table'];
99
+
94
100
  static properties = {
95
101
  sortable: { type: Boolean, default: false, reflect: true },
96
102
  selectable: { type: Boolean, default: false, reflect: true },