@zeyos/zx 4.4.0 → 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 (182) hide show
  1. package/CHANGELOG.md +283 -0
  2. package/README.md +42 -10
  3. package/dist/types/components/activity-list/activity-list.d.ts +22 -0
  4. package/dist/types/components/app-sidebar/app-sidebar.d.ts +5 -3
  5. package/dist/types/components/avatar/avatar.d.ts +46 -7
  6. package/dist/types/components/badge/badge.d.ts +21 -1
  7. package/dist/types/components/button/button.d.ts +15 -0
  8. package/dist/types/components/chart/chart.d.ts +10 -0
  9. package/dist/types/components/checklist/checklist.d.ts +9 -0
  10. package/dist/types/components/code-editor/code-editor.d.ts +6 -17
  11. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  12. package/dist/types/components/datebox/datebox.d.ts +7 -2
  13. package/dist/types/components/dialog/dialog.d.ts +46 -6
  14. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  15. package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
  16. package/dist/types/components/field/field.d.ts +24 -2
  17. package/dist/types/components/fieldset/fieldset.d.ts +19 -1
  18. package/dist/types/components/file-item/file-item.d.ts +34 -6
  19. package/dist/types/components/form/form.d.ts +12 -1
  20. package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
  21. package/dist/types/components/launcher/launcher.d.ts +260 -15
  22. package/dist/types/components/list-toolbar/list-toolbar.d.ts +1 -1
  23. package/dist/types/components/menu-button/menu-button.d.ts +33 -5
  24. package/dist/types/components/message/message.d.ts +119 -17
  25. package/dist/types/components/modal/modal.d.ts +13 -2
  26. package/dist/types/components/select/color.d.ts +12 -0
  27. package/dist/types/components/select/glyphs.d.ts +124 -0
  28. package/dist/types/components/select/select.d.ts +11 -3
  29. package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
  30. package/dist/types/components/tabbox/tabbox.d.ts +9 -0
  31. package/dist/types/components/table/table.d.ts +46 -1
  32. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  33. package/dist/types/components/tree/tree.d.ts +13 -0
  34. package/dist/types/core/breakpoint.d.ts +1 -1
  35. package/dist/types/core/icons.d.ts +23 -25
  36. package/dist/types/core/motion.d.ts +59 -0
  37. package/dist/types/core/overlay-host.d.ts +15 -1
  38. package/dist/types/index.d.ts +5 -3
  39. package/dist/types/zeyos/connect.d.ts +6 -0
  40. package/dist/types/zeyos/form.d.ts +50 -9
  41. package/dist/types/zeyos/index.d.ts +1 -1
  42. package/dist/types/zeyos/query.d.ts +79 -0
  43. package/dist/types/zeyos/schema.d.ts +24 -1
  44. package/dist/types/zeyos/table.d.ts +100 -14
  45. package/dist/types/zeyos/view.d.ts +10 -0
  46. package/dist/zx-zeyos.esm.js +1621 -443
  47. package/dist/zx-zeyos.esm.min.js +53 -5
  48. package/dist/zx.css +1411 -660
  49. package/dist/zx.esm.js +1976 -703
  50. package/dist/zx.esm.min.js +56 -8
  51. package/dist/zx.global.js +1978 -705
  52. package/dist/zx.global.min.js +56 -8
  53. package/dist/zx.min.css +1 -1
  54. package/docs/api.json +219 -34
  55. package/docs/llms.md +472 -110
  56. package/docs/llms.txt +3 -3
  57. package/package.json +1 -1
  58. package/src/components/account-menu/account-menu.css +35 -10
  59. package/src/components/activity-item/activity-item.css +1 -1
  60. package/src/components/activity-item/activity-item.js +3 -0
  61. package/src/components/activity-list/activity-list.css +38 -3
  62. package/src/components/activity-list/activity-list.js +34 -0
  63. package/src/components/app-icon/app-icon.css +1 -1
  64. package/src/components/app-rail/app-rail.css +40 -79
  65. package/src/components/app-sidebar/app-sidebar.css +109 -174
  66. package/src/components/app-sidebar/app-sidebar.js +21 -6
  67. package/src/components/aurora/aurora.css +5 -3
  68. package/src/components/avatar/avatar.css +32 -7
  69. package/src/components/avatar/avatar.js +72 -8
  70. package/src/components/badge/badge.css +54 -2
  71. package/src/components/badge/badge.js +29 -2
  72. package/src/components/button/button.css +67 -9
  73. package/src/components/button/button.js +38 -0
  74. package/src/components/calendar/calendar.css +46 -46
  75. package/src/components/card/card.css +2 -2
  76. package/src/components/card-view/card-view.css +9 -7
  77. package/src/components/chart/chart.css +4 -1
  78. package/src/components/chart/chart.js +12 -0
  79. package/src/components/check-button/check-button.css +10 -2
  80. package/src/components/checklist/checklist.css +9 -2
  81. package/src/components/checklist/checklist.js +11 -5
  82. package/src/components/checklist/field-adapter.js +3 -1
  83. package/src/components/code-editor/code-editor.css +6 -5
  84. package/src/components/code-editor/code-editor.js +19 -1
  85. package/src/components/context-menu/context-menu.css +9 -4
  86. package/src/components/copy/copy-button.css +6 -0
  87. package/src/components/copy/copy-input.css +8 -3
  88. package/src/components/data-filter/data-filter.css +5 -4
  89. package/src/components/date-picker/date-picker.css +6 -6
  90. package/src/components/date-picker/date-range-picker.css +3 -3
  91. package/src/components/date-picker/time-picker.css +1 -1
  92. package/src/components/datebox/date-range-box.css +79 -2
  93. package/src/components/datebox/date-range-box.js +13 -2
  94. package/src/components/datebox/datebox.css +79 -2
  95. package/src/components/datebox/datebox.js +13 -2
  96. package/src/components/dialog/dialog.css +26 -0
  97. package/src/components/dialog/dialog.js +99 -17
  98. package/src/components/dropdown/dropdown.css +1 -1
  99. package/src/components/dropdown/dropdown.js +5 -3
  100. package/src/components/empty-state/empty-state.css +2 -2
  101. package/src/components/entity-ref/entity-ref.css +1 -1
  102. package/src/components/entity-ref/entity-ref.js +1 -1
  103. package/src/components/field/field.css +28 -9
  104. package/src/components/field/field.js +68 -26
  105. package/src/components/field-upload/field-upload.css +1 -1
  106. package/src/components/fieldset/fieldset.css +5 -3
  107. package/src/components/fieldset/fieldset.js +28 -5
  108. package/src/components/file-item/file-item.js +144 -10
  109. package/src/components/filter/filter.js +7 -3
  110. package/src/components/filter-panel/filter-panel.css +1 -1
  111. package/src/components/finder/finder.css +15 -13
  112. package/src/components/form/form.css +5 -4
  113. package/src/components/form/form.js +11 -2
  114. package/src/components/grid/grid.css +1 -1
  115. package/src/components/groupbox/groupbox.css +1 -1
  116. package/src/components/kanban-view/kanban-card.js +11 -3
  117. package/src/components/kanban-view/kanban-view.css +39 -15
  118. package/src/components/kanban-view/kanban-view.js +4 -4
  119. package/src/components/launcher/launcher.css +107 -25
  120. package/src/components/launcher/launcher.js +503 -41
  121. package/src/components/list-toolbar/list-toolbar.css +13 -8
  122. package/src/components/list-toolbar/list-toolbar.js +1 -1
  123. package/src/components/master-panel/master-panel.css +2 -0
  124. package/src/components/menu-button/menu-button.css +9 -4
  125. package/src/components/menu-button/menu-button.js +65 -9
  126. package/src/components/message/message.css +33 -3
  127. package/src/components/message/message.js +217 -15
  128. package/src/components/modal/modal.js +18 -3
  129. package/src/components/navigation-bar/navigation-bar.css +18 -9
  130. package/src/components/number-field/field-adapter.js +1 -0
  131. package/src/components/number-field/number-field.css +70 -0
  132. package/src/components/number-field/number-field.js +2 -0
  133. package/src/components/pagination/pagination.css +8 -6
  134. package/src/components/panel/panel.css +1 -1
  135. package/src/components/rating/rating.css +7 -0
  136. package/src/components/select/color.js +22 -0
  137. package/src/components/select/field-adapter.js +2 -0
  138. package/src/components/select/glyphs.js +165 -0
  139. package/src/components/select/select.css +138 -45
  140. package/src/components/select/select.js +83 -47
  141. package/src/components/sheet/sheet.css +11 -8
  142. package/src/components/slider/slider.css +16 -2
  143. package/src/components/slider/slider.js +1 -1
  144. package/src/components/sort-control/sort-control.css +4 -1
  145. package/src/components/stat-tile/stat-tile.css +41 -46
  146. package/src/components/stat-tile/stat-tile.js +17 -6
  147. package/src/components/tabbox/tabbox.css +32 -27
  148. package/src/components/tabbox/tabbox.js +5 -1
  149. package/src/components/table/table.css +104 -29
  150. package/src/components/table/table.js +90 -16
  151. package/src/components/tag-picker/tag-picker.css +83 -5
  152. package/src/components/tag-picker/tag-picker.js +25 -8
  153. package/src/components/timebox/timebox.css +22 -2
  154. package/src/components/toggle/toggle.css +12 -1
  155. package/src/components/toolbar/toolbar.js +2 -2
  156. package/src/components/tooltip/tooltip.css +4 -0
  157. package/src/components/tooltip/tooltip.js +10 -3
  158. package/src/components/tree/tree.css +25 -16
  159. package/src/components/tree/tree.js +31 -0
  160. package/src/components/value-list/value-list.css +3 -3
  161. package/src/components/view/record-view.js +1 -1
  162. package/src/core/breakpoint.js +1 -1
  163. package/src/core/icons.js +193 -60
  164. package/src/core/motion.js +147 -0
  165. package/src/core/overlay-host.js +21 -2
  166. package/src/index.js +5 -3
  167. package/src/internal/app-rail.js +1 -2
  168. package/src/zeyos/connect.js +6 -2
  169. package/src/zeyos/form.js +67 -27
  170. package/src/zeyos/index.js +1 -1
  171. package/src/zeyos/query.js +153 -0
  172. package/src/zeyos/schema.js +144 -27
  173. package/src/zeyos/table.js +68 -5
  174. package/src/zeyos/view.js +6 -0
  175. package/styles/base.css +56 -0
  176. package/styles/icons.css +11 -1
  177. package/styles/tokens/dark.css +113 -37
  178. package/styles/tokens/global.css +121 -27
  179. package/styles/tokens/semantic.css +100 -31
  180. package/styles/tokens/themes.css +7 -6
  181. package/styles/zx.css +0 -1
  182. package/styles/tokens/density.css +0 -9
@@ -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
+ }
@@ -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
+ }