@adia-ai/web-components 0.8.45 → 0.8.46

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/components/card/card.css +12 -23
  3. package/components/card/card.yaml +11 -0
  4. package/components/chart/chart.a2ui.json +16 -1
  5. package/components/chart/chart.class.js +611 -41
  6. package/components/chart/chart.css +174 -0
  7. package/components/chart/chart.d.ts +5 -1
  8. package/components/chart/chart.yaml +45 -1
  9. package/components/field/field.css +24 -2
  10. package/components/index.js +1 -0
  11. package/components/input/input.css +7 -0
  12. package/components/table/table.a2ui.json +19 -4
  13. package/components/table/table.class.js +163 -12
  14. package/components/table/table.d.ts +9 -3
  15. package/components/table/table.yaml +109 -8
  16. package/components/table-footer/table-footer.a2ui.json +150 -0
  17. package/components/table-footer/table-footer.class.js +391 -0
  18. package/components/table-footer/table-footer.css +64 -0
  19. package/components/table-footer/table-footer.d.ts +39 -0
  20. package/components/table-footer/table-footer.examples.md +46 -0
  21. package/components/table-footer/table-footer.js +17 -0
  22. package/components/table-footer/table-footer.yaml +219 -0
  23. package/components/table-toolbar/table-toolbar.yaml +17 -6
  24. package/custom-elements.json +138 -4
  25. package/dist/theme-provider.min.js +1 -1
  26. package/dist/web-components.min.css +1 -1
  27. package/dist/web-components.min.js +87 -87
  28. package/dist/web-components.sheet.js +1 -1
  29. package/package.json +1 -1
  30. package/patterns/chart-in-card/chart-in-card.examples.html +36 -9
  31. package/patterns/new-enrollments/new-enrollments.examples.html +140 -0
  32. package/patterns/new-enrollments/new-enrollments.html +54 -0
  33. package/patterns/table-in-card/table-in-card.examples.html +168 -0
  34. package/patterns/table-in-card/table-in-card.examples.js +139 -0
  35. package/patterns/table-in-card/table-in-card.html +86 -0
  36. package/styles/components.css +1 -0
@@ -15,6 +15,17 @@
15
15
  --chart-fg: var(--md-sys-color-neutral-on-surface);
16
16
  --chart-bar: var(--md-sys-color-primary);
17
17
  --chart-bar-hover: var(--a-primary-bg-hover);
18
+ /* REQ-F-015/016 — a provisional bar's soft fill, paired with
19
+ `--chart-bar`'s primary role the same way calendar-grid-ui pairs
20
+ `--md-sys-color-primary` with `--md-sys-color-primary-container`
21
+ for its own soft/tonal fill. Flat across every series/color variant
22
+ — a judgment call named here, not left silent: the provisional
23
+ state already discards per-series hue for its OUTLINE color
24
+ (`--chart-bar` above, not each series' own stroke), so a single
25
+ tonal fill costs nothing further and keeps "provisional" reading as
26
+ one consistent state regardless of which series or chart `color` it
27
+ belongs to. */
28
+ --chart-provisional-fill: var(--md-sys-color-primary-container);
18
29
  --chart-line: var(--md-sys-color-primary);
19
30
  --chart-dot: var(--md-sys-color-primary);
20
31
  --chart-grid: var(--md-sys-color-neutral-outline-variant);
@@ -45,6 +56,70 @@
45
56
  --chart-segments-gap: 2px;
46
57
  --chart-pie-gap: 1.25px;
47
58
 
