@zeyos/zx 2.1.0 → 2.2.0
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 +39 -0
- package/README.md +15 -0
- package/dist/types/compat/base.d.ts +156 -0
- package/dist/types/compat/globals.d.ts +20 -0
- package/dist/types/compat/index.d.ts +128 -0
- package/dist/types/compat/map/basic.d.ts +175 -0
- package/dist/types/compat/map/data.d.ts +102 -0
- package/dist/types/compat/map/date-time.d.ts +80 -0
- package/dist/types/compat/map/factory.d.ts +9 -0
- package/dist/types/compat/map/form.d.ts +98 -0
- package/dist/types/compat/map/helpers.d.ts +12 -0
- package/dist/types/compat/map/http.d.ts +36 -0
- package/dist/types/compat/map/layout.d.ts +106 -0
- package/dist/types/compat/map/options.d.ts +80 -0
- package/dist/types/compat/map/select.d.ts +95 -0
- package/dist/types/compat/map/stubs.d.ts +32 -0
- package/dist/types/compat/map/utilities.d.ts +37 -0
- package/dist/types/compat-entry.d.ts +5 -0
- package/dist/types/components/badge/badge.d.ts +46 -0
- package/dist/types/components/breadcrumb/breadcrumb.d.ts +200 -0
- package/dist/types/components/button/button.d.ts +42 -0
- package/dist/types/components/check-button/check-button.d.ts +98 -0
- package/dist/types/components/checklist/checklist.d.ts +143 -0
- package/dist/types/components/checklist/field-adapter.d.ts +5 -0
- package/dist/types/components/context-menu/context-menu.d.ts +130 -0
- package/dist/types/components/copy/copy-button.d.ts +48 -0
- package/dist/types/components/copy/copy-input.d.ts +91 -0
- package/dist/types/components/data-filter/data-filter.d.ts +250 -0
- package/dist/types/components/data-filter/filter-core.d.ts +52 -0
- package/dist/types/components/date-picker/date-picker.d.ts +137 -0
- package/dist/types/components/date-picker/date-range-picker.d.ts +350 -0
- package/dist/types/components/date-picker/field-adapter.d.ts +6 -0
- package/dist/types/components/date-picker/month-picker.d.ts +75 -0
- package/dist/types/components/date-picker/range-field-adapter.d.ts +7 -0
- package/dist/types/components/date-picker/time-picker.d.ts +127 -0
- package/dist/types/components/datebox/date-range-box.d.ts +218 -0
- package/dist/types/components/datebox/datebox.d.ts +160 -0
- package/dist/types/components/datebox/field-adapter.d.ts +5 -0
- package/dist/types/components/dialog/dialog.d.ts +284 -0
- package/dist/types/components/dropdown/dropdown.d.ts +118 -0
- package/dist/types/components/empty-state/empty-state.d.ts +38 -0
- package/dist/types/components/field/field.d.ts +231 -0
- package/dist/types/components/field-upload/field-adapter.d.ts +5 -0
- package/dist/types/components/field-upload/field-upload.d.ts +213 -0
- package/dist/types/components/fieldset/fieldset.d.ts +125 -0
- package/dist/types/components/finder/finder.d.ts +252 -0
- package/dist/types/components/form/form.d.ts +194 -0
- package/dist/types/components/groupbox/groupbox.d.ts +89 -0
- package/dist/types/components/layout/layout.d.ts +132 -0
- package/dist/types/components/loading/inline-loading.d.ts +86 -0
- package/dist/types/components/loading/progress-bar.d.ts +168 -0
- package/dist/types/components/loading/spinner.d.ts +34 -0
- package/dist/types/components/master-panel/master-panel.d.ts +112 -0
- package/dist/types/components/menu-button/menu-button.d.ts +142 -0
- package/dist/types/components/message/message.d.ts +212 -0
- package/dist/types/components/modal/modal.d.ts +105 -0
- package/dist/types/components/multi-value-editor/field-adapter.d.ts +5 -0
- package/dist/types/components/multi-value-editor/multi-value-editor.d.ts +66 -0
- package/dist/types/components/navigation-bar/navigation-bar.d.ts +229 -0
- package/dist/types/components/number-field/field-adapter.d.ts +5 -0
- package/dist/types/components/number-field/number-field.d.ts +292 -0
- package/dist/types/components/pagination/pagination.d.ts +267 -0
- package/dist/types/components/panel/panel.d.ts +194 -0
- package/dist/types/components/permission/permission.d.ts +93 -0
- package/dist/types/components/rating/field-adapter.d.ts +5 -0
- package/dist/types/components/rating/rating.d.ts +213 -0
- package/dist/types/components/search/search.d.ts +98 -0
- package/dist/types/components/select/field-adapter.d.ts +5 -0
- package/dist/types/components/select/filter.d.ts +11 -0
- package/dist/types/components/select/select.d.ts +235 -0
- package/dist/types/components/skeleton/skeleton.d.ts +97 -0
- package/dist/types/components/slider/field-adapter.d.ts +5 -0
- package/dist/types/components/slider/slider.d.ts +216 -0
- package/dist/types/components/split-view/split-view.d.ts +335 -0
- package/dist/types/components/stepper/stepper.d.ts +314 -0
- package/dist/types/components/stepper/steps.d.ts +131 -0
- package/dist/types/components/tabbox/tabbox.d.ts +230 -0
- package/dist/types/components/table/sort.d.ts +49 -0
- package/dist/types/components/table/table.d.ts +872 -0
- package/dist/types/components/tag-picker/field-adapter.d.ts +5 -0
- package/dist/types/components/tag-picker/tag-picker.d.ts +375 -0
- package/dist/types/components/timebox/field-adapter.d.ts +5 -0
- package/dist/types/components/timebox/timebox.d.ts +121 -0
- package/dist/types/components/toggle/field-adapter.d.ts +5 -0
- package/dist/types/components/toggle/toggle.d.ts +94 -0
- package/dist/types/components/toolbar/toolbar.d.ts +230 -0
- package/dist/types/components/tooltip/tooltip.d.ts +183 -0
- package/dist/types/components/tree/hierarchy.d.ts +113 -0
- package/dist/types/components/tree/tree.d.ts +383 -0
- package/dist/types/components/truncate/truncate.d.ts +61 -0
- package/dist/types/components/value-list/field-adapter.d.ts +5 -0
- package/dist/types/components/value-list/value-list.d.ts +143 -0
- package/dist/types/core/breakpoint.d.ts +68 -0
- package/dist/types/core/component.d.ts +115 -0
- package/dist/types/core/date.d.ts +50 -0
- package/dist/types/core/dom.d.ts +86 -0
- package/dist/types/core/export.d.ts +70 -0
- package/dist/types/core/fontawesome.d.ts +107 -0
- package/dist/types/core/format.d.ts +107 -0
- package/dist/types/core/http.d.ts +122 -0
- package/dist/types/core/i18n.d.ts +35 -0
- package/dist/types/core/icons.d.ts +137 -0
- package/dist/types/core/keyboard.d.ts +72 -0
- package/dist/types/core/position.d.ts +45 -0
- package/dist/types/core/storage.d.ts +46 -0
- package/dist/types/core/util.d.ts +117 -0
- package/dist/types/elements/define.d.ts +73 -0
- package/dist/types/elements/reflect.d.ts +27 -0
- package/dist/types/index.d.ts +79 -0
- package/dist/types/zeyos/connect.d.ts +65 -0
- package/dist/types/zeyos/form.d.ts +140 -0
- package/dist/types/zeyos/icons.d.ts +104 -0
- package/dist/types/zeyos/index.d.ts +12 -0
- package/dist/types/zeyos/modules.d.ts +110 -0
- package/dist/types/zeyos/query.d.ts +157 -0
- package/dist/types/zeyos/schema.d.ts +79 -0
- package/dist/types/zeyos/select.d.ts +167 -0
- package/dist/types/zeyos/table.d.ts +205 -0
- package/dist/zx-compat.esm.js +362 -33
- package/dist/zx-compat.esm.min.js +1 -1
- package/dist/zx-compat.global.js +359 -67
- package/dist/zx-compat.global.min.js +3 -3
- package/dist/zx-zeyos.esm.js +342 -43
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +68 -6
- package/dist/zx.esm.js +346 -67
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +346 -67
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +48 -2
- package/docs/llms.md +28 -0
- package/package.json +37 -9
- package/src/compat/base.js +291 -0
- package/src/compat/globals.js +165 -0
- package/src/compat/index.js +84 -0
- package/src/compat/map/basic.js +464 -0
- package/src/compat/map/data.js +232 -0
- package/src/compat/map/date-time.js +177 -0
- package/src/compat/map/factory.js +45 -0
- package/src/compat/map/form.js +330 -0
- package/src/compat/map/helpers.js +46 -0
- package/src/compat/map/http.js +117 -0
- package/src/compat/map/layout.js +256 -0
- package/src/compat/map/options.js +267 -0
- package/src/compat/map/select.js +198 -0
- package/src/compat/map/stubs.js +26 -0
- package/src/compat/map/utilities.js +113 -0
- package/src/compat-entry.js +11 -0
- package/src/components/badge/badge.css +147 -0
- package/src/components/badge/badge.js +92 -0
- package/src/components/breadcrumb/breadcrumb.css +105 -0
- package/src/components/breadcrumb/breadcrumb.js +331 -0
- package/src/components/button/button.css +104 -0
- package/src/components/button/button.js +76 -0
- package/src/components/check-button/check-button.css +66 -0
- package/src/components/check-button/check-button.js +167 -0
- package/src/components/checklist/checklist.css +65 -0
- package/src/components/checklist/checklist.js +282 -0
- package/src/components/checklist/field-adapter.js +46 -0
- package/src/components/context-menu/context-menu.css +54 -0
- package/src/components/context-menu/context-menu.js +334 -0
- package/src/components/copy/copy-button.css +27 -0
- package/src/components/copy/copy-button.js +78 -0
- package/src/components/copy/copy-input.css +59 -0
- package/src/components/copy/copy-input.js +123 -0
- package/src/components/data-filter/data-filter.css +53 -0
- package/src/components/data-filter/data-filter.js +400 -0
- package/src/components/data-filter/filter-core.js +128 -0
- package/src/components/date-picker/date-picker.css +181 -0
- package/src/components/date-picker/date-picker.js +500 -0
- package/src/components/date-picker/date-range-picker.css +264 -0
- package/src/components/date-picker/date-range-picker.js +990 -0
- package/src/components/date-picker/field-adapter.js +36 -0
- package/src/components/date-picker/month-picker.js +223 -0
- package/src/components/date-picker/range-field-adapter.js +31 -0
- package/src/components/date-picker/time-picker.css +191 -0
- package/src/components/date-picker/time-picker.js +406 -0
- package/src/components/datebox/date-range-box.css +128 -0
- package/src/components/datebox/date-range-box.js +492 -0
- package/src/components/datebox/datebox.css +108 -0
- package/src/components/datebox/datebox.js +358 -0
- package/src/components/datebox/field-adapter.js +34 -0
- package/src/components/dialog/dialog.css +74 -0
- package/src/components/dialog/dialog.js +450 -0
- package/src/components/dropdown/dropdown.css +24 -0
- package/src/components/dropdown/dropdown.js +288 -0
- package/src/components/empty-state/empty-state.css +80 -0
- package/src/components/empty-state/empty-state.js +92 -0
- package/src/components/field/field.css +127 -0
- package/src/components/field/field.js +605 -0
- package/src/components/field-upload/field-adapter.js +21 -0
- package/src/components/field-upload/field-upload.css +108 -0
- package/src/components/field-upload/field-upload.js +475 -0
- package/src/components/fieldset/fieldset.css +37 -0
- package/src/components/fieldset/fieldset.js +307 -0
- package/src/components/finder/finder.css +98 -0
- package/src/components/finder/finder.js +509 -0
- package/src/components/form/form.css +32 -0
- package/src/components/form/form.js +302 -0
- package/src/components/groupbox/groupbox.css +51 -0
- package/src/components/groupbox/groupbox.js +199 -0
- package/src/components/layout/layout.css +117 -0
- package/src/components/layout/layout.js +136 -0
- package/src/components/loading/inline-loading.css +34 -0
- package/src/components/loading/inline-loading.js +140 -0
- package/src/components/loading/progress-bar.css +115 -0
- package/src/components/loading/progress-bar.js +259 -0
- package/src/components/loading/spinner.css +58 -0
- package/src/components/loading/spinner.js +57 -0
- package/src/components/master-panel/master-panel.css +51 -0
- package/src/components/master-panel/master-panel.js +203 -0
- package/src/components/menu-button/menu-button.css +49 -0
- package/src/components/menu-button/menu-button.js +294 -0
- package/src/components/message/message.css +122 -0
- package/src/components/message/message.js +499 -0
- package/src/components/modal/modal.css +47 -0
- package/src/components/modal/modal.js +203 -0
- package/src/components/multi-value-editor/field-adapter.js +44 -0
- package/src/components/multi-value-editor/multi-value-editor.css +56 -0
- package/src/components/multi-value-editor/multi-value-editor.js +170 -0
- package/src/components/navigation-bar/navigation-bar.css +130 -0
- package/src/components/navigation-bar/navigation-bar.js +450 -0
- package/src/components/number-field/field-adapter.js +40 -0
- package/src/components/number-field/number-field.css +77 -0
- package/src/components/number-field/number-field.js +545 -0
- package/src/components/pagination/pagination.css +95 -0
- package/src/components/pagination/pagination.js +481 -0
- package/src/components/panel/panel.css +123 -0
- package/src/components/panel/panel.js +336 -0
- package/src/components/permission/permission.css +47 -0
- package/src/components/permission/permission.js +198 -0
- package/src/components/rating/field-adapter.js +34 -0
- package/src/components/rating/rating.css +97 -0
- package/src/components/rating/rating.js +395 -0
- package/src/components/search/search.css +70 -0
- package/src/components/search/search.js +193 -0
- package/src/components/select/field-adapter.js +24 -0
- package/src/components/select/filter.js +39 -0
- package/src/components/select/select.css +180 -0
- package/src/components/select/select.js +663 -0
- package/src/components/skeleton/skeleton.css +76 -0
- package/src/components/skeleton/skeleton.js +139 -0
- package/src/components/slider/field-adapter.js +36 -0
- package/src/components/slider/slider.css +181 -0
- package/src/components/slider/slider.js +399 -0
- package/src/components/split-view/split-view.css +139 -0
- package/src/components/split-view/split-view.js +806 -0
- package/src/components/stepper/stepper.css +234 -0
- package/src/components/stepper/stepper.js +459 -0
- package/src/components/stepper/steps.js +131 -0
- package/src/components/tabbox/tabbox.css +281 -0
- package/src/components/tabbox/tabbox.js +484 -0
- package/src/components/table/sort.js +85 -0
- package/src/components/table/table.css +364 -0
- package/src/components/table/table.js +1693 -0
- package/src/components/tag-picker/field-adapter.js +35 -0
- package/src/components/tag-picker/tag-picker.css +169 -0
- package/src/components/tag-picker/tag-picker.js +837 -0
- package/src/components/timebox/field-adapter.js +24 -0
- package/src/components/timebox/timebox.css +51 -0
- package/src/components/timebox/timebox.js +276 -0
- package/src/components/toggle/field-adapter.js +24 -0
- package/src/components/toggle/toggle.css +66 -0
- package/src/components/toggle/toggle.js +150 -0
- package/src/components/toolbar/toolbar.css +81 -0
- package/src/components/toolbar/toolbar.js +511 -0
- package/src/components/tooltip/tooltip.css +36 -0
- package/src/components/tooltip/tooltip.js +420 -0
- package/src/components/tree/hierarchy.js +151 -0
- package/src/components/tree/tree.css +99 -0
- package/src/components/tree/tree.js +784 -0
- package/src/components/truncate/truncate.css +20 -0
- package/src/components/truncate/truncate.js +92 -0
- package/src/components/value-list/field-adapter.js +24 -0
- package/src/components/value-list/value-list.css +108 -0
- package/src/components/value-list/value-list.js +336 -0
- package/src/core/breakpoint.js +150 -0
- package/src/core/component.js +292 -0
- package/src/core/date.js +216 -0
- package/src/core/dom.js +260 -0
- package/src/core/export.js +204 -0
- package/src/core/fontawesome.js +177 -0
- package/src/core/format.js +283 -0
- package/src/core/http.js +246 -0
- package/src/core/i18n.js +83 -0
- package/src/core/icons.js +283 -0
- package/src/core/keyboard.js +240 -0
- package/src/core/position.js +177 -0
- package/src/core/storage.js +175 -0
- package/src/core/util.js +336 -0
- package/src/elements/define.js +715 -0
- package/src/elements/reflect.js +79 -0
- package/src/index.js +131 -0
- package/src/zeyos/connect.js +142 -0
- package/src/zeyos/form.js +254 -0
- package/src/zeyos/icons.js +92 -0
- package/src/zeyos/index.js +21 -0
- package/src/zeyos/modules.js +269 -0
- package/src/zeyos/query.js +199 -0
- package/src/zeyos/schema.js +474 -0
- package/src/zeyos/select.js +175 -0
- package/src/zeyos/table.js +272 -0
- package/styles/base.css +124 -0
- package/styles/icons.css +34 -0
- package/styles/tokens/dark.css +84 -0
- package/styles/tokens/density.css +9 -0
- package/styles/tokens/global.css +80 -0
- package/styles/tokens/modules.css +109 -0
- package/styles/tokens/semantic.css +49 -0
- package/styles/zx.css +61 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
import { Component } from '../../core/component.js';
|
|
2
|
+
import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
|
|
3
|
+
import { printf } from '../../core/i18n.js';
|
|
4
|
+
import { icon } from '../../core/icons.js';
|
|
5
|
+
|
|
6
|
+
/** @typedef {'pages'|'loadmore'} PaginationMode */
|
|
7
|
+
|
|
8
|
+
/** The gap glyph {@link paginationRange} inserts between runs of page numbers. */
|
|
9
|
+
const ELLIPSIS = '…';
|
|
10
|
+
/** Modes the component supports. */
|
|
11
|
+
const MODES = new Set(['pages', 'loadmore']);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} PaginationRangeOptions
|
|
15
|
+
* @property {number} page One-based current page.
|
|
16
|
+
* @property {number} pages Total number of pages.
|
|
17
|
+
* @property {number} [siblings=1] Pages shown either side of the current one.
|
|
18
|
+
* @property {number} [boundaries=1] Pages pinned at each end of the range.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Computes the page window: the numbers a pager shows, with `'…'` standing in for every run it
|
|
23
|
+
* skipped. Pure arithmetic, no DOM — the component renders whatever this returns.
|
|
24
|
+
*
|
|
25
|
+
* The window has a fixed width of `boundaries * 2 + siblings * 2 + 3` entries (the current page
|
|
26
|
+
* plus two possible gaps), so it never jitters as the user walks through the pages. Any shorter
|
|
27
|
+
* range is returned in full. A single skipped page is printed instead of a gap, because `…`
|
|
28
|
+
* standing for one number would be a lie. `boundaries: 0` drops the pinned first and last pages.
|
|
29
|
+
* @param {PaginationRangeOptions} options Window description.
|
|
30
|
+
* @returns {Array<number|'…'>} One-based page numbers interleaved with ellipses.
|
|
31
|
+
*/
|
|
32
|
+
export function paginationRange({ page = 1, pages = 1, siblings = 1, boundaries = 1 } = {}) {
|
|
33
|
+
const total = toCount(pages);
|
|
34
|
+
if (total <= 0) return [];
|
|
35
|
+
const current = clamp(toCount(page) || 1, 1, total);
|
|
36
|
+
const sibling = Math.max(0, toCount(siblings));
|
|
37
|
+
const boundary = Math.max(0, toCount(boundaries));
|
|
38
|
+
|
|
39
|
+
const slots = boundary * 2 + sibling * 2 + 3;
|
|
40
|
+
if (total <= slots) return numberRange(1, total);
|
|
41
|
+
|
|
42
|
+
const startPages = numberRange(1, Math.min(boundary, total));
|
|
43
|
+
const endPages = numberRange(Math.max(total - boundary + 1, boundary + 1), total);
|
|
44
|
+
|
|
45
|
+
// The sibling run is pushed away from both ends so the window keeps its width at the extremes.
|
|
46
|
+
const siblingsStart = Math.max(
|
|
47
|
+
Math.min(current - sibling, total - boundary - sibling * 2 - 1),
|
|
48
|
+
boundary + 2
|
|
49
|
+
);
|
|
50
|
+
const siblingsEnd = Math.min(
|
|
51
|
+
Math.max(current + sibling, boundary + sibling * 2 + 2),
|
|
52
|
+
endPages.length > 0 ? endPages[0] - 2 : total - 1
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return [
|
|
56
|
+
...startPages,
|
|
57
|
+
...(siblingsStart > boundary + 2
|
|
58
|
+
? [ELLIPSIS]
|
|
59
|
+
: boundary + 1 < total - boundary ? [boundary + 1] : []),
|
|
60
|
+
...numberRange(siblingsStart, siblingsEnd),
|
|
61
|
+
...(siblingsEnd < total - boundary - 1
|
|
62
|
+
? [ELLIPSIS]
|
|
63
|
+
: total - boundary > boundary ? [total - boundary] : []),
|
|
64
|
+
...endPages
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @typedef {Object} PaginationOptions
|
|
70
|
+
* @property {number} [page=1] One-based current page.
|
|
71
|
+
* @property {number} [pageSize=25] Rows per page.
|
|
72
|
+
* @property {number} [total=0] Total row count; 0 is one empty page.
|
|
73
|
+
* @property {number[]} [pageSizes=[25, 50, 100]] Choices offered by the page-size select.
|
|
74
|
+
* @property {number} [siblings=1] Pages shown either side of the current one.
|
|
75
|
+
* @property {number} [boundaries=1] Pages pinned at each end of the range.
|
|
76
|
+
* @property {boolean} [showPageSize=true] Whether to render the page-size select.
|
|
77
|
+
* @property {boolean} [showSummary=true] Whether to render the "26–50 of 312" summary.
|
|
78
|
+
* @property {PaginationMode} [mode='pages'] `pages` renders a numbered pager, `loadmore` a single
|
|
79
|
+
* "Load more" button for append-style lists.
|
|
80
|
+
* @property {boolean} [disabled=false] Whether every control starts disabled.
|
|
81
|
+
* @property {Record<string, string>|Record<string, Record<string, string>>} [msg] Localized messages.
|
|
82
|
+
* @property {(event: CustomEvent<PaginationChangeDetail>) => void} [onchange] Change listener.
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @typedef {Object} PaginationState
|
|
87
|
+
* @property {number} page One-based current page.
|
|
88
|
+
* @property {number} pageSize Rows per page.
|
|
89
|
+
* @property {number} total Total row count.
|
|
90
|
+
* @property {number} pages Number of pages; at least 1.
|
|
91
|
+
* @property {number} offset Zero-based row offset of the current page.
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/** @typedef {PaginationState} PaginationChangeDetail */
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The pager for a server-backed list. It owns no data: it reports which slice the user asked for
|
|
98
|
+
* and lets the application fetch it, which is exactly the shape `zeyosTable()` expects —
|
|
99
|
+
* `offset`/`pageSize` for `load()` in `pages` mode, or one `change` per `loadMore()` in
|
|
100
|
+
* `loadmore` mode.
|
|
101
|
+
*
|
|
102
|
+
* `page` is clamped into `[1, pages]` after every state change, so shrinking `total` under the
|
|
103
|
+
* user's feet can never leave the pager pointing past the end of the data.
|
|
104
|
+
* @fires Pagination#change
|
|
105
|
+
* @extends {Component<PaginationOptions>}
|
|
106
|
+
*/
|
|
107
|
+
export class Pagination extends Component {
|
|
108
|
+
static cssName = 'pagination';
|
|
109
|
+
|
|
110
|
+
/** @type {Readonly<PaginationOptions>} */
|
|
111
|
+
static defaults = {
|
|
112
|
+
page: 1,
|
|
113
|
+
pageSize: 25,
|
|
114
|
+
total: 0,
|
|
115
|
+
pageSizes: [25, 50, 100],
|
|
116
|
+
siblings: 1,
|
|
117
|
+
boundaries: 1,
|
|
118
|
+
showPageSize: true,
|
|
119
|
+
showSummary: true,
|
|
120
|
+
mode: 'pages',
|
|
121
|
+
disabled: false
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Creates or enhances a pager.
|
|
126
|
+
* @param {Element|string|null} target Existing container, selector, or null.
|
|
127
|
+
* @param {PaginationOptions} [options={}] Pagination options.
|
|
128
|
+
*/
|
|
129
|
+
constructor(target, options = {}) {
|
|
130
|
+
super(target, options);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Builds the pager. Runs inside the base constructor, so instance state is initialized here.
|
|
135
|
+
* @returns {HTMLElement}
|
|
136
|
+
*/
|
|
137
|
+
render() {
|
|
138
|
+
this._createdRoot = this.el === null;
|
|
139
|
+
this._snapshot = this._createdRoot ? null : snapshotTarget(this.el);
|
|
140
|
+
this._destroyed = false;
|
|
141
|
+
this._pageSize = toSize(this.options.pageSize, 25);
|
|
142
|
+
this._total = Math.max(0, toCount(this.options.total));
|
|
143
|
+
this._page = Math.max(1, toCount(this.options.page) || 1);
|
|
144
|
+
this._disabled = Boolean(this.options.disabled);
|
|
145
|
+
this._sizeKey = '';
|
|
146
|
+
this._pageSizes = (Array.isArray(this.options.pageSizes) ? this.options.pageSizes : [])
|
|
147
|
+
.map((size) => toSize(size, 0))
|
|
148
|
+
.filter((size) => size > 0);
|
|
149
|
+
|
|
150
|
+
if (!MODES.has(this.options.mode)) {
|
|
151
|
+
throw new RangeError(`Unknown pagination mode: ${this.options.mode}`);
|
|
152
|
+
}
|
|
153
|
+
this._page = clamp(this._page, 1, this._pageCount());
|
|
154
|
+
|
|
155
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('nav'));
|
|
156
|
+
this.el = root;
|
|
157
|
+
if (root.tagName !== 'NAV') root.setAttribute('role', 'navigation');
|
|
158
|
+
root.setAttribute('aria-label', this._message('pagination.label', 'Pagination'));
|
|
159
|
+
root.dataset.mode = this.options.mode;
|
|
160
|
+
|
|
161
|
+
/** @type {Node[]} */
|
|
162
|
+
const children = [];
|
|
163
|
+
if (this.options.showSummary) {
|
|
164
|
+
children.push(h('span', { class: 'zx-pagination__summary', ref: 'summary', ariaLive: 'polite' }));
|
|
165
|
+
}
|
|
166
|
+
children.push(h('span', { class: 'zx-pagination__spacer' }));
|
|
167
|
+
if (this.options.showPageSize) children.push(this._renderPageSize());
|
|
168
|
+
children.push(this.options.mode === 'pages' ? this._renderPages() : this._renderLoadMore());
|
|
169
|
+
root.replaceChildren(...children);
|
|
170
|
+
|
|
171
|
+
this._sync();
|
|
172
|
+
this._wire();
|
|
173
|
+
return root;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Moves to a page, clamped into `[1, pages]`.
|
|
178
|
+
* @param {number} page One-based page number.
|
|
179
|
+
* @returns {this}
|
|
180
|
+
* @fires Pagination#change
|
|
181
|
+
*/
|
|
182
|
+
setPage(page) {
|
|
183
|
+
return this.setState({ page });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Replaces the total row count and re-clamps the page. Silent: servers report the count as part
|
|
188
|
+
* of a response the pager itself asked for, so echoing a `change` back would loop.
|
|
189
|
+
* @param {number} total Total row count.
|
|
190
|
+
* @returns {this}
|
|
191
|
+
*/
|
|
192
|
+
setTotal(total) {
|
|
193
|
+
return this.setState({ total }, { silent: true });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Replaces the page size and returns to page 1.
|
|
198
|
+
* @param {number} pageSize Rows per page.
|
|
199
|
+
* @returns {this}
|
|
200
|
+
* @fires Pagination#change
|
|
201
|
+
*/
|
|
202
|
+
setPageSize(pageSize) {
|
|
203
|
+
return this.setState({ pageSize, page: 1 });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Applies any combination of page, page size, and total in one pass, then clamps the page.
|
|
208
|
+
* `change` fires when the effective page or page size moved — updating only `total` is silent
|
|
209
|
+
* unless the new count clamps the page.
|
|
210
|
+
* @param {Partial<Pick<PaginationState, 'page'|'pageSize'|'total'>>} [state={}] Values to change.
|
|
211
|
+
* @param {{silent?: boolean}} [options={}] Set `silent` to suppress the event.
|
|
212
|
+
* @returns {this}
|
|
213
|
+
* @fires Pagination#change
|
|
214
|
+
*/
|
|
215
|
+
setState(state = {}, { silent = false } = {}) {
|
|
216
|
+
if (!state || typeof state !== 'object' || Array.isArray(state)) {
|
|
217
|
+
throw new TypeError('Pagination state must be an object');
|
|
218
|
+
}
|
|
219
|
+
const fromPage = this._page;
|
|
220
|
+
const fromSize = this._pageSize;
|
|
221
|
+
if (Object.hasOwn(state, 'total')) this._total = Math.max(0, toCount(state.total));
|
|
222
|
+
if (Object.hasOwn(state, 'pageSize')) this._pageSize = toSize(state.pageSize, this._pageSize);
|
|
223
|
+
if (Object.hasOwn(state, 'page')) {
|
|
224
|
+
// Any real number is accepted and then clamped; only garbage leaves the page where it was.
|
|
225
|
+
const page = Math.trunc(Number(state.page));
|
|
226
|
+
if (Number.isFinite(page)) this._page = page;
|
|
227
|
+
}
|
|
228
|
+
this._page = clamp(this._page, 1, this._pageCount());
|
|
229
|
+
this._sync();
|
|
230
|
+
if (!silent && (this._page !== fromPage || this._pageSize !== fromSize)) {
|
|
231
|
+
this.emit('change', this.getState());
|
|
232
|
+
}
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Returns the current slice description.
|
|
238
|
+
* @returns {PaginationState}
|
|
239
|
+
*/
|
|
240
|
+
getState() {
|
|
241
|
+
const pages = this._pageCount();
|
|
242
|
+
return {
|
|
243
|
+
page: this._page,
|
|
244
|
+
pageSize: this._pageSize,
|
|
245
|
+
total: this._total,
|
|
246
|
+
pages,
|
|
247
|
+
offset: (this._page - 1) * this._pageSize
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Enables every control.
|
|
253
|
+
* @returns {this}
|
|
254
|
+
*/
|
|
255
|
+
enable() {
|
|
256
|
+
this._disabled = false;
|
|
257
|
+
this._sync();
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Disables every control, for instance while a page is in flight.
|
|
263
|
+
* @returns {this}
|
|
264
|
+
*/
|
|
265
|
+
disable() {
|
|
266
|
+
this._disabled = true;
|
|
267
|
+
this._sync();
|
|
268
|
+
return this;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Aborts listeners and restores an enhanced target to the markup it had before the takeover.
|
|
273
|
+
* @returns {void}
|
|
274
|
+
*/
|
|
275
|
+
destroy() {
|
|
276
|
+
if (this._destroyed) return;
|
|
277
|
+
this._destroyed = true;
|
|
278
|
+
const root = this.el;
|
|
279
|
+
super.destroy();
|
|
280
|
+
if (!this._createdRoot && root) restoreTarget(root, this._snapshot);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* ------------------------------------------------------------------ internals -- */
|
|
284
|
+
|
|
285
|
+
/** @returns {number} Page count; an empty list is one empty page. */
|
|
286
|
+
_pageCount() {
|
|
287
|
+
return this._total <= 0 ? 1 : Math.ceil(this._total / this._pageSize);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** @returns {HTMLElement} The page-size select and its visible label. */
|
|
291
|
+
_renderPageSize() {
|
|
292
|
+
const label = this._message('pagination.pageSize', 'Rows per page');
|
|
293
|
+
return h('label', { class: 'zx-pagination__size' },
|
|
294
|
+
h('span', { class: 'zx-pagination__size-label' }, label),
|
|
295
|
+
h('select', {
|
|
296
|
+
class: 'zx-pagination__select',
|
|
297
|
+
ref: 'select',
|
|
298
|
+
ariaLabel: label
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** @returns {HTMLElement} Previous / page buttons / next. */
|
|
303
|
+
_renderPages() {
|
|
304
|
+
return h('div', { class: 'zx-pagination__controls' },
|
|
305
|
+
this._navButton('prev', 'chevron-left', this._message('pagination.previous', 'Previous page')),
|
|
306
|
+
h('div', { class: 'zx-pagination__pages', ref: 'pages' }),
|
|
307
|
+
this._navButton('next', 'chevron-right', this._message('pagination.next', 'Next page')));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @param {string} ref Ref name.
|
|
312
|
+
* @param {string} glyph Icon name.
|
|
313
|
+
* @param {string} label Accessible name.
|
|
314
|
+
* @returns {HTMLElement}
|
|
315
|
+
*/
|
|
316
|
+
_navButton(ref, glyph, label) {
|
|
317
|
+
return h('button', {
|
|
318
|
+
class: 'zx-btn zx-pagination__nav',
|
|
319
|
+
type: 'button',
|
|
320
|
+
ref,
|
|
321
|
+
ariaLabel: label,
|
|
322
|
+
dataset: { kind: 'default', size: 'sm' }
|
|
323
|
+
}, icon(glyph, { size: 14 }));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** @returns {HTMLElement} The append-style trigger. */
|
|
327
|
+
_renderLoadMore() {
|
|
328
|
+
return h('button', {
|
|
329
|
+
class: 'zx-btn zx-pagination__more',
|
|
330
|
+
type: 'button',
|
|
331
|
+
ref: 'more',
|
|
332
|
+
dataset: { kind: 'default', size: 'md' }
|
|
333
|
+
}, h('span', { class: 'zx-btn__label' }, this._message('pagination.loadMore', 'Load more')));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Attaches the delegated listeners once, after the shell exists. @returns {void} */
|
|
337
|
+
_wire() {
|
|
338
|
+
if (this.refs.select) {
|
|
339
|
+
this.listen(this.refs.select, 'change', () => {
|
|
340
|
+
this.setPageSize(Number(/** @type {HTMLSelectElement} */ (this.refs.select).value));
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
if (this.options.mode === 'pages') {
|
|
344
|
+
this.listen(this.refs.prev, 'click', () => this.setPage(this._page - 1));
|
|
345
|
+
this.listen(this.refs.next, 'click', () => this.setPage(this._page + 1));
|
|
346
|
+
this.listen(this.refs.pages, 'click', (event) => {
|
|
347
|
+
const control = /** @type {Element} */ (event.target).closest?.('[data-page]');
|
|
348
|
+
if (!control || !this.refs.pages.contains(control)) return;
|
|
349
|
+
this.setPage(Number(/** @type {HTMLElement} */ (control).dataset.page));
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
this.listen(this.refs.more, 'click', () => this.setPage(this._page + 1));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Pushes the current state onto every control. @returns {void} */
|
|
357
|
+
_sync() {
|
|
358
|
+
const pages = this._pageCount();
|
|
359
|
+
this.el.dataset.disabled = this._disabled ? 'true' : 'false';
|
|
360
|
+
if (this.refs.summary) this.refs.summary.textContent = this._summary();
|
|
361
|
+
if (this.refs.select) this._syncPageSize();
|
|
362
|
+
if (this.options.mode === 'pages') {
|
|
363
|
+
/** @type {HTMLButtonElement} */ (this.refs.prev).disabled = this._disabled || this._page <= 1;
|
|
364
|
+
/** @type {HTMLButtonElement} */ (this.refs.next).disabled = this._disabled || this._page >= pages;
|
|
365
|
+
this._syncPages(pages);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
/** @type {HTMLButtonElement} */ (this.refs.more).disabled = this._disabled || this._page >= pages;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @returns {string} The "26–50 of 312" line.
|
|
373
|
+
*/
|
|
374
|
+
_summary() {
|
|
375
|
+
if (this._total <= 0) return this._message('pagination.empty', '0 of 0');
|
|
376
|
+
const offset = (this._page - 1) * this._pageSize;
|
|
377
|
+
const to = Math.min(offset + this._pageSize, this._total);
|
|
378
|
+
// Appending lists have shown everything up to `to`, not just the newest page.
|
|
379
|
+
if (this.options.mode === 'loadmore') {
|
|
380
|
+
return this._message('pagination.loaded', '%1 of %2', count(to), count(this._total));
|
|
381
|
+
}
|
|
382
|
+
return this._message('pagination.summary', '%1–%2 of %3',
|
|
383
|
+
count(offset + 1), count(to), count(this._total));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Rebuilds the page-size options only when the choice set actually changed. @returns {void} */
|
|
387
|
+
_syncPageSize() {
|
|
388
|
+
const select = /** @type {HTMLSelectElement} */ (this.refs.select);
|
|
389
|
+
const sizes = [...new Set([...this._pageSizes, this._pageSize])].sort((left, right) => left - right);
|
|
390
|
+
const key = sizes.join(',');
|
|
391
|
+
if (key !== this._sizeKey) {
|
|
392
|
+
this._sizeKey = key;
|
|
393
|
+
select.replaceChildren(...sizes.map((size) => h('option', { value: String(size) }, String(size))));
|
|
394
|
+
}
|
|
395
|
+
select.value = String(this._pageSize);
|
|
396
|
+
select.disabled = this._disabled;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Rebuilds the page buttons, keeping the keyboard focus on the same page number when the user
|
|
401
|
+
* paged with the keyboard and that number survived the rebuild.
|
|
402
|
+
* @param {number} pages Page count.
|
|
403
|
+
* @returns {void}
|
|
404
|
+
*/
|
|
405
|
+
_syncPages(pages) {
|
|
406
|
+
const container = /** @type {HTMLElement} */ (this.refs.pages);
|
|
407
|
+
const active = this.el.ownerDocument?.activeElement ?? null;
|
|
408
|
+
const focused = active && container.contains(active)
|
|
409
|
+
? /** @type {HTMLElement} */ (active).dataset.page ?? null
|
|
410
|
+
: null;
|
|
411
|
+
|
|
412
|
+
const entries = paginationRange({
|
|
413
|
+
page: this._page,
|
|
414
|
+
pages,
|
|
415
|
+
siblings: this.options.siblings,
|
|
416
|
+
boundaries: this.options.boundaries
|
|
417
|
+
});
|
|
418
|
+
container.replaceChildren(...entries.map((entry) => entry === ELLIPSIS
|
|
419
|
+
? h('span', { class: 'zx-pagination__ellipsis', ariaHidden: 'true' }, ELLIPSIS)
|
|
420
|
+
: h('button', {
|
|
421
|
+
class: 'zx-btn zx-pagination__page',
|
|
422
|
+
type: 'button',
|
|
423
|
+
disabled: this._disabled,
|
|
424
|
+
ariaCurrent: entry === this._page ? 'page' : null,
|
|
425
|
+
ariaLabel: this._message('pagination.page', 'Page %1', entry),
|
|
426
|
+
dataset: { kind: 'default', size: 'sm', page: String(entry) }
|
|
427
|
+
}, String(entry))));
|
|
428
|
+
|
|
429
|
+
if (focused === null) return;
|
|
430
|
+
const restored = /** @type {HTMLButtonElement|null} */ (
|
|
431
|
+
container.querySelector(`[data-page="${focused}"]`));
|
|
432
|
+
if (restored && !restored.disabled) restored.focus();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Resolves a message through the host translator, falling back to the built-in English text.
|
|
437
|
+
* @param {string} key Message key.
|
|
438
|
+
* @param {string} fallback Built-in text, with `%1`-style placeholders.
|
|
439
|
+
* @param {...unknown} args Interpolation values.
|
|
440
|
+
* @returns {string}
|
|
441
|
+
*/
|
|
442
|
+
_message(key, fallback, ...args) {
|
|
443
|
+
const message = this.msg(key, ...args);
|
|
444
|
+
return message === key ? printf(fallback, args) : message;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Fired whenever the user picks a different slice — a page number, previous/next, a new page size
|
|
450
|
+
* (which returns to page 1), or Load more.
|
|
451
|
+
* @event Pagination#change
|
|
452
|
+
* @type {CustomEvent<PaginationChangeDetail>}
|
|
453
|
+
*/
|
|
454
|
+
|
|
455
|
+
/** @param {unknown} value @returns {number} A non-negative integer, or 0. */
|
|
456
|
+
function toCount(value) {
|
|
457
|
+
const number = Math.trunc(Number(value));
|
|
458
|
+
return Number.isFinite(number) && number > 0 ? number : 0;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** @param {unknown} value @param {number} fallback @returns {number} A positive integer. */
|
|
462
|
+
function toSize(value, fallback) {
|
|
463
|
+
const number = Math.trunc(Number(value));
|
|
464
|
+
return Number.isFinite(number) && number > 0 ? number : fallback;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** @param {number} value @param {number} min @param {number} max @returns {number} */
|
|
468
|
+
function clamp(value, min, max) {
|
|
469
|
+
return Math.min(Math.max(value, min), max);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** @param {number} start @param {number} end @returns {number[]} Inclusive range, empty when reversed. */
|
|
473
|
+
function numberRange(start, end) {
|
|
474
|
+
if (end < start) return [];
|
|
475
|
+
return Array.from({ length: end - start + 1 }, (_value, index) => start + index);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** @param {number} value @returns {string} Group-separated number for the summary line. */
|
|
479
|
+
function count(value) {
|
|
480
|
+
return value.toLocaleString();
|
|
481
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
.zx-panel {
|
|
2
|
+
overflow: hidden;
|
|
3
|
+
border: 1px solid var(--zx-color-border);
|
|
4
|
+
border-radius: var(--zx-radius-lg);
|
|
5
|
+
background: var(--zx-color-bg-surface);
|
|
6
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* The header is a row, not one big button: the collapse control takes the title area and the
|
|
10
|
+
action group sits at its trailing edge, since a button cannot contain other buttons. */
|
|
11
|
+
.zx-panel__header {
|
|
12
|
+
display: flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: var(--zx-space-2);
|
|
15
|
+
min-block-size: var(--zx-control-height);
|
|
16
|
+
background: var(--zx-panel-header-bg, var(--zx-color-bg-raised));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.zx-panel__toggle {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex: 1 1 auto;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: var(--zx-space-2);
|
|
24
|
+
min-inline-size: 0;
|
|
25
|
+
border: 0;
|
|
26
|
+
background: none;
|
|
27
|
+
padding: var(--zx-space-3) var(--zx-space-4);
|
|
28
|
+
color: var(--zx-color-text);
|
|
29
|
+
font: inherit;
|
|
30
|
+
font-weight: 650;
|
|
31
|
+
text-align: start;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
button.zx-panel__toggle {
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
button.zx-panel__toggle:hover {
|
|
39
|
+
background: var(--zx-color-bg-hover);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Inset, because the panel clips its own overflow and would cut an outside ring. */
|
|
43
|
+
button.zx-panel__toggle:focus-visible {
|
|
44
|
+
outline: 0;
|
|
45
|
+
box-shadow: inset var(--zx-focus-ring);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.zx-panel__title {
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
text-overflow: ellipsis;
|
|
51
|
+
white-space: nowrap;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.zx-panel__buttons,
|
|
55
|
+
.zx-panel__footer-buttons {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex: none;
|
|
58
|
+
align-items: center;
|
|
59
|
+
gap: var(--zx-space-2);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.zx-panel__buttons {
|
|
63
|
+
padding-inline-end: var(--zx-space-3);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* An empty action group must not add its gap or padding to the row. */
|
|
67
|
+
.zx-panel__buttons:empty,
|
|
68
|
+
.zx-panel__footer-buttons:empty {
|
|
69
|
+
display: none;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.zx-panel__chevron {
|
|
73
|
+
display: inline-grid;
|
|
74
|
+
flex: none;
|
|
75
|
+
place-items: center;
|
|
76
|
+
color: var(--zx-color-text-muted);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.zx-panel[data-state="open"] .zx-panel__chevron {
|
|
80
|
+
transform: rotate(90deg);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.zx-panel__body,
|
|
84
|
+
.zx-panel__footer {
|
|
85
|
+
border-block-start: 1px solid var(--zx-color-border);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.zx-panel__content {
|
|
89
|
+
padding: var(--zx-space-4);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.zx-panel__footer {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: var(--zx-space-3);
|
|
96
|
+
padding: var(--zx-space-4);
|
|
97
|
+
background: var(--zx-color-bg-raised);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.zx-panel__footer[hidden] {
|
|
101
|
+
display: none;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Content takes the free space so the buttons stay pinned to the trailing edge, with or
|
|
105
|
+
without footer text beside them. */
|
|
106
|
+
.zx-panel__footer-content {
|
|
107
|
+
flex: 1 1 auto;
|
|
108
|
+
min-inline-size: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.zx-panel__footer-content:empty {
|
|
112
|
+
display: none;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.zx-panel__footer-buttons {
|
|
116
|
+
margin-inline-start: auto;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
120
|
+
.zx-panel__chevron {
|
|
121
|
+
transition: transform var(--zx-dur-fast) var(--zx-ease);
|
|
122
|
+
}
|
|
123
|
+
}
|