@adia-ai/web-components 0.8.46 → 0.8.48
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 +43 -0
- package/components/card/card.css +25 -2
- package/components/chart/chart.a2ui.json +1 -1
- package/components/chart/chart.d.ts +1 -1
- package/components/chart/chart.yaml +6 -3
- package/components/link/link.css +3 -2
- package/components/nav/nav.css +7 -3
- package/components/nav-group/nav-group.css +38 -41
- package/components/nav-item/nav-item.css +11 -5
- package/components/preview/preview.class.js +12 -2
- package/components/richtext/richtext.css +2 -2
- package/components/table/table.a2ui.json +5 -2
- package/components/table/table.class.js +36 -1
- package/components/table/table.d.ts +2 -2
- package/components/table/table.yaml +17 -1
- package/components/table-footer/table-footer.a2ui.json +20 -2
- package/components/table-footer/table-footer.class.js +94 -8
- package/components/table-footer/table-footer.d.ts +6 -2
- package/components/table-footer/table-footer.yaml +57 -6
- package/components/table-toolbar/table-toolbar.a2ui.json +35 -2
- package/components/table-toolbar/table-toolbar.class.js +117 -9
- package/components/table-toolbar/table-toolbar.css +104 -3
- package/components/table-toolbar/table-toolbar.d.ts +10 -2
- package/components/table-toolbar/table-toolbar.examples.md +47 -45
- package/components/table-toolbar/table-toolbar.yaml +97 -9
- package/core/anchor.js +21 -1
- package/custom-elements.json +50 -5
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +8 -8
- package/dist/themes.min.css +1 -1
- package/dist/themes.sheet.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +52 -52
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/styles/colors/material-static.css +298 -234
- package/styles/themes.css +2676 -0
- package/styles/type/elements.css +1 -0
|
@@ -33,12 +33,23 @@
|
|
|
33
33
|
*
|
|
34
34
|
* Degradation (REQ-S):
|
|
35
35
|
* - range-total ABSENT → indeterminate/loading: both regions hidden.
|
|
36
|
+
* - range-total EXPLICIT "?" → gh#1877/ADR-0082 Amendment — OPEN/unproven
|
|
37
|
+
* total (cursor/hasMore paging): "Showing
|
|
38
|
+
* X–Y" with no "of N", pager stays in
|
|
39
|
+
* has-more mode (next enabled) until a
|
|
40
|
+
* real range-total resolves.
|
|
36
41
|
* - range-total EXPLICIT "0" → pager hidden; [slot="empty"] renders in
|
|
37
42
|
* the range region if supplied, else hides.
|
|
38
43
|
* - page-size ABSENT or "0" → count-only label ("128 items"), no pager
|
|
39
44
|
* (REQ-S-005 — the ordinary DEFAULT state,
|
|
40
45
|
* since page-size defaults to 0).
|
|
41
46
|
* - derived pages ≤ 1 → pager hidden, range label still renders.
|
|
47
|
+
* - range-of="<whole>" → gh#1877/ADR-0082 Amendment — secondary
|
|
48
|
+
* whole-set total, appends "(of <whole>
|
|
49
|
+
* total)"; unset changes nothing.
|
|
50
|
+
* - range-noun="<noun>" → gh#1877/ADR-0082 Amendment — appends the
|
|
51
|
+
* per-slice noun ("of 80 users"); unset
|
|
52
|
+
* changes nothing.
|
|
42
53
|
*
|
|
43
54
|
* Target derivation (REQ-W-006, amended 0.2.0/ADR-0082) — a two-branch
|
|
44
55
|
* ladder when range-total is unset, per-attribute:
|
|
@@ -94,6 +105,15 @@ export class UITableFooter extends UIElement {
|
|
|
94
105
|
page: { type: Number, default: 1, reflect: true },
|
|
95
106
|
pageSize: { type: Number, default: 0, reflect: true, attribute: 'page-size' },
|
|
96
107
|
rangeTotal: { type: Number, default: 0, reflect: true, attribute: 'range-total' },
|
|
108
|
+
// gh#1877/ADR-0082 Amendment — secondary whole-set total (the
|
|
109
|
+
// "filtered-of-whole" gap: a client-side-narrowed range-total plus the
|
|
110
|
+
// unfiltered set size), and the optional per-slice noun consumers used
|
|
111
|
+
// to hand-paint ("of 80 users"). Both presence-gated like range-total —
|
|
112
|
+
// hasAttribute decides whether the suffix renders, never the coerced
|
|
113
|
+
// value alone (an author-set range-of="0" is nonsensical and treated
|
|
114
|
+
// as absent by #draw()'s safeRangeTotal reuse).
|
|
115
|
+
rangeOf: { type: Number, default: 0, reflect: true, attribute: 'range-of' },
|
|
116
|
+
rangeNoun: { type: String, default: '', reflect: true, attribute: 'range-noun' },
|
|
97
117
|
// Pass-throughs to the composed pagination-ui (REQ-F-002) — the
|
|
98
118
|
// defaults below deliberately mirror pagination-ui's own defaults, so
|
|
99
119
|
// an unset pass-through is a no-op mirror, not a silent override.
|
|
@@ -133,7 +153,7 @@ export class UITableFooter extends UIElement {
|
|
|
133
153
|
// one, so it needs the raw attribute callback, not the signal diff.
|
|
134
154
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
135
155
|
super.attributeChangedCallback(name, oldVal, newVal);
|
|
136
|
-
if (name === 'range-total') this.#draw();
|
|
156
|
+
if (name === 'range-total' || name === 'range-of' || name === 'range-noun') this.#draw();
|
|
137
157
|
}
|
|
138
158
|
|
|
139
159
|
// ── DOM stamp ────────────────────────────────────────────────────────────
|
|
@@ -316,11 +336,29 @@ export class UITableFooter extends UIElement {
|
|
|
316
336
|
const n = Number(raw);
|
|
317
337
|
return Number.isFinite(n) && n > 0 ? Math.trunc(n) : 0;
|
|
318
338
|
};
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
339
|
+
// gh#1877/ADR-0082 Amendment — the open/unproven-total state: the
|
|
340
|
+
// resolved source of range-total (local attribute if set, else the
|
|
341
|
+
// server-mode target's own) coerces to a non-finite number. NOT keyed
|
|
342
|
+
// on the raw attribute string — rangeTotal is a Number-typed reflected
|
|
343
|
+
// prop, so range-total="?" parses via `+"?"` → NaN and the property
|
|
344
|
+
// setter's own reflect() immediately rewrites the DOM attribute to the
|
|
345
|
+
// literal string "NaN" (core/element.js) before this ever runs;
|
|
346
|
+
// getAttribute('range-total') would read "NaN", never "?". The
|
|
347
|
+
// coerced PROPERTY still holds NaN stably (Object.is(NaN, NaN) is
|
|
348
|
+
// true, so the reflect's re-parse never re-writes it) — that's the
|
|
349
|
+
// one signal that survives, and what tells "unknown yet" apart from
|
|
350
|
+
// "confirmed zero" (a finite 0) once safeRangeTotal has clamped both
|
|
351
|
+
// to 0.
|
|
352
|
+
const rangeOpen = hasRangeTotal
|
|
353
|
+
? !Number.isFinite(Number(this.rangeTotal))
|
|
354
|
+
: (targetServer && !Number.isFinite(Number(target.rangeTotal)));
|
|
355
|
+
const rangeTotal = rangeOpen
|
|
356
|
+
? null
|
|
357
|
+
: hasRangeTotal
|
|
358
|
+
? safeRangeTotal(this.rangeTotal)
|
|
359
|
+
: targetServer
|
|
360
|
+
? safeRangeTotal(target.rangeTotal)
|
|
361
|
+
: (clientMode ? (Number(target.filteredCount) || 0) : 0);
|
|
324
362
|
// page-size still derives from target.paginate in EITHER derived mode
|
|
325
363
|
// (server or client) — unchanged behavior, just widened to cover the
|
|
326
364
|
// targetServer branch too (a server-mode target's paginate is its
|
|
@@ -329,6 +367,15 @@ export class UITableFooter extends UIElement {
|
|
|
329
367
|
? (Number(this.pageSize) || 0)
|
|
330
368
|
: ((targetServer || clientMode) ? (Number(target.paginate) || 0) : (Number(this.pageSize) || 0));
|
|
331
369
|
|
|
370
|
+
// gh#1877/ADR-0082 Amendment — range-of (secondary whole-set total) and
|
|
371
|
+
// range-noun (per-slice noun) are both additive suffixes onto the
|
|
372
|
+
// normal finite "of N" text; neither applies to the loading, confirmed-
|
|
373
|
+
// empty, or open-total branches below.
|
|
374
|
+
const hasRangeOf = this.hasAttribute('range-of');
|
|
375
|
+
const rangeOfVal = hasRangeOf ? safeRangeTotal(this.rangeOf) : 0;
|
|
376
|
+
const rangeOfSuffix = hasRangeOf && rangeOfVal > 0 ? ` (of ${rangeOfVal} total)` : '';
|
|
377
|
+
const nounSuffix = this.rangeNoun ? ` ${this.rangeNoun}` : '';
|
|
378
|
+
|
|
332
379
|
if (!rangeEl) return;
|
|
333
380
|
|
|
334
381
|
if (!totalKnown) {
|
|
@@ -342,6 +389,45 @@ export class UITableFooter extends UIElement {
|
|
|
342
389
|
|
|
343
390
|
rangeEl.hidden = false;
|
|
344
391
|
|
|
392
|
+
if (rangeOpen) {
|
|
393
|
+
// gh#1877/ADR-0082 Amendment REQ-open — "Showing X–Y" with no
|
|
394
|
+
// "of N", pager stays in has-more mode (next enabled) until a real
|
|
395
|
+
// range-total resolves. Deterministic window math off page/page-size
|
|
396
|
+
// alone, same shape as the finite branch below — no clamp against a
|
|
397
|
+
// total we don't have yet (adiav2 ehr-subscriptions-paging.ts).
|
|
398
|
+
if (rangeEmpty) rangeEmpty.hidden = true;
|
|
399
|
+
if (pageSize <= 0) {
|
|
400
|
+
// No page-size known yet either — nothing to derive a window from.
|
|
401
|
+
if (rangeText) {
|
|
402
|
+
rangeText.hidden = false;
|
|
403
|
+
rangeText.textContent = nounSuffix ? `Loading${nounSuffix}…` : 'Loading…';
|
|
404
|
+
}
|
|
405
|
+
if (pagerWrap) pagerWrap.hidden = true;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const page = Math.max(1, Number(this.page) || 1);
|
|
409
|
+
const start = (page - 1) * pageSize + 1;
|
|
410
|
+
const end = page * pageSize;
|
|
411
|
+
if (rangeText) {
|
|
412
|
+
rangeText.hidden = false;
|
|
413
|
+
rangeText.textContent = `Showing ${start}–${end}${nounSuffix}`;
|
|
414
|
+
}
|
|
415
|
+
if (pagerWrap) {
|
|
416
|
+
pagerWrap.hidden = false;
|
|
417
|
+
}
|
|
418
|
+
if (pag) {
|
|
419
|
+
// Keeps `next` enabled indefinitely (never a false "last page") —
|
|
420
|
+
// mirrors table.class.js's own #pageCount open-mode math
|
|
421
|
+
// (gh#1877/ADR-0082 Amendment): total is always one page ahead of
|
|
422
|
+
// wherever the pager currently sits.
|
|
423
|
+
pag.setAttribute('page', String(page));
|
|
424
|
+
pag.setAttribute('total', String(page + 1));
|
|
425
|
+
pag.setAttribute('siblings', String(Math.max(0, Number(this.siblings) || 0)));
|
|
426
|
+
pag.setAttribute('size', this.size || 'md');
|
|
427
|
+
}
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
345
431
|
if (rangeTotal === 0) {
|
|
346
432
|
// REQ-S-002 — confirmed-empty. The empty slot always wins over the
|
|
347
433
|
// range text whenever range-total is confirmed zero, mutually
|
|
@@ -361,7 +447,7 @@ export class UITableFooter extends UIElement {
|
|
|
361
447
|
// "Showing 1–0 of N" math an unbounded pager would otherwise need.
|
|
362
448
|
if (rangeText) {
|
|
363
449
|
rangeText.hidden = false;
|
|
364
|
-
rangeText.textContent = `${rangeTotal} items`;
|
|
450
|
+
rangeText.textContent = `${rangeTotal} items${rangeOfSuffix}`;
|
|
365
451
|
}
|
|
366
452
|
if (pagerWrap) pagerWrap.hidden = true;
|
|
367
453
|
return;
|
|
@@ -374,7 +460,7 @@ export class UITableFooter extends UIElement {
|
|
|
374
460
|
|
|
375
461
|
if (rangeText) {
|
|
376
462
|
rangeText.hidden = false;
|
|
377
|
-
rangeText.textContent = `Showing ${start}–${end} of ${rangeTotal}`;
|
|
463
|
+
rangeText.textContent = `Showing ${start}–${end} of ${rangeTotal}${nounSuffix}${rangeOfSuffix}`;
|
|
378
464
|
}
|
|
379
465
|
|
|
380
466
|
if (pagerWrap) {
|
|
@@ -22,8 +22,12 @@ export class UITableFooter extends UIElement {
|
|
|
22
22
|
page: number;
|
|
23
23
|
/** Rows per page. Default 0 (unknown) is the ORDINARY default state, not an exotic edge case: with page-size absent/0 and range-total present, the range label renders in count-only form ("128 items") and the pager does not render — the derivation math (pages = range-total / page-size) is undefined without a page size. In a client-mode composition (the resolved target has [paginate] > 0) and this attribute is unset, the footer derives page-size from the target's own [paginate] value. */
|
|
24
24
|
pageSize: number;
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/** gh#1877, ADR-0082 Amendment — optional per-slice noun appended after the range total ("of 80 users") so consumers stop hand-painting the noun themselves. Applies in both the normal finite range text and the open/unproven-total text ("Showing 51–100 users" with no "of N"). Unset (the default) changes nothing. Named identically on table-toolbar-ui (ADR-0082 §"same name on both companions"). */
|
|
26
|
+
rangeNoun: string;
|
|
27
|
+
/** gh#1877, ADR-0082 Amendment — secondary whole-set total for the "filtered-of-whole" shape: a client-side facet narrows [range-total] (a filtered count) while range-of names the unfiltered set size, e.g. "Showing 1–4 of 4 traversals (6 total)". Appends "(of <range-of> total)" onto the normal finite range text; unset (the default) changes nothing. Never applies while [range-total] is absent (loading) or "?" (open/unproven) — there's no finite range text to append onto yet. Named identically on table-toolbar-ui (ADR-0082 §"same name on both companions"). */
|
|
28
|
+
rangeOf: number;
|
|
29
|
+
/** Total ROWS across all pages. Absent (the default) means "not yet known" (loading) — never "zero"; both the range label and the pager stay hidden until this is set. Explicit range-total="0" is the confirmed-empty state (pager hidden; the `empty` slot renders in the range label's position when supplied). Named identically to table-toolbar-ui's [range-total] and table-ui's own [range-total] (gh#1754, ADR-0082) and deliberately NOT `total` (pagination-ui's `total` is total PAGES — the ADR-0063 B5 collision rule). Explicit range-total="?" (gh#1877, ADR-0082 Amendment) is the OPEN/unproven- total state — cursor/hasMore server paging with no known row count yet: the range label renders "Showing X–Y" with no "of N", and the pager stays in has-more mode (next enabled) rather than hiding — distinct from both "loading" (attribute absent) and "confirmed empty" (range-total="0"). Resolves to the normal finite behavior the moment a real number replaces the "?". When this attribute is unset, the footer derives it from the resolved target via a two-branch ladder (REQ-W-006, amended 0.2.0/ADR-0082): (1) the target's own [range-total] attribute, when present — the table-authoritative SERVER mode (gh#1754 Shape 1's second lawful composition; the target's own "?" carries through identically); the identity rule: the footer's range-total derives from the table's range-total, never the table's filteredCount, and this branch wins even when the target's [paginate] is also > 0; (2) otherwise, when the target has [paginate] > 0, CLIENT mode: the target's filtered row count (search + column filters applied, before pagination). Refreshes on the target's `page` / `filter-change` / `sort` events in either branch. Widened named staleness edge: a programmatic `.data` swap on the target — or a `range-total` update on the target without an accompanying page move — emits no event; either re-set this footer's own attributes after such a change, or accept staleness until the target's next event. */
|
|
30
|
+
rangeTotal: number | string;
|
|
27
31
|
/** Passed through to the composed pagination-ui's own [siblings] (page-number window size on each side of the current page). */
|
|
28
32
|
siblings: number;
|
|
29
33
|
/** Passed through to the composed pagination-ui's own [size] (sm | md | lg, the universal size system). */
|
|
@@ -55,14 +55,22 @@ props:
|
|
|
55
55
|
range label's position when supplied). Named identically to
|
|
56
56
|
table-toolbar-ui's [range-total] and table-ui's own [range-total]
|
|
57
57
|
(gh#1754, ADR-0082) and deliberately NOT `total` (pagination-ui's
|
|
58
|
-
`total` is total PAGES — the ADR-0063 B5 collision rule).
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
`total` is total PAGES — the ADR-0063 B5 collision rule). Explicit
|
|
59
|
+
range-total="?" (gh#1877, ADR-0082 Amendment) is the OPEN/unproven-
|
|
60
|
+
total state — cursor/hasMore server paging with no known row count
|
|
61
|
+
yet: the range label renders "Showing X–Y" with no "of N", and the
|
|
62
|
+
pager stays in has-more mode (next enabled) rather than hiding —
|
|
63
|
+
distinct from both "loading" (attribute absent) and "confirmed empty"
|
|
64
|
+
(range-total="0"). Resolves to the normal finite behavior the moment
|
|
65
|
+
a real number replaces the "?". When this attribute is unset, the
|
|
66
|
+
footer derives it from the resolved target via a two-branch ladder
|
|
67
|
+
(REQ-W-006, amended 0.2.0/ADR-0082):
|
|
61
68
|
(1) the target's own [range-total] attribute, when present — the
|
|
62
69
|
table-authoritative SERVER mode (gh#1754 Shape 1's second lawful
|
|
63
|
-
composition
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
composition; the target's own "?" carries through identically); the
|
|
71
|
+
identity rule: the footer's range-total derives from the table's
|
|
72
|
+
range-total, never the table's filteredCount, and this branch wins
|
|
73
|
+
even when the target's [paginate] is also > 0;
|
|
66
74
|
(2) otherwise, when the target has [paginate] > 0, CLIENT mode: the
|
|
67
75
|
target's filtered row count (search + column filters applied, before
|
|
68
76
|
pagination). Refreshes on the target's `page` / `filter-change` /
|
|
@@ -71,9 +79,34 @@ props:
|
|
|
71
79
|
on the target without an accompanying page move — emits no event;
|
|
72
80
|
either re-set this footer's own attributes after such a change, or
|
|
73
81
|
accept staleness until the target's next event.
|
|
82
|
+
type: [number, string]
|
|
83
|
+
default: 0
|
|
84
|
+
reflect: true
|
|
85
|
+
range-of:
|
|
86
|
+
description: >-
|
|
87
|
+
gh#1877, ADR-0082 Amendment — secondary whole-set total for the
|
|
88
|
+
"filtered-of-whole" shape: a client-side facet narrows [range-total]
|
|
89
|
+
(a filtered count) while range-of names the unfiltered set size, e.g.
|
|
90
|
+
"Showing 1–4 of 4 traversals (6 total)". Appends "(of <range-of>
|
|
91
|
+
total)" onto the normal finite range text; unset (the default)
|
|
92
|
+
changes nothing. Never applies while [range-total] is absent
|
|
93
|
+
(loading) or "?" (open/unproven) — there's no finite range text to
|
|
94
|
+
append onto yet. Named identically on table-toolbar-ui (ADR-0082
|
|
95
|
+
§"same name on both companions").
|
|
74
96
|
type: number
|
|
75
97
|
default: 0
|
|
76
98
|
reflect: true
|
|
99
|
+
range-noun:
|
|
100
|
+
description: >-
|
|
101
|
+
gh#1877, ADR-0082 Amendment — optional per-slice noun appended after
|
|
102
|
+
the range total ("of 80 users") so consumers stop hand-painting the
|
|
103
|
+
noun themselves. Applies in both the normal finite range text and the
|
|
104
|
+
open/unproven-total text ("Showing 51–100 users" with no "of N").
|
|
105
|
+
Unset (the default) changes nothing. Named identically on
|
|
106
|
+
table-toolbar-ui (ADR-0082 §"same name on both companions").
|
|
107
|
+
type: string
|
|
108
|
+
default: ""
|
|
109
|
+
reflect: true
|
|
77
110
|
siblings:
|
|
78
111
|
description: Passed through to the composed pagination-ui's own [siblings] (page-number window size on each side of the current page).
|
|
79
112
|
type: number
|
|
@@ -166,6 +199,13 @@ a2ui:
|
|
|
166
199
|
range label's own position, without a layout shift versus the normal
|
|
167
200
|
range text. Only fires on an EXPLICIT range-total="0" — omitting
|
|
168
201
|
range-total entirely (loading) never shows it.
|
|
202
|
+
- >-
|
|
203
|
+
gh#1877, ADR-0082 Amendment — cursor/hasMore server paging (no
|
|
204
|
+
server-reported row count, only "more rows exist or not") sets
|
|
205
|
+
[range-total="?"] instead of a real number: the range label reads
|
|
206
|
+
"Showing 51–100" with no "of N", and the pager keeps `next` enabled
|
|
207
|
+
until a later response supplies a real [range-total], at which point
|
|
208
|
+
the footer renders the normal finite text automatically.
|
|
169
209
|
anti_patterns:
|
|
170
210
|
- wrong: '<table-footer-ui range-start="1" range-end="10" range-total="128"></table-footer-ui>'
|
|
171
211
|
why: >-
|
|
@@ -185,6 +225,17 @@ examples:
|
|
|
185
225
|
{"id": "tests", "component": "Table", "raw": true},
|
|
186
226
|
{"id": "ftr", "component": "TableFooter", "for": "tests", "page": 1, "page-size": 10, "range-total": 128}
|
|
187
227
|
]
|
|
228
|
+
- name: table-footer-open-total
|
|
229
|
+
description: >-
|
|
230
|
+
gh#1877, ADR-0082 Amendment — cursor/hasMore server mode with no known
|
|
231
|
+
row count yet: [range-total="?"] renders "Showing 1–50 subscriptions"
|
|
232
|
+
(via [range-noun]) with `next` kept enabled until a real total resolves.
|
|
233
|
+
a2ui: >-
|
|
234
|
+
[
|
|
235
|
+
{"id": "root", "component": "Column", "gap": "0", "children": ["subs", "ftr"]},
|
|
236
|
+
{"id": "subs", "component": "Table", "raw": true},
|
|
237
|
+
{"id": "ftr", "component": "TableFooter", "for": "subs", "page": 1, "page-size": 50, "range-total": "?", "range-noun": "subscriptions"}
|
|
238
|
+
]
|
|
188
239
|
- name: table-footer-client-mode
|
|
189
240
|
description: Client-mode footer bound to a [paginate]d table-ui with its own internal pager suppressed via [no-pager].
|
|
190
241
|
a2ui: >-
|
|
@@ -70,16 +70,39 @@
|
|
|
70
70
|
"type": "number",
|
|
71
71
|
"default": 0
|
|
72
72
|
},
|
|
73
|
+
"rangeNoun": {
|
|
74
|
+
"description": "gh#1877, ADR-0082 Amendment — optional per-slice noun appended after the range total (\"of 80 users\") so consumers stop hand-painting the noun themselves. Applies in both the normal finite range summary and the open/unproven-total summary (\"Showing 51–100 users\" with no \"of N\"). Unset (the default) changes nothing. Named identically to table-footer-ui's own [range-noun] (ADR-0082 §\"same name on both companions\").",
|
|
75
|
+
"type": "string",
|
|
76
|
+
"default": ""
|
|
77
|
+
},
|
|
78
|
+
"rangeOf": {
|
|
79
|
+
"description": "gh#1877, ADR-0082 Amendment — secondary whole-set total for the \"filtered-of-whole\" shape: a client-side facet narrows [rangeTotal] (a filtered count) while rangeOf names the unfiltered set size, e.g. \"Showing 1–4 of 4 traversals (6 total)\". Appends \"(of <rangeOf> total)\" onto the normal finite range summary; unset (the default) changes nothing. Never applies while the summary is in its open (\"?\") or opted-out (all-zero) state. Named identically to table-footer-ui's own [range-of] (ADR-0082 §\"same name on both companions\").",
|
|
80
|
+
"type": "number",
|
|
81
|
+
"default": 0
|
|
82
|
+
},
|
|
73
83
|
"rangeStart": {
|
|
74
84
|
"description": "1-indexed first row number of the current page, for the \"Showing X–Y of N\" range summary (e.g. `1` in \"Showing 1–25 of 320\"). Renders alongside — never in place of — [text]/[count]; the range summary is its own cluster, independent of the title cluster. All three of rangeStart/rangeEnd/rangeTotal must be set (> 0) for the summary to render; omit all three (the default) to opt out entirely.",
|
|
75
85
|
"type": "number",
|
|
76
86
|
"default": 0
|
|
77
87
|
},
|
|
78
88
|
"rangeTotal": {
|
|
79
|
-
"description": "Total row count across all pages, for the range summary (e.g. `320` in \"Showing 1–25 of 320\"). Distinct from pagination-ui's `total` (total PAGES) — this is total ROWS; deliberately not spelled `total` to avoid that cross-sibling name collision (ADR-0063 B5). See rangeStart.",
|
|
80
|
-
"type":
|
|
89
|
+
"description": "Total row count across all pages, for the range summary (e.g. `320` in \"Showing 1–25 of 320\"). Distinct from pagination-ui's `total` (total PAGES) — this is total ROWS; deliberately not spelled `total` to avoid that cross-sibling name collision (ADR-0063 B5). See rangeStart. Explicit range-total=\"?\" (gh#1877, ADR-0082 Amendment) is the OPEN/unproven-total state — cursor/hasMore server paging with no known row count yet: the summary reads \"Showing X–Y\" with no \"of N\", requiring only [range-start]/[range-end] (> 0) to render, not rangeTotal too. Resolves to the normal finite summary the moment a real number replaces the \"?\". Named identically to table-footer-ui's own [range-total] (ADR-0082 §\"same name on both companions\").",
|
|
90
|
+
"type": [
|
|
91
|
+
"number",
|
|
92
|
+
"string"
|
|
93
|
+
],
|
|
81
94
|
"default": 0
|
|
82
95
|
},
|
|
96
|
+
"size": {
|
|
97
|
+
"description": "Toolbar scale, via sizing.css's universal [size=\"sm|md|lg\"] ambient tokens. Not table-toolbar-specific chrome — the same attribute that scales any component. Set it once on the toolbar instead of on each slotted scope/actions-leading/actions control individually; anything slotted with no [size] of its own inherits the ambient value. Default (empty) = md.",
|
|
98
|
+
"type": "string",
|
|
99
|
+
"enum": [
|
|
100
|
+
"sm",
|
|
101
|
+
"md",
|
|
102
|
+
"lg"
|
|
103
|
+
],
|
|
104
|
+
"default": ""
|
|
105
|
+
},
|
|
83
106
|
"stage": {
|
|
84
107
|
"description": "Explicit compaction-stage override (ADR-0076). Unset (the default, empty string) means auto-snap: the toolbar's own inline-size container query picks the nearest stage from its live width against the three --table-toolbar-bp-* breakpoints, snapping discretely, never interpolating. Setting stage explicitly pins that stage's rendering regardless of the container's actual width — overriding the container query, mirroring chart-ui's ratio override shape. Primary use: visual-eval fixtures that need a deterministic stage without resizing a real container, and a consumer embedding the toolbar inside a known-narrow panel who wants to skip auto-detection entirely. All four stages' CSS is now live (ADR-0076): search-tight steps the search field down (step 2); icon-only drops control labels and compacts/hides the range summary (steps 3+4); overflow collapses Filter/Sort/Columns into one \"More\" trigger routed through the same menu-ui/menu-item-ui pattern used elsewhere in the corpus (step 5). The resolved stage — the pin if set, otherwise the live width classification — also reflects onto the host as `data-stage-resolved` (step 7, informational only, mirrors chart-ui's `data-ratio-resolved` convention — never drives table-toolbar's own rendering, which stays 100% CSS-driven).",
|
|
85
108
|
"type": "string",
|
|
@@ -97,6 +120,11 @@
|
|
|
97
120
|
"type": "string",
|
|
98
121
|
"default": ""
|
|
99
122
|
},
|
|
123
|
+
"titleAlways": {
|
|
124
|
+
"description": "Opt out of the scope-reconciliation title hide: by default, a filled [slot=\"scope\"] region (a select/menu whose own displayed value already names the entity being listed) hides the separate [text]/[count] title cluster next to it, since together they'd say the same thing twice. Set [title-always] to keep the title cluster visible alongside a filled scope slot — e.g. a scope select PLUS an explicit text/count title on the same row (gh#1879).",
|
|
125
|
+
"type": "boolean",
|
|
126
|
+
"default": false
|
|
127
|
+
},
|
|
100
128
|
"variant": {
|
|
101
129
|
"description": "Toolbar visual variant. `default` renders bare on parent surface; `card` adds the same chrome as a card-ui header.",
|
|
102
130
|
"type": "string",
|
|
@@ -169,6 +197,11 @@
|
|
|
169
197
|
"description": "The ratified adiav2 list-page toolbar row (ADIA2-9123, gh#1649): scope menu, range summary, app-owned Filter/Columns triggers (their own filter drawer, not the native popovers — [no-filter]/[no-columns]/ [no-sort]/[no-search] drop the native affordances), page-size select, and a primary CTA — [slot=\"actions-leading\"] carries the Filter/Columns triggers so they land BEFORE the page-size select, while [slot=\"actions\"] carries the CTA so it lands after.",
|
|
170
198
|
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"3\", \"children\": [\"bar\", \"card\"]},\n {\"id\": \"bar\", \"component\": \"TableToolbar\", \"for\": \"accounts\", \"no-filter\": true, \"no-sort\": true, \"no-columns\": true, \"no-search\": true, \"range-start\": 1, \"range-end\": 25, \"range-total\": 320, \"page-size\": 25, \"page-size-options\": [10, 25, 50, 100], \"children\": [\"btn-filter\", \"btn-columns\", \"btn-new\"]},\n {\"id\": \"btn-filter\", \"component\": \"Button\", \"text\": \"Filter\", \"variant\": \"ghost\", \"slot\": \"actions-leading\"},\n {\"id\": \"btn-columns\", \"component\": \"Button\", \"text\": \"Columns\", \"variant\": \"ghost\", \"slot\": \"actions-leading\"},\n {\"id\": \"btn-new\", \"component\": \"Button\", \"text\": \"New account\", \"variant\": \"primary\", \"slot\": \"actions\"},\n {\"id\": \"card\", \"component\": \"Card\", \"children\": [\"sec\"]},\n {\"id\": \"sec\", \"component\": \"Section\", \"bleed\": true, \"children\": [\"tbl\"]},\n {\"id\": \"tbl\", \"component\": \"Table\", \"id\": \"accounts\", \"sortable\": true, \"raw\": true}\n]",
|
|
171
199
|
"name": "adiav2-list-page-toolbar-row"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"description": "gh#1877, ADR-0082 Amendment — cursor/hasMore server paging with no known row count yet: [range-total=\"?\"] renders \"Showing 1–50 subscriptions\" (via [range-noun]) with no \"of N\", resolving to the normal finite summary once a real total arrives.",
|
|
203
|
+
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"3\", \"children\": [\"bar\", \"card\"]},\n {\"id\": \"bar\", \"component\": \"TableToolbar\", \"for\": \"subs\", \"range-start\": 1, \"range-end\": 50, \"range-total\": \"?\", \"range-noun\": \"subscriptions\"},\n {\"id\": \"card\", \"component\": \"Card\", \"children\": [\"sec\"]},\n {\"id\": \"sec\", \"component\": \"Section\", \"bleed\": true, \"children\": [\"tbl\"]},\n {\"id\": \"tbl\", \"component\": \"Table\", \"id\": \"subs\", \"raw\": true}\n]",
|
|
204
|
+
"name": "table-toolbar-open-total"
|
|
172
205
|
}
|
|
173
206
|
],
|
|
174
207
|
"keywords": [
|
|
@@ -153,11 +153,31 @@ function collectUniqueValues(col, data) {
|
|
|
153
153
|
export class UITableToolbar extends UIElement {
|
|
154
154
|
static properties = {
|
|
155
155
|
for: { type: String, default: '', reflect: true },
|
|
156
|
+
// Not consumed by any table-toolbar-specific CSS — sizing.css's
|
|
157
|
+
// universal [size="sm|md|lg"] (unscoped attribute selector, ambient
|
|
158
|
+
// inheriting tokens) already governs the host AND every slotted
|
|
159
|
+
// consumer content (scope select-ui, actions-leading/actions
|
|
160
|
+
// button-ui) that carries no [size] of its own, same as
|
|
161
|
+
// `<div size="sm">…</div>`. Declared (reflect: true) purely so this
|
|
162
|
+
// A2UI-modeled prop survives the docs-transpiler's schema-scoped
|
|
163
|
+
// attribute walk (unmodeled attributes are silently dropped there —
|
|
164
|
+
// verified live: `<table-toolbar-ui size="sm">` authored in
|
|
165
|
+
// table-toolbar.examples.html rendered with no [size] attribute at
|
|
166
|
+
// all on the generated site-a2ui page until this was added) and so
|
|
167
|
+
// `el.size = 'sm'` works from JS, matching card-ui/button-ui's own
|
|
168
|
+
// `size` property shape.
|
|
169
|
+
size: { type: String, default: '', reflect: true },
|
|
156
170
|
text: { type: String, default: '', reflect: false },
|
|
157
171
|
count: { type: String, default: '', reflect: false },
|
|
158
172
|
rangeStart: { type: Number, default: 0, reflect: true, attribute: 'range-start' },
|
|
159
173
|
rangeEnd: { type: Number, default: 0, reflect: true, attribute: 'range-end' },
|
|
160
174
|
rangeTotal: { type: Number, default: 0, reflect: true, attribute: 'range-total' },
|
|
175
|
+
// gh#1877/ADR-0082 Amendment — same-name mirrors of table-footer-ui's
|
|
176
|
+
// own range-of/range-noun (ADR-0082 §"same name on both companions"):
|
|
177
|
+
// range-of is the secondary whole-set total ("filtered-of-whole"),
|
|
178
|
+
// range-noun the optional per-slice noun. Both presence-gated.
|
|
179
|
+
rangeOf: { type: Number, default: 0, reflect: true, attribute: 'range-of' },
|
|
180
|
+
rangeNoun: { type: String, default: '', reflect: true, attribute: 'range-noun' },
|
|
161
181
|
pageSize: { type: Number, default: 0, reflect: true, attribute: 'page-size' },
|
|
162
182
|
noFilter: { type: Boolean, default: false, reflect: true, attribute: 'no-filter' },
|
|
163
183
|
noSort: { type: Boolean, default: false, reflect: true, attribute: 'no-sort' },
|
|
@@ -167,6 +187,13 @@ export class UITableToolbar extends UIElement {
|
|
|
167
187
|
// above. Pure, absolute OR: see #updateControlVisibility(), which is the
|
|
168
188
|
// single place that combines chromeOnly with each granular no-* value.
|
|
169
189
|
chromeOnly: { type: Boolean, default: false, reflect: true, attribute: 'chrome-only' },
|
|
190
|
+
// gh#1879 — opt-out for the scope-reconciliation title hide
|
|
191
|
+
// (table-toolbar.css's `:scope:not([title-always]):has([data-scope]:not(:empty))
|
|
192
|
+
// [data-title] { display: none; }`). Presence-only: the CSS rule reads
|
|
193
|
+
// the reflected attribute directly, so no JS branch is needed here
|
|
194
|
+
// beyond the standard Boolean reflect declaration (same shape as
|
|
195
|
+
// noFilter/noSort/noColumns/noSearch/chromeOnly above).
|
|
196
|
+
titleAlways: { type: Boolean, default: false, reflect: true, attribute: 'title-always' },
|
|
170
197
|
placeholder: { type: String, default: 'Search...', reflect: false },
|
|
171
198
|
variant: { type: String, default: 'default', reflect: true },
|
|
172
199
|
// ADR-0076 REQ-O-001 — explicit compaction-stage override. Unset (the
|
|
@@ -233,6 +260,27 @@ export class UITableToolbar extends UIElement {
|
|
|
233
260
|
|
|
234
261
|
connected() {
|
|
235
262
|
this.setAttribute('role', 'toolbar');
|
|
263
|
+
// `size` (declared above, reflect: true) defaults to '' — the base
|
|
264
|
+
// property system's own reflect pass (core/element.js) deliberately
|
|
265
|
+
// does NOT auto-stamp a non-empty string/number default onto the DOM
|
|
266
|
+
// (gh#961: scoped to Boolean-true defaults only, to avoid a
|
|
267
|
+
// framework-wide `attr=""`-stamping cascade), so a bare
|
|
268
|
+
// `<table-toolbar-ui>` never actually gets a `[size]` attribute from
|
|
269
|
+
// the default alone. Stamped explicitly, once, HERE — sizing.css's
|
|
270
|
+
// `[size="sm"]` is a plain unscoped attribute selector that needs the
|
|
271
|
+
// literal attribute present to set the ambient `--a-size`/`--a-ui-size`
|
|
272
|
+
// tokens at all. Every native control this component stamps below
|
|
273
|
+
// (badge, search, page-size select, granular buttons, overflow
|
|
274
|
+
// trigger) is a light-DOM descendant with no `[size]` of its own, so
|
|
275
|
+
// it inherits those tokens the same way any plain
|
|
276
|
+
// `<div size="sm">…</div>` cascades to its children — no per-child
|
|
277
|
+
// `size=` stamping needed (tried first, reverted: redundant with what
|
|
278
|
+
// inheritance already does for every element still in this light-DOM
|
|
279
|
+
// subtree, and actively wrong once a consumer sets an explicit
|
|
280
|
+
// `size="lg"` after connect — the per-child copies would go stale
|
|
281
|
+
// since nothing re-stamps them on a later property change, while
|
|
282
|
+
// inheritance updates live).
|
|
283
|
+
if (!this.hasAttribute('size')) this.setAttribute('size', this.size || 'sm');
|
|
236
284
|
this.#warnUnknownOptOutAttrs();
|
|
237
285
|
// gh#1615 — hydrate `page-size-options="[10,25,50]"` once at connect,
|
|
238
286
|
// same pattern as table-ui's `data`/`columns` JSON-array attributes
|
|
@@ -307,7 +355,7 @@ export class UITableToolbar extends UIElement {
|
|
|
307
355
|
// value one, so it needs the raw attribute callback, not the signal diff.
|
|
308
356
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
309
357
|
super.attributeChangedCallback(name, oldVal, newVal);
|
|
310
|
-
if (name === 'range-total') this.#updateSummary();
|
|
358
|
+
if (name === 'range-total' || name === 'range-of' || name === 'range-noun') this.#updateSummary();
|
|
311
359
|
// ADR-0076 REQ-M-005 — the SECOND of the two triggers `data-stage-
|
|
312
360
|
// resolved` must react to: a live resize (the ResizeObserver callback
|
|
313
361
|
// above) AND a `stage` attribute change alone, with no resize needed.
|
|
@@ -479,6 +527,26 @@ export class UITableToolbar extends UIElement {
|
|
|
479
527
|
|
|
480
528
|
// ── DOM stamp ────────────────────────────────────────────────────────────
|
|
481
529
|
|
|
530
|
+
// Used ONLY for the two stamped elements that end up OUTSIDE
|
|
531
|
+
// table-toolbar-ui's own light DOM — the popover-panel action buttons
|
|
532
|
+
// (#mkPopoverAction) and the filter-input (#fillFilterPanel), both
|
|
533
|
+
// appended to `document.body` for the Popover API's top-layer promotion
|
|
534
|
+
// (#togglePopover) — so ambient CSS inheritance from the host's own
|
|
535
|
+
// `[size]` attribute (stamped once in connected(), above) can never
|
|
536
|
+
// reach them. Every OTHER stamped control (badge, search, page-size
|
|
537
|
+
// select, granular Filter/Sort/Columns buttons, the overflow trigger)
|
|
538
|
+
// stays in the host's own light DOM and inherits that ambient value
|
|
539
|
+
// directly — no per-element `size=` copy needed, and no risk of one
|
|
540
|
+
// going stale after a later `.size =` change the way an explicit copy
|
|
541
|
+
// would (tried first, reverted: every in-tree control hardcoded
|
|
542
|
+
// `size="sm"` unconditionally, which not only ignored a consumer's
|
|
543
|
+
// `size="lg"` but also fought its OWN copy going stale on any later
|
|
544
|
+
// property change nothing re-stamped). Falls back to "sm" (the
|
|
545
|
+
// pre-existing hardcoded default) when `size` is unset.
|
|
546
|
+
#stampSize() {
|
|
547
|
+
return this.size || 'sm';
|
|
548
|
+
}
|
|
549
|
+
|
|
482
550
|
#stamp() {
|
|
483
551
|
if (this.querySelector(':scope > [data-toolbar]')) return;
|
|
484
552
|
|
|
@@ -509,7 +577,8 @@ export class UITableToolbar extends UIElement {
|
|
|
509
577
|
|
|
510
578
|
const badge = document.createElement('badge-ui');
|
|
511
579
|
badge.setAttribute('data-toolbar-count-badge', '');
|
|
512
|
-
|
|
580
|
+
// No own [size] — inherits the host's ambient --a-size (stamped in
|
|
581
|
+
// connected(), above).
|
|
513
582
|
badge.setAttribute('tone', 'muted');
|
|
514
583
|
badge.hidden = true;
|
|
515
584
|
title.appendChild(badge);
|
|
@@ -577,7 +646,7 @@ export class UITableToolbar extends UIElement {
|
|
|
577
646
|
// name renders as literal text before the icon registry resolves.
|
|
578
647
|
const search = document.createElement('search-ui');
|
|
579
648
|
search.setAttribute('data-search', '');
|
|
580
|
-
|
|
649
|
+
// No own [size] — inherits the host's ambient --a-size, same as badge.
|
|
581
650
|
search.setAttribute('placeholder', this.placeholder);
|
|
582
651
|
search.setAttribute('debounce', String(SEARCH_DEBOUNCE));
|
|
583
652
|
search.addEventListener('search', this.#onSearch);
|
|
@@ -590,7 +659,7 @@ export class UITableToolbar extends UIElement {
|
|
|
590
659
|
// existing default behavior for consumers that never set pageSizeOptions.
|
|
591
660
|
const pageSize = document.createElement('select-ui');
|
|
592
661
|
pageSize.setAttribute('data-page-size', '');
|
|
593
|
-
|
|
662
|
+
// No own [size] — inherits the host's ambient --a-size, same as badge.
|
|
594
663
|
pageSize.setAttribute('aria-label', 'Rows per page');
|
|
595
664
|
pageSize.hidden = true;
|
|
596
665
|
pageSize.addEventListener('change', this.#onPageSizeChange);
|
|
@@ -645,7 +714,12 @@ export class UITableToolbar extends UIElement {
|
|
|
645
714
|
btn.setAttribute('data-popover-action', '');
|
|
646
715
|
btn.setAttribute('text', label);
|
|
647
716
|
btn.setAttribute('variant', 'ghost');
|
|
648
|
-
|
|
717
|
+
// Explicit [size] IS still needed here, unlike the in-tree stamped
|
|
718
|
+
// controls above — #togglePopover appends this button's panel straight
|
|
719
|
+
// to `document.body` (Popover API top-layer promotion), not nested
|
|
720
|
+
// inside table-toolbar-ui in the DOM, so ambient CSS inheritance from
|
|
721
|
+
// the host's own [size] never reaches it.
|
|
722
|
+
btn.setAttribute('size', this.#stampSize());
|
|
649
723
|
btn.setAttribute('stretch', '');
|
|
650
724
|
btn.addEventListener('click', onClick);
|
|
651
725
|
return btn;
|
|
@@ -657,7 +731,7 @@ export class UITableToolbar extends UIElement {
|
|
|
657
731
|
btn.setAttribute('icon', icon);
|
|
658
732
|
btn.setAttribute('text', label);
|
|
659
733
|
btn.setAttribute('variant', 'outline');
|
|
660
|
-
|
|
734
|
+
// No own [size] — inherits the host's ambient --a-size, same as badge.
|
|
661
735
|
btn.setAttribute('aria-haspopup', 'menu');
|
|
662
736
|
// ADR-0076 REQ-X-002 — unconditional (harmless at full-text stages,
|
|
663
737
|
// becomes the hover-tooltip affordance at the icon-only stage once
|
|
@@ -689,7 +763,7 @@ export class UITableToolbar extends UIElement {
|
|
|
689
763
|
trigger.setAttribute('slot', 'trigger');
|
|
690
764
|
trigger.setAttribute('icon', 'dots-three-vertical');
|
|
691
765
|
trigger.setAttribute('variant', 'outline');
|
|
692
|
-
|
|
766
|
+
// No own [size] — inherits the host's ambient --a-size, same as badge.
|
|
693
767
|
// REQ-X-003 — a genuinely new interactive element (not a repurposed
|
|
694
768
|
// existing button), so it needs its own static accessible name rather
|
|
695
769
|
// than an inherited one. No [text] attribute — always icon-only,
|
|
@@ -788,13 +862,45 @@ export class UITableToolbar extends UIElement {
|
|
|
788
862
|
const end = Number(this.rangeEnd) || 0;
|
|
789
863
|
const total = Number(this.rangeTotal) || 0;
|
|
790
864
|
|
|
865
|
+
// gh#1877/ADR-0082 Amendment — same open/unproven-total discipline as
|
|
866
|
+
// table-footer-ui's own #draw(): NOT keyed on the raw attribute
|
|
867
|
+
// string. rangeTotal is a Number-typed reflected prop, so
|
|
868
|
+
// range-total="?" parses via `+"?"` → NaN and the property setter's
|
|
869
|
+
// own reflect() immediately rewrites the DOM attribute to the literal
|
|
870
|
+
// string "NaN" (core/element.js) — getAttribute('range-total') would
|
|
871
|
+
// read "NaN", never "?". The coerced PROPERTY still holds NaN stably
|
|
872
|
+
// (Object.is(NaN, NaN) is true, so the reflect's re-parse never
|
|
873
|
+
// re-writes it), and that's what tells "unknown yet" apart from a
|
|
874
|
+
// confirmed range-total="0" (a finite 0).
|
|
875
|
+
const rangeOpen = !Number.isFinite(Number(this.rangeTotal));
|
|
876
|
+
// gh#1877/ADR-0082 Amendment — additive suffixes onto the normal
|
|
877
|
+
// finite "of N" text; range-of (secondary whole-set total) never
|
|
878
|
+
// applies to the open-total branch (no finite total to append against).
|
|
879
|
+
const hasRangeOf = this.hasAttribute('range-of');
|
|
880
|
+
const rangeOfVal = hasRangeOf ? (Number(this.rangeOf) || 0) : 0;
|
|
881
|
+
const rangeOfSuffix = hasRangeOf && rangeOfVal > 0 ? ` (of ${rangeOfVal} total)` : '';
|
|
882
|
+
const nounSuffix = this.rangeNoun ? ` ${this.rangeNoun}` : '';
|
|
883
|
+
|
|
884
|
+
if (rangeOpen && start > 0 && end > 0) {
|
|
885
|
+
// REQ-open (gh#1877/ADR-0082 Amendment) — "Showing X–Y" with no
|
|
886
|
+
// "of N"; the short-form mirrors it minus the "Showing " prefix,
|
|
887
|
+
// same paired-population discipline as the finite branch below.
|
|
888
|
+
if (text) text.textContent = `Showing ${start}–${end}${nounSuffix}`;
|
|
889
|
+
if (text) text.hidden = false;
|
|
890
|
+
if (compact) compact.textContent = `${start}–${end}`;
|
|
891
|
+
if (compact) compact.hidden = false;
|
|
892
|
+
if (empty) empty.hidden = true;
|
|
893
|
+
summary.hidden = false;
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
|
|
791
897
|
if (start > 0 && end > 0 && total > 0) {
|
|
792
898
|
// Normal range text — always wins over the empty slot (AC-014).
|
|
793
899
|
// Both the full ("Showing X–Y of N") and short-form ("X–Y/N", ADR-0076
|
|
794
900
|
// REQ-S-003) strings are populated together — which one paints is a
|
|
795
901
|
// pure CSS decision (icon-only @container rule / [stage] pin), never
|
|
796
902
|
// a JS branch (REQ-M-002).
|
|
797
|
-
if (text) text.textContent = `Showing ${start}–${end} of ${total}`;
|
|
903
|
+
if (text) text.textContent = `Showing ${start}–${end} of ${total}${nounSuffix}${rangeOfSuffix}`;
|
|
798
904
|
if (text) text.hidden = false;
|
|
799
905
|
if (compact) compact.textContent = `${start}–${end}/${total}`;
|
|
800
906
|
if (compact) compact.hidden = false;
|
|
@@ -1085,7 +1191,9 @@ export class UITableToolbar extends UIElement {
|
|
|
1085
1191
|
} else {
|
|
1086
1192
|
const input = document.createElement('input-ui');
|
|
1087
1193
|
input.setAttribute('type', 'text');
|
|
1088
|
-
|
|
1194
|
+
// Explicit [size] needed — this panel is appended straight to
|
|
1195
|
+
// document.body (see #mkPopoverAction's comment above).
|
|
1196
|
+
input.setAttribute('size', this.#stampSize());
|
|
1089
1197
|
input.setAttribute('data-filter-input', '');
|
|
1090
1198
|
input.setAttribute('placeholder', '—');
|
|
1091
1199
|
if (current?.op === 'contains') input.value = current.value ?? '';
|