59
+ /* ── Charts 2.0 foundations (ADR-0081, gh#1804) ──
60
+ REQ-F-009/010 — chrome-layer clearance + the zero-line→chip-band
61
+ gap, both --a-space-*-aliased (chart.class.js's #dims() reads these
62
+ via getComputedStyle with a matching JS fallback for the
63
+ happy-dom test environment, which doesn't resolve var()/calc()). */
64
+ --chart-chrome-inset: var(--a-space-2);
65
+ --chart-band-gap: var(--a-space-1);
66
+ /* REQ-F-008 — today-marker color, default the primary role. */
67
+ --chart-today: var(--md-sys-color-primary);
68
+ /* REQ-F-005 — chip token contract, all six resolving through the
69
+ existing --a-* / --md-sys-color-* ladders (no new roles minted).
70
+ OPEN-5 (chip background role) — MEASURED, gh#1814 (real resolved
71
+ oklch → sRGB + WCAG contrast math, both schemes, both named
72
+ backdrops: --md-sys-color-neutral-container-low composited over
73
+ the page background for the full-bleed case, line 842 below; a
74
+ plain top-level card's own --md-sys-color-neutral-surface for the
75
+ other). Text legibility (chip-fg on chip-bg) clears AA with a wide
76
+ margin in every combination (>=11:1, gate floor 4.5:1) — this
77
+ repo's own verify:contrast discipline is fully satisfied regardless
78
+ of which of these two tokens is used, since both sit in the same
79
+ tight, deliberately-subtle neutral "surface elevation" band.
80
+ Non-text surface DISTINCTNESS is the real, disclosed limitation:
81
+ no existing --a-* / --md-sys-color-* neutral role reaches the
82
+ informal 3:1 UI-boundary heuristic against either backdrop, in
83
+ either scheme — swapping among them is a matter of degree, not a
84
+ pass/fail token pick, and minting a new role to clear 3:1 would
85
+ violate this SPEC's own no-new-roles non-goal. The originally
86
+ shipped --md-sys-color-neutral-surface-bright measured ~1.0-1.1:1
87
+ against both backdrops in both schemes (barely perceptible — a
88
+ real gap, not the "distinct pill" its own prior comment claimed
89
+ without having actually rendered it). --md-sys-color-neutral-
90
+ container-high measures ~1.24-1.40:1 instead (a real, screenshot-
91
+ confirmed improvement — the chip's rounded boundary reads clearly
92
+ in both schemes and against both backdrops) while keeping text
93
+ legibility comfortably >=11:1 — the better of the two available
94
+ options, not a 3:1-clearing fix. Full numbers + screenshots in the
95
+ gh#1814 Findings. */
96
+ --chart-chip-bg: var(--md-sys-color-neutral-container-high);
97
+ --chart-chip-fg: var(--md-sys-color-neutral-on-surface);
98
+ --chart-chip-radius: var(--a-radius-sm);
99
+ --chart-chip-pad-x: var(--a-space-1-5);
100
+ --chart-chip-pad-y: var(--a-space-1);
101
+ --chart-chip-font-size: var(--a-ui-tiny);
102
+ /* REQ-F-013/OPEN-4 — min-plot-height degradation floors (chip mode
103
+ only, see #dims()'s own doc comment for why `outside` mode never
104
+ applies these). MEASURED breakpoints, not spacing values (OPEN-4's
105
+ resolution posture distinguishes the two) — but expressed through
106
+ the SAME --a-density-scaled-rem mechanism --a-space-* itself is
107
+ built from (space.css), rather than a bare px literal, so a scoped
108
+ [density] host still rescales the floor consistently with every
109
+ other foundations token (REQ-F-014) even though there's no single
110
+ NAMED --a-space-* rung at exactly 180/120px.
111
+ OPEN-4 RESOLVED (gh#1814): a real-browser measurement sweep across
112
+ all three ratio buckets confirmed both values unchanged AND
113
+ confirmed the model itself — height-only, never per-bucket (the
114
+ chip/gridline vertical geometry these floors gate doesn't depend on
115
+ plot width). Full rationale in the SPEC's REQ-F-013 resolution
116
+ note. That same sweep found and fixed an unrelated real defect: the
117
+ domain-min/-max y-chip's own edge clamp could overlap its unclamped
118
+ neighbor at ~155-220px with 7 nice-number ticks — see
119
+ `#renderYChips`'s own doc comment. */
120
+ --chart-min-plot-height-chips: calc(var(--a-density) * 11.25rem); /* 180px at d=1 */
121
+ --chart-min-plot-height-grid: calc(var(--a-density) * 7.5rem); /* 120px at d=1 */
122
+
48
123
  /* Transitions */
49
124
  --chart-duration: var(--a-duration-fast);
50
125
  --chart-easing: var(--a-easing);
