@adia-ai/web-components 0.8.43 → 0.8.44
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 +30 -0
- package/components/avatar/avatar.class.js +12 -1
- package/components/button/button.d.ts +4 -0
- package/components/chart/chart.a2ui.json +26 -0
- package/components/chart/chart.class.js +1304 -222
- package/components/chart/chart.css +151 -1
- package/components/chart/chart.d.ts +6 -0
- package/components/chart/chart.yaml +57 -0
- package/components/chart-legend/chart-legend.a2ui.json +17 -2
- package/components/chart-legend/chart-legend.class.js +69 -4
- package/components/chart-legend/chart-legend.css +65 -1
- package/components/chart-legend/chart-legend.d.ts +4 -2
- package/components/chart-legend/chart-legend.examples.md +4 -4
- package/components/chart-legend/chart-legend.yaml +43 -3
- package/components/combobox/combobox.a2ui.json +3 -0
- package/components/combobox/combobox.class.js +9 -8
- package/components/combobox/combobox.css +25 -4
- package/components/combobox/combobox.yaml +6 -0
- package/components/command/command.css +17 -1
- package/components/context-menu/context-menu.d.ts +2 -0
- package/components/heatmap/heatmap.a2ui.json +5 -0
- package/components/heatmap/heatmap.d.ts +2 -0
- package/components/heatmap/heatmap.yaml +8 -0
- package/components/input/input.a2ui.json +2 -2
- package/components/input/input.class.js +12 -7
- package/components/input/input.yaml +6 -5
- package/components/integration-card/integration-card.d.ts +2 -0
- package/components/noodles/noodles.a2ui.json +0 -15
- package/components/noodles/noodles.yaml +0 -12
- package/components/number-format/number-format.class.js +10 -0
- package/components/pagination/pagination.class.js +88 -4
- package/components/popover/popover.d.ts +2 -0
- package/components/progress/progress.class.js +11 -6
- package/components/range/range.class.js +9 -1
- package/components/select/select.a2ui.json +5 -1
- package/components/select/select.class.js +14 -14
- package/components/select/select.css +14 -1
- package/components/select/select.yaml +7 -1
- package/components/stream/stream.d.ts +2 -0
- package/components/table/table.a2ui.json +6 -1
- package/components/table/table.class.js +18 -1
- package/components/table/table.css +31 -6
- package/components/table/table.d.ts +3 -1
- package/components/table/table.examples.md +2 -2
- package/components/table/table.yaml +9 -1
- package/components/table-toolbar/table-toolbar.a2ui.json +28 -4
- package/components/table-toolbar/table-toolbar.class.js +346 -11
- package/components/table-toolbar/table-toolbar.css +265 -0
- package/components/table-toolbar/table-toolbar.d.ts +5 -1
- package/components/table-toolbar/table-toolbar.yaml +130 -15
- package/components/tag/tag.class.js +13 -1
- package/components/text/text.a2ui.json +3 -3
- package/components/text/text.d.ts +5 -3
- package/components/text/text.yaml +3 -3
- package/components/textarea/textarea.class.js +9 -1
- package/components/tour/tour.d.ts +10 -0
- package/core/a11y.d.ts +22 -0
- package/core/a11y.js +64 -0
- package/core/anchor.js +9 -5
- package/core/element.js +1 -1
- package/custom-elements.json +73 -29
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +119 -119
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/patterns/access-requests/access-requests.examples.html +1 -1
- package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +7 -7
- package/patterns/agent-cost/agent-cost.examples.html +6 -6
- package/patterns/agent-memory/agent-memory.examples.html +3 -3
- package/patterns/agent-prompt-library/agent-prompt-library.examples.html +8 -8
- package/patterns/agent-tool-call/agent-tool-call.examples.html +1 -1
- package/patterns/approvals/approvals.examples.html +6 -6
- package/patterns/audit-log/audit-log.examples.html +1 -1
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +4 -4
- package/patterns/chart-in-card/chart-in-card.examples.html +114 -0
- package/patterns/chart-in-card/chart-in-card.examples.js +37 -0
- package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +5 -5
- package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +2 -2
- package/patterns/diff-review/diff-review.examples.html +6 -6
- package/patterns/export-flow/export-flow.examples.html +2 -2
- package/patterns/filter-bar/filter-bar.examples.html +2 -2
- package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +2 -2
- package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
- package/patterns/marketing-engagement/marketing-engagement.examples.html +4 -4
- package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +1 -1
- package/patterns/permissions-matrix/permissions-matrix.examples.html +1 -1
- package/patterns/permissions-sharing/permissions-sharing.examples.html +2 -2
- package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +4 -4
- package/patterns/search-discovery/search-discovery.examples.html +2 -2
- package/styles/type/roles.css +29 -9
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
* • title + optional count badge
|
|
19
19
|
* • filter / sort / columns popover buttons
|
|
20
20
|
* • search input
|
|
21
|
-
* • optional [slot="actions"]
|
|
21
|
+
* • optional [slot="actions-leading"] region, BEFORE the page-size select (gh#1649)
|
|
22
|
+
* • optional page-size select
|
|
23
|
+
* • optional [slot="actions"] trailing region, AFTER the page-size select
|
|
22
24
|
*
|
|
23
25
|
* [for] resolution mirrors chart-legend-ui — the toolbar mounts an element by
|
|
24
26
|
* id, then dispatches state changes against it. When [for] is absent, falls
|
|
@@ -35,10 +37,24 @@
|
|
|
35
37
|
*/
|
|
36
38
|
|
|
37
39
|
import { UIElement } from '../../core/element.js';
|
|
38
|
-
import { anchorPopover } from '../../core/anchor.js';
|
|
40
|
+
import { anchorPopover, resolveLengthPx } from '../../core/anchor.js';
|
|
39
41
|
|
|
40
42
|
const SEARCH_DEBOUNCE = 200;
|
|
41
43
|
|
|
44
|
+
// ADR-0076 REQ-M-006 — the three breakpoints' NORMATIVE default pixel
|
|
45
|
+
// widths (56rem/40rem/26rem at the standard 16px root), duplicated here
|
|
46
|
+
// ONLY as a fallback for #classifyStage() below when the live
|
|
47
|
+
// `--table-toolbar-bp-*` custom property can't be read (no table-toolbar.css
|
|
48
|
+
// stylesheet attached yet, an SSR shim, or a unit-test harness with no real
|
|
49
|
+
// cascade) — resolveLengthPx() returns NaN in that case, `||` falls
|
|
50
|
+
// through to these. Table-toolbar.css's own `:where(:scope)` declarations
|
|
51
|
+
// are the actual source of truth whenever a real stylesheet IS present;
|
|
52
|
+
// this fallback exists so the reflection degrades to the DOCUMENTED
|
|
53
|
+
// default instead of silently misclassifying against NaN.
|
|
54
|
+
const DEFAULT_BP_SEARCH_TIGHT_PX = 56 * 16;
|
|
55
|
+
const DEFAULT_BP_ICON_ONLY_PX = 40 * 16;
|
|
56
|
+
const DEFAULT_BP_OVERFLOW_PX = 26 * 16;
|
|
57
|
+
|
|
42
58
|
function emptyHint(text) {
|
|
43
59
|
const el = document.createElement('text-ui');
|
|
44
60
|
el.setAttribute('data-popover-empty', '');
|
|
@@ -136,14 +152,28 @@ export class UITableToolbar extends UIElement {
|
|
|
136
152
|
noSort: { type: Boolean, default: false, reflect: true, attribute: 'no-sort' },
|
|
137
153
|
noColumns: { type: Boolean, default: false, reflect: true, attribute: 'no-columns' },
|
|
138
154
|
noSearch: { type: Boolean, default: false, reflect: true, attribute: 'no-search' },
|
|
155
|
+
// ADR-0076 (ADIA2-9123) — additive sugar over the four `no-*` attributes
|
|
156
|
+
// above. Pure, absolute OR: see #updateControlVisibility(), which is the
|
|
157
|
+
// single place that combines chromeOnly with each granular no-* value.
|
|
158
|
+
chromeOnly: { type: Boolean, default: false, reflect: true, attribute: 'chrome-only' },
|
|
139
159
|
placeholder: { type: String, default: 'Search...', reflect: false },
|
|
140
160
|
variant: { type: String, default: 'default', reflect: true },
|
|
161
|
+
// ADR-0076 REQ-O-001 — explicit compaction-stage override. Unset (the
|
|
162
|
+
// default, empty string) means auto-snap via the @container query
|
|
163
|
+
// (table-toolbar.css's :scope container-type/container-name). All four
|
|
164
|
+
// stages' CSS is now live: search-tight steps the search field down
|
|
165
|
+
// (step 2); icon-only drops control labels and compacts/hides the
|
|
166
|
+
// range summary (steps 3+4); overflow collapses Filter/Sort/Columns
|
|
167
|
+
// into one "More" trigger (step 5).
|
|
168
|
+
stage: { type: String, default: '', reflect: true },
|
|
141
169
|
};
|
|
142
170
|
|
|
143
171
|
// §205 (v0.5.7): dynamic sort-indicator icons (table-toolbar.class.js:576 — nested ternary
|
|
144
172
|
// `dir === 'asc' ? 'arrow-up' : dir === 'desc' ? 'arrow-down' : 'caret-up-down'`).
|
|
145
173
|
// Per FEEDBACK-16 §1 + §209 slot-11 ternary-walker discovery.
|
|
146
|
-
|
|
174
|
+
// 'dots-three-vertical' — the overflow-stage "More" trigger icon
|
|
175
|
+
// (ADR-0076 REQ-S-004, PLAN step 5, #mkOverflowTrigger()).
|
|
176
|
+
static requiredIcons = ['arrow-up', 'arrow-down', 'caret-up-down', 'dots-three-vertical'];
|
|
147
177
|
|
|
148
178
|
static template = () => null;
|
|
149
179
|
|
|
@@ -154,6 +184,17 @@ export class UITableToolbar extends UIElement {
|
|
|
154
184
|
#docListenerRaf = null;
|
|
155
185
|
#sortIndicatorRafs = new Set();
|
|
156
186
|
#pageSizeOptions = [];
|
|
187
|
+
// ADR-0076 REQ-M-005 (ADIA2-9123 S4-ii), PLAN step 7 — data-stage-resolved
|
|
188
|
+
// reflection. #resizeObs classifies live width against the SAME three
|
|
189
|
+
// breakpoints table-toolbar.css's @container rules use (read from the
|
|
190
|
+
// custom properties themselves, never a second hardcoded copy of
|
|
191
|
+
// 56/40/26 — see #classifyStage()). #lastWidthPx caches the last
|
|
192
|
+
// measured inline-size so a `stage` attribute change alone (no resize)
|
|
193
|
+
// can still re-classify without waiting on the next ResizeObserver
|
|
194
|
+
// callback (REQ-M-005's dual-trigger requirement).
|
|
195
|
+
#resizeObs = null;
|
|
196
|
+
#lastWidthPx = null;
|
|
197
|
+
#stageRaf = null;
|
|
157
198
|
|
|
158
199
|
// ── Public API: pageSizeOptions ──────────────────────────────────────────
|
|
159
200
|
// Dynamic array prop (JS-set collection, custom setter) — same shape as
|
|
@@ -195,6 +236,34 @@ export class UITableToolbar extends UIElement {
|
|
|
195
236
|
this.#stamp();
|
|
196
237
|
this.#resolveTarget();
|
|
197
238
|
this.#syncFromTarget();
|
|
239
|
+
|
|
240
|
+
// ADR-0076 REQ-M-005, PLAN step 7 — classify synchronously off
|
|
241
|
+
// `clientWidth` at connect time too (0 pre-layout treated as "no
|
|
242
|
+
// measurement yet", the same `null`-width fallback #reflectResolvedStage
|
|
243
|
+
// uses) rather than waiting on the ResizeObserver's own first
|
|
244
|
+
// (asynchronous) callback — avoids a brief connect-time gap with no
|
|
245
|
+
// `data-stage-resolved` reflected at all, mirroring how chart.class.js's
|
|
246
|
+
// own `data-ratio-resolved` is set synchronously from `#dims()` at
|
|
247
|
+
// first render(), not deferred to its ResizeObserver's first tick.
|
|
248
|
+
this.#lastWidthPx = this.clientWidth || null;
|
|
249
|
+
this.#reflectResolvedStage();
|
|
250
|
+
|
|
251
|
+
// gh#285 SSR DOM shims (linkedom) have no ResizeObserver global, same
|
|
252
|
+
// guard chart.class.js's own ResizeObserver setup uses.
|
|
253
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
254
|
+
this.#resizeObs = new ResizeObserver((entries) => {
|
|
255
|
+
const { inlineSize } = entries[0].contentBoxSize[0];
|
|
256
|
+
this.#lastWidthPx = inlineSize;
|
|
257
|
+
// rAF-batch, mirroring chart.class.js's own ResizeObserver setup —
|
|
258
|
+
// avoids a same-tick reflect→layout→observer-refire loop.
|
|
259
|
+
if (this.#stageRaf) return;
|
|
260
|
+
this.#stageRaf = requestAnimationFrame(() => {
|
|
261
|
+
this.#stageRaf = null;
|
|
262
|
+
this.#reflectResolvedStage();
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
this.#resizeObs.observe(this);
|
|
266
|
+
}
|
|
198
267
|
}
|
|
199
268
|
|
|
200
269
|
// gh#1620 — this used to live in a declared `attributeChanged(name)` hook
|
|
@@ -204,7 +273,8 @@ export class UITableToolbar extends UIElement {
|
|
|
204
273
|
// hook, keyed by the matching property names.
|
|
205
274
|
updated(changed) {
|
|
206
275
|
if (changed.has('noFilter') || changed.has('noSort')
|
|
207
|
-
|| changed.has('noColumns') || changed.has('noSearch')
|
|
276
|
+
|| changed.has('noColumns') || changed.has('noSearch')
|
|
277
|
+
|| changed.has('chromeOnly')) {
|
|
208
278
|
this.#updateControlVisibility();
|
|
209
279
|
}
|
|
210
280
|
if (changed.has('placeholder')) {
|
|
@@ -213,6 +283,30 @@ export class UITableToolbar extends UIElement {
|
|
|
213
283
|
}
|
|
214
284
|
}
|
|
215
285
|
|
|
286
|
+
// ADR-0076 REQ-E-001 — `updated(changed)` alone MISSES a live loading→
|
|
287
|
+
// confirmed-zero transition: `rangeTotal`'s default is already 0, so
|
|
288
|
+
// setting `range-total="0"` from an unset start parses to the SAME
|
|
289
|
+
// Number(0), the base class's property setter's `Object.is` short-circuit
|
|
290
|
+
// swallows the signal write (core/element.js `installProps()`), and
|
|
291
|
+
// `changed` never gets a `rangeTotal` entry — `#updateSummary()`'s
|
|
292
|
+
// hasAttribute-based branch never re-runs. Verified live in a browser
|
|
293
|
+
// (not just the vitest suite, which happened to only exercise the
|
|
294
|
+
// positive→zero transition, where the signal DOES change). REQ-E-001's
|
|
295
|
+
// check is fundamentally an ATTRIBUTE-presence concern, not a coerced-
|
|
296
|
+
// value one, so it needs the raw attribute callback, not the signal diff.
|
|
297
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
298
|
+
super.attributeChangedCallback(name, oldVal, newVal);
|
|
299
|
+
if (name === 'range-total') this.#updateSummary();
|
|
300
|
+
// ADR-0076 REQ-M-005 — the SECOND of the two triggers `data-stage-
|
|
301
|
+
// resolved` must react to: a live resize (the ResizeObserver callback
|
|
302
|
+
// above) AND a `stage` attribute change alone, with no resize needed.
|
|
303
|
+
// Re-classifies against the cached last-measured width — a pin set
|
|
304
|
+
// before the first resize observation (#lastWidthPx still null) simply
|
|
305
|
+
// reports the pin; classifyStage() below treats a null width as "no
|
|
306
|
+
// live width to compare against yet", never a false 0-width match.
|
|
307
|
+
if (name === 'stage') this.#reflectResolvedStage();
|
|
308
|
+
}
|
|
309
|
+
|
|
216
310
|
disconnected() {
|
|
217
311
|
if (this.#docListenerRaf != null) {
|
|
218
312
|
cancelAnimationFrame(this.#docListenerRaf);
|
|
@@ -222,6 +316,68 @@ export class UITableToolbar extends UIElement {
|
|
|
222
316
|
this.#sortIndicatorRafs.clear();
|
|
223
317
|
this.#closePopover();
|
|
224
318
|
this.#detachTarget();
|
|
319
|
+
if (this.#stageRaf != null) {
|
|
320
|
+
cancelAnimationFrame(this.#stageRaf);
|
|
321
|
+
this.#stageRaf = null;
|
|
322
|
+
}
|
|
323
|
+
this.#resizeObs?.disconnect();
|
|
324
|
+
this.#resizeObs = null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── data-stage-resolved reflection (ADR-0076 REQ-M-005, ADIA2-9123 ─────────
|
|
328
|
+
// S4-ii, PLAN step 7) ────────────────────────────────────────────────────
|
|
329
|
+
//
|
|
330
|
+
// Purely informational — mirrors the CSS-decided stage for a consumer's
|
|
331
|
+
// own [slot="actions-leading"] content, never drives table-toolbar's own
|
|
332
|
+
// rendering (REQ-S stays 100% CSS `@container`-driven, REQ-M-002). The
|
|
333
|
+
// three breakpoints are read live off the SAME custom properties the CSS
|
|
334
|
+
// `@container` rules consume (--table-toolbar-bp-search-tight/-icon-only/
|
|
335
|
+
// -overflow) via `resolveLengthPx` (core/anchor.js, shared with its
|
|
336
|
+
// --popover-max-height use) — one source of truth, never a second
|
|
337
|
+
// hardcoded copy of 56/40/26 here.
|
|
338
|
+
|
|
339
|
+
#classifyStage(widthPx) {
|
|
340
|
+
const cs = getComputedStyle(this);
|
|
341
|
+
// resolveLengthPx() returns NaN when the custom property is unreadable
|
|
342
|
+
// — no table-toolbar.css stylesheet attached yet, an SSR shim, or a
|
|
343
|
+
// headless unit-test harness with no real cascade — so an explicit
|
|
344
|
+
// Number.isFinite() check falls through to each breakpoint's own
|
|
345
|
+
// documented REQ-M-006 default. This must be a real NaN check, not a
|
|
346
|
+
// `|| DEFAULT` fallback: `0` is a legitimate consumer override (e.g.
|
|
347
|
+
// `--table-toolbar-bp-overflow: 0rem` to disable the overflow stage
|
|
348
|
+
// entirely) and `||` would silently discard it as falsy, substituting
|
|
349
|
+
// the default instead — reviewer finding on PR #1676, fixed here.
|
|
350
|
+
// Whenever a real stylesheet IS present, the live custom property
|
|
351
|
+
// (overridable per-consumer, same posture as every other
|
|
352
|
+
// --table-toolbar-* token) wins, zero included.
|
|
353
|
+
const resolveBp = (raw, fallback) => {
|
|
354
|
+
const v = resolveLengthPx(raw);
|
|
355
|
+
return Number.isFinite(v) ? v : fallback;
|
|
356
|
+
};
|
|
357
|
+
const searchTightPx = resolveBp(cs.getPropertyValue('--table-toolbar-bp-search-tight'), DEFAULT_BP_SEARCH_TIGHT_PX);
|
|
358
|
+
const iconOnlyPx = resolveBp(cs.getPropertyValue('--table-toolbar-bp-icon-only'), DEFAULT_BP_ICON_ONLY_PX);
|
|
359
|
+
const overflowPx = resolveBp(cs.getPropertyValue('--table-toolbar-bp-overflow'), DEFAULT_BP_OVERFLOW_PX);
|
|
360
|
+
|
|
361
|
+
// Inclusive-on-the-narrower-side (max-width semantics, REQ-M-006) — a
|
|
362
|
+
// width exactly AT a boundary classifies to the narrower stage, same
|
|
363
|
+
// rule the CSS `@container (max-width: …)` conditions already encode.
|
|
364
|
+
if (widthPx <= overflowPx) return 'overflow';
|
|
365
|
+
if (widthPx <= iconOnlyPx) return 'icon-only';
|
|
366
|
+
if (widthPx <= searchTightPx) return 'search-tight';
|
|
367
|
+
return 'full';
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
#reflectResolvedStage() {
|
|
371
|
+
// REQ-O-001/REQ-M-005 — the pin wins for the reflection exactly as it
|
|
372
|
+
// wins for rendering: an explicit `stage` reports itself, never the
|
|
373
|
+
// width-derived classification, even inside a container sized for a
|
|
374
|
+
// different stage.
|
|
375
|
+
const resolved = this.stage || (this.#lastWidthPx == null ? 'full' : this.#classifyStage(this.#lastWidthPx));
|
|
376
|
+
// Is-different guard — same shape as chart.class.js's own
|
|
377
|
+
// data-ratio-resolved reflection (chart.class.js:533).
|
|
378
|
+
if (this.getAttribute('data-stage-resolved') !== resolved) {
|
|
379
|
+
this.setAttribute('data-stage-resolved', resolved);
|
|
380
|
+
}
|
|
225
381
|
}
|
|
226
382
|
|
|
227
383
|
render() {
|
|
@@ -357,12 +513,51 @@ export class UITableToolbar extends UIElement {
|
|
|
357
513
|
summaryText.setAttribute('data-summary-text', '');
|
|
358
514
|
summary.appendChild(summaryText);
|
|
359
515
|
|
|
516
|
+
// ADR-0076 REQ-S-003 — short-form compact text ("1–25/320"), a sibling
|
|
517
|
+
// of [data-summary-text] holding the SAME range values in a terser
|
|
518
|
+
// shape. Which of the two is visible is decided entirely by CSS
|
|
519
|
+
// (table-toolbar.css's icon-only @container rule / [stage] pin) —
|
|
520
|
+
// both nodes are always populated together in #updateSummary(), never
|
|
521
|
+
// a JS branch keyed on a resolved-stage signal (REQ-M-002 stays true:
|
|
522
|
+
// no JS decides rendering here, only which text each stage's CSS shows).
|
|
523
|
+
const summaryCompact = document.createElement('span');
|
|
524
|
+
summaryCompact.setAttribute('data-summary-compact', '');
|
|
525
|
+
summaryCompact.hidden = true;
|
|
526
|
+
summary.appendChild(summaryCompact);
|
|
527
|
+
|
|
528
|
+
// Empty-state slot (ADR-0076 REQ-E, adiav2 ADIA2-9123) — renders in
|
|
529
|
+
// [data-summary]'s own position, alongside [data-summary-text], never a
|
|
530
|
+
// separate region: only one of the two is ever visible at a time (see
|
|
531
|
+
// #updateSummary()). Same real-insertion-point pattern as [slot="scope"]
|
|
532
|
+
// / [slot="actions"] / [slot="actions-leading"] above — move any
|
|
533
|
+
// pre-existing [slot="empty"] children into this stamped wrapper rather
|
|
534
|
+
// than declaring a template-owned `parts:` entry (ADR-0067).
|
|
535
|
+
const summaryEmpty = document.createElement('div');
|
|
536
|
+
summaryEmpty.setAttribute('data-summary-empty', '');
|
|
537
|
+
summaryEmpty.hidden = true;
|
|
538
|
+
for (const node of [...this.children]) {
|
|
539
|
+
if (node === root) continue;
|
|
540
|
+
if (node.getAttribute?.('slot') === 'empty') {
|
|
541
|
+
summaryEmpty.appendChild(node);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
summary.appendChild(summaryEmpty);
|
|
545
|
+
|
|
360
546
|
// Controls cluster
|
|
361
547
|
const controls = document.createElement('div');
|
|
362
548
|
controls.setAttribute('data-controls', '');
|
|
363
549
|
controls.appendChild(this.#mkButton('filter', 'Filter', 'funnel-simple'));
|
|
364
550
|
controls.appendChild(this.#mkButton('sort', 'Sort', 'arrows-down-up'));
|
|
365
551
|
controls.appendChild(this.#mkButton('columns', 'Columns', 'columns'));
|
|
552
|
+
// ADR-0076 REQ-S-004/REQ-X-003, PLAN step 5 — overflow-stage "More"
|
|
553
|
+
// trigger. Stamped unconditionally alongside the three granular
|
|
554
|
+
// buttons above (never removed/re-created), CSS-hidden except at the
|
|
555
|
+
// overflow breakpoint/pin (REQ-M-002 stays true: which cluster paints
|
|
556
|
+
// is a pure CSS decision, not a JS branch). Keeping all four buttons
|
|
557
|
+
// permanently in the DOM is the simplest way to honor REQ-X-004's
|
|
558
|
+
// tab-order guarantee — nothing is ever created/destroyed across a
|
|
559
|
+
// stage transition, only shown/hidden.
|
|
560
|
+
controls.appendChild(this.#mkOverflowTrigger());
|
|
366
561
|
|
|
367
562
|
// Search — compose <search-ui>, which already stamps input-ui with
|
|
368
563
|
// the magnifying-glass prefix + clear suffix and debounces a `search`
|
|
@@ -389,6 +584,25 @@ export class UITableToolbar extends UIElement {
|
|
|
389
584
|
pageSize.hidden = true;
|
|
390
585
|
pageSize.addEventListener('change', this.#onPageSizeChange);
|
|
391
586
|
|
|
587
|
+
// Actions-leading slot passthrough (gh#1649) — a real insertion point
|
|
588
|
+
// (ADR-0067), same pattern as [slot="scope"]/[slot="actions"] above,
|
|
589
|
+
// rendered BEFORE the page-size select rather than after it: app-owned
|
|
590
|
+
// trigger buttons (e.g. custom Filter/Columns triggers that open the
|
|
591
|
+
// consumer's own drawer instead of the native filter/columns popovers)
|
|
592
|
+
// need to land ahead of page-size, which [slot="actions"] alone can't
|
|
593
|
+
// reach since it renders trailing (after page-size).
|
|
594
|
+
const actionsLeadingSlot = document.createElement('div');
|
|
595
|
+
actionsLeadingSlot.setAttribute('data-actions-leading', '');
|
|
596
|
+
// Move any pre-existing [slot="actions-leading"] children into this
|
|
597
|
+
// stamped wrapper — same real-insertion-point pattern as [slot="scope"]
|
|
598
|
+
// / [slot="actions"] above.
|
|
599
|
+
for (const node of [...this.children]) {
|
|
600
|
+
if (node === root) continue;
|
|
601
|
+
if (node.getAttribute?.('slot') === 'actions-leading') {
|
|
602
|
+
actionsLeadingSlot.appendChild(node);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
392
606
|
// Actions slot passthrough — we move any pre-existing [slot="actions"] children here
|
|
393
607
|
const actionsSlot = document.createElement('div');
|
|
394
608
|
actionsSlot.setAttribute('data-actions', '');
|
|
@@ -404,6 +618,7 @@ export class UITableToolbar extends UIElement {
|
|
|
404
618
|
root.appendChild(summary);
|
|
405
619
|
root.appendChild(controls);
|
|
406
620
|
root.appendChild(search);
|
|
621
|
+
root.appendChild(actionsLeadingSlot);
|
|
407
622
|
root.appendChild(pageSize);
|
|
408
623
|
root.appendChild(actionsSlot);
|
|
409
624
|
|
|
@@ -433,6 +648,14 @@ export class UITableToolbar extends UIElement {
|
|
|
433
648
|
btn.setAttribute('variant', 'outline');
|
|
434
649
|
btn.setAttribute('size', 'sm');
|
|
435
650
|
btn.setAttribute('aria-haspopup', 'menu');
|
|
651
|
+
// ADR-0076 REQ-X-002 — unconditional (harmless at full-text stages,
|
|
652
|
+
// becomes the hover-tooltip affordance at the icon-only stage once
|
|
653
|
+
// the rendered label is CSS-hidden, REQ-M-003). button-ui's own
|
|
654
|
+
// title→aria-label mirror (button.class.js) only fires when no
|
|
655
|
+
// aria-label is already present — button-ui stamps aria-label from
|
|
656
|
+
// [text] unconditionally in its own render(), so this never conflicts
|
|
657
|
+
// with REQ-X-001's already-set accessible name.
|
|
658
|
+
btn.setAttribute('title', label);
|
|
436
659
|
btn.addEventListener('click', (e) => {
|
|
437
660
|
e.stopPropagation();
|
|
438
661
|
this.#togglePopover(kind, btn);
|
|
@@ -440,6 +663,55 @@ export class UITableToolbar extends UIElement {
|
|
|
440
663
|
return btn;
|
|
441
664
|
}
|
|
442
665
|
|
|
666
|
+
// ADR-0076 REQ-S-004/REQ-X-003, PLAN step 5 — the overflow-stage "More"
|
|
667
|
+
// trigger. Reuses menu-ui/menu-item-ui unchanged (table-toolbar already
|
|
668
|
+
// `composes: menu-item-ui`; menu-ui is added to the yaml's composes list
|
|
669
|
+
// alongside it — no new runtime dependency, both already ship in this
|
|
670
|
+
// package) and routes every item straight through the EXISTING
|
|
671
|
+
// #togglePopover(kind, btn) — the same method the three granular buttons
|
|
672
|
+
// above call, never a reimplementation of filter/sort/columns logic.
|
|
673
|
+
#mkOverflowTrigger() {
|
|
674
|
+
const menu = document.createElement('menu-ui');
|
|
675
|
+
menu.setAttribute('data-toolbar-overflow', '');
|
|
676
|
+
|
|
677
|
+
const trigger = document.createElement('button-ui');
|
|
678
|
+
trigger.setAttribute('slot', 'trigger');
|
|
679
|
+
trigger.setAttribute('icon', 'dots-three-vertical');
|
|
680
|
+
trigger.setAttribute('variant', 'outline');
|
|
681
|
+
trigger.setAttribute('size', 'sm');
|
|
682
|
+
// REQ-X-003 — a genuinely new interactive element (not a repurposed
|
|
683
|
+
// existing button), so it needs its own static accessible name rather
|
|
684
|
+
// than an inherited one. No [text] attribute — always icon-only,
|
|
685
|
+
// button-ui's own render() reflects [data-icon-only] geometry from
|
|
686
|
+
// that (no CSS override needed here, unlike REQ-M-004's granular
|
|
687
|
+
// buttons which carry a real text label at wider stages).
|
|
688
|
+
trigger.setAttribute('aria-label', 'More table actions');
|
|
689
|
+
trigger.setAttribute('aria-haspopup', 'menu');
|
|
690
|
+
menu.appendChild(trigger);
|
|
691
|
+
|
|
692
|
+
for (const [kind, label, icon] of [
|
|
693
|
+
['filter', 'Filter', 'funnel-simple'],
|
|
694
|
+
['sort', 'Sort', 'arrows-down-up'],
|
|
695
|
+
['columns', 'Columns', 'columns'],
|
|
696
|
+
]) {
|
|
697
|
+
const item = document.createElement('menu-item-ui');
|
|
698
|
+
item.setAttribute('icon', icon);
|
|
699
|
+
item.setAttribute('text', label);
|
|
700
|
+
item.setAttribute('value', kind);
|
|
701
|
+
menu.appendChild(item);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// menu-ui fires `action` with { value, text } (menu.class.js's own
|
|
705
|
+
// #onItemClick) — anchor the resulting popover on the "More" trigger
|
|
706
|
+
// itself (not any of the three hidden granular buttons), same
|
|
707
|
+
// #togglePopover(kind, btn) every other entry point already calls.
|
|
708
|
+
menu.addEventListener('action', (e) => {
|
|
709
|
+
this.#togglePopover(e.detail.value, trigger);
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
return menu;
|
|
713
|
+
}
|
|
714
|
+
|
|
443
715
|
#updateTitle() {
|
|
444
716
|
const heading = this.querySelector(':scope [data-heading]');
|
|
445
717
|
if (heading) {
|
|
@@ -488,19 +760,56 @@ export class UITableToolbar extends UIElement {
|
|
|
488
760
|
// Independent of #updateTitle's [data-title] cluster by design — coexists
|
|
489
761
|
// with [text]/[count] rather than sharing their hide-with-children logic.
|
|
490
762
|
|
|
763
|
+
// ADR-0076 REQ-E — the `empty` slot renders ONLY on an explicit
|
|
764
|
+
// range-total="0" (checked via hasAttribute, never the computed number
|
|
765
|
+
// alone — REQ-E-001/AC-013: an unset range-total, adiav2's loading state,
|
|
766
|
+
// must never flash the empty-state message). A positive range-total
|
|
767
|
+
// always wins over empty-slot content (REQ-E-002's additive guarantee /
|
|
768
|
+
// AC-014's mutual-exclusion check) — the two states can never render
|
|
769
|
+
// simultaneously.
|
|
491
770
|
#updateSummary() {
|
|
492
771
|
const summary = this.querySelector(':scope > [data-toolbar] > [data-summary]');
|
|
493
772
|
if (!summary) return;
|
|
773
|
+
const text = summary.querySelector(':scope > [data-summary-text]');
|
|
774
|
+
const compact = summary.querySelector(':scope > [data-summary-compact]');
|
|
775
|
+
const empty = summary.querySelector(':scope > [data-summary-empty]');
|
|
494
776
|
const start = Number(this.rangeStart) || 0;
|
|
495
777
|
const end = Number(this.rangeEnd) || 0;
|
|
496
778
|
const total = Number(this.rangeTotal) || 0;
|
|
779
|
+
|
|
497
780
|
if (start > 0 && end > 0 && total > 0) {
|
|
498
|
-
|
|
781
|
+
// Normal range text — always wins over the empty slot (AC-014).
|
|
782
|
+
// Both the full ("Showing X–Y of N") and short-form ("X–Y/N", ADR-0076
|
|
783
|
+
// REQ-S-003) strings are populated together — which one paints is a
|
|
784
|
+
// pure CSS decision (icon-only @container rule / [stage] pin), never
|
|
785
|
+
// a JS branch (REQ-M-002).
|
|
499
786
|
if (text) text.textContent = `Showing ${start}–${end} of ${total}`;
|
|
787
|
+
if (text) text.hidden = false;
|
|
788
|
+
if (compact) compact.textContent = `${start}–${end}/${total}`;
|
|
789
|
+
if (compact) compact.hidden = false;
|
|
790
|
+
if (empty) empty.hidden = true;
|
|
500
791
|
summary.hidden = false;
|
|
501
|
-
|
|
502
|
-
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const confirmedZero = this.hasAttribute('range-total') && this.getAttribute('range-total') === '0';
|
|
796
|
+
const hasEmptyContent = !!empty && empty.childNodes.length > 0;
|
|
797
|
+
|
|
798
|
+
if (confirmedZero && hasEmptyContent) {
|
|
799
|
+
// Explicit range-total="0" + author-supplied empty content (AC-011).
|
|
800
|
+
if (text) text.hidden = true;
|
|
801
|
+
if (compact) compact.hidden = true;
|
|
802
|
+
empty.hidden = false;
|
|
803
|
+
summary.hidden = false;
|
|
804
|
+
return;
|
|
503
805
|
}
|
|
806
|
+
|
|
807
|
+
// Loading (range-total unset, AC-013) or confirmed-zero-with-no-slot-
|
|
808
|
+
// content (AC-012) — hide [data-summary] exactly as today (REQ-E-002).
|
|
809
|
+
if (text) text.hidden = true;
|
|
810
|
+
if (compact) compact.hidden = true;
|
|
811
|
+
if (empty) empty.hidden = true;
|
|
812
|
+
summary.hidden = true;
|
|
504
813
|
}
|
|
505
814
|
|
|
506
815
|
// ── Page-size select (gh#1615) ───────────────────────────────────────────
|
|
@@ -548,19 +857,45 @@ export class UITableToolbar extends UIElement {
|
|
|
548
857
|
}));
|
|
549
858
|
};
|
|
550
859
|
|
|
860
|
+
// ADR-0076 REQ-C-002 — `chromeOnly` is PURE, ABSOLUTE OR over the four
|
|
861
|
+
// granular `no-*` attributes: while set, all four stay off regardless of
|
|
862
|
+
// any individual `no-*` attribute's own value, with no partial re-enable
|
|
863
|
+
// path (AC-010). This is the ONLY place chromeOnly is combined with the
|
|
864
|
+
// granular attributes — never re-derive the OR elsewhere.
|
|
551
865
|
#updateControlVisibility() {
|
|
552
866
|
const root = this.querySelector(':scope > [data-toolbar]');
|
|
553
867
|
if (!root) return;
|
|
554
868
|
|
|
869
|
+
const chromeOnly = this.chromeOnly;
|
|
555
870
|
const setHidden = (sel, hidden) => {
|
|
556
871
|
const el = root.querySelector(sel);
|
|
557
872
|
if (el) el.hidden = hidden;
|
|
558
873
|
};
|
|
559
874
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
875
|
+
const filterHidden = chromeOnly || this.noFilter;
|
|
876
|
+
const sortHidden = chromeOnly || this.noSort;
|
|
877
|
+
const columnsHidden = chromeOnly || this.noColumns;
|
|
878
|
+
|
|
879
|
+
setHidden('[data-toolbar-btn="filter"]', filterHidden);
|
|
880
|
+
setHidden('[data-toolbar-btn="sort"]', sortHidden);
|
|
881
|
+
setHidden('[data-toolbar-btn="columns"]', columnsHidden);
|
|
882
|
+
setHidden('[data-search]', chromeOnly || this.noSearch);
|
|
883
|
+
|
|
884
|
+
// ADR-0076 REQ-S-004, PLAN step 5 — the overflow "More" trigger's own
|
|
885
|
+
// menu-item-ui children mirror the SAME granular/chrome-only
|
|
886
|
+
// suppression as the three native buttons above: a control suppressed
|
|
887
|
+
// via no-*/chrome-only stays suppressed inside the overflow menu too,
|
|
888
|
+
// never resurrected there. Hide the whole trigger only once every item
|
|
889
|
+
// it would offer is suppressed — an empty "More" menu is worse than no
|
|
890
|
+
// trigger at all.
|
|
891
|
+
const overflow = root.querySelector('[data-toolbar-overflow]');
|
|
892
|
+
if (overflow) {
|
|
893
|
+
const hiddenByKind = { filter: filterHidden, sort: sortHidden, columns: columnsHidden };
|
|
894
|
+
for (const item of overflow.querySelectorAll('menu-item-ui')) {
|
|
895
|
+
item.hidden = !!hiddenByKind[item.getAttribute('value')];
|
|
896
|
+
}
|
|
897
|
+
overflow.hidden = filterHidden && sortHidden && columnsHidden;
|
|
898
|
+
}
|
|
564
899
|
}
|
|
565
900
|
|
|
566
901
|
// ── Search ───────────────────────────────────────────────────────────────
|