@zeyos/zx 4.4.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/README.md +44 -11
  3. package/dist/types/components/account-menu/account-menu.d.ts +8 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +31 -1
  5. package/dist/types/components/app-icon/app-icon.d.ts +14 -2
  6. package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
  7. package/dist/types/components/avatar/avatar.d.ts +46 -7
  8. package/dist/types/components/badge/badge.d.ts +21 -1
  9. package/dist/types/components/button/button.d.ts +15 -0
  10. package/dist/types/components/calendar/calendar.d.ts +67 -5
  11. package/dist/types/components/card-view/card-view.d.ts +7 -4
  12. package/dist/types/components/card-view/record-card.d.ts +11 -0
  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 +6 -17
  16. package/dist/types/components/context-menu/context-menu.d.ts +6 -2
  17. package/dist/types/components/data-filter/data-filter.d.ts +4 -4
  18. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  19. package/dist/types/components/datebox/datebox.d.ts +7 -2
  20. package/dist/types/components/dialog/dialog.d.ts +46 -6
  21. package/dist/types/components/dropdown/dropdown.d.ts +24 -9
  22. package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
  23. package/dist/types/components/field/field.d.ts +54 -2
  24. package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
  25. package/dist/types/components/fieldset/fieldset.d.ts +26 -1
  26. package/dist/types/components/file-item/file-item.d.ts +34 -6
  27. package/dist/types/components/file-list/file-list.d.ts +35 -3
  28. package/dist/types/components/filter/filter.d.ts +127 -3
  29. package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
  30. package/dist/types/components/form/form.d.ts +12 -1
  31. package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
  32. package/dist/types/components/launcher/launcher.d.ts +268 -15
  33. package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
  34. package/dist/types/components/menu-button/menu-button.d.ts +39 -5
  35. package/dist/types/components/message/message.d.ts +132 -17
  36. package/dist/types/components/modal/modal.d.ts +43 -2
  37. package/dist/types/components/module-bar/module-bar.d.ts +395 -0
  38. package/dist/types/components/pagination/pagination.d.ts +17 -3
  39. package/dist/types/components/select/color.d.ts +12 -0
  40. package/dist/types/components/select/glyphs.d.ts +124 -0
  41. package/dist/types/components/select/select.d.ts +17 -3
  42. package/dist/types/components/sheet/sheet.d.ts +15 -0
  43. package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
  44. package/dist/types/components/tabbox/tabbox.d.ts +9 -0
  45. package/dist/types/components/table/table.d.ts +311 -18
  46. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  47. package/dist/types/components/tree/tree.d.ts +13 -0
  48. package/dist/types/components/view/record-view.d.ts +59 -2
  49. package/dist/types/components/view-settings/view-settings.d.ts +879 -0
  50. package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
  51. package/dist/types/core/breakpoint.d.ts +1 -1
  52. package/dist/types/core/component.d.ts +12 -2
  53. package/dist/types/core/i18n.d.ts +27 -3
  54. package/dist/types/core/icons.d.ts +23 -25
  55. package/dist/types/core/keyboard.d.ts +98 -1
  56. package/dist/types/core/messages.d.ts +382 -0
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +42 -1
  59. package/dist/types/core/overlay-stack.d.ts +260 -0
  60. package/dist/types/index.d.ts +10 -5
  61. package/dist/types/internal/app-rail.d.ts +38 -0
  62. package/dist/types/internal/menu-items.d.ts +71 -3
  63. package/dist/types/zeyos/connect.d.ts +6 -0
  64. package/dist/types/zeyos/form.d.ts +50 -9
  65. package/dist/types/zeyos/index.d.ts +2 -1
  66. package/dist/types/zeyos/launcher.d.ts +9 -0
  67. package/dist/types/zeyos/module-bar.d.ts +176 -0
  68. package/dist/types/zeyos/query.d.ts +110 -5
  69. package/dist/types/zeyos/schema.d.ts +24 -1
  70. package/dist/types/zeyos/table.d.ts +345 -22
  71. package/dist/types/zeyos/view.d.ts +26 -2
  72. package/dist/zx-zeyos.esm.js +6178 -1008
  73. package/dist/zx-zeyos.esm.min.js +53 -5
  74. package/dist/zx.css +2861 -827
  75. package/dist/zx.esm.js +8909 -1480
  76. package/dist/zx.esm.min.js +55 -7
  77. package/dist/zx.global.js +8911 -1482
  78. package/dist/zx.global.min.js +55 -7
  79. package/dist/zx.min.css +1 -1
  80. package/docs/api.json +1079 -117
  81. package/docs/llms.md +956 -154
  82. package/docs/llms.txt +9 -5
  83. package/package.json +1 -1
  84. package/src/components/account-menu/account-menu.css +35 -10
  85. package/src/components/account-menu/account-menu.js +12 -0
  86. package/src/components/activity-item/activity-item.css +1 -1
  87. package/src/components/activity-item/activity-item.js +3 -0
  88. package/src/components/activity-list/activity-list.css +38 -3
  89. package/src/components/activity-list/activity-list.js +61 -1
  90. package/src/components/app-icon/app-icon.css +14 -3
  91. package/src/components/app-icon/app-icon.js +11 -1
  92. package/src/components/app-rail/app-rail.css +59 -79
  93. package/src/components/app-sidebar/app-sidebar.css +304 -168
  94. package/src/components/app-sidebar/app-sidebar.js +667 -46
  95. package/src/components/aurora/aurora.css +5 -3
  96. package/src/components/avatar/avatar.css +32 -7
  97. package/src/components/avatar/avatar.js +72 -8
  98. package/src/components/badge/badge.css +54 -2
  99. package/src/components/badge/badge.js +29 -2
  100. package/src/components/breadcrumb/breadcrumb.css +1 -1
  101. package/src/components/button/button.css +67 -9
  102. package/src/components/button/button.js +38 -0
  103. package/src/components/calendar/calendar.css +47 -47
  104. package/src/components/calendar/calendar.js +26 -52
  105. package/src/components/card/card.css +4 -4
  106. package/src/components/card-view/card-view.css +14 -12
  107. package/src/components/card-view/card-view.js +13 -9
  108. package/src/components/card-view/record-card.js +14 -3
  109. package/src/components/chart/chart.css +4 -1
  110. package/src/components/chart/chart.js +12 -0
  111. package/src/components/check-button/check-button.css +10 -2
  112. package/src/components/checklist/checklist.css +9 -2
  113. package/src/components/checklist/checklist.js +11 -5
  114. package/src/components/checklist/field-adapter.js +3 -1
  115. package/src/components/code-editor/code-editor.css +7 -6
  116. package/src/components/code-editor/code-editor.js +19 -1
  117. package/src/components/code-editor/field-adapter.js +9 -2
  118. package/src/components/context-menu/context-menu.css +9 -4
  119. package/src/components/context-menu/context-menu.js +8 -2
  120. package/src/components/copy/copy-button.css +6 -0
  121. package/src/components/copy/copy-input.css +9 -4
  122. package/src/components/data-filter/data-filter.css +5 -4
  123. package/src/components/data-filter/data-filter.js +2 -2
  124. package/src/components/date-picker/date-picker.css +6 -6
  125. package/src/components/date-picker/date-range-picker.css +3 -3
  126. package/src/components/date-picker/time-picker.css +3 -3
  127. package/src/components/datebox/date-range-box.css +79 -2
  128. package/src/components/datebox/date-range-box.js +13 -2
  129. package/src/components/datebox/datebox.css +79 -2
  130. package/src/components/datebox/datebox.js +13 -2
  131. package/src/components/dialog/dialog.css +26 -0
  132. package/src/components/dialog/dialog.js +99 -17
  133. package/src/components/dock/dock.css +5 -4
  134. package/src/components/dropdown/dropdown.css +1 -1
  135. package/src/components/dropdown/dropdown.js +159 -72
  136. package/src/components/empty-state/empty-state.css +2 -2
  137. package/src/components/entity-ref/entity-ref.css +1 -1
  138. package/src/components/entity-ref/entity-ref.js +1 -1
  139. package/src/components/field/field.css +28 -9
  140. package/src/components/field/field.js +101 -29
  141. package/src/components/field-upload/field-upload.css +1 -1
  142. package/src/components/fieldset/external-adapter.js +363 -0
  143. package/src/components/fieldset/fieldset.css +5 -3
  144. package/src/components/fieldset/fieldset.js +38 -64
  145. package/src/components/file-item/file-item.js +144 -10
  146. package/src/components/file-list/file-list.js +144 -21
  147. package/src/components/filter/filter.css +86 -2
  148. package/src/components/filter/filter.js +307 -11
  149. package/src/components/filter-panel/filter-panel.css +1 -1
  150. package/src/components/filter-panel/filter-panel.js +149 -32
  151. package/src/components/finder/finder.css +16 -14
  152. package/src/components/form/form.css +5 -4
  153. package/src/components/form/form.js +11 -2
  154. package/src/components/grid/grid.css +1 -1
  155. package/src/components/groupbox/groupbox.css +1 -1
  156. package/src/components/kanban-view/kanban-card.js +11 -3
  157. package/src/components/kanban-view/kanban-view.css +42 -18
  158. package/src/components/kanban-view/kanban-view.js +9 -6
  159. package/src/components/launcher/launcher.css +108 -26
  160. package/src/components/launcher/launcher.js +530 -42
  161. package/src/components/list-toolbar/list-toolbar.css +13 -8
  162. package/src/components/list-toolbar/list-toolbar.js +40 -14
  163. package/src/components/master-panel/master-panel.css +2 -0
  164. package/src/components/master-panel/master-panel.js +0 -1
  165. package/src/components/menu-button/menu-button.css +72 -4
  166. package/src/components/menu-button/menu-button.js +160 -18
  167. package/src/components/message/message.css +33 -3
  168. package/src/components/message/message.js +264 -22
  169. package/src/components/modal/modal.css +1 -1
  170. package/src/components/modal/modal.js +83 -15
  171. package/src/components/module-bar/module-bar.css +486 -0
  172. package/src/components/module-bar/module-bar.js +1074 -0
  173. package/src/components/multi-value-editor/multi-value-editor.css +1 -1
  174. package/src/components/navigation-bar/navigation-bar.css +18 -9
  175. package/src/components/number-field/field-adapter.js +3 -1
  176. package/src/components/number-field/number-field.css +70 -0
  177. package/src/components/number-field/number-field.js +2 -0
  178. package/src/components/pagination/pagination.css +13 -11
  179. package/src/components/pagination/pagination.js +20 -4
  180. package/src/components/panel/panel.css +4 -3
  181. package/src/components/questionnaire/questionnaire.css +4 -4
  182. package/src/components/rating/field-adapter.js +2 -1
  183. package/src/components/rating/rating.css +8 -1
  184. package/src/components/search/search.css +4 -1
  185. package/src/components/select/color.js +22 -0
  186. package/src/components/select/field-adapter.js +2 -0
  187. package/src/components/select/glyphs.js +165 -0
  188. package/src/components/select/select.css +138 -45
  189. package/src/components/select/select.js +92 -47
  190. package/src/components/sheet/sheet.css +12 -9
  191. package/src/components/sheet/sheet.js +74 -32
  192. package/src/components/slider/field-adapter.js +6 -1
  193. package/src/components/slider/slider.css +18 -4
  194. package/src/components/slider/slider.js +1 -1
  195. package/src/components/sort-control/sort-control.css +4 -1
  196. package/src/components/split-view/split-view.css +1 -1
  197. package/src/components/stat-tile/stat-tile.css +42 -47
  198. package/src/components/stat-tile/stat-tile.js +17 -6
  199. package/src/components/stepper/stepper.css +1 -1
  200. package/src/components/tabbox/tabbox.css +41 -32
  201. package/src/components/tabbox/tabbox.js +5 -1
  202. package/src/components/table/table.css +187 -37
  203. package/src/components/table/table.js +718 -73
  204. package/src/components/tag-picker/field-adapter.js +2 -1
  205. package/src/components/tag-picker/tag-picker.css +83 -5
  206. package/src/components/tag-picker/tag-picker.js +25 -8
  207. package/src/components/timebox/timebox.css +22 -2
  208. package/src/components/toggle/toggle.css +12 -1
  209. package/src/components/toolbar/toolbar.js +2 -2
  210. package/src/components/tooltip/tooltip.css +4 -0
  211. package/src/components/tooltip/tooltip.js +44 -19
  212. package/src/components/tree/tree.css +26 -17
  213. package/src/components/tree/tree.js +31 -0
  214. package/src/components/value-list/value-list.css +3 -3
  215. package/src/components/view/record-view.css +1 -1
  216. package/src/components/view/record-view.js +178 -18
  217. package/src/components/view-settings/view-settings.css +285 -0
  218. package/src/components/view-settings/view-settings.js +1830 -0
  219. package/src/components/workspace-switcher/workspace-switcher.css +188 -0
  220. package/src/components/workspace-switcher/workspace-switcher.js +463 -0
  221. package/src/core/breakpoint.js +1 -1
  222. package/src/core/component.js +88 -17
  223. package/src/core/fontawesome.js +5 -1
  224. package/src/core/i18n.js +47 -4
  225. package/src/core/icons.js +204 -61
  226. package/src/core/keyboard.js +206 -11
  227. package/src/core/messages.js +427 -0
  228. package/src/core/motion.js +147 -0
  229. package/src/core/overlay-host.js +77 -3
  230. package/src/core/overlay-stack.js +487 -0
  231. package/src/core/position.js +22 -3
  232. package/src/index.js +10 -5
  233. package/src/internal/app-rail.js +135 -55
  234. package/src/internal/menu-items.js +61 -3
  235. package/src/zeyos/connect.js +6 -2
  236. package/src/zeyos/form.js +69 -27
  237. package/src/zeyos/index.js +4 -1
  238. package/src/zeyos/launcher.js +8 -3
  239. package/src/zeyos/module-bar.js +181 -0
  240. package/src/zeyos/query.js +197 -13
  241. package/src/zeyos/schema.js +144 -27
  242. package/src/zeyos/table.js +287 -84
  243. package/src/zeyos/view.js +49 -93
  244. package/styles/base.css +72 -1
  245. package/styles/icons.css +11 -1
  246. package/styles/print.css +1 -1
  247. package/styles/tokens/dark.css +117 -37
  248. package/styles/tokens/global.css +121 -27
  249. package/styles/tokens/semantic.css +140 -43
  250. package/styles/tokens/themes.css +7 -6
  251. package/styles/zx.css +3 -1
  252. package/styles/tokens/density.css +0 -9