@@ -211,6 +286,85 @@
211
286
  font-family: var(--chart-font);
212
287
  }
213
288
 
289
+ /* ── Chip labels (REQ-F-004/005/006, ADR-0081) ──
290
+ Rendered by #renderYChips/#renderXChips (chart.class.js) only when
291
+ `data-labels-resolved="chip"` — geometry (position/clamp/thin/corner
292
+ clearance) is computed in JS; this is styling only. `rx`/`ry` are set
293
+ here (not as SVG attributes) because CSS custom properties don't
294
+ resolve inside a bare SVG presentation-attribute string
295
+ (svg-authoring.md §5) — a real CSS declaration is required. */
296
+
297
+ [data-chip] {
298
+ fill: var(--chart-chip-bg);
299
+ rx: var(--chart-chip-radius);
300
+ ry: var(--chart-chip-radius);
301
+ }
302
+
303
+ [data-chip-label] {
304
+ fill: var(--chart-chip-fg);
305
+ font-size: var(--chart-chip-font-size);
306
+ font-family: var(--chart-font);
307
+ }
308
+
309
+ /* ── Today-marker (REQ-F-008, ADR-0081) ──
310
+ Anatomy: a baseline dot + a short band tick, never a full-height rule
311
+ — geometry (position, direction) is computed in
312
+ #todayMarkerVertical/#todayMarkerHorizontal (chart.class.js); this is
313
+ styling only. */
314
+
315
+ [data-today-dot] {
316
+ fill: var(--chart-today);
317
+ stroke: var(--chart-dot-stroke);
318
+ stroke-width: 1.5;
319
+ }
320
+
321
+ [data-today-tick] {
322
+ stroke: var(--chart-today);
323
+ stroke-width: 2;
324
+ stroke-linecap: round;
325
+ }
326
+
327
+ /* ── Provisional datum (REQ-F-015/016, ADR-0081 §3) ──
328
+ Per-datum `.provisional` flag — bar family renders a soft tonal fill +
329
+ dashed outline (never the ordinary solid fill) instead of the
330
+ ordinary filled bar; scatter's dot renders a hollow ring the same way
331
+ (no fill at all — a dot has no area to give hover feedback the way a
332
+ bar's own body does, so it stays hollow). Multi-series types
333
+ (stacked-bar/grouped-bar/composed) set their per-series color via an
334
+ inline `style="fill:var(--color-{key},...)"` (#seriesFill(), fill
335
+ only) — the flat `--chart-bar`/`--chart-provisional-fill`/`--chart-dot`
336
+ tokens here are this build's deliberate simplification for the
337
+ provisional OUTLINE/FILL color rather than re-deriving each series'
338
+ own color, named as a judgment call in the build's Findings rather
339
+ than left silent. Multi-series bars never carry `data-bar` (only
340
+ `data-slice` — see #seriesFill), so the selector below also targets
341
+ `[data-slice]` bar shapes directly; `!important` on `fill` is
342
+ required to beat that same inline style (expressing this any other
343
+ way needs the render path to skip #seriesFill for provisional rows,
344
+ a larger change than this selector fix warrants).
345
+ Giving the fill a real, visible value (rather than `none`) is
346
+ deliberate, not decorative: a hollow bar has nothing for a `:hover`
347
+ rule to darken/lighten, so hovering a provisional bar previously gave
348
+ no feedback at all. No separate `[data-provisional]:hover` rule is
349
+ needed — `path[data-slice]:hover`/`rect[data-slice]:hover`/
350
+ `[data-bar]:hover`'s existing `opacity: 0.92` already match these
351
+ same elements; they simply had nothing visible to dim before. */
352
+
353
+ [data-bar][data-provisional],
354
+ path[data-slice][data-provisional],
355
+ rect[data-slice][data-provisional] {
356
+ fill: var(--chart-provisional-fill) !important;
357
+ stroke: var(--chart-bar);
358
+ stroke-width: 1.5;
359
+ stroke-dasharray: 4 3;
360
+ }
361
+
362
+ [data-dot][data-provisional] {
363
+ fill: none;
364
+ stroke: var(--chart-dot);
365
+ stroke-width: 1.5;
366
+ }
367
+
214
368
  /* ── Bar ── */
