@adia-ai/web-components 0.6.18 → 0.6.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +88 -0
- package/USAGE.md +6 -0
- package/components/chart/chart.a2ui.json +5 -0
- package/components/chart/chart.d.ts +2 -0
- package/components/chart/chart.yaml +13 -0
- package/components/chart/class.js +15 -0
- package/components/drawer/class.js +60 -2
- package/components/drawer/drawer.a2ui.json +11 -1
- package/components/drawer/drawer.d.ts +2 -0
- package/components/drawer/drawer.yaml +26 -1
- package/components/segmented/class.js +23 -0
- package/components/segmented/segmented.a2ui.json +11 -4
- package/components/segmented/segmented.yaml +52 -6
- package/components/select/class.js +9 -0
- package/components/select/select.a2ui.json +1 -1
- package/components/select/select.yaml +4 -1
- package/components/table/class.js +8 -0
- package/components/table/table.a2ui.json +15 -2
- package/components/table/table.d.ts +11 -2
- package/components/table/table.yaml +11 -2
- package/components/textarea/textarea.a2ui.json +25 -0
- package/components/textarea/textarea.yaml +23 -0
- package/core/element.js +13 -0
- package/css-module.d.ts +6 -0
- package/package.json +18 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,93 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.6.20] — 2026-05-21
|
|
4
|
+
|
|
5
|
+
### Added — `table-ui` `row-click` event (FEEDBACK-35)
|
|
6
|
+
|
|
7
|
+
- `<table-ui>` now dispatches a bubbling `row-click` event (detail:
|
|
8
|
+
`{ row, dataIndex }`) once per row click, alongside the existing per-cell
|
|
9
|
+
`cell-click`. Master-detail / open-flyout wiring no longer has to
|
|
10
|
+
deduplicate `cell-click` by `dataIndex`. `table.yaml` documents it.
|
|
11
|
+
|
|
12
|
+
### Changed — `select-ui` honours declarative `<option selected>` (FEEDBACK-36)
|
|
13
|
+
|
|
14
|
+
- `<select-ui>` now promotes an `<option selected>` child to the initial
|
|
15
|
+
value when the host carries no `[value]` attribute — matching native
|
|
16
|
+
`<select>` semantics. Previously the `selected` attribute was silently
|
|
17
|
+
ignored and the placeholder shown. Guarded on an empty `this.value`, so
|
|
18
|
+
consumers who set `[value]` explicitly are unaffected.
|
|
19
|
+
|
|
20
|
+
### Changed — `chart-ui` wrong-`.data`-shape warning shows the correct shape (FEEDBACK-34)
|
|
21
|
+
|
|
22
|
+
- The one-shot `console.warn` emitted when `.data` is set to a non-array
|
|
23
|
+
now embeds a minimal correct-shape example
|
|
24
|
+
(`[{ label: "Jan", value: 100 }, … ]`) instead of only naming the wrong
|
|
25
|
+
API and pointing at `chart.yaml`.
|
|
26
|
+
|
|
27
|
+
### Docs — `table.yaml` / `select.yaml` description clarifications (FEEDBACK-35 §2, FEEDBACK-36)
|
|
28
|
+
|
|
29
|
+
- `components/table/table.yaml` `sort` event: the `key` / `dir` detail
|
|
30
|
+
descriptions now name the fields explicitly (`key` not `column`, `dir` not
|
|
31
|
+
`direction`) — closing the guess the bare names invited.
|
|
32
|
+
- `components/select/select.yaml` `value`: documents that a declarative
|
|
33
|
+
`<option selected>` child sets the initial value when the host `[value]`
|
|
34
|
+
attribute is absent.
|
|
35
|
+
|
|
36
|
+
## [0.6.19] — 2026-05-21
|
|
37
|
+
|
|
38
|
+
### Fixed — `drawer-ui` `innerHTML`-on-host orphaned the internal `<dialog>` (FEEDBACK-30)
|
|
39
|
+
|
|
40
|
+
- Setting `.innerHTML` on a mounted `<drawer-ui>` wiped the stamped
|
|
41
|
+
`<dialog>` part; `#dialogRef` (bound once in `connected()`) was left
|
|
42
|
+
detached, so `#syncDialog()` called `showModal()` on a detached node and
|
|
43
|
+
the drawer silently never reopened. `render()` now re-binds `#dialogRef`
|
|
44
|
+
+ its listeners when `ensure('dialog')` re-stamps; `#syncDialog()` guards
|
|
45
|
+
`dialog.isConnected`; a failed `showModal()` surfaces a `console.error`.
|
|
46
|
+
`drawer.yaml` gains an `anti_patterns` entry for the innerHTML trap.
|
|
47
|
+
|
|
48
|
+
### Added — `drawer-ui` `opened` event (FEEDBACK-27)
|
|
49
|
+
|
|
50
|
+
- `<drawer-ui>` now dispatches a bubbling `opened` event after the open
|
|
51
|
+
transition completes (mirrors `close`). Lets tests + consumers await full
|
|
52
|
+
interactivity instead of `waitForTimeout` heuristics. `drawer.yaml`
|
|
53
|
+
`events:` documents it.
|
|
54
|
+
|
|
55
|
+
### Added — `chart-ui` `.data` shape docs + wrong-shape warning (FEEDBACK-24)
|
|
56
|
+
|
|
57
|
+
- `chart.yaml` now documents the `data` prop: an array of plain objects
|
|
58
|
+
keyed by the `x`/`y` attributes — not the Chart.js `{labels, datasets}`
|
|
59
|
+
envelope. Setting `.data` to a non-array (the common envelope mistake)
|
|
60
|
+
emits a one-shot `console.warn` instead of silently rendering blank.
|
|
61
|
+
(`x` and `y` were already documented props — the ticket's Finding 2
|
|
62
|
+
claim otherwise was inaccurate.)
|
|
63
|
+
|
|
64
|
+
### Added — `segmented-ui` non-`<segment-ui>`-child warning (FEEDBACK-23)
|
|
65
|
+
|
|
66
|
+
- `<segmented-ui>` emits a one-shot `console.warn` when a direct child is
|
|
67
|
+
not a `<segment-ui>` (a bare `<segment>` renders text but gets no sliding
|
|
68
|
+
indicator, `role`, or `aria-checked`). `segmented.yaml` `description` +
|
|
69
|
+
`slots` now document the `<segment-ui>` child contract.
|
|
70
|
+
|
|
71
|
+
### Changed — `UIElement.ensure()` part-wipe invariant (FEEDBACK-31)
|
|
72
|
+
|
|
73
|
+
- `UIElement.ensure()` (`core/element.js`) marks stamped structural parts
|
|
74
|
+
with `_uiPart = true` and its doc comment states the invariant: call
|
|
75
|
+
`ensure()` from `render()`, never cache a part reference once in
|
|
76
|
+
`connected()` — a host `innerHTML` wipe detaches it.
|
|
77
|
+
|
|
78
|
+
### Added — `types` condition on CSS subpath exports (FEEDBACK-26)
|
|
79
|
+
|
|
80
|
+
- `@adia-ai/web-components/css` and the per-component `.css` subpath exports
|
|
81
|
+
now carry a `types` condition (`css-module.d.ts` stub), so TypeScript
|
|
82
|
+
under `moduleResolution: bundler` no longer raises TS2882 on CSS
|
|
83
|
+
side-effect imports.
|
|
84
|
+
|
|
85
|
+
### Docs — no-`.js`-suffix import rule (FEEDBACK-22)
|
|
86
|
+
|
|
87
|
+
- `USAGE.md` notes that component subpath imports use the directory name
|
|
88
|
+
(`…/components/button`), never the explicit file (`…/button/button.js`) —
|
|
89
|
+
the `exports` wildcard rejects the suffixed form under Vite 8.
|
|
90
|
+
|
|
3
91
|
## [0.6.18] — 2026-05-21
|
|
4
92
|
|
|
5
93
|
### Added — `[data-scheme]` attribute on `toggle-scheme-ui` (FB-14)
|
package/USAGE.md
CHANGED
|
@@ -49,6 +49,12 @@ import '@adia-ai/web-components/components/button';
|
|
|
49
49
|
import '@adia-ai/web-components/components/button.css';
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
> **No `.js` suffix on component subpaths.** Import the directory name —
|
|
53
|
+
> `…/components/button`, never `…/components/button/button.js`. The
|
|
54
|
+
> `exports` wildcard matches only the directory form; appending the
|
|
55
|
+
> explicit filename fails to resolve under Vite 8 (rolldown). The on-disk
|
|
56
|
+
> filename is irrelevant to the subpath specifier.
|
|
57
|
+
|
|
52
58
|
Subpath exports also available:
|
|
53
59
|
|
|
54
60
|
```js
|
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
"component": {
|
|
66
66
|
"const": "Chart"
|
|
67
67
|
},
|
|
68
|
+
"data": {
|
|
69
|
+
"description": "JS property (set programmatically — `el.data = [...]`). An array of plain objects; each object's keys are named by the `x` and `y` attributes — e.g. `<chart-ui x=\"month\" y=\"revenue\">` consumes `[{month:'Jan', revenue:3200}, {month:'Feb', revenue:4100}]`. The Chart.js `{labels, datasets}` envelope is NOT chart-ui's API — passing it (or any non-array value) renders an empty chart. May also be supplied declaratively as a JSON-array `data=\"[…]\"` attribute, hydrated once at connect. Custom accessor on the element class, not a reflected attribute.",
|
|
70
|
+
"type": "array",
|
|
71
|
+
"default": []
|
|
72
|
+
},
|
|
68
73
|
"format": {
|
|
69
74
|
"description": "Number-format mode applied to axis labels + value overlays + donut total + gauge value + treemap value + funnel value + internal tooltip. `abbr` is the legacy 1.2K / 3M format; `decimal` fixes 2 decimals; `currency` prefixes via `--chart-currency-prefix` token (default \"$\"); `percent` multiplies × 100 and adds a % suffix.",
|
|
70
75
|
"type": "string",
|
|
@@ -24,6 +24,8 @@ export class UIChart extends UIElement {
|
|
|
24
24
|
aspect: 'std' | 'wide' | 'square' | 'tall';
|
|
25
25
|
/** Color scheme */
|
|
26
26
|
color: 'accent' | 'success' | 'warning' | 'danger' | 'info';
|
|
27
|
+
/** JS property (set programmatically — `el.data = [...]`). An array of plain objects; each object's keys are named by the `x` and `y` attributes — e.g. `<chart-ui x="month" y="revenue">` consumes `[{month:'Jan', revenue:3200}, {month:'Feb', revenue:4100}]`. The Chart.js `{labels, datasets}` envelope is NOT chart-ui's API — passing it (or any non-array value) renders an empty chart. May also be supplied declaratively as a JSON-array `data="[…]"` attribute, hydrated once at connect. Custom accessor on the element class, not a reflected attribute. */
|
|
28
|
+
data: unknown[];
|
|
27
29
|
/** Number-format mode applied to axis labels + value overlays + donut total + gauge value + treemap value + funnel value + internal tooltip. `abbr` is the legacy 1.2K / 3M format; `decimal` fixes 2 decimals; `currency` prefixes via `--chart-currency-prefix` token (default "$"); `percent` multiplies × 100 and adds a % suffix. */
|
|
28
30
|
format: 'abbr' | 'decimal' | 'currency' | 'percent';
|
|
29
31
|
/** 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. */
|
|
@@ -122,6 +122,19 @@ props:
|
|
|
122
122
|
description: Y-axis key(s), comma-separated for multi-series
|
|
123
123
|
type: string
|
|
124
124
|
default: ""
|
|
125
|
+
data:
|
|
126
|
+
description: >-
|
|
127
|
+
JS property (set programmatically — `el.data = [...]`). An array of
|
|
128
|
+
plain objects; each object's keys are named by the `x` and `y`
|
|
129
|
+
attributes — e.g. `<chart-ui x="month" y="revenue">` consumes
|
|
130
|
+
`[{month:'Jan', revenue:3200}, {month:'Feb', revenue:4100}]`. The
|
|
131
|
+
Chart.js `{labels, datasets}` envelope is NOT chart-ui's API —
|
|
132
|
+
passing it (or any non-array value) renders an empty chart. May also
|
|
133
|
+
be supplied declaratively as a JSON-array `data="[…]"` attribute,
|
|
134
|
+
hydrated once at connect. Custom accessor on the element class, not a
|
|
135
|
+
reflected attribute.
|
|
136
|
+
type: array
|
|
137
|
+
default: []
|
|
125
138
|
events:
|
|
126
139
|
chart-hover:
|
|
127
140
|
description: >-
|
|
@@ -231,6 +231,7 @@ export class UIChart extends UIElement {
|
|
|
231
231
|
static template = () => null;
|
|
232
232
|
|
|
233
233
|
#data = [];
|
|
234
|
+
#shapeWarned = false; // FEEDBACK-24: one-shot wrong-.data-shape warning
|
|
234
235
|
#resizeObs = null;
|
|
235
236
|
#resizeRaf = null;
|
|
236
237
|
#lastW = 0;
|
|
@@ -250,6 +251,20 @@ export class UIChart extends UIElement {
|
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
set data(arr) {
|
|
254
|
+
// FEEDBACK-24: a non-array — typically the Chart.js `{labels, datasets}`
|
|
255
|
+
// envelope — is silently coerced to [] below, producing a blank chart
|
|
256
|
+
// with no diagnostic. Warn once per element.
|
|
257
|
+
if (!Array.isArray(arr) && !this.#shapeWarned) {
|
|
258
|
+
this.#shapeWarned = true;
|
|
259
|
+
// eslint-disable-next-line no-console
|
|
260
|
+
console.warn(
|
|
261
|
+
`[chart-ui] .data must be an array of plain objects — received ` +
|
|
262
|
+
`${arr === null ? 'null' : typeof arr}. The {labels, datasets} ` +
|
|
263
|
+
`envelope is the Chart.js API, not the chart-ui API.\n` +
|
|
264
|
+
`Correct shape for <chart-ui x="label" y="value">:\n` +
|
|
265
|
+
` [{ label: "Jan", value: 100 }, { label: "Feb", value: 200 }]`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
253
268
|
this.#data = Array.isArray(arr) ? arr : [];
|
|
254
269
|
this.#warnIfOverBudget();
|
|
255
270
|
this.#renderChart();
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
* drawer.show() · drawer.hide() · drawer.open = true|false
|
|
40
40
|
*
|
|
41
41
|
* Events:
|
|
42
|
-
* close
|
|
42
|
+
* close — fired after the drawer finishes closing
|
|
43
|
+
* opened — fired after the drawer finishes its open transition
|
|
43
44
|
*/
|
|
44
45
|
|
|
45
46
|
import { UIElement } from '../../core/element.js';
|
|
@@ -49,6 +50,7 @@ export class UIDrawer extends UIElement {
|
|
|
49
50
|
#closing = false;
|
|
50
51
|
#previousFocus = null;
|
|
51
52
|
#closeTimer = null;
|
|
53
|
+
#openTimer = null;
|
|
52
54
|
#dialogRef = null;
|
|
53
55
|
// §156 (v0.5.3): track the reason the drawer closed so the dispatched
|
|
54
56
|
// `close` CustomEvent carries `detail.reason`. Set at each entry point
|
|
@@ -161,6 +163,10 @@ export class UIDrawer extends UIElement {
|
|
|
161
163
|
clearTimeout(this.#closeTimer);
|
|
162
164
|
this.#closeTimer = null;
|
|
163
165
|
}
|
|
166
|
+
if (this.#openTimer != null) {
|
|
167
|
+
clearTimeout(this.#openTimer);
|
|
168
|
+
this.#openTimer = null;
|
|
169
|
+
}
|
|
164
170
|
this.#bound = false;
|
|
165
171
|
this.#dialogRef = null;
|
|
166
172
|
}
|
|
@@ -213,6 +219,27 @@ export class UIDrawer extends UIElement {
|
|
|
213
219
|
render() {
|
|
214
220
|
const dialog = this.ensure('dialog');
|
|
215
221
|
|
|
222
|
+
// §FB-30 (P0): render() is the single source of truth for the dialog
|
|
223
|
+
// binding. When a consumer sets `drawer.innerHTML` on a mounted drawer
|
|
224
|
+
// the internal <dialog> is wiped; ensure('dialog') re-stamps a fresh
|
|
225
|
+
// one, but #dialogRef (set once in connected()) still points at the
|
|
226
|
+
// detached old dialog. Re-bind the cancel/close/click listeners onto
|
|
227
|
+
// the new dialog so #syncDialog() never calls showModal() on a
|
|
228
|
+
// detached node. On first render after mount `dialog` already equals
|
|
229
|
+
// #dialogRef (set in connected()), so the listeners are NOT
|
|
230
|
+
// double-added. Per FEEDBACK-30.
|
|
231
|
+
if (dialog !== this.#dialogRef) {
|
|
232
|
+
if (this.#dialogRef) {
|
|
233
|
+
this.#dialogRef.removeEventListener('cancel', this.#onDialogCancel);
|
|
234
|
+
this.#dialogRef.removeEventListener('close', this.#onDialogClose);
|
|
235
|
+
this.#dialogRef.removeEventListener('click', this.#onDialogClick);
|
|
236
|
+
}
|
|
237
|
+
this.#dialogRef = dialog;
|
|
238
|
+
dialog.addEventListener('cancel', this.#onDialogCancel);
|
|
239
|
+
dialog.addEventListener('close', this.#onDialogClose);
|
|
240
|
+
dialog.addEventListener('click', this.#onDialogClick);
|
|
241
|
+
}
|
|
242
|
+
|
|
216
243
|
if (this.text) {
|
|
217
244
|
dialog.setAttribute('aria-label', this.text);
|
|
218
245
|
this.setAttribute('aria-label', this.text);
|
|
@@ -289,10 +316,23 @@ export class UIDrawer extends UIElement {
|
|
|
289
316
|
#syncDialog() {
|
|
290
317
|
const dialog = this.#dialogRef;
|
|
291
318
|
if (!dialog) return;
|
|
319
|
+
// §FB-30 (P0): guard against a detached dialog. If a consumer wiped
|
|
320
|
+
// the host's children via innerHTML between render() passes, #dialogRef
|
|
321
|
+
// could momentarily point at a node outside the document — showModal()
|
|
322
|
+
// on a detached <dialog> throws InvalidStateError. Per FEEDBACK-30.
|
|
323
|
+
if (!dialog.isConnected) return;
|
|
292
324
|
if (this.open && !dialog.open) {
|
|
293
325
|
this.#closing = false;
|
|
294
326
|
this.#previousFocus = document.activeElement;
|
|
295
|
-
|
|
327
|
+
// §FB-30 (P0): wrap showModal() so a detached-dialog InvalidStateError
|
|
328
|
+
// surfaces synchronously instead of being swallowed by the effect
|
|
329
|
+
// runner and rethrown as an unhandled microtask rejection. Per
|
|
330
|
+
// FEEDBACK-30.
|
|
331
|
+
try {
|
|
332
|
+
dialog.showModal();
|
|
333
|
+
} catch (e) {
|
|
334
|
+
console.error('[drawer-ui] showModal() failed — dialog may be detached:', e);
|
|
335
|
+
}
|
|
296
336
|
// Synchronous reflow instead of rAF — Safari throttles
|
|
297
337
|
// requestAnimationFrame when a top-layer dialog is open, sometimes
|
|
298
338
|
// delaying [data-open] (and the slide-in transition) by tens of
|
|
@@ -300,6 +340,17 @@ export class UIDrawer extends UIElement {
|
|
|
300
340
|
// synchronous frame. See docs/BROWSER-COMPAT.md §3a (Flavor C).
|
|
301
341
|
void dialog.offsetHeight;
|
|
302
342
|
dialog.setAttribute('data-open', '');
|
|
343
|
+
// §FB-27 (P2): emit `opened` after the slide-in transition completes
|
|
344
|
+
// so Playwright (and any consumer waiting for "drawer fully open and
|
|
345
|
+
// interactive") can listen for it instead of guessing with
|
|
346
|
+
// waitForTimeout. Mirrors #closeTimer — cleared in #animateClose and
|
|
347
|
+
// disconnected() so a rapid close→open never fires a stale `opened`.
|
|
348
|
+
// Per FEEDBACK-27.
|
|
349
|
+
if (this.#openTimer != null) clearTimeout(this.#openTimer);
|
|
350
|
+
this.#openTimer = setTimeout(() => {
|
|
351
|
+
this.#openTimer = null;
|
|
352
|
+
this.dispatchEvent(new CustomEvent('opened', { bubbles: true }));
|
|
353
|
+
}, this.#getDuration());
|
|
303
354
|
} else if (!this.open && dialog.open && !this.#closing) {
|
|
304
355
|
this.#animateClose(dialog);
|
|
305
356
|
}
|
|
@@ -307,6 +358,13 @@ export class UIDrawer extends UIElement {
|
|
|
307
358
|
|
|
308
359
|
#animateClose(dialog) {
|
|
309
360
|
this.#closing = true;
|
|
361
|
+
// §FB-27 (P2): a close cancels any pending `opened` — clear the open
|
|
362
|
+
// timer so a rapid open→close never fires a stale `opened` event after
|
|
363
|
+
// the drawer is already sliding out. Per FEEDBACK-27.
|
|
364
|
+
if (this.#openTimer != null) {
|
|
365
|
+
clearTimeout(this.#openTimer);
|
|
366
|
+
this.#openTimer = null;
|
|
367
|
+
}
|
|
310
368
|
// Set [data-closing] FIRST (carries the transition spec), force a
|
|
311
369
|
// reflow, THEN remove [data-open]. If both attribute changes batch
|
|
312
370
|
// into a single style update, Safari can skip the slide-out animation
|
|
@@ -58,7 +58,14 @@
|
|
|
58
58
|
],
|
|
59
59
|
"unevaluatedProperties": false,
|
|
60
60
|
"x-adiaui": {
|
|
61
|
-
"anti_patterns": [
|
|
61
|
+
"anti_patterns": [
|
|
62
|
+
{
|
|
63
|
+
"description": "Replacing a drawer's children wholesale — `drawer.innerHTML = '…'` — wipes the internal <dialog> part UIDrawer stamps and tracks. render() re-stamps and re-binds the dialog defensively (FEEDBACK-30), but the authored header/section/footer skeleton is still destroyed on every mutation, and any consumer-held reference to a former child is stale.",
|
|
64
|
+
"right": "<drawer-ui><header>Title</header><section id=\"body\"></section></drawer-ui>\n<!-- mutate a stable inner element, never the component host -->\ndocument.getElementById('body').innerHTML = renderFields(claim);\n",
|
|
65
|
+
"rule": "Never set .innerHTML on a UIElement component host. Keep <header>/<section>/<footer> as persistent children and mutate the content of a plain inner element instead.",
|
|
66
|
+
"wrong": "drawer.innerHTML = `<header>${title}</header><section>${body}</section>`;\n"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
62
69
|
"category": "container",
|
|
63
70
|
"composes": [],
|
|
64
71
|
"events": {
|
|
@@ -76,6 +83,9 @@
|
|
|
76
83
|
]
|
|
77
84
|
}
|
|
78
85
|
}
|
|
86
|
+
},
|
|
87
|
+
"opened": {
|
|
88
|
+
"description": "Fired after the drawer finishes its open transition — scheduled `--drawer-duration` ms after `showModal()` + `[data-open]`. Lets Playwright (and any consumer code waiting for the drawer to be fully open and interactive) listen for an event instead of guessing with a fixed timeout. Cancelled if the drawer closes before the open transition completes, so a rapid close→open never fires a stale `opened`. Added per FEEDBACK-27."
|
|
79
89
|
}
|
|
80
90
|
},
|
|
81
91
|
"examples": [
|
|
@@ -18,6 +18,7 @@ export interface DrawerCloseEventDetail {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export type DrawerCloseEvent = CustomEvent<DrawerCloseEventDetail>;
|
|
21
|
+
export type DrawerOpenedEvent = CustomEvent<unknown>;
|
|
21
22
|
|
|
22
23
|
export class UIDrawer extends UIElement {
|
|
23
24
|
/** Controls visibility. When false, backdrop and panel are removed from DOM. */
|
|
@@ -37,4 +38,5 @@ export class UIDrawer extends UIElement {
|
|
|
37
38
|
options?: boolean | AddEventListenerOptions,
|
|
38
39
|
): void;
|
|
39
40
|
addEventListener(type: 'close', listener: (ev: DrawerCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
41
|
+
addEventListener(type: 'opened', listener: (ev: DrawerOpenedEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
40
42
|
}
|
|
@@ -60,6 +60,15 @@ events:
|
|
|
60
60
|
`'programmatic'` (consumer set `.open = false` from JS).
|
|
61
61
|
Defaults to `'programmatic'` when no event-driven path
|
|
62
62
|
captured a reason. Added v0.5.3 §156 per FEEDBACK-06 §2.
|
|
63
|
+
opened:
|
|
64
|
+
description: >-
|
|
65
|
+
Fired after the drawer finishes its open transition — scheduled
|
|
66
|
+
`--drawer-duration` ms after `showModal()` + `[data-open]`. Lets
|
|
67
|
+
Playwright (and any consumer code waiting for the drawer to be fully
|
|
68
|
+
open and interactive) listen for an event instead of guessing with
|
|
69
|
+
a fixed timeout. Cancelled if the drawer closes before the open
|
|
70
|
+
transition completes, so a rapid close→open never fires a stale
|
|
71
|
+
`opened`. Added per FEEDBACK-27.
|
|
63
72
|
slots:
|
|
64
73
|
backdrop:
|
|
65
74
|
description: Scrim overlay behind the drawer (stamped by the component).
|
|
@@ -123,7 +132,23 @@ traits: []
|
|
|
123
132
|
tokens: {}
|
|
124
133
|
a2ui:
|
|
125
134
|
rules: []
|
|
126
|
-
anti_patterns:
|
|
135
|
+
anti_patterns:
|
|
136
|
+
- description: >-
|
|
137
|
+
Replacing a drawer's children wholesale — `drawer.innerHTML = '…'` —
|
|
138
|
+
wipes the internal <dialog> part UIDrawer stamps and tracks. render()
|
|
139
|
+
re-stamps and re-binds the dialog defensively (FEEDBACK-30), but the
|
|
140
|
+
authored header/section/footer skeleton is still destroyed on every
|
|
141
|
+
mutation, and any consumer-held reference to a former child is stale.
|
|
142
|
+
wrong: |
|
|
143
|
+
drawer.innerHTML = `<header>${title}</header><section>${body}</section>`;
|
|
144
|
+
right: |
|
|
145
|
+
<drawer-ui><header>Title</header><section id="body"></section></drawer-ui>
|
|
146
|
+
<!-- mutate a stable inner element, never the component host -->
|
|
147
|
+
document.getElementById('body').innerHTML = renderFields(claim);
|
|
148
|
+
rule: >-
|
|
149
|
+
Never set .innerHTML on a UIElement component host. Keep
|
|
150
|
+
<header>/<section>/<footer> as persistent children and mutate the
|
|
151
|
+
content of a plain inner element instead.
|
|
127
152
|
examples:
|
|
128
153
|
- name: drawer-panel
|
|
129
154
|
description: Card with a trigger button that opens a Drawer side panel containing a form with inputs
|
|
@@ -33,6 +33,12 @@ export class UISegmented extends UIFormElement {
|
|
|
33
33
|
|
|
34
34
|
static template = () => null;
|
|
35
35
|
|
|
36
|
+
// FEEDBACK-23: one-shot per-element warn when a consumer authors a direct
|
|
37
|
+
// child that isn't <segment-ui> (typically a bare <segment> tag — renders
|
|
38
|
+
// text but gets no sliding indicator, role, or aria-checked). WeakSet so
|
|
39
|
+
// the warn never pins the element from GC.
|
|
40
|
+
static #warnedNonSegment = new WeakSet();
|
|
41
|
+
|
|
36
42
|
#indicator = null;
|
|
37
43
|
#bound = false;
|
|
38
44
|
#transitionRaf = null;
|
|
@@ -97,6 +103,23 @@ export class UISegmented extends UIFormElement {
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
render() {
|
|
106
|
+
// FEEDBACK-23: flag a non-<segment-ui> direct child once per element.
|
|
107
|
+
// Runs before the empty-guard so an all-bare-<segment> group still warns.
|
|
108
|
+
if (!UISegmented.#warnedNonSegment.has(this)) {
|
|
109
|
+
const bad = [...this.children].find(
|
|
110
|
+
(c) => c.tagName !== 'SEGMENT-UI' && !c.hasAttribute('data-indicator'),
|
|
111
|
+
);
|
|
112
|
+
if (bad) {
|
|
113
|
+
UISegmented.#warnedNonSegment.add(this);
|
|
114
|
+
// eslint-disable-next-line no-console
|
|
115
|
+
console.warn(
|
|
116
|
+
`[segmented-ui] child <${bad.tagName.toLowerCase()}> is not ` +
|
|
117
|
+
`<segment-ui> — bare <segment> tags render text but receive no ` +
|
|
118
|
+
`sliding indicator or aria-checked state. Use <segment-ui>.`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
100
123
|
const segs = this.#segments;
|
|
101
124
|
if (!segs.length) return;
|
|
102
125
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://adiaui.dev/a2ui/v0_9/components/Segmented.json",
|
|
4
4
|
"title": "Segmented",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Single-select toggle group with an animated sliding indicator. Children must be segment-ui elements.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"allOf": [
|
|
8
8
|
{
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"examples": [
|
|
39
39
|
{
|
|
40
|
-
"description": "
|
|
41
|
-
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Card\",\n \"children\": [\n \"sec\"\n ]\n },\n {\n \"id\": \"sec\",\n \"component\": \"Section\",\n \"children\": [\n \"comp\"\n ]\n },\n {\n \"id\": \"comp\",\n \"component\": \"Segmented\",\n \"value\": \"\"\n }\n]",
|
|
40
|
+
"description": "Segmented control with three segment-ui children for view switching.",
|
|
41
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Card\",\n \"children\": [\n \"sec\"\n ]\n },\n {\n \"id\": \"sec\",\n \"component\": \"Section\",\n \"children\": [\n \"comp\"\n ]\n },\n {\n \"id\": \"comp\",\n \"component\": \"Segmented\",\n \"value\": \"daily\",\n \"children\": [\n \"s1\",\n \"s2\",\n \"s3\"\n ]\n },\n {\n \"id\": \"s1\",\n \"component\": \"Segment\",\n \"value\": \"daily\",\n \"text\": \"Daily\"\n },\n {\n \"id\": \"s2\",\n \"component\": \"Segment\",\n \"value\": \"weekly\",\n \"text\": \"Weekly\"\n },\n {\n \"id\": \"s3\",\n \"component\": \"Segment\",\n \"value\": \"monthly\",\n \"text\": \"Monthly\"\n }\n]",
|
|
42
42
|
"name": "basic-segmented"
|
|
43
43
|
}
|
|
44
44
|
],
|
|
@@ -54,7 +54,14 @@
|
|
|
54
54
|
],
|
|
55
55
|
"name": "UISegmented",
|
|
56
56
|
"related": [],
|
|
57
|
-
"slots": {
|
|
57
|
+
"slots": {
|
|
58
|
+
"default": {
|
|
59
|
+
"description": "Child segment-ui elements that form the toggle group. Children MUST be segment-ui — bare segment tags render text but are silently ignored for the sliding indicator and role/aria-checked state."
|
|
60
|
+
},
|
|
61
|
+
"indicator": {
|
|
62
|
+
"description": "Auto-created sliding indicator element prepended on first render."
|
|
63
|
+
}
|
|
64
|
+
},
|
|
58
65
|
"states": [
|
|
59
66
|
{
|
|
60
67
|
"description": "Default, ready for interaction.",
|
|
@@ -4,7 +4,7 @@ tag: segmented-ui
|
|
|
4
4
|
component: Segmented
|
|
5
5
|
category: navigation
|
|
6
6
|
version: 1
|
|
7
|
-
description:
|
|
7
|
+
description: Single-select toggle group with an animated sliding indicator. Children must be segment-ui elements.
|
|
8
8
|
props:
|
|
9
9
|
value:
|
|
10
10
|
description: Value of the currently selected segment.
|
|
@@ -13,7 +13,11 @@ props:
|
|
|
13
13
|
events:
|
|
14
14
|
change:
|
|
15
15
|
description: Fired when the selected segment changes. detail contains { value }.
|
|
16
|
-
slots:
|
|
16
|
+
slots:
|
|
17
|
+
default:
|
|
18
|
+
description: Child segment-ui elements that form the toggle group. Children MUST be segment-ui — bare segment tags render text but are silently ignored for the sliding indicator and role/aria-checked state.
|
|
19
|
+
indicator:
|
|
20
|
+
description: Auto-created sliding indicator element prepended on first render.
|
|
17
21
|
states:
|
|
18
22
|
- name: idle
|
|
19
23
|
description: Default, ready for interaction.
|
|
@@ -24,10 +28,52 @@ a2ui:
|
|
|
24
28
|
anti_patterns: []
|
|
25
29
|
examples:
|
|
26
30
|
- name: basic-segmented
|
|
27
|
-
description:
|
|
28
|
-
a2ui:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
description: Segmented control with three segment-ui children for view switching.
|
|
32
|
+
a2ui: >-
|
|
33
|
+
[
|
|
34
|
+
{
|
|
35
|
+
"id": "root",
|
|
36
|
+
"component": "Card",
|
|
37
|
+
"children": [
|
|
38
|
+
"sec"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "sec",
|
|
43
|
+
"component": "Section",
|
|
44
|
+
"children": [
|
|
45
|
+
"comp"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "comp",
|
|
50
|
+
"component": "Segmented",
|
|
51
|
+
"value": "daily",
|
|
52
|
+
"children": [
|
|
53
|
+
"s1",
|
|
54
|
+
"s2",
|
|
55
|
+
"s3"
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "s1",
|
|
60
|
+
"component": "Segment",
|
|
61
|
+
"value": "daily",
|
|
62
|
+
"text": "Daily"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "s2",
|
|
66
|
+
"component": "Segment",
|
|
67
|
+
"value": "weekly",
|
|
68
|
+
"text": "Weekly"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "s3",
|
|
72
|
+
"component": "Segment",
|
|
73
|
+
"value": "monthly",
|
|
74
|
+
"text": "Monthly"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
31
77
|
keywords:
|
|
32
78
|
- segmented
|
|
33
79
|
- options
|
|
@@ -212,15 +212,20 @@ export class UISelect extends UIFormElement {
|
|
|
212
212
|
this.#options = [];
|
|
213
213
|
// §225 (v0.5.9): track unknown-child-tag names so we can warn once per element.
|
|
214
214
|
const unknownTags = new Set();
|
|
215
|
+
// FEEDBACK-36: capture the value of an <option selected> child so a
|
|
216
|
+
// declarative initial selection is honoured (native <select> parity).
|
|
217
|
+
let preSelected;
|
|
215
218
|
for (const child of this.children) {
|
|
216
219
|
if (child.tagName === 'OPTGROUP') {
|
|
217
220
|
const group = { label: child.label || child.getAttribute('label') || '', options: [] };
|
|
218
221
|
for (const opt of child.querySelectorAll('option')) {
|
|
219
222
|
group.options.push({ value: opt.value, label: opt.textContent.trim(), disabled: opt.disabled });
|
|
223
|
+
if (preSelected === undefined && opt.hasAttribute('selected')) preSelected = opt.value;
|
|
220
224
|
}
|
|
221
225
|
this.#options.push(group);
|
|
222
226
|
} else if (child.tagName === 'OPTION') {
|
|
223
227
|
this.#options.push({ value: child.value, label: child.textContent.trim(), disabled: child.disabled });
|
|
228
|
+
if (preSelected === undefined && child.hasAttribute('selected')) preSelected = child.value;
|
|
224
229
|
} else if (
|
|
225
230
|
// §225: skip [slot="display"] / [slot="listbox"] / [slot="action"] etc. — these are
|
|
226
231
|
// stamped by the component itself, not consumer-authored options. Anything other
|
|
@@ -231,6 +236,10 @@ export class UISelect extends UIFormElement {
|
|
|
231
236
|
}
|
|
232
237
|
}
|
|
233
238
|
for (const child of [...this.querySelectorAll('option, optgroup')]) child.remove();
|
|
239
|
+
// FEEDBACK-36: honour <option selected> as the initial value when the host
|
|
240
|
+
// carries no [value] — matches native <select> semantics. Guarded on an
|
|
241
|
+
// empty this.value so consumers who set [value] explicitly are unaffected.
|
|
242
|
+
if (!this.value && preSelected !== undefined) this.value = preSelected;
|
|
234
243
|
// §225 (v0.5.9): warn once per element when consumer authored non-<option> children.
|
|
235
244
|
if (unknownTags.size > 0 && !UISelect.#warnedNonOption.has(this)) {
|
|
236
245
|
UISelect.#warnedNonOption.add(this);
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"default": "md"
|
|
123
123
|
},
|
|
124
124
|
"value": {
|
|
125
|
-
"description": "Currently selected option value",
|
|
125
|
+
"description": "Currently selected option value. With declarative <option> children, a child carrying the native `selected` attribute sets the initial value when this attribute is absent (native <select> parity).",
|
|
126
126
|
"type": "string",
|
|
127
127
|
"default": ""
|
|
128
128
|
},
|
|
@@ -111,7 +111,10 @@ props:
|
|
|
111
111
|
type: boolean
|
|
112
112
|
default: false
|
|
113
113
|
value:
|
|
114
|
-
description:
|
|
114
|
+
description: >-
|
|
115
|
+
Currently selected option value. With declarative <option> children, a
|
|
116
|
+
child carrying the native `selected` attribute sets the initial value
|
|
117
|
+
when this attribute is absent (native <select> parity).
|
|
115
118
|
type: string
|
|
116
119
|
default: ""
|
|
117
120
|
events:
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
* page — { detail: { page } }
|
|
48
48
|
* resize — { detail: { key, width } }
|
|
49
49
|
* cell-click — { detail: { key, row, value, dataIndex } }
|
|
50
|
+
* row-click — { detail: { row, dataIndex } } — row-level companion to cell-click
|
|
50
51
|
*/
|
|
51
52
|
|
|
52
53
|
import { UIElement } from '../../core/element.js';
|
|
@@ -1303,6 +1304,13 @@ export class UITable extends UIElement {
|
|
|
1303
1304
|
bubbles: true,
|
|
1304
1305
|
detail: { key, row: rowData, value, dataIndex },
|
|
1305
1306
|
}));
|
|
1307
|
+
// FEEDBACK-35: row-click convenience event — fires once per row,
|
|
1308
|
+
// regardless of which cell was hit. detail is a strict subset of
|
|
1309
|
+
// cell-click (no key/value) so it reads as row identity, not a cell.
|
|
1310
|
+
this.dispatchEvent(new CustomEvent('row-click', {
|
|
1311
|
+
bubbles: true,
|
|
1312
|
+
detail: { row: rowData, dataIndex },
|
|
1313
|
+
}));
|
|
1306
1314
|
}
|
|
1307
1315
|
}
|
|
1308
1316
|
};
|
|
@@ -144,6 +144,19 @@
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
},
|
|
147
|
+
"row-click": {
|
|
148
|
+
"description": "Fired once when any cell in a data row is clicked — the row-level companion to cell-click. Use it for master-detail / open-flyout wiring.",
|
|
149
|
+
"detail": {
|
|
150
|
+
"dataIndex": {
|
|
151
|
+
"description": "Row index in the underlying data array.",
|
|
152
|
+
"type": "number"
|
|
153
|
+
},
|
|
154
|
+
"row": {
|
|
155
|
+
"description": "Row data object.",
|
|
156
|
+
"type": "object"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
147
160
|
"row-collapse": {
|
|
148
161
|
"description": "Fired when an already-expanded row's chevron is activated (collapses the row). Mirror of row-expand.",
|
|
149
162
|
"detail": {
|
|
@@ -183,11 +196,11 @@
|
|
|
183
196
|
"description": "Fired when sort state changes via header click.",
|
|
184
197
|
"detail": {
|
|
185
198
|
"dir": {
|
|
186
|
-
"description": "Sort direction — \"asc\" / \"desc\" / null (cleared).",
|
|
199
|
+
"description": "Sort direction — \"asc\" / \"desc\" / null (cleared). The detail field is `dir` (not `direction`).",
|
|
187
200
|
"type": "string"
|
|
188
201
|
},
|
|
189
202
|
"key": {
|
|
190
|
-
"description": "Column key being sorted.",
|
|
203
|
+
"description": "Column key being sorted — the detail field is `key` (not `column`).",
|
|
191
204
|
"type": "string"
|
|
192
205
|
},
|
|
193
206
|
"sortState": {
|
|
@@ -44,6 +44,14 @@ export interface TableResizeEventDetail {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export type TableResizeEvent = CustomEvent<TableResizeEventDetail>;
|
|
47
|
+
export interface TableRowClickEventDetail {
|
|
48
|
+
/** Row index in the underlying data array. */
|
|
49
|
+
dataIndex: number;
|
|
50
|
+
/** Row data object. */
|
|
51
|
+
row: Record<string, unknown>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type TableRowClickEvent = CustomEvent<TableRowClickEventDetail>;
|
|
47
55
|
export interface TableRowCollapseEventDetail {
|
|
48
56
|
/** Row index in the underlying data array. */
|
|
49
57
|
index: number;
|
|
@@ -67,9 +75,9 @@ export interface TableSelectEventDetail {
|
|
|
67
75
|
|
|
68
76
|
export type TableSelectEvent = CustomEvent<TableSelectEventDetail>;
|
|
69
77
|
export interface TableSortEventDetail {
|
|
70
|
-
/** Sort direction — "asc" / "desc" / null (cleared). */
|
|
78
|
+
/** Sort direction — "asc" / "desc" / null (cleared). The detail field is `dir` (not `direction`). */
|
|
71
79
|
dir: string;
|
|
72
|
-
/** Column key being sorted. */
|
|
80
|
+
/** Column key being sorted — the detail field is `key` (not `column`). */
|
|
73
81
|
key: string;
|
|
74
82
|
/** Full sort state array (multi-column support). */
|
|
75
83
|
sortState: unknown[];
|
|
@@ -110,6 +118,7 @@ export class UITable extends UIElement {
|
|
|
110
118
|
addEventListener(type: 'filter-change', listener: (ev: TableFilterChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
111
119
|
addEventListener(type: 'page', listener: (ev: TablePageEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
112
120
|
addEventListener(type: 'resize', listener: (ev: TableResizeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
121
|
+
addEventListener(type: 'row-click', listener: (ev: TableRowClickEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
113
122
|
addEventListener(type: 'row-collapse', listener: (ev: TableRowCollapseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
114
123
|
addEventListener(type: 'row-expand', listener: (ev: TableRowExpandEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
115
124
|
addEventListener(type: 'select', listener: (ev: TableSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
@@ -91,6 +91,15 @@ events:
|
|
|
91
91
|
dataIndex:
|
|
92
92
|
type: number
|
|
93
93
|
description: Row index in the underlying data array.
|
|
94
|
+
row-click:
|
|
95
|
+
description: Fired once when any cell in a data row is clicked — the row-level companion to cell-click. Use it for master-detail / open-flyout wiring.
|
|
96
|
+
detail:
|
|
97
|
+
row:
|
|
98
|
+
type: object
|
|
99
|
+
description: Row data object.
|
|
100
|
+
dataIndex:
|
|
101
|
+
type: number
|
|
102
|
+
description: Row index in the underlying data array.
|
|
94
103
|
filter-change:
|
|
95
104
|
description: Fired when an interactive filter row applies / clears. detail.filters is the current filter map.
|
|
96
105
|
detail:
|
|
@@ -141,10 +150,10 @@ events:
|
|
|
141
150
|
detail:
|
|
142
151
|
key:
|
|
143
152
|
type: string
|
|
144
|
-
description: Column key being sorted.
|
|
153
|
+
description: Column key being sorted — the detail field is `key` (not `column`).
|
|
145
154
|
dir:
|
|
146
155
|
type: string
|
|
147
|
-
description: Sort direction — "asc" / "desc" / null (cleared).
|
|
156
|
+
description: Sort direction — "asc" / "desc" / null (cleared). The detail field is `dir` (not `direction`).
|
|
148
157
|
sortState:
|
|
149
158
|
type: array
|
|
150
159
|
description: Full sort state array (multi-column support).
|
|
@@ -13,9 +13,24 @@
|
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
|
+
"required": {
|
|
17
|
+
"description": "Marks the field as required for form validation. Sets aria-required. Inherited from UIFormElement.",
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": false
|
|
20
|
+
},
|
|
16
21
|
"component": {
|
|
17
22
|
"const": "Textarea"
|
|
18
23
|
},
|
|
24
|
+
"disabled": {
|
|
25
|
+
"description": "Disables interaction, removes contenteditable, and dims the control. Inherited from UIFormElement.",
|
|
26
|
+
"type": "boolean",
|
|
27
|
+
"default": false
|
|
28
|
+
},
|
|
29
|
+
"error": {
|
|
30
|
+
"description": "Validation error message. Set by constraint validation or manually via setInvalid(). Inherited from UIFormElement.",
|
|
31
|
+
"type": "string",
|
|
32
|
+
"default": ""
|
|
33
|
+
},
|
|
19
34
|
"hint": {
|
|
20
35
|
"description": "Help text displayed below the textarea. Hidden when error is set.",
|
|
21
36
|
"type": "string",
|
|
@@ -36,6 +51,11 @@
|
|
|
36
51
|
"type": "string",
|
|
37
52
|
"default": ""
|
|
38
53
|
},
|
|
54
|
+
"readonly": {
|
|
55
|
+
"description": "Prevents editing while keeping the surface focusable. Sets contenteditable=false. Inherited from UIFormElement.",
|
|
56
|
+
"type": "boolean",
|
|
57
|
+
"default": false
|
|
58
|
+
},
|
|
39
59
|
"resize": {
|
|
40
60
|
"description": "Resize behavior of the textarea.",
|
|
41
61
|
"type": "string",
|
|
@@ -56,6 +76,11 @@
|
|
|
56
76
|
"description": "§220 (v0.5.9, FEEDBACK-14 §3). Trailing-debounce on the `input`\nevent in milliseconds. When > 0, value mutates immediately + the UI\nstays responsive, but `input` dispatch is collapsed so only the\nfinal value in the throttle window emits. Useful for expensive\n`input`-driven computation (autosave preview, server-side\nautocomplete, large-doc reflow). `change` fires unthrottled on blur\n/ Enter; any pending `input` flushes before `change` so consumers\nsee input→input→…→input→change ordering. Default 0 preserves\nsynchronous emission.",
|
|
57
77
|
"type": "number",
|
|
58
78
|
"default": 0
|
|
79
|
+
},
|
|
80
|
+
"value": {
|
|
81
|
+
"description": "Current textarea value, synced with the contenteditable text surface. Inherited from UIFormElement.",
|
|
82
|
+
"type": "string",
|
|
83
|
+
"default": ""
|
|
59
84
|
}
|
|
60
85
|
},
|
|
61
86
|
"required": [
|
|
@@ -8,6 +8,15 @@ category: layout
|
|
|
8
8
|
version: 1
|
|
9
9
|
description: Multiline text input. The host IS the interactive surface.
|
|
10
10
|
props:
|
|
11
|
+
disabled:
|
|
12
|
+
description: Disables interaction, removes contenteditable, and dims the control. Inherited from UIFormElement.
|
|
13
|
+
type: boolean
|
|
14
|
+
default: false
|
|
15
|
+
reflect: true
|
|
16
|
+
error:
|
|
17
|
+
description: Validation error message. Set by constraint validation or manually via setInvalid(). Inherited from UIFormElement.
|
|
18
|
+
type: string
|
|
19
|
+
default: ""
|
|
11
20
|
hint:
|
|
12
21
|
description: Help text displayed below the textarea. Hidden when error is set.
|
|
13
22
|
type: string
|
|
@@ -24,6 +33,16 @@ props:
|
|
|
24
33
|
description: Placeholder text shown when textarea is empty.
|
|
25
34
|
type: string
|
|
26
35
|
default: ""
|
|
36
|
+
readonly:
|
|
37
|
+
description: Prevents editing while keeping the surface focusable. Sets contenteditable=false. Inherited from UIFormElement.
|
|
38
|
+
type: boolean
|
|
39
|
+
default: false
|
|
40
|
+
reflect: true
|
|
41
|
+
required:
|
|
42
|
+
description: Marks the field as required for form validation. Sets aria-required. Inherited from UIFormElement.
|
|
43
|
+
type: boolean
|
|
44
|
+
default: false
|
|
45
|
+
reflect: true
|
|
27
46
|
resize:
|
|
28
47
|
description: Resize behavior of the textarea.
|
|
29
48
|
type: string
|
|
@@ -51,6 +70,10 @@ props:
|
|
|
51
70
|
type: number
|
|
52
71
|
default: 0
|
|
53
72
|
reflect: true
|
|
73
|
+
value:
|
|
74
|
+
description: Current textarea value, synced with the contenteditable text surface. Inherited from UIFormElement.
|
|
75
|
+
type: string
|
|
76
|
+
default: ""
|
|
54
77
|
events:
|
|
55
78
|
change:
|
|
56
79
|
description: Fired when the textarea loses focus after a value change.
|
package/core/element.js
CHANGED
|
@@ -262,6 +262,18 @@ export class UIElement extends HTMLElement {
|
|
|
262
262
|
|
|
263
263
|
signal(v) { const s = signal(v); this.#as.push(s); return s; }
|
|
264
264
|
|
|
265
|
+
/**
|
|
266
|
+
* Get-or-create a structural part by slot name. Returns the existing
|
|
267
|
+
* `[slot="X"]` child when present, otherwise clones the part blueprint
|
|
268
|
+
* (`static parts`) and appends it. Re-stamped parts carry `_uiPart = true`
|
|
269
|
+
* so a part wiped by a host `innerHTML` mutation is detectable downstream.
|
|
270
|
+
*
|
|
271
|
+
* INVARIANT (FEEDBACK-31) — call `ensure()` from `render()`; never cache
|
|
272
|
+
* its result once in `connected()`. A consumer that replaces the host's
|
|
273
|
+
* children (`el.innerHTML = …`) wipes stamped parts; `render()` re-stamps
|
|
274
|
+
* them, but a reference captured once in `connected()` is left pointing at
|
|
275
|
+
* detached DOM. Part identity does not survive a host child-list wipe.
|
|
276
|
+
*/
|
|
265
277
|
ensure(slot) {
|
|
266
278
|
for (const ch of this.children) {
|
|
267
279
|
if (ch.getAttribute('slot') === slot) return ch;
|
|
@@ -272,6 +284,7 @@ export class UIElement extends HTMLElement {
|
|
|
272
284
|
const blueprint = this.constructor._pp?.[slot];
|
|
273
285
|
if (!blueprint) return null;
|
|
274
286
|
const el = blueprint.cloneNode(true);
|
|
287
|
+
el._uiPart = true;
|
|
275
288
|
this.appendChild(el);
|
|
276
289
|
return el;
|
|
277
290
|
}
|
package/css-module.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Ambient declaration so TypeScript (moduleResolution: bundler / node16)
|
|
2
|
+
// can resolve CSS side-effect subpath imports — e.g.
|
|
3
|
+
// import '@adia-ai/web-components/css';
|
|
4
|
+
// without a TS2882 "no type declarations for side-effect import" error.
|
|
5
|
+
// See FEEDBACK-26.
|
|
6
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-components",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.20",
|
|
4
4
|
"description": "AdiaUI web components \u2014 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",
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
"import": "./index.js",
|
|
14
14
|
"default": "./index.js"
|
|
15
15
|
},
|
|
16
|
-
"./css":
|
|
16
|
+
"./css": {
|
|
17
|
+
"types": "./css-module.d.ts",
|
|
18
|
+
"default": "./index.css"
|
|
19
|
+
},
|
|
17
20
|
"./core": {
|
|
18
21
|
"types": "./core/index.d.ts",
|
|
19
22
|
"import": "./core/index.js",
|
|
@@ -39,9 +42,18 @@
|
|
|
39
42
|
"import": "./components/*/class.js",
|
|
40
43
|
"default": "./components/*/class.js"
|
|
41
44
|
},
|
|
42
|
-
"./components/*.css":
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
"./components/*.css": {
|
|
46
|
+
"types": "./css-module.d.ts",
|
|
47
|
+
"default": "./components/*/*.css"
|
|
48
|
+
},
|
|
49
|
+
"./components/*/*.css": {
|
|
50
|
+
"types": "./css-module.d.ts",
|
|
51
|
+
"default": "./components/*/*.css"
|
|
52
|
+
},
|
|
53
|
+
"./components/*/css/*.css": {
|
|
54
|
+
"types": "./css-module.d.ts",
|
|
55
|
+
"default": "./components/*/css/*.css"
|
|
56
|
+
},
|
|
45
57
|
"./styles/*": "./styles/*",
|
|
46
58
|
"./traits": {
|
|
47
59
|
"types": "./traits.d.ts",
|
|
@@ -77,6 +89,7 @@
|
|
|
77
89
|
"index.js",
|
|
78
90
|
"index.css",
|
|
79
91
|
"index.d.ts",
|
|
92
|
+
"css-module.d.ts",
|
|
80
93
|
"bin/",
|
|
81
94
|
"custom-elements.json",
|
|
82
95
|
"USAGE.md",
|