@adia-ai/web-components 0.5.1 → 0.5.3

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 (50) 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 +6 -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/color-picker/class.js +6 -0
  21. package/components/color-picker/color-picker.yaml +2 -0
  22. package/components/command/class.js +6 -0
  23. package/components/command/command.yaml +2 -0
  24. package/components/drawer/class.js +25 -3
  25. package/components/drawer/drawer.a2ui.json +13 -1
  26. package/components/drawer/drawer.d.ts +6 -1
  27. package/components/drawer/drawer.yaml +11 -1
  28. package/components/pane/class.js +6 -0
  29. package/components/pane/pane.yaml +2 -0
  30. package/components/row/row.a2ui.json +1 -5
  31. package/components/row/row.d.ts +0 -9
  32. package/components/row/row.yaml +1 -3
  33. package/components/select/class.js +6 -0
  34. package/components/select/select.yaml +2 -0
  35. package/components/slider/class.js +25 -0
  36. package/components/table/class.js +6 -0
  37. package/components/table/table.a2ui.json +13 -0
  38. package/components/table/table.d.ts +9 -0
  39. package/components/table/table.yaml +13 -0
  40. package/components/tag/class.js +6 -0
  41. package/components/tag/tag.yaml +2 -0
  42. package/components/toast/toast.a2ui.json +1 -5
  43. package/components/toast/toast.d.ts +0 -9
  44. package/components/toast/toast.yaml +1 -3
  45. package/components/tree/class.js +6 -0
  46. package/components/tree/tree.yaml +2 -0
  47. package/core/icons.js +148 -5
  48. package/core/icons.test.js +187 -0
  49. package/core/template.js +59 -3
  50. package/package.json +16 -2
package/USAGE.md CHANGED
@@ -134,6 +134,33 @@ const tpl3 = html`<slider-ui .value=${minL.value * 100} min="0" max="100"></slid
134
134
 
135
135
  The `.prop=` syntax sets the JS property (not the attribute). The `attr=` syntax sets an attribute. Both round-trip through the signal-backed setter, so either works for reactive updates from your side.
136
136
 
137
+ ### Template attribute bindings — full / property / no-partial (§152, v0.5.3)
138
+
139
+ The `html\`` template literal supports three attribute-binding modes:
140
+
141
+ | Syntax | Behavior |
142
+ |---|---|
143
+ | `attr="${value}"` (or just `attr=${value}`) | **Full attribute replacement.** The entire attribute value is the placeholder. The string `value` becomes the attribute value via `setAttribute()`. Works for any string-typed attribute. |
144
+ | `.prop=${value}` (leading dot) | **Property assignment.** Sets `element.prop = value` directly (bypasses attributes). Preferred for objects, functions, arrays, signals — anything that doesn't serialize to a string. |
145
+ | `attr="prefix-${value}-suffix"` | **NOT SUPPORTED.** Partial interpolation is not implemented — the attribute-part contract is whole-value-only. The framework will `console.warn` at scan time. Compute the full attribute value as a single expression and interpolate the whole. |
146
+
147
+ Example of the partial-interpolation pitfall and its fix:
148
+
149
+ ```js
150
+ // ❌ NOT SUPPORTED — partial attribute interpolation
151
+ // Will console.warn at scan time + the `{{p:0}}` placeholder text reaches the DOM as literal.
152
+ html`<div style="background:${bgCss}; color:${labelColor}">…</div>`;
153
+
154
+ // ✅ Compute the full attribute as one expression
155
+ const swatchStyle = `background:${bgCss}; color:${labelColor};`;
156
+ html`<div style="${swatchStyle}">…</div>`;
157
+
158
+ // ✅ Or use property binding with an object (CSSStyleDeclaration-like)
159
+ html`<div .style=${{ background: bgCss, color: labelColor }}>…</div>`;
160
+ ```
161
+
162
+ **Why the constraint exists**: the engine pre-processes static-string chunks to insert placeholder markers (`{{p:N}}` for attribute positions, `<!--p:N-->` for content positions). At scan time, the post-substitution attribute value must match `^\{\{p:N\}\}$` for the placeholder-to-part mapping to register. Sub-string placeholders break this contract. The runtime guard (added v0.5.3 §152) makes the failure mode explicit instead of silent.
163
+
137
164
  ### Using React, Lit, Vue, Svelte, etc.
