@adia-ai/web-components 0.8.44 → 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 (69) hide show
  1. package/CHANGELOG.md +33 -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/context-menu/context-menu.a2ui.json +8 -3
  10. package/components/context-menu/context-menu.class.js +46 -5
  11. package/components/context-menu/context-menu.d.ts +6 -3
  12. package/components/context-menu/context-menu.examples.md +2 -2
  13. package/components/context-menu/context-menu.yaml +22 -5
  14. package/components/field/field.css +24 -2
  15. package/components/index.js +1 -0
  16. package/components/input/input.css +7 -0
  17. package/components/nav/nav.a2ui.json +2 -2
  18. package/components/nav/nav.css +1 -1
  19. package/components/nav/nav.d.ts +1 -1
  20. package/components/nav/nav.yaml +14 -3
  21. package/components/nav-group/nav-group.css +37 -3
  22. package/components/pagination/pagination.class.js +52 -22
  23. package/components/search/search.class.js +39 -5
  24. package/components/select/select.a2ui.json +5 -0
  25. package/components/select/select.class.js +20 -0
  26. package/components/select/select.css +25 -0
  27. package/components/select/select.d.ts +2 -0
  28. package/components/select/select.yaml +12 -0
  29. package/components/table/cell-types.js +9 -0
  30. package/components/table/table.a2ui.json +19 -4
  31. package/components/table/table.class.js +410 -45
  32. package/components/table/table.css +7 -4
  33. package/components/table/table.d.ts +9 -3
  34. package/components/table/table.yaml +115 -9
  35. package/components/table-footer/table-footer.a2ui.json +150 -0
  36. package/components/table-footer/table-footer.class.js +391 -0
  37. package/components/table-footer/table-footer.css +64 -0
  38. package/components/table-footer/table-footer.d.ts +39 -0
  39. package/components/table-footer/table-footer.examples.md +46 -0
  40. package/components/table-footer/table-footer.js +17 -0
  41. package/components/table-footer/table-footer.yaml +219 -0
  42. package/components/table-toolbar/table-toolbar.a2ui.json +15 -0
  43. package/components/table-toolbar/table-toolbar.class.js +61 -17
  44. package/components/table-toolbar/table-toolbar.css +34 -0
  45. package/components/table-toolbar/table-toolbar.d.ts +10 -0
  46. package/components/table-toolbar/table-toolbar.yaml +58 -15
  47. package/core/data-stream.js +37 -2
  48. package/core/index.d.ts +1 -0
  49. package/core/index.js +1 -0
  50. package/core/provider.d.ts +9 -13
  51. package/core/provider.js +9 -113
  52. package/core/store.d.ts +46 -0
  53. package/core/store.js +89 -0
  54. package/custom-elements.json +192 -8
  55. package/dist/host.min.css +1 -1
  56. package/dist/host.sheet.js +1 -1
  57. package/dist/theme-provider.min.js +1 -1
  58. package/dist/web-components.min.css +1 -1
  59. package/dist/web-components.min.js +96 -96
  60. package/dist/web-components.sheet.js +1 -1
  61. package/package.json +1 -1
  62. package/patterns/chart-in-card/chart-in-card.examples.html +36 -9
  63. package/patterns/new-enrollments/new-enrollments.examples.html +140 -0
  64. package/patterns/new-enrollments/new-enrollments.html +54 -0
  65. package/patterns/table-in-card/table-in-card.examples.html +168 -0
  66. package/patterns/table-in-card/table-in-card.examples.js +139 -0
  67. package/patterns/table-in-card/table-in-card.html +86 -0
  68. package/styles/api/sizing.css +46 -0
  69. package/styles/components.css +1 -0
@@ -486,10 +486,13 @@
486
486
  /* ═══════ Density ═══════ */
487
487
 
