@adia-ai/web-components 0.8.42 → 0.8.44
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 +44 -0
- package/MIGRATION.md +130 -80
- package/USAGE.md +1 -1
- package/color/index.js +1 -1
- package/components/action-list/action-item.a2ui.json +3 -8
- package/components/action-list/action-item.yaml +10 -16
- package/components/action-list/action-list.class.js +5 -61
- package/components/action-list/action-list.d.ts +1 -3
- package/components/avatar/avatar.class.js +12 -1
- package/components/button/button.d.ts +4 -0
- package/components/button/button.yaml +1 -1
- package/components/calendar-picker/calendar-picker.a2ui.json +1 -1
- package/components/calendar-picker/calendar-picker.class.js +2 -3
- package/components/calendar-picker/calendar-picker.css +16 -18
- package/components/calendar-picker/calendar-picker.yaml +2 -2
- package/components/card/card.css +3 -3
- package/components/chart/chart.a2ui.json +29 -18
- package/components/chart/chart.class.js +1308 -266
- package/components/chart/chart.css +151 -1
- package/components/chart/chart.d.ts +9 -9
- package/components/chart/chart.yaml +63 -31
- package/components/chart-legend/chart-legend.a2ui.json +20 -14
- package/components/chart-legend/chart-legend.class.js +79 -58
- package/components/chart-legend/chart-legend.css +65 -1
- package/components/chart-legend/chart-legend.d.ts +5 -5
- package/components/chart-legend/chart-legend.examples.md +4 -4
- package/components/chart-legend/chart-legend.yaml +50 -20
- package/components/check/check.css +9 -0
- package/components/color-area/color-area.a2ui.json +1 -1
- package/components/color-area/color-area.class.js +2 -2
- package/components/color-area/color-area.css +4 -7
- package/components/color-area/color-area.d.ts +1 -1
- package/components/color-area/color-area.yaml +3 -3
- package/components/color-input/color-input.a2ui.json +1 -1
- package/components/color-input/color-input.class.js +1 -1
- package/components/color-input/color-input.yaml +2 -2
- package/components/combobox/combobox.a2ui.json +3 -0
- package/components/combobox/combobox.class.js +9 -8
- package/components/combobox/combobox.css +25 -4
- package/components/combobox/combobox.yaml +6 -0
- package/components/command/command.css +17 -1
- package/components/context-menu/context-menu.d.ts +2 -0
- package/components/drilldown/drilldown.a2ui.json +3 -0
- package/components/drilldown/drilldown.css +8 -1
- package/components/drilldown/drilldown.yaml +2 -0
- package/components/field/field.class.js +5 -5
- package/components/heatmap/heatmap.a2ui.json +5 -0
- package/components/heatmap/heatmap.d.ts +2 -0
- package/components/heatmap/heatmap.yaml +8 -0
- package/components/index.js +0 -4
- package/components/input/input.a2ui.json +4 -4
- package/components/input/input.class.js +20 -7
- package/components/input/input.css +6 -0
- package/components/input/input.yaml +15 -11
- package/components/integration-card/integration-card.d.ts +2 -0
- package/components/menu/menu-item.a2ui.json +3 -8
- package/components/menu/menu-item.yaml +13 -21
- package/components/menu/menu.class.js +5 -27
- package/components/menu/menu.d.ts +1 -3
- package/components/noodles/noodles.a2ui.json +0 -15
- package/components/noodles/noodles.yaml +0 -12
- package/components/number-format/number-format.class.js +10 -0
- package/components/pagination/pagination.class.js +88 -4
- package/components/pane/pane.a2ui.json +2 -12
- package/components/pane/pane.class.js +5 -30
- package/components/pane/pane.d.ts +3 -5
- package/components/pane/pane.yaml +7 -19
- package/components/popover/popover.d.ts +2 -0
- package/components/progress/progress.a2ui.json +1 -1
- package/components/progress/progress.class.js +13 -8
- package/components/progress/progress.d.ts +1 -1
- package/components/progress/progress.yaml +2 -2
- package/components/radio/radio.css +7 -0
- package/components/range/range.class.js +9 -1
- package/components/search/search.css +10 -1
- package/components/segment/segment.a2ui.json +1 -2
- package/components/segment/segment.css +10 -0
- package/components/segment/segment.yaml +1 -2
- package/components/segmented/segmented.a2ui.json +1 -3
- package/components/segmented/segmented.class.js +2 -2
- package/components/segmented/segmented.yaml +2 -4
- package/components/select/select.a2ui.json +5 -1
- package/components/select/select.class.js +14 -14
- package/components/select/select.css +20 -1
- package/components/select/select.yaml +7 -1
- package/components/step-progress/step-progress.yaml +1 -1
- package/components/stream/stream.d.ts +2 -0
- package/components/switch/switch.css +8 -0
- package/components/table/table.a2ui.json +6 -1
- package/components/table/table.class.js +18 -1
- package/components/table/table.css +31 -6
- package/components/table/table.d.ts +3 -1
- package/components/table/table.examples.md +2 -2
- package/components/table/table.yaml +9 -1
- package/components/table-toolbar/table-toolbar.a2ui.json +67 -2
- package/components/table-toolbar/table-toolbar.class.js +492 -17
- package/components/table-toolbar/table-toolbar.css +311 -0
- package/components/table-toolbar/table-toolbar.d.ts +17 -1
- package/components/table-toolbar/table-toolbar.yaml +220 -15
- package/components/tag/tag.class.js +13 -1
- package/components/text/text.a2ui.json +3 -3
- package/components/text/text.d.ts +5 -3
- package/components/text/text.yaml +3 -3
- package/components/textarea/textarea.class.js +9 -1
- package/components/textarea/textarea.css +4 -0
- package/components/time-picker/time-picker.yaml +3 -2
- package/components/tour/tour.d.ts +10 -0
- package/components/tree/tree.yaml +1 -1
- package/components/upload/upload.class.js +16 -0
- package/components/upload/upload.d.ts +7 -0
- package/components/upload/upload.yaml +7 -3
- package/core/a11y.d.ts +22 -0
- package/core/a11y.js +64 -0
- package/core/anchor.js +9 -5
- package/core/element.js +1 -1
- package/core/icons.js +2 -5
- package/custom-elements.json +142 -594
- package/dist/host.min.css +1 -1
- package/dist/host.sheet.js +1 -1
- package/dist/theme-provider.min.js +1 -1
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +122 -119
- package/dist/web-components.sheet.js +1 -1
- package/index.d.ts +0 -15
- package/package.json +1 -1
- package/patterns/access-requests/access-requests.examples.html +1 -1
- package/patterns/admin-keys-and-export/admin-keys-and-export.examples.html +7 -7
- package/patterns/agent-cost/agent-cost.examples.html +6 -6
- package/patterns/agent-memory/agent-memory.examples.html +3 -3
- package/patterns/agent-prompt-library/agent-prompt-library.examples.html +8 -8
- package/patterns/agent-tool-call/agent-tool-call.examples.html +1 -1
- package/patterns/approvals/approvals.examples.html +6 -6
- package/patterns/audit-log/audit-log.examples.html +1 -1
- package/patterns/bulk-action-toolbar/bulk-action-toolbar.examples.html +4 -4
- package/patterns/chart-in-card/chart-in-card.examples.html +124 -10
- package/patterns/chart-in-card/chart-in-card.examples.js +37 -0
- package/patterns/comments-and-collaboration/comments-and-collaboration.examples.html +5 -5
- package/patterns/conversion-funnel/conversion-funnel.html +2 -2
- package/patterns/data-tables-inline-edit-and-tree/data-tables-inline-edit-and-tree.examples.html +2 -2
- package/patterns/diff-review/diff-review.examples.html +6 -6
- package/patterns/editor-shell/editor-shell.examples.html +3 -3
- package/patterns/export-flow/export-flow.examples.html +2 -2
- package/patterns/filter-bar/filter-bar.examples.html +2 -2
- package/patterns/forms-conditional-and-autocomplete/forms-conditional-and-autocomplete.examples.html +2 -2
- package/patterns/inline-dialog/inline-dialog.examples.html +2 -2
- package/patterns/marketing-engagement/marketing-engagement.examples.html +4 -4
- package/patterns/notifications-bell-and-digest/notifications-bell-and-digest.examples.html +1 -1
- package/patterns/permissions-matrix/permissions-matrix.examples.html +1 -1
- package/patterns/permissions-sharing/permissions-sharing.examples.html +2 -2
- package/patterns/profile-public-and-verification/profile-public-and-verification.examples.html +4 -4
- package/patterns/search-discovery/search-discovery.examples.html +2 -2
- package/styles/colors/semantics/features.css +8 -1
- package/styles/components.css +0 -4
- package/styles/type/roles.css +29 -9
- package/traits/arrow-grid-nav/arrow-grid-nav.js +1 -1
- package/traits/resize-observer/resize-observer.js +1 -1
- package/components/color-picker/color-picker.a2ui.json +0 -148
- package/components/color-picker/color-picker.class.js +0 -46
- package/components/color-picker/color-picker.css +0 -14
- package/components/color-picker/color-picker.d.ts +0 -99
- package/components/color-picker/color-picker.examples.md +0 -19
- package/components/color-picker/color-picker.js +0 -17
- package/components/color-picker/color-picker.yaml +0 -175
- package/components/datetime-picker/datetime-picker.a2ui.json +0 -256
- package/components/datetime-picker/datetime-picker.class.js +0 -58
- package/components/datetime-picker/datetime-picker.css +0 -11
- package/components/datetime-picker/datetime-picker.d.ts +0 -87
- package/components/datetime-picker/datetime-picker.examples.md +0 -25
- package/components/datetime-picker/datetime-picker.js +0 -17
- package/components/datetime-picker/datetime-picker.yaml +0 -254
- package/components/progress-row/progress-row.a2ui.json +0 -107
- package/components/progress-row/progress-row.class.js +0 -118
- package/components/progress-row/progress-row.css +0 -60
- package/components/progress-row/progress-row.d.ts +0 -24
- package/components/progress-row/progress-row.examples.md +0 -7
- package/components/progress-row/progress-row.js +0 -17
- package/components/progress-row/progress-row.yaml +0 -85
- package/components/toggle-group/toggle-group.a2ui.json +0 -148
- package/components/toggle-group/toggle-group.class.js +0 -195
- package/components/toggle-group/toggle-group.css +0 -127
- package/components/toggle-group/toggle-group.d.ts +0 -50
- package/components/toggle-group/toggle-group.examples.md +0 -33
- package/components/toggle-group/toggle-group.js +0 -18
- package/components/toggle-group/toggle-group.yaml +0 -135
- package/components/toggle-group/toggle-option.a2ui.json +0 -86
- package/components/toggle-group/toggle-option.yaml +0 -75
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
* • title + optional count badge
|
|
19
19
|
* • filter / sort / columns popover buttons
|
|
20
20
|
* • search input
|
|
21
|
-
* • optional [slot="actions"]
|
|
21
|
+
* • optional [slot="actions-leading"] region, BEFORE the page-size select (gh#1649)
|
|
22
|
+
* • optional page-size select
|
|
23
|
+
* • optional [slot="actions"] trailing region, AFTER the page-size select
|
|
22
24
|
*
|
|
23
25
|
* [for] resolution mirrors chart-legend-ui — the toolbar mounts an element by
|
|
24
26
|
* id, then dispatches state changes against it. When [for] is absent, falls
|
|
@@ -35,10 +37,24 @@
|
|
|
35
37
|
*/
|
|
36
38
|
|
|
37
39
|
import { UIElement } from '../../core/element.js';
|
|
38
|
-
import { anchorPopover } from '../../core/anchor.js';
|
|
40
|
+
import { anchorPopover, resolveLengthPx } from '../../core/anchor.js';
|
|
39
41
|
|
|
40
42
|
const SEARCH_DEBOUNCE = 200;
|
|
41
43
|
|
|
44
|
+
// ADR-0076 REQ-M-006 — the three breakpoints' NORMATIVE default pixel
|
|
45
|
+
// widths (56rem/40rem/26rem at the standard 16px root), duplicated here
|
|
46
|
+
// ONLY as a fallback for #classifyStage() below when the live
|
|
47
|
+
// `--table-toolbar-bp-*` custom property can't be read (no table-toolbar.css
|
|
48
|
+
// stylesheet attached yet, an SSR shim, or a unit-test harness with no real
|
|
49
|
+
// cascade) — resolveLengthPx() returns NaN in that case, `||` falls
|
|
50
|
+
// through to these. Table-toolbar.css's own `:where(:scope)` declarations
|
|
51
|
+
// are the actual source of truth whenever a real stylesheet IS present;
|
|
52
|
+
// this fallback exists so the reflection degrades to the DOCUMENTED
|
|
53
|
+
// default instead of silently misclassifying against NaN.
|
|
54
|
+
const DEFAULT_BP_SEARCH_TIGHT_PX = 56 * 16;
|
|
55
|
+
const DEFAULT_BP_ICON_ONLY_PX = 40 * 16;
|
|
56
|
+
const DEFAULT_BP_OVERFLOW_PX = 26 * 16;
|
|
57
|
+
|
|
42
58
|
function emptyHint(text) {
|
|
43
59
|
const el = document.createElement('text-ui');
|
|
44
60
|
el.setAttribute('data-popover-empty', '');
|
|
@@ -128,18 +144,36 @@ export class UITableToolbar extends UIElement {
|
|
|
128
144
|
for: { type: String, default: '', reflect: true },
|
|
129
145
|
text: { type: String, default: '', reflect: false },
|
|
130
146
|
count: { type: String, default: '', reflect: false },
|
|
147
|
+
rangeStart: { type: Number, default: 0, reflect: true, attribute: 'range-start' },
|
|
148
|
+
rangeEnd: { type: Number, default: 0, reflect: true, attribute: 'range-end' },
|
|
149
|
+
rangeTotal: { type: Number, default: 0, reflect: true, attribute: 'range-total' },
|
|
150
|
+
pageSize: { type: Number, default: 0, reflect: true, attribute: 'page-size' },
|
|
131
151
|
noFilter: { type: Boolean, default: false, reflect: true, attribute: 'no-filter' },
|
|
132
152
|
noSort: { type: Boolean, default: false, reflect: true, attribute: 'no-sort' },
|
|
133
153
|
noColumns: { type: Boolean, default: false, reflect: true, attribute: 'no-columns' },
|
|
134
154
|
noSearch: { type: Boolean, default: false, reflect: true, attribute: 'no-search' },
|
|
155
|
+
// ADR-0076 (ADIA2-9123) — additive sugar over the four `no-*` attributes
|
|
156
|
+
// above. Pure, absolute OR: see #updateControlVisibility(), which is the
|
|
157
|
+
// single place that combines chromeOnly with each granular no-* value.
|
|
158
|
+
chromeOnly: { type: Boolean, default: false, reflect: true, attribute: 'chrome-only' },
|
|
135
159
|
placeholder: { type: String, default: 'Search...', reflect: false },
|
|
136
160
|
variant: { type: String, default: 'default', reflect: true },
|
|
161
|
+
// ADR-0076 REQ-O-001 — explicit compaction-stage override. Unset (the
|
|
162
|
+
// default, empty string) means auto-snap via the @container query
|
|
163
|
+
// (table-toolbar.css's :scope container-type/container-name). All four
|
|
164
|
+
// stages' CSS is now live: search-tight steps the search field down
|
|
165
|
+
// (step 2); icon-only drops control labels and compacts/hides the
|
|
166
|
+
// range summary (steps 3+4); overflow collapses Filter/Sort/Columns
|
|
167
|
+
// into one "More" trigger (step 5).
|
|
168
|
+
stage: { type: String, default: '', reflect: true },
|
|
137
169
|
};
|
|
138
170
|
|
|
139
171
|
// §205 (v0.5.7): dynamic sort-indicator icons (table-toolbar.class.js:576 — nested ternary
|
|
140
172
|
// `dir === 'asc' ? 'arrow-up' : dir === 'desc' ? 'arrow-down' : 'caret-up-down'`).
|
|
141
173
|
// Per FEEDBACK-16 §1 + §209 slot-11 ternary-walker discovery.
|
|
142
|
-
|
|
174
|
+
// 'dots-three-vertical' — the overflow-stage "More" trigger icon
|
|
175
|
+
// (ADR-0076 REQ-S-004, PLAN step 5, #mkOverflowTrigger()).
|
|
176
|
+
static requiredIcons = ['arrow-up', 'arrow-down', 'caret-up-down', 'dots-three-vertical'];
|
|
143
177
|
|
|
144
178
|
static template = () => null;
|
|
145
179
|
|
|
@@ -149,6 +183,30 @@ export class UITableToolbar extends UIElement {
|
|
|
149
183
|
#docListenersBound = false;
|
|
150
184
|
#docListenerRaf = null;
|
|
151
185
|
#sortIndicatorRafs = new Set();
|
|
186
|
+
#pageSizeOptions = [];
|
|
187
|
+
// ADR-0076 REQ-M-005 (ADIA2-9123 S4-ii), PLAN step 7 — data-stage-resolved
|
|
188
|
+
// reflection. #resizeObs classifies live width against the SAME three
|
|
189
|
+
// breakpoints table-toolbar.css's @container rules use (read from the
|
|
190
|
+
// custom properties themselves, never a second hardcoded copy of
|
|
191
|
+
// 56/40/26 — see #classifyStage()). #lastWidthPx caches the last
|
|
192
|
+
// measured inline-size so a `stage` attribute change alone (no resize)
|
|
193
|
+
// can still re-classify without waiting on the next ResizeObserver
|
|
194
|
+
// callback (REQ-M-005's dual-trigger requirement).
|
|
195
|
+
#resizeObs = null;
|
|
196
|
+
#lastWidthPx = null;
|
|
197
|
+
#stageRaf = null;
|
|
198
|
+
|
|
199
|
+
// ── Public API: pageSizeOptions ──────────────────────────────────────────
|
|
200
|
+
// Dynamic array prop (JS-set collection, custom setter) — same shape as
|
|
201
|
+
// table-ui's `columns`/`data`. Deliberately not in `static properties`
|
|
202
|
+
// (gh#970 — an array prop needs its own setter, not the scalar signal path).
|
|
203
|
+
|
|
204
|
+
set pageSizeOptions(arr) {
|
|
205
|
+
this.#pageSizeOptions = Array.isArray(arr) ? arr.map(Number).filter((n) => Number.isFinite(n)) : [];
|
|
206
|
+
this.#updatePageSizeSelect();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
get pageSizeOptions() { return this.#pageSizeOptions; }
|
|
152
210
|
|
|
153
211
|
// §381 (v0.6.10, slice F): one-shot dev-mode warning when consumers
|
|
154
212
|
// set common-but-unknown opt-out attributes (e.g. `searchable`,
|
|
@@ -165,9 +223,88 @@ export class UITableToolbar extends UIElement {
|
|
|
165
223
|
connected() {
|
|
166
224
|
this.setAttribute('role', 'toolbar');
|
|
167
225
|
this.#warnUnknownOptOutAttrs();
|
|
226
|
+
// gh#1615 — hydrate `page-size-options="[10,25,50]"` once at connect,
|
|
227
|
+
// same pattern as table-ui's `data`/`columns` JSON-array attributes
|
|
228
|
+
// (table.class.js connected()). A later programmatic `.pageSizeOptions
|
|
229
|
+
// = [...]` set still wins (guarded by the length check).
|
|
230
|
+
if (this.#pageSizeOptions.length === 0 && this.hasAttribute('page-size-options')) {
|
|
231
|
+
try {
|
|
232
|
+
const parsed = JSON.parse(this.getAttribute('page-size-options'));
|
|
233
|
+
if (Array.isArray(parsed)) this.pageSizeOptions = parsed;
|
|
234
|
+
} catch (_) { /* malformed JSON — leave empty, select renders hidden */ }
|
|
235
|
+
}
|
|
168
236
|
this.#stamp();
|
|
169
237
|
this.#resolveTarget();
|
|
170
238
|
this.#syncFromTarget();
|
|
239
|
+
|
|
240
|
+
// ADR-0076 REQ-M-005, PLAN step 7 — classify synchronously off
|
|
241
|
+
// `clientWidth` at connect time too (0 pre-layout treated as "no
|
|
242
|
+
// measurement yet", the same `null`-width fallback #reflectResolvedStage
|
|
243
|
+
// uses) rather than waiting on the ResizeObserver's own first
|
|
244
|
+
// (asynchronous) callback — avoids a brief connect-time gap with no
|
|
245
|
+
// `data-stage-resolved` reflected at all, mirroring how chart.class.js's
|
|
246
|
+
// own `data-ratio-resolved` is set synchronously from `#dims()` at
|
|
247
|
+
// first render(), not deferred to its ResizeObserver's first tick.
|
|
248
|
+
this.#lastWidthPx = this.clientWidth || null;
|
|
249
|
+
this.#reflectResolvedStage();
|
|
250
|
+
|
|
251
|
+
// gh#285 SSR DOM shims (linkedom) have no ResizeObserver global, same
|
|
252
|
+
// guard chart.class.js's own ResizeObserver setup uses.
|
|
253
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
254
|
+
this.#resizeObs = new ResizeObserver((entries) => {
|
|
255
|
+
const { inlineSize } = entries[0].contentBoxSize[0];
|
|
256
|
+
this.#lastWidthPx = inlineSize;
|
|
257
|
+
// rAF-batch, mirroring chart.class.js's own ResizeObserver setup —
|
|
258
|
+
// avoids a same-tick reflect→layout→observer-refire loop.
|
|
259
|
+
if (this.#stageRaf) return;
|
|
260
|
+
this.#stageRaf = requestAnimationFrame(() => {
|
|
261
|
+
this.#stageRaf = null;
|
|
262
|
+
this.#reflectResolvedStage();
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
this.#resizeObs.observe(this);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// gh#1620 — this used to live in a declared `attributeChanged(name)` hook
|
|
270
|
+
// that the base class never calls (core/element.js only ever invokes
|
|
271
|
+
// `updated(changed)`, keyed by property name, not attribute name); the
|
|
272
|
+
// reactions below silently never fired. Migrated verbatim onto the live
|
|
273
|
+
// hook, keyed by the matching property names.
|
|
274
|
+
updated(changed) {
|
|
275
|
+
if (changed.has('noFilter') || changed.has('noSort')
|
|
276
|
+
|| changed.has('noColumns') || changed.has('noSearch')
|
|
277
|
+
|| changed.has('chromeOnly')) {
|
|
278
|
+
this.#updateControlVisibility();
|
|
279
|
+
}
|
|
280
|
+
if (changed.has('placeholder')) {
|
|
281
|
+
const search = this.querySelector(':scope [data-search]');
|
|
282
|
+
search?.setAttribute('placeholder', this.placeholder);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ADR-0076 REQ-E-001 — `updated(changed)` alone MISSES a live loading→
|
|
287
|
+
// confirmed-zero transition: `rangeTotal`'s default is already 0, so
|
|
288
|
+
// setting `range-total="0"` from an unset start parses to the SAME
|
|
289
|
+
// Number(0), the base class's property setter's `Object.is` short-circuit
|
|
290
|
+
// swallows the signal write (core/element.js `installProps()`), and
|
|
291
|
+
// `changed` never gets a `rangeTotal` entry — `#updateSummary()`'s
|
|
292
|
+
// hasAttribute-based branch never re-runs. Verified live in a browser
|
|
293
|
+
// (not just the vitest suite, which happened to only exercise the
|
|
294
|
+
// positive→zero transition, where the signal DOES change). REQ-E-001's
|
|
295
|
+
// check is fundamentally an ATTRIBUTE-presence concern, not a coerced-
|
|
296
|
+
// value one, so it needs the raw attribute callback, not the signal diff.
|
|
297
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
298
|
+
super.attributeChangedCallback(name, oldVal, newVal);
|
|
299
|
+
if (name === 'range-total') this.#updateSummary();
|
|
300
|
+
// ADR-0076 REQ-M-005 — the SECOND of the two triggers `data-stage-
|
|
301
|
+
// resolved` must react to: a live resize (the ResizeObserver callback
|
|
302
|
+
// above) AND a `stage` attribute change alone, with no resize needed.
|
|
303
|
+
// Re-classifies against the cached last-measured width — a pin set
|
|
304
|
+
// before the first resize observation (#lastWidthPx still null) simply
|
|
305
|
+
// reports the pin; classifyStage() below treats a null width as "no
|
|
306
|
+
// live width to compare against yet", never a false 0-width match.
|
|
307
|
+
if (name === 'stage') this.#reflectResolvedStage();
|
|
171
308
|
}
|
|
172
309
|
|
|
173
310
|
disconnected() {
|
|
@@ -179,6 +316,68 @@ export class UITableToolbar extends UIElement {
|
|
|
179
316
|
this.#sortIndicatorRafs.clear();
|
|
180
317
|
this.#closePopover();
|
|
181
318
|
this.#detachTarget();
|
|
319
|
+
if (this.#stageRaf != null) {
|
|
320
|
+
cancelAnimationFrame(this.#stageRaf);
|
|
321
|
+
this.#stageRaf = null;
|
|
322
|
+
}
|
|
323
|
+
this.#resizeObs?.disconnect();
|
|
324
|
+
this.#resizeObs = null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── data-stage-resolved reflection (ADR-0076 REQ-M-005, ADIA2-9123 ─────────
|
|
328
|
+
// S4-ii, PLAN step 7) ────────────────────────────────────────────────────
|
|
329
|
+
//
|
|
330
|
+
// Purely informational — mirrors the CSS-decided stage for a consumer's
|
|
331
|
+
// own [slot="actions-leading"] content, never drives table-toolbar's own
|
|
332
|
+
// rendering (REQ-S stays 100% CSS `@container`-driven, REQ-M-002). The
|
|
333
|
+
// three breakpoints are read live off the SAME custom properties the CSS
|
|
334
|
+
// `@container` rules consume (--table-toolbar-bp-search-tight/-icon-only/
|
|
335
|
+
// -overflow) via `resolveLengthPx` (core/anchor.js, shared with its
|
|
336
|
+
// --popover-max-height use) — one source of truth, never a second
|
|
337
|
+
// hardcoded copy of 56/40/26 here.
|
|
338
|
+
|
|
339
|
+
#classifyStage(widthPx) {
|
|
340
|
+
const cs = getComputedStyle(this);
|
|
341
|
+
// resolveLengthPx() returns NaN when the custom property is unreadable
|
|
342
|
+
// — no table-toolbar.css stylesheet attached yet, an SSR shim, or a
|
|
343
|
+
// headless unit-test harness with no real cascade — so an explicit
|
|
344
|
+
// Number.isFinite() check falls through to each breakpoint's own
|
|
345
|
+
// documented REQ-M-006 default. This must be a real NaN check, not a
|
|
346
|
+
// `|| DEFAULT` fallback: `0` is a legitimate consumer override (e.g.
|
|
347
|
+
// `--table-toolbar-bp-overflow: 0rem` to disable the overflow stage
|
|
348
|
+
// entirely) and `||` would silently discard it as falsy, substituting
|
|
349
|
+
// the default instead — reviewer finding on PR #1676, fixed here.
|
|
350
|
+
// Whenever a real stylesheet IS present, the live custom property
|
|
351
|
+
// (overridable per-consumer, same posture as every other
|
|
352
|
+
// --table-toolbar-* token) wins, zero included.
|
|
353
|
+
const resolveBp = (raw, fallback) => {
|
|
354
|
+
const v = resolveLengthPx(raw);
|
|
355
|
+
return Number.isFinite(v) ? v : fallback;
|
|
356
|
+
};
|
|
357
|
+
const searchTightPx = resolveBp(cs.getPropertyValue('--table-toolbar-bp-search-tight'), DEFAULT_BP_SEARCH_TIGHT_PX);
|
|
358
|
+
const iconOnlyPx = resolveBp(cs.getPropertyValue('--table-toolbar-bp-icon-only'), DEFAULT_BP_ICON_ONLY_PX);
|
|
359
|
+
const overflowPx = resolveBp(cs.getPropertyValue('--table-toolbar-bp-overflow'), DEFAULT_BP_OVERFLOW_PX);
|
|
360
|
+
|
|
361
|
+
// Inclusive-on-the-narrower-side (max-width semantics, REQ-M-006) — a
|
|
362
|
+
// width exactly AT a boundary classifies to the narrower stage, same
|
|
363
|
+
// rule the CSS `@container (max-width: …)` conditions already encode.
|
|
364
|
+
if (widthPx <= overflowPx) return 'overflow';
|
|
365
|
+
if (widthPx <= iconOnlyPx) return 'icon-only';
|
|
366
|
+
if (widthPx <= searchTightPx) return 'search-tight';
|
|
367
|
+
return 'full';
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
#reflectResolvedStage() {
|
|
371
|
+
// REQ-O-001/REQ-M-005 — the pin wins for the reflection exactly as it
|
|
372
|
+
// wins for rendering: an explicit `stage` reports itself, never the
|
|
373
|
+
// width-derived classification, even inside a container sized for a
|
|
374
|
+
// different stage.
|
|
375
|
+
const resolved = this.stage || (this.#lastWidthPx == null ? 'full' : this.#classifyStage(this.#lastWidthPx));
|
|
376
|
+
// Is-different guard — same shape as chart.class.js's own
|
|
377
|
+
// data-ratio-resolved reflection (chart.class.js:533).
|
|
378
|
+
if (this.getAttribute('data-stage-resolved') !== resolved) {
|
|
379
|
+
this.setAttribute('data-stage-resolved', resolved);
|
|
380
|
+
}
|
|
182
381
|
}
|
|
183
382
|
|
|
184
383
|
render() {
|
|
@@ -186,6 +385,8 @@ export class UITableToolbar extends UIElement {
|
|
|
186
385
|
this.#resolveTarget();
|
|
187
386
|
this.#syncFromTarget();
|
|
188
387
|
this.#updateTitle();
|
|
388
|
+
this.#updateSummary();
|
|
389
|
+
this.#updatePageSizeSelect();
|
|
189
390
|
}
|
|
190
391
|
|
|
191
392
|
// ── Unknown opt-out attribute warning (§381, v0.6.10 slice F) ────────────
|
|
@@ -273,6 +474,20 @@ export class UITableToolbar extends UIElement {
|
|
|
273
474
|
const root = document.createElement('div');
|
|
274
475
|
root.setAttribute('data-toolbar', '');
|
|
275
476
|
|
|
477
|
+
// Scope slot passthrough (gh#1615) — leading region before the title
|
|
478
|
+
// cluster. Same real-insertion-point pattern as [slot="actions"] below:
|
|
479
|
+
// move any pre-existing [slot="scope"] children into a stamped wrapper
|
|
480
|
+
// rather than declaring a template-owned `parts:` entry (ADR-0067 — an
|
|
481
|
+
// author-fillable span is `slots:`, never `parts:`).
|
|
482
|
+
const scopeSlot = document.createElement('div');
|
|
483
|
+
scopeSlot.setAttribute('data-scope', '');
|
|
484
|
+
for (const node of [...this.children]) {
|
|
485
|
+
if (node === root) continue;
|
|
486
|
+
if (node.getAttribute?.('slot') === 'scope') {
|
|
487
|
+
scopeSlot.appendChild(node);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
276
491
|
// Title cluster
|
|
277
492
|
const title = document.createElement('div');
|
|
278
493
|
title.setAttribute('data-title', '');
|
|
@@ -288,12 +503,61 @@ export class UITableToolbar extends UIElement {
|
|
|
288
503
|
badge.hidden = true;
|
|
289
504
|
title.appendChild(badge);
|
|
290
505
|
|
|
506
|
+
// Range summary ("Showing X–Y of N", gh#1615) — its own cluster,
|
|
507
|
+
// independent of [data-title]: coexists with [text]/[count] rather than
|
|
508
|
+
// nesting inside their hide-with-children logic (ADIA2-8897).
|
|
509
|
+
const summary = document.createElement('div');
|
|
510
|
+
summary.setAttribute('data-summary', '');
|
|
511
|
+
summary.hidden = true;
|
|
512
|
+
const summaryText = document.createElement('span');
|
|
513
|
+
summaryText.setAttribute('data-summary-text', '');
|
|
514
|
+
summary.appendChild(summaryText);
|
|
515
|
+
|
|
516
|
+
// ADR-0076 REQ-S-003 — short-form compact text ("1–25/320"), a sibling
|
|
517
|
+
// of [data-summary-text] holding the SAME range values in a terser
|
|
518
|
+
// shape. Which of the two is visible is decided entirely by CSS
|
|
519
|
+
// (table-toolbar.css's icon-only @container rule / [stage] pin) —
|
|
520
|
+
// both nodes are always populated together in #updateSummary(), never
|
|
521
|
+
// a JS branch keyed on a resolved-stage signal (REQ-M-002 stays true:
|
|
522
|
+
// no JS decides rendering here, only which text each stage's CSS shows).
|
|
523
|
+
const summaryCompact = document.createElement('span');
|
|
524
|
+
summaryCompact.setAttribute('data-summary-compact', '');
|
|
525
|
+
summaryCompact.hidden = true;
|
|
526
|
+
summary.appendChild(summaryCompact);
|
|
527
|
+
|
|
528
|
+
// Empty-state slot (ADR-0076 REQ-E, adiav2 ADIA2-9123) — renders in
|
|
529
|
+
// [data-summary]'s own position, alongside [data-summary-text], never a
|
|
530
|
+
// separate region: only one of the two is ever visible at a time (see
|
|
531
|
+
// #updateSummary()). Same real-insertion-point pattern as [slot="scope"]
|
|
532
|
+
// / [slot="actions"] / [slot="actions-leading"] above — move any
|
|
533
|
+
// pre-existing [slot="empty"] children into this stamped wrapper rather
|
|
534
|
+
// than declaring a template-owned `parts:` entry (ADR-0067).
|
|
535
|
+
const summaryEmpty = document.createElement('div');
|
|
536
|
+
summaryEmpty.setAttribute('data-summary-empty', '');
|
|
537
|
+
summaryEmpty.hidden = true;
|
|
538
|
+
for (const node of [...this.children]) {
|
|
539
|
+
if (node === root) continue;
|
|
540
|
+
if (node.getAttribute?.('slot') === 'empty') {
|
|
541
|
+
summaryEmpty.appendChild(node);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
summary.appendChild(summaryEmpty);
|
|
545
|
+
|
|
291
546
|
// Controls cluster
|
|
292
547
|
const controls = document.createElement('div');
|
|
293
548
|
controls.setAttribute('data-controls', '');
|
|
294
549
|
controls.appendChild(this.#mkButton('filter', 'Filter', 'funnel-simple'));
|
|
295
550
|
controls.appendChild(this.#mkButton('sort', 'Sort', 'arrows-down-up'));
|
|
296
551
|
controls.appendChild(this.#mkButton('columns', 'Columns', 'columns'));
|
|
552
|
+
// ADR-0076 REQ-S-004/REQ-X-003, PLAN step 5 — overflow-stage "More"
|
|
553
|
+
// trigger. Stamped unconditionally alongside the three granular
|
|
554
|
+
// buttons above (never removed/re-created), CSS-hidden except at the
|
|
555
|
+
// overflow breakpoint/pin (REQ-M-002 stays true: which cluster paints
|
|
556
|
+
// is a pure CSS decision, not a JS branch). Keeping all four buttons
|
|
557
|
+
// permanently in the DOM is the simplest way to honor REQ-X-004's
|
|
558
|
+
// tab-order guarantee — nothing is ever created/destroyed across a
|
|
559
|
+
// stage transition, only shown/hidden.
|
|
560
|
+
controls.appendChild(this.#mkOverflowTrigger());
|
|
297
561
|
|
|
298
562
|
// Search — compose <search-ui>, which already stamps input-ui with
|
|
299
563
|
// the magnifying-glass prefix + clear suffix and debounces a `search`
|
|
@@ -307,6 +571,38 @@ export class UITableToolbar extends UIElement {
|
|
|
307
571
|
search.setAttribute('debounce', String(SEARCH_DEBOUNCE));
|
|
308
572
|
search.addEventListener('search', this.#onSearch);
|
|
309
573
|
|
|
574
|
+
// Page-size select (gh#1615) — hidden until pageSizeOptions is set
|
|
575
|
+
// (additive opt-in, zero footprint on existing consumers). Carries the
|
|
576
|
+
// same `margin-inline-start: auto` as [data-search] so the trailing
|
|
577
|
+
// group (controls/search/page-size/actions) still pushes to the far
|
|
578
|
+
// edge when [no-search] drops search — without touching search's own
|
|
579
|
+
// existing default behavior for consumers that never set pageSizeOptions.
|
|
580
|
+
const pageSize = document.createElement('select-ui');
|
|
581
|
+
pageSize.setAttribute('data-page-size', '');
|
|
582
|
+
pageSize.setAttribute('size', 'sm');
|
|
583
|
+
pageSize.setAttribute('aria-label', 'Rows per page');
|
|
584
|
+
pageSize.hidden = true;
|
|
585
|
+
pageSize.addEventListener('change', this.#onPageSizeChange);
|
|
586
|
+
|
|
587
|
+
// Actions-leading slot passthrough (gh#1649) — a real insertion point
|
|
588
|
+
// (ADR-0067), same pattern as [slot="scope"]/[slot="actions"] above,
|
|
589
|
+
// rendered BEFORE the page-size select rather than after it: app-owned
|
|
590
|
+
// trigger buttons (e.g. custom Filter/Columns triggers that open the
|
|
591
|
+
// consumer's own drawer instead of the native filter/columns popovers)
|
|
592
|
+
// need to land ahead of page-size, which [slot="actions"] alone can't
|
|
593
|
+
// reach since it renders trailing (after page-size).
|
|
594
|
+
const actionsLeadingSlot = document.createElement('div');
|
|
595
|
+
actionsLeadingSlot.setAttribute('data-actions-leading', '');
|
|
596
|
+
// Move any pre-existing [slot="actions-leading"] children into this
|
|
597
|
+
// stamped wrapper — same real-insertion-point pattern as [slot="scope"]
|
|
598
|
+
// / [slot="actions"] above.
|
|
599
|
+
for (const node of [...this.children]) {
|
|
600
|
+
if (node === root) continue;
|
|
601
|
+
if (node.getAttribute?.('slot') === 'actions-leading') {
|
|
602
|
+
actionsLeadingSlot.appendChild(node);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
310
606
|
// Actions slot passthrough — we move any pre-existing [slot="actions"] children here
|
|
311
607
|
const actionsSlot = document.createElement('div');
|
|
312
608
|
actionsSlot.setAttribute('data-actions', '');
|
|
@@ -317,14 +613,20 @@ export class UITableToolbar extends UIElement {
|
|
|
317
613
|
}
|
|
318
614
|
}
|
|
319
615
|
|
|
616
|
+
root.appendChild(scopeSlot);
|
|
320
617
|
root.appendChild(title);
|
|
618
|
+
root.appendChild(summary);
|
|
321
619
|
root.appendChild(controls);
|
|
322
620
|
root.appendChild(search);
|
|
621
|
+
root.appendChild(actionsLeadingSlot);
|
|
622
|
+
root.appendChild(pageSize);
|
|
323
623
|
root.appendChild(actionsSlot);
|
|
324
624
|
|
|
325
625
|
this.appendChild(root);
|
|
326
626
|
this.#updateTitle();
|
|
627
|
+
this.#updateSummary();
|
|
327
628
|
this.#updateControlVisibility();
|
|
629
|
+
this.#updatePageSizeSelect();
|
|
328
630
|
}
|
|
329
631
|
|
|
330
632
|
#mkPopoverAction(label, onClick) {
|
|
@@ -346,6 +648,14 @@ export class UITableToolbar extends UIElement {
|
|
|
346
648
|
btn.setAttribute('variant', 'outline');
|
|
347
649
|
btn.setAttribute('size', 'sm');
|
|
348
650
|
btn.setAttribute('aria-haspopup', 'menu');
|
|
651
|
+
// ADR-0076 REQ-X-002 — unconditional (harmless at full-text stages,
|
|
652
|
+
// becomes the hover-tooltip affordance at the icon-only stage once
|
|
653
|
+
// the rendered label is CSS-hidden, REQ-M-003). button-ui's own
|
|
654
|
+
// title→aria-label mirror (button.class.js) only fires when no
|
|
655
|
+
// aria-label is already present — button-ui stamps aria-label from
|
|
656
|
+
// [text] unconditionally in its own render(), so this never conflicts
|
|
657
|
+
// with REQ-X-001's already-set accessible name.
|
|
658
|
+
btn.setAttribute('title', label);
|
|
349
659
|
btn.addEventListener('click', (e) => {
|
|
350
660
|
e.stopPropagation();
|
|
351
661
|
this.#togglePopover(kind, btn);
|
|
@@ -353,6 +663,55 @@ export class UITableToolbar extends UIElement {
|
|
|
353
663
|
return btn;
|
|
354
664
|
}
|
|
355
665
|
|
|
666
|
+
// ADR-0076 REQ-S-004/REQ-X-003, PLAN step 5 — the overflow-stage "More"
|
|
667
|
+
// trigger. Reuses menu-ui/menu-item-ui unchanged (table-toolbar already
|
|
668
|
+
// `composes: menu-item-ui`; menu-ui is added to the yaml's composes list
|
|
669
|
+
// alongside it — no new runtime dependency, both already ship in this
|
|
670
|
+
// package) and routes every item straight through the EXISTING
|
|
671
|
+
// #togglePopover(kind, btn) — the same method the three granular buttons
|
|
672
|
+
// above call, never a reimplementation of filter/sort/columns logic.
|
|
673
|
+
#mkOverflowTrigger() {
|
|
674
|
+
const menu = document.createElement('menu-ui');
|
|
675
|
+
menu.setAttribute('data-toolbar-overflow', '');
|
|
676
|
+
|
|
677
|
+
const trigger = document.createElement('button-ui');
|
|
678
|
+
trigger.setAttribute('slot', 'trigger');
|
|
679
|
+
trigger.setAttribute('icon', 'dots-three-vertical');
|
|
680
|
+
trigger.setAttribute('variant', 'outline');
|
|
681
|
+
trigger.setAttribute('size', 'sm');
|
|
682
|
+
// REQ-X-003 — a genuinely new interactive element (not a repurposed
|
|
683
|
+
// existing button), so it needs its own static accessible name rather
|
|
684
|
+
// than an inherited one. No [text] attribute — always icon-only,
|
|
685
|
+
// button-ui's own render() reflects [data-icon-only] geometry from
|
|
686
|
+
// that (no CSS override needed here, unlike REQ-M-004's granular
|
|
687
|
+
// buttons which carry a real text label at wider stages).
|
|
688
|
+
trigger.setAttribute('aria-label', 'More table actions');
|
|
689
|
+
trigger.setAttribute('aria-haspopup', 'menu');
|
|
690
|
+
menu.appendChild(trigger);
|
|
691
|
+
|
|
692
|
+
for (const [kind, label, icon] of [
|
|
693
|
+
['filter', 'Filter', 'funnel-simple'],
|
|
694
|
+
['sort', 'Sort', 'arrows-down-up'],
|
|
695
|
+
['columns', 'Columns', 'columns'],
|
|
696
|
+
]) {
|
|
697
|
+
const item = document.createElement('menu-item-ui');
|
|
698
|
+
item.setAttribute('icon', icon);
|
|
699
|
+
item.setAttribute('text', label);
|
|
700
|
+
item.setAttribute('value', kind);
|
|
701
|
+
menu.appendChild(item);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// menu-ui fires `action` with { value, text } (menu.class.js's own
|
|
705
|
+
// #onItemClick) — anchor the resulting popover on the "More" trigger
|
|
706
|
+
// itself (not any of the three hidden granular buttons), same
|
|
707
|
+
// #togglePopover(kind, btn) every other entry point already calls.
|
|
708
|
+
menu.addEventListener('action', (e) => {
|
|
709
|
+
this.#togglePopover(e.detail.value, trigger);
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
return menu;
|
|
713
|
+
}
|
|
714
|
+
|
|
356
715
|
#updateTitle() {
|
|
357
716
|
const heading = this.querySelector(':scope [data-heading]');
|
|
358
717
|
if (heading) {
|
|
@@ -397,29 +756,145 @@ export class UITableToolbar extends UIElement {
|
|
|
397
756
|
}
|
|
398
757
|
}
|
|
399
758
|
|
|
759
|
+
// ── Range summary ("Showing X–Y of N", gh#1615) ─────────────────────────
|
|
760
|
+
// Independent of #updateTitle's [data-title] cluster by design — coexists
|
|
761
|
+
// with [text]/[count] rather than sharing their hide-with-children logic.
|
|
762
|
+
|
|
763
|
+
// ADR-0076 REQ-E — the `empty` slot renders ONLY on an explicit
|
|
764
|
+
// range-total="0" (checked via hasAttribute, never the computed number
|
|
765
|
+
// alone — REQ-E-001/AC-013: an unset range-total, adiav2's loading state,
|
|
766
|
+
// must never flash the empty-state message). A positive range-total
|
|
767
|
+
// always wins over empty-slot content (REQ-E-002's additive guarantee /
|
|
768
|
+
// AC-014's mutual-exclusion check) — the two states can never render
|
|
769
|
+
// simultaneously.
|
|
770
|
+
#updateSummary() {
|
|
771
|
+
const summary = this.querySelector(':scope > [data-toolbar] > [data-summary]');
|
|
772
|
+
if (!summary) return;
|
|
773
|
+
const text = summary.querySelector(':scope > [data-summary-text]');
|
|
774
|
+
const compact = summary.querySelector(':scope > [data-summary-compact]');
|
|
775
|
+
const empty = summary.querySelector(':scope > [data-summary-empty]');
|
|
776
|
+
const start = Number(this.rangeStart) || 0;
|
|
777
|
+
const end = Number(this.rangeEnd) || 0;
|
|
778
|
+
const total = Number(this.rangeTotal) || 0;
|
|
779
|
+
|
|
780
|
+
if (start > 0 && end > 0 && total > 0) {
|
|
781
|
+
// Normal range text — always wins over the empty slot (AC-014).
|
|
782
|
+
// Both the full ("Showing X–Y of N") and short-form ("X–Y/N", ADR-0076
|
|
783
|
+
// REQ-S-003) strings are populated together — which one paints is a
|
|
784
|
+
// pure CSS decision (icon-only @container rule / [stage] pin), never
|
|
785
|
+
// a JS branch (REQ-M-002).
|
|
786
|
+
if (text) text.textContent = `Showing ${start}–${end} of ${total}`;
|
|
787
|
+
if (text) text.hidden = false;
|
|
788
|
+
if (compact) compact.textContent = `${start}–${end}/${total}`;
|
|
789
|
+
if (compact) compact.hidden = false;
|
|
790
|
+
if (empty) empty.hidden = true;
|
|
791
|
+
summary.hidden = false;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const confirmedZero = this.hasAttribute('range-total') && this.getAttribute('range-total') === '0';
|
|
796
|
+
const hasEmptyContent = !!empty && empty.childNodes.length > 0;
|
|
797
|
+
|
|
798
|
+
if (confirmedZero && hasEmptyContent) {
|
|
799
|
+
// Explicit range-total="0" + author-supplied empty content (AC-011).
|
|
800
|
+
if (text) text.hidden = true;
|
|
801
|
+
if (compact) compact.hidden = true;
|
|
802
|
+
empty.hidden = false;
|
|
803
|
+
summary.hidden = false;
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// Loading (range-total unset, AC-013) or confirmed-zero-with-no-slot-
|
|
808
|
+
// content (AC-012) — hide [data-summary] exactly as today (REQ-E-002).
|
|
809
|
+
if (text) text.hidden = true;
|
|
810
|
+
if (compact) compact.hidden = true;
|
|
811
|
+
if (empty) empty.hidden = true;
|
|
812
|
+
summary.hidden = true;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// ── Page-size select (gh#1615) ───────────────────────────────────────────
|
|
816
|
+
// Hidden whenever pageSizeOptions is empty — additive opt-in, matching the
|
|
817
|
+
// [slot="actions"]:empty pattern's "no footprint unless a consumer opts in".
|
|
818
|
+
|
|
819
|
+
#updatePageSizeSelect() {
|
|
820
|
+
const select = this.querySelector(':scope > [data-toolbar] > [data-page-size]');
|
|
821
|
+
if (!select) return;
|
|
822
|
+
const options = this.#pageSizeOptions;
|
|
823
|
+
if (!options.length) {
|
|
824
|
+
select.hidden = true;
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
select.hidden = false;
|
|
828
|
+
|
|
829
|
+
// `.options = [...]` (select-ui's documented programmatic API) rather
|
|
830
|
+
// than appending light-DOM <option> children: select-ui's own
|
|
831
|
+
// #parseOptions() PARSES-THEN-REMOVES any <option>/<optgroup> children
|
|
832
|
+
// once connected, and only re-parses newly-appended ones via an async
|
|
833
|
+
// MutationObserver — appending after this element is already connected
|
|
834
|
+
// (which it is, by the time a later pageSizeOptions/pageSize change
|
|
835
|
+
// reaches this method) races that observer. `.options=` is synchronous
|
|
836
|
+
// and connection-state-agnostic, matching table-toolbar's own
|
|
837
|
+
// initial-mount-through-any-later-update determinism elsewhere.
|
|
838
|
+
const desired = options.map((n) => ({ value: String(n), label: `${n} / page` }));
|
|
839
|
+
select.options = desired;
|
|
840
|
+
|
|
841
|
+
const size = Number(this.pageSize) || 0;
|
|
842
|
+
const value = options.includes(size) ? size : options[0];
|
|
843
|
+
select.value = String(value);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
#onPageSizeChange = (e) => {
|
|
847
|
+
const pageSize = Number(e.detail?.value ?? this.querySelector(':scope [data-page-size]')?.value) || 0;
|
|
848
|
+
this.pageSize = pageSize;
|
|
849
|
+
// Applied directly to the bound table's own [paginate] (rows-per-page)
|
|
850
|
+
// prop — the same "mirror to URL/analytics via the event, but the
|
|
851
|
+
// toolbar already wires the change into the target" pattern
|
|
852
|
+
// columns-change/sort-change already use.
|
|
853
|
+
if (this.#target) this.#target.paginate = pageSize;
|
|
854
|
+
this.dispatchEvent(new CustomEvent('page-size-change', {
|
|
855
|
+
bubbles: true,
|
|
856
|
+
detail: { pageSize },
|
|
857
|
+
}));
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
// ADR-0076 REQ-C-002 — `chromeOnly` is PURE, ABSOLUTE OR over the four
|
|
861
|
+
// granular `no-*` attributes: while set, all four stay off regardless of
|
|
862
|
+
// any individual `no-*` attribute's own value, with no partial re-enable
|
|
863
|
+
// path (AC-010). This is the ONLY place chromeOnly is combined with the
|
|
864
|
+
// granular attributes — never re-derive the OR elsewhere.
|
|
400
865
|
#updateControlVisibility() {
|
|
401
866
|
const root = this.querySelector(':scope > [data-toolbar]');
|
|
402
867
|
if (!root) return;
|
|
403
868
|
|
|
869
|
+
const chromeOnly = this.chromeOnly;
|
|
404
870
|
const setHidden = (sel, hidden) => {
|
|
405
871
|
const el = root.querySelector(sel);
|
|
406
872
|
if (el) el.hidden = hidden;
|
|
407
873
|
};
|
|
408
874
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
875
|
+
const filterHidden = chromeOnly || this.noFilter;
|
|
876
|
+
const sortHidden = chromeOnly || this.noSort;
|
|
877
|
+
const columnsHidden = chromeOnly || this.noColumns;
|
|
878
|
+
|
|
879
|
+
setHidden('[data-toolbar-btn="filter"]', filterHidden);
|
|
880
|
+
setHidden('[data-toolbar-btn="sort"]', sortHidden);
|
|
881
|
+
setHidden('[data-toolbar-btn="columns"]', columnsHidden);
|
|
882
|
+
setHidden('[data-search]', chromeOnly || this.noSearch);
|
|
883
|
+
|
|
884
|
+
// ADR-0076 REQ-S-004, PLAN step 5 — the overflow "More" trigger's own
|
|
885
|
+
// menu-item-ui children mirror the SAME granular/chrome-only
|
|
886
|
+
// suppression as the three native buttons above: a control suppressed
|
|
887
|
+
// via no-*/chrome-only stays suppressed inside the overflow menu too,
|
|
888
|
+
// never resurrected there. Hide the whole trigger only once every item
|
|
889
|
+
// it would offer is suppressed — an empty "More" menu is worse than no
|
|
890
|
+
// trigger at all.
|
|
891
|
+
const overflow = root.querySelector('[data-toolbar-overflow]');
|
|
892
|
+
if (overflow) {
|
|
893
|
+
const hiddenByKind = { filter: filterHidden, sort: sortHidden, columns: columnsHidden };
|
|
894
|
+
for (const item of overflow.querySelectorAll('menu-item-ui')) {
|
|
895
|
+
item.hidden = !!hiddenByKind[item.getAttribute('value')];
|
|
896
|
+
}
|
|
897
|
+
overflow.hidden = filterHidden && sortHidden && columnsHidden;
|
|
423
898
|
}
|
|
424
899
|
}
|
|
425
900
|
|