@adia-ai/web-components 0.6.8 → 0.6.10
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 +16 -0
- package/USAGE.md +29 -0
- package/components/action-list/action-list.css +1 -1
- package/components/card/card.css +43 -43
- package/components/chart/chart.css +1 -1
- package/components/chat-thread/chat-thread.css +6 -6
- package/components/code/code.css +17 -17
- package/components/command/command.css +7 -7
- package/components/grid/grid.css +6 -6
- package/components/pane/pane.css +10 -10
- package/components/stack/stack.css +1 -1
- package/components/table-toolbar/class.js +49 -0
- package/components/table-toolbar/table-toolbar.test.js +152 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-components
|
|
2
2
|
|
|
3
|
+
## [0.6.10] — 2026-05-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`<table-toolbar-ui>` — one-shot dev-mode `console.warn` when unknown opt-out attributes are set (claims-ui FB-04 enhancement).** Triggered by claims-ui FB-04 (RESPONSE-04, v0.6.9 §379): consumers writing `<table-toolbar-ui searchable>` (or `exportable` / `sortable` / `filterable` / `columns-visible`) thinking those attributes toggle visibility saw silent no-ops — the canonical attrs are `no-search` / `no-filter` / `no-sort` / `no-columns`. Pre-v0.6.10 behavior: the unknown attrs simply did nothing. Post-fix: a one-shot `console.warn` per element fires from `connected()`, names the unknown attrs, and points consumers at `npx adia-ai-doc table-toolbar` and the yaml SoT for the canonical reference. Pattern: warn-once-per-element via static `WeakSet` (`UITableToolbar._warnedUnknownAttrs`), matching the `tooltip-ui._warnedMissing` discipline. Scope: case-insensitive attrs starting with `search` / `export` / `sort` / `filter` / `column`, minus the 4 canonical opt-outs. Standard HTML/ARIA attrs (`id`, `class`, `role`, `aria-*`, `data-*`) are never flagged. Pinned by 12 new vitest cases in `table-toolbar.test.js`.
|
|
7
|
+
|
|
8
|
+
### Internal
|
|
9
|
+
- **`scripts/release/check-browser-safe.mjs` — strict-by-default + `/bin/` allowlist.** Flipped `STRICT = args.has('--strict')` → `STRICT = !args.has('--loose')` to match the convention shared by the other 6 release gates (`check:absolute-imports`, `check:no-self-import-css`, `check:scope-bare-descendants`, `check:lightningcss-build`, `check:rolldown-glob`, `check:with-css-pairing`). Paired with `/\/bin\//` added to `NODE_ONLY_PATTERNS` — silences the only pre-existing warning (`packages/web-components/bin/doc.mjs`, the `adia-ui-doc` CLI shipped v0.6.7). The CLI's `node:fs` / `node:path` / `node:url` imports are by design (#!/usr/bin/env node shebang); the broad `/bin/` pattern covers any future package-bin CLIs. Tracked at journal §378 as deferred-to-v0.6.10. No consumer-facing API change.
|
|
10
|
+
|
|
11
|
+
## [0.6.9] — 2026-05-21
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **9 component CSS files — `@scope` blocks no longer use bare-combinator descendants (F-002, P1).** Pre-fix: 92 selector clauses across `action-list.css`, `card.css`, `chart.css`, `chat-thread.css`, `code.css`, `command.css`, `grid.css`, `pane.css`, `stack.css` used the `@scope { > X { } }` shape (bare `>` combinator with no left-hand selector inside `@scope`). Chromium/Firefox tolerate this; LightningCSS rejects it as "Invalid empty selector" (matches the CSS Nesting + `@scope` spec strictly). Symptom: consumers on Vite 7 (default `cssMinify: 'lightningcss'`) saw build-time failures at `card.css:152`; Vite 6 + earlier Vite 7 (esbuild minify) silently swallowed the same source. Fix: codemod-driven rewrite of all 92 clauses from `> X` to `& > X` (explicit `&` reference matches the modern CSS Nesting idiom; semantically identical; LightningCSS-clean). All 120 CSS files now pass LightningCSS minify against Vite 8 default targets (Chromium 125+, Safari 18+, Firefox 129+). Codemod source: `scripts/build/codemod-scope-bare-descendants.mjs` (committed for future audits; idempotent + `--verify` mode). Paired CI gate `check:scope-bare-descendants` blocks regression at PR time; companion `check:lightningcss-build` smoke-minifies every CSS file end-to-end against Vite 8 targets. Closes claims-ui FEEDBACK-02.
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
- **`USAGE.md` — new "Looking up a component's API" subsection.** Three discovery paths now documented in one place: `npx adia-ai-doc <component>` CLI (machine-readable + offline-safe), `<component>.examples.html` per-component live demo + Properties table, and `<component>.yaml` source-of-truth schema. Closes claims-ui FEEDBACK-09 (per-component opt-out attributes are documented; this subsection makes the doc-path discoverable from the top-level USAGE entrypoint).
|
|
18
|
+
|
|
3
19
|
## [0.6.8] — 2026-05-20
|
|
4
20
|
|
|
5
21
|
### Fixed
|
package/USAGE.md
CHANGED
|
@@ -59,6 +59,35 @@ import { signal, computed, effect, html, UIElement, UIFormElement } from '@adia-
|
|
|
59
59
|
import '@adia-ai/web-components/traits';
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
### Looking up a component's API
|
|
63
|
+
|
|
64
|
+
Three reliable surfaces, in order of immediacy:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 1. CLI (since v0.6.7) — prints yaml summary + live demo URL + first example
|
|
68
|
+
npx adia-ui-doc table-toolbar # full prop / slot / event surface
|
|
69
|
+
npx adia-ui-doc # bare invocation lists all 96 components
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# 2. Per-component examples (ship in npm tarball since v0.6.7)
|
|
74
|
+
cat node_modules/@adia-ai/web-components/components/table-toolbar/table-toolbar.examples.md
|
|
75
|
+
|
|
76
|
+
# Or the richer .examples.html for Properties tables + multi-section walks
|
|
77
|
+
open node_modules/@adia-ai/web-components/components/table-toolbar/table-toolbar.examples.html
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# 3. The component's yaml (authoritative — all props, defaults, descriptions)
|
|
82
|
+
cat node_modules/@adia-ai/web-components/components/table-toolbar/table-toolbar.yaml
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The yaml is the source of truth for every property, slot, event, and token.
|
|
86
|
+
The CLI reads it. The `.d.ts` files are generated from it. When this USAGE
|
|
87
|
+
guide and the yaml disagree, the yaml wins — file a doc-bug FEEDBACK.
|
|
88
|
+
|
|
89
|
+
For the live demo of every component: <https://ui-kit.exe.xyz/site/components/>.
|
|
90
|
+
|
|
62
91
|
---
|
|
63
92
|
|
|
64
93
|
## The mental model
|
|
@@ -99,7 +99,7 @@ action-item-ui:hover [slot="icon"] {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/* Default slot (trailing content: kbd, badge, chevron) pushed to far right */
|
|
102
|
-
> :not([slot="icon"]):not([slot="text"]) {
|
|
102
|
+
& > :not([slot="icon"]):not([slot="text"]) {
|
|
103
103
|
flex-shrink: 0;
|
|
104
104
|
margin-inline-start: auto;
|
|
105
105
|
color: var(--action-item-icon-fg);
|
package/components/card/card.css
CHANGED
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
[padding] switches to padding mode with background.
|
|
150
150
|
Grid layout: optional icon | heading+description | optional action */
|
|
151
151
|
|
|
152
|
-
> header {
|
|
152
|
+
& > header {
|
|
153
153
|
display: block;
|
|
154
154
|
margin: var(--card-inset);
|
|
155
155
|
}
|
|
@@ -157,24 +157,24 @@
|
|
|
157
157
|
/* Activate grid layout only when a DIRECT slotted child is present.
|
|
158
158
|
Constraining to `:has(> [slot])` prevents deeply nested slots (e.g. an
|
|
159
159
|
[slot="icon"] inside an <avatar-ui>) from falsely activating the grid. */
|
|
160
|
-
> header:has(> [slot]) {
|
|
160
|
+
& > header:has(> [slot]) {
|
|
161
161
|
display: grid;
|
|
162
162
|
gap: var(--card-header-gap);
|
|
163
163
|
align-items: center;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
> header[padding] {
|
|
166
|
+
& > header[padding] {
|
|
167
167
|
margin: 0;
|
|
168
168
|
padding: var(--card-inset);
|
|
169
169
|
background: var(--card-bg-padded);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
> header[divider] {
|
|
172
|
+
& > header[divider] {
|
|
173
173
|
padding-bottom: var(--card-inset);
|
|
174
174
|
border-bottom: 1px solid var(--card-divider);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
> header[center] {
|
|
177
|
+
& > header[center] {
|
|
178
178
|
text-align: center;
|
|
179
179
|
justify-items: center;
|
|
180
180
|
}
|
|
@@ -182,16 +182,16 @@
|
|
|
182
182
|
/* Column templates — match gen-ui-kit pattern. Direct-child `:has(> …)` so
|
|
183
183
|
nested [slot="icon"] inside a composite (e.g. <avatar-ui>) can't trigger
|
|
184
184
|
the icon column by accident. */
|
|
185
|
-
> header:has(> [slot="icon"]):has(> [slot="action"]) { grid-template-columns: max-content 1fr max-content; }
|
|
186
|
-
> header:has(> [slot="icon"]):not(:has(> [slot="action"])) { grid-template-columns: max-content 1fr; }
|
|
187
|
-
> header:not(:has(> [slot="icon"])):has(> [slot="action"]) { grid-template-columns: 1fr max-content; }
|
|
188
|
-
> header:not(:has(> [slot="icon"])):not(:has(> [slot="action"])) { grid-template-columns: 1fr; }
|
|
185
|
+
& > header:has(> [slot="icon"]):has(> [slot="action"]) { grid-template-columns: max-content 1fr max-content; }
|
|
186
|
+
& > header:has(> [slot="icon"]):not(:has(> [slot="action"])) { grid-template-columns: max-content 1fr; }
|
|
187
|
+
& > header:not(:has(> [slot="icon"])):has(> [slot="action"]) { grid-template-columns: 1fr max-content; }
|
|
188
|
+
& > header:not(:has(> [slot="icon"])):not(:has(> [slot="action"])) { grid-template-columns: 1fr; }
|
|
189
189
|
|
|
190
190
|
/* Unslotted children (zettel fragment injection, etc.) — each on its own row,
|
|
191
191
|
spanning the full width, stacked ABOVE the heading/description/action grid.
|
|
192
192
|
This lets compositions inject logos, banners, etc. into a header without
|
|
193
193
|
having to know the slot vocabulary. */
|
|
194
|
-
> header > *:not([slot]):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(p):not(small) {
|
|
194
|
+
& > header > *:not([slot]):not(h1):not(h2):not(h3):not(h4):not(h5):not(h6):not(p):not(small) {
|
|
195
195
|
grid-column: 1 / -1;
|
|
196
196
|
justify-self: center;
|
|
197
197
|
}
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
Default: single row, center-aligned with heading/action.
|
|
201
201
|
When a description row exists, spans both rows and anchors to the
|
|
202
202
|
start so the icon visually aligns with the heading. */
|
|
203
|
-
> header > [slot="icon"] {
|
|
203
|
+
& > header > [slot="icon"] {
|
|
204
204
|
grid-column: 1;
|
|
205
205
|
grid-row: 1;
|
|
206
206
|
align-self: center;
|
|
@@ -209,39 +209,39 @@
|
|
|
209
209
|
justify-content: center;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
> header:has(> :is([slot="description"], p, small)) > [slot="icon"] {
|
|
212
|
+
& > header:has(> :is([slot="description"], p, small)) > [slot="icon"] {
|
|
213
213
|
grid-row: 1 / span 2;
|
|
214
214
|
align-self: start;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
/* Heading — row 1 */
|
|
218
|
-
> header > :is([slot="heading"], h1, h2, h3, h4, h5, h6),
|
|
219
|
-
> header > [slot="heading"] :is(h1, h2, h3, h4, h5, h6) {
|
|
218
|
+
& > header > :is([slot="heading"], h1, h2, h3, h4, h5, h6),
|
|
219
|
+
& > header > [slot="heading"] :is(h1, h2, h3, h4, h5, h6) {
|
|
220
220
|
grid-row: 1;
|
|
221
221
|
line-height: 1.3;
|
|
222
222
|
margin: 0;
|
|
223
223
|
}
|
|
224
224
|
/* Heading slot is a flex container — can hold title + inline badges/metadata */
|
|
225
|
-
> header > [slot="heading"] {
|
|
225
|
+
& > header > [slot="heading"] {
|
|
226
226
|
display: flex;
|
|
227
227
|
align-items: center;
|
|
228
228
|
gap: var(--card-header-gap);
|
|
229
229
|
}
|
|
230
|
-
> header:has(> [slot="icon"]) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) { grid-column: 2; }
|
|
231
|
-
> header:not(:has(> [slot="icon"])) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) { grid-column: 1; }
|
|
230
|
+
& > header:has(> [slot="icon"]) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) { grid-column: 2; }
|
|
231
|
+
& > header:not(:has(> [slot="icon"])) > :is([slot="heading"], h1, h2, h3, h4, h5, h6) { grid-column: 1; }
|
|
232
232
|
|
|
233
233
|
/* Description — row 2 */
|
|
234
|
-
> header > :is([slot="description"], p, small) {
|
|
234
|
+
& > header > :is([slot="description"], p, small) {
|
|
235
235
|
grid-row: 2;
|
|
236
236
|
grid-column: 1 / -1;
|
|
237
237
|
line-height: 1.4;
|
|
238
238
|
margin: 0;
|
|
239
239
|
}
|
|
240
|
-
> header:has(> [slot="icon"]) > :is([slot="description"], p, small) { grid-column: 2 / -1; }
|
|
240
|
+
& > header:has(> [slot="icon"]) > :is([slot="description"], p, small) { grid-column: 2 / -1; }
|
|
241
241
|
|
|
242
242
|
/* Action — row 1, last column.
|
|
243
243
|
Flex container so it can hold badge + button + anything inline. */
|
|
244
|
-
> header [slot="action"] {
|
|
244
|
+
& > header [slot="action"] {
|
|
245
245
|
justify-self: end;
|
|
246
246
|
align-self: center;
|
|
247
247
|
grid-row: 1;
|
|
@@ -256,17 +256,17 @@
|
|
|
256
256
|
[padding] switches to padding with background.
|
|
257
257
|
[bleed] removes all spacing for edge-to-edge content. */
|
|
258
258
|
|
|
259
|
-
> section {
|
|
259
|
+
& > section {
|
|
260
260
|
margin: var(--card-inset);
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
> section[padding] {
|
|
263
|
+
& > section[padding] {
|
|
264
264
|
margin: 0;
|
|
265
265
|
padding: var(--card-inset);
|
|
266
266
|
background: var(--card-bg-padded);
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
> section[bleed] {
|
|
269
|
+
& > section[bleed] {
|
|
270
270
|
margin: 0;
|
|
271
271
|
padding: 0;
|
|
272
272
|
}
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
[divider] adds top border.
|
|
277
277
|
[padding] switches to padded mode. */
|
|
278
278
|
|
|
279
|
-
> footer {
|
|
279
|
+
& > footer {
|
|
280
280
|
display: block;
|
|
281
281
|
margin: var(--card-inset);
|
|
282
282
|
}
|
|
@@ -284,8 +284,8 @@
|
|
|
284
284
|
/* Activate flex layout when a direct slotted child or multiple children are
|
|
285
285
|
present. `:has(> [slot])` so nested [slot="…"] inside composites can't
|
|
286
286
|
trigger the flex row. */
|
|
287
|
-
> footer:has(> [slot]),
|
|
288
|
-
> footer:has(> :nth-child(2)) {
|
|
287
|
+
& > footer:has(> [slot]),
|
|
288
|
+
& > footer:has(> :nth-child(2)) {
|
|
289
289
|
display: flex;
|
|
290
290
|
flex-wrap: wrap;
|
|
291
291
|
align-items: center;
|
|
@@ -293,30 +293,30 @@
|
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
/* col-ui in footer takes full width (common for stacked footer: button + divider + social) */
|
|
296
|
-
> footer > col-ui { width: 100%; }
|
|
296
|
+
& > footer > col-ui { width: 100%; }
|
|
297
297
|
|
|
298
|
-
> footer[justify="end"] {
|
|
298
|
+
& > footer[justify="end"] {
|
|
299
299
|
justify-content: flex-end;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
> footer[divider] {
|
|
302
|
+
& > footer[divider] {
|
|
303
303
|
margin-top: var(--card-inset);
|
|
304
304
|
padding-top: var(--card-inset);
|
|
305
305
|
border-top: 1px solid var(--card-divider);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
> footer[padding] {
|
|
308
|
+
& > footer[padding] {
|
|
309
309
|
margin: 0;
|
|
310
310
|
padding: var(--card-inset);
|
|
311
311
|
background: var(--card-bg-padded);
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
/* Footer with direct-child description + action = space-between */
|
|
315
|
-
> footer:has(> :is([slot="description"], p, small)):has(> [slot="action"]) {
|
|
315
|
+
& > footer:has(> :is([slot="description"], p, small)):has(> [slot="action"]) {
|
|
316
316
|
justify-content: space-between;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
> footer :is([slot="description"], p, small) {
|
|
319
|
+
& > footer :is([slot="description"], p, small) {
|
|
320
320
|
font-size: var(--card-description-size);
|
|
321
321
|
color: var(--card-description-fg);
|
|
322
322
|
flex: 1;
|
|
@@ -327,7 +327,7 @@
|
|
|
327
327
|
/* Footer heading — trend-stat line (symmetric with header's [slot="heading"]).
|
|
328
328
|
Used for chart trend-footer patterns like "Trending up by 5.2% this month".
|
|
329
329
|
Paired with [slot="description"] for a "Jan – Mar 2024" period caption. */
|
|
330
|
-
> footer > [slot="heading"] {
|
|
330
|
+
& > footer > [slot="heading"] {
|
|
331
331
|
font-size: var(--card-font-size);
|
|
332
332
|
font-weight: var(--a-weight-medium);
|
|
333
333
|
color: var(--card-heading-fg);
|
|
@@ -339,21 +339,21 @@
|
|
|
339
339
|
/* Footer with heading + description = column stack, heading on top. The
|
|
340
340
|
existing flex-row layout becomes a flex-column when a heading is present,
|
|
341
341
|
so the trend line and period caption stack naturally. */
|
|
342
|
-
> footer:has(> [slot="heading"]) {
|
|
342
|
+
& > footer:has(> [slot="heading"]) {
|
|
343
343
|
flex-direction: column;
|
|
344
344
|
align-items: flex-start;
|
|
345
345
|
gap: var(--a-space-0-5);
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
> footer:has(> [slot="heading"])[justify="end"] {
|
|
348
|
+
& > footer:has(> [slot="heading"])[justify="end"] {
|
|
349
349
|
align-items: flex-end;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
-
> footer:has(> [slot="heading"])[justify="center"] {
|
|
352
|
+
& > footer:has(> [slot="heading"])[justify="center"] {
|
|
353
353
|
align-items: center;
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
> footer [slot="action"] {
|
|
356
|
+
& > footer [slot="action"] {
|
|
357
357
|
margin-inline-start: auto;
|
|
358
358
|
display: flex;
|
|
359
359
|
gap: var(--card-footer-gap);
|
|
@@ -361,14 +361,14 @@
|
|
|
361
361
|
|
|
362
362
|
/* Multiple direct action-slotted children (A2UI pattern):
|
|
363
363
|
only the first pushes the group right; subsequent ones flow with gap */
|
|
364
|
-
> footer > [slot="action"] ~ [slot="action"] {
|
|
364
|
+
& > footer > [slot="action"] ~ [slot="action"] {
|
|
365
365
|
margin-inline-start: 0;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
/* Dual-cluster footer: leading action (e.g. Delete) on the inline-start
|
|
369
369
|
edge, trailing action cluster on the inline-end. margin-inline-end:
|
|
370
370
|
auto fills the gap between the two groups. */
|
|
371
|
-
> footer > [slot="action-leading"] {
|
|
371
|
+
& > footer > [slot="action-leading"] {
|
|
372
372
|
margin-inline-end: auto;
|
|
373
373
|
display: flex;
|
|
374
374
|
gap: var(--card-footer-gap);
|
|
@@ -376,15 +376,15 @@
|
|
|
376
376
|
|
|
377
377
|
/* ═══════ Images ═══════ */
|
|
378
378
|
|
|
379
|
-
> img,
|
|
380
|
-
> [slot="media"] {
|
|
379
|
+
& > img,
|
|
380
|
+
& > [slot="media"] {
|
|
381
381
|
display: block;
|
|
382
382
|
width: 100%;
|
|
383
383
|
object-fit: cover;
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
> img:first-child,
|
|
387
|
-
> [slot="media"]:first-child {
|
|
386
|
+
& > img:first-child,
|
|
387
|
+
& > [slot="media"]:first-child {
|
|
388
388
|
border-radius: var(--card-radius) var(--card-radius) 0 0;
|
|
389
389
|
}
|
|
390
390
|
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
/* Empty-state slot — author places <empty-state-ui slot="empty"> inside
|
|
113
113
|
chart-ui. CSS toggles visibility on [data-has-data] (set by chart.js
|
|
114
114
|
when .data is non-empty). No data ⇒ empty-state visible; data ⇒ hidden. */
|
|
115
|
-
> [slot="empty"] { display: none; }
|
|
115
|
+
& > [slot="empty"] { display: none; }
|
|
116
116
|
:scope:not([data-has-data]) > [slot="empty"] {
|
|
117
117
|
display: flex;
|
|
118
118
|
flex: 1 1 auto;
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/* -- Header -- */
|
|
68
|
-
> header {
|
|
68
|
+
& > header {
|
|
69
69
|
display: flex;
|
|
70
70
|
align-items: center;
|
|
71
71
|
gap: var(--chat-gap);
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
border-bottom: 1px solid var(--chat-border-color);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
> header [slot="avatar"] {
|
|
76
|
+
& > header [slot="avatar"] {
|
|
77
77
|
width: var(--chat-header-avatar-size);
|
|
78
78
|
height: var(--chat-header-avatar-size);
|
|
79
79
|
border-radius: var(--chat-header-avatar-radius);
|
|
@@ -87,19 +87,19 @@
|
|
|
87
87
|
flex-shrink: 0;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
> header [slot="name"] {
|
|
90
|
+
& > header [slot="name"] {
|
|
91
91
|
font-weight: var(--chat-header-name-weight);
|
|
92
92
|
font-size: var(--chat-header-name-size);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
> header [slot="status"] {
|
|
95
|
+
& > header [slot="status"] {
|
|
96
96
|
font-size: var(--chat-header-status-size);
|
|
97
97
|
color: var(--chat-header-status-fg);
|
|
98
98
|
margin-inline-start: auto;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
/* -- Messages -- */
|
|
102
|
-
> section {
|
|
102
|
+
& > section {
|
|
103
103
|
flex: 1;
|
|
104
104
|
overflow-y: auto;
|
|
105
105
|
padding: var(--chat-messages-padding);
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
/* -- Footer -- */
|
|
178
|
-
> footer {
|
|
178
|
+
& > footer {
|
|
179
179
|
min-height: var(--chat-footer-min-height);
|
|
180
180
|
display: flex;
|
|
181
181
|
align-items: center;
|
package/components/code/code.css
CHANGED
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
/* Header — chrome band above the code block */
|
|
93
|
-
> header {
|
|
93
|
+
& > header {
|
|
94
94
|
display: flex;
|
|
95
95
|
flex-direction: row;
|
|
96
96
|
align-items: center;
|
|
@@ -103,14 +103,14 @@
|
|
|
103
103
|
color: var(--code-header-fg);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
> header [slot="label"] {
|
|
106
|
+
& > header [slot="label"] {
|
|
107
107
|
font-weight: 500;
|
|
108
108
|
text-transform: uppercase;
|
|
109
109
|
letter-spacing: 0.05em;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/* Copy button — ghost style */
|
|
113
|
-
> header [slot="copy"] {
|
|
113
|
+
& > header [slot="copy"] {
|
|
114
114
|
all: unset;
|
|
115
115
|
cursor: pointer;
|
|
116
116
|
font-size: var(--code-header-font);
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
background var(--code-duration) var(--code-easing),
|
|
122
122
|
color var(--code-duration) var(--code-easing);
|
|
123
123
|
}
|
|
124
|
-
> header [slot="copy"]:hover {
|
|
124
|
+
& > header [slot="copy"]:hover {
|
|
125
125
|
background: var(--code-copy-hover-bg);
|
|
126
126
|
color: var(--code-copy-hover-fg);
|
|
127
127
|
}
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
/* Pre > Code — reset any ambient page styling (margins, borders,
|
|
130
130
|
radii, backgrounds) so the <code-ui> chrome is the single source
|
|
131
131
|
of visual framing. */
|
|
132
|
-
> pre {
|
|
132
|
+
& > pre {
|
|
133
133
|
margin: 0;
|
|
134
134
|
border: none;
|
|
135
135
|
border-radius: 0;
|
|
@@ -141,11 +141,11 @@
|
|
|
141
141
|
line-height: 1.5;
|
|
142
142
|
scrollbar-width: none;
|
|
143
143
|
}
|
|
144
|
-
> pre::-webkit-scrollbar {
|
|
144
|
+
& > pre::-webkit-scrollbar {
|
|
145
145
|
display: none;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
> pre > code {
|
|
148
|
+
& > pre > code {
|
|
149
149
|
margin: 0;
|
|
150
150
|
padding: 0;
|
|
151
151
|
border: none;
|
|
@@ -162,10 +162,10 @@
|
|
|
162
162
|
a `[data-line-state]` row whose bg picks up the state token. The
|
|
163
163
|
CodeMirror path doesn't use these markers; line decorations there go
|
|
164
164
|
through CM extensions instead. */
|
|
165
|
-
> pre > code[data-line-state-mode] {
|
|
165
|
+
& > pre > code[data-line-state-mode] {
|
|
166
166
|
display: block;
|
|
167
167
|
}
|
|
168
|
-
> pre > code[data-line-state-mode] > [data-line-state] {
|
|
168
|
+
& > pre > code[data-line-state-mode] > [data-line-state] {
|
|
169
169
|
display: grid;
|
|
170
170
|
grid-template-columns: 1fr;
|
|
171
171
|
/* Bleed the row tint to the pre's padding edges so it reads as a
|
|
@@ -173,33 +173,33 @@
|
|
|
173
173
|
margin-inline: calc(-1 * var(--code-px));
|
|
174
174
|
padding-inline: var(--code-px);
|
|
175
175
|
}
|
|
176
|
-
> pre > code[data-line-numbers] > [data-line-state] {
|
|
176
|
+
& > pre > code[data-line-numbers] > [data-line-state] {
|
|
177
177
|
grid-template-columns: auto 1fr;
|
|
178
178
|
column-gap: var(--a-space-3);
|
|
179
179
|
}
|
|
180
|
-
> pre > code[data-line-state-mode] [data-line-num] {
|
|
180
|
+
& > pre > code[data-line-state-mode] [data-line-num] {
|
|
181
181
|
color: var(--a-fg-subtle);
|
|
182
182
|
text-align: end;
|
|
183
183
|
user-select: none;
|
|
184
184
|
min-width: 1.5ch;
|
|
185
185
|
}
|
|
186
|
-
> pre > code[data-line-state-mode] [data-line-body] {
|
|
186
|
+
& > pre > code[data-line-state-mode] [data-line-body] {
|
|
187
187
|
white-space: pre;
|
|
188
188
|
}
|
|
189
189
|
/* Empty lines still need height so the diff column counts line up. */
|
|
190
|
-
> pre > code[data-line-state-mode] [data-line-body]:empty::before {
|
|
190
|
+
& > pre > code[data-line-state-mode] [data-line-body]:empty::before {
|
|
191
191
|
content: " ";
|
|
192
192
|
}
|
|
193
|
-
> pre > code [data-line-state="added"] {
|
|
193
|
+
& > pre > code [data-line-state="added"] {
|
|
194
194
|
background: var(--a-success-muted);
|
|
195
195
|
}
|
|
196
|
-
> pre > code [data-line-state="removed"] {
|
|
196
|
+
& > pre > code [data-line-state="removed"] {
|
|
197
197
|
background: var(--a-danger-muted);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/* Footer — optional chrome band below the code block
|
|
201
201
|
(line counts, byte size, language family, etc.) */
|
|
202
|
-
> footer {
|
|
202
|
+
& > footer {
|
|
203
203
|
display: flex;
|
|
204
204
|
flex-direction: row;
|
|
205
205
|
align-items: center;
|
|
@@ -288,7 +288,7 @@
|
|
|
288
288
|
CM's CSS-in-JS stylesheets load first + at lower specificity, so
|
|
289
289
|
these rules override without !important. */
|
|
290
290
|
|
|
291
|
-
> [data-cm-mount] {
|
|
291
|
+
& > [data-cm-mount] {
|
|
292
292
|
display: block;
|
|
293
293
|
overflow: hidden;
|
|
294
294
|
}
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/* ── Search header ── */
|
|
90
|
-
> header {
|
|
90
|
+
& > header {
|
|
91
91
|
display: flex;
|
|
92
92
|
align-items: center;
|
|
93
93
|
gap: var(--command-gap);
|
|
@@ -96,13 +96,13 @@
|
|
|
96
96
|
flex-shrink: 0;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
> header icon-ui {
|
|
99
|
+
& > header icon-ui {
|
|
100
100
|
flex-shrink: 0;
|
|
101
101
|
color: var(--command-fg-muted);
|
|
102
102
|
--a-icon-size: 1rem;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
> header input {
|
|
105
|
+
& > header input {
|
|
106
106
|
flex: 1;
|
|
107
107
|
min-width: 0;
|
|
108
108
|
border: none;
|
|
@@ -115,16 +115,16 @@
|
|
|
115
115
|
padding: 0;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
> header input::placeholder {
|
|
118
|
+
& > header input::placeholder {
|
|
119
119
|
color: var(--command-fg-muted);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/* Suppress focus ring on the input — the palette itself is the focused surface */
|
|
123
|
-
> header input:focus-visible {
|
|
123
|
+
& > header input:focus-visible {
|
|
124
124
|
outline: none;
|
|
125
125
|
box-shadow: none;
|
|
126
126
|
}
|
|
127
|
-
> header:focus-within {
|
|
127
|
+
& > header:focus-within {
|
|
128
128
|
outline: none;
|
|
129
129
|
box-shadow: none;
|
|
130
130
|
}
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
/* ── Footer ── */
|
|
220
|
-
> footer {
|
|
220
|
+
& > footer {
|
|
221
221
|
display: flex;
|
|
222
222
|
align-items: center;
|
|
223
223
|
gap: var(--command-px);
|
package/components/grid/grid.css
CHANGED
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
:scope[columns="auto-fit"] { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
|
|
34
34
|
|
|
35
35
|
/* Column span — children can span multiple columns */
|
|
36
|
-
> [span="2"] { grid-column: span 2; }
|
|
37
|
-
> [span="3"] { grid-column: span 3; }
|
|
38
|
-
> [span="4"] { grid-column: span 4; }
|
|
39
|
-
> [span="5"] { grid-column: span 5; }
|
|
40
|
-
> [span="6"] { grid-column: span 6; }
|
|
41
|
-
> [span="full"] { grid-column: 1 / -1; }
|
|
36
|
+
& > [span="2"] { grid-column: span 2; }
|
|
37
|
+
& > [span="3"] { grid-column: span 3; }
|
|
38
|
+
& > [span="4"] { grid-column: span 4; }
|
|
39
|
+
& > [span="5"] { grid-column: span 5; }
|
|
40
|
+
& > [span="6"] { grid-column: span 6; }
|
|
41
|
+
& > [span="full"] { grid-column: 1 / -1; }
|
|
42
42
|
}
|
package/components/pane/pane.css
CHANGED
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/* ── Pane header ── */
|
|
84
|
-
> header {
|
|
84
|
+
& > header {
|
|
85
85
|
box-sizing: border-box;
|
|
86
86
|
display: flex;
|
|
87
87
|
align-items: center;
|
|
@@ -98,17 +98,17 @@
|
|
|
98
98
|
transition: background var(--pane-duration) var(--pane-easing);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
> header:hover {
|
|
101
|
+
& > header:hover {
|
|
102
102
|
background: var(--pane-header-bg-hover);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
> header:focus-visible {
|
|
105
|
+
& > header:focus-visible {
|
|
106
106
|
outline: none;
|
|
107
107
|
box-shadow: var(--a-focus-ring) inset;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
/* Collapse indicator — stamped by JS as icon-ui */
|
|
111
|
-
> header > [slot="chevron"] {
|
|
111
|
+
& > header > [slot="chevron"] {
|
|
112
112
|
--a-icon-size: var(--a-caret-size);
|
|
113
113
|
flex-shrink: 0;
|
|
114
114
|
margin-inline-start: auto;
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/* ── Sections ── */
|
|
125
|
-
> section {
|
|
125
|
+
& > section {
|
|
126
126
|
flex: 1;
|
|
127
127
|
min-height: 0;
|
|
128
128
|
overflow-y: auto;
|
|
@@ -130,13 +130,13 @@
|
|
|
130
130
|
padding: var(--pane-section-py) var(--pane-section-px);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
> section::-webkit-scrollbar {
|
|
133
|
+
& > section::-webkit-scrollbar {
|
|
134
134
|
display: none;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/* Section header — section's padding is the single source of horizontal inset;
|
|
138
138
|
content inside the section must not add its own padding. */
|
|
139
|
-
> section > header {
|
|
139
|
+
& > section > header {
|
|
140
140
|
font-size: var(--pane-section-header-size);
|
|
141
141
|
color: var(--pane-section-header-fg);
|
|
142
142
|
padding-bottom: var(--pane-col-gap);
|
|
@@ -147,12 +147,12 @@
|
|
|
147
147
|
|
|
148
148
|
/* Defensive: a data-col directly under a section inherits the section's inset;
|
|
149
149
|
adding its own padding would compound and misalign with the section header. */
|
|
150
|
-
> section > [data-col] {
|
|
150
|
+
& > section > [data-col] {
|
|
151
151
|
padding: 0;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/* Section divider between sections */
|
|
155
|
-
> section + section {
|
|
155
|
+
& > section + section {
|
|
156
156
|
border-top: 1px solid var(--pane-border);
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
/* ── Footer ── */
|
|
168
|
-
> footer {
|
|
168
|
+
& > footer {
|
|
169
169
|
min-height: var(--pane-bar-height);
|
|
170
170
|
display: flex;
|
|
171
171
|
align-items: center;
|
|
@@ -138,10 +138,21 @@ export class UITableToolbar extends UIElement {
|
|
|
138
138
|
#docListenerRaf = null;
|
|
139
139
|
#sortIndicatorRafs = new Set();
|
|
140
140
|
|
|
141
|
+
// §381 (v0.6.10, slice F): one-shot dev-mode warning when consumers
|
|
142
|
+
// set common-but-unknown opt-out attributes (e.g. `searchable`,
|
|
143
|
+
// `exportable`, `sortable`) thinking they control table-toolbar
|
|
144
|
+
// visibility. Canonical attrs are `no-search` / `no-filter` /
|
|
145
|
+
// `no-sort` / `no-columns`. WeakSet pinned static so each element
|
|
146
|
+
// warns at most once even across reconnects.
|
|
147
|
+
static _warnedUnknownAttrs = new WeakSet();
|
|
148
|
+
static _CANONICAL_OPT_OUT_ATTRS = new Set(['no-search', 'no-filter', 'no-sort', 'no-columns']);
|
|
149
|
+
static _OPT_OUT_PREFIX_RE = /^(search|export|sort|filter|column)/i;
|
|
150
|
+
|
|
141
151
|
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
142
152
|
|
|
143
153
|
connected() {
|
|
144
154
|
this.setAttribute('role', 'toolbar');
|
|
155
|
+
this.#warnUnknownOptOutAttrs();
|
|
145
156
|
this.#stamp();
|
|
146
157
|
this.#resolveTarget();
|
|
147
158
|
this.#syncFromTarget();
|
|
@@ -165,6 +176,44 @@ export class UITableToolbar extends UIElement {
|
|
|
165
176
|
this.#updateTitle();
|
|
166
177
|
}
|
|
167
178
|
|
|
179
|
+
// ── Unknown opt-out attribute warning (§381, v0.6.10 slice F) ────────────
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Warn once (per element) when the consumer sets attributes that look
|
|
183
|
+
* like opt-outs but aren't canonical. Triggered by claims-ui FB-04:
|
|
184
|
+
* consumers set `searchable` / `exportable` / `sortable` thinking they
|
|
185
|
+
* toggle visibility; the canonical attrs are `no-search`/`no-filter`/
|
|
186
|
+
* `no-sort`/`no-columns`. Silent no-op was the previous behavior —
|
|
187
|
+
* confusing UX. Pattern: warn-once-per-element via static WeakSet.
|
|
188
|
+
*
|
|
189
|
+
* Scope: attributes whose names start with `search`, `export`, `sort`,
|
|
190
|
+
* `filter`, `column` (case-insensitive). Canonical attrs are
|
|
191
|
+
* subtracted before the warn fires. Standard HTML/ARIA attrs (`id`,
|
|
192
|
+
* `class`, `role`, `aria-*`, `data-*`, etc.) are never flagged.
|
|
193
|
+
*
|
|
194
|
+
* Pinned by table-toolbar.test.js.
|
|
195
|
+
*/
|
|
196
|
+
#warnUnknownOptOutAttrs() {
|
|
197
|
+
if (UITableToolbar._warnedUnknownAttrs.has(this)) return;
|
|
198
|
+
const unknowns = [];
|
|
199
|
+
for (const attr of this.attributes) {
|
|
200
|
+
const name = attr.name.toLowerCase();
|
|
201
|
+
if (UITableToolbar._CANONICAL_OPT_OUT_ATTRS.has(name)) continue;
|
|
202
|
+
if (!UITableToolbar._OPT_OUT_PREFIX_RE.test(name)) continue;
|
|
203
|
+
unknowns.push(name);
|
|
204
|
+
}
|
|
205
|
+
if (unknowns.length === 0) return;
|
|
206
|
+
UITableToolbar._warnedUnknownAttrs.add(this);
|
|
207
|
+
const list = unknowns.map((n) => `"${n}"`).join(', ');
|
|
208
|
+
// eslint-disable-next-line no-console
|
|
209
|
+
console.warn(
|
|
210
|
+
`[table-toolbar-ui] Unknown opt-out attribute(s) ${list}. ` +
|
|
211
|
+
`Canonical opt-out attrs are: no-search, no-filter, no-sort, no-columns. ` +
|
|
212
|
+
`See packages/web-components/components/table-toolbar/table-toolbar.yaml ` +
|
|
213
|
+
`or run \`npx adia-ui-doc table-toolbar\` for the full attribute reference.`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
168
217
|
// ── Target resolution ────────────────────────────────────────────────────
|
|
169
218
|
|
|
170
219
|
#resolveTarget() {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* table-toolbar-ui tests
|
|
3
|
+
*
|
|
4
|
+
* Pinning the §381 (v0.6.10 slice F) one-shot dev-mode warning for
|
|
5
|
+
* unknown opt-out attributes. Triggered by claims-ui FB-04: consumers
|
|
6
|
+
* set `searchable` / `exportable` / `sortable` thinking they toggle
|
|
7
|
+
* visibility; canonical attrs are `no-search`/`no-filter`/`no-sort`/
|
|
8
|
+
* `no-columns`. Previous behavior: silent no-op.
|
|
9
|
+
*
|
|
10
|
+
* Contract:
|
|
11
|
+
* 1. Unknown attrs matching /^(search|export|sort|filter|column)/i
|
|
12
|
+
* → console.warn fires once per element with the attr names listed
|
|
13
|
+
* 2. Canonical attrs (no-search/no-filter/no-sort/no-columns) → no warn
|
|
14
|
+
* 3. Unrelated attrs (id, class, role, aria-*, data-*) → no warn
|
|
15
|
+
* 4. Multiple unknown attrs in one declaration → single warn (one warn
|
|
16
|
+
* per element, all unknown attrs in the message)
|
|
17
|
+
* 5. Repeated reconnects of the same element → still one warn total
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
21
|
+
import '../../core/element.js';
|
|
22
|
+
import './table-toolbar.js';
|
|
23
|
+
|
|
24
|
+
const tick = () => new Promise((r) => queueMicrotask(r));
|
|
25
|
+
|
|
26
|
+
function mount(html) {
|
|
27
|
+
const wrap = document.createElement('div');
|
|
28
|
+
wrap.innerHTML = html;
|
|
29
|
+
document.body.appendChild(wrap);
|
|
30
|
+
return wrap.firstElementChild;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('table-toolbar-ui — unknown opt-out attribute warning (§381 / FB-04)', () => {
|
|
34
|
+
beforeEach(() => { document.body.innerHTML = ''; });
|
|
35
|
+
|
|
36
|
+
it('warns when an unknown `searchable` attribute is set', async () => {
|
|
37
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
38
|
+
mount('<table-toolbar-ui searchable></table-toolbar-ui>');
|
|
39
|
+
await tick();
|
|
40
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
41
|
+
const msg = warn.mock.calls[0][0];
|
|
42
|
+
expect(msg).toContain('[table-toolbar-ui]');
|
|
43
|
+
expect(msg).toContain('"searchable"');
|
|
44
|
+
expect(msg).toContain('no-search');
|
|
45
|
+
warn.mockRestore();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('warns when an unknown `exportable` attribute is set', async () => {
|
|
49
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
50
|
+
mount('<table-toolbar-ui exportable></table-toolbar-ui>');
|
|
51
|
+
await tick();
|
|
52
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
53
|
+
const msg = warn.mock.calls[0][0];
|
|
54
|
+
expect(msg).toContain('"exportable"');
|
|
55
|
+
warn.mockRestore();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('warns when an unknown `sortable` attribute is set', async () => {
|
|
59
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
60
|
+
mount('<table-toolbar-ui sortable></table-toolbar-ui>');
|
|
61
|
+
await tick();
|
|
62
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
63
|
+
expect(warn.mock.calls[0][0]).toContain('"sortable"');
|
|
64
|
+
warn.mockRestore();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('warns when an unknown `filterable` attribute is set', async () => {
|
|
68
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
69
|
+
mount('<table-toolbar-ui filterable></table-toolbar-ui>');
|
|
70
|
+
await tick();
|
|
71
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
72
|
+
expect(warn.mock.calls[0][0]).toContain('"filterable"');
|
|
73
|
+
warn.mockRestore();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('warns when an unknown `columns-visible` attribute is set', async () => {
|
|
77
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
78
|
+
mount('<table-toolbar-ui columns-visible></table-toolbar-ui>');
|
|
79
|
+
await tick();
|
|
80
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
81
|
+
expect(warn.mock.calls[0][0]).toContain('"columns-visible"');
|
|
82
|
+
warn.mockRestore();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('does NOT warn on canonical no-search', async () => {
|
|
86
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
87
|
+
mount('<table-toolbar-ui no-search></table-toolbar-ui>');
|
|
88
|
+
await tick();
|
|
89
|
+
expect(warn).not.toHaveBeenCalled();
|
|
90
|
+
warn.mockRestore();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('does NOT warn on canonical no-filter / no-sort / no-columns', async () => {
|
|
94
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
95
|
+
mount('<table-toolbar-ui no-filter no-sort no-columns></table-toolbar-ui>');
|
|
96
|
+
await tick();
|
|
97
|
+
expect(warn).not.toHaveBeenCalled();
|
|
98
|
+
warn.mockRestore();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('does NOT warn on unrelated attributes (id, class, role, aria-*, data-*)', async () => {
|
|
102
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
103
|
+
mount('<table-toolbar-ui id="x" class="y" role="toolbar" aria-label="z" data-test="ok"></table-toolbar-ui>');
|
|
104
|
+
await tick();
|
|
105
|
+
expect(warn).not.toHaveBeenCalled();
|
|
106
|
+
warn.mockRestore();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('does NOT warn on canonical attrs even when placeholder is also set', async () => {
|
|
110
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
111
|
+
mount('<table-toolbar-ui no-search no-sort placeholder="Find..."></table-toolbar-ui>');
|
|
112
|
+
await tick();
|
|
113
|
+
expect(warn).not.toHaveBeenCalled();
|
|
114
|
+
warn.mockRestore();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('emits a single warn that names all unknown opt-out attrs', async () => {
|
|
118
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
119
|
+
mount('<table-toolbar-ui searchable exportable sortable></table-toolbar-ui>');
|
|
120
|
+
await tick();
|
|
121
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
122
|
+
const msg = warn.mock.calls[0][0];
|
|
123
|
+
expect(msg).toContain('"searchable"');
|
|
124
|
+
expect(msg).toContain('"exportable"');
|
|
125
|
+
expect(msg).toContain('"sortable"');
|
|
126
|
+
warn.mockRestore();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('warns at most once per element across reconnects', async () => {
|
|
130
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
131
|
+
const el = mount('<table-toolbar-ui searchable></table-toolbar-ui>');
|
|
132
|
+
await tick();
|
|
133
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
134
|
+
// Disconnect + reconnect.
|
|
135
|
+
el.remove();
|
|
136
|
+
document.body.appendChild(el);
|
|
137
|
+
await tick();
|
|
138
|
+
expect(warn).toHaveBeenCalledTimes(1); // unchanged
|
|
139
|
+
warn.mockRestore();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('mixes canonical + unknown — names only the unknown in the warning', async () => {
|
|
143
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
144
|
+
mount('<table-toolbar-ui no-search searchable></table-toolbar-ui>');
|
|
145
|
+
await tick();
|
|
146
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
147
|
+
const msg = warn.mock.calls[0][0];
|
|
148
|
+
expect(msg).toContain('"searchable"');
|
|
149
|
+
expect(msg).not.toContain('"no-search"');
|
|
150
|
+
warn.mockRestore();
|
|
151
|
+
});
|
|
152
|
+
});
|
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.10",
|
|
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",
|