@zeyos/zx 4.3.4 → 5.0.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 +438 -0
- package/README.md +49 -10
- package/dist/types/components/activity-item/activity-item.d.ts +351 -0
- package/dist/types/components/activity-list/activity-list.d.ts +217 -0
- package/dist/types/components/app-sidebar/app-sidebar.d.ts +33 -3
- package/dist/types/components/avatar/avatar.d.ts +46 -7
- package/dist/types/components/badge/badge.d.ts +21 -1
- package/dist/types/components/button/button.d.ts +23 -0
- package/dist/types/components/calendar/calendar.d.ts +19 -0
- package/dist/types/components/card/card.d.ts +7 -0
- package/dist/types/components/card-view/card-view.d.ts +11 -3
- package/dist/types/components/card-view/record-card.d.ts +7 -1
- package/dist/types/components/chart/chart.d.ts +10 -0
- package/dist/types/components/checklist/checklist.d.ts +9 -0
- package/dist/types/components/code-editor/code-editor.d.ts +149 -0
- package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
- package/dist/types/components/context-menu/context-menu.d.ts +18 -2
- package/dist/types/components/date-picker/date-picker.d.ts +8 -0
- package/dist/types/components/date-picker/time-picker.d.ts +7 -0
- package/dist/types/components/datebox/date-range-box.d.ts +5 -1
- package/dist/types/components/datebox/datebox.d.ts +7 -2
- package/dist/types/components/dialog/dialog.d.ts +75 -6
- package/dist/types/components/dropdown/dropdown.d.ts +2 -2
- package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
- package/dist/types/components/field/field.d.ts +24 -2
- package/dist/types/components/fieldset/fieldset.d.ts +19 -1
- package/dist/types/components/file-item/file-item.d.ts +262 -0
- package/dist/types/components/file-list/file-list.d.ts +171 -0
- package/dist/types/components/filter/filter.d.ts +91 -36
- package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
- package/dist/types/components/form/form.d.ts +12 -1
- package/dist/types/components/kanban-view/kanban-card.d.ts +146 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
- package/dist/types/components/launcher/launcher.d.ts +278 -26
- package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
- package/dist/types/components/menu-button/menu-button.d.ts +37 -42
- package/dist/types/components/message/message.d.ts +126 -17
- package/dist/types/components/modal/modal.d.ts +61 -2
- package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
- package/dist/types/components/panel/panel.d.ts +7 -0
- package/dist/types/components/search/search.d.ts +7 -0
- package/dist/types/components/select/color.d.ts +12 -0
- package/dist/types/components/select/glyphs.d.ts +124 -0
- package/dist/types/components/select/select.d.ts +15 -3
- package/dist/types/components/sort-control/sort-control.d.ts +105 -0
- package/dist/types/components/stat-tile/stat-tile.d.ts +288 -0
- package/dist/types/components/tabbox/tabbox.d.ts +16 -0
- package/dist/types/components/table/table.d.ts +190 -1
- package/dist/types/components/table-view/table-view.d.ts +11 -5
- package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
- package/dist/types/components/timebox/timebox.d.ts +7 -0
- package/dist/types/components/tree/tree.d.ts +13 -0
- package/dist/types/components/value-list/value-list.d.ts +8 -0
- package/dist/types/components/view/record-view.d.ts +96 -5
- package/dist/types/core/breakpoint.d.ts +1 -1
- package/dist/types/core/icons.d.ts +37 -22
- package/dist/types/core/motion.d.ts +59 -0
- package/dist/types/core/overlay-host.d.ts +15 -1
- package/dist/types/index.d.ts +16 -3
- package/dist/types/internal/app-rail.d.ts +10 -0
- package/dist/types/internal/menu-items.d.ts +134 -0
- package/dist/types/zeyos/connect.d.ts +6 -0
- package/dist/types/zeyos/form.d.ts +50 -9
- package/dist/types/zeyos/index.d.ts +1 -1
- package/dist/types/zeyos/query.d.ts +79 -0
- package/dist/types/zeyos/schema.d.ts +24 -1
- package/dist/types/zeyos/table.d.ts +100 -14
- package/dist/types/zeyos/view.d.ts +10 -0
- package/dist/zx-zeyos.esm.js +2206 -489
- package/dist/zx-zeyos.esm.min.js +56 -1
- package/dist/zx.css +3322 -724
- package/dist/zx.esm.js +12244 -5809
- package/dist/zx.esm.min.js +58 -3
- package/dist/zx.global.js +12246 -5811
- package/dist/zx.global.min.js +58 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1683 -146
- package/docs/llms.md +777 -110
- package/docs/llms.txt +14 -8
- package/package.json +1 -1
- package/src/components/account-menu/account-menu.css +35 -10
- package/src/components/activity-item/activity-item.css +137 -0
- package/src/components/activity-item/activity-item.js +602 -0
- package/src/components/activity-list/activity-list.css +99 -0
- package/src/components/activity-list/activity-list.js +409 -0
- package/src/components/app-icon/app-icon.css +1 -1
- package/src/components/app-rail/app-rail.css +191 -22
- package/src/components/app-sidebar/app-sidebar.css +349 -24
- package/src/components/app-sidebar/app-sidebar.js +86 -11
- package/src/components/aurora/aurora.css +5 -3
- package/src/components/avatar/avatar.css +32 -7
- package/src/components/avatar/avatar.js +72 -8
- package/src/components/badge/badge.css +54 -2
- package/src/components/badge/badge.js +29 -2
- package/src/components/button/button.css +74 -8
- package/src/components/button/button.js +44 -1
- package/src/components/calendar/calendar.css +46 -46
- package/src/components/calendar/calendar.js +33 -3
- package/src/components/card/card.css +2 -2
- package/src/components/card/card.js +12 -1
- package/src/components/card-view/card-view.css +10 -7
- package/src/components/card-view/card-view.js +25 -9
- package/src/components/card-view/record-card.js +7 -3
- package/src/components/chart/chart.css +4 -1
- package/src/components/chart/chart.js +12 -0
- package/src/components/check-button/check-button.css +10 -2
- package/src/components/checklist/checklist.css +9 -2
- package/src/components/checklist/checklist.js +11 -5
- package/src/components/checklist/field-adapter.js +3 -1
- package/src/components/code-editor/code-editor.css +68 -0
- package/src/components/code-editor/code-editor.js +306 -0
- package/src/components/code-editor/field-adapter.js +34 -0
- package/src/components/context-menu/context-menu.css +87 -5
- package/src/components/context-menu/context-menu.js +22 -30
- package/src/components/copy/copy-button.css +6 -0
- package/src/components/copy/copy-input.css +8 -3
- package/src/components/data-filter/data-filter.css +5 -4
- package/src/components/date-picker/date-picker.css +6 -6
- package/src/components/date-picker/date-picker.js +22 -8
- package/src/components/date-picker/date-range-picker.css +3 -3
- package/src/components/date-picker/month-picker.js +3 -3
- package/src/components/date-picker/time-picker.css +1 -1
- package/src/components/date-picker/time-picker.js +20 -8
- package/src/components/datebox/date-range-box.css +79 -2
- package/src/components/datebox/date-range-box.js +13 -2
- package/src/components/datebox/datebox.css +79 -2
- package/src/components/datebox/datebox.js +13 -2
- package/src/components/dialog/dialog.css +26 -0
- package/src/components/dialog/dialog.js +174 -70
- package/src/components/dropdown/dropdown.css +1 -1
- package/src/components/dropdown/dropdown.js +7 -5
- package/src/components/empty-state/empty-state.css +2 -2
- package/src/components/entity-ref/entity-ref.css +182 -0
- package/src/components/entity-ref/entity-ref.js +496 -0
- package/src/components/field/field.css +28 -9
- package/src/components/field/field.js +68 -26
- package/src/components/field-upload/field-upload.css +1 -1
- package/src/components/fieldset/fieldset.css +5 -3
- package/src/components/fieldset/fieldset.js +28 -5
- package/src/components/file-item/file-item.css +88 -0
- package/src/components/file-item/file-item.js +505 -0
- package/src/components/file-list/file-list.css +51 -0
- package/src/components/file-list/file-list.js +320 -0
- package/src/components/filter/filter.css +27 -1
- package/src/components/filter/filter.js +205 -47
- package/src/components/filter-panel/filter-panel.css +104 -0
- package/src/components/filter-panel/filter-panel.js +964 -0
- package/src/components/finder/finder.css +15 -13
- package/src/components/form/form.css +5 -4
- package/src/components/form/form.js +11 -2
- package/src/components/grid/grid.css +1 -1
- package/src/components/groupbox/groupbox.css +1 -1
- package/src/components/kanban-view/kanban-card.js +247 -0
- package/src/components/kanban-view/kanban-view.css +226 -8
- package/src/components/kanban-view/kanban-view.js +255 -12
- package/src/components/launcher/launcher.css +107 -25
- package/src/components/launcher/launcher.js +557 -63
- package/src/components/list-toolbar/list-toolbar.css +194 -0
- package/src/components/list-toolbar/list-toolbar.js +851 -0
- package/src/components/master-panel/master-panel.css +2 -0
- package/src/components/menu-button/menu-button.css +87 -5
- package/src/components/menu-button/menu-button.js +86 -46
- package/src/components/message/message.css +38 -5
- package/src/components/message/message.js +230 -17
- package/src/components/modal/modal.css +15 -0
- package/src/components/modal/modal.js +159 -3
- package/src/components/navigation-bar/navigation-bar.css +95 -11
- package/src/components/navigation-bar/navigation-bar.js +171 -4
- package/src/components/number-field/field-adapter.js +1 -0
- package/src/components/number-field/number-field.css +70 -0
- package/src/components/number-field/number-field.js +2 -0
- package/src/components/pagination/pagination.css +8 -6
- package/src/components/panel/panel.css +1 -1
- package/src/components/panel/panel.js +13 -2
- package/src/components/rating/rating.css +7 -0
- package/src/components/search/search.js +17 -5
- package/src/components/select/color.js +22 -0
- package/src/components/select/field-adapter.js +6 -1
- package/src/components/select/glyphs.js +165 -0
- package/src/components/select/select.css +138 -45
- package/src/components/select/select.js +86 -47
- package/src/components/sheet/sheet.css +11 -8
- package/src/components/slider/slider.css +16 -2
- package/src/components/slider/slider.js +1 -1
- package/src/components/sort-control/sort-control.css +20 -0
- package/src/components/sort-control/sort-control.js +140 -0
- package/src/components/stat-tile/stat-tile.css +208 -0
- package/src/components/stat-tile/stat-tile.js +601 -0
- package/src/components/tabbox/tabbox.css +32 -27
- package/src/components/tabbox/tabbox.js +17 -2
- package/src/components/table/table.css +155 -31
- package/src/components/table/table.js +386 -33
- package/src/components/table-view/table-view.css +8 -2
- package/src/components/table-view/table-view.js +21 -6
- package/src/components/tag-picker/tag-picker.css +86 -6
- package/src/components/tag-picker/tag-picker.js +25 -8
- package/src/components/timebox/timebox.css +22 -2
- package/src/components/timebox/timebox.js +18 -5
- package/src/components/toggle/toggle.css +12 -1
- package/src/components/toolbar/toolbar.js +2 -2
- package/src/components/tooltip/tooltip.css +4 -0
- package/src/components/tooltip/tooltip.js +10 -3
- package/src/components/tree/tree.css +25 -16
- package/src/components/tree/tree.js +31 -0
- package/src/components/value-list/value-list.css +3 -3
- package/src/components/value-list/value-list.js +22 -6
- package/src/components/view/record-view.css +5 -0
- package/src/components/view/record-view.js +100 -7
- package/src/core/breakpoint.js +1 -1
- package/src/core/icons.js +259 -57
- package/src/core/motion.js +147 -0
- package/src/core/overlay-host.js +21 -2
- package/src/index.js +30 -3
- package/src/internal/app-rail.js +18 -1
- package/src/internal/menu-items.js +142 -0
- package/src/zeyos/connect.js +6 -2
- package/src/zeyos/form.js +67 -27
- package/src/zeyos/index.js +1 -1
- package/src/zeyos/query.js +153 -0
- package/src/zeyos/schema.js +144 -27
- package/src/zeyos/table.js +68 -5
- package/src/zeyos/view.js +6 -0
- package/styles/base.css +56 -0
- package/styles/icons.css +11 -1
- package/styles/print.css +192 -0
- package/styles/tokens/dark.css +113 -37
- package/styles/tokens/global.css +121 -27
- package/styles/tokens/semantic.css +100 -31
- package/styles/tokens/themes.css +7 -6
- package/styles/zx.css +11 -1
- package/styles/tokens/density.css +0 -9
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
padding-inline: var(--zx-space-2);
|
|
30
30
|
color: var(--zx-color-accent);
|
|
31
31
|
font-size: var(--zx-text-xl);
|
|
32
|
-
font-weight:
|
|
32
|
+
font-weight: 650;
|
|
33
|
+
letter-spacing: var(--zx-tracking-title);
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
.zx-navigation-bar__navigation {
|
|
@@ -51,10 +52,11 @@
|
|
|
51
52
|
gap: var(--zx-space-2);
|
|
52
53
|
border: 0;
|
|
53
54
|
border-radius: var(--zx-control-radius);
|
|
54
|
-
background:
|
|
55
|
+
background: transparent;
|
|
55
56
|
padding-inline: var(--zx-control-pad-x);
|
|
56
57
|
color: var(--zx-color-text-muted);
|
|
57
58
|
font: inherit;
|
|
59
|
+
font-weight: var(--zx-weight-medium);
|
|
58
60
|
text-decoration: none;
|
|
59
61
|
cursor: pointer;
|
|
60
62
|
}
|
|
@@ -64,11 +66,12 @@
|
|
|
64
66
|
color: var(--zx-color-text);
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
/* Where you are, not a pick: the neutral current fill, the full text colour, a heavier label. */
|
|
67
70
|
.zx-navigation-bar__item[aria-current="page"],
|
|
68
71
|
.zx-navigation-bar__item[aria-selected="true"] {
|
|
69
|
-
background: var(--zx-color-bg-
|
|
70
|
-
color: var(--zx-color-
|
|
71
|
-
font-weight:
|
|
72
|
+
background: var(--zx-color-bg-current);
|
|
73
|
+
color: var(--zx-color-text);
|
|
74
|
+
font-weight: var(--zx-weight-semibold);
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
.zx-navigation-bar__item[aria-disabled="true"] {
|
|
@@ -81,18 +84,24 @@
|
|
|
81
84
|
box-shadow: var(--zx-focus-ring);
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
/* A count beside a label, not a call to action: neutral, like the Tabbox count. */
|
|
84
88
|
.zx-navigation-bar__badge {
|
|
85
89
|
min-inline-size: var(--zx-space-4);
|
|
86
90
|
border-radius: var(--zx-radius-full);
|
|
87
|
-
background: var(--zx-color-
|
|
91
|
+
background: var(--zx-color-bg-current);
|
|
88
92
|
padding-inline: var(--zx-space-1);
|
|
89
|
-
color: var(--zx-color-
|
|
90
|
-
font-size: var(--zx-text-
|
|
91
|
-
font-
|
|
93
|
+
color: var(--zx-color-text-muted);
|
|
94
|
+
font-size: var(--zx-text-xs);
|
|
95
|
+
font-variant-numeric: tabular-nums;
|
|
96
|
+
font-weight: var(--zx-weight-medium);
|
|
92
97
|
line-height: var(--zx-space-4);
|
|
93
98
|
text-align: center;
|
|
94
99
|
}
|
|
95
100
|
|
|
101
|
+
.zx-navigation-bar__item:is([aria-current="page"], [aria-selected="true"]) .zx-navigation-bar__badge {
|
|
102
|
+
color: var(--zx-color-text);
|
|
103
|
+
}
|
|
104
|
+
|
|
96
105
|
.zx-navigation-bar__more {
|
|
97
106
|
display: none;
|
|
98
107
|
}
|
|
@@ -138,12 +147,87 @@
|
|
|
138
147
|
box-shadow: inset var(--zx-focus-ring);
|
|
139
148
|
}
|
|
140
149
|
|
|
150
|
+
/*
|
|
151
|
+
* Overflow.
|
|
152
|
+
*
|
|
153
|
+
* Three modes, written by the component onto the root as `data-overflow`:
|
|
154
|
+
*
|
|
155
|
+
* all — every item collapses (the default, `minVisible: 0`): the row is taken out and the
|
|
156
|
+
* More button replaces it, which is what a top app bar wants.
|
|
157
|
+
* partial — `minVisible` items stay in the row and the rest move into the menu. The row and
|
|
158
|
+
* the button are then on screen together, which is the one case the navigation has
|
|
159
|
+
* to lay them out side by side.
|
|
160
|
+
* off — `overflow: false` or `overflowBelow: false`: nothing ever collapses, and the
|
|
161
|
+
* More button never appears at any width.
|
|
162
|
+
*
|
|
163
|
+
* The threshold is the awkward part, because a container query cannot read a custom property. So
|
|
164
|
+
* the default threshold lives here, in the query below, and any other one is measured by the
|
|
165
|
+
* component, which writes `data-overflow-below` (stand down, this bar has its own threshold) and
|
|
166
|
+
* `data-narrow`. The two blocks carry the same three declarations for that reason — a change to
|
|
167
|
+
* one belongs in the other.
|
|
168
|
+
*/
|
|
169
|
+
.zx-navigation-bar[data-overflow="partial"] .zx-navigation-bar__navigation {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
gap: var(--zx-space-1);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/*
|
|
176
|
+
* `partial` and `off` are the two modes that keep items in the row at a width the row was never
|
|
177
|
+
* going to fit: `minVisible` is a promise that those items stay, not that they fit, and
|
|
178
|
+
* `overflow: false` is the same promise for all of them. They give way by truncating their own
|
|
179
|
+
* labels rather than by spilling out of the bar. The badge and the More button keep their size —
|
|
180
|
+
* a count that says "4" and a button that says "More" have nothing to give.
|
|
181
|
+
*
|
|
182
|
+
* The default `all` mode is deliberately not in this list: it takes the whole row out instead, and
|
|
183
|
+
* it never has both on screen at once.
|
|
184
|
+
*/
|
|
185
|
+
.zx-navigation-bar[data-overflow="partial"] .zx-navigation-bar__item,
|
|
186
|
+
.zx-navigation-bar[data-overflow="partial"] .zx-navigation-bar__label,
|
|
187
|
+
.zx-navigation-bar[data-overflow="off"] .zx-navigation-bar__item,
|
|
188
|
+
.zx-navigation-bar[data-overflow="off"] .zx-navigation-bar__label {
|
|
189
|
+
min-inline-size: 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.zx-navigation-bar[data-overflow="partial"] .zx-navigation-bar__label,
|
|
193
|
+
.zx-navigation-bar[data-overflow="off"] .zx-navigation-bar__label {
|
|
194
|
+
overflow: hidden;
|
|
195
|
+
text-overflow: ellipsis;
|
|
196
|
+
white-space: nowrap;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.zx-navigation-bar[data-overflow="partial"] .zx-navigation-bar__badge,
|
|
200
|
+
.zx-navigation-bar[data-overflow="partial"] .zx-navigation-bar__more,
|
|
201
|
+
.zx-navigation-bar[data-overflow="off"] .zx-navigation-bar__badge {
|
|
202
|
+
flex: none;
|
|
203
|
+
}
|
|
204
|
+
|
|
141
205
|
@container (max-width: 44rem) {
|
|
142
|
-
.zx-navigation-bar__items {
|
|
206
|
+
.zx-navigation-bar[data-overflow="all"]:not([data-overflow-below]) .zx-navigation-bar__items {
|
|
143
207
|
display: none;
|
|
144
208
|
}
|
|
145
209
|
|
|
146
|
-
.zx-navigation-
|
|
210
|
+
.zx-navigation-bar[data-overflow="partial"]:not([data-overflow-below])
|
|
211
|
+
.zx-navigation-bar__item[data-collapsed="true"] {
|
|
212
|
+
display: none;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.zx-navigation-bar:not([data-overflow="off"]):not([data-overflow-below])
|
|
216
|
+
.zx-navigation-bar__more:not([data-empty="true"]) {
|
|
147
217
|
display: inline-flex;
|
|
148
218
|
}
|
|
149
219
|
}
|
|
220
|
+
|
|
221
|
+
.zx-navigation-bar[data-overflow="all"][data-narrow="true"] .zx-navigation-bar__items {
|
|
222
|
+
display: none;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.zx-navigation-bar[data-overflow="partial"][data-narrow="true"]
|
|
226
|
+
.zx-navigation-bar__item[data-collapsed="true"] {
|
|
227
|
+
display: none;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.zx-navigation-bar[data-narrow="true"]:not([data-overflow="off"])
|
|
231
|
+
.zx-navigation-bar__more:not([data-empty="true"]) {
|
|
232
|
+
display: inline-flex;
|
|
233
|
+
}
|
|
@@ -35,9 +35,29 @@ import { MenuButton } from '../menu-button/menu-button.js';
|
|
|
35
35
|
* @property {NavigationItem[]} [items=[]] Navigation items.
|
|
36
36
|
* @property {string|null} [active=null] Initially active item, or the first enabled item.
|
|
37
37
|
* @property {NavigationAction[]} [actions=[]] Right-aligned action elements or descriptors.
|
|
38
|
+
* @property {boolean} [overflow=true] Whether items collapse into the overflow menu at all.
|
|
39
|
+
* `false` keeps every item in the bar at every width and never shows the More button.
|
|
40
|
+
* @property {string|number|false} [overflowBelow='44rem'] Container inline size at or below which
|
|
41
|
+
* items collapse, as a `px` or `rem` length (a number is read as `px`); `false` disables
|
|
42
|
+
* collapsing exactly as `overflow: false` does. The default is the threshold the stylesheet's
|
|
43
|
+
* own container query carries, and a bar that keeps it collapses in CSS without measuring
|
|
44
|
+
* itself; any other value is watched with a `ResizeObserver`.
|
|
45
|
+
* @property {number} [minVisible=0] How many items, counted from the start, never collapse. The
|
|
46
|
+
* default collapses all of them, which is right for a top app bar and wrong for a phone bottom
|
|
47
|
+
* bar — `minVisible: 4` is what makes that bar usable.
|
|
38
48
|
* @property {(event: CustomEvent<{name: string}>) => void} [onchange] Change callback.
|
|
39
49
|
*/
|
|
40
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The container width `navigation-bar.css` collapses at. A bar that keeps this exact threshold is
|
|
53
|
+
* served by the stylesheet's container query, so it needs no measurement and no script at all;
|
|
54
|
+
* anything else has to be watched. Keep it in step with the `@container` rule.
|
|
55
|
+
*/
|
|
56
|
+
export const OVERFLOW_BELOW = '44rem';
|
|
57
|
+
|
|
58
|
+
/** The lengths a threshold may be written in — the two a `ResizeObserver` can resolve alone. */
|
|
59
|
+
const OVERFLOW_LENGTH = /^\d+(?:\.\d+)?(?:px|rem)$/;
|
|
60
|
+
|
|
41
61
|
/**
|
|
42
62
|
* @typedef {Object} NavigationRecord
|
|
43
63
|
* @property {NavigationItem} definition Normalized item definition.
|
|
@@ -60,7 +80,10 @@ export class NavigationBar extends Component {
|
|
|
60
80
|
title: '',
|
|
61
81
|
items: [],
|
|
62
82
|
active: null,
|
|
63
|
-
actions: []
|
|
83
|
+
actions: [],
|
|
84
|
+
overflow: true,
|
|
85
|
+
overflowBelow: OVERFLOW_BELOW,
|
|
86
|
+
minVisible: 0
|
|
64
87
|
};
|
|
65
88
|
|
|
66
89
|
/**
|
|
@@ -85,6 +108,7 @@ export class NavigationBar extends Component {
|
|
|
85
108
|
if (record.definition.href) globalThis.location?.assign(record.definition.href);
|
|
86
109
|
});
|
|
87
110
|
this._syncOverflow();
|
|
111
|
+
this._observeWidth();
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
/** @returns {HTMLElement} */
|
|
@@ -100,6 +124,8 @@ export class NavigationBar extends Component {
|
|
|
100
124
|
this._activeName = null;
|
|
101
125
|
this._tabMode = false;
|
|
102
126
|
this._overflow = null;
|
|
127
|
+
this._observer = null;
|
|
128
|
+
this._frame = 0;
|
|
103
129
|
|
|
104
130
|
const bar = h('header', { class: 'zx-navigation-bar__bar' },
|
|
105
131
|
h('div', { class: 'zx-navigation-bar__brand', ref: 'title' }),
|
|
@@ -257,6 +283,10 @@ export class NavigationBar extends Component {
|
|
|
257
283
|
destroy() {
|
|
258
284
|
if (this._cleaned) return;
|
|
259
285
|
this._cleaned = true;
|
|
286
|
+
if (this._frame && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(this._frame);
|
|
287
|
+
this._frame = 0;
|
|
288
|
+
this._observer?.disconnect();
|
|
289
|
+
this._observer = null;
|
|
260
290
|
this._overflow?.destroy();
|
|
261
291
|
super.destroy();
|
|
262
292
|
if (!this._createdRoot && this._original) restore(this.el, this._original);
|
|
@@ -382,11 +412,34 @@ export class NavigationBar extends Component {
|
|
|
382
412
|
}
|
|
383
413
|
}
|
|
384
414
|
|
|
385
|
-
/**
|
|
415
|
+
/**
|
|
416
|
+
* Republishes the overflow state: which items may collapse, which mode the stylesheet should
|
|
417
|
+
* apply, and what the More menu contains. With the default `minVisible: 0` every item collapses
|
|
418
|
+
* and the menu lists them all, which is what it has always done.
|
|
419
|
+
* @returns {void}
|
|
420
|
+
*/
|
|
386
421
|
_syncOverflow() {
|
|
422
|
+
const below = resolveOverflowBelow(this.options);
|
|
423
|
+
const minVisible = below === null
|
|
424
|
+
? this._items.length
|
|
425
|
+
: resolveMinVisible(this.options, this._items.length);
|
|
426
|
+
const collapsing = this._items.slice(minVisible);
|
|
427
|
+
const root = /** @type {HTMLElement} */ (this.el);
|
|
428
|
+
|
|
429
|
+
for (const [index, record] of this._items.entries()) {
|
|
430
|
+
record.item.dataset.collapsed = String(index >= minVisible);
|
|
431
|
+
}
|
|
432
|
+
root.dataset.overflow = below === null ? 'off' : (minVisible === 0 ? 'all' : 'partial');
|
|
433
|
+
/*
|
|
434
|
+
* The attribute is the stylesheet's own switch: present means "a threshold I do not know", so
|
|
435
|
+
* the container query stands down and the observer below drives `data-narrow` instead.
|
|
436
|
+
*/
|
|
437
|
+
if (below === null || below === OVERFLOW_BELOW) delete root.dataset.overflowBelow;
|
|
438
|
+
else root.dataset.overflowBelow = below;
|
|
439
|
+
|
|
387
440
|
if (!this._overflow) return;
|
|
388
|
-
this.refs.more.dataset.empty =
|
|
389
|
-
this._overflow.setItems(
|
|
441
|
+
this.refs.more.dataset.empty = collapsing.length === 0 ? 'true' : 'false';
|
|
442
|
+
this._overflow.setItems(collapsing.map((record) => ({
|
|
390
443
|
label: record.definition.badge === null ? record.definition.title :
|
|
391
444
|
`${record.definition.title} (${record.definition.badge})`,
|
|
392
445
|
value: record.definition.name,
|
|
@@ -394,6 +447,53 @@ export class NavigationBar extends Component {
|
|
|
394
447
|
})));
|
|
395
448
|
}
|
|
396
449
|
|
|
450
|
+
/**
|
|
451
|
+
* Watches the bar's own inline size when `overflowBelow` names a threshold the stylesheet cannot
|
|
452
|
+
* express. A bar on the default threshold installs nothing: its container query has already
|
|
453
|
+
* decided before the first frame of script, which is why the default path stays exactly as it
|
|
454
|
+
* was.
|
|
455
|
+
* @returns {void}
|
|
456
|
+
*/
|
|
457
|
+
_observeWidth() {
|
|
458
|
+
const below = resolveOverflowBelow(this.options);
|
|
459
|
+
if (below === null || below === OVERFLOW_BELOW) return;
|
|
460
|
+
if (typeof ResizeObserver !== 'function') return;
|
|
461
|
+
const root = /** @type {HTMLElement} */ (this.el);
|
|
462
|
+
|
|
463
|
+
const measure = () => {
|
|
464
|
+
const width = root.getBoundingClientRect().width;
|
|
465
|
+
/*
|
|
466
|
+
* Zero is not a narrow bar, it is no measurement at all — what an element reports before it
|
|
467
|
+
* has been laid out. Recording it would latch the collapsed state and never notice the real
|
|
468
|
+
* width, which is the failure `src/core/breakpoint.js` documents.
|
|
469
|
+
*/
|
|
470
|
+
if (!width) return;
|
|
471
|
+
const rootElement = globalThis.document?.documentElement;
|
|
472
|
+
const rootFontSize = rootElement
|
|
473
|
+
? Number.parseFloat(globalThis.getComputedStyle(rootElement).fontSize)
|
|
474
|
+
: Number.NaN;
|
|
475
|
+
// `<=`, because the container query this replaces is a `max-width` and that is inclusive.
|
|
476
|
+
root.dataset.narrow = String(width <= lengthToPixels(below, rootFontSize));
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
this._observer = new ResizeObserver(() => {
|
|
480
|
+
/*
|
|
481
|
+
* Answered on the next frame rather than inside the callback: collapsing the bar resizes the
|
|
482
|
+
* very element being observed, which the browser reports as an undelivered-notification loop.
|
|
483
|
+
*/
|
|
484
|
+
if (this._frame || typeof requestAnimationFrame !== 'function') {
|
|
485
|
+
if (typeof requestAnimationFrame !== 'function') measure();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
this._frame = requestAnimationFrame(() => {
|
|
489
|
+
this._frame = 0;
|
|
490
|
+
measure();
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
this._observer.observe(root);
|
|
494
|
+
measure();
|
|
495
|
+
}
|
|
496
|
+
|
|
397
497
|
/** @param {NavigationRecord} record @returns {void} */
|
|
398
498
|
_focusTab(record) {
|
|
399
499
|
for (const item of this._items) item.item.tabIndex = item === record ? 0 : -1;
|
|
@@ -424,6 +524,73 @@ export class NavigationBar extends Component {
|
|
|
424
524
|
* @type {CustomEvent<{name: string}>}
|
|
425
525
|
*/
|
|
426
526
|
|
|
527
|
+
/**
|
|
528
|
+
* Resolves the container inline size at or below which items collapse into the overflow menu.
|
|
529
|
+
*
|
|
530
|
+
* Two options can switch collapsing off, because they answer two different questions: `overflow`
|
|
531
|
+
* is whether the bar has an overflow menu at all, and `overflowBelow` is where it starts using it.
|
|
532
|
+
* Either being `false` means the same thing to the stylesheet — nothing ever collapses — so both
|
|
533
|
+
* resolve to `null` here and the caller has one state to handle rather than two.
|
|
534
|
+
* @param {NavigationBarOptions} [options={}] Navigation options, whole or partial.
|
|
535
|
+
* @returns {string|null} A `px` or `rem` length, or `null` when nothing ever collapses.
|
|
536
|
+
*/
|
|
537
|
+
export function resolveOverflowBelow(options = {}) {
|
|
538
|
+
const enabled = options.overflow ?? true;
|
|
539
|
+
if (typeof enabled !== 'boolean') {
|
|
540
|
+
throw new TypeError('NavigationBar overflow must be a boolean');
|
|
541
|
+
}
|
|
542
|
+
const below = options.overflowBelow ?? OVERFLOW_BELOW;
|
|
543
|
+
if (!enabled || below === false) return null;
|
|
544
|
+
if (typeof below === 'number') {
|
|
545
|
+
if (!Number.isFinite(below) || below < 0) {
|
|
546
|
+
throw new RangeError(`NavigationBar overflowBelow must not be negative: ${below}`);
|
|
547
|
+
}
|
|
548
|
+
return `${below}px`;
|
|
549
|
+
}
|
|
550
|
+
if (typeof below !== 'string' || !OVERFLOW_LENGTH.test(below.trim())) {
|
|
551
|
+
throw new TypeError(
|
|
552
|
+
`NavigationBar overflowBelow must be a px or rem length, a number of pixels, or false: ${String(below)}`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
return below.trim();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* How many items, counted from the start, stay in the bar when it collapses.
|
|
560
|
+
*
|
|
561
|
+
* Clamping to the item count is the whole arithmetic: `minVisible: 4` on a three-item bar keeps
|
|
562
|
+
* three, so the overflow menu is empty rather than holding a phantom fourth entry, and the bar
|
|
563
|
+
* hides its More button instead of opening onto nothing.
|
|
564
|
+
* @param {NavigationBarOptions} [options={}] Navigation options, whole or partial.
|
|
565
|
+
* @param {number} [count=0] How many items the bar currently has.
|
|
566
|
+
* @returns {number} A count between 0 and `count`.
|
|
567
|
+
*/
|
|
568
|
+
export function resolveMinVisible(options = {}, count = 0) {
|
|
569
|
+
const requested = options.minVisible ?? 0;
|
|
570
|
+
if (!Number.isInteger(requested) || requested < 0) {
|
|
571
|
+
throw new TypeError(`NavigationBar minVisible must be a non-negative integer: ${String(requested)}`);
|
|
572
|
+
}
|
|
573
|
+
const total = Number.isInteger(count) && count > 0 ? count : 0;
|
|
574
|
+
return Math.min(requested, total);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Converts an overflow threshold to pixels, so a measured width can be compared with it.
|
|
579
|
+
* @param {string|number} length A `px` or `rem` length, or a number of pixels.
|
|
580
|
+
* @param {number} [rootFontSize=16] Computed root font size; anything unusable falls back to 16.
|
|
581
|
+
* @returns {number} The threshold in pixels.
|
|
582
|
+
*/
|
|
583
|
+
export function lengthToPixels(length, rootFontSize = 16) {
|
|
584
|
+
const value = typeof length === 'number' ? `${length}px` : String(length).trim();
|
|
585
|
+
if (!OVERFLOW_LENGTH.test(value)) {
|
|
586
|
+
throw new TypeError(`NavigationBar cannot measure the length "${String(length)}"`);
|
|
587
|
+
}
|
|
588
|
+
const size = Number.parseFloat(value);
|
|
589
|
+
if (!value.endsWith('rem')) return size;
|
|
590
|
+
const root = Number(rootFontSize);
|
|
591
|
+
return size * (Number.isFinite(root) && root > 0 ? root : 16);
|
|
592
|
+
}
|
|
593
|
+
|
|
427
594
|
/** @param {unknown} content @returns {content is NavigationPanelContent} */
|
|
428
595
|
function isPanelContent(content) {
|
|
429
596
|
if (typeof content === 'function' || content instanceof Component) return true;
|
|
@@ -11,6 +11,7 @@ export function registerNumberFieldAdapter() {
|
|
|
11
11
|
label: options.label ? String(options.label) : undefined,
|
|
12
12
|
placeholder: options.placeholder,
|
|
13
13
|
required: options.required,
|
|
14
|
+
name: field.name || null,
|
|
14
15
|
...(options.props ?? {})
|
|
15
16
|
}));
|
|
16
17
|
let setting = false;
|
|
@@ -75,3 +75,73 @@
|
|
|
75
75
|
border-end-end-radius: var(--zx-control-radius);
|
|
76
76
|
border-inline-start: 1px solid var(--zx-color-border-control);
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
/* In a Field the control fills its column, as a text input does, and gives way in a narrow one. */
|
|
80
|
+
.zx-field__control > .zx-number-field {
|
|
81
|
+
display: flex;
|
|
82
|
+
inline-size: 100%;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.zx-field__control > .zx-number-field .zx-number-field__input {
|
|
86
|
+
min-inline-size: 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* Quiet fields. Inside `[data-zx-fields="quiet"]` the number reads as text: no border, no fill,
|
|
91
|
+
* and the value at the start, where the text of the quiet fields around it starts. The steppers
|
|
92
|
+
* move after it, both at the trailing end, and stay hidden until hover or focus — kept in the
|
|
93
|
+
* layout, so nothing moves when they return. Focus brings back the border, the control fill and
|
|
94
|
+
* the focus ring. Disabled and read-only fields never light up.
|
|
95
|
+
*/
|
|
96
|
+
[data-zx-fields="quiet"] .zx-number-field {
|
|
97
|
+
border-color: transparent;
|
|
98
|
+
background: transparent;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[data-zx-fields="quiet"] .zx-number-field__input {
|
|
102
|
+
padding-inline-start: var(--zx-control-pad-x);
|
|
103
|
+
text-align: start;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
[data-zx-fields="quiet"] .zx-number-field__step {
|
|
107
|
+
border-color: transparent;
|
|
108
|
+
opacity: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
[data-zx-fields="quiet"] .zx-number-field__step[data-step="down"] {
|
|
112
|
+
order: 1;
|
|
113
|
+
border-inline-end: 0;
|
|
114
|
+
border-inline-start: 1px solid transparent;
|
|
115
|
+
border-radius: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
[data-zx-fields="quiet"] .zx-number-field__step[data-step="up"] {
|
|
119
|
+
order: 2;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
[data-zx-fields="quiet"] .zx-number-field:not([data-disabled], [data-readonly]):hover {
|
|
123
|
+
background: var(--zx-color-bg-hover);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
[data-zx-fields="quiet"] .zx-number-field:not([data-disabled], [data-readonly]):is(:hover, :focus-within) .zx-number-field__step {
|
|
127
|
+
opacity: 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
[data-zx-fields="quiet"] .zx-number-field:not([data-disabled], [data-readonly]):focus-within {
|
|
131
|
+
border-color: var(--zx-color-border-control);
|
|
132
|
+
background: var(--zx-color-bg-control);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
[data-zx-fields="quiet"] .zx-number-field:not([data-disabled], [data-readonly]):focus-within .zx-number-field__step {
|
|
136
|
+
border-color: var(--zx-color-border-control);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
140
|
+
[data-zx-fields="quiet"] .zx-number-field {
|
|
141
|
+
transition: background-color var(--zx-dur-fast) var(--zx-ease), border-color var(--zx-dur-fast) var(--zx-ease);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
[data-zx-fields="quiet"] .zx-number-field__step {
|
|
145
|
+
transition: opacity var(--zx-dur-fast) var(--zx-ease), border-color var(--zx-dur-fast) var(--zx-ease);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -336,6 +336,8 @@ export class NumberField extends Component {
|
|
|
336
336
|
/** @param {KeyboardEvent} event @returns {void} */
|
|
337
337
|
_onKeydown(event) {
|
|
338
338
|
if (this._disabled || this._readonly) return;
|
|
339
|
+
// Chords belong to the page — Mod+Enter submits the form or dialog the field is in.
|
|
340
|
+
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
|
339
341
|
const large = Math.max(1, Math.trunc(Number(this.options.largeStep) || 1));
|
|
340
342
|
const actions = {
|
|
341
343
|
ArrowUp: () => this._stepBy(1),
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* One row that wraps: the summary is pushed to the start, the page-size select and the pager sit
|
|
5
5
|
* at the end. Page buttons are ordinary `.zx-btn` controls so they inherit the library's control
|
|
6
|
-
* height, radius, and focus ring;
|
|
6
|
+
* height, radius, and focus ring; the current page takes the neutral current fill — it is where
|
|
7
|
+
* you are, not a call to action.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
.zx-pagination {
|
|
@@ -70,15 +71,16 @@
|
|
|
70
71
|
font-variant-numeric: tabular-nums;
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
/* The page you are on: the neutral current fill and a heavier figure, not a solid accent. */
|
|
73
75
|
.zx-pagination__page[aria-current="page"] {
|
|
74
|
-
border-color:
|
|
75
|
-
background: var(--zx-color-
|
|
76
|
-
color: var(--zx-color-
|
|
76
|
+
border-color: transparent;
|
|
77
|
+
background: var(--zx-color-bg-current);
|
|
78
|
+
color: var(--zx-color-text);
|
|
79
|
+
font-weight: var(--zx-weight-semibold);
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
.zx-pagination__page[aria-current="page"]:hover:not(:disabled) {
|
|
80
|
-
|
|
81
|
-
background: var(--zx-color-accent-hover);
|
|
83
|
+
background: var(--zx-color-bg-current);
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
/* Decorative gap marker: never focusable, never announced. */
|
|
@@ -103,7 +103,7 @@ export class Panel extends Component {
|
|
|
103
103
|
class: 'zx-panel__buttons',
|
|
104
104
|
ref: 'buttons',
|
|
105
105
|
role: 'group',
|
|
106
|
-
ariaLabel: 'Panel actions'
|
|
106
|
+
ariaLabel: this._message('panel.actions', 'Panel actions')
|
|
107
107
|
})
|
|
108
108
|
);
|
|
109
109
|
const content = h('div', {
|
|
@@ -125,7 +125,7 @@ export class Panel extends Component {
|
|
|
125
125
|
class: 'zx-panel__footer-buttons',
|
|
126
126
|
ref: 'footerButtons',
|
|
127
127
|
role: 'group',
|
|
128
|
-
ariaLabel: 'Panel footer actions'
|
|
128
|
+
ariaLabel: this._message('panel.footerActions', 'Panel footer actions')
|
|
129
129
|
}));
|
|
130
130
|
root.replaceChildren(header, body, footer);
|
|
131
131
|
|
|
@@ -286,6 +286,17 @@ export class Panel extends Component {
|
|
|
286
286
|
return element;
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Resolves a message through the host translator, falling back to the built-in English text.
|
|
292
|
+
* @param {string} key Message key.
|
|
293
|
+
* @param {string} fallback English default.
|
|
294
|
+
* @returns {string}
|
|
295
|
+
*/
|
|
296
|
+
_message(key, fallback) {
|
|
297
|
+
const message = this.msg(key);
|
|
298
|
+
return message === key ? fallback : message;
|
|
299
|
+
}
|
|
289
300
|
}
|
|
290
301
|
|
|
291
302
|
/**
|
|
@@ -30,6 +30,13 @@
|
|
|
30
30
|
clip-path: inset(0 calc(100% - var(--zx-rating-fill)) 0 0);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/* The icons are outlines; a chosen star is a solid one, so the two states differ in shape as well
|
|
34
|
+
as in colour. */
|
|
35
|
+
.zx-rating__glyph[data-part="on"],
|
|
36
|
+
.zx-rating__glyph[data-part="on"] .zx-icon {
|
|
37
|
+
fill: currentColor;
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
.zx-rating__symbol[data-preview] .zx-rating__glyph[data-part="on"] {
|
|
34
41
|
color: var(--zx-color-warning);
|
|
35
42
|
opacity: 0.75;
|
|
@@ -39,27 +39,28 @@ export class Search extends Component {
|
|
|
39
39
|
this._original = created ? null : snapshot(root);
|
|
40
40
|
this._inputTimer = null;
|
|
41
41
|
root.setAttribute('role', 'search');
|
|
42
|
+
const clearLabel = this._message('search.clear', 'Clear search');
|
|
42
43
|
const input = h('input', {
|
|
43
44
|
class: 'zx-search__input',
|
|
44
45
|
ref: 'input',
|
|
45
46
|
type: 'search',
|
|
46
47
|
placeholder: String(this.options.placeholder),
|
|
47
48
|
value: String(this.options.value),
|
|
48
|
-
ariaLabel: String(this.options.placeholder || 'Search')
|
|
49
|
+
ariaLabel: String(this.options.placeholder || this._message('search.label', 'Search'))
|
|
49
50
|
});
|
|
50
51
|
const clear = this.options.clearable ? h('button', {
|
|
51
52
|
class: 'zx-search__clear',
|
|
52
53
|
ref: 'clear',
|
|
53
54
|
type: 'button',
|
|
54
|
-
ariaLabel:
|
|
55
|
-
title:
|
|
55
|
+
ariaLabel: clearLabel,
|
|
56
|
+
title: clearLabel
|
|
56
57
|
}, icon('x', { size: 15 })) : null;
|
|
57
58
|
const submit = h('button', {
|
|
58
59
|
class: 'zx-search__submit',
|
|
59
60
|
ref: 'submit',
|
|
60
61
|
type: root.localName === 'form' ? 'submit' : 'button',
|
|
61
|
-
ariaLabel: 'Submit search',
|
|
62
|
-
title: 'Search'
|
|
62
|
+
ariaLabel: this._message('search.submit', 'Submit search'),
|
|
63
|
+
title: this._message('search.submitTitle', 'Search')
|
|
63
64
|
}, icon('search', { size: 16 }));
|
|
64
65
|
root.replaceChildren(...[input, clear, submit].filter(Boolean));
|
|
65
66
|
this._syncClear();
|
|
@@ -157,6 +158,17 @@ export class Search extends Component {
|
|
|
157
158
|
/** @type {HTMLButtonElement} */ (this.refs.clear).hidden = this.get() === '';
|
|
158
159
|
}
|
|
159
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolves a message through the host translator, falling back to the built-in English text.
|
|
164
|
+
* @param {string} key Message key.
|
|
165
|
+
* @param {string} fallback English default.
|
|
166
|
+
* @returns {string}
|
|
167
|
+
*/
|
|
168
|
+
_message(key, fallback) {
|
|
169
|
+
const message = this.msg(key);
|
|
170
|
+
return message === key ? fallback : message;
|
|
171
|
+
}
|
|
160
172
|
}
|
|
161
173
|
|
|
162
174
|
/**
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isCssColor } from '../../core/util.js';
|
|
2
|
+
|
|
3
|
+
/** A reference to one custom property, such as `var(--zx-color-tone-blue-fg)`. */
|
|
4
|
+
const TOKEN_REFERENCE = /^var\(\s*--[\w-]+\s*\)$/;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Accepts an item colour for an inline custom property: a CSS colour literal (`#0b7285`, `rgb(…)`,
|
|
8
|
+
* `oklch(…)`, a named colour) or a reference to a design token (`var(--zx-color-tone-blue-fg)`),
|
|
9
|
+
* so an item can follow the theme rather than fix one colour for light and dark alike. Anything
|
|
10
|
+
* else — other functions, declarations, a `var()` with a fallback — is refused.
|
|
11
|
+
*
|
|
12
|
+
* Shared by `Select.entity()` and `TagPicker`, the two components that take a colour per item.
|
|
13
|
+
*
|
|
14
|
+
* @param {unknown} value Candidate colour.
|
|
15
|
+
* @returns {string|null} The trimmed colour, or null when the value is not one.
|
|
16
|
+
*/
|
|
17
|
+
export function itemColor(value) {
|
|
18
|
+
if (typeof value !== 'string') return null;
|
|
19
|
+
const text = value.trim();
|
|
20
|
+
if (TOKEN_REFERENCE.test(text)) return text;
|
|
21
|
+
return isCssColor(text) ? text : null;
|
|
22
|
+
}
|
|
@@ -9,8 +9,13 @@ export function registerSelectFieldAdapter() {
|
|
|
9
9
|
Field.register('zxselect', (field, options) => {
|
|
10
10
|
const props = options.props ?? {};
|
|
11
11
|
const preset = props.preset;
|
|
12
|
-
const selectOptions = { ...props, value: options.value };
|
|
12
|
+
const selectOptions = { ...props, value: options.value, label: options.label || props.label, placeholder: props.placeholder ?? options.placeholder };
|
|
13
|
+
field.listen(field.el, 'click', (event) => {
|
|
14
|
+
if (event.target.closest?.('.zx-field__label')) field.adapter.focus();
|
|
15
|
+
});
|
|
13
16
|
delete selectOptions.preset;
|
|
17
|
+
// The priority preset offers "No priority" (null) by default; a required field cannot hold it.
|
|
18
|
+
if (preset === 'priority' && props.clearable === undefined) selectOptions.clearable = !options.required;
|
|
14
19
|
const select = field.own(preset === 'priority'
|
|
15
20
|
? Select.priority(null, selectOptions)
|
|
16
21
|
: preset === 'status'
|