@adia-ai/web-components 0.8.45 → 0.8.47
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 +18 -0
- package/components/card/card.css +12 -23
- package/components/card/card.yaml +11 -0
- package/components/chart/chart.a2ui.json +16 -1
- package/components/chart/chart.class.js +611 -41
- package/components/chart/chart.css +174 -0
- package/components/chart/chart.d.ts +5 -1
- package/components/chart/chart.yaml +45 -1
- package/components/field/field.css +24 -2
- package/components/index.js +1 -0
- package/components/input/input.css +7 -0
- package/components/nav/nav.css +7 -3
- package/components/nav-group/nav-group.css +38 -41
- package/components/nav-item/nav-item.css +11 -5
- package/components/preview/preview.class.js +12 -2
- package/components/table/table.a2ui.json +19 -4
- package/components/table/table.class.js +163 -12
- package/components/table/table.d.ts +9 -3
- package/components/table/table.yaml +109 -8
- package/components/table-footer/table-footer.a2ui.json +150 -0
- package/components/table-footer/table-footer.class.js +391 -0
- package/components/table-footer/table-footer.css +64 -0
- package/components/table-footer/table-footer.d.ts +39 -0
- package/components/table-footer/table-footer.examples.md +46 -0
- package/components/table-footer/table-footer.js +17 -0
- package/components/table-footer/table-footer.yaml +219 -0
- package/components/table-toolbar/table-toolbar.a2ui.json +10 -0
- package/components/table-toolbar/table-toolbar.class.js +70 -7
- package/components/table-toolbar/table-toolbar.css +100 -3
- package/components/table-toolbar/table-toolbar.d.ts +2 -0
- package/components/table-toolbar/table-toolbar.examples.md +41 -41
- package/components/table-toolbar/table-toolbar.yaml +31 -6
- package/core/anchor.js +21 -1
- package/custom-elements.json +145 -4
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +2 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +87 -87
- package/dist/web-components.sheet.js +1 -1
- package/package.json +1 -1
- package/patterns/chart-in-card/chart-in-card.examples.html +36 -9
- package/patterns/new-enrollments/new-enrollments.examples.html +140 -0
- package/patterns/new-enrollments/new-enrollments.html +54 -0
- package/patterns/table-in-card/table-in-card.examples.html +168 -0
- package/patterns/table-in-card/table-in-card.examples.js +139 -0
- package/patterns/table-in-card/table-in-card.html +86 -0
- package/styles/colors/material-static.css +276 -234
- package/styles/components.css +1 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
$schema: ../../../../scripts/schemas/component.yaml.schema.json
|
|
2
|
+
name: UITableFooter
|
|
3
|
+
tag: table-footer-ui
|
|
4
|
+
status: beta
|
|
5
|
+
component: TableFooter
|
|
6
|
+
category: agent
|
|
7
|
+
version: 1
|
|
8
|
+
description: >-
|
|
9
|
+
Footer / companion bar for a sibling table-ui (gh#1807, ADR-0080) — a
|
|
10
|
+
"Showing X–Y of N" range label paired with a composed pagination-ui.
|
|
11
|
+
Derivation-model API: set `page` / `page-size` / `range-total` and the
|
|
12
|
+
footer derives total pages and the range window itself, so it never
|
|
13
|
+
drifts out of sync with its own pager. Wired to the target table via a
|
|
14
|
+
[for] id-ref and CustomEvents only (ADR-0079/ADR-0080) — the footer never
|
|
15
|
+
writes a property or calls a method on the bound table. Drop below (or
|
|
16
|
+
inside a card-ui footer alongside) any table-ui to add the standard
|
|
17
|
+
data-grid pager + range summary without re-implementing pagination-ui's
|
|
18
|
+
ellipsis truncation, keyboard handling, or ARIA.
|
|
19
|
+
# Per ADR-0027 — primitives that programmatically create other primitives
|
|
20
|
+
# do NOT auto-import them. Consumer (or demo shell) must explicitly import.
|
|
21
|
+
composes:
|
|
22
|
+
- pagination-ui
|
|
23
|
+
props:
|
|
24
|
+
for:
|
|
25
|
+
description: id-ref of the table-ui to control. Falls back to the first sibling table-ui within the same parent when omitted.
|
|
26
|
+
type: string
|
|
27
|
+
default: ""
|
|
28
|
+
reflect: true
|
|
29
|
+
page:
|
|
30
|
+
description: >-
|
|
31
|
+
Current page, 1-based. Explicit attributes always win over derivation
|
|
32
|
+
(per-attribute, not all-or-nothing) — see page-size/range-total.
|
|
33
|
+
type: number
|
|
34
|
+
default: 1
|
|
35
|
+
reflect: true
|
|
36
|
+
page-size:
|
|
37
|
+
description: >-
|
|
38
|
+
Rows per page. Default 0 (unknown) is the ORDINARY default state, not
|
|
39
|
+
an exotic edge case: with page-size absent/0 and range-total present,
|
|
40
|
+
the range label renders in count-only form ("128 items") and the
|
|
41
|
+
pager does not render — the derivation math (pages = range-total /
|
|
42
|
+
page-size) is undefined without a page size. In a client-mode
|
|
43
|
+
composition (the resolved target has [paginate] > 0) and this
|
|
44
|
+
attribute is unset, the footer derives page-size from the target's
|
|
45
|
+
own [paginate] value.
|
|
46
|
+
type: number
|
|
47
|
+
default: 0
|
|
48
|
+
reflect: true
|
|
49
|
+
range-total:
|
|
50
|
+
description: >-
|
|
51
|
+
Total ROWS across all pages. Absent (the default) means "not yet
|
|
52
|
+
known" (loading) — never "zero"; both the range label and the pager
|
|
53
|
+
stay hidden until this is set. Explicit range-total="0" is the
|
|
54
|
+
confirmed-empty state (pager hidden; the `empty` slot renders in the
|
|
55
|
+
range label's position when supplied). Named identically to
|
|
56
|
+
table-toolbar-ui's [range-total] and table-ui's own [range-total]
|
|
57
|
+
(gh#1754, ADR-0082) and deliberately NOT `total` (pagination-ui's
|
|
58
|
+
`total` is total PAGES — the ADR-0063 B5 collision rule). When this
|
|
59
|
+
attribute is unset, the footer derives it from the resolved target
|
|
60
|
+
via a two-branch ladder (REQ-W-006, amended 0.2.0/ADR-0082):
|
|
61
|
+
(1) the target's own [range-total] attribute, when present — the
|
|
62
|
+
table-authoritative SERVER mode (gh#1754 Shape 1's second lawful
|
|
63
|
+
composition); the identity rule: the footer's range-total derives
|
|
64
|
+
from the table's range-total, never the table's filteredCount, and
|
|
65
|
+
this branch wins even when the target's [paginate] is also > 0;
|
|
66
|
+
(2) otherwise, when the target has [paginate] > 0, CLIENT mode: the
|
|
67
|
+
target's filtered row count (search + column filters applied, before
|
|
68
|
+
pagination). Refreshes on the target's `page` / `filter-change` /
|
|
69
|
+
`sort` events in either branch. Widened named staleness edge: a
|
|
70
|
+
programmatic `.data` swap on the target — or a `range-total` update
|
|
71
|
+
on the target without an accompanying page move — emits no event;
|
|
72
|
+
either re-set this footer's own attributes after such a change, or
|
|
73
|
+
accept staleness until the target's next event.
|
|
74
|
+
type: number
|
|
75
|
+
default: 0
|
|
76
|
+
reflect: true
|
|
77
|
+
siblings:
|
|
78
|
+
description: Passed through to the composed pagination-ui's own [siblings] (page-number window size on each side of the current page).
|
|
79
|
+
type: number
|
|
80
|
+
default: 1
|
|
81
|
+
reflect: true
|
|
82
|
+
size:
|
|
83
|
+
description: Passed through to the composed pagination-ui's own [size] (sm | md | lg, the universal size system).
|
|
84
|
+
type: string
|
|
85
|
+
default: md
|
|
86
|
+
reflect: true
|
|
87
|
+
events:
|
|
88
|
+
footer-page:
|
|
89
|
+
description: >-
|
|
90
|
+
gh#1807, ADR-0080 (events-only interaction contract, extending
|
|
91
|
+
ADR-0079's sender-prefixed toolbar-* precedent to a second sender) —
|
|
92
|
+
dispatched directly at the resolved [for] target on every pager
|
|
93
|
+
interaction. Detail: { page } (1-based). table-ui listens for this on
|
|
94
|
+
itself, applied only when [paginate] > 0 (clamped to the valid page
|
|
95
|
+
range; a non-numeric/NaN detail.page is a no-op); at [paginate="0"]
|
|
96
|
+
the command is a documented no-op on the table. Additive: any
|
|
97
|
+
consumer, not only table-footer-ui, may dispatch this at a table-ui.
|
|
98
|
+
page-change:
|
|
99
|
+
description: >-
|
|
100
|
+
The footer's own consumer-facing notification — a distinct name from
|
|
101
|
+
the inbound footer-page command (ADR-0079's command≠notification
|
|
102
|
+
separation). Fires on every pager interaction, in both client- and
|
|
103
|
+
server-mode. Detail: { page } (1-based). This is the server-mode
|
|
104
|
+
fetch hook: a consumer listens for it, fetches the new page, then
|
|
105
|
+
sets `table.data` and the footer's own `page` (and `range-total` if
|
|
106
|
+
the server's total moved) — the footer never optimistically advances
|
|
107
|
+
its own state in server-mode.
|
|
108
|
+
slots:
|
|
109
|
+
empty:
|
|
110
|
+
description: >-
|
|
111
|
+
Renders in place of the range label — the same [data-range] region —
|
|
112
|
+
when [range-total] is EXPLICITLY present and equal to "0" (checked
|
|
113
|
+
via hasAttribute, never the computed value alone, so an unset
|
|
114
|
+
range-total during loading never flashes this content). Mutually
|
|
115
|
+
exclusive with a positive [range-total]: the normal "Showing X–Y of N"
|
|
116
|
+
(or count-only) text always wins whenever range-total resolves
|
|
117
|
+
positive. Plain author-supplied markup — table-footer-ui does not
|
|
118
|
+
template or constrain its shape. Naming matches table-toolbar-ui's own
|
|
119
|
+
`empty` slot convention.
|
|
120
|
+
states:
|
|
121
|
+
- name: idle
|
|
122
|
+
description: Default, ready for interaction.
|
|
123
|
+
traits: []
|
|
124
|
+
tokens:
|
|
125
|
+
--table-footer-gap:
|
|
126
|
+
description: Gap between the range label and the pager
|
|
127
|
+
--table-footer-py:
|
|
128
|
+
description: Vertical padding
|
|
129
|
+
--table-footer-px:
|
|
130
|
+
description: Horizontal padding
|
|
131
|
+
--table-footer-summary-fg:
|
|
132
|
+
description: Range-label text color
|
|
133
|
+
--table-footer-summary-size:
|
|
134
|
+
description: Range-label font size
|
|
135
|
+
a2ui:
|
|
136
|
+
rules:
|
|
137
|
+
- >-
|
|
138
|
+
Pair <table-footer-ui> with <table-ui> via [for="<table-id>"] (or
|
|
139
|
+
rely on first-sibling fallback when both are inside the same parent).
|
|
140
|
+
Client-mode composition (a [paginate]-sliced table): set [no-pager]
|
|
141
|
+
on the table-ui so its own internal pagination bar doesn't render
|
|
142
|
+
alongside the footer's composed one — table-footer-ui never
|
|
143
|
+
auto-suppresses it for you.
|
|
144
|
+
- >-
|
|
145
|
+
Server-mode composition (gh#1754 Shape 1 — the server returns the
|
|
146
|
+
page's rows plus the total row count): set [range-total] to the
|
|
147
|
+
server's total, [page] / [page-size] from your own request state, and
|
|
148
|
+
listen for the footer's `page-change` event to fetch the next page —
|
|
149
|
+
the footer does not optimistically advance its own [page] in
|
|
150
|
+
server-mode; write it back after the fetch resolves.
|
|
151
|
+
- >-
|
|
152
|
+
The toolbar's [count] badge and this footer's range label coexist by
|
|
153
|
+
design — they answer different questions (the collection summary vs.
|
|
154
|
+
the page-window position) and neither replaces the other. In a
|
|
155
|
+
composition that includes a table-footer-ui, leave the toolbar's
|
|
156
|
+
[range-start]/[range-end]/[range-total] unset — the range summary's
|
|
157
|
+
home is the footer.
|
|
158
|
+
- >-
|
|
159
|
+
Canonical composition: card-ui header (title + primary action) →
|
|
160
|
+
a plain <section> wrapping a title-less <table-toolbar-ui for> → a
|
|
161
|
+
<section bleed> wrapping the <table-ui id> → a <footer divider>
|
|
162
|
+
wrapping this <table-footer-ui for> — see the table-in-card pattern.
|
|
163
|
+
- >-
|
|
164
|
+
Use <span slot="empty"> (or any markup) inside a table-footer-ui that
|
|
165
|
+
also sets [range-total="0"] to show a "no results" message in the
|
|
166
|
+
range label's own position, without a layout shift versus the normal
|
|
167
|
+
range text. Only fires on an EXPLICIT range-total="0" — omitting
|
|
168
|
+
range-total entirely (loading) never shows it.
|
|
169
|
+
anti_patterns:
|
|
170
|
+
- wrong: '<table-footer-ui range-start="1" range-end="10" range-total="128"></table-footer-ui>'
|
|
171
|
+
why: >-
|
|
172
|
+
table-footer-ui is derivation-only (REQ-F-002) — there is no
|
|
173
|
+
[range-start]/[range-end] attribute pair (that shape belongs to
|
|
174
|
+
table-toolbar-ui, which has no page/page-size knowledge of its own).
|
|
175
|
+
Setting [page] + [page-size] + [range-total] derives the range window
|
|
176
|
+
instead; a permanent three-way drift hazard is exactly what the
|
|
177
|
+
derivation model exists to avoid.
|
|
178
|
+
fix: '<table-footer-ui page="1" page-size="10" range-total="128"></table-footer-ui>'
|
|
179
|
+
examples:
|
|
180
|
+
- name: table-footer-server-mode
|
|
181
|
+
description: Server-mode footer (gh#1754 Shape 1) paired with a for=-bound table-ui.
|
|
182
|
+
a2ui: >-
|
|
183
|
+
[
|
|
184
|
+
{"id": "root", "component": "Column", "gap": "0", "children": ["tests", "ftr"]},
|
|
185
|
+
{"id": "tests", "component": "Table", "raw": true},
|
|
186
|
+
{"id": "ftr", "component": "TableFooter", "for": "tests", "page": 1, "page-size": 10, "range-total": 128}
|
|
187
|
+
]
|
|
188
|
+
- name: table-footer-client-mode
|
|
189
|
+
description: Client-mode footer bound to a [paginate]d table-ui with its own internal pager suppressed via [no-pager].
|
|
190
|
+
a2ui: >-
|
|
191
|
+
[
|
|
192
|
+
{"id": "root", "component": "Column", "gap": "0", "children": ["tests-c", "ftr"]},
|
|
193
|
+
{"id": "tests-c", "component": "Table", "paginate": 10, "no-pager": true, "raw": true},
|
|
194
|
+
{"id": "ftr", "component": "TableFooter", "for": "tests-c"}
|
|
195
|
+
]
|
|
196
|
+
keywords:
|
|
197
|
+
- table-footer
|
|
198
|
+
- pagination
|
|
199
|
+
- pager
|
|
200
|
+
- range
|
|
201
|
+
- showing
|
|
202
|
+
- data-grid
|
|
203
|
+
- directory
|
|
204
|
+
- admin
|
|
205
|
+
- backoffice
|
|
206
|
+
- listing
|
|
207
|
+
- records
|
|
208
|
+
synonyms:
|
|
209
|
+
pagination:
|
|
210
|
+
- table-footer
|
|
211
|
+
- pagination
|
|
212
|
+
pager:
|
|
213
|
+
- table-footer
|
|
214
|
+
- pagination
|
|
215
|
+
related:
|
|
216
|
+
- table
|
|
217
|
+
- table-toolbar
|
|
218
|
+
- pagination
|
|
219
|
+
- card
|
|
@@ -80,6 +80,16 @@
|
|
|
80
80
|
"type": "number",
|
|
81
81
|
"default": 0
|
|
82
82
|
},
|
|
83
|
+
"size": {
|
|
84
|
+
"description": "Toolbar scale, via sizing.css's universal [size=\"sm|md|lg\"] ambient tokens. Not table-toolbar-specific chrome — the same attribute that scales any component. Set it once on the toolbar instead of on each slotted scope/actions-leading/actions control individually; anything slotted with no [size] of its own inherits the ambient value. Default (empty) = md.",
|
|
85
|
+
"type": "string",
|
|
86
|
+
"enum": [
|
|
87
|
+
"sm",
|
|
88
|
+
"md",
|
|
89
|
+
"lg"
|
|
90
|
+
],
|
|
91
|
+
"default": ""
|
|
92
|
+
},
|
|
83
93
|
"stage": {
|
|
84
94
|
"description": "Explicit compaction-stage override (ADR-0076). Unset (the default, empty string) means auto-snap: the toolbar's own inline-size container query picks the nearest stage from its live width against the three --table-toolbar-bp-* breakpoints, snapping discretely, never interpolating. Setting stage explicitly pins that stage's rendering regardless of the container's actual width — overriding the container query, mirroring chart-ui's ratio override shape. Primary use: visual-eval fixtures that need a deterministic stage without resizing a real container, and a consumer embedding the toolbar inside a known-narrow panel who wants to skip auto-detection entirely. All four stages' CSS is now live (ADR-0076): search-tight steps the search field down (step 2); icon-only drops control labels and compacts/hides the range summary (steps 3+4); overflow collapses Filter/Sort/Columns into one \"More\" trigger routed through the same menu-ui/menu-item-ui pattern used elsewhere in the corpus (step 5). The resolved stage — the pin if set, otherwise the live width classification — also reflects onto the host as `data-stage-resolved` (step 7, informational only, mirrors chart-ui's `data-ratio-resolved` convention — never drives table-toolbar's own rendering, which stays 100% CSS-driven).",
|
|
85
95
|
"type": "string",
|
|
@@ -153,6 +153,20 @@ function collectUniqueValues(col, data) {
|
|
|
153
153
|
export class UITableToolbar extends UIElement {
|
|
154
154
|
static properties = {
|
|
155
155
|
for: { type: String, default: '', reflect: true },
|
|
156
|
+
// Not consumed by any table-toolbar-specific CSS — sizing.css's
|
|
157
|
+
// universal [size="sm|md|lg"] (unscoped attribute selector, ambient
|
|
158
|
+
// inheriting tokens) already governs the host AND every slotted
|
|
159
|
+
// consumer content (scope select-ui, actions-leading/actions
|
|
160
|
+
// button-ui) that carries no [size] of its own, same as
|
|
161
|
+
// `<div size="sm">…</div>`. Declared (reflect: true) purely so this
|
|
162
|
+
// A2UI-modeled prop survives the docs-transpiler's schema-scoped
|
|
163
|
+
// attribute walk (unmodeled attributes are silently dropped there —
|
|
164
|
+
// verified live: `<table-toolbar-ui size="sm">` authored in
|
|
165
|
+
// table-toolbar.examples.html rendered with no [size] attribute at
|
|
166
|
+
// all on the generated site-a2ui page until this was added) and so
|
|
167
|
+
// `el.size = 'sm'` works from JS, matching card-ui/button-ui's own
|
|
168
|
+
// `size` property shape.
|
|
169
|
+
size: { type: String, default: '', reflect: true },
|
|
156
170
|
text: { type: String, default: '', reflect: false },
|
|
157
171
|
count: { type: String, default: '', reflect: false },
|
|
158
172
|
rangeStart: { type: Number, default: 0, reflect: true, attribute: 'range-start' },
|
|
@@ -233,6 +247,27 @@ export class UITableToolbar extends UIElement {
|
|
|
233
247
|
|
|
234
248
|
connected() {
|
|
235
249
|
this.setAttribute('role', 'toolbar');
|
|
250
|
+
// `size` (declared above, reflect: true) defaults to '' — the base
|
|
251
|
+
// property system's own reflect pass (core/element.js) deliberately
|
|
252
|
+
// does NOT auto-stamp a non-empty string/number default onto the DOM
|
|
253
|
+
// (gh#961: scoped to Boolean-true defaults only, to avoid a
|
|
254
|
+
// framework-wide `attr=""`-stamping cascade), so a bare
|
|
255
|
+
// `<table-toolbar-ui>` never actually gets a `[size]` attribute from
|
|
256
|
+
// the default alone. Stamped explicitly, once, HERE — sizing.css's
|
|
257
|
+
// `[size="sm"]` is a plain unscoped attribute selector that needs the
|
|
258
|
+
// literal attribute present to set the ambient `--a-size`/`--a-ui-size`
|
|
259
|
+
// tokens at all. Every native control this component stamps below
|
|
260
|
+
// (badge, search, page-size select, granular buttons, overflow
|
|
261
|
+
// trigger) is a light-DOM descendant with no `[size]` of its own, so
|
|
262
|
+
// it inherits those tokens the same way any plain
|
|
263
|
+
// `<div size="sm">…</div>` cascades to its children — no per-child
|
|
264
|
+
// `size=` stamping needed (tried first, reverted: redundant with what
|
|
265
|
+
// inheritance already does for every element still in this light-DOM
|
|
266
|
+
// subtree, and actively wrong once a consumer sets an explicit
|
|
267
|
+
// `size="lg"` after connect — the per-child copies would go stale
|
|
268
|
+
// since nothing re-stamps them on a later property change, while
|
|
269
|
+
// inheritance updates live).
|
|
270
|
+
if (!this.hasAttribute('size')) this.setAttribute('size', this.size || 'sm');
|
|
236
271
|
this.#warnUnknownOptOutAttrs();
|
|
237
272
|
// gh#1615 — hydrate `page-size-options="[10,25,50]"` once at connect,
|
|
238
273
|
// same pattern as table-ui's `data`/`columns` JSON-array attributes
|
|
@@ -479,6 +514,26 @@ export class UITableToolbar extends UIElement {
|
|
|
479
514
|
|
|
480
515
|
// ── DOM stamp ────────────────────────────────────────────────────────────
|
|
481
516
|
|
|
517
|
+
// Used ONLY for the two stamped elements that end up OUTSIDE
|
|
518
|
+
// table-toolbar-ui's own light DOM — the popover-panel action buttons
|
|
519
|
+
// (#mkPopoverAction) and the filter-input (#fillFilterPanel), both
|
|
520
|
+
// appended to `document.body` for the Popover API's top-layer promotion
|
|
521
|
+
// (#togglePopover) — so ambient CSS inheritance from the host's own
|
|
522
|
+
// `[size]` attribute (stamped once in connected(), above) can never
|
|
523
|
+
// reach them. Every OTHER stamped control (badge, search, page-size
|
|
524
|
+
// select, granular Filter/Sort/Columns buttons, the overflow trigger)
|
|
525
|
+
// stays in the host's own light DOM and inherits that ambient value
|
|
526
|
+
// directly — no per-element `size=` copy needed, and no risk of one
|
|
527
|
+
// going stale after a later `.size =` change the way an explicit copy
|
|
528
|
+
// would (tried first, reverted: every in-tree control hardcoded
|
|
529
|
+
// `size="sm"` unconditionally, which not only ignored a consumer's
|
|
530
|
+
// `size="lg"` but also fought its OWN copy going stale on any later
|
|
531
|
+
// property change nothing re-stamped). Falls back to "sm" (the
|
|
532
|
+
// pre-existing hardcoded default) when `size` is unset.
|
|
533
|
+
#stampSize() {
|
|
534
|
+
return this.size || 'sm';
|
|
535
|
+
}
|
|
536
|
+
|
|
482
537
|
#stamp() {
|
|
483
538
|
if (this.querySelector(':scope > [data-toolbar]')) return;
|
|
484
539
|
|
|
@@ -509,7 +564,8 @@ export class UITableToolbar extends UIElement {
|
|
|
509
564
|
|
|
510
565
|
const badge = document.createElement('badge-ui');
|
|
511
566
|
badge.setAttribute('data-toolbar-count-badge', '');
|
|
512
|
-
|
|
567
|
+
// No own [size] — inherits the host's ambient --a-size (stamped in
|
|
568
|
+
// connected(), above).
|
|
513
569
|
badge.setAttribute('tone', 'muted');
|
|
514
570
|
badge.hidden = true;
|
|
515
571
|
title.appendChild(badge);
|
|
@@ -577,7 +633,7 @@ export class UITableToolbar extends UIElement {
|
|
|
577
633
|
// name renders as literal text before the icon registry resolves.
|
|
578
634
|
const search = document.createElement('search-ui');
|
|
579
635
|
search.setAttribute('data-search', '');
|
|
580
|
-
|
|
636
|
+
// No own [size] — inherits the host's ambient --a-size, same as badge.
|
|
581
637
|
search.setAttribute('placeholder', this.placeholder);
|
|
582
638
|
search.setAttribute('debounce', String(SEARCH_DEBOUNCE));
|
|
583
639
|
search.addEventListener('search', this.#onSearch);
|
|
@@ -590,7 +646,7 @@ export class UITableToolbar extends UIElement {
|
|
|
590
646
|
// existing default behavior for consumers that never set pageSizeOptions.
|
|
591
647
|
const pageSize = document.createElement('select-ui');
|
|
592
648
|
pageSize.setAttribute('data-page-size', '');
|
|
593
|
-
|
|
649
|
+
// No own [size] — inherits the host's ambient --a-size, same as badge.
|
|
594
650
|
pageSize.setAttribute('aria-label', 'Rows per page');
|
|
595
651
|
pageSize.hidden = true;
|
|
596
652
|
pageSize.addEventListener('change', this.#onPageSizeChange);
|
|
@@ -645,7 +701,12 @@ export class UITableToolbar extends UIElement {
|
|
|
645
701
|
btn.setAttribute('data-popover-action', '');
|
|
646
702
|
btn.setAttribute('text', label);
|
|
647
703
|
btn.setAttribute('variant', 'ghost');
|
|
648
|
-
|
|
704
|
+
// Explicit [size] IS still needed here, unlike the in-tree stamped
|
|
705
|
+
// controls above — #togglePopover appends this button's panel straight
|
|
706
|
+
// to `document.body` (Popover API top-layer promotion), not nested
|
|
707
|
+
// inside table-toolbar-ui in the DOM, so ambient CSS inheritance from
|
|
708
|
+
// the host's own [size] never reaches it.
|
|
709
|
+
btn.setAttribute('size', this.#stampSize());
|
|
649
710
|
btn.setAttribute('stretch', '');
|
|
650
711
|
btn.addEventListener('click', onClick);
|
|
651
712
|
return btn;
|
|
@@ -657,7 +718,7 @@ export class UITableToolbar extends UIElement {
|
|
|
657
718
|
btn.setAttribute('icon', icon);
|
|
658
719
|
btn.setAttribute('text', label);
|
|
659
720
|
btn.setAttribute('variant', 'outline');
|
|
660
|
-
|
|
721
|
+
// No own [size] — inherits the host's ambient --a-size, same as badge.
|
|
661
722
|
btn.setAttribute('aria-haspopup', 'menu');
|
|
662
723
|
// ADR-0076 REQ-X-002 — unconditional (harmless at full-text stages,
|
|
663
724
|
// becomes the hover-tooltip affordance at the icon-only stage once
|
|
@@ -689,7 +750,7 @@ export class UITableToolbar extends UIElement {
|
|
|
689
750
|
trigger.setAttribute('slot', 'trigger');
|
|
690
751
|
trigger.setAttribute('icon', 'dots-three-vertical');
|
|
691
752
|
trigger.setAttribute('variant', 'outline');
|
|
692
|
-
|
|
753
|
+
// No own [size] — inherits the host's ambient --a-size, same as badge.
|
|
693
754
|
// REQ-X-003 — a genuinely new interactive element (not a repurposed
|
|
694
755
|
// existing button), so it needs its own static accessible name rather
|
|
695
756
|
// than an inherited one. No [text] attribute — always icon-only,
|
|
@@ -1085,7 +1146,9 @@ export class UITableToolbar extends UIElement {
|
|
|
1085
1146
|
} else {
|
|
1086
1147
|
const input = document.createElement('input-ui');
|
|
1087
1148
|
input.setAttribute('type', 'text');
|
|
1088
|
-
|
|
1149
|
+
// Explicit [size] needed — this panel is appended straight to
|
|
1150
|
+
// document.body (see #mkPopoverAction's comment above).
|
|
1151
|
+
input.setAttribute('size', this.#stampSize());
|
|
1089
1152
|
input.setAttribute('data-filter-input', '');
|
|
1090
1153
|
input.setAttribute('placeholder', '—');
|
|
1091
1154
|
if (current?.op === 'contains') input.value = current.value ?? '';
|
|
@@ -284,6 +284,21 @@
|
|
|
284
284
|
flex: 0 0 auto;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
/* See [data-page-size]'s own comment below — same --select-min-width
|
|
288
|
+
floor, same fix, but targeted at the select-ui DESCENDANT directly
|
|
289
|
+
rather than set as an inheriting custom property on the [data-scope]
|
|
290
|
+
wrapper: select-ui's own `:where(:scope) { --select-min-width: 20ch }`
|
|
291
|
+
(select.css) directly assigns the property on the select-ui element
|
|
292
|
+
itself — a direct assignment always wins over an inherited value
|
|
293
|
+
regardless of the assigning rule's (here, zero) specificity, so a
|
|
294
|
+
value merely inherited from an ancestor wrapper never actually
|
|
295
|
+
reaches it. Matching the select-ui element directly puts this
|
|
296
|
+
override in the same competition as that rule, where normal
|
|
297
|
+
specificity comparison (0,2,0) vs (0,0,0) applies and this wins. */
|
|
298
|
+
[data-scope] select-ui {
|
|
299
|
+
--select-min-width: 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
287
302
|
[data-scope]:empty { display: none; }
|
|
288
303
|
|
|
289
304
|
/* ═══════ Title cluster ═══════ */
|
|
@@ -305,6 +320,21 @@
|
|
|
305
320
|
|
|
306
321
|
[data-toolbar-count-badge][hidden] { display: none; }
|
|
307
322
|
|
|
323
|
+
/* Scope reconciliation — a filled [slot="scope"] already names the
|
|
324
|
+
entity being listed (a select/menu whose own displayed value IS the
|
|
325
|
+
label, e.g. "All Accounts (3)"), so the separate [text]/[count] title
|
|
326
|
+
cluster next to it says the same thing twice ("All accounts" +
|
|
327
|
+
"Accounts 3"). CSS-only, not #updateTitle() (JS never even sees
|
|
328
|
+
[text]/[count] are redundant here — [count] still falls back to the
|
|
329
|
+
bound table's row length regardless, so a JS hide would need a new
|
|
330
|
+
prop; this reads what's already in the DOM, same `:has()` idiom the
|
|
331
|
+
[data-summary]/[data-page-size] reconciliation above already uses).
|
|
332
|
+
A consumer wanting BOTH visible can still override with normal CSS
|
|
333
|
+
specificity — this isn't a hard mode switch, just the default. */
|
|
334
|
+
:scope:has([data-scope]:not(:empty)) [data-title] {
|
|
335
|
+
display: none;
|
|
336
|
+
}
|
|
337
|
+
|
|
308
338
|
/* ═══════ Range summary ═══════ (gh#1615 — "Showing X–Y of N", independent of [data-title]) */
|
|
309
339
|
|
|
310
340
|
[data-summary] {
|
|
@@ -371,7 +401,17 @@
|
|
|
371
401
|
--table-toolbar-bp-overflow's own default (same duplication +
|
|
372
402
|
source-assertion discipline as the search-tight/icon-only boundaries
|
|
373
403
|
above). */
|
|
374
|
-
|
|
404
|
+
/* BUG (found live, verified in a real browser — a fresh table-toolbar-ui
|
|
405
|
+
at ANY stage, including "full", rendered Filter/Sort/Columns AND the
|
|
406
|
+
"More" trigger simultaneously): menu-ui's own base rule (menu.css,
|
|
407
|
+
`:scope { display: flex; }`) is the same (0,1,0) specificity as a bare
|
|
408
|
+
`[data-toolbar-overflow]` selector — a cascade TIE this file's source
|
|
409
|
+
order loses (menu.css wins), so this default-hide rule never actually
|
|
410
|
+
applied. The `:scope … [data-toolbar-overflow][hidden]` override below
|
|
411
|
+
already carries the `:scope` prefix for exactly this reason (its own
|
|
412
|
+
comment: "must match or exceed the (0,3,0) specificity … to win the
|
|
413
|
+
tie") — this rule needed the same prefix and never got it. */
|
|
414
|
+
:scope [data-toolbar-overflow] { display: none; }
|
|
375
415
|
|
|
376
416
|
@container table-toolbar (max-width: 26rem) {
|
|
377
417
|
:scope:is(:not([stage]), [stage=""]) [data-toolbar-btn] {
|
|
@@ -405,19 +445,62 @@
|
|
|
405
445
|
conflicting author rule. */
|
|
406
446
|
:scope [data-toolbar-overflow][hidden] { display: none; }
|
|
407
447
|
|
|
448
|
+
/* menu-ui's own popover carries a hardcoded `min-width: 10rem` (menu.css
|
|
449
|
+
— a LITERAL, not even the `--menu-popover-min-width` token declared
|
|
450
|
+
next to it: the popover is promoted to the top layer via the Popover
|
|
451
|
+
API but stays a real light-DOM child of <menu-ui>, per menu.css's own
|
|
452
|
+
comment "the top-layer element can't inherit the scoped token", so
|
|
453
|
+
that rule hardcodes the value instead of reading the custom property).
|
|
454
|
+
Same class of bug as --input-min-width/--select-min-width above: sized
|
|
455
|
+
for a generic dropdown menu of arbitrary action items, not this
|
|
456
|
+
specific "More" trigger's three short labels (Filter/Sort/Columns),
|
|
457
|
+
each already narrower than 10rem on its own. Scoped to JUST this
|
|
458
|
+
menu-ui instance (`[data-toolbar-overflow]`) — the [data-menu-popover]
|
|
459
|
+
it targets is menu-ui's own internal implementation selector, not
|
|
460
|
+
table-toolbar's; overriding the LITERAL selector directly here rather
|
|
461
|
+
than touching menu.css keeps every other menu-ui consumer's own
|
|
462
|
+
10rem floor (a reasonable default for a menu of arbitrary items)
|
|
463
|
+
untouched. */
|
|
464
|
+
:scope [data-toolbar-overflow] [data-menu-popover] {
|
|
465
|
+
min-width: 0;
|
|
466
|
+
}
|
|
467
|
+
|
|
408
468
|
/* ═══════ Search ═══════ */
|
|
409
469
|
|
|
410
470
|
[data-search] {
|
|
411
471
|
flex: 1 1 var(--table-toolbar-search-min);
|
|
412
472
|
/* gh#1076 — never collapse to 0: floor at a usable width and let the
|
|
413
|
-
flex-wrap above move it to its own row when the container is tight.
|
|
414
|
-
|
|
473
|
+
flex-wrap above move it to its own row when the container is tight.
|
|
474
|
+
BUG (found live, 2026-08-22): this used to be a hardcoded
|
|
475
|
+
`min(10rem, 100%)`, independent of `--table-toolbar-search-min` —
|
|
476
|
+
at the search-tight/icon-only/overflow stages that token gets
|
|
477
|
+
reassigned to `--table-toolbar-search-tight-min` (9rem default),
|
|
478
|
+
but the hardcoded 10rem floor here still won, so the tight tier's
|
|
479
|
+
own declared 9rem target could never actually be reached. Deriving
|
|
480
|
+
the floor from the SAME token as the flex-basis keeps them in sync
|
|
481
|
+
at every stage instead of drifting against each other. */
|
|
482
|
+
min-width: min(var(--table-toolbar-search-min), 100%);
|
|
415
483
|
max-width: var(--table-toolbar-search-max);
|
|
416
484
|
margin-inline-start: auto;
|
|
417
485
|
}
|
|
418
486
|
|
|
419
487
|
[data-search][hidden] { display: none; }
|
|
420
488
|
|
|
489
|
+
/* input-ui's own --input-min-width default (20ch — input.css) is sized
|
|
490
|
+
for a BARE, standalone field where the component has no other context
|
|
491
|
+
to size against. Inside [data-search] that's a second, independent
|
|
492
|
+
floor competing with the one above — at the tight/icon-only/overflow
|
|
493
|
+
stages input-ui's 20ch (~164px) is WIDER than
|
|
494
|
+
--table-toolbar-search-tight-min (9rem/144px), so it silently won
|
|
495
|
+
regardless of what [data-search]'s own min-width — even after the fix
|
|
496
|
+
above — asked for. table-toolbar-ui owns the search field's sizing
|
|
497
|
+
budget end-to-end (it's the one deciding how much room is left after
|
|
498
|
+
the title/controls/page-size clusters); input-ui's independent
|
|
499
|
+
opinion has nothing to add here and only fights the outer token. */
|
|
500
|
+
[data-search] input-ui {
|
|
501
|
+
--input-min-width: 0;
|
|
502
|
+
}
|
|
503
|
+
|
|
421
504
|
/* ═══════ Actions-leading slot ═══════ (gh#1649 — app-owned triggers, e.g.
|
|
422
505
|
custom Filter/Columns buttons, that land BEFORE the page-size select)
|
|
423
506
|
Carries the SAME margin-inline-start:auto as [data-search]/[data-page-size]
|
|
@@ -449,9 +532,23 @@
|
|
|
449
532
|
is safe — only the first one with unclaimed free space to its left
|
|
450
533
|
actually receives it. */
|
|
451
534
|
|
|
535
|
+
/* select-ui's own --select-min-width default (20ch — select.css,
|
|
536
|
+
gh#1633, the SAME UA-baseline floor input.css's --input-min-width
|
|
537
|
+
already documents for input-ui) is sized for a bare, standalone
|
|
538
|
+
select. Found live (root-caused alongside the earlier search-field
|
|
539
|
+
min-width fix, same shape): with no override here, [data-page-size]
|
|
540
|
+
and [data-scope] select-ui both floor at ~151px regardless of how
|
|
541
|
+
short their actual content is ("25 / page", "All Accounts (3)") —
|
|
542
|
+
visible dead space inside the trigger. The filter/sort/columns
|
|
543
|
+
popover's OWN per-column selects already avoid this (they share the
|
|
544
|
+
`[data-filter-input]` marker with the text-input case, and
|
|
545
|
+
`[data-toolbar-popover] [data-filter-input] { min-width: 0; }`
|
|
546
|
+
already resets it there) — this is the same fix for the two
|
|
547
|
+
main-row selects that marker doesn't reach. */
|
|
452
548
|
[data-page-size] {
|
|
453
549
|
flex: 0 0 auto;
|
|
454
550
|
margin-inline-start: auto;
|
|
551
|
+
--select-min-width: 0;
|
|
455
552
|
}
|
|
456
553
|
|
|
457
554
|
[data-page-size][hidden] { display: none; }
|
|
@@ -50,6 +50,8 @@ export class UITableToolbar extends UIElement {
|
|
|
50
50
|
rangeStart: number;
|
|
51
51
|
/** Total row count across all pages, for the range summary (e.g. `320` in "Showing 1–25 of 320"). Distinct from pagination-ui's `total` (total PAGES) — this is total ROWS; deliberately not spelled `total` to avoid that cross-sibling name collision (ADR-0063 B5). See rangeStart. */
|
|
52
52
|
rangeTotal: number;
|
|
53
|
+
/** Toolbar scale, via sizing.css's universal [size="sm|md|lg"] ambient tokens. Not table-toolbar-specific chrome — the same attribute that scales any component. Set it once on the toolbar instead of on each slotted scope/actions-leading/actions control individually; anything slotted with no [size] of its own inherits the ambient value. Default (empty) = md. */
|
|
54
|
+
size: 'sm' | 'md' | 'lg';
|
|
53
55
|
/** Explicit compaction-stage override (ADR-0076). Unset (the default, empty string) means auto-snap: the toolbar's own inline-size container query picks the nearest stage from its live width against the three --table-toolbar-bp-* breakpoints, snapping discretely, never interpolating. Setting stage explicitly pins that stage's rendering regardless of the container's actual width — overriding the container query, mirroring chart-ui's ratio override shape. Primary use: visual-eval fixtures that need a deterministic stage without resizing a real container, and a consumer embedding the toolbar inside a known-narrow panel who wants to skip auto-detection entirely. All four stages' CSS is now live (ADR-0076): search-tight steps the search field down (step 2); icon-only drops control labels and compacts/hides the range summary (steps 3+4); overflow collapses Filter/Sort/Columns into one "More" trigger routed through the same menu-ui/menu-item-ui pattern used elsewhere in the corpus (step 5). The resolved stage — the pin if set, otherwise the live width classification — also reflects onto the host as `data-stage-resolved` (step 7, informational only, mirrors chart-ui's `data-ratio-resolved` convention — never drives table-toolbar's own rendering, which stays 100% CSS-driven). */
|
|
54
56
|
stage: '' | 'full' | 'search-tight' | 'icon-only' | 'overflow';
|
|
55
57
|
/** Title text shown on the left. */
|
|
@@ -14,45 +14,45 @@
|
|
|
14
14
|
## Rendered demos
|
|
15
15
|
|
|
16
16
|
```html
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
</
|
|
29
|
-
</
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
</
|
|
37
|
-
</
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</
|
|
44
|
-
</
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</
|
|
51
|
-
</
|
|
52
|
-
</
|
|
53
|
-
</
|
|
54
|
-
</table
|
|
55
|
-
</
|
|
56
|
-
</
|
|
57
|
-
</
|
|
17
|
+
<card-ui>
|
|
18
|
+
<header>
|
|
19
|
+
<table-toolbar-ui for="demo-table" text="All employees" count="32" placeholder="Search employees…"></table-toolbar-ui>
|
|
20
|
+
</header>
|
|
21
|
+
<section bleed>
|
|
22
|
+
<table-ui id="demo-table" sortable raw>
|
|
23
|
+
<table>
|
|
24
|
+
<thead>
|
|
25
|
+
<tr>
|
|
26
|
+
<th>Name</th>
|
|
27
|
+
<th>Role</th>
|
|
28
|
+
<th>Status</th>
|
|
29
|
+
</tr>
|
|
30
|
+
</thead>
|
|
31
|
+
<tbody>
|
|
32
|
+
<tr>
|
|
33
|
+
<td>Maya Chen</td>
|
|
34
|
+
<td>Designer</td>
|
|
35
|
+
<td>
|
|
36
|
+
<tag-ui variant="success">Active</tag-ui>
|
|
37
|
+
</td>
|
|
38
|
+
</tr>
|
|
39
|
+
<tr>
|
|
40
|
+
<td>Alex Park</td>
|
|
41
|
+
<td>Engineer</td>
|
|
42
|
+
<td>
|
|
43
|
+
<tag-ui variant="success">Active</tag-ui>
|
|
44
|
+
</td>
|
|
45
|
+
</tr>
|
|
46
|
+
<tr>
|
|
47
|
+
<td>Jordan Lee</td>
|
|
48
|
+
<td>Product</td>
|
|
49
|
+
<td>
|
|
50
|
+
<tag-ui variant="warning">Away</tag-ui>
|
|
51
|
+
</td>
|
|
52
|
+
</tr>
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
</table-ui>
|
|
56
|
+
</section>
|
|
57
|
+
</card-ui>
|
|
58
58
|
```
|