@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
@@ -68,10 +68,11 @@
68
68
  }
69
69
 
70
70
  /*
71
- * WCAG 2.5.5 Target Size: 44 px, in both densities. Density tightens a layout; it does not make
72
- * a finger smaller, so this floor is deliberately not expressed through --zx-control-height
73
- * (32 px cozy, 28 px compact) the way the rest of the controls in this row are. The chrome is flat
74
- * until the tool is hovered, focused, or pressed, so a target this size does not read as a slab.
71
+ * WCAG 2.5.5 Target Size: 44 px, whatever the theme does to spacing. A tighter theme tightens a
72
+ * layout; it does not make a finger smaller, so this floor is deliberately not expressed through
73
+ * --zx-control-height (30 px by default) the way the rest of the controls in this row are. The
74
+ * chrome is flat until the tool is hovered, focused, or pressed, so a target this size does not
75
+ * read as a slab.
75
76
  */
76
77
  .zx-list-toolbar__tool {
77
78
  position: relative;
@@ -100,11 +101,12 @@
100
101
  box-shadow: var(--zx-focus-ring);
101
102
  }
102
103
 
103
- /* A pressed tool is marked three ways — the state in `aria-pressed`, a filled ground, and a
104
- border where the other states have none — so it never rests on colour alone. */
104
+ /* A pressed tool is marked three ways — the state in `aria-pressed`, the neutral current fill, and
105
+ a border where the other states have none — so it never rests on colour alone. A toggled filter
106
+ is a mode the list is in, not a pick, so the fill is neutral rather than the accent tint. */
105
107
  .zx-list-toolbar__tool[aria-pressed="true"] {
106
108
  border-color: var(--zx-color-border-strong);
107
- background: var(--zx-color-bg-selected);
109
+ background: var(--zx-color-bg-current);
108
110
  }
109
111
 
110
112
  .zx-list-toolbar__tool:disabled {
@@ -150,9 +152,12 @@
150
152
  box-shadow: var(--zx-focus-ring);
151
153
  }
152
154
 
155
+ /* The current view is where you are: the neutral current fill, the full text colour, a heavier
156
+ label. Hover stays a lighter step of the same fill. */
153
157
  .zx-list-toolbar__view[aria-pressed="true"] {
154
- background: var(--zx-color-bg-selected);
158
+ background: var(--zx-color-bg-current);
155
159
  color: var(--zx-color-text);
160
+ font-weight: var(--zx-weight-semibold);
156
161
  }
157
162
 
