@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
|
@@ -55,6 +55,21 @@ export class UISearch extends UIFormElement {
|
|
|
55
55
|
|
|
56
56
|
static template = () => null;
|
|
57
57
|
|
|
58
|
+
// ADR-0077 / spec-form-control-sizing-posture — `[inline]` is a CSS-only
|
|
59
|
+
// universal layout attribute (ADR-0037 §2: no yaml, no static properties
|
|
60
|
+
// entry on THIS host). But search-ui composes input-ui via light-DOM
|
|
61
|
+
// child stamping (connected()'s innerHTML below), so a plain CSS
|
|
62
|
+
// `:scope[inline]` on input-ui's own @scope can't reach across that
|
|
63
|
+
// composition boundary — the host's `[inline]` has to be forwarded onto
|
|
64
|
+
// the internal <input-ui> in JS. Hand-rolled `observedAttributes` +
|
|
65
|
+
// `attributeChangedCallback`, same shape as tags-input.class.js's `value`
|
|
66
|
+
// and drilldown.class.js's `path` — a non-declared-property attribute
|
|
67
|
+
// that still needs a JS reaction.
|
|
68
|
+
static get observedAttributes() {
|
|
69
|
+
const base = super.observedAttributes;
|
|
70
|
+
return base.includes('inline') ? base : [...base, 'inline'];
|
|
71
|
+
}
|
|
72
|
+
|
|
58
73
|
#inputEl = null;
|
|
59
74
|
#suffixEl = null;
|
|
60
75
|
#timer = null;
|
|
@@ -77,6 +92,7 @@ export class UISearch extends UIFormElement {
|
|
|
77
92
|
suffix="x-circle"
|
|
78
93
|
placeholder="${this.placeholder}"
|
|
79
94
|
${this.disabled ? 'disabled' : ''}
|
|
95
|
+
${this.hasAttribute('inline') ? 'inline' : ''}
|
|
80
96
|
${size ? `size="${size}"` : ''}
|
|
81
97
|
></input-ui>`;
|
|
82
98
|
}
|
|
@@ -106,11 +122,38 @@ export class UISearch extends UIFormElement {
|
|
|
106
122
|
setAttrIfChanged(this.#inputEl, 'placeholder', this.placeholder);
|
|
107
123
|
if (this.disabled) setAttrIfChanged(this.#inputEl, 'disabled', '');
|
|
108
124
|
else removeAttrIfPresent(this.#inputEl, 'disabled');
|
|
125
|
+
this.#syncInline();
|
|
109
126
|
|
|
110
127
|
// Reflect value for CSS (clear button visibility)
|
|
111
128
|
setAttrIfChanged(this, 'value', this.value || '');
|
|
112
129
|
}
|
|
113
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Forward this host's own `[inline]` (a CSS-only universal attribute,
|
|
133
|
+
* ADR-0037 §2 — never a declared property) onto the internal <input-ui>.
|
|
134
|
+
* Same idempotent-write shape as the placeholder/disabled sync above
|
|
135
|
+
* (gh#1755): setAttrIfChanged/removeAttrIfPresent so a byte-identical
|
|
136
|
+
* SSR-rendered child survives upgrade with zero attribute mutations.
|
|
137
|
+
*/
|
|
138
|
+
#syncInline() {
|
|
139
|
+
if (!this.#inputEl) return;
|
|
140
|
+
if (this.hasAttribute('inline')) setAttrIfChanged(this.#inputEl, 'inline', '');
|
|
141
|
+
else removeAttrIfPresent(this.#inputEl, 'inline');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* `[inline]` isn't a declared property (see `observedAttributes` above),
|
|
146
|
+
* so a post-connect `el.setAttribute('inline', '')` wouldn't otherwise
|
|
147
|
+
* trigger a re-render — render()'s effect only tracks this element's own
|
|
148
|
+
* reactive signals. Sync it directly here so a later toggle still
|
|
149
|
+
* propagates to the already-created child, same guarantee `.disabled =`
|
|
150
|
+
* gets for free from the declared-property/render-effect path.
|
|
151
|
+
*/
|
|
152
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
153
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
154
|
+
if (name === 'inline') this.#syncInline();
|
|
155
|
+
}
|
|
156
|
+
|
|
114
157
|
#onInput = () => {
|
|
115
158
|
this.value = this.#inputEl.value;
|
|
116
159
|
this.syncValue(this.value);
|
|
@@ -9,6 +9,34 @@
|
|
|
9
9
|
|
|
10
10
|
:scope {
|
|
11
11
|
display: block;
|
|
12
|
+
/* ADR-0077 / spec-form-control-sizing-posture REQ-P-001/REQ-R-001
|
|
13
|
+
(search-ui row): fill by default, matching input-ui's own posture
|
|
14
|
+
(input.css). `display: block` alone gives no width once this host
|
|
15
|
+
becomes a flex item (row-ui) — flex items size from content, not
|
|
16
|
+
display type — so the fill is explicit. The inner <input-ui> (see
|
|
17
|
+
the `input-ui` override rule below) carries the matching
|
|
18
|
+
`width: 100%` already shipped on its own default :scope, so it
|
|
19
|
+
fills THIS now-100%-wide
|
|
20
|
+
host in turn — two widths, one contract. No host floor here: no
|
|
21
|
+
`--search-min-width` token is minted (REQ-F-003's documented
|
|
22
|
+
build-time choice) because the inner input's own hug-state floor
|
|
23
|
+
(input.css's --input-min-width, forwarded via [inline] below)
|
|
24
|
+
already delivers the family's legibility guarantee. */
|
|
25
|
+
width: 100%;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ADR-0077 / spec-form-control-sizing-posture REQ-P-003 (hug, via
|
|
29
|
+
[inline]): ADR-0037 §2 display-flip mechanics + `width: fit-content`
|
|
30
|
+
(not the mere absence of the fill rule — a grid item is blockified/
|
|
31
|
+
stretch-aligned by default, so hug needs the explicit rule to win
|
|
32
|
+
there too). search.class.js forwards this host's own `[inline]`
|
|
33
|
+
attribute onto the internal <input-ui> (light-DOM composition — CSS
|
|
34
|
+
`:scope[inline]` can't reach across that boundary on its own), so the
|
|
35
|
+
inner input's own :scope[inline] floor (--input-min-width, 20ch)
|
|
36
|
+
becomes this control's visible hug-state legibility floor. */
|
|
37
|
+
:scope[inline] {
|
|
38
|
+
display: inline-block;
|
|
39
|
+
width: fit-content;
|
|
12
40
|
}
|
|
13
41
|
|
|
14
42
|
/* Hide clear icon when empty */
|
|
@@ -7,7 +7,10 @@ status: stable
|
|
|
7
7
|
component: Search
|
|
8
8
|
category: input
|
|
9
9
|
version: 1
|
|
10
|
-
description: Search input with magnifying-glass icon, clear button, and debounced search event.
|
|
10
|
+
description: Search input with magnifying-glass icon, clear button, and debounced search event. Fills its
|
|
11
|
+
container's available inline width by default (ADR-0077 / spec-form-control-sizing-posture) — in a flex row
|
|
12
|
+
(row-ui) or grid track it tracks the container all the way down. `[inline]` opts into the old shrink-wrap
|
|
13
|
+
behavior and forwards onto the internal input-ui so its 20ch legibility floor applies.
|
|
11
14
|
props:
|
|
12
15
|
name:
|
|
13
16
|
description: Form field name
|
|
@@ -755,7 +755,10 @@ export class UISelect extends UIFormElement {
|
|
|
755
755
|
if (!opt) return '';
|
|
756
756
|
if (opt.mark) return `<adia-mark-ui data-select-mark size="sm"></adia-mark-ui>`;
|
|
757
757
|
if (opt.avatar) return `<img data-option-avatar src="${escapeHTML(opt.avatar)}" alt="" />`;
|
|
758
|
-
|
|
758
|
+
// gh#1857 — data-select-icon mirrors data-select-mark: a marker the
|
|
759
|
+
// trigger/option-row icon-sizing rule (select.css) keys off, so this
|
|
760
|
+
// stamped leading icon renders at the same 1.25rem diameter as the mark.
|
|
761
|
+
if (opt.icon) return `<icon-ui data-select-icon name="${escapeHTML(opt.icon)}"></icon-ui>`;
|
|
759
762
|
return '';
|
|
760
763
|
}
|
|
761
764
|
|
|
@@ -622,3 +622,16 @@ select-ui adia-mark-ui[data-select-leading][size],
|
|
|
622
622
|
select-ui adia-mark-ui[data-select-mark][size] {
|
|
623
623
|
--adia-mark-size: 1.25rem;
|
|
624
624
|
}
|
|
625
|
+
|
|
626
|
+
/* gh#1857 — stamped leading icons match the mark's 1.25rem diameter above
|
|
627
|
+
(visual parity between the two leading-visual kinds). The trigger's
|
|
628
|
+
icon-ui already carries [data-select-leading] (shared with mark/avatar,
|
|
629
|
+
select.class.js #syncLeading()); the option-row icon-ui carries the
|
|
630
|
+
parallel [data-select-icon] marker (#optionLeadHTML()). Both selectors
|
|
631
|
+
add the [role="option"] / tag qualifiers needed to strictly outspecify
|
|
632
|
+
the generic `select-ui [role="option"] icon-ui` (--a-ui-size) default
|
|
633
|
+
above without relying on source order. */
|
|
634
|
+
select-ui icon-ui[data-select-leading],
|
|
635
|
+
select-ui [role="option"] icon-ui[data-select-icon] {
|
|
636
|
+
--a-icon-size: 1.25rem;
|
|
637
|
+
}
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
],
|
|
15
15
|
"properties": {
|
|
16
16
|
"band": {
|
|
17
|
-
"description": "Chart-band KPI layout. With a `slot=\"chart\"` child, label / value / change stack full width top to bottom and the chart forms a short full-width band between the value and the change row (the Gmail/Vercel/shadcn stat-card shape). Composes with `icon` exactly as the base layout does. No effect without a chart slot.",
|
|
17
|
+
"description": "Chart-band KPI layout. With a `slot=\"chart\"` child, label / value / change stack full width top to bottom and the chart forms a short full-width band between the value and the change row (the Gmail/Vercel/shadcn stat-card shape). Composes with `icon` exactly as the base layout does. Set together with `bleed` (`band bleed`) for the KPI tile: the band bleeds to the card's inline-start, inline-end, and block-end edges, and the `change` delta overlays it as a chip instead of stacking below it (ADR-0083) — the tile's `stat-ui` must be the card's last content (no trailing footer/section). No effect without a chart slot.",
|
|
18
18
|
"type": "boolean",
|
|
19
19
|
"default": false
|
|
20
20
|
},
|
|
21
21
|
"bleed": {
|
|
22
|
-
"description": "
|
|
22
|
+
"description": "The chart reaches the card's edges — which edges depends on `band`: a column bleeding top/right/bottom without it, a three-edge (inline-start, inline-end, block-end) bottom band with it. With `slot=\"chart\"` alone (no `band`), the value / label / change stack on the left while the chart fills the right column at full height and bleeds to the card's top / right / bottom edges (the horizontal counterpart to a chart in a card `<section bleed>`). Set together with `band` for the full-bleed KPI tile — see `band`. No effect without a chart slot.",
|
|
23
23
|
"type": "boolean",
|
|
24
24
|
"default": false
|
|
25
25
|
},
|
|
@@ -76,6 +76,11 @@
|
|
|
76
76
|
"description": "Basic Stat usage",
|
|
77
77
|
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Card\",\n \"children\": [\n \"sec\"\n ]\n },\n {\n \"id\": \"sec\",\n \"component\": \"Section\",\n \"children\": [\n \"comp\"\n ]\n },\n {\n \"id\": \"comp\",\n \"component\": \"Stat\",\n \"label\": \"Example Stat\",\n \"value\": \"\"\n }\n]",
|
|
78
78
|
"name": "basic-stat"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"description": "KPI tile — band bleed with an overlaid delta chip (ADR-0083)",
|
|
82
|
+
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"Card\",\n \"children\": [\n \"sec\"\n ]\n },\n {\n \"id\": \"sec\",\n \"component\": \"Section\",\n \"children\": [\n \"comp\"\n ]\n },\n {\n \"id\": \"comp\",\n \"component\": \"Stat\",\n \"band\": true,\n \"bleed\": true,\n \"label\": \"Sessions\",\n \"value\": \"84,232\",\n \"change\": \"+57%\",\n \"trend\": \"up\",\n \"children\": [\n \"chart\"\n ]\n },\n {\n \"id\": \"chart\",\n \"component\": \"Chart\",\n \"slot\": \"chart\",\n \"type\": \"area\",\n \"x\": \"d\",\n \"y\": \"v\",\n \"noGrid\": true,\n \"noValues\": true,\n \"data\": [\n { \"d\": \"Mon\", \"v\": 12 },\n { \"d\": \"Tue\", \"v\": 18 },\n { \"d\": \"Wed\", \"v\": 15 },\n { \"d\": \"Thu\", \"v\": 27 },\n { \"d\": \"Fri\", \"v\": 22 },\n { \"d\": \"Sat\", \"v\": 31 },\n { \"d\": \"Sun\", \"v\": 29 }\n ]\n }\n]",
|
|
83
|
+
"name": "tile"
|
|
79
84
|
}
|
|
80
85
|
],
|
|
81
86
|
"keywords": [
|
|
@@ -96,7 +101,8 @@
|
|
|
96
101
|
"telemetry",
|
|
97
102
|
"observability",
|
|
98
103
|
"performance",
|
|
99
|
-
"scoreboard"
|
|
104
|
+
"scoreboard",
|
|
105
|
+
"tile"
|
|
100
106
|
],
|
|
101
107
|
"name": "UIStat",
|
|
102
108
|
"parts": {},
|
|
@@ -236,6 +242,27 @@
|
|
|
236
242
|
},
|
|
237
243
|
"tag": "stat-ui",
|
|
238
244
|
"tokens": {
|
|
245
|
+
"--stat-change-chip-bg": {
|
|
246
|
+
"description": "Background of the `band bleed` tile's change chip. Falls back to `chart-ui`'s `--chart-chip-bg`, so an ancestor override re-themes chart axis chips and this delta chip together."
|
|
247
|
+
},
|
|
248
|
+
"--stat-change-chip-fg": {
|
|
249
|
+
"description": "Text color of the `band bleed` tile's change chip when no canonical `trend` is set (a set `trend` keeps painting via `--stat-up-fg` / `--stat-down-fg`). Falls back to `chart-ui`'s `--chart-chip-fg`."
|
|
250
|
+
},
|
|
251
|
+
"--stat-change-chip-font-size": {
|
|
252
|
+
"description": "Font size of the `band bleed` tile's change chip. Falls back to `chart-ui`'s `--chart-chip-font-size`."
|
|
253
|
+
},
|
|
254
|
+
"--stat-change-chip-pad-x": {
|
|
255
|
+
"description": "Inline padding of the `band bleed` tile's change chip. Falls back to `chart-ui`'s `--chart-chip-pad-x`."
|
|
256
|
+
},
|
|
257
|
+
"--stat-change-chip-pad-y": {
|
|
258
|
+
"description": "Block padding of the `band bleed` tile's change chip. Falls back to `chart-ui`'s `--chart-chip-pad-y`."
|
|
259
|
+
},
|
|
260
|
+
"--stat-change-chip-radius": {
|
|
261
|
+
"description": "Corner radius of the `band bleed` tile's change chip. Falls back to `chart-ui`'s `--chart-chip-radius`."
|
|
262
|
+
},
|
|
263
|
+
"--stat-change-inset": {
|
|
264
|
+
"description": "Clearance of the `band bleed` tile's change chip from the chart band's edges. Aliases the same ladder rung as `chart-ui`'s `--chart-chrome-inset`."
|
|
265
|
+
},
|
|
239
266
|
"--stat-change-size": {
|
|
240
267
|
"description": "Font size for the change badge"
|
|
241
268
|
},
|
package/components/stat/stat.css
CHANGED
|
@@ -19,6 +19,19 @@
|
|
|
19
19
|
|
|
20
20
|
/* ── Chart-band mode ── */
|
|
21
21
|
--stat-chart-band-height: 3rem;
|
|
22
|
+
|
|
23
|
+
/* ── Tile mode (band + bleed, ADR-0083) — change-chip overlay ──
|
|
24
|
+
Stat-scoped aliases falling back to the exact rung chart.css
|
|
25
|
+
resolves the corresponding --chart-chip-* token to, so an
|
|
26
|
+
ancestor override re-themes both chart axis chips and this delta
|
|
27
|
+
chip in one stroke (ADR-0083 decision 3). */
|
|
28
|
+
--stat-change-inset: var(--a-space-2);
|
|
29
|
+
--stat-change-chip-bg: var(--chart-chip-bg, var(--md-sys-color-neutral-container-high));
|
|
30
|
+
--stat-change-chip-fg: var(--chart-chip-fg, var(--md-sys-color-neutral-on-surface));
|
|
31
|
+
--stat-change-chip-radius: var(--chart-chip-radius, var(--a-radius-sm));
|
|
32
|
+
--stat-change-chip-pad-x: var(--chart-chip-pad-x, var(--a-space-1-5));
|
|
33
|
+
--stat-change-chip-pad-y: var(--chart-chip-pad-y, var(--a-space-1));
|
|
34
|
+
--stat-change-chip-font-size: var(--chart-chip-font-size, var(--a-ui-tiny));
|
|
22
35
|
}
|
|
23
36
|
|
|
24
37
|
:scope {
|
|
@@ -166,6 +179,81 @@
|
|
|
166
179
|
aspect-ratio: auto;
|
|
167
180
|
}
|
|
168
181
|
|
|
182
|
+
/* ── Tile layout: band + bleed together (ADR-0083) ──
|
|
183
|
+
The chart-band from `[band]` above, generalized to bleed three edges
|
|
184
|
+
(inline-start, inline-end, block-end) the way `[bleed]` already
|
|
185
|
+
bleeds horizontally, with the `change` delta overlaid as a chip
|
|
186
|
+
inside the band instead of stacking below it. Declared AFTER the
|
|
187
|
+
shipped `[band]` block above so it wins the equal-specificity tie
|
|
188
|
+
the undefined `band bleed` combination used to lose — `[band]`'s
|
|
189
|
+
grid-template-areas is replaced wholesale (the `change` row is
|
|
190
|
+
removed; the chip lives in the chart grid area instead). Host
|
|
191
|
+
precondition (author-controlled, undetected — the same posture
|
|
192
|
+
gh#1801 ratified for `section[bleed]`): this stat-ui must be the
|
|
193
|
+
last content before the card's block-end edge, no trailing
|
|
194
|
+
footer/section — a following region would collide with the
|
|
195
|
+
negative block-end margin below. Compose inside a card section:
|
|
196
|
+
<stat-ui band bleed value=… label=… change=… trend=…>
|
|
197
|
+
<chart-ui slot="chart" type="area" …></chart-ui>
|
|
198
|
+
</stat-ui> */
|
|
199
|
+
:scope[band][bleed]:has([slot="chart"]) {
|
|
200
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
201
|
+
grid-template-areas:
|
|
202
|
+
"label icon"
|
|
203
|
+
"value value"
|
|
204
|
+
"chart chart";
|
|
205
|
+
align-items: baseline;
|
|
206
|
+
}
|
|
207
|
+
:scope[band][bleed] [slot="chart"] {
|
|
208
|
+
grid-area: chart;
|
|
209
|
+
align-self: stretch;
|
|
210
|
+
height: var(--stat-chart-band-height);
|
|
211
|
+
aspect-ratio: auto;
|
|
212
|
+
/* Three-edge bleed via the --card-inset negative-extent technique
|
|
213
|
+
[bleed] already uses horizontally (see the horizontal-bleed block
|
|
214
|
+
above for the width/margin-block rationale — chart-ui pins its own
|
|
215
|
+
width:100%, so the box is explicitly sized rather than stretched). */
|
|
216
|
+
margin-inline: calc(-1 * var(--card-inset, 0px));
|
|
217
|
+
margin-block-end: calc(-1 * var(--card-inset, 0px));
|
|
218
|
+
width: calc(100% + 2 * var(--card-inset, 0px));
|
|
219
|
+
min-width: 0;
|
|
220
|
+
}
|
|
221
|
+
/* Change chip: overlaid in the chart's own grid area, above it in
|
|
222
|
+
paint order, anchored block-start / inline-end of the band (logical
|
|
223
|
+
properties — RTL mirrors for free). Corner clearance from the
|
|
224
|
+
card's rounded corners is structural: the band's rounded corners are
|
|
225
|
+
block-end only (its block-start edge is mid-card, square), so a
|
|
226
|
+
block-start-anchored chip can never meet one. pointer-events: none —
|
|
227
|
+
it's text, not a control, so it never blocks the chart's own
|
|
228
|
+
hover/tooltip hit-testing beneath it. */
|
|
229
|
+
:scope[band][bleed] [slot="change"] {
|
|
230
|
+
grid-area: chart;
|
|
231
|
+
justify-self: end;
|
|
232
|
+
align-self: start;
|
|
233
|
+
z-index: 1;
|
|
234
|
+
inset-block-start: var(--stat-change-inset);
|
|
235
|
+
inset-inline-end: var(--stat-change-inset);
|
|
236
|
+
position: relative;
|
|
237
|
+
pointer-events: none;
|
|
238
|
+
background: var(--stat-change-chip-bg);
|
|
239
|
+
border-radius: var(--stat-change-chip-radius);
|
|
240
|
+
padding: var(--stat-change-chip-pad-y) var(--stat-change-chip-pad-x);
|
|
241
|
+
font-size: var(--stat-change-chip-font-size);
|
|
242
|
+
}
|
|
243
|
+
/* Chip text fallback — deliberately LOW specificity via :where() (0,1,0,
|
|
244
|
+
same as the generic [slot="change"] rule below it) so the
|
|
245
|
+
[trend="up"/"down"] rules further down this file (0,2,0 — they win
|
|
246
|
+
regardless of source order) keep painting the chip's text + arrow
|
|
247
|
+
whenever a canonical trend is set. --stat-change-chip-fg applies only
|
|
248
|
+
when no trend is set — the fallback, not an override. Splitting this
|
|
249
|
+
one declaration out of the block above (which stays 0,3,0 for its
|
|
250
|
+
geometry/surface properties, none of which trend re-paints) is the
|
|
251
|
+
only way to give color a lower specificity than the rest of that
|
|
252
|
+
block while keeping this selector, not a shape-widening rewrite. */
|
|
253
|
+
:where(:scope[band][bleed]) [slot="change"] {
|
|
254
|
+
color: var(--stat-change-chip-fg);
|
|
255
|
+
}
|
|
256
|
+
|
|
169
257
|
/* ── Label (eyebrow) ── */
|
|
170
258
|
[slot="label"] {
|
|
171
259
|
grid-area: label;
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
import { UIElement } from '../../core/element.js';
|
|
14
14
|
|
|
15
15
|
export class UIStat extends UIElement {
|
|
16
|
-
/** Chart-band KPI layout. With a `slot="chart"` child, label / value / change stack full width top to bottom and the chart forms a short full-width band between the value and the change row (the Gmail/Vercel/shadcn stat-card shape). Composes with `icon` exactly as the base layout does. No effect without a chart slot. */
|
|
16
|
+
/** Chart-band KPI layout. With a `slot="chart"` child, label / value / change stack full width top to bottom and the chart forms a short full-width band between the value and the change row (the Gmail/Vercel/shadcn stat-card shape). Composes with `icon` exactly as the base layout does. Set together with `bleed` (`band bleed`) for the KPI tile: the band bleeds to the card's inline-start, inline-end, and block-end edges, and the `change` delta overlays it as a chip instead of stacking below it (ADR-0083) — the tile's `stat-ui` must be the card's last content (no trailing footer/section). No effect without a chart slot. */
|
|
17
17
|
band: boolean;
|
|
18
|
-
/**
|
|
18
|
+
/** The chart reaches the card's edges — which edges depends on `band`: a column bleeding top/right/bottom without it, a three-edge (inline-start, inline-end, block-end) bottom band with it. With `slot="chart"` alone (no `band`), the value / label / change stack on the left while the chart fills the right column at full height and bleeds to the card's top / right / bottom edges (the horizontal counterpart to a chart in a card `<section bleed>`). Set together with `band` for the full-bleed KPI tile — see `band`. No effect without a chart slot. */
|
|
19
19
|
bleed: boolean;
|
|
20
20
|
/** Change indicator text (e.g. '+12%', '-3%') */
|
|
21
21
|
change: string;
|
|
@@ -51,11 +51,14 @@ props:
|
|
|
51
51
|
default: ""
|
|
52
52
|
bleed:
|
|
53
53
|
description: >-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
The chart reaches the card's edges — which edges depends on `band`: a
|
|
55
|
+
column bleeding top/right/bottom without it, a three-edge (inline-start,
|
|
56
|
+
inline-end, block-end) bottom band with it. With `slot="chart"` alone
|
|
57
|
+
(no `band`), the value / label / change stack on the left while the
|
|
58
|
+
chart fills the right column at full height and bleeds to the card's
|
|
59
|
+
top / right / bottom edges (the horizontal counterpart to a chart in a
|
|
60
|
+
card `<section bleed>`). Set together with `band` for the full-bleed KPI
|
|
61
|
+
tile — see `band`. No effect without a chart slot.
|
|
59
62
|
type: boolean
|
|
60
63
|
default: false
|
|
61
64
|
band:
|
|
@@ -64,7 +67,12 @@ props:
|
|
|
64
67
|
change stack full width top to bottom and the chart forms a short
|
|
65
68
|
full-width band between the value and the change row (the
|
|
66
69
|
Gmail/Vercel/shadcn stat-card shape). Composes with `icon` exactly as
|
|
67
|
-
the base layout does.
|
|
70
|
+
the base layout does. Set together with `bleed` (`band bleed`) for the
|
|
71
|
+
KPI tile: the band bleeds to the card's inline-start, inline-end, and
|
|
72
|
+
block-end edges, and the `change` delta overlays it as a chip instead
|
|
73
|
+
of stacking below it (ADR-0083) — the tile's `stat-ui` must be the
|
|
74
|
+
card's last content (no trailing footer/section). No effect without a
|
|
75
|
+
chart slot.
|
|
68
76
|
type: boolean
|
|
69
77
|
default: false
|
|
70
78
|
events: {}
|
|
@@ -112,6 +120,37 @@ tokens:
|
|
|
112
120
|
description: Font size for the primary value
|
|
113
121
|
--stat-value-weight:
|
|
114
122
|
description: Font weight for the primary value
|
|
123
|
+
--stat-change-inset:
|
|
124
|
+
description: >-
|
|
125
|
+
Clearance of the `band bleed` tile's change chip from the chart band's
|
|
126
|
+
edges. Aliases the same ladder rung as `chart-ui`'s
|
|
127
|
+
`--chart-chrome-inset`.
|
|
128
|
+
--stat-change-chip-bg:
|
|
129
|
+
description: >-
|
|
130
|
+
Background of the `band bleed` tile's change chip. Falls back to
|
|
131
|
+
`chart-ui`'s `--chart-chip-bg`, so an ancestor override re-themes chart
|
|
132
|
+
axis chips and this delta chip together.
|
|
133
|
+
--stat-change-chip-fg:
|
|
134
|
+
description: >-
|
|
135
|
+
Text color of the `band bleed` tile's change chip when no canonical
|
|
136
|
+
`trend` is set (a set `trend` keeps painting via `--stat-up-fg` /
|
|
137
|
+
`--stat-down-fg`). Falls back to `chart-ui`'s `--chart-chip-fg`.
|
|
138
|
+
--stat-change-chip-radius:
|
|
139
|
+
description: >-
|
|
140
|
+
Corner radius of the `band bleed` tile's change chip. Falls back to
|
|
141
|
+
`chart-ui`'s `--chart-chip-radius`.
|
|
142
|
+
--stat-change-chip-pad-x:
|
|
143
|
+
description: >-
|
|
144
|
+
Inline padding of the `band bleed` tile's change chip. Falls back to
|
|
145
|
+
`chart-ui`'s `--chart-chip-pad-x`.
|
|
146
|
+
--stat-change-chip-pad-y:
|
|
147
|
+
description: >-
|
|
148
|
+
Block padding of the `band bleed` tile's change chip. Falls back to
|
|
149
|
+
`chart-ui`'s `--chart-chip-pad-y`.
|
|
150
|
+
--stat-change-chip-font-size:
|
|
151
|
+
description: >-
|
|
152
|
+
Font size of the `band bleed` tile's change chip. Falls back to
|
|
153
|
+
`chart-ui`'s `--chart-chip-font-size`.
|
|
115
154
|
requiredIcons:
|
|
116
155
|
- arrow-up
|
|
117
156
|
- arrow-down
|
|
@@ -123,6 +162,8 @@ a2ui:
|
|
|
123
162
|
reason: 'Different visual + semantic role.'
|
|
124
163
|
- rule: 'Delta indicator uses positive/negative semantic tokens; pass change= attribute with sign.'
|
|
125
164
|
reason: 'Built-in trend coloring; no manual styling needed.'
|
|
165
|
+
- rule: 'For the KPI tile — big value over a full-bleed sparkline/area band with the delta as a chip — set `band bleed` together on the stat-ui; the tile must be the card''s last content.'
|
|
166
|
+
reason: 'band bleed composes the shipped chart-band and horizontal-bleed layouts into the full-bleed tile with the change delta overlaid inside the band as a chip (ADR-0083); a trailing footer/section after it collides with the band''s negative block-end margin.'
|
|
126
167
|
anti_patterns: []
|
|
127
168
|
examples:
|
|
128
169
|
- name: basic-stat
|
|
@@ -150,6 +191,57 @@ examples:
|
|
|
150
191
|
"value": ""
|
|
151
192
|
}
|
|
152
193
|
]
|
|
194
|
+
- name: tile
|
|
195
|
+
description: KPI tile — band bleed with an overlaid delta chip (ADR-0083)
|
|
196
|
+
a2ui: >-
|
|
197
|
+
[
|
|
198
|
+
{
|
|
199
|
+
"id": "root",
|
|
200
|
+
"component": "Card",
|
|
201
|
+
"children": [
|
|
202
|
+
"sec"
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"id": "sec",
|
|
207
|
+
"component": "Section",
|
|
208
|
+
"children": [
|
|
209
|
+
"comp"
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"id": "comp",
|
|
214
|
+
"component": "Stat",
|
|
215
|
+
"band": true,
|
|
216
|
+
"bleed": true,
|
|
217
|
+
"label": "Sessions",
|
|
218
|
+
"value": "84,232",
|
|
219
|
+
"change": "+57%",
|
|
220
|
+
"trend": "up",
|
|
221
|
+
"children": [
|
|
222
|
+
"chart"
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"id": "chart",
|
|
227
|
+
"component": "Chart",
|
|
228
|
+
"slot": "chart",
|
|
229
|
+
"type": "area",
|
|
230
|
+
"x": "d",
|
|
231
|
+
"y": "v",
|
|
232
|
+
"noGrid": true,
|
|
233
|
+
"noValues": true,
|
|
234
|
+
"data": [
|
|
235
|
+
{ "d": "Mon", "v": 12 },
|
|
236
|
+
{ "d": "Tue", "v": 18 },
|
|
237
|
+
{ "d": "Wed", "v": 15 },
|
|
238
|
+
{ "d": "Thu", "v": 27 },
|
|
239
|
+
{ "d": "Fri", "v": 22 },
|
|
240
|
+
{ "d": "Sat", "v": 31 },
|
|
241
|
+
{ "d": "Sun", "v": 29 }
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
]
|
|
153
245
|
keywords:
|
|
154
246
|
- stat
|
|
155
247
|
- stats
|
|
@@ -169,6 +261,7 @@ keywords:
|
|
|
169
261
|
- observability
|
|
170
262
|
- performance
|
|
171
263
|
- scoreboard
|
|
264
|
+
- tile
|
|
172
265
|
synonyms:
|
|
173
266
|
analytics:
|
|
174
267
|
- chart
|
|
@@ -60,8 +60,11 @@
|
|
|
60
60
|
"default": 0
|
|
61
61
|
},
|
|
62
62
|
"range-total": {
|
|
63
|
-
"description": "Total row count across ALL server pages (gh#1754, ADR-0082) — the table-authoritative server-mode data contract. Presence-gated: the attribute's PRESENCE, not its value, is the mode switch (checked via hasAttribute, never the coerced value alone — the same discipline table-footer-ui's own [range-total] ships). Absent (the default) means client mode: today's behavior exactly, [paginate] slices `.data` as always. Present with [paginate] > 0 means server mode: no local slicing (`.data` IS the current page and renders whole, after local search/sort/filter), the internal pager's page count becomes `max(1, ceil(range-total / paginate))` (one calc site, consolidated), and the internal page-reset sites (`data` set, `setFilter`, `clearFilters`) are suppressed so a fetch write-back never fights the pager back to page 0 — page state then moves only via pager interaction, the `footer-page` command, or `setState()`. The existing `page` event (0-based, unchanged) becomes the fetch trigger: the consumer listens for it, fetches that server page, and writes `.data` back. Explicit `range-total=\"0\"` is server-confirmed empty (pager hidden), never conflated with absent. With [paginate] absent/0, [range-total] is inert for this table's own rendering but stays readable by a bound table-footer-ui (REQ-W-006 branch 2) for its count-only label — see the footer-authoritative shape below, which remains lawful and is unaffected by this attribute. Named identically to table-toolbar-ui's and table-footer-ui's own [range-total] (rows, never `pagination-ui[total]`'s pages — the ADR-0063 B5 collision rule); a third instance of one name for one concept.",
|
|
64
|
-
"type":
|
|
63
|
+
"description": "Total row count across ALL server pages (gh#1754, ADR-0082) — the table-authoritative server-mode data contract. Presence-gated: the attribute's PRESENCE, not its value, is the mode switch (checked via hasAttribute, never the coerced value alone — the same discipline table-footer-ui's own [range-total] ships). Absent (the default) means client mode: today's behavior exactly, [paginate] slices `.data` as always. Present with [paginate] > 0 means server mode: no local slicing (`.data` IS the current page and renders whole, after local search/sort/filter), the internal pager's page count becomes `max(1, ceil(range-total / paginate))` (one calc site, consolidated), and the internal page-reset sites (`data` set, `setFilter`, `clearFilters`) are suppressed so a fetch write-back never fights the pager back to page 0 — page state then moves only via pager interaction, the `footer-page` command, or `setState()`. The existing `page` event (0-based, unchanged) becomes the fetch trigger: the consumer listens for it, fetches that server page, and writes `.data` back. Explicit `range-total=\"0\"` is server-confirmed empty (pager hidden), never conflated with absent. With [paginate] absent/0, [range-total] is inert for this table's own rendering but stays readable by a bound table-footer-ui (REQ-W-006 branch 2) for its count-only label — see the footer-authoritative shape below, which remains lawful and is unaffected by this attribute. Named identically to table-toolbar-ui's and table-footer-ui's own [range-total] (rows, never `pagination-ui[total]`'s pages — the ADR-0063 B5 collision rule); a third instance of one name for one concept. Explicit `range-total=\"?\"` (gh#1877, ADR-0082 Amendment) is the OPEN/unproven-total state — cursor/hasMore server paging with no known row count yet: still server mode (still presence-gated), but the internal pager's page count stays exactly one page ahead of wherever the pager currently sits (never a fixed ceil(...) total), keeping `next` enabled indefinitely instead of hiding the pager the way a confirmed `range-total=\"0\"` would. Resolves to the normal finite server-mode behavior the instant a real number replaces the \"?\" — every reader downstream (table-toolbar-ui, table-footer-ui) shares this same distinction. Note: this prop is Number-typed and reflected; \"?\" parses to NaN (`+\"?\"`), and the coerced numeric PROPERTY is what carries the open/unproven signal (non-finite) — every reader checks the property, never `getAttribute(...) === '?'`. The DOM attribute itself keeps the authored \"?\" (gh#1895 — the framework's attribute-reflection leaves a non-finite Number value unwritten rather than stringifying it back as the literal \"NaN\").",
|
|
64
|
+
"type": [
|
|
65
|
+
"number",
|
|
66
|
+
"string"
|
|
67
|
+
],
|
|
65
68
|
"default": 0
|
|
66
69
|
},
|
|
67
70
|
"raw": {
|
|
@@ -41,6 +41,16 @@
|
|
|
41
41
|
* conflated with absent. With [paginate] absent/0, [range-total] is
|
|
42
42
|
* inert for this table's own rendering but stays readable by a
|
|
43
43
|
* bound table-footer-ui for its count-only label.
|
|
44
|
+
* range-total="?" — gh#1877/ADR-0082 Amendment (2026-08-22) — the OPEN/
|
|
45
|
+
* unproven-total state: server mode with no known total yet
|
|
46
|
+
* (cursor/hasMore paging). Still presence-gated into server
|
|
47
|
+
* mode like any other [range-total] value; #pageCount stays
|
|
48
|
+
* one page ahead of the current page (never a fixed number)
|
|
49
|
+
* so the pager's `next` stays enabled indefinitely, and
|
|
50
|
+
* pagination visibility no longer requires a positive
|
|
51
|
+
* #safeRangeTotal() while open. Resolves to a normal finite
|
|
52
|
+
* server-mode table the moment a real range-total replaces
|
|
53
|
+
* the "?".
|
|
44
54
|
* loading — show loading overlay
|
|
45
55
|
* search — global search filter string
|
|
46
56
|
*
|
|
@@ -326,6 +336,20 @@ export class UITable extends UIElement {
|
|
|
326
336
|
// reset-suppression call sites for why that widened scope is safe).
|
|
327
337
|
get #serverMode() { return this.hasAttribute('range-total'); }
|
|
328
338
|
|
|
339
|
+
// gh#1877/ADR-0082 Amendment — the open/unproven-total state. NOT keyed
|
|
340
|
+
// on the raw attribute string: `rangeTotal` is a Number-typed reflected
|
|
341
|
+
// prop (installProps in core/element.js), so setting range-total="?" is
|
|
342
|
+
// parsed via `+"?"` → NaN. gh#1895 — reflect() (core/element.js) leaves
|
|
343
|
+
// a non-finite Number value unwritten instead of stringifying it back
|
|
344
|
+
// onto the attribute, so by the time #draw()/render() runs,
|
|
345
|
+
// `getAttribute('range-total')` still reads the authored "?". The
|
|
346
|
+
// coerced PROPERTY still holds NaN stably, though (Object.is(NaN, NaN)
|
|
347
|
+
// is true, so re-parsing the unchanged attribute never re-triggers a
|
|
348
|
+
// write either) — that's what #pageCount/showPagination below need to
|
|
349
|
+
// tell "no total yet" apart from "confirmed empty" (range-total="0", a
|
|
350
|
+
// finite 0).
|
|
351
|
+
get #rangeOpen() { return this.#serverMode && !Number.isFinite(Number(this.rangeTotal)); }
|
|
352
|
+
|
|
329
353
|
// gh#1754/ADR-0082 REQ-D-003 (CodeRabbit, PR #1828) — a raw
|
|
330
354
|
// `Number(x) || 0` accepts negative and non-finite values verbatim (both
|
|
331
355
|
// are truthy), which can drive #pageCount/the footer's own range text
|
|
@@ -933,6 +957,13 @@ export class UITable extends UIElement {
|
|
|
933
957
|
// derived, not the loaded page's own length (the exact dual-math drift
|
|
934
958
|
// gh#1754's evidence quoted — this is now the ONE calc site;
|
|
935
959
|
// #renderPagination reads this getter instead of recomputing).
|
|
960
|
+
// gh#1877/ADR-0082 Amendment — open/unproven total: no real total to
|
|
961
|
+
// derive from, so pageCount stays exactly one page ahead of wherever
|
|
962
|
+
// the pager currently is. That keeps `next` enabled indefinitely
|
|
963
|
+
// (never a false "last page") without inventing a fake total; the
|
|
964
|
+
// moment range-total resolves to a real number, this branch stops
|
|
965
|
+
// firing and the real ceil(...) math above takes over.
|
|
966
|
+
if (this.#rangeOpen) return this.#page + 2;
|
|
936
967
|
if (this.#serverMode) return Math.max(1, Math.ceil(this.#safeRangeTotal() / this.paginate));
|
|
937
968
|
// pageCount should reflect filtered data, not raw data
|
|
938
969
|
const filteredCount = this.#getProcessedIndices().length;
|
|
@@ -1054,8 +1085,12 @@ export class UITable extends UIElement {
|
|
|
1054
1085
|
// `data.length > 0` term: an in-flight fetch (momentarily empty `.data`)
|
|
1055
1086
|
// must not flicker the pager away, so visibility gates on `range-total`
|
|
1056
1087
|
// instead.
|
|
1088
|
+
// gh#1877/ADR-0082 Amendment — an open/unproven total (REQ-D-003
|
|
1089
|
+
// widened) always shows the pager while server mode is active: there's
|
|
1090
|
+
// no confirmed-zero signal to gate on yet, and hiding it would read as
|
|
1091
|
+
// "no more rows" when the real answer is "unknown, keep going".
|
|
1057
1092
|
const showPagination = this.paginate > 0 && !this.noPager
|
|
1058
|
-
&& (this.#serverMode ? this.#safeRangeTotal() > 0 : this.#data.length > 0);
|
|
1093
|
+
&& (this.#serverMode ? (this.#rangeOpen || this.#safeRangeTotal() > 0) : this.#data.length > 0);
|
|
1059
1094
|
let footer = this.querySelector(':scope > [data-footer]');
|
|
1060
1095
|
|
|
1061
1096
|
if (showPagination) {
|
|
@@ -104,8 +104,8 @@ export class UITable extends UIElement {
|
|
|
104
104
|
noPager: boolean;
|
|
105
105
|
/** Rows per page. 0 = show all rows without pagination. When > 0, renders the internal pagination bar below the table — UNLESS [no-pager] is also set (gh#1807, ADR-0080), which keeps [paginate]'s slicing/page-state/`page`-event/`footer-page`-listener behavior but suppresses only the bar itself (the anti-doubled-pager path for a table-footer-ui composition). When [range-total] is also set (gh#1754, ADR-0082), [paginate] becomes purely presentational — it supplies only the page-size for pager math; no local slicing runs. */
|
|
106
106
|
paginate: number;
|
|
107
|
-
/** Total row count across ALL server pages (gh#1754, ADR-0082) — the table-authoritative server-mode data contract. Presence-gated: the attribute's PRESENCE, not its value, is the mode switch (checked via hasAttribute, never the coerced value alone — the same discipline table-footer-ui's own [range-total] ships). Absent (the default) means client mode: today's behavior exactly, [paginate] slices `.data` as always. Present with [paginate] > 0 means server mode: no local slicing (`.data` IS the current page and renders whole, after local search/sort/filter), the internal pager's page count becomes `max(1, ceil(range-total / paginate))` (one calc site, consolidated), and the internal page-reset sites (`data` set, `setFilter`, `clearFilters`) are suppressed so a fetch write-back never fights the pager back to page 0 — page state then moves only via pager interaction, the `footer-page` command, or `setState()`. The existing `page` event (0-based, unchanged) becomes the fetch trigger: the consumer listens for it, fetches that server page, and writes `.data` back. Explicit `range-total="0"` is server-confirmed empty (pager hidden), never conflated with absent. With [paginate] absent/0, [range-total] is inert for this table's own rendering but stays readable by a bound table-footer-ui (REQ-W-006 branch 2) for its count-only label — see the footer-authoritative shape below, which remains lawful and is unaffected by this attribute. Named identically to table-toolbar-ui's and table-footer-ui's own [range-total] (rows, never `pagination-ui[total]`'s pages — the ADR-0063 B5 collision rule); a third instance of one name for one concept. */
|
|
108
|
-
rangeTotal: number;
|
|
107
|
+
/** Total row count across ALL server pages (gh#1754, ADR-0082) — the table-authoritative server-mode data contract. Presence-gated: the attribute's PRESENCE, not its value, is the mode switch (checked via hasAttribute, never the coerced value alone — the same discipline table-footer-ui's own [range-total] ships). Absent (the default) means client mode: today's behavior exactly, [paginate] slices `.data` as always. Present with [paginate] > 0 means server mode: no local slicing (`.data` IS the current page and renders whole, after local search/sort/filter), the internal pager's page count becomes `max(1, ceil(range-total / paginate))` (one calc site, consolidated), and the internal page-reset sites (`data` set, `setFilter`, `clearFilters`) are suppressed so a fetch write-back never fights the pager back to page 0 — page state then moves only via pager interaction, the `footer-page` command, or `setState()`. The existing `page` event (0-based, unchanged) becomes the fetch trigger: the consumer listens for it, fetches that server page, and writes `.data` back. Explicit `range-total="0"` is server-confirmed empty (pager hidden), never conflated with absent. With [paginate] absent/0, [range-total] is inert for this table's own rendering but stays readable by a bound table-footer-ui (REQ-W-006 branch 2) for its count-only label — see the footer-authoritative shape below, which remains lawful and is unaffected by this attribute. Named identically to table-toolbar-ui's and table-footer-ui's own [range-total] (rows, never `pagination-ui[total]`'s pages — the ADR-0063 B5 collision rule); a third instance of one name for one concept. Explicit `range-total="?"` (gh#1877, ADR-0082 Amendment) is the OPEN/unproven-total state — cursor/hasMore server paging with no known row count yet: still server mode (still presence-gated), but the internal pager's page count stays exactly one page ahead of wherever the pager currently sits (never a fixed ceil(...) total), keeping `next` enabled indefinitely instead of hiding the pager the way a confirmed `range-total="0"` would. Resolves to the normal finite server-mode behavior the instant a real number replaces the "?" — every reader downstream (table-toolbar-ui, table-footer-ui) shares this same distinction. Note: this prop is Number-typed and reflected; "?" parses to NaN (`+"?"`), and the coerced numeric PROPERTY is what carries the open/unproven signal (non-finite) — every reader checks the property, never `getAttribute(...) === '?'`. The DOM attribute itself keeps the authored "?" (gh#1895 — the framework's attribute-reflection leaves a non-finite Number value unwritten rather than stringifying it back as the literal "NaN"). */
|
|
108
|
+
rangeTotal: number | string;
|
|
109
109
|
/** Visual-only passthrough — applies `<table-ui>`'s chrome reset (background / border / border-radius all transparent) AND short-circuits the data lifecycle entirely. The consumer owns the body shape: no header injection, no row reconciliation from `.data`, no empty-state / loading overlays, no aggregation or pagination footers. Use raw when embedding `<table-ui>` inside surfaces that supply their own chrome (e.g. `<card-ui><section bleed>`), or when wrapping a consumer-authored native `<table>` for design-token styling without the framework's data semantics. Note: a `<table-ui>` placed directly inside `<card-ui><section bleed>` drops the table's visual chrome automatically (card.css, gh#796) even without `raw` — `raw` is still required to also short-circuit the data lifecycle. Pre-v0.6.33 (FB-53 §2) `raw` was visual-only and the data lifecycle still ran — wrapping a `.data`-unset native table produced a phantom "No data" overlay. v0.6.33+ matches the documented contract. */
|
|
110
110
|
raw: boolean;
|
|
111
111
|
/** Global search/filter string. Filters visible rows across all columns using case-insensitive substring matching. Does NOT reset the current page (verified against source at the gh#1754/ADR-0082 build — unlike setFilter()/clearFilters(), no internal page reset ever ran for a search change; this description previously claimed "resets to page 1 on change," which the source never did). Aligning the doc to observed behavior here, not a behavior change — a page reset on search, if wanted, is a separate client-mode ticket. */
|
|
@@ -98,7 +98,23 @@ props:
|
|
|
98
98
|
to table-toolbar-ui's and table-footer-ui's own [range-total]
|
|
99
99
|
(rows, never `pagination-ui[total]`'s pages — the ADR-0063 B5
|
|
100
100
|
collision rule); a third instance of one name for one concept.
|
|
101
|
-
|
|
101
|
+
Explicit `range-total="?"` (gh#1877, ADR-0082 Amendment) is the
|
|
102
|
+
OPEN/unproven-total state — cursor/hasMore server paging with no
|
|
103
|
+
known row count yet: still server mode (still presence-gated), but
|
|
104
|
+
the internal pager's page count stays exactly one page ahead of
|
|
105
|
+
wherever the pager currently sits (never a fixed ceil(...) total),
|
|
106
|
+
keeping `next` enabled indefinitely instead of hiding the pager the
|
|
107
|
+
way a confirmed `range-total="0"` would. Resolves to the normal
|
|
108
|
+
finite server-mode behavior the instant a real number replaces the
|
|
109
|
+
"?" — every reader downstream (table-toolbar-ui, table-footer-ui)
|
|
110
|
+
shares this same distinction. Note: this prop is Number-typed and
|
|
111
|
+
reflected; "?" parses to NaN (`+"?"`), and the coerced numeric
|
|
112
|
+
PROPERTY is what carries the open/unproven signal (non-finite) —
|
|
113
|
+
every reader checks the property, never `getAttribute(...) === '?'`.
|
|
114
|
+
The DOM attribute itself keeps the authored "?" (gh#1895 — the
|
|
115
|
+
framework's attribute-reflection leaves a non-finite Number value
|
|
116
|
+
unwritten rather than stringifying it back as the literal "NaN").
|
|
117
|
+
type: [number, string]
|
|
102
118
|
default: 0
|
|
103
119
|
reflect: true
|
|
104
120
|
filteredCount:
|