@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
@@ -30,7 +30,10 @@
30
30
  padding-inline-start: var(--zx-space-2) !important;
31
31
  }
32
32
 
33
- .zx-select__control:has(.zx-select__input:focus-visible) {
33
+ /* The ring follows keyboard focus. A text input matches :focus-visible on a click as well, so the
34
+ component marks a pointer focus on a read-only input (`data-focus-origin="pointer"`) and the ring
35
+ waits for the first key. An editable input keeps it on click, as any text field does. */
36
+ .zx-select:not([data-focus-origin="pointer"]) .zx-select__control:has(.zx-select__input:focus-visible) {
34
37
  box-shadow: var(--zx-focus-ring);
35
38
  }
36
39
 
@@ -85,9 +88,17 @@
85
88
  border-radius: var(--zx-radius-full);
86
89
  }
87
90
 
91
+ /* Disabled reads the way a disabled Field does: text and frame fade together, nothing else
92
+ changes. Inside a disabled Field the field has already faded, so the Select does not again. */
93
+ .zx-select[aria-disabled="true"] {
94
+ opacity: 0.65;
95
+ }
96
+
97
+ .zx-field[data-disabled="true"] .zx-select[aria-disabled="true"] {
98
+ opacity: 1;
99
+ }
100
+
88
101
  .zx-select[aria-disabled="true"] .zx-select__control {
89
- background: var(--zx-color-bg-page);
90
- color: var(--zx-color-text-muted);
91
102
  cursor: default;
92
103
  }
93
104
 
@@ -97,7 +108,7 @@
97
108
  max-inline-size: calc(100vw - var(--zx-space-4));
98
109
  overflow: hidden;
99
110
  border: 1px solid var(--zx-color-overlay-border);
100
- border-radius: var(--zx-radius-md);
111
+ border-radius: var(--zx-radius-lg);
101
112
  background-color: var(--zx-color-overlay-surface);
102
113
  background-image: var(--zx-overlay-surface-image);
103
114
  background-clip: padding-box;
@@ -118,10 +129,12 @@
118
129
  display: flex;
119
130
  align-items: center;
120
131
  gap: var(--zx-space-2);
121
- min-block-size: var(--zx-control-height);
132
+ /* The same row as a menu item: a little under a control, the smallest radius. */
133
+ min-block-size: calc(var(--zx-control-height) - 2px);
122
134
  border-radius: var(--zx-radius-sm);
123
135
  cursor: default;
124
- padding: var(--zx-space-1) var(--zx-control-pad-x);
136
+ padding: var(--zx-space-1) var(--zx-space-2);
137
+ line-height: 1.35;
125
138
  }
126
139
 
127
140
  .zx-select__option-content {
@@ -137,6 +150,8 @@
137
150
  background: var(--zx-color-overlay-hover);
138
151
  }
139
152
 
153
+ /* The highlighted option (keyboard or pointer) is a neutral step; a chosen option is a pick and
154
+ keeps the accent tint and its check mark. */
140
155
  .zx-select__option[aria-selected="true"] {
141
156
  background: var(--zx-color-overlay-selected);
142
157
  }
@@ -146,6 +161,7 @@
146
161
  color: var(--zx-color-accent);
147
162
  }
148
163
 
164
+ /* A caption for the options under it, in sentence case: quieter than the options, never louder. */
149
165
  .zx-select__group {
150
166
  position: sticky;
151
167
  z-index: 1;
@@ -153,10 +169,8 @@
153
169
  background: var(--zx-color-overlay-scrim);
154
170
  color: var(--zx-color-text-muted);
155
171
  font-size: var(--zx-text-xs);
156
- font-weight: 700;
157
- letter-spacing: 0.04em;
158
- padding: var(--zx-space-2) var(--zx-control-pad-x) var(--zx-space-1);
159
- text-transform: uppercase;
172
+ font-weight: var(--zx-weight-semibold);
173
+ padding: var(--zx-space-1) var(--zx-space-2);
160
174
  }
161
175
 