@@ -97,7 +97,7 @@
97
97
  }
98
98
 
99
99
  .zx-multi-value-editor__handle:focus-visible {
100
- outline: none;
100
+ outline-color: transparent;
101
101
  box-shadow: var(--zx-focus-ring);
102
102
  }
103
103
 
@@ -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
  }
@@ -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;
@@ -34,7 +35,8 @@ export function registerNumberFieldAdapter() {
34
35
  }
35
36
  },
36
37
  focus: () => numberField.focus(),
37
- setDisabled: (value) => (value ? numberField.disable() : numberField.enable())
38
+ setDisabled: (value) => (value ? numberField.disable() : numberField.enable()),
39
+ setReadonly: (value) => { numberField.setReadonly(value); }
38
40
  };
39
41
  });
40
42
  }
@@ -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),
@@ -1,9 +1,10 @@
1
1
  /*
2
2
  * Pagination — the footer bar of a server-backed list.
3
3
  *
4
- * One row that wraps: the summary is pushed to the start, the page-size select and the pager sit
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.
4
+ * One row that wraps: the summary and the page-size select describe the slice and sit at the
5
+ * start, the pager sits alone at the end. Page buttons are ordinary `.zx-btn` controls so they inherit the library's control
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 {
@@ -21,8 +22,8 @@
21
22
  white-space: nowrap;
22
23
  }
23
24
 
24
- /* Absorbs the slack, so summary and controls end up at opposite ends of a wide bar and simply
25
- stack when there is no room. */
25
+ /* Absorbs the slack, so the summary and page size end up at the start and the pager at the end of
26
+ a wide bar, and they simply stack when there is no room. */
26
27
  .zx-pagination__spacer {
27
28
  flex: 1 1 var(--zx-space-4);
28
29
  }