158
163
  @media (prefers-reduced-motion: no-preference) {
@@ -88,7 +88,7 @@ const VIEW_PRESETS = Object.freeze({
88
88
  * control, rather than found separately beside it.
89
89
  * - **The count line is a live region**, and `setCount(null)` clears it. The previous number
90
90
  * describes the previous filters, and a stale count during a load is worse than none.
91
- * - **Tool buttons meet the 44 px target** in both densities. An icon button sized to its glyph is
91
+ * - **Tool buttons meet the 44 px target** at any spacing. An icon button sized to its glyph is
92
92
  * the control people miss twice before hitting once.
93
93
  *
94
94
  * `bind()` is optional sugar; the toolbar is fully functional without it.
@@ -26,6 +26,8 @@
26
26
  margin: 0;
27
27
  flex: 1;
28
28
  font-size: var(--zx-text-xl);
29
+ font-weight: 650;
30
+ letter-spacing: var(--zx-tracking-title);
29
31
  }
30
32
 
31
33
  .zx-master-panel__buttons {
@@ -16,12 +16,16 @@
16
16
  align-items: center;
17
17
  gap: var(--zx-space-2);
18
18
  inline-size: 100%;
19
- min-block-size: var(--zx-control-height);
20
- border: 1px solid transparent;
19
+ /* A menu row sits a little under a control so a list of them reads lighter than a column of
20
+ fields; the radius is the smallest step, inset inside the panel's own. */
21
+ min-block-size: calc(var(--zx-control-height) - 2px);
22
+ border: 0;
23
+ border-radius: var(--zx-radius-sm);
21
24
  background: transparent;
22
- padding-inline: var(--zx-control-pad-x);
25
+ padding: var(--zx-space-1) var(--zx-space-2);
23
26
  color: inherit;
24
27
  font: inherit;
28
+ line-height: 1.35;
25
29
  text-align: start;
26
30
  text-decoration: none;
27
31
  }
@@ -112,8 +116,9 @@
112
116
  box-shadow: inset 0 0 0 2px var(--zx-color-bg-raised);
113
117
  }
114
118
 
119
+ /* A section label is a caption, never taller than the rows it introduces. */
115
120
  .zx-menu-button__heading {
116
- padding: var(--zx-space-2) var(--zx-control-pad-x) var(--zx-space-1);
121
+ padding: var(--zx-space-1) var(--zx-space-2);
117
122
  color: var(--zx-color-text-muted);
118
123
  font-size: var(--zx-text-xs);
119
124
  font-weight: 600;
@@ -1,5 +1,6 @@
1
1
  import { Component } from '../../core/component.js';
2
2
  import { h, resolveElement } from '../../core/dom.js';
3
+ import { uid } from '../../core/util.js';
3
4
  import { icon } from '../../core/icons.js';
4
5
  import { rovingTabindex, typeahead } from '../../core/keyboard.js';
5
6
  import {
@@ -23,8 +24,19 @@ import { Dropdown } from '../dropdown/dropdown.js';
23
24
  * @property {(event: CustomEvent<Record<string, never>>) => void} [onclose] Close event listener.
24
25
  */
25
26
 
27
+ /**
28
+ * @typedef {Object} MenuButtonOpenOptions
29
+ * @property {boolean|HTMLElement} [returnFocus=true] Where focus goes when this opening ends with a
30
+ * selection or Escape: `true` is the trigger, an element is that element (the trigger again if
31
+ * it has left the document), and `false` leaves focus alone.
32
+ */
33
+
26
34
  /**
27
35
  * APG menu-button trigger backed by an anchored Dropdown.
36
+ *
37
+ * Choosing an item closes the menu and puts focus back on the trigger before the `select` event
38
+ * and the item's `onselect` run, so whatever they open — a dialog, a sheet — records the trigger
39
+ * as the place to return to, and focus they move themselves stays where they put it.
28
40
  * @fires MenuButton#select
29
41
  * @fires MenuButton#open
30
42
  * @fires MenuButton#close
@@ -54,6 +66,8 @@ export class MenuButton extends Component {
54
66
  #items = [];
55
67
  /** @type {{attributes: Array<[string, string]>, children: Node[]}|null} */
56
68
  #original = null;
69
+ /** @type {boolean|HTMLElement} Focus destination for the current opening. */
70
+ #returnFocus = true;
57
71
  #created = false;
58
72
  #destroyed = false;
59
73
 
@@ -89,6 +103,10 @@ export class MenuButton extends Component {
89
103
  this.#panel = this.#dropdown.getPanel();
90
104
  this.#panel.classList.add('zx-menu-button__menu');
91
105
  this.#panel.setAttribute('role', 'menu');
106
+ // The menu is named by its trigger, as the APG menu-button pattern has it. An id given here to
107
+ // an enhanced trigger goes again on destroy(), with the rest of the attributes it gained.
108
+ if (!this.el.id) this.el.id = uid('zx-menu-button');
109
+ this.#panel.setAttribute('aria-labelledby', this.el.id);
92
110
  this.el.setAttribute('aria-haspopup', 'menu');
93
111
  if (existing && hasExplicitLabel) this.setLabel(this.options.label);
94
112
 
@@ -100,7 +118,10 @@ export class MenuButton extends Component {
100
118
  );
101
119
 
102
120
  this.#dropdown.on('open', () => this.emit('open'));
103
- this.#dropdown.on('close', () => this.emit('close'));
121
+ this.#dropdown.on('close', () => {
122
+ this.#returnFocus = true;
123
+ this.emit('close');
124
+ });
104
125
  this.listen(this.el, 'click', () => {
105
126
  if (this.isOpen()) this.close();
106
127
  else {
@@ -143,9 +164,12 @@ export class MenuButton extends Component {
143
164
 
144
165
  /**
145
166
  * Opens the menu.
167
+ * @param {MenuButtonOpenOptions} [options={}] Where focus returns when this opening ends; ignored
168
+ * while the menu is already open.
146
169
  * @returns {this}
147
170
  */
148
- open() {
171
+ open(options = {}) {
172
+ if (!this.isOpen()) this.#returnFocus = options?.returnFocus ?? true;
149
173
  this.#dropdown.open();
150
174
  return this;
151
175
  }
@@ -177,16 +201,24 @@ export class MenuButton extends Component {
177
201
  return this.#panel;
178
202
  }
179
203
 
180
- /** Opens the menu and focuses its first enabled item. @returns {this} */
181
- focusFirst() {
182
- this.open();
204
+ /**
205
+ * Opens the menu and focuses its first enabled item.
206
+ * @param {MenuButtonOpenOptions} [options={}] Passed to `open()`.
207
+ * @returns {this}
208
+ */
209
+ focusFirst(options = {}) {
210
+ this.open(options);
183
211
  this.#roving.focusFirst();
184
212
  return this;
185
213
  }
186
214
 
187
- /** Opens the menu and focuses its last enabled item. @returns {this} */
188
- focusLast() {
189
- this.open();
215
+ /**
216
+ * Opens the menu and focuses its last enabled item.
217
+ * @param {MenuButtonOpenOptions} [options={}] Passed to `open()`.
218
+ * @returns {this}
219
+ */
220
+ focusLast(options = {}) {
221
+ this.open(options);
190
222
  this.#roving.focusLast();
191
223
  return this;
192
224
  }
@@ -231,8 +263,9 @@ export class MenuButton extends Component {
231
263
  #onMenuKeydown(event) {
232
264
  if (event.key === 'Escape') {
233
265
  event.preventDefault();
266
+ const target = this.#returnTarget();
234
267
  this.close();
235
- this.el.focus();
268
+ target?.focus();
236
269
  return;
237
270
  }
238
271
  if (event.key === 'Tab') {
@@ -258,9 +291,21 @@ export class MenuButton extends Component {
258
291
  event?.preventDefault();
259
292
  return;
260
293
  }
294
+ /*
295
+ * Focus goes back first, as the APG pattern has it, and only if it is still the menu's to give:
296
+ * inside the panel, or already lost to the body. A dialog opened by a listener below then
297
+ * records the trigger as its opener rather than an item that is about to be hidden, and focus
298
+ * a listener moves on purpose is left where it was put.
299
+ */
300
+ const target = this.#returnTarget();
301
+ const active = document.activeElement;
302
+ const reclaim = Boolean(target) && (!active || active === document.body || this.#panel.contains(active));
303
+ if (reclaim) target?.focus();
261
304
  const selected = this.emit('select', { value: item.value, item });
262
305
  if (selected.defaultPrevented) {
263
306
  event?.preventDefault();
307
+ // The menu stays open, so focus returns to the item, unless a listener has moved it again.
308
+ if (reclaim && document.activeElement === target && element instanceof HTMLElement) element.focus();
264
309
  return;
265
310
  }
266
311
  try {
@@ -270,6 +315,17 @@ export class MenuButton extends Component {
270
315
  }
271
316
  }
272
317
 
318
+ /**
319
+ * The element focus returns to when the current opening ends, or null when it should stay put.
320
+ * @returns {HTMLElement|null}
321
+ */
322
+ #returnTarget() {
323
+ const target = this.#returnFocus;
324
+ if (target === false) return null;
325
+ if (target instanceof HTMLElement && target.isConnected) return target;
326
+ return /** @type {HTMLElement} */ (this.el);
327
+ }
328
+
273
329
  /** @returns {HTMLElement[]} */
274
330
  #menuItems() {
275
331
  return Array.from(this.#panel.querySelectorAll(MENU_ITEM_SELECTOR));
@@ -25,12 +25,30 @@
25
25
  align-content: start;
26
26
  }
27
27
 
28
+ /* On a phone the toasts rise from the foot of the screen, full width, where they cover content
29
+ rather than the header's controls, and sit where a thumb can reach their action. */
30
+ @media (max-width: 640px) {
31
+ .zx-message-region {
32
+ inset-block-start: auto;
33
+ inset-block-end: var(--zx-space-4);
34
+ inset-inline: var(--zx-space-4);
35
+ inline-size: auto;
36
+ }
37
+
38
+ .zx-message-region:popover-open {
39
+ align-content: end;
40
+ }
41
+ }
42
+
28
43
  .zx-message__toast {
29
44
  --zx-message-kind: var(--zx-color-info);
30
45
  --zx-message-fallback-bg: var(--zx-color-info-bg);
31
46
  position: relative;
32
47
  display: grid;
33
- grid-template-columns: auto minmax(0, 1fr) auto;
48
+ /* Icon and text, then whatever follows — the action, the close button — in columns of their own. */
49
+ grid-template-columns: auto minmax(0, 1fr);
50
+ grid-auto-columns: auto;
51
+ grid-auto-flow: column;
34
52
  align-items: start;
35
53
  gap: var(--zx-space-3);
36
54
  border: 1px solid var(--zx-color-overlay-border);
@@ -64,8 +82,8 @@
64
82
  background-clip: padding-box;
65
83
  -webkit-backdrop-filter: var(--zx-glass-filter-overlay);
66
84
  backdrop-filter: var(--zx-glass-filter-overlay);
67
- box-shadow: inset 3px 0 0 color-mix(in srgb, var(--zx-message-kind) 72%,
68
- var(--zx-color-glass-highlight-strong)), var(--zx-glass-overlay-shadow);
85
+ /* No bar on the leading edge: the kind shows in the icon and the tinted border. */
86
+ box-shadow: var(--zx-glass-overlay-shadow);
69
87
  }
70
88
  }
71
89
 
@@ -90,6 +108,18 @@
90
108
  overflow-wrap: anywhere;
91
109
  }
92
110
 
111
+ /* A leading visual — an avatar, say — keeps its own size, centred on the first line of text. */
112
+ .zx-message__toast[data-leading] > .zx-message__icon {
113
+ margin-block: calc(-0.5 * var(--zx-space-1)) 0;
114
+ }
115
+
116
+ /* The action sits level with the close button: both are pulled up the same step so their centres
117
+ meet the first line of text rather than hanging below it. */
118
+ .zx-message__action {
119
+ margin-block: calc(-1 * var(--zx-space-1)) 0;
120
+ white-space: nowrap;
121
+ }
122
+
93
123
  /* Visual chrome comes from the shared .zx-icon-btn utility; only alignment is set here so
94
124
  the close control sits flush with the trailing top corner without stretching the toast.
95
125
  Trailing, not right: in an RTL document the close control belongs on the left, and a