@adia-ai/web-components 0.5.3 → 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.
@@ -53,6 +53,7 @@ function sameDay(a, b) {
53
53
  }
54
54
 
55
55
  export class UICalendarPicker extends UIFormElement {
56
+ static labelDeprecated = false; // §170 (v0.5.4): label is first-class per calendar-picker.yaml
56
57
  // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
57
58
  // consumer markup). Aggregated by installIconLoadersForRegistered()
58
59
  // across all defined elements. Audited by check-required-icons.mjs
@@ -0,0 +1,158 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/ChatInput.json",
4
+ "title": "ChatInput",
5
+ "description": "Composable chat input bar — a self-contained chat-message composer\nthat stamps its OWN inner structure (textarea + model picker + send\nbutton) when authored as a bare `<chat-input-ui>` tag.\n\nIMPORTANT (closes the 2026-05-14 redundant-send-button class): the\ncomponent stamps a built-in send button (paper-plane-right icon,\nprimary variant). DO NOT add a separate Button sibling for \"send\" —\nthe user gets two send buttons. The submit event fires on Enter or\nsend-button click; `detail` is `{ text, model }`.\n\nInner stamped structure (default):\n <chat-input-ui>\n <textarea-ui placeholder=\"Type a message...\" rows=\"1\"></textarea-ui>\n <div slot=\"toolbar\">\n <select-ui slot=\"model\" placeholder=\"Model\">...</select-ui>\n <button-ui icon=\"paper-plane-right\" variant=\"primary\" slot=\"send\"></button-ui>\n </div>\n </chat-input-ui>\n\nLayout:\n ┌──────────────────────────────────┐\n │ textarea (grows vertically) │\n ├──────────────────────────────────┤\n │ [model ▾] [⏎ send] │ ← toolbar (model picker + built-in send)\n └──────────────────────────────────┘\n\nComposite wrapper, not a form field itself. The inner textarea-ui\nis form-associated via UIFormElement and submits through the parent\nform. `chat-input-ui`'s `disabled` / `placeholder` props propagate\nto the inner textarea.\n\nFor module-tier composer surfaces (slot vocabulary for file attach,\nautocomplete, trailing/leading controls), wrap inside\n`<chat-composer>` — see ChatComposer for the module-tier shape.\n",
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": "ChatInput"
18
+ },
19
+ "disabled": {
20
+ "description": "Disable the entire input. Textarea becomes contenteditable=false;\nsend button disabled.\n",
21
+ "type": "boolean",
22
+ "default": false
23
+ },
24
+ "loading": {
25
+ "description": "In-flight / streaming state. Send button disabled, submit events\nsuppressed, but textarea stays editable so the user can draft a\nfollow-up while the model is still responding.\n",
26
+ "type": "boolean",
27
+ "default": false
28
+ },
29
+ "model": {
30
+ "description": "Currently selected model value (reflected, two-way with inner\n`<select-ui slot=\"model\">`). Empty when no model picker is shown.\n",
31
+ "type": "string",
32
+ "default": ""
33
+ },
34
+ "models": {
35
+ "description": "JSON array of model options for the inner model picker —\n[{value, label}] or [{label, options: [...]}] groups. Empty array\nhides the model picker.\n",
36
+ "type": "array",
37
+ "default": []
38
+ },
39
+ "placeholder": {
40
+ "description": "Textarea placeholder. Defaults to \"Type a message...\".",
41
+ "type": "string",
42
+ "default": "Type a message..."
43
+ }
44
+ },
45
+ "required": [
46
+ "component"
47
+ ],
48
+ "unevaluatedProperties": false,
49
+ "x-adiaui": {
50
+ "anti_patterns": [
51
+ {
52
+ "description": "Adding a separate Button(primary) sibling next to ChatInput for \"send\". The component stamps its own send button (paper-plane-right icon). Two send buttons render side-by-side, confusing users."
53
+ },
54
+ {
55
+ "description": "Stamping a Select next to ChatInput for model picker. The component has a built-in model picker driven by the `models` prop. Set `models=[{value, label}, ...]` instead of stamping a separate Select."
56
+ },
57
+ {
58
+ "description": "Wrapping ChatInput inside <field-ui label=\"...\"> for label association. ChatInput is a composite container, not a form field. For a labeled composer surface, use <chat-composer> + slot label markup."
59
+ }
60
+ ],
61
+ "category": "agent",
62
+ "composes": [],
63
+ "events": {
64
+ "submit": {
65
+ "description": "Fires when the user presses Enter (without Shift) in the textarea OR clicks the built-in send button. The composer suppresses submission while `[loading]` is set.",
66
+ "detail": {
67
+ "model": {
68
+ "description": "Currently selected model value (empty if no model picker).",
69
+ "type": "string"
70
+ },
71
+ "text": {
72
+ "description": "Submitted message text from the inner textarea.",
73
+ "type": "string"
74
+ }
75
+ }
76
+ }
77
+ },
78
+ "examples": [
79
+ {
80
+ "description": "Minimal chat input — placeholder, no model picker. Sits inside a chat shell footer.",
81
+ "a2ui": "[\n {\"id\": \"root\", \"component\": \"ChatInput\", \"placeholder\": \"Ask me anything...\"}\n]",
82
+ "name": "basic-chat-input"
83
+ },
84
+ {
85
+ "description": "Chat input with model selection. The `models` prop drives the built-in model picker — DO NOT add a separate Select sibling.",
86
+ "a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"ChatInput\",\n \"placeholder\": \"Type a message...\",\n \"model\": \"claude-opus-4-7\",\n \"models\": [\n {\"value\": \"claude-opus-4-7\", \"label\": \"Claude Opus 4.7\"},\n {\"value\": \"claude-haiku-4-5\", \"label\": \"Claude Haiku 4.5\"},\n {\"value\": \"claude-sonnet-4-5\", \"label\": \"Claude Sonnet 4.5\"}\n ]\n }\n]",
87
+ "name": "chat-input-with-models"
88
+ },
89
+ {
90
+ "description": "Streaming response state. `[loading]` reflects on the host; send button disables; textarea stays editable so the user can draft a follow-up while the LLM is responding.",
91
+ "a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"ChatInput\",\n \"placeholder\": \"Drafting follow-up while streaming...\",\n \"loading\": true\n }\n]",
92
+ "name": "chat-input-loading-state"
93
+ },
94
+ {
95
+ "description": "Full chat shell — header + thread + ChatInput in the footer. Note: ChatInput is the sole footer child; no separate send button.",
96
+ "a2ui": "[\n {\"id\": \"root\", \"component\": \"ChatShell\", \"children\": [\"header\", \"thread\", \"input\"]},\n {\"id\": \"header\", \"component\": \"ChatHeader\", \"title\": \"Chat\"},\n {\"id\": \"thread\", \"component\": \"ChatThread\"},\n {\"id\": \"input\", \"component\": \"ChatInput\", \"placeholder\": \"Send a message...\"}\n]",
97
+ "name": "chat-shell-with-chat-input"
98
+ }
99
+ ],
100
+ "keywords": [
101
+ "chat-input",
102
+ "chat",
103
+ "message-input",
104
+ "composer",
105
+ "send-message",
106
+ "conversation",
107
+ "prompt",
108
+ "submit"
109
+ ],
110
+ "name": "UIChatInput",
111
+ "related": [
112
+ "ChatShell",
113
+ "ChatThread",
114
+ "ChatComposer",
115
+ "ChatHeader",
116
+ "TextArea",
117
+ "Input"
118
+ ],
119
+ "slots": {
120
+ "model": {
121
+ "description": "Override slot for the model picker. Most authors should NOT override this — set the `models` prop instead."
122
+ },
123
+ "send": {
124
+ "description": "Override slot for the send button only. The default stamped send button has `[icon=\"paper-plane-right\"] [variant=\"primary\"]`. Most authors should NOT override this — the built-in send button IS the send control. Adding a separate Button sibling for \"send\" duplicates this functionality."
125
+ },
126
+ "toolbar": {
127
+ "description": "Override slot for the entire toolbar row (model picker + send button). Most authors should NOT override this — the built-in toolbar handles model selection + send. Use this only when custom toolbar layout is required."
128
+ }
129
+ },
130
+ "states": [
131
+ {
132
+ "description": "Default, accepting input. Send button enabled when textarea has content.",
133
+ "name": "idle"
134
+ },
135
+ {
136
+ "description": "Input fully disabled (typically during initial form setup).",
137
+ "attribute": "disabled",
138
+ "name": "disabled"
139
+ },
140
+ {
141
+ "description": "LLM is responding. Send button disabled + submit events suppressed, but textarea stays editable for follow-up drafts.",
142
+ "attribute": "loading",
143
+ "name": "loading"
144
+ }
145
+ ],
146
+ "synonyms": {
147
+ "message-input": [
148
+ "conversation-input",
149
+ "prompt-input",
150
+ "send-bar"
151
+ ]
152
+ },
153
+ "tag": "chat-input-ui",
154
+ "tokens": {},
155
+ "traits": [],
156
+ "version": 1
157
+ }
158
+ }
@@ -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 },
@@ -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
+ }
@@ -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 },
@@ -19,6 +19,7 @@ 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
22
23
  // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
