@adia-ai/web-components 0.8.47 → 0.8.50
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 +50 -0
- package/MIGRATION.md +136 -0
- package/components/card/card.css +25 -2
- package/components/chart/chart.a2ui.json +1 -1
- package/components/chart/chart.class.js +41 -10
- package/components/chart/chart.d.ts +1 -1
- package/components/chart/chart.yaml +6 -3
- package/components/input/input.a2ui.json +2 -2
- package/components/input/input.css +45 -16
- package/components/input/input.yaml +11 -9
- package/components/link/link.css +3 -2
- package/components/richtext/richtext.css +2 -2
- package/components/search/search.a2ui.json +1 -1
- package/components/search/search.class.js +43 -0
- package/components/search/search.css +28 -0
- package/components/search/search.yaml +4 -1
- package/components/select/select.class.js +4 -1
- package/components/select/select.css +13 -0
- package/components/stat/stat.a2ui.json +30 -3
- package/components/stat/stat.css +88 -0
- package/components/stat/stat.d.ts +2 -2
- package/components/stat/stat.yaml +99 -6
- 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 +28 -2
- package/components/table-toolbar/table-toolbar.class.js +69 -2
- package/components/table-toolbar/table-toolbar.css +36 -2
- package/components/table-toolbar/table-toolbar.d.ts +8 -2
- package/components/table-toolbar/table-toolbar.examples.md +6 -4
- package/components/table-toolbar/table-toolbar.yaml +140 -26
- package/components/theme-provider/theme-provider.a2ui.json +3 -5
- package/components/theme-provider/theme-provider.class.js +9 -30
- package/components/theme-provider/theme-provider.d.ts +3 -5
- package/components/theme-provider/theme-provider.yaml +4 -8
- package/core/element.js +16 -3
- package/custom-elements.json +52 -10
- 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 +70 -69
- package/dist/web-components.sheet.js +1 -1
- package/index.css +1 -2
- package/package.json +1 -1
- package/styles/README.md +1 -1
- package/styles/api/sizing.css +1 -1
- package/styles/colors/material-static.css +34 -12
- package/styles/host.css +1 -4
- package/styles/index.css +2 -2
- package/styles/scale.css +19 -26
- package/styles/themes.css +2678 -2
- package/styles/type/elements.css +1 -0
- package/styles/type/roles.css +3 -3
- package/styles/typography.css +6 -5
- package/dist/prose.min.css +0 -1
- package/dist/prose.sheet.js +0 -11
- package/dist/verse.min.css +0 -1
- package/dist/verse.sheet.js +0 -11
- package/styles/prose.css +0 -211
- package/styles/verse.css +0 -151
|
@@ -31,11 +31,24 @@
|
|
|
31
31
|
"type": "number",
|
|
32
32
|
"default": 0
|
|
33
33
|
},
|
|
34
|
-
"range-
|
|
35
|
-
"description": "
|
|
34
|
+
"range-noun": {
|
|
35
|
+
"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 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\").",
|
|
36
|
+
"type": "string",
|
|
37
|
+
"default": ""
|
|
38
|
+
},
|
|
39
|
+
"range-of": {
|
|
40
|
+
"description": "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\").",
|
|
36
41
|
"type": "number",
|
|
37
42
|
"default": 0
|
|
38
43
|
},
|
|
44
|
+
"range-total": {
|
|
45
|
+
"description": "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.",
|
|
46
|
+
"type": [
|
|
47
|
+
"number",
|
|
48
|
+
"string"
|
|
49
|
+
],
|
|
50
|
+
"default": 0
|
|
51
|
+
},
|
|
39
52
|
"siblings": {
|
|
40
53
|
"description": "Passed through to the composed pagination-ui's own [siblings] (page-number window size on each side of the current page).",
|
|
41
54
|
"type": "number",
|
|
@@ -77,6 +90,11 @@
|
|
|
77
90
|
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"0\", \"children\": [\"tests\", \"ftr\"]},\n {\"id\": \"tests\", \"component\": \"Table\", \"raw\": true},\n {\"id\": \"ftr\", \"component\": \"TableFooter\", \"for\": \"tests\", \"page\": 1, \"page-size\": 10, \"range-total\": 128}\n]",
|
|
78
91
|
"name": "table-footer-server-mode"
|
|
79
92
|
},
|
|
93
|
+
{
|
|
94
|
+
"description": "gh#1877, ADR-0082 Amendment — cursor/hasMore server mode with no known row count yet: [range-total=\"?\"] renders \"Showing 1–50 subscriptions\" (via [range-noun]) with `next` kept enabled until a real total resolves.",
|
|
95
|
+
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"0\", \"children\": [\"subs\", \"ftr\"]},\n {\"id\": \"subs\", \"component\": \"Table\", \"raw\": true},\n {\"id\": \"ftr\", \"component\": \"TableFooter\", \"for\": \"subs\", \"page\": 1, \"page-size\": 50, \"range-total\": \"?\", \"range-noun\": \"subscriptions\"}\n]",
|
|
96
|
+
"name": "table-footer-open-total"
|
|
97
|
+
},
|
|
80
98
|
{
|
|
81
99
|
"description": "Client-mode footer bound to a [paginate]d table-ui with its own internal pager suppressed via [no-pager].",
|
|
82
100
|
"a2ui": "[\n {\"id\": \"root\", \"component\": \"Column\", \"gap\": \"0\", \"children\": [\"tests-c\", \"ftr\"]},\n {\"id\": \"tests-c\", \"component\": \"Table\", \"paginate\": 10, \"no-pager\": true, \"raw\": true},\n {\"id\": \"ftr\", \"component\": \"TableFooter\", \"for\": \"tests-c\"}\n]",
|
|
@@ -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. gh#1895 — reflect()
|
|
344
|
+
// (core/element.js) now leaves a non-finite Number value unwritten
|
|
345
|
+
// instead of stomping the sentinel with the literal string "NaN", so
|
|
346
|
+
// getAttribute('range-total') keeps reading "?" as authored. The
|
|
347
|
+
// coerced PROPERTY still holds NaN stably (Object.is(NaN, NaN) is
|
|
348
|
+
// true, so re-parsing the unchanged attribute never re-triggers a
|
|
349
|
+
// write either) — that's 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,14 +70,27 @@
|
|
|
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
|
},
|
|
83
96
|
"size": {
|
|
@@ -107,6 +120,11 @@
|
|
|
107
120
|
"type": "string",
|
|
108
121
|
"default": ""
|
|
109
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
|
+
},
|
|
110
128
|
"variant": {
|
|
111
129
|
"description": "Toolbar visual variant. `default` renders bare on parent surface; `card` adds the same chrome as a card-ui header.",
|
|
112
130
|
"type": "string",
|
|
@@ -179,6 +197,11 @@
|
|
|
179
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.",
|
|
180
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]",
|
|
181
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"
|
|
182
205
|
}
|
|
183
206
|
],
|
|
184
207
|
"keywords": [
|
|
@@ -216,6 +239,9 @@
|
|
|
216
239
|
},
|
|
217
240
|
"scope": {
|
|
218
241
|
"description": "Leading region rendered BEFORE the [text]/[count] title cluster — e.g. a scope/view-switcher menu (an org's teams, a saved view). Positioning is CSS by DOM order, mirroring how [slot=\"actions\"] is a real, author-fillable insertion point rather than template-stamped content."
|
|
242
|
+
},
|
|
243
|
+
"summary": {
|
|
244
|
+
"description": "gh#1883 — trailing, text-tier cluster for an aggregate datum that belongs with the table but has no home in the range summary or the actions area (e.g. batch-detail's line-items Subtotal: `<span slot=\"summary\">Subtotal <strong>$1,240.00</strong></span>`). Rendered in the same flex row, after [slot=\"actions-leading\"] and before the page-size select / [slot=\"actions\"] — closer to the trailing edge than [slot=\"scope\"]/[range-*], since it reads as a result/total rather than pagination or view-switching context. Text sized via [--table-toolbar-summary-size] (shared with the \"Showing X–Y of N\" range summary — both are the same text tier). Plain author-supplied markup, same real-insertion-point pattern as every other table-toolbar slot — no forced typography beyond the font-size token. Stays visible at every ADR-0076 compaction stage: it is DATA (an aggregate figure the consumer chose to show), not chrome, so it is never dropped or compacted the way the range summary/ controls are — a consumer needing narrower-container behavior owns that via its own slotted content, not a component-side compaction rule. Empty (the default) has zero footprint on existing consumers."
|
|
219
245
|
}
|
|
220
246
|
},
|
|
221
247
|
"states": [
|
|
@@ -172,6 +172,12 @@ export class UITableToolbar extends UIElement {
|
|
|
172
172
|
rangeStart: { type: Number, default: 0, reflect: true, attribute: 'range-start' },
|
|
173
173
|
rangeEnd: { type: Number, default: 0, reflect: true, attribute: 'range-end' },
|
|
174
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' },
|
|
175
181
|
pageSize: { type: Number, default: 0, reflect: true, attribute: 'page-size' },
|
|
176
182
|
noFilter: { type: Boolean, default: false, reflect: true, attribute: 'no-filter' },
|
|
177
183
|
noSort: { type: Boolean, default: false, reflect: true, attribute: 'no-sort' },
|
|
@@ -181,6 +187,13 @@ export class UITableToolbar extends UIElement {
|
|
|
181
187
|
// above. Pure, absolute OR: see #updateControlVisibility(), which is the
|
|
182
188
|
// single place that combines chromeOnly with each granular no-* value.
|
|
183
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' },
|
|
184
197
|
placeholder: { type: String, default: 'Search...', reflect: false },
|
|
185
198
|
variant: { type: String, default: 'default', reflect: true },
|
|
186
199
|
// ADR-0076 REQ-O-001 — explicit compaction-stage override. Unset (the
|
|
@@ -342,7 +355,7 @@ export class UITableToolbar extends UIElement {
|
|
|
342
355
|
// value one, so it needs the raw attribute callback, not the signal diff.
|
|
343
356
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
344
357
|
super.attributeChangedCallback(name, oldVal, newVal);
|
|
345
|
-
if (name === 'range-total') this.#updateSummary();
|
|
358
|
+
if (name === 'range-total' || name === 'range-of' || name === 'range-noun') this.#updateSummary();
|
|
346
359
|
// ADR-0076 REQ-M-005 — the SECOND of the two triggers `data-stage-
|
|
347
360
|
// resolved` must react to: a live resize (the ResizeObserver callback
|
|
348
361
|
// above) AND a `stage` attribute change alone, with no resize needed.
|
|
@@ -670,6 +683,26 @@ export class UITableToolbar extends UIElement {
|
|
|
670
683
|
}
|
|
671
684
|
}
|
|
672
685
|
|
|
686
|
+
// Summary slot passthrough (gh#1883) — a real insertion point (ADR-0067),
|
|
687
|
+
// same pattern as [slot="scope"]/[slot="actions-leading"]/[slot="actions"]
|
|
688
|
+
// above, for a trailing aggregate/total datum (e.g. line-items Subtotal)
|
|
689
|
+
// that belongs with the table but isn't pagination context. Rendered
|
|
690
|
+
// AFTER [slot="actions-leading"] and BEFORE the page-size select /
|
|
691
|
+
// [slot="actions"] — closer to the trailing edge than the range summary,
|
|
692
|
+
// controls, or search, since it reads as a result rather than view
|
|
693
|
+
// chrome. Deliberately NOT named [data-summary] — that marker already
|
|
694
|
+
// belongs to the "Showing X–Y of N" range-summary cluster stamped above;
|
|
695
|
+
// reusing it here would collide with every existing #updateSummary()/CSS
|
|
696
|
+
// selector keyed on that attribute.
|
|
697
|
+
const summarySlot = document.createElement('div');
|
|
698
|
+
summarySlot.setAttribute('data-summary-slot', '');
|
|
699
|
+
for (const node of [...this.children]) {
|
|
700
|
+
if (node === root) continue;
|
|
701
|
+
if (node.getAttribute?.('slot') === 'summary') {
|
|
702
|
+
summarySlot.appendChild(node);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
673
706
|
// Actions slot passthrough — we move any pre-existing [slot="actions"] children here
|
|
674
707
|
const actionsSlot = document.createElement('div');
|
|
675
708
|
actionsSlot.setAttribute('data-actions', '');
|
|
@@ -686,6 +719,7 @@ export class UITableToolbar extends UIElement {
|
|
|
686
719
|
root.appendChild(controls);
|
|
687
720
|
root.appendChild(search);
|
|
688
721
|
root.appendChild(actionsLeadingSlot);
|
|
722
|
+
root.appendChild(summarySlot);
|
|
689
723
|
root.appendChild(pageSize);
|
|
690
724
|
root.appendChild(actionsSlot);
|
|
691
725
|
|
|
@@ -849,13 +883,46 @@ export class UITableToolbar extends UIElement {
|
|
|
849
883
|
const end = Number(this.rangeEnd) || 0;
|
|
850
884
|
const total = Number(this.rangeTotal) || 0;
|
|
851
885
|
|
|
886
|
+
// gh#1877/ADR-0082 Amendment — same open/unproven-total discipline as
|
|
887
|
+
// table-footer-ui's own #draw(): NOT keyed on the raw attribute
|
|
888
|
+
// string. rangeTotal is a Number-typed reflected prop, so
|
|
889
|
+
// range-total="?" parses via `+"?"` → NaN. gh#1895 — reflect()
|
|
890
|
+
// (core/element.js) leaves a non-finite Number value unwritten instead
|
|
891
|
+
// of stomping the sentinel with the literal string "NaN", so
|
|
892
|
+
// getAttribute('range-total') keeps reading "?" as authored. The
|
|
893
|
+
// coerced PROPERTY still holds NaN stably (Object.is(NaN, NaN) is
|
|
894
|
+
// true, so re-parsing the unchanged attribute never re-triggers a
|
|
895
|
+
// write either), and that's what tells "unknown yet" apart from a
|
|
896
|
+
// confirmed range-total="0" (a finite 0).
|
|
897
|
+
const rangeOpen = !Number.isFinite(Number(this.rangeTotal));
|
|
898
|
+
// gh#1877/ADR-0082 Amendment — additive suffixes onto the normal
|
|
899
|
+
// finite "of N" text; range-of (secondary whole-set total) never
|
|
900
|
+
// applies to the open-total branch (no finite total to append against).
|
|
901
|
+
const hasRangeOf = this.hasAttribute('range-of');
|
|
902
|
+
const rangeOfVal = hasRangeOf ? (Number(this.rangeOf) || 0) : 0;
|
|
903
|
+
const rangeOfSuffix = hasRangeOf && rangeOfVal > 0 ? ` (of ${rangeOfVal} total)` : '';
|
|
904
|
+
const nounSuffix = this.rangeNoun ? ` ${this.rangeNoun}` : '';
|
|
905
|
+
|
|
906
|
+
if (rangeOpen && start > 0 && end > 0) {
|
|
907
|
+
// REQ-open (gh#1877/ADR-0082 Amendment) — "Showing X–Y" with no
|
|
908
|
+
// "of N"; the short-form mirrors it minus the "Showing " prefix,
|
|
909
|
+
// same paired-population discipline as the finite branch below.
|
|
910
|
+
if (text) text.textContent = `Showing ${start}–${end}${nounSuffix}`;
|
|
911
|
+
if (text) text.hidden = false;
|
|
912
|
+
if (compact) compact.textContent = `${start}–${end}`;
|
|
913
|
+
if (compact) compact.hidden = false;
|
|
914
|
+
if (empty) empty.hidden = true;
|
|
915
|
+
summary.hidden = false;
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
852
919
|
if (start > 0 && end > 0 && total > 0) {
|
|
853
920
|
// Normal range text — always wins over the empty slot (AC-014).
|
|
854
921
|
// Both the full ("Showing X–Y of N") and short-form ("X–Y/N", ADR-0076
|
|
855
922
|
// REQ-S-003) strings are populated together — which one paints is a
|
|
856
923
|
// pure CSS decision (icon-only @container rule / [stage] pin), never
|
|
857
924
|
// a JS branch (REQ-M-002).
|
|
858
|
-
if (text) text.textContent = `Showing ${start}–${end} of ${total}`;
|
|
925
|
+
if (text) text.textContent = `Showing ${start}–${end} of ${total}${nounSuffix}${rangeOfSuffix}`;
|
|
859
926
|
if (text) text.hidden = false;
|
|
860
927
|
if (compact) compact.textContent = `${start}–${end}/${total}`;
|
|
861
928
|
if (compact) compact.hidden = false;
|
|
@@ -330,8 +330,12 @@
|
|
|
330
330
|
prop; this reads what's already in the DOM, same `:has()` idiom the
|
|
331
331
|
[data-summary]/[data-page-size] reconciliation above already uses).
|
|
332
332
|
A consumer wanting BOTH visible can still override with normal CSS
|
|
333
|
-
specificity — this isn't a hard mode switch, just the default.
|
|
334
|
-
|
|
333
|
+
specificity — this isn't a hard mode switch, just the default.
|
|
334
|
+
[title-always] (gh#1879) is the declared opt-out for that same case:
|
|
335
|
+
a consumer wanting BOTH the scope select AND the explicit text/count
|
|
336
|
+
title visible together sets the attribute instead of reaching for an
|
|
337
|
+
app-side CSS override. */
|
|
338
|
+
:scope:not([title-always]):has([data-scope]:not(:empty)) [data-title] {
|
|
335
339
|
display: none;
|
|
336
340
|
}
|
|
337
341
|
|
|
@@ -522,6 +526,36 @@
|
|
|
522
526
|
|
|
523
527
|
[data-actions-leading]:empty { display: none; }
|
|
524
528
|
|
|
529
|
+
/* ═══════ Summary slot ═══════ (gh#1883 — trailing aggregate/total datum,
|
|
530
|
+
e.g. line-items Subtotal, rendered AFTER [slot="actions-leading"] and
|
|
531
|
+
BEFORE the page-size select / [slot="actions"]. Text-tier sized via
|
|
532
|
+
--table-toolbar-summary-size — the SAME token the range summary
|
|
533
|
+
([data-summary-text]/[data-summary-compact]) already uses, since both
|
|
534
|
+
read as the same "small figure next to the toolbar chrome" tier;
|
|
535
|
+
color is NOT forced (unlike the range summary, which paints its own
|
|
536
|
+
generated text) — this slot holds plain author-supplied markup that
|
|
537
|
+
may want its own emphasis, e.g. a bold `<strong>` total.
|
|
538
|
+
Deliberately carries no margin-inline-start:auto of its own: it sits
|
|
539
|
+
between [data-actions-leading] and [data-page-size], both of which
|
|
540
|
+
already push the trailing group to the far edge, so a third auto
|
|
541
|
+
margin here would only ever be a no-op (the first unclaimed-space
|
|
542
|
+
sibling already claims it) — see [data-page-size]'s own comment below.
|
|
543
|
+
No compaction-stage hide/compact rule at any breakpoint or [stage]
|
|
544
|
+
pin, unlike [data-summary] above — this is DATA the consumer chose to
|
|
545
|
+
show, not chrome; a consumer needing narrower-container behavior owns
|
|
546
|
+
that via its own slotted content. */
|
|
547
|
+
|
|
548
|
+
[data-summary-slot] {
|
|
549
|
+
display: inline-flex;
|
|
550
|
+
align-items: center;
|
|
551
|
+
gap: var(--table-toolbar-cluster-gap);
|
|
552
|
+
flex: 0 0 auto;
|
|
553
|
+
font-size: var(--table-toolbar-summary-size);
|
|
554
|
+
line-height: var(--a-font-leading-snug);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
[data-summary-slot]:empty { display: none; }
|
|
558
|
+
|
|
525
559
|
/* ═══════ Page-size select ═══════ (gh#1615)
|
|
526
560
|
Carries the SAME margin-inline-start:auto as [data-search] so the
|
|
527
561
|
trailing group (controls/search/page-size/actions) still pushes to the
|
|
@@ -46,16 +46,22 @@ export class UITableToolbar extends UIElement {
|
|
|
46
46
|
placeholder: string;
|
|
47
47
|
/** 1-indexed last row number of the current page, for the range summary (e.g. `25` in "Showing 1–25 of 320"). See rangeStart. */
|
|
48
48
|
rangeEnd: number;
|
|
49
|
+
/** 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"). */
|
|
50
|
+
rangeNoun: string;
|
|
51
|
+
/** 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"). */
|
|
52
|
+
rangeOf: number;
|
|
49
53
|
/** 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. */
|
|
50
54
|
rangeStart: number;
|
|
51
|
-
/** 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. */
|
|
52
|
-
rangeTotal: number;
|
|
55
|
+
/** 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"). */
|
|
56
|
+
rangeTotal: number | string;
|
|
53
57
|
/** 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. */
|
|
54
58
|
size: 'sm' | 'md' | 'lg';
|
|
55
59
|
/** 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). */
|
|
56
60
|
stage: '' | 'full' | 'search-tight' | 'icon-only' | 'overflow';
|
|
57
61
|
/** Title text shown on the left. */
|
|
58
62
|
text: string;
|
|
63
|
+
/** 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). */
|
|
64
|
+
titleAlways: boolean;
|
|
59
65
|
/** Toolbar visual variant. `default` renders bare on parent surface; `card` adds the same chrome as a card-ui header. */
|
|
60
66
|
variant: 'default' | 'card';
|
|
61
67
|
|