162
176
  @media (prefers-reduced-transparency: reduce) {
@@ -175,13 +189,13 @@
175
189
 
176
190
  .zx-select__separator {
177
191
  block-size: 1px;
178
- margin: var(--zx-space-1) var(--zx-control-pad-x);
192
+ margin: var(--zx-space-1) var(--zx-space-2);
179
193
  background: var(--zx-color-overlay-divider);
180
194
  }
181
195
 
182
196
  .zx-select__empty {
183
197
  color: var(--zx-color-text-muted);
184
- padding: var(--zx-space-3) var(--zx-control-pad-x);
198
+ padding: var(--zx-space-3) var(--zx-space-2);
185
199
  text-align: center;
186
200
  }
187
201
 
@@ -191,30 +205,59 @@
191
205
  gap: var(--zx-space-2);
192
206
  }
193
207
 
194
- .zx-select__priority-icon {
208
+ /* The priority glyph — `priorityIcon()`, and the one Select.priority() draws, which also carries
209
+ `.zx-select__priority-icon`. Priority is read by height, not by hue: four signal bars, the filled
210
+ ones in muted ink and the rest a step quieter. Only the top level, the one that needs attention,
211
+ is coloured — a danger square with an exclamation mark. "No priority" is a row of dashes. Every
212
+ glyph takes the same box, so the labels after them line up. */
213
+ .zx-priority-icon {
195
214
  display: inline-flex;
196
- gap: 2px;
197
215
  flex: none;
198
- color: var(--zx-color-warning);
216
+ vertical-align: middle;
217
+ align-items: flex-end;
218
+ gap: 1px;
219
+ box-sizing: border-box;
220
+ inline-size: 15px;
221
+ block-size: 14px;
222
+ padding-block: 1px;
223
+ color: var(--zx-color-text-muted);
199
224
  }
200
225
 
201
- .zx-select__priority-icon > span {
202
- inline-size: 7px;
203
- block-size: 7px;
204
- border: 1px solid currentColor;
205
- border-radius: 2px;
226
+ .zx-priority-icon > span {
227
+ inline-size: 3px;
228
+ border-radius: 1px;
229
+ background: var(--zx-color-border-strong);
206
230
  }
207
231
 
208
- .zx-select__priority-icon > span[data-filled="true"] { background: currentColor; }
209
- .zx-select__priority-icon[data-level="0"] { color: var(--zx-color-success); }
210
- .zx-select__priority-icon[data-level="1"] {
211
- color: color-mix(in srgb, var(--zx-color-success) 35%, var(--zx-color-warning));
232
+ .zx-priority-icon > span:nth-child(1) { block-size: 25%; }
233
+ .zx-priority-icon > span:nth-child(2) { block-size: 50%; }
234
+ .zx-priority-icon > span:nth-child(3) { block-size: 75%; }
235
+ .zx-priority-icon > span:nth-child(4) { block-size: 100%; }
236
+ .zx-priority-icon > span[data-filled="true"] { background: currentColor; }
237
+
238
+ .zx-priority-icon[data-level="none"] { align-items: center; }
239
+ .zx-priority-icon[data-level="none"] > span {
240
+ block-size: 2px;
241
+ background: currentColor;
212
242
  }
213
- .zx-select__priority-icon[data-level="2"] { color: var(--zx-color-warning); }
214
- .zx-select__priority-icon[data-level="3"] {
215
- color: color-mix(in srgb, var(--zx-color-warning) 35%, var(--zx-color-danger));
243
+
244
+ .zx-priority-icon[data-level="4"] {
245
+ align-items: center;
246
+ justify-content: center;
247
+ inline-size: 14px;
248
+ margin-inline-end: 1px;
249
+ padding: 0;
250
+ border-radius: var(--zx-radius-sm);
251
+ background: var(--zx-color-danger);
252
+ color: var(--zx-color-on-accent);
253
+ font-size: 11px;
254
+ font-weight: var(--zx-weight-semibold);
255
+ line-height: 1;
256
+ }
257
+
258
+ .zx-priority-icon[data-level="4"]::before {
259
+ content: "!";
216
260
  }
217
- .zx-select__priority-icon[data-level="4"] { color: var(--zx-color-danger); }
218
261
 
219
262
  .zx-select__status {
220
263
  display: grid;
@@ -223,13 +266,20 @@
223
266
  gap: var(--zx-space-2);
224
267
  }
225
268
 
269
+ /* An entity without a colour of its own is a neutral tile with muted ink; one with a colour is
270
+ filled with it and drawn in the inverse text colour. */
226
271
  .zx-select__entity-glyph {
227
272
  display: inline-grid;
228
273
  place-items: center;
229
274
  inline-size: 20px;
230
275
  block-size: 20px;
231
276
  border-radius: var(--zx-radius-sm);
232
- background: var(--zx-select-entity-color, var(--zx-color-accent-subtle));
277
+ background: var(--zx-color-bg-muted);
278
+ color: var(--zx-color-text-muted);
279
+ }
280
+
281
+ .zx-select__entity-glyph[style*="--zx-select-entity-color"] {
282
+ background: var(--zx-select-entity-color);
233
283
  color: var(--zx-color-text-invert);
234
284
  }
235
285
 
@@ -256,10 +306,14 @@
256
306
  font-size: var(--zx-text-xs);
257
307
  }
258
308
 
259
- .zx-select__status-indicator {
309
+ /* The workflow-status glyph — `statusIcon()`, and the one Select.status() draws, which also carries
310
+ `.zx-select__status-indicator`. Each status is a shape first and a colour second. */
311
+ .zx-status-icon {
260
312
  position: relative;
261
313
  display: inline-grid;
262
314
  place-items: center;
315
+ box-sizing: border-box;
316
+ vertical-align: middle;
263
317
  inline-size: 14px;
264
318
  block-size: 14px;
265
319
  flex: none;
@@ -268,37 +322,37 @@
268
322
  color: var(--zx-color-text-muted);
269
323
  }
270
324
 
271
- .zx-select__status-indicator[data-tone="neutral"] { color: var(--zx-color-border-strong); }
272
- .zx-select__status-indicator[data-tone="warning"] { color: var(--zx-color-warning); }
273
- .zx-select__status-indicator[data-tone="info"] { color: var(--zx-color-info); }
274
- .zx-select__status-indicator[data-tone="accent"] { color: var(--zx-color-accent); }
275
- .zx-select__status-indicator[data-tone="success"] { color: var(--zx-color-success); }
276
- .zx-select__status-indicator[data-tone="danger"] { color: var(--zx-color-danger); }
325
+ .zx-status-icon[data-tone="neutral"] { color: var(--zx-color-border-strong); }
326
+ .zx-status-icon[data-tone="warning"] { color: var(--zx-color-warning); }
327
+ .zx-status-icon[data-tone="info"] { color: var(--zx-color-info); }
328
+ .zx-status-icon[data-tone="accent"] { color: var(--zx-color-accent); }
329
+ .zx-status-icon[data-tone="success"] { color: var(--zx-color-success); }
330
+ .zx-status-icon[data-tone="danger"] { color: var(--zx-color-danger); }
277
331
 
278
- .zx-select__status-indicator[data-indicator="dotted"] { border-style: dotted; }
279
- .zx-select__status-indicator[data-indicator="progress"]::after {
332
+ .zx-status-icon[data-indicator="dotted"] { border-style: dotted; }
333
+ .zx-status-icon[data-indicator="progress"]::after {
280
334
  content: "";
281
335
  inline-size: 4px;
282
336
  block-size: 4px;
283
337
  border-radius: inherit;
284
338
  background: currentColor;
285
339
  }
286
- .zx-select__status-indicator[data-indicator="review"] {
340
+ .zx-status-icon[data-indicator="review"] {
287
341
  background: linear-gradient(90deg, currentColor 50%, transparent 50%);
288
342
  }
289
- .zx-select__status-indicator[data-indicator="ready"] {
343
+ .zx-status-icon[data-indicator="ready"] {
290
344
  background: conic-gradient(currentColor 0 35%, transparent 35% 100%);
291
345
  }
292
- .zx-select__status-indicator[data-indicator="waiting"] {
346
+ .zx-status-icon[data-indicator="waiting"] {
293
347
  background: conic-gradient(currentColor 0 70%, transparent 70% 100%);
294
348
  }
295
- .zx-select__status-indicator:is([data-indicator="done"], [data-indicator="canceled"]) {
349
+ .zx-status-icon:is([data-indicator="done"], [data-indicator="canceled"]) {
296
350
  background: currentColor;
297
351
  }
298
- .zx-select__status-indicator:is([data-indicator="done"], [data-indicator="canceled"]) > .zx-icon {
352
+ .zx-status-icon:is([data-indicator="done"], [data-indicator="canceled"]) > .zx-icon {
299
353
  color: var(--zx-color-bg-raised);
300
354
  }
301
- .zx-select__status-indicator[data-indicator="duplicate"]::after {
355
+ .zx-status-icon[data-indicator="duplicate"]::after {
302
356
  content: "";
303
357
  inline-size: 8px;
304
358
  block-size: 2px;
@@ -343,7 +397,7 @@
343
397
 
344
398
  @media (prefers-reduced-motion: no-preference) {
345
399
  .zx-select__loading {
346
- animation: zx-select-spin var(--zx-dur-slow) linear infinite;
400
+ animation: zx-select-spin 720ms linear infinite;
347
401
  }
348
402
  }
349
403
 
@@ -352,3 +406,42 @@
352
406
  transform: rotate(1turn);
353
407
  }
354
408
  }
409
+
410
+ /*
411
+ * Quiet fields. Inside `[data-zx-fields="quiet"]` — a property panel, an inspector — the control
412
+ * reads as its value: no border, no fill, no chevron or clear button. Hover brings the hover fill
413
+ * and the chevron back; focus or an open list brings back the border, the control fill and the
414
+ * focus ring. A disabled control never lights up.
415
+ */
416
+ [data-zx-fields="quiet"] .zx-select__control,
417
+ [data-zx-fields="quiet"] .zx-select[aria-disabled="true"] .zx-select__control {
418
+ border-color: transparent;
419
+ background: transparent;
420
+ }
421
+
422
+ [data-zx-fields="quiet"] .zx-select :is(.zx-select__toggle, .zx-select__clear) {
423
+ opacity: 0;
424
+ }
425
+
426
+ [data-zx-fields="quiet"] .zx-select:not([aria-disabled="true"]):hover .zx-select__control {
427
+ background: var(--zx-color-bg-hover);
428
+ }
429
+
430
+ [data-zx-fields="quiet"] .zx-select:not([aria-disabled="true"]):is(:hover, :focus-within, [data-state="open"]) :is(.zx-select__toggle, .zx-select__clear) {
431
+ opacity: 1;
432
+ }
433
+
434
+ [data-zx-fields="quiet"] .zx-select:not([aria-disabled="true"]):is(:focus-within, [data-state="open"]) .zx-select__control {
435
+ border-color: var(--zx-color-border-control);
436
+ background: var(--zx-color-bg-control);
437
+ }
438
+
439
+ @media (prefers-reduced-motion: no-preference) {
440
+ [data-zx-fields="quiet"] .zx-select__control {
441
+ transition: background-color var(--zx-dur-fast) var(--zx-ease), border-color var(--zx-dur-fast) var(--zx-ease);
442
+ }
443
+
444
+ [data-zx-fields="quiet"] .zx-select :is(.zx-select__toggle, .zx-select__clear) {
445
+ transition: opacity var(--zx-dur-fast) var(--zx-ease);
446
+ }
447
+ }
@@ -3,8 +3,12 @@ import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
3
3
  import { icon } from '../../core/icons.js';
4
4
  import { typeahead } from '../../core/keyboard.js';
5
5
  import { position } from '../../core/position.js';
6
- import { isCssColor, uid } from '../../core/util.js';
6
+ import { uid } from '../../core/util.js';
7
+ import { itemColor } from './color.js';
7
8
  import { matchItems } from './filter.js';
9
+ import {
10
+ DEFAULT_STATUSES, STATUS_INDICATORS, STATUS_TONES, normalizePriorityLevel, priorityIcon, statusIcon
11
+ } from './glyphs.js';
8
12
 
9
13
  /** @typedef {string|number|boolean|symbol|bigint|null|undefined} Primitive */
10
14
  /** @typedef {Record<string, any>|Primitive} SelectItem */
@@ -49,7 +53,9 @@ import { matchItems } from './filter.js';
49
53
  * @property {string} [recentLabel='Recent'] Heading above recent choices.
50
54
  * @property {boolean|SelectAction} [create=false] Optional create command at the bottom.
51
55
  * @property {string|((item: SelectItem)=>unknown)} [iconKey='icon'] Item icon reader.
52
- * @property {string|((item: SelectItem)=>unknown)} [colorKey='color'] Optional icon colour reader.
56
+ * @property {string|((item: SelectItem)=>unknown)} [colorKey='color'] Optional icon colour reader: a CSS
57
+ * colour or a token reference such as `var(--zx-color-tone-blue-fg)`. Without one the glyph is a
58
+ * neutral tile in muted ink.
53
59
  * @property {string|((item: SelectItem)=>unknown)} [subtitleKey='description'] Secondary-text reader.
54
60
  * @property {((item: SelectItem, context:{selected:boolean})=>Node|string|null)|null} [renderIcon=null] Entity icon renderer.
55
61
  */
@@ -69,18 +75,6 @@ const PRIORITY_KEYS = [
69
75
  ];
70
76
  const PRIORITY_FALLBACKS = ['Lowest', 'Low', 'Medium', 'High', 'Highest'];
71
77
 
72
- const DEFAULT_STATUSES = Object.freeze([
73
- ['backlog', 'status.backlog', 'Backlog', 'dotted', 'muted'],
74
- ['todo', 'status.todo', 'Todo', 'ring', 'neutral'],
75
- ['in-progress', 'status.inProgress', 'In Progress', 'progress', 'warning'],
76
- ['in-review', 'status.inReview', 'In Review', 'review', 'info'],
77
- ['ready', 'status.ready', 'Ready to Merge', 'ready', 'accent'],
78
- ['waiting', 'status.waiting', 'Waiting for feedback', 'waiting', 'warning'],
79
- ['done', 'status.done', 'Done', 'done', 'info'],
80
- ['canceled', 'status.canceled', 'Canceled', 'canceled', 'muted'],
81
- ['duplicate', 'status.duplicate', 'Duplicate', 'duplicate', 'muted']
82
- ]);
83
-
84
78
  /**
85
79
  * APG editable combobox with optional local or asynchronous filtering.
86
80
  *
@@ -125,7 +119,7 @@ export class Select extends Component {
125
119
  render() {
126
120
  // Instance state initialized here because render() runs inside the base
127
121
  // constructor, before class-field initializers would run (and clobber it).
128
- this._items = []; this._visibleItems = []; this._fixedItems = []; this._visibleFixed = []; this._actions = []; this._selected = null; this._optionEntries = []; this._activeIndex = -1; this._open = false; this._disabled = false; this._loading = false; this._requestSequence = 0; this._queryTimer = null; this._position = null; this._createdRoot = false; this._root = null; this._snapshot = null;
122
+ this._items = []; this._visibleItems = []; this._fixedItems = []; this._visibleFixed = []; this._actions = []; this._selected = null; this._optionEntries = []; this._activeIndex = -1; this._open = false; this._disabled = false; this._loading = false; this._requestSequence = 0; this._queryTimer = null; this._position = null; this._createdRoot = false; this._root = null; this._snapshot = null; this._pointerModality = false;
129
123
  this._createdRoot = this.el === null;
130
124
  const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
131
125
  this.el = root;
@@ -209,7 +203,22 @@ export class Select extends Component {
209
203
 
210
204
  this.listen(input, 'click', () => this.open());
211
205
  this.listen(input, 'input', () => this._onInput());
212
- this.listen(input, 'keydown', (event) => this._onKeydown(/** @type {KeyboardEvent} */ (event)));
206
+ this.listen(input, 'keydown', (event) => {
207
+ if (!isModifierKey(/** @type {KeyboardEvent} */ (event))) root.removeAttribute('data-focus-origin');
208
+ this._onKeydown(/** @type {KeyboardEvent} */ (event));
209
+ });
210
+ // A text input matches :focus-visible however it was focused, read-only or not, so a click on
211
+ // a read-only Select would draw the keyboard ring. Track how the user last interacted and mark
212
+ // a pointer focus; the ring then follows keyboard focus only, and returns on the first key.
213
+ this.listen(input, 'focus', () => {
214
+ if (this._pointerModality && /** @type {HTMLInputElement} */ (input).readOnly) {
215
+ root.setAttribute('data-focus-origin', 'pointer');
216
+ } else root.removeAttribute('data-focus-origin');
217
+ });
218
+ this.listen(input, 'blur', () => root.removeAttribute('data-focus-origin'));
219
+ this.listen(document, 'keydown', (event) => {
220
+ if (!isModifierKey(/** @type {KeyboardEvent} */ (event))) this._pointerModality = false;
221
+ }, { capture: true });
213
222
  this.listen(toggle, 'click', () => {
214
223
  this.focus();
215
224
  if (this._open) this.close();
@@ -222,6 +231,7 @@ export class Select extends Component {
222
231
  this.listen(panel, 'pointerdown', (event) => event.preventDefault());
223
232
  this.listen(panel, 'click', (event) => this._onOptionClick(event));
224
233
  this.listen(document, 'pointerdown', (event) => {
234
+ this._pointerModality = true;
225
235
  if (this._open && !root.contains(/** @type {Node} */ (event.target))) this.close();
226
236
  }, { capture: true });
227
237
 
@@ -241,6 +251,15 @@ export class Select extends Component {
241
251
  this.set(id);
242
252
  }
243
253
 
254
+ /**
255
+ * Returns the selected item ID, or null: the same as reading `value`, under the `get()`/`set()`
256
+ * pair the other value components share.
257
+ * @returns {unknown}
258
+ */
259
+ get() {
260
+ return this.value;
261
+ }
262
+
244
263
  /** Selected item, or null. @returns {SelectItem|null} */
245
264
  get selected() {
246
265
  return this._selected;
@@ -381,26 +400,44 @@ export class Select extends Component {
381
400
  }
382
401
 
383
402
  /**
384
- * Creates the five-level priority preset.
403
+ * Creates the five-level priority preset: values 0 (lowest) to 4 (highest), each drawn as grey
404
+ * signal bars filled to its level, except the highest, which is the one coloured level — a
405
+ * danger square with an exclamation mark. A "No priority" row (value null, a row of dashes)
406
+ * heads the list and labels the empty control; `clearable: false` removes it.
385
407
  * @param {Element|string|null} target Component target.
386
- * @param {SelectOptions} [options={}] Select overrides other than fixed items and renderer.
408
+ * @param {SelectOptions} [options={}] Select overrides other than items and renderers.
409
+ * `clearable` defaults to true here; `noneLabel` and `placeholder` default to the
410
+ * `priority.none` message ("No priority").
387
411
  * @returns {Select}
388
412
  */
389
413
  static priority(target, options = {}) {
390
414
  const requestedValue = options.value ?? null;
391
- const select = new Select(target, {
415
+ const clearable = options.clearable ?? true;
416
+ /** @type {Select|null} */
417
+ let select = null;
418
+ // The constructor renders the list once before `select` is assigned; the label is settled by
419
+ // the setItems() below, which renders it again.
420
+ const noneLabel = () => options.noneLabel
421
+ ?? (select ? select._message('priority.none', 'No priority') : 'No priority');
422
+ select = new Select(target, {
392
423
  ...options,
424
+ clearable,
393
425
  value: null,
394
426
  items: [],
395
427
  renderItem(item) {
396
428
  return priorityContent(item, true);
397
429
  },
430
+ renderNone: options.renderNone ?? (() => priorityContent({ level: null, name: noneLabel() }, true)),
398
431
  renderValueAdornment(item) { return priorityIndicator(item.level); }
399
432
  });
433
+ if (clearable && !options.placeholder) {
434
+ /** @type {HTMLInputElement} */ (select.refs.input).placeholder = noneLabel();
435
+ }
436
+ const preset = select;
400
437
  select.setItems(PRIORITY_KEYS.map((key, level) => {
401
- let name = level === 2 ? select.msg('priority.medium') : key;
402
- if (name === 'priority.medium') name = select._message(key, PRIORITY_FALLBACKS[level]);
403
- else if (level !== 2) name = select._message(key, PRIORITY_FALLBACKS[level]);
438
+ let name = level === 2 ? preset.msg('priority.medium') : key;
439
+ if (name === 'priority.medium') name = preset._message(key, PRIORITY_FALLBACKS[level]);
440
+ else if (level !== 2) name = preset._message(key, PRIORITY_FALLBACKS[level]);
404
441
  return { ID: level, name, level };
405
442
  }));
406
443
  select.set(requestedValue, { silent: true });
@@ -453,10 +490,10 @@ export class Select extends Component {
453
490
  const value = readSelectItem(item, iconKey);
454
491
  if (value && typeof value === 'object' && typeof value.nodeType === 'number') return value.cloneNode(true);
455
492
  if (typeof value !== 'string' || !value) return null;
456
- const color = readSelectItem(item, colorKey);
493
+ const color = itemColor(readSelectItem(item, colorKey));
457
494
  return h('span', {
458
495
  class: 'zx-select__entity-glyph',
459
- style: isCssColor(color) ? { '--zx-select-entity-color': String(color) } : null,
496
+ style: color ? { '--zx-select-entity-color': color } : null,
460
497
  ariaHidden: 'true'
461
498
  }, icon(value, { size: 14 }));
462
499
  };
@@ -992,28 +1029,26 @@ function priorityContent(item, label) {
992
1029
  label ? h('span', {}, String(item.name)) : null);
993
1030
  }
994
1031
 
995
- /** @param {unknown} level @returns {Node} */
1032
+ /**
1033
+ * The priority glyph `priorityIcon()` draws, carrying the Select's own class as well so styles
1034
+ * written against it keep applying.
1035
+ * @param {unknown} level Priority level 0–4, or null for none.
1036
+ * @returns {Node}
1037
+ */
996
1038
  function priorityIndicator(level) {
997
- const normalized = Math.max(0, Math.min(4, Math.trunc(Number(level) || 0)));
998
- return h('span', {
999
- class: 'zx-select__priority-icon',
1000
- dataset: { level: normalized },
1001
- ariaHidden: 'true'
1002
- }, Array.from({ length: 5 }, (_, index) => h('span', {
1003
- dataset: { filled: String(index <= normalized) }
1004
- })));
1039
+ const glyph = priorityIcon(/** @type {number|null} */ (normalizePriorityLevel(level)));
1040
+ glyph.classList.add('zx-select__priority-icon');
1041
+ return glyph;
1005
1042
  }
1006
1043
 
1007
1044
  /** @param {unknown} input @returns {Record<string, any>|null} */
1008
1045
  function normalizeStatusItem(input) {
1009
1046
  if (!input || typeof input !== 'object' || input.ID == null || input.name == null) return null;
1010
- const indicators = ['dotted', 'ring', 'progress', 'review', 'ready', 'waiting', 'done', 'canceled', 'duplicate'];
1011
- const tones = ['muted', 'neutral', 'warning', 'info', 'accent', 'success', 'danger'];
1012
1047
  return {
1013
1048
  ...input,
1014
1049
  name: String(input.name),
1015
- indicator: indicators.includes(String(input.indicator)) ? String(input.indicator) : 'ring',
1016
- tone: tones.includes(String(input.tone)) ? String(input.tone) : 'muted',
1050
+ indicator: STATUS_INDICATORS.includes(String(input.indicator)) ? String(input.indicator) : 'ring',
1051
+ tone: STATUS_TONES.includes(String(input.tone)) ? String(input.tone) : 'muted',
1017
1052
  description: input.description == null ? '' : String(input.description),
1018
1053
  shortcut: input.shortcut == null ? '' : String(input.shortcut)
1019
1054
  };
@@ -1029,17 +1064,16 @@ function statusContent(item) {
1029
1064
  item.shortcut ? h('kbd', { class: 'zx-select__status-shortcut' }, item.shortcut) : null);
1030
1065
  }
1031
1066
 
1032
- /** @param {Record<string, any>} item @returns {Node} */
1067
+ /**
1068
+ * The status glyph `statusIcon()` draws for a settled status item, carrying the Select's own class
1069
+ * as well so styles written against it keep applying.
1070
+ * @param {Record<string, any>} item Normalized status item.
1071
+ * @returns {Node}
1072
+ */
1033
1073
  function statusIndicator(item) {
1034
- const indicator = String(item.indicator ?? 'ring');
1035
- const marker = h('span', {
1036
- class: 'zx-select__status-indicator',
1037
- dataset: { indicator, tone: String(item.tone ?? 'muted') },
1038
- ariaHidden: 'true'
1039
- });
1040
- if (indicator === 'done') marker.append(icon('check', { size: 8 }));
1041
- if (indicator === 'canceled') marker.append(icon('x', { size: 7 }));
1042
- return marker;
1074
+ const glyph = statusIcon({ indicator: item.indicator ?? 'ring', tone: item.tone ?? 'muted' });
1075
+ glyph.classList.add('zx-select__status-indicator');
1076
+ return glyph;
1043
1077
  }
1044
1078
 
1045
1079
  /** Change emitted when the selected value changes. @event Select#change @type {CustomEvent<SelectChangeDetail>} */
@@ -1061,6 +1095,17 @@ function readItem(item, reader) {
1061
1095
  return item[reader];
1062
1096
  }
1063
1097
 
1098
+ /**
1099
+ * Reports a key press that says nothing about how the user is moving focus: a lone modifier, or a
1100
+ * shortcut chord the page or the browser handles.
1101
+ * @param {KeyboardEvent} event Key event.
1102
+ * @returns {boolean}
1103
+ */
1104
+ function isModifierKey(event) {
1105
+ return ['Shift', 'Control', 'Alt', 'Meta', 'CapsLock'].includes(event.key)
1106
+ || event.ctrlKey || event.metaKey;
1107
+ }
1108
+
1064
1109
  /** @param {KeyboardEvent} event @returns {boolean} */
1065
1110
  function isPrintable(event) {
1066
1111
  return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
@@ -57,8 +57,8 @@
57
57
  inset-block-start: 0;
58
58
  inset-block-end: auto;
59
59
  border-block-end: 1px solid var(--zx-color-overlay-border);
60
- border-end-start-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
61
- border-end-end-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
60
+ border-end-start-radius: var(--zx-sheet-radius, var(--zx-radius-lg));
61
+ border-end-end-radius: var(--zx-sheet-radius, var(--zx-radius-lg));
62
62
  --zx-sheet-enter: 0 -100%;
63
63
  }
64
64
 
@@ -66,8 +66,8 @@
66
66
  inset-block-start: auto;
67
67
  inset-block-end: 0;
68
68
  border-block-start: 1px solid var(--zx-color-overlay-border);
69
- border-start-start-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
70
- border-start-end-radius: var(--zx-sheet-radius, var(--zx-radius-xl));
69
+ border-start-start-radius: var(--zx-sheet-radius, var(--zx-radius-lg));
70
+ border-start-end-radius: var(--zx-sheet-radius, var(--zx-radius-lg));
71
71
  --zx-sheet-enter: 0 100%;
72
72
  }
73
73
 
@@ -105,11 +105,14 @@
105
105
  * `display: none` immediately — so `display` and `overlay` transition with `allow-discrete`, which
106
106
  * defers the discrete jump until the rest of the transition has finished. `@starting-style` gives
107
107
  * the entry its from-state. Where either is unsupported the sheet simply appears and disappears.
108
+ *
109
+ * The slide runs on the drawer curve — most of the distance at once, then a settle — while the
110
+ * fade keeps the ordinary ease, so the panel arrives decisively without flashing in.
108
111
  */
109
112
  @media (prefers-reduced-motion: no-preference) {
110
113
  .zx-sheet {
111
114
  transition:
112
- translate var(--zx-dur-base) var(--zx-ease),
115
+ translate var(--zx-dur-base) var(--zx-ease-drawer),
113
116
  opacity var(--zx-dur-base) var(--zx-ease),
114
117
  display var(--zx-dur-base) allow-discrete,
115
118
  overlay var(--zx-dur-base) allow-discrete;
@@ -193,7 +196,7 @@
193
196
  @media (prefers-reduced-motion: no-preference) {
194
197
  .zx-sheet[data-docked] {
195
198
  transition:
196
- flex-basis var(--zx-dur-base) var(--zx-ease),
199
+ flex-basis var(--zx-dur-base) var(--zx-ease-drawer),
197
200
  opacity var(--zx-dur-base) var(--zx-ease),
198
201
  display var(--zx-dur-base) allow-discrete,
199
202
  overlay var(--zx-dur-base) allow-discrete;
@@ -246,7 +249,7 @@
246
249
  }
247
250
 
248
251
  .zx-sheet__handle:focus-visible {
249
- outline: none;
252
+ outline-color: transparent;
250
253
  box-shadow: var(--zx-focus-ring);
251
254
  }
252
255
 
@@ -363,8 +366,8 @@
363
366
  @media (prefers-reduced-motion: no-preference) {
364
367
  .zx-sheet[data-stack] {
365
368
  transition:
366
- transform var(--zx-dur-base) var(--zx-ease),
367
- translate var(--zx-dur-base) var(--zx-ease),
369
+ transform var(--zx-dur-base) var(--zx-ease-drawer),
370
+ translate var(--zx-dur-base) var(--zx-ease-drawer),
368
371
  opacity var(--zx-dur-base) var(--zx-ease),
369
372
  display var(--zx-dur-base) allow-discrete,
370
373
  overlay var(--zx-dur-base) allow-discrete;