23
24
  // consumer markup). Aggregated by installIconLoadersForRegistered()
24
25
  // across all defined elements. Audited by check-required-icons.mjs
@@ -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 },
@@ -20,6 +20,7 @@
20
20
  import { UIFormElement } from '../../core/form.js';
21
21
 
22
22
  export class UITextarea extends UIFormElement {
23
+ static labelDeprecated = false; // §170 (v0.5.4): label is first-class per textarea.yaml
23
24
  static properties = {
24
25
  ...UIFormElement.properties,
25
26
  placeholder: { type: String, default: '', reflect: true },
@@ -20,6 +20,7 @@
20
20
  import { UIFormElement } from '../../core/form.js';
21
21
 
22
22
  export class UIUpload extends UIFormElement {
23
+ static labelDeprecated = false; // §170 (v0.5.4): label is first-class per upload.yaml
23
24
  static properties = {
24
25
  ...UIFormElement.properties,
25
26
  label: { type: String, default: '', reflect: true },
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Icon registry — pluggable lookup map for `<icon-ui>` SVG resolution.
3
+ *
4
+ * Three consumer paths, picked by bundle-size budget:
5
+ *
6
+ * 1. **Zero-config** — import the main barrel; phosphor side-effect-loads
7
+ * via `core/icons-phosphor.js`. Largest bundle (~9 000 chunks under
8
+ * `vite build`). Best for prototypes.
9
+ *
10
+ * 2. **Opt-in phosphor with piecemeal component imports** — when you
11
+ * `import '@adia-ai/web-components/components/button'` etc. you skip
12
+ * the barrel's phosphor side-effect. Add it explicitly:
13
+ * import '@adia-ai/web-components/core/icons-phosphor';
14
+ *
15
+ * 3. **Scoped registration (smallest bundle)** — install only the icons
16
+ * you use via `installIconLoaders()` with a brace-list glob, OR
17
+ * `installIconLoadersForRegistered()` to auto-discover from defined
18
+ * primitives' `static requiredIcons`.
19
+ *
20
+ * Runtime exports mirror `core/icons.js`. `installIconLoadersForRegistered`
21
+ * shipped in v0.5.3 §154 (FEEDBACK-06 §4 + FEEDBACK-07 §4). `.d.ts`
22
+ * authored in v0.5.4 to close the missing-type-surface gap that blocked
23
+ * TypeScript consumers from importing the helper.
24
+ *
25
+ * @see ../USAGE.md (consumer guide)
26
+ * @see ./icons-phosphor.js (the zero-config side-effect path)
27
+ */
28
+
29
+ /**
30
+ * Phosphor weight identifiers. `regular` is the default; the other five
31
+ * weights resolve to the corresponding `@phosphor-icons/core/assets/<weight>/`
32
+ * subtree when consumers install loaders for them.
33
+ */
34
+ export const ICON_WEIGHTS: ReadonlyArray<'regular' | 'thin' | 'light' | 'bold' | 'fill' | 'duotone'>;
35
+
36
+ /** A single phosphor weight identifier. */
37
+ export type IconWeight = 'regular' | 'thin' | 'light' | 'bold' | 'fill' | 'duotone';
38
+
39
+ /**
40
+ * Loader value in an icon-loader map. Either the SVG string itself (eager
41
+ * `import.meta.glob({ eager: true })` output) or a thunk that resolves to
42
+ * the SVG string (lazy / dynamic import).
43
+ */
44
+ export type IconLoader = string | (() => string | Promise<string>);
45
+
46
+ /**
47
+ * Per-path loader map for a single weight. Keys are typically the
48
+ * `/node_modules/@phosphor-icons/core/assets/<weight>/<filename>.svg` paths
49
+ * `import.meta.glob` produces — but any keys work (§140's filename-suffix
50
+ * fallback handles pnpm / yarn-berry / custom-Vite-root prefixes).
51
+ */
52
+ export type IconLoaderMap = Record<string, IconLoader>;
53
+
54
+ /**
55
+ * Per-weight loader maps. Pass to `installIconLoaders()` /
56
+ * `installIconLoadersForRegistered()`. Most consumers pass only `regular`;
57
+ * weight-overloaded usage (e.g. `<icon-ui name="star" weight="fill">`)
58
+ * needs the corresponding weight populated.
59
+ */
60
+ export interface WeightModules {
61
+ regular?: IconLoaderMap;
62
+ thin?: IconLoaderMap;
63
+ light?: IconLoaderMap;
64
+ bold?: IconLoaderMap;
65
+ fill?: IconLoaderMap;
66
+ duotone?: IconLoaderMap;
67
+ }
68
+
69
+ /** Register a single icon by name. Bypasses loader-map lookup entirely. */
70
+ export function registerIcon(name: string, svg: string, weight?: IconWeight): void;
71
+
72
+ /** Register multiple icons at a single weight. Bypasses loader-map lookup. */
73
+ export function registerIcons(icons: Record<string, string>, weight?: IconWeight): void;
74
+
75
+ /**
76
+ * Get icon SVG by name + weight. Returns the cached SVG if present;
77
+ * otherwise triggers an async load (returning `''` immediately) + the
78
+ * registered `<icon-ui>` elements re-render once the load resolves.
79
+ */
80
+ export function getIcon(name: string, weight?: IconWeight): string;
81
+
82
+ /** Sync check — is `name` cached in the registry at this weight? */
83
+ export function hasIcon(name: string, weight?: IconWeight): boolean;
84
+
85
+ /** List all cached `weight:name` keys in the registry. */
86
+ export function listIcons(): string[];
87
+
88
+ /**
89
+ * Sync check — is `name` (or its `ICON_ALIASES` alias) a known icon at
90
+ * the given weight? Doesn't trigger loading. Used by primitives that
91
+ * accept either an icon name or a text label (`<input-ui prefix>`,
92
+ * `<input-ui suffix>`) to disambiguate.
93
+ */
94
+ export function isIconName(name: string, weight?: IconWeight): boolean;
95
+
96
+ /**
97
+ * Resolves once the icon registry is sync-checkable — i.e., after the
98
+ * first `installIconLoaders()` call. Primitives that branch on
99
+ * `isIconName` at connect time should await this to avoid the static-
100
+ * deploy race (the registry starts empty, fills async via manifest fetch).
101
+ */
102
+ export const whenIconRegistryReady: Promise<void>;
103
+
104
+ /**
105
+ * Install a per-weight loader map. Idempotent — subsequent calls replace
106
+ * the previous map. After install, re-stamps any existing
107
+ * `<icon-ui[name]>` elements so they re-render with the newly-loadable
108
+ * SVGs.
109
+ *
110
+ * @param modules per-weight maps; missing weights stay empty.
111
+ */
112
+ export function installIconLoaders(modules: WeightModules): void;
113
+
114
+ /**
115
+ * Auto-discover the icon set from defined primitives' `static
116
+ * requiredIcons` declarations, then install loaders.
117
+ *
118
+ * Each AdiaUI primitive that auto-stamps icons declares them via:
119
+ *
120
+ * export class UISelect extends UIFormElement {
121
+ * static requiredIcons = ['caret-up-down'];
122
+ * // ...
123
+ * }
124
+ *
125
+ * This helper aggregates the union across all currently-defined
126
+ * primitives (post-side-effect imports) and installs `modules` as the
127
+ * loader source. The returned list is the discovered set — useful for
128
+ * trimming a wide glob to only what's needed.
129
+ *
130
+ * Shipped v0.5.3 §154 (FEEDBACK-06 §4 + FEEDBACK-07 §4).
131
+ *
132
+ * @param modules - per-weight loader maps (typically `import.meta.glob`
133
+ * output for `@phosphor-icons/core/assets/<weight>/*.svg`)
134
+ * @returns the union of `static requiredIcons` across all defined primitives
135
+ */
136
+ export function installIconLoadersForRegistered(
137
+ modules: IconLoaderMap | WeightModules
138
+ ): string[];
139
+
140
+ export namespace installIconLoadersForRegistered {
141
+ /**
142
+ * Same-shape companion to `installIconLoadersForRegistered` but
143
+ * doesn't install. Returns the union of `static requiredIcons` across
144
+ * all currently-defined primitives — useful when you want to build a
145
+ * narrower glob from the discovered list.
146
+ */
147
+ function discover(): string[];
148
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-components",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "AdiaUI web components — vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-runtime.",
5
5
  "type": "module",
6
6
  "types": "./index.d.ts",