@adia-ai/web-components 0.6.19 → 0.6.21
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 +47 -0
- package/components/chart/class.js +3 -1
- package/components/feed/feed.css +9 -4
- package/components/input/input.css +8 -1
- 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 +14 -0
- package/components/table/table.a2ui.json +21 -3
- package/components/table/table.css +19 -0
- package/components/table/table.d.ts +14 -3
- package/components/table/table.test.js +64 -0
- package/components/table/table.yaml +23 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.6.21] — 2026-05-21
|
|
4
|
+
|
|
5
|
+
### Changed — `table-ui` body cells truncate by default (opt-in wrap via `[wrap]` / `[data-wrap]`)
|
|
6
|
+
|
|
7
|
+
- **`<table-ui>` body cells now default to single-line with ellipsis truncation** instead of `overflow-wrap: anywhere`. Long unbreakable strings (URLs, IDs) clip gracefully rather than re-wrapping the row, matching the row convention shared by `<select-ui>` + `<nav-item-ui>`. Three opt-in paths to wrapping: (1) `<table-ui wrap>` — new reflected boolean prop, whole-table opt-in; (2) `col.wrap: true` in `columns[]` — renderer sets `[data-wrap]` on each body cell in that column (per-column opt-in); (3) `<td data-wrap>` — surgical per-cell opt-in for hand-authored cells. Behavior change for consumers who relied on the legacy wrap default — set `[wrap]` on the host to restore. New tests cover the three paths. Files: `components/table/{table.css,table.yaml,class.js,table.test.js}`.
|
|
8
|
+
|
|
9
|
+
### Fixed — toast / feed-item variant text colour now safe in light + dark mode
|
|
10
|
+
|
|
11
|
+
- **`<feed-item-ui>` — the element `<toast-ui>` / `UIToast.show()` render through — paired a scheme-locked foreground with a scheme-flipping background.** The `info` / `success` / `warning` / `danger` variants set the text to `--a-{variant}-fg` (= `--a-{variant}-text-strong`, locked for solid `--variant-strong` fills) over a `--a-{variant}-muted` tint that flips light↔dark — going low-contrast in one scheme (e.g. a warning toast in dark mode). They now use the scheme-aware `--a-{variant}-text`, which flips with the background — matching `alert.css` and `toast.css` variant wiring. Files: `feed.css`.
|
|
12
|
+
|
|
13
|
+
### Fixed — `input-ui` inline `[slot="label"]` no longer crowds the value text
|
|
14
|
+
|
|
15
|
+
- **An inline `<span slot="label">` sat flush against the value** with only the field gap between them, so a short label and the value text read as one run-on string. `[slot="label"]` now carries a `min-width` floor (new `--input-label-min-width` token, default `8ch`) — it reserves a label column and the value start aligns down a stack of inputs. `flex-shrink: 0` keeps it from collapsing; a label wider than the floor still grows past it. Files: `input.css`.
|
|
16
|
+
|
|
17
|
+
## [0.6.20] — 2026-05-21
|
|
18
|
+
|
|
19
|
+
### Added — `table-ui` `row-click` event (FEEDBACK-35)
|
|
20
|
+
|
|
21
|
+
- `<table-ui>` now dispatches a bubbling `row-click` event (detail:
|
|
22
|
+
`{ row, dataIndex }`) once per row click, alongside the existing per-cell
|
|
23
|
+
`cell-click`. Master-detail / open-flyout wiring no longer has to
|
|
24
|
+
deduplicate `cell-click` by `dataIndex`. `table.yaml` documents it.
|
|
25
|
+
|
|
26
|
+
### Changed — `select-ui` honours declarative `<option selected>` (FEEDBACK-36)
|
|
27
|
+
|
|
28
|
+
- `<select-ui>` now promotes an `<option selected>` child to the initial
|
|
29
|
+
value when the host carries no `[value]` attribute — matching native
|
|
30
|
+
`<select>` semantics. Previously the `selected` attribute was silently
|
|
31
|
+
ignored and the placeholder shown. Guarded on an empty `this.value`, so
|
|
32
|
+
consumers who set `[value]` explicitly are unaffected.
|
|
33
|
+
|
|
34
|
+
### Changed — `chart-ui` wrong-`.data`-shape warning shows the correct shape (FEEDBACK-34)
|
|
35
|
+
|
|
36
|
+
- The one-shot `console.warn` emitted when `.data` is set to a non-array
|
|
37
|
+
now embeds a minimal correct-shape example
|
|
38
|
+
(`[{ label: "Jan", value: 100 }, … ]`) instead of only naming the wrong
|
|
39
|
+
API and pointing at `chart.yaml`.
|
|
40
|
+
|
|
41
|
+
### Docs — `table.yaml` / `select.yaml` description clarifications (FEEDBACK-35 §2, FEEDBACK-36)
|
|
42
|
+
|
|
43
|
+
- `components/table/table.yaml` `sort` event: the `key` / `dir` detail
|
|
44
|
+
descriptions now name the fields explicitly (`key` not `column`, `dir` not
|
|
45
|
+
`direction`) — closing the guess the bare names invited.
|
|
46
|
+
- `components/select/select.yaml` `value`: documents that a declarative
|
|
47
|
+
`<option selected>` child sets the initial value when the host `[value]`
|
|
48
|
+
attribute is absent.
|
|
49
|
+
|
|
3
50
|
## [0.6.19] — 2026-05-21
|
|
4
51
|
|
|
5
52
|
### Fixed — `drawer-ui` `innerHTML`-on-host orphaned the internal `<dialog>` (FEEDBACK-30)
|
|
@@ -260,7 +260,9 @@ export class UIChart extends UIElement {
|
|
|
260
260
|
console.warn(
|
|
261
261
|
`[chart-ui] .data must be an array of plain objects — received ` +
|
|
262
262
|
`${arr === null ? 'null' : typeof arr}. The {labels, datasets} ` +
|
|
263
|
-
`envelope is the Chart.js API, not the chart-ui API
|
|
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 }]`,
|
|
264
266
|
);
|
|
265
267
|
}
|
|
266
268
|
this.#data = Array.isArray(arr) ? arr : [];
|
package/components/feed/feed.css
CHANGED
|
@@ -104,10 +104,15 @@ feed-item-ui[data-closing] {
|
|
|
104
104
|
--feed-item-gap: var(--a-space-3);
|
|
105
105
|
--feed-item-max-width: 22rem; /* §230-bundle (v0.5.9): component-local. Was orphaned --a-feed-max-width — hoisted per RESPONSE-21 guidance. */
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
/* fg = the scheme-aware --a-{variant}-text, which flips light↔dark via
|
|
108
|
+
light-dark() to track the --a-{variant}-muted bg (also scheme-aware).
|
|
109
|
+
NOT --a-{variant}-fg (= --a-{variant}-text-strong, locked for solid
|
|
110
|
+
--variant-strong fills) — pairing a locked fg with a flipping bg goes
|
|
111
|
+
low-contrast in one scheme. Mirrors alert.css + toast.css wiring. */
|
|
112
|
+
:scope[variant="info"] { --feed-item-fg: var(--a-info-text); --feed-item-bg: var(--a-info-muted); }
|
|
113
|
+
:scope[variant="success"] { --feed-item-fg: var(--a-success-text); --feed-item-bg: var(--a-success-muted); }
|
|
114
|
+
:scope[variant="warning"] { --feed-item-fg: var(--a-warning-text); --feed-item-bg: var(--a-warning-muted); }
|
|
115
|
+
:scope[variant="danger"] { --feed-item-fg: var(--a-danger-text); --feed-item-bg: var(--a-danger-muted); }
|
|
111
116
|
|
|
112
117
|
:scope {
|
|
113
118
|
box-sizing: border-box;
|
|
@@ -30,6 +30,7 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
|
|
|
30
30
|
--input-placeholder-fg: var(--a-ui-text-placeholder);
|
|
31
31
|
--input-affix-fg: var(--a-ui-text-placeholder);
|
|
32
32
|
--input-field-gap: var(--a-space-1);
|
|
33
|
+
--input-label-min-width: 8ch;
|
|
33
34
|
|
|
34
35
|
/* ── Transitions ── */
|
|
35
36
|
--input-duration: var(--a-duration-fast);
|
|
@@ -55,9 +56,15 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
|
|
|
55
56
|
/* Inline label — sits inside [slot="field"] as a leading caption,
|
|
56
57
|
between [slot="prefix"] and [slot="text"]. Dim by default; shares
|
|
57
58
|
the input chrome border. For stacked label / hint / error, wrap
|
|
58
|
-
with field-ui.
|
|
59
|
+
with field-ui.
|
|
60
|
+
|
|
61
|
+
min-width reserves a label column so a short label (e.g. "PIN")
|
|
62
|
+
doesn't sit flush against the value text, and the value start aligns
|
|
63
|
+
down a stack of inputs. flex-shrink: 0 keeps it from collapsing; a
|
|
64
|
+
label wider than the floor grows past it. */
|
|
59
65
|
[slot="label"] {
|
|
60
66
|
flex-shrink: 0;
|
|
67
|
+
min-width: var(--input-label-min-width);
|
|
61
68
|
color: var(--input-affix-fg);
|
|
62
69
|
font-size: var(--input-font-size);
|
|
63
70
|
user-select: none;
|
|
@@ -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';
|
|
@@ -110,6 +111,7 @@ export class UITable extends UIElement {
|
|
|
110
111
|
selectable: { type: Boolean, default: false, reflect: true },
|
|
111
112
|
expandable: { type: Boolean, default: false, reflect: true },
|
|
112
113
|
striped: { type: Boolean, default: false, reflect: true },
|
|
114
|
+
wrap: { type: Boolean, default: false, reflect: true },
|
|
113
115
|
raw: { type: Boolean, default: false, reflect: true },
|
|
114
116
|
density: { type: String, default: 'standard', reflect: true },
|
|
115
117
|
paginate: { type: Number, default: 0, reflect: true },
|
|
@@ -826,6 +828,11 @@ export class UITable extends UIElement {
|
|
|
826
828
|
cell.setAttribute('data-pinned', col.pinned);
|
|
827
829
|
}
|
|
828
830
|
|
|
831
|
+
// Wrap (per-column opt-in to multi-line cell content)
|
|
832
|
+
if (col.wrap) {
|
|
833
|
+
cell.setAttribute('data-wrap', '');
|
|
834
|
+
}
|
|
835
|
+
|
|
829
836
|
cells.push(cell);
|
|
830
837
|
}
|
|
831
838
|
|
|
@@ -1303,6 +1310,13 @@ export class UITable extends UIElement {
|
|
|
1303
1310
|
bubbles: true,
|
|
1304
1311
|
detail: { key, row: rowData, value, dataIndex },
|
|
1305
1312
|
}));
|
|
1313
|
+
// FEEDBACK-35: row-click convenience event — fires once per row,
|
|
1314
|
+
// regardless of which cell was hit. detail is a strict subset of
|
|
1315
|
+
// cell-click (no key/value) so it reads as row identity, not a cell.
|
|
1316
|
+
this.dispatchEvent(new CustomEvent('row-click', {
|
|
1317
|
+
bubbles: true,
|
|
1318
|
+
detail: { row: rowData, dataIndex },
|
|
1319
|
+
}));
|
|
1306
1320
|
}
|
|
1307
1321
|
}
|
|
1308
1322
|
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
16
|
"columns": {
|
|
17
|
-
"description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.",
|
|
17
|
+
"description": "Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.",
|
|
18
18
|
"type": "array",
|
|
19
19
|
"default": []
|
|
20
20
|
},
|
|
@@ -75,6 +75,11 @@
|
|
|
75
75
|
"description": "Alternate row background colors for visual scanning.",
|
|
76
76
|
"type": "boolean",
|
|
77
77
|
"default": false
|
|
78
|
+
},
|
|
79
|
+
"wrap": {
|
|
80
|
+
"description": "Allow body-cell content to wrap onto multiple lines. Default is single-line with ellipsis truncation (matches the row convention shared by <select-ui> + <nav-item-ui>); long unbreakable strings clip gracefully rather than rewrapping the row. With [wrap], row height auto-grows to fit wrapped content. For surgical opt-in on a single column, set [data-wrap] on the cell / col-def instead.",
|
|
81
|
+
"type": "boolean",
|
|
82
|
+
"default": false
|
|
78
83
|
}
|
|
79
84
|
},
|
|
80
85
|
"required": [
|
|
@@ -144,6 +149,19 @@
|
|
|
144
149
|
}
|
|
145
150
|
}
|
|
146
151
|
},
|
|
152
|
+
"row-click": {
|
|
153
|
+
"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.",
|
|
154
|
+
"detail": {
|
|
155
|
+
"dataIndex": {
|
|
156
|
+
"description": "Row index in the underlying data array.",
|
|
157
|
+
"type": "number"
|
|
158
|
+
},
|
|
159
|
+
"row": {
|
|
160
|
+
"description": "Row data object.",
|
|
161
|
+
"type": "object"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
147
165
|
"row-collapse": {
|
|
148
166
|
"description": "Fired when an already-expanded row's chevron is activated (collapses the row). Mirror of row-expand.",
|
|
149
167
|
"detail": {
|
|
@@ -183,11 +201,11 @@
|
|
|
183
201
|
"description": "Fired when sort state changes via header click.",
|
|
184
202
|
"detail": {
|
|
185
203
|
"dir": {
|
|
186
|
-
"description": "Sort direction — \"asc\" / \"desc\" / null (cleared).",
|
|
204
|
+
"description": "Sort direction — \"asc\" / \"desc\" / null (cleared). The detail field is `dir` (not `direction`).",
|
|
187
205
|
"type": "string"
|
|
188
206
|
},
|
|
189
207
|
"key": {
|
|
190
|
-
"description": "Column key being sorted.",
|
|
208
|
+
"description": "Column key being sorted — the detail field is `key` (not `column`).",
|
|
191
209
|
"type": "string"
|
|
192
210
|
},
|
|
193
211
|
"sortState": {
|
|
@@ -216,6 +216,25 @@
|
|
|
216
216
|
padding: var(--table-py) var(--table-px);
|
|
217
217
|
border-bottom: 1px solid var(--table-border);
|
|
218
218
|
min-width: 0;
|
|
219
|
+
/* Default: single line with ellipsis. Authors opt in to wrapping
|
|
220
|
+
via [wrap] on the host (whole table) or [data-wrap] on a column /
|
|
221
|
+
individual cell. Matches `<select-ui>` / `<nav-item-ui>` single-line
|
|
222
|
+
row convention; long unbreakable strings (URLs, IDs) clip gracefully
|
|
223
|
+
rather than rewrapping the row. */
|
|
224
|
+
white-space: nowrap;
|
|
225
|
+
overflow: hidden;
|
|
226
|
+
text-overflow: ellipsis;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* Wrap opt-in: per-table (all body cells) or per-cell (one column).
|
|
230
|
+
Restores the legacy `overflow-wrap: anywhere` behavior so wrapped
|
|
231
|
+
content can break inside long unbroken tokens (URLs, IDs). Row
|
|
232
|
+
height auto-grows to fit. */
|
|
233
|
+
:scope[wrap] [data-body] [role="gridcell"],
|
|
234
|
+
[data-body] [role="gridcell"][data-wrap] {
|
|
235
|
+
white-space: normal;
|
|
236
|
+
overflow: visible;
|
|
237
|
+
text-overflow: clip;
|
|
219
238
|
overflow-wrap: anywhere;
|
|
220
239
|
}
|
|
221
240
|
|
|
@@ -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[];
|
|
@@ -78,7 +86,7 @@ export interface TableSortEventDetail {
|
|
|
78
86
|
export type TableSortEvent = CustomEvent<TableSortEventDetail>;
|
|
79
87
|
|
|
80
88
|
export class UITable extends UIElement {
|
|
81
|
-
/** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children. */
|
|
89
|
+
/** Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children. */
|
|
82
90
|
columns: unknown[];
|
|
83
91
|
/** Row records. Array of plain objects keyed to columns[].key. */
|
|
84
92
|
data: unknown[];
|
|
@@ -100,6 +108,8 @@ export class UITable extends UIElement {
|
|
|
100
108
|
sortable: boolean;
|
|
101
109
|
/** Alternate row background colors for visual scanning. */
|
|
102
110
|
striped: boolean;
|
|
111
|
+
/** Allow body-cell content to wrap onto multiple lines. Default is single-line with ellipsis truncation (matches the row convention shared by <select-ui> + <nav-item-ui>); long unbreakable strings clip gracefully rather than rewrapping the row. With [wrap], row height auto-grows to fit wrapped content. For surgical opt-in on a single column, set [data-wrap] on the cell / col-def instead. */
|
|
112
|
+
wrap: boolean;
|
|
103
113
|
|
|
104
114
|
addEventListener<K extends keyof HTMLElementEventMap>(
|
|
105
115
|
type: K,
|
|
@@ -110,6 +120,7 @@ export class UITable extends UIElement {
|
|
|
110
120
|
addEventListener(type: 'filter-change', listener: (ev: TableFilterChangeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
111
121
|
addEventListener(type: 'page', listener: (ev: TablePageEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
112
122
|
addEventListener(type: 'resize', listener: (ev: TableResizeEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
123
|
+
addEventListener(type: 'row-click', listener: (ev: TableRowClickEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
113
124
|
addEventListener(type: 'row-collapse', listener: (ev: TableRowCollapseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
114
125
|
addEventListener(type: 'row-expand', listener: (ev: TableRowExpandEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
115
126
|
addEventListener(type: 'select', listener: (ev: TableSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
@@ -172,3 +172,67 @@ describe('table-ui — v0.6.18 loading=skeleton-rows (FB-12 P2)', () => {
|
|
|
172
172
|
expect(afterHeader.children.length).toBe(3);
|
|
173
173
|
});
|
|
174
174
|
});
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Body-cell wrap default + opt-in paths (PATCH-class behavior change).
|
|
178
|
+
*
|
|
179
|
+
* Default: cells truncate single-line with ellipsis (the CSS rule sets
|
|
180
|
+
* white-space: nowrap; overflow: hidden; text-overflow: ellipsis). Three
|
|
181
|
+
* opt-in surfaces lift the truncation:
|
|
182
|
+
* - host [wrap] attribute (reflected from the `wrap` prop)
|
|
183
|
+
* - col.wrap=true in columns[] → renderer sets [data-wrap] on each
|
|
184
|
+
* body cell in that column
|
|
185
|
+
* - [data-wrap] directly on a hand-authored <td>
|
|
186
|
+
*
|
|
187
|
+
* Tests lock the attribute contract; CSS-rule application is covered by
|
|
188
|
+
* the source-grep stylistic invariants of table.css.
|
|
189
|
+
*/
|
|
190
|
+
describe('table-ui — wrap default + opt-in', () => {
|
|
191
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
192
|
+
|
|
193
|
+
it('host [wrap] attribute reflects from the wrap property', async () => {
|
|
194
|
+
const el = mount('<table-ui></table-ui>');
|
|
195
|
+
el.columns = COLS;
|
|
196
|
+
el.data = ROWS;
|
|
197
|
+
await raf();
|
|
198
|
+
expect(el.hasAttribute('wrap')).toBe(false);
|
|
199
|
+
el.wrap = true;
|
|
200
|
+
await raf();
|
|
201
|
+
expect(el.hasAttribute('wrap')).toBe(true);
|
|
202
|
+
el.wrap = false;
|
|
203
|
+
await raf();
|
|
204
|
+
expect(el.hasAttribute('wrap')).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('col.wrap=true marks body cells in that column with [data-wrap]', async () => {
|
|
208
|
+
const el = mount('<table-ui></table-ui>');
|
|
209
|
+
el.columns = [
|
|
210
|
+
{ key: 'id', label: 'ID' },
|
|
211
|
+
{ key: 'name', label: 'Name', wrap: true },
|
|
212
|
+
{ key: 'email', label: 'Email' },
|
|
213
|
+
];
|
|
214
|
+
el.data = ROWS;
|
|
215
|
+
await raf();
|
|
216
|
+
const bodyRows = el.querySelectorAll(':scope > [data-body] > [role="row"]');
|
|
217
|
+
expect(bodyRows.length).toBe(2);
|
|
218
|
+
for (const row of bodyRows) {
|
|
219
|
+
const cells = row.querySelectorAll('[role="gridcell"]');
|
|
220
|
+
expect(cells.length).toBe(3);
|
|
221
|
+
expect(cells[0].hasAttribute('data-wrap')).toBe(false);
|
|
222
|
+
expect(cells[1].hasAttribute('data-wrap')).toBe(true);
|
|
223
|
+
expect(cells[2].hasAttribute('data-wrap')).toBe(false);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('omitting col.wrap leaves all body cells without [data-wrap]', async () => {
|
|
228
|
+
const el = mount('<table-ui></table-ui>');
|
|
229
|
+
el.columns = COLS;
|
|
230
|
+
el.data = ROWS;
|
|
231
|
+
await raf();
|
|
232
|
+
const cells = el.querySelectorAll(':scope > [data-body] [role="gridcell"]');
|
|
233
|
+
expect(cells.length).toBeGreaterThan(0);
|
|
234
|
+
for (const cell of cells) {
|
|
235
|
+
expect(cell.hasAttribute('data-wrap')).toBe(false);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
@@ -19,7 +19,7 @@ composes:
|
|
|
19
19
|
- badge-ui
|
|
20
20
|
props:
|
|
21
21
|
columns:
|
|
22
|
-
description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.
|
|
22
|
+
description: Column definitions. Array of {key, label, type?, width?, minWidth?, maxWidth?, flex?, sortable?, resizable?, filterable?, pinned?, hidden?, wrap?, accessor?, format?, render?, sortFn?, filterType?, meta?}. Alternative to declarative <col-def> children.
|
|
23
23
|
type: array
|
|
24
24
|
default: []
|
|
25
25
|
data:
|
|
@@ -76,6 +76,17 @@ props:
|
|
|
76
76
|
description: Alternate row background colors for visual scanning.
|
|
77
77
|
type: boolean
|
|
78
78
|
default: false
|
|
79
|
+
wrap:
|
|
80
|
+
description: >-
|
|
81
|
+
Allow body-cell content to wrap onto multiple lines. Default is
|
|
82
|
+
single-line with ellipsis truncation (matches the row convention
|
|
83
|
+
shared by <select-ui> + <nav-item-ui>); long unbreakable strings
|
|
84
|
+
clip gracefully rather than rewrapping the row. With [wrap], row
|
|
85
|
+
height auto-grows to fit wrapped content. For surgical opt-in on
|
|
86
|
+
a single column, set [data-wrap] on the cell / col-def instead.
|
|
87
|
+
type: boolean
|
|
88
|
+
default: false
|
|
89
|
+
reflect: true
|
|
79
90
|
events:
|
|
80
91
|
cell-click:
|
|
81
92
|
description: Fired when a data cell is clicked. detail carries the cell location + value.
|
|
@@ -91,6 +102,15 @@ events:
|
|
|
91
102
|
dataIndex:
|
|
92
103
|
type: number
|
|
93
104
|
description: Row index in the underlying data array.
|
|
105
|
+
row-click:
|
|
106
|
+
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.
|
|
107
|
+
detail:
|
|
108
|
+
row:
|
|
109
|
+
type: object
|
|
110
|
+
description: Row data object.
|
|
111
|
+
dataIndex:
|
|
112
|
+
type: number
|
|
113
|
+
description: Row index in the underlying data array.
|
|
94
114
|
filter-change:
|
|
95
115
|
description: Fired when an interactive filter row applies / clears. detail.filters is the current filter map.
|
|
96
116
|
detail:
|
|
@@ -141,10 +161,10 @@ events:
|
|
|
141
161
|
detail:
|
|
142
162
|
key:
|
|
143
163
|
type: string
|
|
144
|
-
description: Column key being sorted.
|
|
164
|
+
description: Column key being sorted — the detail field is `key` (not `column`).
|
|
145
165
|
dir:
|
|
146
166
|
type: string
|
|
147
|
-
description: Sort direction — "asc" / "desc" / null (cleared).
|
|
167
|
+
description: Sort direction — "asc" / "desc" / null (cleared). The detail field is `dir` (not `direction`).
|
|
148
168
|
sortState:
|
|
149
169
|
type: array
|
|
150
170
|
description: Full sort state array (multi-column support).
|
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.21",
|
|
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",
|