@adia-ai/web-components 0.6.20 → 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 CHANGED
@@ -1,5 +1,19 @@
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
+
3
17
  ## [0.6.20] — 2026-05-21
4
18
 
5
19
  ### Added — `table-ui` `row-click` event (FEEDBACK-35)
@@ -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
- :scope[variant="info"] { --feed-item-fg: var(--a-info-fg); --feed-item-bg: var(--a-info-muted); }
108
- :scope[variant="success"] { --feed-item-fg: var(--a-success-fg); --feed-item-bg: var(--a-success-muted); }
109
- :scope[variant="warning"] { --feed-item-fg: var(--a-warning-fg); --feed-item-bg: var(--a-warning-muted); }
110
- :scope[variant="danger"] { --feed-item-fg: var(--a-danger-fg); --feed-item-bg: var(--a-danger-muted); }
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;
@@ -111,6 +111,7 @@ export class UITable extends UIElement {
111
111
  selectable: { type: Boolean, default: false, reflect: true },
112
112
  expandable: { type: Boolean, default: false, reflect: true },
113
113
  striped: { type: Boolean, default: false, reflect: true },
114
+ wrap: { type: Boolean, default: false, reflect: true },
114
115
  raw: { type: Boolean, default: false, reflect: true },
115
116
  density: { type: String, default: 'standard', reflect: true },
116
117
  paginate: { type: Number, default: 0, reflect: true },
@@ -827,6 +828,11 @@ export class UITable extends UIElement {
827
828
  cell.setAttribute('data-pinned', col.pinned);
828
829
  }
829
830
 
831
+ // Wrap (per-column opt-in to multi-line cell content)
832
+ if (col.wrap) {
833
+ cell.setAttribute('data-wrap', '');
834
+ }
835
+
830
836
  cells.push(cell);
831
837
  }
832
838
 
@@ -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": [
@@ -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
 
@@ -86,7 +86,7 @@ export interface TableSortEventDetail {
86
86
  export type TableSortEvent = CustomEvent<TableSortEventDetail>;
87
87
 
88
88
  export class UITable extends UIElement {
89
- /** 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. */
90
90
  columns: unknown[];
91
91
  /** Row records. Array of plain objects keyed to columns[].key. */
92
92
  data: unknown[];
@@ -108,6 +108,8 @@ export class UITable extends UIElement {
108
108
  sortable: boolean;
109
109
  /** Alternate row background colors for visual scanning. */
110
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;
111
113
 
112
114
  addEventListener<K extends keyof HTMLElementEventMap>(
113
115
  type: K,
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-components",
3
- "version": "0.6.20",
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",