@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.
Files changed (231) hide show
  1. package/CHANGELOG.md +438 -0
  2. package/README.md +49 -10
  3. package/dist/types/components/activity-item/activity-item.d.ts +351 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +217 -0
  5. package/dist/types/components/app-sidebar/app-sidebar.d.ts +33 -3
  6. package/dist/types/components/avatar/avatar.d.ts +46 -7
  7. package/dist/types/components/badge/badge.d.ts +21 -1
  8. package/dist/types/components/button/button.d.ts +23 -0
  9. package/dist/types/components/calendar/calendar.d.ts +19 -0
  10. package/dist/types/components/card/card.d.ts +7 -0
  11. package/dist/types/components/card-view/card-view.d.ts +11 -3
  12. package/dist/types/components/card-view/record-card.d.ts +7 -1
  13. package/dist/types/components/chart/chart.d.ts +10 -0
  14. package/dist/types/components/checklist/checklist.d.ts +9 -0
  15. package/dist/types/components/code-editor/code-editor.d.ts +149 -0
  16. package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
  17. package/dist/types/components/context-menu/context-menu.d.ts +18 -2
  18. package/dist/types/components/date-picker/date-picker.d.ts +8 -0
  19. package/dist/types/components/date-picker/time-picker.d.ts +7 -0
  20. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  21. package/dist/types/components/datebox/datebox.d.ts +7 -2
  22. package/dist/types/components/dialog/dialog.d.ts +75 -6
  23. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  24. package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
  25. package/dist/types/components/field/field.d.ts +24 -2
  26. package/dist/types/components/fieldset/fieldset.d.ts +19 -1
  27. package/dist/types/components/file-item/file-item.d.ts +262 -0
  28. package/dist/types/components/file-list/file-list.d.ts +171 -0
  29. package/dist/types/components/filter/filter.d.ts +91 -36
  30. package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
  31. package/dist/types/components/form/form.d.ts +12 -1
  32. package/dist/types/components/kanban-view/kanban-card.d.ts +146 -0
  33. package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
  34. package/dist/types/components/launcher/launcher.d.ts +278 -26
  35. package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
  36. package/dist/types/components/menu-button/menu-button.d.ts +37 -42
  37. package/dist/types/components/message/message.d.ts +126 -17
  38. package/dist/types/components/modal/modal.d.ts +61 -2
  39. package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
  40. package/dist/types/components/panel/panel.d.ts +7 -0
  41. package/dist/types/components/search/search.d.ts +7 -0
  42. package/dist/types/components/select/color.d.ts +12 -0
  43. package/dist/types/components/select/glyphs.d.ts +124 -0
  44. package/dist/types/components/select/select.d.ts +15 -3
  45. package/dist/types/components/sort-control/sort-control.d.ts +105 -0
  46. package/dist/types/components/stat-tile/stat-tile.d.ts +288 -0
  47. package/dist/types/components/tabbox/tabbox.d.ts +16 -0
  48. package/dist/types/components/table/table.d.ts +190 -1
  49. package/dist/types/components/table-view/table-view.d.ts +11 -5
  50. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  51. package/dist/types/components/timebox/timebox.d.ts +7 -0
  52. package/dist/types/components/tree/tree.d.ts +13 -0
  53. package/dist/types/components/value-list/value-list.d.ts +8 -0
  54. package/dist/types/components/view/record-view.d.ts +96 -5
  55. package/dist/types/core/breakpoint.d.ts +1 -1
  56. package/dist/types/core/icons.d.ts +37 -22
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +15 -1
  59. package/dist/types/index.d.ts +16 -3
  60. package/dist/types/internal/app-rail.d.ts +10 -0
  61. package/dist/types/internal/menu-items.d.ts +134 -0
  62. package/dist/types/zeyos/connect.d.ts +6 -0
  63. package/dist/types/zeyos/form.d.ts +50 -9
  64. package/dist/types/zeyos/index.d.ts +1 -1
  65. package/dist/types/zeyos/query.d.ts +79 -0
  66. package/dist/types/zeyos/schema.d.ts +24 -1
  67. package/dist/types/zeyos/table.d.ts +100 -14
  68. package/dist/types/zeyos/view.d.ts +10 -0
  69. package/dist/zx-zeyos.esm.js +2206 -489
  70. package/dist/zx-zeyos.esm.min.js +56 -1
  71. package/dist/zx.css +3322 -724
  72. package/dist/zx.esm.js +12244 -5809
  73. package/dist/zx.esm.min.js +58 -3
  74. package/dist/zx.global.js +12246 -5811
  75. package/dist/zx.global.min.js +58 -3
  76. package/dist/zx.min.css +1 -1
  77. package/docs/api.json +1683 -146
  78. package/docs/llms.md +777 -110
  79. package/docs/llms.txt +14 -8
  80. package/package.json +1 -1
  81. package/src/components/account-menu/account-menu.css +35 -10
  82. package/src/components/activity-item/activity-item.css +137 -0
  83. package/src/components/activity-item/activity-item.js +602 -0
  84. package/src/components/activity-list/activity-list.css +99 -0
  85. package/src/components/activity-list/activity-list.js +409 -0
  86. package/src/components/app-icon/app-icon.css +1 -1
  87. package/src/components/app-rail/app-rail.css +191 -22
  88. package/src/components/app-sidebar/app-sidebar.css +349 -24
  89. package/src/components/app-sidebar/app-sidebar.js +86 -11
  90. package/src/components/aurora/aurora.css +5 -3
  91. package/src/components/avatar/avatar.css +32 -7
  92. package/src/components/avatar/avatar.js +72 -8
  93. package/src/components/badge/badge.css +54 -2
  94. package/src/components/badge/badge.js +29 -2
  95. package/src/components/button/button.css +74 -8
  96. package/src/components/button/button.js +44 -1
  97. package/src/components/calendar/calendar.css +46 -46
  98. package/src/components/calendar/calendar.js +33 -3
  99. package/src/components/card/card.css +2 -2
  100. package/src/components/card/card.js +12 -1
  101. package/src/components/card-view/card-view.css +10 -7
  102. package/src/components/card-view/card-view.js +25 -9
  103. package/src/components/card-view/record-card.js +7 -3
  104. package/src/components/chart/chart.css +4 -1
  105. package/src/components/chart/chart.js +12 -0
  106. package/src/components/check-button/check-button.css +10 -2
  107. package/src/components/checklist/checklist.css +9 -2
  108. package/src/components/checklist/checklist.js +11 -5
  109. package/src/components/checklist/field-adapter.js +3 -1
  110. package/src/components/code-editor/code-editor.css +68 -0
  111. package/src/components/code-editor/code-editor.js +306 -0
  112. package/src/components/code-editor/field-adapter.js +34 -0
  113. package/src/components/context-menu/context-menu.css +87 -5
  114. package/src/components/context-menu/context-menu.js +22 -30
  115. package/src/components/copy/copy-button.css +6 -0
  116. package/src/components/copy/copy-input.css +8 -3
  117. package/src/components/data-filter/data-filter.css +5 -4
  118. package/src/components/date-picker/date-picker.css +6 -6
  119. package/src/components/date-picker/date-picker.js +22 -8
  120. package/src/components/date-picker/date-range-picker.css +3 -3
  121. package/src/components/date-picker/month-picker.js +3 -3
  122. package/src/components/date-picker/time-picker.css +1 -1
  123. package/src/components/date-picker/time-picker.js +20 -8
  124. package/src/components/datebox/date-range-box.css +79 -2
  125. package/src/components/datebox/date-range-box.js +13 -2
  126. package/src/components/datebox/datebox.css +79 -2
  127. package/src/components/datebox/datebox.js +13 -2
  128. package/src/components/dialog/dialog.css +26 -0
  129. package/src/components/dialog/dialog.js +174 -70
  130. package/src/components/dropdown/dropdown.css +1 -1
  131. package/src/components/dropdown/dropdown.js +7 -5
  132. package/src/components/empty-state/empty-state.css +2 -2
  133. package/src/components/entity-ref/entity-ref.css +182 -0
  134. package/src/components/entity-ref/entity-ref.js +496 -0
  135. package/src/components/field/field.css +28 -9
  136. package/src/components/field/field.js +68 -26
  137. package/src/components/field-upload/field-upload.css +1 -1
  138. package/src/components/fieldset/fieldset.css +5 -3
  139. package/src/components/fieldset/fieldset.js +28 -5
  140. package/src/components/file-item/file-item.css +88 -0
  141. package/src/components/file-item/file-item.js +505 -0
  142. package/src/components/file-list/file-list.css +51 -0
  143. package/src/components/file-list/file-list.js +320 -0
  144. package/src/components/filter/filter.css +27 -1
  145. package/src/components/filter/filter.js +205 -47
  146. package/src/components/filter-panel/filter-panel.css +104 -0
  147. package/src/components/filter-panel/filter-panel.js +964 -0
  148. package/src/components/finder/finder.css +15 -13
  149. package/src/components/form/form.css +5 -4
  150. package/src/components/form/form.js +11 -2
  151. package/src/components/grid/grid.css +1 -1
  152. package/src/components/groupbox/groupbox.css +1 -1
  153. package/src/components/kanban-view/kanban-card.js +247 -0
  154. package/src/components/kanban-view/kanban-view.css +226 -8
  155. package/src/components/kanban-view/kanban-view.js +255 -12
  156. package/src/components/launcher/launcher.css +107 -25
  157. package/src/components/launcher/launcher.js +557 -63
  158. package/src/components/list-toolbar/list-toolbar.css +194 -0
  159. package/src/components/list-toolbar/list-toolbar.js +851 -0
  160. package/src/components/master-panel/master-panel.css +2 -0
  161. package/src/components/menu-button/menu-button.css +87 -5
  162. package/src/components/menu-button/menu-button.js +86 -46
  163. package/src/components/message/message.css +38 -5
  164. package/src/components/message/message.js +230 -17
  165. package/src/components/modal/modal.css +15 -0
  166. package/src/components/modal/modal.js +159 -3
  167. package/src/components/navigation-bar/navigation-bar.css +95 -11
  168. package/src/components/navigation-bar/navigation-bar.js +171 -4
  169. package/src/components/number-field/field-adapter.js +1 -0
  170. package/src/components/number-field/number-field.css +70 -0
  171. package/src/components/number-field/number-field.js +2 -0
  172. package/src/components/pagination/pagination.css +8 -6
  173. package/src/components/panel/panel.css +1 -1
  174. package/src/components/panel/panel.js +13 -2
  175. package/src/components/rating/rating.css +7 -0
  176. package/src/components/search/search.js +17 -5
  177. package/src/components/select/color.js +22 -0
  178. package/src/components/select/field-adapter.js +6 -1
  179. package/src/components/select/glyphs.js +165 -0
  180. package/src/components/select/select.css +138 -45
  181. package/src/components/select/select.js +86 -47
  182. package/src/components/sheet/sheet.css +11 -8
  183. package/src/components/slider/slider.css +16 -2
  184. package/src/components/slider/slider.js +1 -1
  185. package/src/components/sort-control/sort-control.css +20 -0
  186. package/src/components/sort-control/sort-control.js +140 -0
  187. package/src/components/stat-tile/stat-tile.css +208 -0
  188. package/src/components/stat-tile/stat-tile.js +601 -0
  189. package/src/components/tabbox/tabbox.css +32 -27
  190. package/src/components/tabbox/tabbox.js +17 -2
  191. package/src/components/table/table.css +155 -31
  192. package/src/components/table/table.js +386 -33
  193. package/src/components/table-view/table-view.css +8 -2
  194. package/src/components/table-view/table-view.js +21 -6
  195. package/src/components/tag-picker/tag-picker.css +86 -6
  196. package/src/components/tag-picker/tag-picker.js +25 -8
  197. package/src/components/timebox/timebox.css +22 -2
  198. package/src/components/timebox/timebox.js +18 -5
  199. package/src/components/toggle/toggle.css +12 -1
  200. package/src/components/toolbar/toolbar.js +2 -2
  201. package/src/components/tooltip/tooltip.css +4 -0
  202. package/src/components/tooltip/tooltip.js +10 -3
  203. package/src/components/tree/tree.css +25 -16
  204. package/src/components/tree/tree.js +31 -0
  205. package/src/components/value-list/value-list.css +3 -3
  206. package/src/components/value-list/value-list.js +22 -6
  207. package/src/components/view/record-view.css +5 -0
  208. package/src/components/view/record-view.js +100 -7
  209. package/src/core/breakpoint.js +1 -1
  210. package/src/core/icons.js +259 -57
  211. package/src/core/motion.js +147 -0
  212. package/src/core/overlay-host.js +21 -2
  213. package/src/index.js +30 -3
  214. package/src/internal/app-rail.js +18 -1
  215. package/src/internal/menu-items.js +142 -0
  216. package/src/zeyos/connect.js +6 -2
  217. package/src/zeyos/form.js +67 -27
  218. package/src/zeyos/index.js +1 -1
  219. package/src/zeyos/query.js +153 -0
  220. package/src/zeyos/schema.js +144 -27
  221. package/src/zeyos/table.js +68 -5
  222. package/src/zeyos/view.js +6 -0
  223. package/styles/base.css +56 -0
  224. package/styles/icons.css +11 -1
  225. package/styles/print.css +192 -0
  226. package/styles/tokens/dark.css +113 -37
  227. package/styles/tokens/global.css +121 -27
  228. package/styles/tokens/semantic.css +100 -31
  229. package/styles/tokens/themes.css +7 -6
  230. package/styles/zx.css +11 -1
  231. 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: 750;
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: var(--zx-color-bg-raised);
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-selected);
70
- color: var(--zx-color-accent);
71
- font-weight: 650;
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-accent);
91
+ background: var(--zx-color-bg-current);
88
92
  padding-inline: var(--zx-space-1);
89
- color: var(--zx-color-on-accent);
90
- font-size: var(--zx-text-sm);
91
- font-weight: 700;
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-bar__more:not([data-empty="true"]) {
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
- /** @returns {void} */
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 = this._items.length === 0 ? 'true' : 'false';
389
- this._overflow.setItems(this._items.map((record) => ({
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; only the current page repaints in the accent colour.
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: var(--zx-color-accent);
75
- background: var(--zx-color-accent);
76
- color: var(--zx-color-on-accent);
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
- border-color: var(--zx-color-accent-hover);
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. */
@@ -35,7 +35,7 @@
35
35
  padding: var(--zx-space-3) var(--zx-space-4);
36
36
  color: var(--zx-color-text);
37
37
  font: inherit;
38
- font-weight: 650;
38
+ font-weight: var(--zx-weight-semibold);
39
39
  text-align: start;
40
40
  }
41
41
 
@@ -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: 'Clear search',
55
- title: 'Clear search'
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'