@@ -42,7 +43,7 @@
42
43
  }
43
44
 
44
45
  .zx-pagination__select:focus-visible {
45
- outline: 0;
46
+ outline-color: transparent;
46
47
  box-shadow: var(--zx-focus-ring);
47
48
  }
48
49
 
@@ -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. */
@@ -95,9 +95,23 @@ export function paginationRange({ page = 1, pages = 1, siblings = 1, boundaries
95
95
 
96
96
  /**
97
97
  * The pager for a server-backed list. It owns no data: it reports which slice the user asked for
98
- * and lets the application fetch it, which is exactly the shape `zeyosTable()` expects —
99
- * `offset`/`pageSize` for `load()` in `pages` mode, or one `change` per `loadMore()` in
100
- * `loadmore` mode.
98
+ * — `page`, `pageSize`, and the zero-based row `offset = (page - 1) * pageSize` — and lets the
99
+ * application fetch it.
100
+ *
101
+ * The pager is one-based and defaults to 25 rows per page. The ZeyOS bindings are zero-based,
102
+ * default to 50 rows, and append pages above zero unless told to replace, so connecting the two
103
+ * takes a small mapping rather than a direct hand-off:
104
+ *
105
+ * ```js
106
+ * const binding = zeyosTable(client, 'contacts', { pageSize: 25 });
107
+ * const pager = new Pagination(host, { pageSize: binding.pageSize });
108
+ * pager.on('change', async ({ detail }) => {
109
+ * const result = await binding.load({ page: detail.page - 1, pageSize: detail.pageSize, replace: true });
110
+ * pager.setTotal(result.count ?? 0);
111
+ * });
112
+ * ```
113
+ *
114
+ * In `loadmore` mode each `change` stands for one `binding.loadMore()`, which appends.
101
115
  *
102
116
  * `page` is clamped into `[1, pages]` after every state change, so shrinking `total` under the
103
117
  * user's feet can never leave the pager pointing past the end of the data.
@@ -163,8 +177,10 @@ export class Pagination extends Component {
163
177
  if (this.options.showSummary) {
164
178
  children.push(h('span', { class: 'zx-pagination__summary', ref: 'summary', ariaLive: 'polite' }));
165
179
  }
166
- children.push(h('span', { class: 'zx-pagination__spacer' }));
180
+ // The summary and the page size describe the slice and sit together at the start; the pager
181
+ // walks the slice and sits alone at the end, where the next-page action is reached last.
167
182
  if (this.options.showPageSize) children.push(this._renderPageSize());
183
+ children.push(h('span', { class: 'zx-pagination__spacer' }));
168
184
  children.push(this.options.mode === 'pages' ? this._renderPages() : this._renderLoadMore());
169
185
  root.replaceChildren(...children);
170
186
 
@@ -3,7 +3,7 @@
3
3
  border: 1px solid var(--zx-color-border);
4
4
  border-radius: var(--zx-radius-lg);
5
5
  background: var(--zx-color-bg-surface);
6
- box-shadow: var(--zx-shadow-1);
6
+ box-shadow: var(--zx-surface-shadow);
7
7
  }
8
8
 
9
9
  /* The header is a row, not one big button: the collapse control takes the title area and the
@@ -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
 
@@ -49,7 +49,8 @@ button.zx-panel__toggle:hover {
49
49
 
50
50
  /* Inset, because the panel clips its own overflow and would cut an outside ring. */
51
51
  button.zx-panel__toggle:focus-visible {
52
- outline: 0;
52
+ outline-color: transparent;
53
+ outline-offset: -2px;
53
54
  box-shadow: inset var(--zx-focus-ring);
54
55
  }
55
56
 
@@ -51,11 +51,11 @@
51
51
  }
52
52
 
53
53
  .zx-questionnaire__item:focus {
54
- outline: none;
54
+ outline-color: transparent;
55
55
  }
56
56
 
57
57
  .zx-questionnaire__item:focus-visible {
58
- outline: none;
58
+ outline-color: transparent;
59
59
  box-shadow: var(--zx-focus-ring);
60
60
  border-radius: var(--zx-questionnaire-radius);
61
61
  }
@@ -275,7 +275,7 @@
275
275
  }