215
369
 
216
370
  [data-bar] {
@@ -232,6 +386,26 @@
232
386
  stroke-linecap: round;
233
387
  }
234
388
 
389
+ /* ── Line/area provisional-span overlay (REQ-F-015, ADR-0081 §3) ──
390
+ #lineProvisionalOverlay (chart.class.js) emits these two elements
391
+ only when at least one datum in the series is provisional; a fixture
392
+ with no provisional data never emits them at all. */
393
+
394
+ [data-line-provisional] {
395
+ stroke: var(--chart-line);
396
+ stroke-width: var(--chart-line-width);
397
+ fill: none;
398
+ stroke-linejoin: round;
399
+ stroke-linecap: round;
400
+ stroke-dasharray: 5 4;
401
+ }
402
+
403
+ [data-area-provisional-fade] {
404
+ fill: var(--md-sys-color-neutral-surface);
405
+ opacity: 0.55;
406
+ pointer-events: none;
407
+ }
408
+
235
409
  /* ── Area fill ──
236
410
  Enabled by default for line / multi-line charts. Fades from the top
237
411
  (most visible, tinted by --chart-area-opacity) to transparent at the
@@ -24,12 +24,14 @@ export class UIChart extends UIElement {
24
24
  color: 'accent' | 'success' | 'warning' | 'danger' | 'info';
25
25
  /** Bar corner radius in px (null = let CSS tokens decide) */
26
26
  cornerRadius: number | null;
27
- /** JS property (set programmatically — `el.data = [...]`). An array of plain objects; each object's keys are named by the `x` and `y` attributes — e.g. `<chart-ui x="month" y="revenue">` consumes `[{month:'Jan', revenue:3200}, {month:'Feb', revenue:4100}]`. The Chart.js `{labels, datasets}` envelope is NOT chart-ui's API — passing it (or any non-array value) renders an empty chart. May also be supplied declaratively as a JSON-array `data="[…]"` attribute, hydrated once at connect. Custom accessor on the element class, not a reflected attribute. */
27
+ /** JS property (set programmatically — `el.data = [...]`). An array of plain objects; each object's keys are named by the `x` and `y` attributes — e.g. `<chart-ui x="month" y="revenue">` consumes `[{month:'Jan', revenue:3200}, {month:'Feb', revenue:4100}]`. The Chart.js `{labels, datasets}` envelope is NOT chart-ui's API — passing it (or any non-array value) renders an empty chart. May also be supplied declaratively as a JSON-array `data="[…]"` attribute, hydrated once at connect. Custom accessor on the element class, not a reflected attribute. A datum object carrying a truthy `provisional` key (ADR-0081) renders that period's type-specific incomplete-period treatment (hollow/dashed bar, dashed line + faded area, hollow ring dot) on cartesian types; a no-op on radial/part-to-whole types and sparkline (gh#1690's N/A list). Provisional state is per-datum data-shape, not a chart-ui attribute — see ADR-0081 §3. */
28
28
  data: string;
29
29
  /** Marker (dot) visibility for line and multi-line series. `all` draws a circle[data-dot] on every datum (default — matches pre-gh#561 behavior). `none` hides all dots so a dense series reads as a clean line. `hover` shows only the currently-hovered datum's dot. `last` shows only the terminal point per series (sparkline convention). Tooltip hit targets ([data-hit]) are a separate always-rendered layer — every mode keeps hover/click working along the whole line. */
30
30
  dots: 'all' | 'none' | 'hover' | 'last';
31
31
  /** Number-format mode applied to axis labels + value overlays + donut total + gauge value + treemap value + funnel value + internal tooltip. `abbr` is the legacy 1.2K / 3M format; `decimal` fixes 2 decimals; `currency` prefixes via `--chart-currency-prefix` token (default "$"); `percent` multiplies × 100 and adds a % suffix. */
32
32
  format: 'abbr' | 'decimal' | 'currency' | 'percent';