488
488
  /* No local override (ADR-0054 convergence) — --table-py/-px above already
489
- derive from --a-space-* steps, which the ambient global
490
- [density="compact"|"spacious"] attribute already scales via
491
- --a-density. Do not reintroduce a component-local :scope[density=…]
492
- block; that would re-shadow the global grammar (ADR-0053). */
489
+ derive from --a-space-* steps, which the global [density="compact"|
490
+ "spacious"] attribute scales via --a-density on ANY host, root or
491
+ scoped (gh#1771 — api/sizing.css re-declares the --a-space-* formulas
492
+ at the [density] boundary itself, the gh#570 cure; previously this
493
+ comment's "already scales" claim only held for root-level [density]).
494
+ Do not reintroduce a component-local :scope[density=…] block; that
495
+ would re-shadow the global grammar (ADR-0053). */
493
496
 
494
497
  /* ═══════ Pinned cells ═══════ */
495
498
 
@@ -31,7 +31,7 @@ export interface TableFilterChangeEventDetail {
31
31
 
32
32
  export type TableFilterChangeEvent = CustomEvent<TableFilterChangeEventDetail>;
33
33
  export interface TablePageEventDetail {
34
- /** New active page index (1-based). */
34
+ /** 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). */
35
35
  page: number;
36
36
  }
37
37
 
@@ -94,15 +94,21 @@ export class UITable extends UIElement {
94
94
  data: string;
95
95
  /** Enable row expansion */
96
96
  expandable: boolean;
97
+ /** 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). */
98
+ readonly filteredCount: number;
97
99
  /** 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. */
98
100
  frameless: boolean;
99
101
  /** Renders N ghost skeleton rows in place of the body data (count derived from `paginate` if set, else 5). Header + columns stay intact so the table layout is preserved while data fetches. Sets aria-busy="true" on the host. Data updates are deferred until loading is set back to false. */
100
102
  loading: boolean;
101
- /** Rows per page. 0 = show all rows without pagination. When > 0, renders a pagination bar below the table. */
103
+ /** 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. */
104
+ noPager: boolean;
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. */
102
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;
103
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. */
104
110
  raw: boolean;
105
- /** Global search/filter string. Filters visible rows across all columns using case-insensitive substring matching. Resets to page 1 on change. */
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. */
106
112
  search: string;
107
113
  /** Show checkbox column for row selection. Select-all checkbox in header. Shift+click for range select. */
108
114
  selectable: boolean;
@@ -50,10 +50,71 @@ props:
50
50
  default: false
51
51
  reflect: true
52
52
  paginate:
53
- description: Rows per page. 0 = show all rows without pagination. When > 0, renders a pagination bar
54
- below the table.
53
+ description: Rows per page. 0 = show all rows without pagination. When > 0, renders the internal
54
+ pagination bar below the table — UNLESS [no-pager] is also set (gh#1807, ADR-0080), which keeps
55
+ [paginate]'s slicing/page-state/`page`-event/`footer-page`-listener behavior but suppresses only
56
+ the bar itself (the anti-doubled-pager path for a table-footer-ui composition). When [range-total]
57
+ is also set (gh#1754, ADR-0082), [paginate] becomes purely presentational — it supplies only the
58
+ page-size for pager math; no local slicing runs.
55
59
  type: number
56
60
  default: 0
61
+ no-pager:
62
+ description: >-
63
+ Hide the internal pagination bar while leaving [paginate] slicing, page
64
+ state, the `page` event, and the `footer-page` command listener all
65
+ intact (gh#1807, ADR-0080). The anti-doubled-pager mechanism for a
66
+ table-footer-ui composition, client- or server-mode alike (gh#1754,
67
+ ADR-0082): without it, a bound footer's own composed pager and the
68
+ table's own internal one would both render for any non-empty paginated
69
+ table. Explicit author intent — never suppressed automatically just
70
+ because a footer happens to be bound.
71
+ type: boolean
72
+ default: false
73
+ reflect: true
74
+ range-total:
75
+ description: >-
76
+ Total row count across ALL server pages (gh#1754, ADR-0082) — the
77
+ table-authoritative server-mode data contract. Presence-gated: the
78
+ attribute's PRESENCE, not its value, is the mode switch (checked via
79
+ hasAttribute, never the coerced value alone — the same discipline
80
+ table-footer-ui's own [range-total] ships). Absent (the default) means
81
+ client mode: today's behavior exactly, [paginate] slices `.data` as
82
+ always. Present with [paginate] > 0 means server mode: no local
83
+ slicing (`.data` IS the current page and renders whole, after local
84
+ search/sort/filter), the internal pager's page count becomes
85
+ `max(1, ceil(range-total / paginate))` (one calc site, consolidated),
86
+ and the internal page-reset sites (`data` set, `setFilter`,
87
+ `clearFilters`) are suppressed so a fetch write-back never fights the
88
+ pager back to page 0 — page state then moves only via pager
89
+ interaction, the `footer-page` command, or `setState()`. The existing
90
+ `page` event (0-based, unchanged) becomes the fetch trigger: the
91
+ consumer listens for it, fetches that server page, and writes `.data`
92
+ back. Explicit `range-total="0"` is server-confirmed empty (pager
93
+ hidden), never conflated with absent. With [paginate] absent/0,
94
+ [range-total] is inert for this table's own rendering but stays
95
+ readable by a bound table-footer-ui (REQ-W-006 branch 2) for its
96
+ count-only label — see the footer-authoritative shape below, which
97
+ remains lawful and is unaffected by this attribute. Named identically
98
+ to table-toolbar-ui's and table-footer-ui's own [range-total]
99
+ (rows, never `pagination-ui[total]`'s pages — the ADR-0063 B5
100
+ collision rule); a third instance of one name for one concept.
101
+ type: number
102
+ default: 0
103
+ reflect: true
104
+ filteredCount:
105
+ description: >-
106
+ Read-only. The row count AFTER search + column filters apply but
107
+ BEFORE pagination slices it (gh#1807, ADR-0080, REQ-W-006) — a plain
108
+ JS getter, no setter, never a reflected attribute. table-footer-ui's
109
+ client-mode range-total derivation reads this directly; a raw
110
+ `.data.length` read (the toolbar's own `count` fallback precedent)
111
+ would double-count a filtered-out row. In server mode ([range-total]
112
+ set, gh#1754, ADR-0082), filteredCount stays page-scoped — the loaded
113
+ page's rows after local filters, NOT the server total across all
114
+ pages; [range-total] is the server total, filteredCount never is
115
+ (REQ-D-006).
116
+ type: number
117
+ readonly: true # getter-only, no setter — never A2UI-bindable (not `dynamic:`)
57
118
  raw:
58
119
  description: >-
59
120
  Visual-only passthrough — applies `<table-ui>`'s chrome reset
@@ -76,8 +137,13 @@ props:
76
137
  default: false
77
138
  reflect: true
78
139
  search:
79
- description: Global search/filter string. Filters visible rows across all columns using
80
- case-insensitive substring matching. Resets to page 1 on change.
140
+ description: >-
141
+ Global search/filter string. Filters visible rows across all columns using
142
+ case-insensitive substring matching. Does NOT reset the current page (verified against
143
+ source at the gh#1754/ADR-0082 build — unlike setFilter()/clearFilters(), no internal page
144
+ reset ever ran for a search change; this description previously claimed "resets to page 1
145
+ on change," which the source never did). Aligning the doc to observed behavior here, not a
146
+ behavior change — a page reset on search, if wanted, is a separate client-mode ticket.
81
147
  type: string
82
148
  default: ""
83
149
  selectable:
@@ -180,11 +246,21 @@ events:
180
246
  type: object
181
247
  description: "Map of `{ [columnKey]: filterValue }` reflecting active filters."
182
248
  page:
183
- description: Fired when the user navigates to a different page.
249
+ description: >-
250
+ Fired when the table's own internal page state changes — an internal
251
+ pager click, or a `footer-page` command applied (REQ-W-002). This is a
252
+ RESYNC notification a bound `<table-footer-ui>` listens to for its own
253
+ `page` attribute (REQ-W-004) — it is NOT the public server-mode fetch
254
+ trigger a consumer should listen to; use `<table-footer-ui>`'s own
255
+ `page-change` event for that (1-based, gh#1754/ADR-0082 REQ-W-003).
256
+ Listening to both risks a duplicate fetch or an index-base mismatch.
184
257
  detail:
185
258
  page:
186
259
  type: number
187
- description: New active page index (1-based).
260
+ description: >-
261
+ New active page index (0-based — gh#1808; the `footer-page`
262
+ command and the `page` attribute on <table-footer-ui> are
263
+ 1-based, this event's own detail is not).
188
264
  resize:
189
265
  description: Fired when a column is resized via drag.
190
266
  detail:
@@ -302,7 +378,25 @@ a2ui:
302
378
  needs search / filter / sort / columns visibility. Do NOT
303
379
  re-implement those affordances in the card header — the toolbar
304
380
  auto-wires search/filter/sort/columns changes into the bound
305
- table.
381
+ table via `toolbar-*` CustomEvents (gh#1764/#1780, ADR-0079 —
382
+ events-only interaction contract; table-ui listens for
383
+ toolbar-search/toolbar-filter-set/toolbar-filter-clear/
384
+ toolbar-columns-set/toolbar-paginate on itself). Any consumer, not
385
+ only table-toolbar-ui, may dispatch these events directly at a
386
+ table-ui instance.
387
+ - >-
388
+ Pair with <table-footer-ui for="<table-id>"> for the pager +
389
+ "Showing X-Y of N" range label (gh#1807, ADR-0080; gh#1754, ADR-0082)
390
+ — table-ui listens for its `footer-page` command (detail {page},
391
+ 1-based) on itself, applied whenever [paginate] > 0 — client mode
392
+ AND table-authoritative server mode ([range-total] set) alike —
393
+ clamped to the valid page range (server-derived when [range-total]
394
+ is set). Only at [paginate="0"] does the table treat it as a
395
+ documented no-op, in either mode. Set [no-pager] on the table when
396
+ a composition also binds a footer (client- or server-mode), or the
397
+ internal pagination bar and the footer's composed one both render —
398
+ [no-pager] leaves slicing/page-state/the `page` event untouched, it
399
+ only hides the internal bar.
306
400
  - >-
307
401
  Cells truncate single-line by default (v0.6.21 §403
308
402
  truncate-default). Opt out per-table with [wrap] for whole-table
@@ -329,8 +423,20 @@ a2ui:
329
423
  rendered rows and add a pagination bar. [virtual] is NOT a
330
424
  recognized prop and is silently ignored — there is no DOM-level
331
425
  virtual scrolling in table-ui. [paginate] is the supported
332
- performance mechanism. Wire [paginate] with optional [search]
333
- and the `page` event for server-driven pagination.
426
+ performance mechanism. For server-driven pagination (gh#1754,
427
+ ADR-0082), set [paginate="N"] (page size) + [range-total="M"]
428
+ (server total row count) together — [paginate] then becomes purely
429
+ presentational (no local slicing), `.data` holds exactly the
430
+ current server page. The table's own `page` event (0-based) is an
431
+ internal resync notification, not the fetch trigger — a bound
432
+ <table-footer-ui> listens to it to keep its own [page] in sync.
433
+ The PUBLIC fetch trigger is <table-footer-ui>'s own `page-change`
434
+ event (1-based): listen for it, fetch that server page, then write
435
+ the rows back to `.data` and the footer's own [page]. Listening to
436
+ the table's `page` event instead risks a duplicate fetch or an
437
+ index-base mismatch (0-based vs 1-based). [search]/[sort]/column
438
+ filters, when used alongside server paging, still operate on the
439
+ loaded page only — not a server-side query.
334
440
  anti_patterns:
335
441
  - wrong: '<table-ui virtual></table-ui>'
336
442
  why: >-
@@ -0,0 +1,150 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/TableFooter.json",
4
+ "title": "TableFooter",
5
+ "description": "Footer / companion bar for a sibling table-ui (gh#1807, ADR-0080) — a \"Showing X–Y of N\" range label paired with a composed pagination-ui. Derivation-model API: set `page` / `page-size` / `range-total` and the footer derives total pages and the range window itself, so it never drifts out of sync with its own pager. Wired to the target table via a [for] id-ref and CustomEvents only (ADR-0079/ADR-0080) — the footer never writes a property or calls a method on the bound table. Drop below (or inside a card-ui footer alongside) any table-ui to add the standard data-grid pager + range summary without re-implementing pagination-ui's ellipsis truncation, keyboard handling, or ARIA.",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "component": {
17
+ "const": "TableFooter"
18
+ },
19
+ "for": {
20
+ "description": "id-ref of the table-ui to control. Falls back to the first sibling table-ui within the same parent when omitted.",
21
+ "type": "string",
22
+ "default": ""
23
+ },
24
+ "page": {
25
+ "description": "Current page, 1-based. Explicit attributes always win over derivation (per-attribute, not all-or-nothing) — see page-size/range-total.",
26
+ "type": "number",
27
+ "default": 1
28
+ },
29
+ "page-size": {
30
+ "description": "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.",
31
+ "type": "number",
32
+ "default": 0
33
+ },
34
+ "range-total": {
35
+ "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). 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 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.",
36
+ "type": "number",
37
+ "default": 0
38
+ },
39
+ "siblings": {
40
+ "description": "Passed through to the composed pagination-ui's own [siblings] (page-number window size on each side of the current page).",
41
+ "type": "number",
42
+ "default": 1
43
+ },
44
+ "size": {
45
+ "description": "Passed through to the composed pagination-ui's own [size] (sm | md | lg, the universal size system).",
46
+ "type": "string",
47
+ "default": "md"
48
+ }
49
+ },
50
+ "required": [
51
+ "component"
52
+ ],
53
+ "unevaluatedProperties": false,
54
+ "x-adiaui": {
55
+ "anti_patterns": [
56
+ {
57
+ "fix": "<table-footer-ui page=\"1\" page-size=\"10\" range-total=\"128\"></table-footer-ui>",
58
+ "why": "table-footer-ui is derivation-only (REQ-F-002) — there is no [range-start]/[range-end] attribute pair (that shape belongs to table-toolbar-ui, which has no page/page-size knowledge of its own). Setting [page] + [page-size] + [range-total] derives the range window instead; a permanent three-way drift hazard is exactly what the derivation model exists to avoid.",
59
+ "wrong": "<table-footer-ui range-start=\"1\" range-end=\"10\" range-total=\"128\"></table-footer-ui>"
60
+ }
61
+ ],
62
+ "category": "agent",
63
+ "composes": [
64
+ "pagination-ui"
65
+ ],
66
+ "events": {
67
+ "footer-page": {
68
+ "description": "gh#1807, ADR-0080 (events-only interaction contract, extending ADR-0079's sender-prefixed toolbar-* precedent to a second sender) — dispatched directly at the resolved [for] target on every pager interaction. Detail: { page } (1-based). table-ui listens for this on itself, applied only when [paginate] > 0 (clamped to the valid page range; a non-numeric/NaN detail.page is a no-op); at [paginate=\"0\"] the command is a documented no-op on the table. Additive: any consumer, not only table-footer-ui, may dispatch this at a table-ui."
69
+ },
70
+ "page-change": {
71
+ "description": "The footer's own consumer-facing notification — a distinct name from the inbound footer-page command (ADR-0079's command≠notification separation). Fires on every pager interaction, in both client- and server-mode. Detail: { page } (1-based). This is the server-mode fetch hook: a consumer listens for it, fetches the new page, then sets `table.data` and the footer's own `page` (and `range-total` if the server's total moved) — the footer never optimistically advances its own state in server-mode."
72
+ }
73
+ },
74
+ "examples": [
75
+ {
76
+ "description": "Server-mode footer (gh#1754 Shape 1) paired with a for=-bound table-ui.",
77
+ "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
+ "name": "table-footer-server-mode"
79
+ },
80
+ {
81
+ "description": "Client-mode footer bound to a [paginate]d table-ui with its own internal pager suppressed via [no-pager].",
82
+ "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]",
83
+ "name": "table-footer-client-mode"
84
+ }
85
+ ],
86
+ "keywords": [
87
+ "table-footer",
88
+ "pagination",
89
+ "pager",
90
+ "range",
91
+ "showing",
92
+ "data-grid",
93
+ "directory",
94
+ "admin",
95
+ "backoffice",
96
+ "listing",
97
+ "records"
98
+ ],
99
+ "name": "UITableFooter",
100
+ "parts": {},
101
+ "related": [
102
+ "table",
103
+ "table-toolbar",
104
+ "pagination",
105
+ "card"
106
+ ],
107
+ "slots": {
108
+ "empty": {
109
+ "description": "Renders in place of the range label — the same [data-range] region — when [range-total] is EXPLICITLY present and equal to \"0\" (checked via hasAttribute, never the computed value alone, so an unset range-total during loading never flashes this content). Mutually exclusive with a positive [range-total]: the normal \"Showing X–Y of N\" (or count-only) text always wins whenever range-total resolves positive. Plain author-supplied markup — table-footer-ui does not template or constrain its shape. Naming matches table-toolbar-ui's own `empty` slot convention."
110
+ }
111
+ },
112
+ "states": [
113
+ {
114
+ "description": "Default, ready for interaction.",
115
+ "name": "idle"
116
+ }
117
+ ],
118
+ "status": "beta",
119
+ "synonyms": {
120
+ "pager": [
121
+ "table-footer",
122
+ "pagination"
123
+ ],
124
+ "pagination": [
125
+ "table-footer",
126
+ "pagination"
127
+ ]
128
+ },
129
+ "tag": "table-footer-ui",
130
+ "tokens": {
131
+ "--table-footer-gap": {
132
+ "description": "Gap between the range label and the pager"
133
+ },
134
+ "--table-footer-px": {
135
+ "description": "Horizontal padding"
136
+ },
137
+ "--table-footer-py": {
138
+ "description": "Vertical padding"
139
+ },
140
+ "--table-footer-summary-fg": {
141
+ "description": "Range-label text color"
142
+ },
143
+ "--table-footer-summary-size": {
144
+ "description": "Range-label font size"
145
+ }
146
+ },
147
+ "traits": [],
148
+ "version": 1
149
+ }
150
+ }