276
276
 
277
277
  .zx-questionnaire__freeform-input:focus-visible {
278
- outline: none;
278
+ outline-color: transparent;
279
279
  border-color: var(--zx-color-accent);
280
280
  box-shadow: var(--zx-focus-ring);
281
281
  }
@@ -301,7 +301,7 @@
301
301
  }
302
302
 
303
303
  .zx-questionnaire__review:focus {
304
- outline: none;
304
+ outline-color: transparent;
305
305
  }
306
306
 
307
307
 
@@ -28,7 +28,8 @@ export function registerRatingFieldAdapter() {
28
28
  }
29
29
  },
30
30
  focus: () => rating.focus(),
31
- setDisabled: (value) => (value ? rating.disable() : rating.enable())
31
+ setDisabled: (value) => (value ? rating.disable() : rating.enable()),
32
+ setReadonly: (value) => { rating.setReadonly(value); }
32
33
  };
33
34
  });
34
35
  }
@@ -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;
@@ -69,7 +76,7 @@
69
76
  }
70
77
 
71
78
  .zx-rating__radio:focus-visible {
72
- outline: 0;
79
+ outline-color: transparent;
73
80
  box-shadow: var(--zx-focus-ring);
74
81
  }
75
82
 
@@ -13,8 +13,11 @@
13
13
  }