33
+ /** Chart 2.0 axis-label mode (ADR-0081). Unset (default, "") always resolves to `outside` — today's pre-2.0 outside-the-plot axis labels, byte-for-byte unchanged. Set explicitly to `chip` to opt into the pill-treated overlay labels inset within the plot box (never hanging outside it) — the resolved value reflects onto the host as `data-labels-resolved="chip"|"outside"`. Deliberately explicit-opt-in rather than auto-detected from full-bleed ancestry (`section[bleed]`/ `card-ui[padding="none"]`): an earlier draft auto-activated chip mode on any full-bleed composition, which silently changed the rendering of every ALREADY-SHIPPED full-bleed chart the moment it shipped — this build's own 60-fixture Charts visual-eval floor caught it (2 `comp-chart-in-card-n-*` regressions). `chart-in-card` compositions opt in explicitly once their own examples are updated (gh#1805). */
34
+ labels: '' | 'chip' | 'outside';
33
35
  /** Show a skeleton placeholder instead of the chart body. Set to true while data is being fetched; clear once data arrives. Preserves the element's aspect-ratio dimensions so the skeleton occupies the same space the chart will fill. Parity with stat-ui[loading] and table-ui[loading] (§FB-12, v0.6.27). */
34
36
  loading: boolean;
35
37
  /** When true, suppress the overlaid average line. Canonical spelling (ADR-0063, gh#1563) — the deprecated `hideAverage` dual-read shim was cut in 0.8.43 (gh#1617). */
