@zeyos/zx 2.1.0 → 2.2.1
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 +51 -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 +375 -39
- package/dist/zx-compat.esm.min.js +1 -1
- package/dist/zx-compat.global.js +372 -73
- package/dist/zx-compat.global.min.js +3 -3
- package/dist/zx-zeyos.esm.js +355 -49
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +68 -6
- package/dist/zx.esm.js +359 -73
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +359 -73
- 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 +31 -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 +1711 -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,400 @@
|
|
|
1
|
+
import { Component } from '../../core/component.js';
|
|
2
|
+
import { h } from '../../core/dom.js';
|
|
3
|
+
import {
|
|
4
|
+
applyFilters, deriveSelectOptions, isActiveFilterValue
|
|
5
|
+
} from './filter-core.js';
|
|
6
|
+
|
|
7
|
+
/** @typedef {Record<string, any>} DataFilterRow */
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} DataFilterOption
|
|
10
|
+
* @property {unknown} value Option value.
|
|
11
|
+
* @property {string} [label] Option label.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} SelectFilterDefinition
|
|
16
|
+
* @property {'select'} type
|
|
17
|
+
* @property {string} id State key.
|
|
18
|
+
* @property {string} label Visible label.
|
|
19
|
+
* @property {string} [field] Single row field.
|
|
20
|
+
* @property {Array<string|((row: DataFilterRow) => unknown)>} [fields] Row fields combined with OR.
|
|
21
|
+
* @property {(row: DataFilterRow) => unknown} [get] Custom value accessor.
|
|
22
|
+
* @property {Array<unknown|DataFilterOption>} [options] Explicit options; omitted derives distinct values.
|
|
23
|
+
* @property {string} [emptyLabel] Empty option label.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} TextFilterDefinition
|
|
28
|
+
* @property {'text'} type
|
|
29
|
+
* @property {string} id State key.
|
|
30
|
+
* @property {string} label Visible label.
|
|
31
|
+
* @property {Array<string|((row: DataFilterRow) => unknown)>} fields Search fields.
|
|
32
|
+
* @property {string} [placeholder] Search placeholder.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {Object} CustomFilterDefinition
|
|
37
|
+
* @property {'custom'} type
|
|
38
|
+
* @property {string} id State key.
|
|
39
|
+
* @property {string} label Visible label.
|
|
40
|
+
* @property {Element} element User-supplied native or custom control.
|
|
41
|
+
* @property {(row: DataFilterRow, value: unknown) => boolean} predicate Row predicate.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/** @typedef {SelectFilterDefinition|TextFilterDefinition|CustomFilterDefinition} FilterDefinition */
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {Object} DataFilterOptions
|
|
48
|
+
* @property {FilterDefinition[]} [filters=[]] Declarative filters.
|
|
49
|
+
* @property {DataFilterRow[]} [data=[]] Rows to filter.
|
|
50
|
+
* @property {boolean} [autoApply=true] Apply and emit whenever a control changes.
|
|
51
|
+
* @property {string|null} [clearLabel=null] Clear button label; null resolves `dataFilter.clear`.
|
|
52
|
+
* @property {(event: CustomEvent<DataFilterEventDetail>) => void} [onfilter]
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/** @typedef {{rows: DataFilterRow[], state: Record<string, unknown>}} DataFilterEventDetail */
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Inline declarative filter bar that produces a filtered copy of client-side data.
|
|
59
|
+
* @fires DataFilter#filter
|
|
60
|
+
* @extends {Component<DataFilterOptions>}
|
|
61
|
+
*/
|
|
62
|
+
export class DataFilter extends Component {
|
|
63
|
+
static cssName = 'data-filter';
|
|
64
|
+
|
|
65
|
+
/** @type {DataFilterOptions} */
|
|
66
|
+
static defaults = {
|
|
67
|
+
filters: [],
|
|
68
|
+
data: [],
|
|
69
|
+
autoApply: true,
|
|
70
|
+
clearLabel: null
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates a filter bar around a target or creates a new root when target is null.
|
|
75
|
+
* @param {Element|string|null} target Existing root, selector, or null.
|
|
76
|
+
* @param {DataFilterOptions} [options={}] Filter options.
|
|
77
|
+
*/
|
|
78
|
+
constructor(target, options = {}) {
|
|
79
|
+
super(target, options);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** @returns {HTMLElement} Filter root. */
|
|
83
|
+
render() {
|
|
84
|
+
const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
|
|
85
|
+
this._originalChildren = this.el ? Array.from(this.el.childNodes) : null;
|
|
86
|
+
this._filters = Array.isArray(this.options.filters) ? this.options.filters.map((definition) => ({ ...definition })) : [];
|
|
87
|
+
this._data = Array.isArray(this.options.data) ? [...this.options.data] : [];
|
|
88
|
+
this._state = {};
|
|
89
|
+
this._entries = new Map();
|
|
90
|
+
this._customOrigins = new Map();
|
|
91
|
+
this._restored = false;
|
|
92
|
+
|
|
93
|
+
const ids = new Set();
|
|
94
|
+
for (const definition of this._filters) {
|
|
95
|
+
validateDefinition(definition);
|
|
96
|
+
if (ids.has(definition.id)) throw new RangeError(`Duplicate filter id: ${definition.id}`);
|
|
97
|
+
ids.add(definition.id);
|
|
98
|
+
if (definition.type === 'custom' && !this._customOrigins.has(definition.element)) {
|
|
99
|
+
this._customOrigins.set(definition.element, {
|
|
100
|
+
parent: definition.element.parentNode,
|
|
101
|
+
nextSibling: definition.element.nextSibling
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
this._state[definition.id] = definition.type === 'custom' ? readElementValue(definition.element) : '';
|
|
105
|
+
}
|
|
106
|
+
this._filtered = applyFilters(this._data, this._filters, this._state);
|
|
107
|
+
|
|
108
|
+
this._controls = h('div', { class: 'zx-data-filter__controls' });
|
|
109
|
+
this._badge = h('span', {
|
|
110
|
+
class: 'zx-data-filter__badge',
|
|
111
|
+
ariaLive: 'polite',
|
|
112
|
+
hidden: true
|
|
113
|
+
}, '0');
|
|
114
|
+
this._clearButton = h('button', {
|
|
115
|
+
class: 'zx-data-filter__clear',
|
|
116
|
+
type: 'button',
|
|
117
|
+
hidden: true
|
|
118
|
+
}, this.options.clearLabel == null ? this.msg('dataFilter.clear') : String(this.options.clearLabel));
|
|
119
|
+
root.replaceChildren(this._controls, this._badge, this._clearButton);
|
|
120
|
+
this.listen(this._clearButton, 'click', () => this.clear());
|
|
121
|
+
for (const definition of this._filters) this._appendFilter(definition);
|
|
122
|
+
this._syncActiveState();
|
|
123
|
+
return root;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Replaces source rows and refreshes auto-derived select options.
|
|
128
|
+
* @param {DataFilterRow[]} rows New source rows.
|
|
129
|
+
* @returns {this}
|
|
130
|
+
* @fires DataFilter#filter
|
|
131
|
+
*/
|
|
132
|
+
setData(rows) {
|
|
133
|
+
assertRows(rows);
|
|
134
|
+
this._data = [...rows];
|
|
135
|
+
for (const definition of this._filters) {
|
|
136
|
+
if (definition.type !== 'select') continue;
|
|
137
|
+
const entry = this._entries.get(definition.id);
|
|
138
|
+
if (entry) this._populateSelect(definition, entry);
|
|
139
|
+
}
|
|
140
|
+
this._syncActiveState();
|
|
141
|
+
if (this.options.autoApply) this.apply();
|
|
142
|
+
else this._filtered = applyFilters(this._data, this._filters, this._state);
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Applies current state, emits the filtered rows, and returns them.
|
|
148
|
+
* @returns {DataFilterRow[]} Filtered copy.
|
|
149
|
+
* @fires DataFilter#filter
|
|
150
|
+
*/
|
|
151
|
+
apply() {
|
|
152
|
+
this._filtered = applyFilters(this._data, this._filters, this._state);
|
|
153
|
+
const rows = [...this._filtered];
|
|
154
|
+
this.emit('filter', { rows, state: this.getState() });
|
|
155
|
+
return rows;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Clears every control and applies the empty state.
|
|
160
|
+
* @returns {this}
|
|
161
|
+
* @fires DataFilter#filter
|
|
162
|
+
*/
|
|
163
|
+
clear() {
|
|
164
|
+
for (const definition of this._filters) this._state[definition.id] = '';
|
|
165
|
+
this._syncControls();
|
|
166
|
+
this._syncActiveState();
|
|
167
|
+
this.apply();
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** @returns {Record<string, unknown>} Shallow copy of state keyed by filter id. */
|
|
172
|
+
getState() {
|
|
173
|
+
return { ...this._state };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Replaces filter state. Missing filter ids are cleared.
|
|
178
|
+
* @param {Record<string, unknown>} state State keyed by filter id.
|
|
179
|
+
* @returns {this}
|
|
180
|
+
* @fires DataFilter#filter
|
|
181
|
+
*/
|
|
182
|
+
setState(state) {
|
|
183
|
+
if (!state || typeof state !== 'object' || Array.isArray(state)) {
|
|
184
|
+
throw new TypeError('DataFilter state must be an object');
|
|
185
|
+
}
|
|
186
|
+
for (const definition of this._filters) {
|
|
187
|
+
this._state[definition.id] = Object.prototype.hasOwnProperty.call(state, definition.id) ? state[definition.id] : '';
|
|
188
|
+
}
|
|
189
|
+
this._syncControls();
|
|
190
|
+
this._syncActiveState();
|
|
191
|
+
if (this.options.autoApply) this.apply();
|
|
192
|
+
else this._filtered = applyFilters(this._data, this._filters, this._state);
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Adds one filter control without mutating the original options array.
|
|
198
|
+
* @param {FilterDefinition} definition Filter definition.
|
|
199
|
+
* @returns {this}
|
|
200
|
+
*/
|
|
201
|
+
addFilter(definition) {
|
|
202
|
+
validateDefinition(definition);
|
|
203
|
+
const copy = { ...definition };
|
|
204
|
+
this._filters.push(copy);
|
|
205
|
+
this._state[copy.id] = copy.type === 'custom' ? readElementValue(copy.element) : '';
|
|
206
|
+
this._appendFilter(copy);
|
|
207
|
+
this._filtered = applyFilters(this._data, this._filters, this._state);
|
|
208
|
+
this._syncActiveState();
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Restores enhanced content and user-owned custom controls. @returns {void} */
|
|
213
|
+
destroy() {
|
|
214
|
+
if (!this._restored) {
|
|
215
|
+
if (this._originalChildren) this.el.replaceChildren(...this._originalChildren);
|
|
216
|
+
restoreCustomElements(this._customOrigins);
|
|
217
|
+
this._restored = true;
|
|
218
|
+
}
|
|
219
|
+
super.destroy();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** @param {FilterDefinition} definition @returns {void} */
|
|
223
|
+
_appendFilter(definition) {
|
|
224
|
+
validateDefinition(definition);
|
|
225
|
+
if (this._entries.has(definition.id)) throw new RangeError(`Duplicate filter id: ${definition.id}`);
|
|
226
|
+
|
|
227
|
+
let element;
|
|
228
|
+
const entry = { element: null, options: [] };
|
|
229
|
+
if (definition.type === 'select') {
|
|
230
|
+
element = h('select', { ariaLabel: definition.label });
|
|
231
|
+
entry.element = element;
|
|
232
|
+
this._populateSelect(definition, entry);
|
|
233
|
+
this.listen(element, 'change', () => {
|
|
234
|
+
const index = Number(element.value) - 1;
|
|
235
|
+
this._state[definition.id] = index >= 0 ? entry.options[index]?.value ?? '' : '';
|
|
236
|
+
this._controlChanged();
|
|
237
|
+
});
|
|
238
|
+
} else if (definition.type === 'text') {
|
|
239
|
+
element = h('input', {
|
|
240
|
+
type: 'search',
|
|
241
|
+
placeholder: definition.placeholder ?? '',
|
|
242
|
+
ariaLabel: definition.label,
|
|
243
|
+
autocomplete: 'off'
|
|
244
|
+
});
|
|
245
|
+
entry.element = element;
|
|
246
|
+
this.listen(element, 'input', () => {
|
|
247
|
+
this._state[definition.id] = element.value;
|
|
248
|
+
this._controlChanged();
|
|
249
|
+
});
|
|
250
|
+
} else {
|
|
251
|
+
element = definition.element;
|
|
252
|
+
if (!this._customOrigins.has(element)) {
|
|
253
|
+
this._customOrigins.set(element, { parent: element.parentNode, nextSibling: element.nextSibling });
|
|
254
|
+
}
|
|
255
|
+
entry.element = element;
|
|
256
|
+
const inputEvent = element.matches?.('input:not([type="checkbox"]):not([type="radio"]), textarea') ? 'input' : 'change';
|
|
257
|
+
this.listen(element, inputEvent, () => {
|
|
258
|
+
this._state[definition.id] = readElementValue(element);
|
|
259
|
+
this._controlChanged();
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this._entries.set(definition.id, entry);
|
|
264
|
+
const field = h('label', { class: 'zx-data-filter__field' },
|
|
265
|
+
h('span', { class: 'zx-data-filter__label' }, definition.label),
|
|
266
|
+
element
|
|
267
|
+
);
|
|
268
|
+
this._controls.append(field);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** @param {SelectFilterDefinition} definition @param {{element: HTMLSelectElement, options: Array<{value: unknown, label: string}>}} entry @returns {void} */
|
|
272
|
+
_populateSelect(definition, entry) {
|
|
273
|
+
const selected = this._state[definition.id];
|
|
274
|
+
entry.options = Array.isArray(definition.options) ?
|
|
275
|
+
definition.options.map(normalizeExplicitOption) : deriveSelectOptions(this._data, definition);
|
|
276
|
+
const empty = h('option', { value: '' }, definition.emptyLabel ?? this.msg('dataFilter.all'));
|
|
277
|
+
const fragment = document.createDocumentFragment();
|
|
278
|
+
fragment.append(empty);
|
|
279
|
+
entry.options.forEach((option, index) => {
|
|
280
|
+
fragment.append(h('option', { value: String(index + 1) }, option.label));
|
|
281
|
+
});
|
|
282
|
+
entry.element.replaceChildren(fragment);
|
|
283
|
+
const selectedIndex = entry.options.findIndex((option) => sameValue(option.value, selected));
|
|
284
|
+
entry.element.value = selectedIndex >= 0 ? String(selectedIndex + 1) : '';
|
|
285
|
+
if (selectedIndex < 0) this._state[definition.id] = '';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** @returns {void} */
|
|
289
|
+
_controlChanged() {
|
|
290
|
+
this._syncActiveState();
|
|
291
|
+
if (this.options.autoApply) this.apply();
|
|
292
|
+
else this._filtered = applyFilters(this._data, this._filters, this._state);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** @returns {void} */
|
|
296
|
+
_syncControls() {
|
|
297
|
+
for (const definition of this._filters) {
|
|
298
|
+
const entry = this._entries.get(definition.id);
|
|
299
|
+
if (!entry) continue;
|
|
300
|
+
const value = this._state[definition.id];
|
|
301
|
+
if (definition.type === 'select') {
|
|
302
|
+
const index = entry.options.findIndex((option) => sameValue(option.value, value));
|
|
303
|
+
entry.element.value = index >= 0 ? String(index + 1) : '';
|
|
304
|
+
if (index < 0) this._state[definition.id] = '';
|
|
305
|
+
} else {
|
|
306
|
+
writeElementValue(entry.element, value);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** @returns {void} */
|
|
312
|
+
_syncActiveState() {
|
|
313
|
+
const count = this._filters.reduce((total, definition) =>
|
|
314
|
+
total + Number(isActiveFilterValue(this._state[definition.id])), 0);
|
|
315
|
+
this._badge.textContent = String(count);
|
|
316
|
+
this._badge.hidden = count === 0;
|
|
317
|
+
this._clearButton.hidden = count === 0;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Filter result event.
|
|
323
|
+
* @event DataFilter#filter
|
|
324
|
+
* @type {CustomEvent<DataFilterEventDetail>}
|
|
325
|
+
*/
|
|
326
|
+
|
|
327
|
+
/** @param {unknown} definition @returns {asserts definition is FilterDefinition} */
|
|
328
|
+
function validateDefinition(definition) {
|
|
329
|
+
if (!definition || typeof definition !== 'object') throw new TypeError('Filter definition must be an object');
|
|
330
|
+
if (!['select', 'text', 'custom'].includes(definition.type)) throw new TypeError(`Unknown filter type: ${definition.type}`);
|
|
331
|
+
if (typeof definition.id !== 'string' || !definition.id) throw new TypeError('Filter definition requires an id');
|
|
332
|
+
if (definition.type === 'custom') {
|
|
333
|
+
if (!definition.element || definition.element.nodeType !== 1) throw new TypeError('Custom filter requires an Element');
|
|
334
|
+
if (typeof definition.predicate !== 'function') throw new TypeError('Custom filter requires a predicate');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** @param {unknown} option @returns {{value: unknown, label: string}} */
|
|
339
|
+
function normalizeExplicitOption(option) {
|
|
340
|
+
if (option && typeof option === 'object' && Object.prototype.hasOwnProperty.call(option, 'value')) {
|
|
341
|
+
return { value: option.value, label: String(option.label ?? option.value ?? '') };
|
|
342
|
+
}
|
|
343
|
+
return { value: option, label: String(option ?? '') };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** @param {Element} element @returns {unknown} */
|
|
347
|
+
function readElementValue(element) {
|
|
348
|
+
if ('checked' in element && ['checkbox', 'radio'].includes(element.type)) return element.checked;
|
|
349
|
+
return 'value' in element ? element.value : element.getAttribute('data-value') ?? '';
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** @param {Element} element @param {unknown} value @returns {void} */
|
|
353
|
+
function writeElementValue(element, value) {
|
|
354
|
+
if ('checked' in element && ['checkbox', 'radio'].includes(element.type)) element.checked = Boolean(value);
|
|
355
|
+
else if ('value' in element) element.value = value == null ? '' : String(value);
|
|
356
|
+
else if (value == null || value === '') element.removeAttribute('data-value');
|
|
357
|
+
else element.setAttribute('data-value', String(value));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** @param {unknown} left @param {unknown} right @returns {boolean} */
|
|
361
|
+
function sameValue(left, right) {
|
|
362
|
+
if (Object.is(left, right)) return true;
|
|
363
|
+
if (left == null || right == null) return false;
|
|
364
|
+
return String(left) === String(right);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** @param {unknown} rows @returns {asserts rows is DataFilterRow[]} */
|
|
368
|
+
function assertRows(rows) {
|
|
369
|
+
if (!Array.isArray(rows)) throw new TypeError('DataFilter data must be an array');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Restores custom elements once their original following sibling is back in place. This
|
|
374
|
+
* dependency-aware order preserves adjacent custom controls as well as isolated controls.
|
|
375
|
+
* @param {Map<Element, {parent: ParentNode|null, nextSibling: Node|null}>} origins Origins.
|
|
376
|
+
* @returns {void}
|
|
377
|
+
*/
|
|
378
|
+
function restoreCustomElements(origins) {
|
|
379
|
+
const pending = [...origins];
|
|
380
|
+
for (let pass = pending.length; pending.length > 0 && pass >= 0; pass -= 1) {
|
|
381
|
+
let restored = 0;
|
|
382
|
+
for (let index = pending.length - 1; index >= 0; index -= 1) {
|
|
383
|
+
const [element, origin] = pending[index];
|
|
384
|
+
if (!origin.parent) {
|
|
385
|
+
element.remove();
|
|
386
|
+
} else if (origin.nextSibling && origin.nextSibling.parentNode !== origin.parent) {
|
|
387
|
+
continue;
|
|
388
|
+
} else {
|
|
389
|
+
origin.parent.insertBefore(element, origin.nextSibling);
|
|
390
|
+
}
|
|
391
|
+
pending.splice(index, 1);
|
|
392
|
+
restored += 1;
|
|
393
|
+
}
|
|
394
|
+
if (restored === 0) break;
|
|
395
|
+
}
|
|
396
|
+
for (const [element, origin] of pending) {
|
|
397
|
+
if (origin.parent) origin.parent.append(element);
|
|
398
|
+
else element.remove();
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/** @typedef {Record<string, any>} FilterRow */
|
|
2
|
+
/** @typedef {Record<string, unknown>} FilterState */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes searchable text for case- and diacritic-insensitive matching.
|
|
6
|
+
* @param {unknown} value Value to normalize.
|
|
7
|
+
* @returns {string} Normalized text.
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeText(value) {
|
|
10
|
+
return String(value ?? '')
|
|
11
|
+
.normalize('NFD')
|
|
12
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
13
|
+
.toLocaleLowerCase();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Tests a text corpus with whitespace-delimited, multi-word AND semantics.
|
|
18
|
+
* @param {unknown[]} values Searchable field values.
|
|
19
|
+
* @param {unknown} query Query value.
|
|
20
|
+
* @returns {boolean} Whether every query term occurs in the combined fields.
|
|
21
|
+
*/
|
|
22
|
+
export function matchesText(values, query) {
|
|
23
|
+
// TODO(unify): share this tiny matcher with WP4's select filter once both branches merge.
|
|
24
|
+
const terms = normalizeText(query).trim().split(/\s+/).filter(Boolean);
|
|
25
|
+
if (terms.length === 0) return true;
|
|
26
|
+
const corpus = normalizeText(values.flat(Infinity).join(' '));
|
|
27
|
+
return terms.every((term) => corpus.includes(term));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns whether a filter state value represents an active filter.
|
|
32
|
+
* @param {unknown} value Filter value.
|
|
33
|
+
* @returns {boolean} Whether the value is active.
|
|
34
|
+
*/
|
|
35
|
+
export function isActiveFilterValue(value) {
|
|
36
|
+
return value != null && !(typeof value === 'string' && value.trim() === '');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolves all values addressed by a select or text filter definition.
|
|
41
|
+
* @param {FilterRow} row Source row.
|
|
42
|
+
* @param {Record<string, any>} definition Filter definition.
|
|
43
|
+
* @returns {unknown[]} Resolved values.
|
|
44
|
+
*/
|
|
45
|
+
export function getFilterValues(row, definition) {
|
|
46
|
+
if (typeof definition.get === 'function') return flattenValue(definition.get(row));
|
|
47
|
+
const fields = definition.fields ?? definition.field ?? definition.id;
|
|
48
|
+
const keys = Array.isArray(fields) ? fields : [fields];
|
|
49
|
+
return keys.flatMap((field) => {
|
|
50
|
+
if (typeof field === 'function') return flattenValue(field(row));
|
|
51
|
+
return flattenValue(field == null ? undefined : row?.[field]);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Derives distinct native-select options in first-seen order.
|
|
57
|
+
* Values shaped as `{value, label}` (or legacy `{field, label}`) retain their display label.
|
|
58
|
+
* @param {FilterRow[]} rows Source rows.
|
|
59
|
+
* @param {Record<string, any>} definition Select filter definition.
|
|
60
|
+
* @returns {Array<{value: unknown, label: string}>} Distinct options.
|
|
61
|
+
*/
|
|
62
|
+
export function deriveSelectOptions(rows, definition) {
|
|
63
|
+
const options = [];
|
|
64
|
+
const seen = new Map();
|
|
65
|
+
for (const row of rows) {
|
|
66
|
+
for (const candidate of getFilterValues(row, definition)) {
|
|
67
|
+
const normalized = normalizeOption(candidate);
|
|
68
|
+
if (normalized.value == null || normalized.value === '' || seen.has(normalized.value)) continue;
|
|
69
|
+
seen.set(normalized.value, true);
|
|
70
|
+
options.push(normalized);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return options;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Alias for the distinct-option derivation used by DataFilter. */
|
|
77
|
+
export const deriveOptions = deriveSelectOptions;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Applies all active filter definitions to an array without mutating it.
|
|
81
|
+
* Filters combine with AND; multiple fields inside one select filter combine with OR.
|
|
82
|
+
* @param {FilterRow[]} rows Source rows.
|
|
83
|
+
* @param {Array<Record<string, any>>} filterDefs Filter definitions.
|
|
84
|
+
* @param {FilterState} state Values keyed by filter id.
|
|
85
|
+
* @returns {FilterRow[]} Filtered copy.
|
|
86
|
+
*/
|
|
87
|
+
export function applyFilters(rows, filterDefs, state) {
|
|
88
|
+
return rows.filter((row) => filterDefs.every((definition) => {
|
|
89
|
+
const value = state?.[definition.id];
|
|
90
|
+
if (!isActiveFilterValue(value)) return true;
|
|
91
|
+
|
|
92
|
+
if (definition.type === 'custom') {
|
|
93
|
+
return typeof definition.predicate !== 'function' || Boolean(definition.predicate(row, value));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const values = getFilterValues(row, definition);
|
|
97
|
+
if (definition.type === 'text') return matchesText(values, value);
|
|
98
|
+
if (definition.type === 'select') {
|
|
99
|
+
return values.some((candidate) => sameValue(normalizeOption(candidate).value, value));
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** @param {unknown} value @returns {unknown[]} */
|
|
106
|
+
function flattenValue(value) {
|
|
107
|
+
return Array.isArray(value) ? value.flat(Infinity) : [value];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** @param {unknown} candidate @returns {{value: unknown, label: string}} */
|
|
111
|
+
function normalizeOption(candidate) {
|
|
112
|
+
if (candidate && typeof candidate === 'object' && !(candidate instanceof Date)) {
|
|
113
|
+
if (Object.prototype.hasOwnProperty.call(candidate, 'value')) {
|
|
114
|
+
return { value: candidate.value, label: String(candidate.label ?? candidate.value ?? '') };
|
|
115
|
+
}
|
|
116
|
+
if (Object.prototype.hasOwnProperty.call(candidate, 'field')) {
|
|
117
|
+
return { value: candidate.field, label: String(candidate.label ?? candidate.field ?? '') };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { value: candidate, label: String(candidate ?? '') };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @param {unknown} left @param {unknown} right @returns {boolean} */
|
|
124
|
+
function sameValue(left, right) {
|
|
125
|
+
if (Object.is(left, right)) return true;
|
|
126
|
+
if (left == null || right == null) return false;
|
|
127
|
+
return String(left) === String(right);
|
|
128
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
.zx-date-picker,
|
|
2
|
+
.zx-month-picker {
|
|
3
|
+
display: inline-block;
|
|
4
|
+
color: var(--zx-color-text);
|
|
5
|
+
font-size: var(--zx-control-font-size);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.zx-date-picker__surface,
|
|
9
|
+
.zx-month-picker__surface {
|
|
10
|
+
position: relative;
|
|
11
|
+
inline-size: 292px;
|
|
12
|
+
border: 1px solid var(--zx-color-border);
|
|
13
|
+
border-radius: var(--zx-radius-lg);
|
|
14
|
+
background: var(--zx-color-bg-surface);
|
|
15
|
+
padding: var(--zx-space-3);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.zx-date-picker__header,
|
|
19
|
+
.zx-month-picker__header,
|
|
20
|
+
.zx-date-picker__quick-header {
|
|
21
|
+
display: grid;
|
|
22
|
+
grid-template-columns: var(--zx-control-height) minmax(0, 1fr) var(--zx-control-height);
|
|
23
|
+
align-items: center;
|
|
24
|
+
gap: var(--zx-space-2);
|
|
25
|
+
margin-block-end: var(--zx-space-2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.zx-date-picker__nav,
|
|
29
|
+
.zx-month-picker__nav {
|
|
30
|
+
display: inline-grid;
|
|
31
|
+
place-items: center;
|
|
32
|
+
min-inline-size: var(--zx-control-height);
|
|
33
|
+
padding: 0;
|
|
34
|
+
border-color: var(--zx-color-bg-surface);
|
|
35
|
+
background: inherit;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.zx-date-picker__heading {
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
border-color: var(--zx-color-bg-surface);
|
|
41
|
+
background: inherit;
|
|
42
|
+
font-weight: 650;
|
|
43
|
+
text-overflow: ellipsis;
|
|
44
|
+
text-wrap: nowrap;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.zx-month-picker__year,
|
|
48
|
+
.zx-date-picker__quick-year {
|
|
49
|
+
text-align: center;
|
|
50
|
+
font-weight: 650;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.zx-date-picker__grid {
|
|
54
|
+
inline-size: 100%;
|
|
55
|
+
border-collapse: separate;
|
|
56
|
+
border-spacing: 2px;
|
|
57
|
+
table-layout: fixed;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.zx-date-picker__grid th {
|
|
61
|
+
block-size: 24px;
|
|
62
|
+
padding: 0;
|
|
63
|
+
color: var(--zx-color-text-muted);
|
|
64
|
+
font-size: var(--zx-text-xs);
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
text-align: center;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.zx-date-picker__grid td {
|
|
70
|
+
padding: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.zx-date-picker__day {
|
|
74
|
+
position: relative;
|
|
75
|
+
inline-size: 100%;
|
|
76
|
+
min-inline-size: 0;
|
|
77
|
+
min-block-size: var(--zx-control-height);
|
|
78
|
+
padding: 0;
|
|
79
|
+
border-color: var(--zx-color-bg-surface);
|
|
80
|
+
border-radius: var(--zx-radius-md);
|
|
81
|
+
background: inherit;
|
|
82
|
+
font-variant-numeric: tabular-nums;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.zx-date-picker__day[data-other-month="true"] {
|
|
86
|
+
color: var(--zx-color-text-muted);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.zx-date-picker__day[aria-current="date"] {
|
|
90
|
+
border-color: var(--zx-color-accent);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.zx-date-picker__day[aria-selected="true"] {
|
|
94
|
+
border-color: var(--zx-color-accent);
|
|
95
|
+
background: var(--zx-color-accent);
|
|
96
|
+
color: var(--zx-color-on-accent);
|
|
97
|
+
font-weight: 700;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.zx-date-picker__day[aria-disabled="true"] {
|
|
101
|
+
color: var(--zx-color-text-placeholder);
|
|
102
|
+
cursor: not-allowed;
|
|
103
|
+
text-decoration: line-through;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.zx-date-picker__day:focus-visible,
|
|
107
|
+
.zx-date-picker__heading:focus-visible,
|
|
108
|
+
.zx-date-picker__nav:focus-visible,
|
|
109
|
+
.zx-date-picker__quick-month:focus-visible,
|
|
110
|
+
.zx-month-picker__month:focus-visible,
|
|
111
|
+
.zx-month-picker__nav:focus-visible {
|
|
112
|
+
z-index: 1;
|
|
113
|
+
box-shadow: var(--zx-focus-ring);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.zx-date-picker__week,
|
|
117
|
+
.zx-date-picker__week-heading {
|
|
118
|
+
inline-size: 24px;
|
|
119
|
+
border-inline-end: 1px solid var(--zx-color-border);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.zx-date-picker__quick {
|
|
123
|
+
position: absolute;
|
|
124
|
+
z-index: 2;
|
|
125
|
+
inset: var(--zx-space-3);
|
|
126
|
+
border: 1px solid var(--zx-color-border-strong);
|
|
127
|
+
border-radius: var(--zx-radius-md);
|
|
128
|
+
background: var(--zx-color-bg-raised);
|
|
129
|
+
box-shadow: var(--zx-overlay-shadow);
|
|
130
|
+
padding: var(--zx-space-3);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.zx-date-picker__quick[hidden] {
|
|
134
|
+
display: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.zx-date-picker__quick-grid,
|
|
138
|
+
.zx-month-picker__grid {
|
|
139
|
+
display: grid;
|
|
140
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
141
|
+
gap: var(--zx-space-2);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.zx-date-picker__quick-month,
|
|
145
|
+
.zx-month-picker__month {
|
|
146
|
+
min-inline-size: 0;
|
|
147
|
+
padding-inline: var(--zx-space-1);
|
|
148
|
+
border-color: var(--zx-color-bg-surface);
|
|
149
|
+
background: inherit;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.zx-date-picker__quick-month[aria-selected="true"],
|
|
153
|
+
.zx-month-picker__month[aria-selected="true"] {
|
|
154
|
+
background: var(--zx-color-bg-selected);
|
|
155
|
+
color: var(--zx-color-accent);
|
|
156
|
+
font-weight: 700;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.zx-month-picker__month[aria-disabled="true"] {
|
|
160
|
+
color: var(--zx-color-text-placeholder);
|
|
161
|
+
cursor: not-allowed;
|
|
162
|
+
text-decoration: line-through;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.zx-date-picker > .zx-time-picker,
|
|
166
|
+
.zx-date-picker__surface > .zx-time-picker {
|
|
167
|
+
display: block;
|
|
168
|
+
margin-block-start: var(--zx-space-3);
|
|
169
|
+
border-block-start: 1px solid var(--zx-color-border);
|
|
170
|
+
padding-block-start: var(--zx-space-3);
|
|
171
|
+
text-align: center;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
175
|
+
.zx-date-picker__day,
|
|
176
|
+
.zx-date-picker__quick-month,
|
|
177
|
+
.zx-month-picker__month {
|
|
178
|
+
transition: background-color var(--zx-dur-fast) var(--zx-ease),
|
|
179
|
+
color var(--zx-dur-fast) var(--zx-ease);
|
|
180
|
+
}
|
|
181
|
+
}
|