14
14
 
15
15
  /* The input reserves room for the clear button, which floats over its trailing edge, so showing
16
- or hiding the button never changes the control's total width. */
16
+ or hiding the button never changes the control's total width. It takes every bit of width the
17
+ field is given — a toolbar stretches the field, and the submit glyph must stay on its trailing
18
+ edge rather than stopping where the input's own width ran out. */
17
19
  .zx-search__input {
20
+ flex: 1 1 auto;
18
21
  min-inline-size: 12rem;
19
22
  min-block-size: 0;
20
23
  border: 0;
@@ -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
+ }
@@ -14,6 +14,8 @@ export function registerSelectFieldAdapter() {
14
14
  if (event.target.closest?.('.zx-field__label')) field.adapter.focus();
15
15
  });
16
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;
17
19
  const select = field.own(preset === 'priority'
18
20
  ? Select.priority(null, selectOptions)
19
21
  : preset === 'status'
@@ -0,0 +1,165 @@
1
+ import { h } from '../../core/dom.js';
2
+ import { icon } from '../../core/icons.js';
3
+
4
+ /**
5
+ * The shapes a workflow-status glyph can take. Each status reads by shape first and colour
6
+ * second, so the set stays legible to readers who do not see the colour.
7
+ * @typedef {'dotted'|'ring'|'progress'|'review'|'ready'|'waiting'|'done'|'canceled'|'duplicate'} StatusIndicator
8
+ */
9
+ /** @typedef {'muted'|'neutral'|'warning'|'info'|'accent'|'success'|'danger'} StatusTone */
10
+ /**
11
+ * The built-in workflow statuses. `review` and `cancelled` are accepted as spellings of
12
+ * `in-review` and `canceled`.
13
+ * @typedef {'backlog'|'todo'|'in-progress'|'in-review'|'review'|'ready'|'waiting'|'done'|'canceled'|'cancelled'|'duplicate'} StatusId
14
+ */
15
+ /**
16
+ * @typedef {Object} StatusIconOptions
17
+ * @property {StatusIndicator} [indicator] Shape, overriding the status's own.
18
+ * @property {StatusTone} [tone] Colour, overriding the status's own.
19
+ * @property {string|null} [label=null] Accessible name. Without one the glyph is decorative
20
+ * (`aria-hidden`) and must sit beside text, or inside a control with a tooltip, that says the
21
+ * status.
22
+ */
23
+ /**
24
+ * @typedef {Object} PriorityIconOptions
25
+ * @property {string|null} [label=null] Accessible name. Without one the glyph is decorative
26
+ * (`aria-hidden`) and must sit beside text, or inside a control with a tooltip, that says the
27
+ * priority.
28
+ */
29
+
30
+ /** Every shape `statusIcon()` draws. */
31
+ export const STATUS_INDICATORS = Object.freeze([
32
+ 'dotted', 'ring', 'progress', 'review', 'ready', 'waiting', 'done', 'canceled', 'duplicate'
33
+ ]);
34
+
35
+ /** Every colour a status glyph takes, from the semantic set. */
36
+ export const STATUS_TONES = Object.freeze([
37
+ 'muted', 'neutral', 'warning', 'info', 'accent', 'success', 'danger'
38
+ ]);
39
+
40
+ /**
41
+ * The built-in statuses in workflow order, as `[id, message key, English name, indicator, tone]`.
42
+ * `Select.status()` lists them; `statusIcon()` draws them.
43
+ */
44
+ export const DEFAULT_STATUSES = Object.freeze([
45
+ ['backlog', 'status.backlog', 'Backlog', 'dotted', 'muted'],
46
+ ['todo', 'status.todo', 'Todo', 'ring', 'neutral'],
47
+ ['in-progress', 'status.inProgress', 'In Progress', 'progress', 'warning'],
48
+ ['in-review', 'status.inReview', 'In Review', 'review', 'info'],
49
+ ['ready', 'status.ready', 'Ready to Merge', 'ready', 'accent'],
50
+ ['waiting', 'status.waiting', 'Waiting for feedback', 'waiting', 'warning'],
51
+ ['done', 'status.done', 'Done', 'done', 'info'],
52
+ ['canceled', 'status.canceled', 'Canceled', 'canceled', 'muted'],
53
+ ['duplicate', 'status.duplicate', 'Duplicate', 'duplicate', 'muted']
54
+ ]);
55
+
56
+ /** Other spellings of the built-in status ids. */
57
+ const STATUS_ALIASES = Object.freeze({ review: 'in-review', cancelled: 'canceled' });
58
+
59
+ /** The top priority level, the one coloured level: a danger square with an exclamation mark. */
60
+ export const PRIORITY_URGENT = 4;
61
+
62
+ /**
63
+ * Resolves the shape and colour of a status glyph.
64
+ *
65
+ * A status is a built-in id (or one of its other spellings), or a status item of the kind
66
+ * `Select.status({items})` takes, whose own `indicator` and `tone` win over the built-in status
67
+ * its `ID` names. Options win over both. An unknown shape falls back to the empty ring and an
68
+ * unknown colour to muted, as `Select.status()` does with a custom item.
69
+ * @param {StatusId|string|{ID?: unknown, id?: unknown, indicator?: unknown, tone?: unknown}|null|undefined} status
70
+ * @param {StatusIconOptions} [options={}] Overrides.
71
+ * @returns {{indicator: StatusIndicator, tone: StatusTone}}
72
+ */
73
+ export function resolveStatusGlyph(status, options = {}) {
74
+ const item = status && typeof status === 'object' ? status : null;
75
+ const id = normalizeStatusId(item ? (item.ID ?? item.id) : status);
76
+ const preset = DEFAULT_STATUSES.find(([key]) => key === id);
77
+ const indicator = String(options.indicator ?? item?.indicator ?? preset?.[3] ?? 'ring');
78
+ const tone = String(options.tone ?? item?.tone ?? preset?.[4] ?? 'muted');
79
+ return {
80
+ indicator: /** @type {StatusIndicator} */ (STATUS_INDICATORS.includes(indicator) ? indicator : 'ring'),
81
+ tone: /** @type {StatusTone} */ (STATUS_TONES.includes(tone) ? tone : 'muted')
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Maps a status id to its built-in spelling: trimmed, lower-cased, and `review` / `cancelled`
87
+ * read as `in-review` / `canceled`.
88
+ * @param {unknown} id Status id.
89
+ * @returns {string}
90
+ */
91
+ export function normalizeStatusId(id) {
92
+ const key = String(id ?? '').trim().toLowerCase();
93
+ return STATUS_ALIASES[key] ?? key;
94
+ }
95
+
96
+ /**
97
+ * Settles a priority level: an integer from 0 (lowest) to 4 (highest), or `null` for no priority.
98
+ * Out-of-range numbers clamp; anything that is not a number reads as 0.
99
+ * @param {unknown} level Priority level.
100
+ * @returns {number|null}
101
+ */
102
+ export function normalizePriorityLevel(level) {
103
+ if (level === null || level === undefined || level === '') return null;
104
+ return Math.max(0, Math.min(PRIORITY_URGENT, Math.trunc(Number(level) || 0)));
105
+ }
106
+
107
+ /**
108
+ * Draws the workflow-status glyph `Select.status()` shows, for a list row, a board column, a
109
+ * filter chip, or a heading — so every place a status appears reads as the picker that changes it.
110
+ *
111
+ * Built-in statuses: `backlog` (dotted ring), `todo` (empty ring), `in-progress` (ring with a
112
+ * centre dot), `in-review` (half filled; also `review`), `ready` (a third filled), `waiting` (two
113
+ * thirds filled), `done` (filled, with a check), `canceled` (filled, with a cross; also
114
+ * `cancelled`), and `duplicate` (ring with a slash). Colour only repeats what the shape says.
115
+ * @param {StatusId|string|{ID?: unknown, indicator?: unknown, tone?: unknown}} status Built-in id,
116
+ * or a status item with its own `indicator` and `tone`.
117
+ * @param {StatusIconOptions} [options={}] Shape, colour, and accessible-name overrides.
118
+ * @returns {HTMLSpanElement} A `.zx-status-icon` element, decorative unless `label` is given.
119
+ */
120
+ export function statusIcon(status, options = {}) {
121
+ const { indicator, tone } = resolveStatusGlyph(status, options ?? {});
122
+ const marker = /** @type {HTMLSpanElement} */ (h('span', {
123
+ class: 'zx-status-icon',
124
+ dataset: { indicator, tone }
125
+ }));
126
+ if (indicator === 'done') marker.append(icon('check', { size: 8 }));
127
+ if (indicator === 'canceled') marker.append(icon('x', { size: 7 }));
128
+ return name(marker, options?.label);
129
+ }
130
+
131
+ /**
132
+ * Draws the priority glyph `Select.priority()` shows. Levels 0–3 are four signal bars in muted
133
+ * ink, filled up to the level; level 4, the highest, is a filled danger square whose "!" the
134
+ * stylesheet draws, so it never joins the text beside it; no priority (`null`) is a row of dashes.
135
+ * @param {number|null} level Priority level, 0 (lowest) to 4 (highest), or `null` for none.
136
+ * @param {PriorityIconOptions} [options={}] Accessible-name option.
137
+ * @returns {HTMLSpanElement} A `.zx-priority-icon` element, decorative unless `label` is given.
138
+ */
139
+ export function priorityIcon(level, options = {}) {
140
+ const normalized = normalizePriorityLevel(level);
141
+ const glyph = /** @type {HTMLSpanElement} */ (h('span', {
142
+ class: 'zx-priority-icon',
143
+ dataset: { level: normalized === null ? 'none' : normalized }
144
+ }, normalized === PRIORITY_URGENT ? null : Array.from({ length: PRIORITY_URGENT }, (_, index) => h('span', {
145
+ dataset: { filled: String(normalized !== null && index <= normalized) }
146
+ }))));
147
+ return name(glyph, options?.label);
148
+ }
149
+
150
+ /**
151
+ * Makes a glyph an image with an accessible name, or hides it from assistive technology.
152
+ * @param {HTMLSpanElement} glyph Glyph element.
153
+ * @param {unknown} label Accessible name, if any.
154
+ * @returns {HTMLSpanElement}
155
+ */
156
+ function name(glyph, label) {
157
+ const text = label == null ? '' : String(label).trim();
158
+ if (text) {
159
+ glyph.setAttribute('role', 'img');
160
+ glyph.setAttribute('aria-label', text);
161
+ } else {
162
+ glyph.setAttribute('aria-hidden', 'true');
163
+ }
164
+ return glyph;
165
+ }