@@ -46,6 +48,8 @@ export class UIChart extends UIElement {
46
48
  size: 'sm' | 'md' | 'lg';
47
49
  /** Line smoothing factor */
48
50
  smooth: number;
51
+ /** Names the datum whose x-axis value marks "today" (ADR-0081). Unset (default, "") renders no marker — additive. Compared against each datum's x-key value via a loose string comparison (`String(datum[x]) === String(today)`), so category axes ("month", "region") work the same as literal date strings; chart-ui never auto-derives today from the clock. Renders a baseline dot + a short band tick (REQ-F-008) on cartesian types (bar, line, area, scatter, multi-line, stacked-bar, grouped-bar, composed); a no-op on every radial/part-to-whole type and on sparkline (gh#1690's N/A list). Fallback (not the primary mechanism): a datum already carrying a truthy `today` key is treated as an equivalent marker even with this attribute unset. */
52
+ today: string;
49
53
  /** Data key for x-axis (category) values */
50
54
  x: string;
51
55
  /** Y-axis key(s), comma-separated for multi-series */
@@ -115,6 +115,45 @@ props:
115
115
  - "1:1"
116
116
  - "2:3"
117
117
  reflect: true
118
+ labels:
119
+ description: >-
120
+ Chart 2.0 axis-label mode (ADR-0081). Unset (default, "") always
121
+ resolves to `outside` — today's pre-2.0 outside-the-plot axis labels,
122
+ byte-for-byte unchanged. Set explicitly to `chip` to opt into the
123
+ pill-treated overlay labels inset within the plot box (never hanging
124
+ outside it) — the resolved value reflects onto the host as
125
+ `data-labels-resolved="chip"|"outside"`. Deliberately explicit-opt-in
126
+ rather than auto-detected from full-bleed ancestry (`section[bleed]`/
127
+ `card-ui[padding="none"]`): an earlier draft auto-activated chip mode
128
+ on any full-bleed composition, which silently changed the rendering of
129
+ every ALREADY-SHIPPED full-bleed chart the moment it shipped — this
130
+ build's own 60-fixture Charts visual-eval floor caught it
131
+ (2 `comp-chart-in-card-n-*` regressions). `chart-in-card` compositions
132
+ opt in explicitly once their own examples are updated (gh#1805).
133
+ type: string
134
+ default: ""
135
+ enum:
136
+ - ""
137
+ - chip
138
+ - outside
139
+ reflect: true
140
+ today:
141
+ description: >-
142
+ Names the datum whose x-axis value marks "today" (ADR-0081). Unset
143
+ (default, "") renders no marker — additive. Compared against each
144
+ datum's x-key value via a loose string comparison
145
+ (`String(datum[x]) === String(today)`), so category axes ("month",
146
+ "region") work the same as literal date strings; chart-ui never
147
+ auto-derives today from the clock. Renders a baseline dot + a short
148
+ band tick (REQ-F-008) on cartesian types (bar, line, area, scatter,
149
+ multi-line, stacked-bar, grouped-bar, composed); a no-op on every
150
+ radial/part-to-whole type and on sparkline (gh#1690's N/A list).
151
+ Fallback (not the primary mechanism): a datum already carrying a
152
+ truthy `today` key is treated as an equivalent marker even with this
153
+ attribute unset.
154
+ type: string
155
+ default: ""
156
+ reflect: true
118
157
  smooth:
119
158
  description: Line smoothing factor
120
159
  type: number
@@ -213,7 +252,12 @@ props:
213
252
  passing it (or any non-array value) renders an empty chart. May also
214
253
  be supplied declaratively as a JSON-array `data="[…]"` attribute,
215
254
  hydrated once at connect. Custom accessor on the element class, not a
216
- reflected attribute.
255
+ reflected attribute. A datum object carrying a truthy `provisional`
256
+ key (ADR-0081) renders that period's type-specific incomplete-period
257
+ treatment (hollow/dashed bar, dashed line + faded area, hollow ring
258
+ dot) on cartesian types; a no-op on radial/part-to-whole types and
259
+ sparkline (gh#1690's N/A list). Provisional state is per-datum
260
+ data-shape, not a chart-ui attribute — see ADR-0081 §3.
217
261
  type: array
218
262
  items:
219
263
  type: object
@@ -77,8 +77,30 @@
77
77
  row-gap: var(--field-gap);
78
78
  }
79
79
 
80
- /* Stacked + (trailing or action) 2-col */
81
- :scope:has(> :is([slot="trailing"], [slot="action"])) {
80
+ /* Stacked + trailing only (no action) the "action" column would
81
+ otherwise still be sized by row 1's trailing content while row 2's
82
+ action cell sits empty, stealing width from `control` (gh#1836).
83
+ `control` spans both columns so it fills the full row instead. */
84
+ :scope:has(> [slot="trailing"]):not(:has(> [slot="action"])) {
85
+ grid-template-columns: minmax(0, 1fr) auto;
86
+ grid-template-areas:
87
+ "label trailing"
88
+ "control control"
89
+ "message message";
90
+ }
91
+ /* Stacked + action only (no trailing) — label spans the full row since
92
+ there's no trailing content contesting it. */
93
+ :scope:has(> [slot="action"]):not(:has(> [slot="trailing"])) {
94
+ grid-template-columns: minmax(0, 1fr) auto;
95
+ grid-template-areas:
96
+ "label label"
97
+ "control action"
98
+ "message message";
99
+ }
100
+ /* Stacked + both trailing and action — today's combined template,
101
+ unchanged: row 2's second column is legitimately contested only by
102
+ `action`, since `trailing` lives in row 1. */
103
+ :scope:has(> [slot="trailing"]):has(> [slot="action"]) {
82
104
  grid-template-columns: minmax(0, 1fr) auto;
83
105
  grid-template-areas:
84
106
  "label trailing"
@@ -55,6 +55,7 @@ export { UIColorInput } from './color-input/color-input.js';
55
55
  export { UINoodles } from './noodles/noodles.js';
56
56
  export { UITable } from './table/table.js';
57
57
  export { UITableToolbar } from './table-toolbar/table-toolbar.js';
58
+ export { UITableFooter } from './table-footer/table-footer.js';
58
59
  export { UITimeline, UITimelineItem } from './timeline/timeline.js';
59
60
  export { UIStepper, UIStepperItem } from './stepper/stepper.js';
60
61
  export { UISwiper } from './swiper/swiper.js';
@@ -92,6 +92,13 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
92
92
  min-width: var(--input-label-min-width);
93
93
  color: var(--input-affix-fg);
94
94
  font-size: var(--input-font-size);
95
+ /* Match [slot="field"]'s line-height (§113) — without it the label
96
+ falls back to the UA's normal line-height for its font/size, which
97
+ differs from --a-font-leading-normal. align-items: center on the
98
+ field centers each flex item's own line box, so a mismatched
99
+ line-height shifts the label's glyph baseline off the value/
100
+ placeholder text's baseline even though both boxes are centered. */
101
+ line-height: var(--a-font-leading-normal);
95
102
  user-select: none;
96
103
  pointer-events: none;
97
104
  }
@@ -34,6 +34,11 @@
34
34
  "type": "boolean",
35
35
  "default": false
36
36
  },
37
+ "filteredCount": {
38
+ "description": "Read-only. The row count AFTER search + column filters apply but BEFORE pagination slices it (gh#1807, ADR-0080, REQ-W-006) — a plain JS getter, no setter, never a reflected attribute. table-footer-ui's client-mode range-total derivation reads this directly; a raw `.data.length` read (the toolbar's own `count` fallback precedent) would double-count a filtered-out row. In server mode ([range-total] set, gh#1754, ADR-0082), filteredCount stays page-scoped — the loaded page's rows after local filters, NOT the server total across all pages; [range-total] is the server total, filteredCount never is (REQ-D-006).",
39
+ "type": "number",
40
+ "readOnly": true
41
+ },
37
42
  "frameless": {
38
43
  "description": "Drop the table's outer 1px perimeter and nothing else (gh#511) — row dividers are untouched (the perimeter is split onto its own --table-perimeter token, defaulting to --table-border). For card-composed tables: the card owns the frame, so the table's square perimeter otherwise renders a doubled edge (flush bleed) or a stray inner box (with content above). Standalone tables keep their chrome by default. Theming path without the attribute: set --table-perimeter to transparent per instance. Note: `<card-ui><section bleed>` already drops a direct-child table-ui's chrome automatically (card.css, gh#796) — `frameless`/`raw` on the instance still applies and is honored, but is no longer required just to avoid the doubled-edge look in that composition.",
39
44
  "type": "boolean",
@@ -44,8 +49,18 @@
44
49
  "type": "boolean",
45
50
  "default": false
46
51
  },
52
+ "no-pager": {
53
+ "description": "Hide the internal pagination bar while leaving [paginate] slicing, page state, the `page` event, and the `footer-page` command listener all intact (gh#1807, ADR-0080). The anti-doubled-pager mechanism for a table-footer-ui composition, client- or server-mode alike (gh#1754, ADR-0082): without it, a bound footer's own composed pager and the table's own internal one would both render for any non-empty paginated table. Explicit author intent — never suppressed automatically just because a footer happens to be bound.",
54
+ "type": "boolean",
55
+ "default": false
56
+ },
47
57
  "paginate": {
48
- "description": "Rows per page. 0 = show all rows without pagination. When > 0, renders a pagination bar below the table.",
58
+ "description": "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.",
59
+ "type": "number",
60
+ "default": 0
61
+ },
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.",
49
64
  "type": "number",
50
65
  "default": 0
51
66
  },
@@ -55,7 +70,7 @@
55
70
  "default": false
56
71
  },
57
72
  "search": {
58
- "description": "Global search/filter string. Filters visible rows across all columns using case-insensitive substring matching. Resets to page 1 on change.",
73
+ "description": "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.",
59
74
  "type": "string",
60
75
  "default": ""
61
76
  },
@@ -141,10 +156,10 @@
141
156
  }
142
157
  },
143
158
  "page": {
144
- "description": "Fired when the user navigates to a different page.",
159
+ "description": "Fired when the table's own internal page state changes — an internal pager click, or a `footer-page` command applied (REQ-W-002). This is a RESYNC notification a bound `<table-footer-ui>` listens to for its own `page` attribute (REQ-W-004) — it is NOT the public server-mode fetch trigger a consumer should listen to; use `<table-footer-ui>`'s own `page-change` event for that (1-based, gh#1754/ADR-0082 REQ-W-003). Listening to both risks a duplicate fetch or an index-base mismatch.",
145
160
  "detail": {
146
161
  "page": {
147
- "description": "New active page index (1-based).",
162
+ "description": "New active page index (0-based — gh#1808; the `footer-page` command and the `page` attribute on <table-footer-ui> are 1-based, this event's own detail is not).",
148
163
  "type": "number"
149
164
  }
150
165
  }