138
165
 
139
166
  When the binding system isn't AdiaUI's `html` tag, the rule generalizes:
@@ -186,6 +213,21 @@ html`<slider-ui .value=${(v) => v * 100} />`
186
213
 
187
214
  The rule: **inside `.prop=${…}`, anything that reads `.value` directly is a snapshot. Wrap in `() => …` to get reactivity.**
188
215
 
216
+ **Dev-mode guard (v0.5.3 §153 / FEEDBACK-07 §3):** primitives that read their value synchronously (e.g. `<slider-ui>`) emit a `console.warn` when their `.value` is assigned a function — this catches the case where the function wasn't wrapped by the template engine (e.g. assigned imperatively `sliderEl.value = fn`, or returned by a framework binding that doesn't recognize the AdiaUI reactivity contract). The warn names the two correct patterns:
217
+
218
+ ```
219
+ [slider-ui] .value received a function. Did you mean:
220
+ .value=${fn()} ← call the function to get the current value
221
+ .value=${signal.value} ← read the signal's current value
222
+ Functions are not auto-invoked at render time; the slider reads
223
+ .value synchronously. See USAGE.md "Reactive binding" for the
224
+ parent-template-re-read pattern that works across frameworks.
225
+ ```
226
+
227
+ If you see this warn in dev console, either:
228
+ 1. You're using AdiaUI's `html\``: ensure the function reads at least one signal value (otherwise the function isn't reactive, just expensive). Pattern A above with `() => signal.value * 100` is correct.
229
+ 2. You're using a different framework (React/Lit/Vue): the binding system isn't AdiaUI's `html` engine, so functions don't auto-wrap. Use Pattern B from the parent-template-re-read recipe — read the signal in the parent's render, pass the extracted value imperatively or via your framework's binding.
230
+
189
231
  For more complex derived values, `computed()` gives you a named reactive holder you can pass directly or read in templates:
190
232
 
191
233
  ```js
@@ -33,8 +33,14 @@
33
33
  "icon-ui"
34
34
  ],
35
35
  "events": {
36
- "change": {
37
- "description": "Fired when the set of open panels changes"
36
+ "toggle": {
37
+ "description": "Fired when the accordion panel opens or closes (single-pane accordion-ui dispatch — multi-pane group emits the same event per child).",
38
+ "detail": {
39
+ "open": {
40
+ "description": "New open state of the panel.",
41
+ "type": "boolean"
42
+ }
43
+ }
38
44
  }
39
45
  },
40
46
  "examples": [
@@ -12,7 +12,12 @@
12
12
 
13
13
  import { UIElement } from '../../core/element.js';
14
14
 
15
- export type AccordionChangeEvent = CustomEvent<unknown>;
15
+ export interface AccordionToggleEventDetail {
16
+ /** New open state of the panel. */
17
+ open: boolean;
18
+ }
19
+
20
+ export type AccordionToggleEvent = CustomEvent<AccordionToggleEventDetail>;
16
21
 
17
22
  export class UIAccordion extends UIElement {
18
23
  /** Allow multiple panels to be open simultaneously */
@@ -23,5 +28,5 @@ export class UIAccordion extends UIElement {
23
28
  listener: (this: UIAccordion, ev: HTMLElementEventMap[K]) => unknown,
24
29
  options?: boolean | AddEventListenerOptions,
25
30
  ): void;
26
- addEventListener(type: 'change', listener: (ev: AccordionChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
31
+ addEventListener(type: 'toggle', listener: (ev: AccordionToggleEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
27
32
  }
@@ -17,8 +17,12 @@ props:
17
17
  type: boolean
18
18
  default: false
19
19
  events:
20
- change:
21
- description: Fired when the set of open panels changes
20
+ toggle:
21
+ description: Fired when the accordion panel opens or closes (single-pane accordion-ui dispatch — multi-pane group emits the same event per child).
22
+ detail:
23
+ open:
24
+ type: boolean
25
+ description: New open state of the panel.
22
26
  slots:
23
27
  default:
24
28
  description: pane-ui children
@@ -27,6 +31,8 @@ states:
27
31
  description: Default, ready for interaction.
28
32
  traits: []
29
33
  tokens: {}
34
+ requiredIcons:
35
+ - caret-down
30
36
  a2ui:
31
37
  rules: []
32
38
  anti_patterns: []
@@ -30,6 +30,12 @@ import { UIElement } from '../../core/element.js';
30
30
  // ── Accordion Container ────────────────────────────────────────
31
31
 
32
32
  export class UIAccordion extends UIElement {
33
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
34
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
35
+ // across all defined elements. Audited by check-required-icons.mjs
36
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
37
+ static requiredIcons = ['caret-down'];
38
+
33
39
  static properties = {
34
40
  multiple: { type: Boolean, default: false, reflect: true },
35
41
  };
@@ -47,6 +47,8 @@ states:
47
47
  description: Default, ready for interaction.
48
48
  traits: []
49
49
  tokens: {}
50
+ requiredIcons:
51
+ - check
50
52
  a2ui:
51
53
  rules: []
52
54
  anti_patterns: []
@@ -50,6 +50,12 @@ function escapeAttr(s) {
50
50
  }
51
51
 
52
52
  export class UIAgentQuestions extends UIElement {
53
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
54
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
55
+ // across all defined elements. Audited by check-required-icons.mjs
56
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
57
+ static requiredIcons = ['check'];
58
+
53
59
  static properties = {
54
60
  multi: { type: Boolean, default: false, reflect: true },
55
61
  question: { type: String, default: '', reflect: true },
@@ -62,6 +62,11 @@ states:
62
62
  description: Default, ready for interaction.
63
63
  traits: []
64
64
  tokens: {}
65
+ requiredIcons:
66
+ - check-circle
67
+ - circle
68
+ - warning
69
+ - warning-circle
65
70
  a2ui:
66
71
  rules: []
67
72
  anti_patterns: []
@@ -77,6 +77,12 @@ function formatDuration(ms) {
77
77
  }
78
78
 
79
79
  export class UIAgentReasoning extends UIElement {
80
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
81
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
82
+ // across all defined elements. Audited by check-required-icons.mjs
83
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
84
+ static requiredIcons = ['check-circle', 'circle', 'warning', 'warning-circle'];
85
+
80
86
  static properties = {
81
87
  collapsed: { type: Boolean, default: false, reflect: true },
82
88
  noAutocollapse: { type: Boolean, default: false, reflect: true, attribute: 'no-autocollapse' },
@@ -37,6 +37,12 @@
37
37
  import { UIElement } from '../../core/element.js';
38
38
 
39
39
  class UIAgentTrace extends UIElement {
40
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
41
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
42
+ // across all defined elements. Audited by check-required-icons.mjs
43
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
44
+ static requiredIcons = ['caret-right', 'circle-fill'];
45
+
40
46
  static properties = {
41
47
  // agent-* family is default-visible disclosure (see component-token-contract.md
42
48
  // §"Toggle state naming"). Use `collapsed` to hide; bare element shows.
@@ -32,6 +32,9 @@ states:
32
32
  description: Default, ready for interaction.
33
33
  traits: []
34
34
  tokens: {}
35
+ requiredIcons:
36
+ - caret-right
37
+ - circle-fill
35
38
  a2ui:
36
39
  rules: []
37
40
  anti_patterns: []
@@ -71,6 +71,10 @@ states:
71
71
  attribute: disabled
72
72
  traits: []
73
73
  tokens: {}
74
+ requiredIcons:
75
+ - calendar
76
+ - caret-left
77
+ - caret-right
74
78
  a2ui:
75
79
  rules: []
76
80
  anti_patterns: []
@@ -53,6 +53,12 @@ function sameDay(a, b) {
53
53
  }
54
54
 
55
55
  export class UICalendarPicker extends UIFormElement {
56
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
57
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
58
+ // across all defined elements. Audited by check-required-icons.mjs
59
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
60
+ static requiredIcons = ['calendar', 'caret-left', 'caret-right'];
61
+
56
62
  static properties = {
57
63
  ...UIFormElement.properties,
58
64
  label: { type: String, default: '', reflect: true },
@@ -32,16 +32,7 @@
32
32
  "composes": [],
33
33
  "events": {
34
34
  "canvas-interaction": {
35
- "description": "Fired on canvas-interaction."
36
- },
37
- "change": {
38
- "description": "Fired when the value changes (on blur for inputs, on selection for pickers)."
39
- },
40
- "click": {
41
- "description": "Fired on pointer click."
42
- },
43
- "input": {
44
- "description": "Fired on each input change during typing."
35
+ "description": "Fired on canvas interactions (focus, blur, pointer events). Dispatched at canvas.js:50 + :167."
45
36
  }
46
37
  },
47
38
  "examples": [
@@ -13,9 +13,6 @@
13
13
  import { UIElement } from '../../core/element.js';
14
14
 
15
15
  export type CanvasInteractionEvent = CustomEvent<unknown>;
16
- export type CanvasChangeEvent = CustomEvent<unknown>;
17
- export type CanvasClickEvent = CustomEvent<unknown>;
18
- export type CanvasInputEvent = CustomEvent<unknown>;
19
16
 
20
17
  export class UICanvas extends UIElement {
21
18
  /** Component property: theme. */
@@ -27,7 +24,4 @@ export class UICanvas extends UIElement {
27
24
  options?: boolean | AddEventListenerOptions,
28
25
  ): void;
29
26
  addEventListener(type: 'canvas-interaction', listener: (ev: CanvasInteractionEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
30
- addEventListener(type: 'change', listener: (ev: CanvasChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
31
- addEventListener(type: 'click', listener: (ev: CanvasClickEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
32
- addEventListener(type: 'input', listener: (ev: CanvasInputEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
33
27
  }
@@ -24,13 +24,7 @@ props:
24
24
  default: ''
25
25
  events:
26
26
  canvas-interaction:
27
- description: Fired on canvas-interaction.
28
- change:
29
- description: Fired when the value changes (on blur for inputs, on selection for pickers).
30
- click:
31
- description: Fired on pointer click.
32
- input:
33
- description: Fired on each input change during typing.
27
+ description: Fired on canvas interactions (focus, blur, pointer events). Dispatched at canvas.js:50 + :167.
34
28
  slots:
35
29
  default:
36
30
  description: Default slot — primary child content.
@@ -76,11 +76,7 @@
76
76
  "anti_patterns": [],
77
77
  "category": "container",
78
78
  "composes": [],
79
- "events": {
80
- "drag-end": {
81
- "description": "Fired when a drag completes."
82
- }
83
- },
79
+ "events": {},
84
80
  "examples": [
85
81
  {
86
82
  "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 CardDragEndEvent = CustomEvent<unknown>;
16
-
17
15
  export class UICard extends UIElement {
18
16
  /** Enables drag handle + cursor:grab. Wires the draggable trait; dispatches drag-end. */
19
17
  draggable: boolean;
@@ -27,11 +25,4 @@ export class UICard extends UIElement {
27
25
  size: 'sm' | 'md' | 'lg';
28
26
  /** Visual style. `outline` is an alias for `outlined`; `flat` removes shadow; `soft`/`primary` apply tinted surfaces. */
29
27
  variant: 'default' | 'outlined' | 'outline' | 'filled' | 'ghost' | 'flat' | 'soft' | 'primary';
30
-
31
- addEventListener<K extends keyof HTMLElementEventMap>(
32
- type: K,
33
- listener: (this: UICard, ev: HTMLElementEventMap[K]) => unknown,
34
- options?: boolean | AddEventListenerOptions,
35
- ): void;
36
- addEventListener(type: 'drag-end', listener: (ev: CardDragEndEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
37
28
  }
@@ -56,9 +56,7 @@ props:
56
56
  - flat
57
57
  - soft
58
58
  - primary
59
- events:
60
- drag-end:
61
- description: Fired when a drag completes.
59
+ events: {}
62
60
  slots: {}
63
61
  states:
64
62
  - name: idle
@@ -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).
@@ -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: []
@@ -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,12 @@ function escapeHTML(s) {
19
19
  }
20
20
 
21
21
  export class UISelect extends UIFormElement {
22
+ // §154 (v0.5.3): Phosphor icons this primitive auto-stamps (without
23
+ // consumer markup). Aggregated by installIconLoadersForRegistered()
24
+ // across all defined elements. Audited by check-required-icons.mjs
25
+ // (slot 11). Per FEEDBACK-06 §4 + FEEDBACK-07 §4.
26
+ static requiredIcons = ['caret-up-down'];
27
+
22
28
  static properties = {
23
29
  ...UIFormElement.properties,
24
30
  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}%`;
@@ -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 },