@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
@@ -47,7 +47,8 @@ import {
47
47
  * @property {'outlined'|'raised'|'filled'} [variant='outlined'] Card surface treatment.
48
48
  * @property {number} [loadingCount=6] Skeleton cards shown while loading.
49
49
  * @property {1|2|3|4|5|6} [headingLevel=3] Per-card title heading level.
50
- * @property {string} [label='Card view'] Accessible collection label.
50
+ * @property {string|null} [label=null] Accessible collection label; null resolves message
51
+ * `cardView.label`, "Card view".
51
52
  * @property {(event:CustomEvent<Record<string,unknown>>)=>void} [onrecordaction]
52
53
  */
53
54
 
@@ -96,7 +97,7 @@ export class CardView extends RecordView {
96
97
  variant: 'outlined',
97
98
  loadingCount: 6,
98
99
  headingLevel: 3,
99
- label: 'Card view'
100
+ label: null
100
101
  };
101
102
 
102
103
  /**
@@ -126,7 +127,7 @@ export class CardView extends RecordView {
126
127
  this._cardHeadingLevel = normalizeHeadingLevel(options.headingLevel);
127
128
  try {
128
129
  this._initRecordView(root);
129
- root.setAttribute('aria-label', String(options.label || 'Card view'));
130
+ root.setAttribute('aria-label', String(options.label || this.msg('cardView.label')));
130
131
  const sort = h('label', { ref: 'sortWrap', class: 'zx-card-view__sort' },
131
132
  h('span', { class: 'zx-card-view__sort-label' }, this._message('cardView.sort', 'Sort')),
132
133
  h('select', { ref: 'sort', ariaLabel: this._message('cardView.sortCards', 'Sort cards') }));
@@ -258,7 +259,7 @@ export class CardView extends RecordView {
258
259
  return;
259
260
  }
260
261
  const groups = groupCardRecords(this._viewData, this._cardOptions().groupBy,
261
- this._cardGroupOrder, this._viewFields);
262
+ this._cardGroupOrder, this._viewFields, this.msg('cardView.ungrouped'));
262
263
  if (this._viewData.length === 0 && groups.length === 0) {
263
264
  content.replaceChildren(this._createCardEmpty());
264
265
  return;
@@ -298,13 +299,14 @@ export class CardView extends RecordView {
298
299
  selectionTrigger: options.selectionTrigger,
299
300
  selected: this._isViewSelected(id),
300
301
  variant: this._cardVariant,
301
- headingLevel: this._cardHeadingLevel
302
+ headingLevel: this._cardHeadingLevel,
303
+ message: (key, ...args) => this.msg(key, ...args)
302
304
  });
303
305
  this._cardNodesById.set(id, card);
304
306
  list.append(card);
305
307
  }
306
308
  if (group.records.length === 0) {
307
- list.append(h('li', { class: 'zx-card-view__group-empty' }, 'No records in this group.'));
309
+ list.append(h('li', { class: 'zx-card-view__group-empty' }, this.msg('cardView.groupEmpty')));
308
310
  }
309
311
  if (group.id === null) return list;
310
312
  return h('section', { class: 'zx-card-view__group', ariaLabelledby: headingId },
@@ -339,7 +341,8 @@ export class CardView extends RecordView {
339
341
  for (const [id, card] of this._cardNodesById) {
340
342
  const selected = this._isViewSelected(id);
341
343
  card.dataset.selected = String(selected);
342
- if (this.options.selectable) card.setAttribute('aria-description', selected ? 'Selected' : 'Not selected');
344
+ if (this.options.selectable) card.setAttribute('aria-description',
345
+ this.msg(selected ? 'recordCard.selected' : 'recordCard.notSelected'));
343
346
  const checkbox = /** @type {HTMLInputElement|null} */ (card.querySelector('[data-record-selection]'));
344
347
  if (checkbox) checkbox.checked = selected;
345
348
  }
@@ -444,9 +447,10 @@ export class CardView extends RecordView {
444
447
  * @param {CardViewOptions['groupBy']} groupBy Group field or resolver.
445
448
  * @param {unknown[]} [groupOrder=[]] Preferred group ids.
446
449
  * @param {ViewField[]} [fields=[]] Complete fields for accessor-aware grouping.
450
+ * @param {string} [ungroupedLabel='Ungrouped'] Label of the group collecting records without a value.
447
451
  * @returns {CardRecordGroup[]} Deterministic groups.
448
452
  */
449
- export function groupCardRecords(records, groupBy, groupOrder = [], fields = []) {
453
+ export function groupCardRecords(records, groupBy, groupOrder = [], fields = [], ungroupedLabel = 'Ungrouped') {
450
454
  if (!Array.isArray(records)) throw new TypeError('Card view records must be an array');
451
455
  if (groupBy == null) return records.length ? [{ id: null, label: '', records: [...records] }] : [];
452
456
  const preferred = normalizeGroupOrder(groupOrder);
@@ -461,7 +465,7 @@ export function groupCardRecords(records, groupBy, groupOrder = [], fields = [])
461
465
  }
462
466
  return [...groups].map(([id, grouped]) => ({
463
467
  id,
464
- label: id || 'Ungrouped',
468
+ label: id || ungroupedLabel,
465
469
  records: grouped
466
470
  }));
467
471
  }
@@ -1,6 +1,7 @@
1
1
  // @ts-check
2
2
  import { h, safeHref } from '../../core/dom.js';
3
3
  import { icon } from '../../core/icons.js';
4
+ import { translate } from '../../core/i18n.js';
4
5
  import { uid } from '../../core/util.js';
5
6
  import { readViewField, renderViewField } from '../view/record-view.js';
6
7
 
@@ -67,6 +68,10 @@ import { readViewField, renderViewField } from '../view/record-view.js';
67
68
  * @property {'outlined'|'raised'|'filled'} [variant='outlined'] Surface treatment.
68
69
  * @property {1|2|3|4|5|6} [headingLevel=3] Card title heading level.
69
70
  * @property {Node[]} [controls=[]] Listener-free, owner-created controls such as a move handle.
71
+ * @property {(key: string, ...args: unknown[]) => string} [message] Resolves the card's built-in strings
72
+ * (`recordCard.select`, `recordCard.actions`, `recordCard.untitled`, `recordCard.selected`,
73
+ * `recordCard.notSelected`). An owning component passes its own `msg()`; omitted, the host
74
+ * translator and then the default English catalogue answer.
70
75
  */
71
76
 
72
77
  /**
@@ -111,6 +116,11 @@ export function createRecordCard(record, index, options = {}) {
111
116
  const selected = Boolean(options.selected);
112
117
  const headingId = uid('zx-record-card-title');
113
118
  const headingLevel = normalizeHeadingLevel(options.headingLevel);
119
+ /** @type {(key: string, ...args: unknown[]) => string} */
120
+ const message = typeof options.message === 'function'
121
+ ? options.message
122
+ : (key, ...args) => translate(key, args);
123
+ const name = textAlternative(title) || message('recordCard.untitled');
114
124
 
115
125
  const headingContent = link
116
126
  ? h('a', {
@@ -137,13 +147,13 @@ export function createRecordCard(record, index, options = {}) {
137
147
  type: 'checkbox',
138
148
  checked: selected,
139
149
  dataset: { recordSelection: '' },
140
- ariaLabel: `Select ${textAlternative(title) || 'record'}`
150
+ ariaLabel: message('recordCard.select', name)
141
151
  })) : null;
142
152
  const actionNodes = actions.map(createAction);
143
153
  const actionArea = actionNodes.length || controls.length ? h('div', {
144
154
  class: 'zx-record-card__actions',
145
155
  role: 'group',
146
- ariaLabel: `Actions for ${textAlternative(title) || 'record'}`
156
+ ariaLabel: message('recordCard.actions', name)
147
157
  }, controls, actionNodes) : null;
148
158
  const header = h('header', { class: 'zx-record-card__header' }, selection,
149
159
  h('div', { class: 'zx-record-card__titles' }, titleRow,
@@ -154,7 +164,8 @@ export function createRecordCard(record, index, options = {}) {
154
164
  const card = /** @type {HTMLLIElement} */ (h('li', {
155
165
  class: 'zx-record-card',
156
166
  tabindex: 0,
157
- ariaDescription: selectable ? selected ? 'Selected' : 'Not selected' : undefined,
167
+ ariaDescription: selectable
168
+ ? message(selected ? 'recordCard.selected' : 'recordCard.notSelected') : undefined,
158
169
  ariaLabelledby: headingId,
159
170
  ariaKeyshortcuts: selectable && options.selectionTrigger === 'card' ? 'Space Enter' : undefined,
160
171
  dataset: {
@@ -8,6 +8,9 @@
8
8
  .zx-chart__viewport {
9
9
  position: relative;
10
10
  inline-size: 100%;
11
+ /* The automatic minimum width would carry the 220px floor through the aspect ratio (587px at
12
+ 16 / 6) and push the canvas out of a narrow card; narrow, the chart grows taller instead. */
13
+ min-inline-size: 0;
11
14
  min-block-size: 220px;
12
15
  aspect-ratio: var(--zx-chart-aspect-ratio, 16 / 9);
13
16
  }
@@ -54,7 +57,7 @@
54
57
  border-collapse: collapse;
55
58
  }
56
59
 
57
- .zx-chart__table caption { margin-block-end: var(--zx-space-2); font-weight: 700; text-align: start; }
60
+ .zx-chart__table caption { margin-block-end: var(--zx-space-2); font-weight: var(--zx-weight-semibold); text-align: start; }
58
61
  .zx-chart__table th,
59
62
  .zx-chart__table td { border-block-end: 1px solid var(--zx-color-border); padding: var(--zx-space-2); text-align: end; }
60
63
  .zx-chart__table th:first-child,
@@ -1,5 +1,6 @@
1
1
  import { Component } from '../../core/component.js';
2
2
  import { h } from '../../core/dom.js';
3
+ import { prefersReducedMotion, watchMotion } from '../../core/motion.js';
3
4
  import { uid } from '../../core/util.js';
4
5
 
5
6
  /** @typedef {{label?: string, data?: unknown[], [key: string]: unknown}} ChartDataset */
@@ -9,6 +10,9 @@ import { uid } from '../../core/util.js';
9
10
  * @property {string} type Chart type.
10
11
  * @property {ChartData} data Chart data.
11
12
  * @property {Record<string, unknown>} options Engine-specific options.
13
+ * @property {boolean} reducedMotion Whether the chart must not animate: the operating system asks
14
+ * for reduced motion, or the host sits inside a `data-zx-motion="reduced"` region. An adapter turns
15
+ * its engine's own animation off when this is true; `ChartJsAdapter` sets `animation: false`.
12
16
  * @property {(detail: ChartSelectDetail) => void} onSelect Point-selection callback.
13
17
  */
14
18
  /**
@@ -145,6 +149,9 @@ export class Chart extends Component {
145
149
 
146
150
  this._observer = typeof ResizeObserver === 'function' ? new ResizeObserver(() => this.resize()) : null;
147
151
  this._observer?.observe(viewport);
152
+ // The engine tweens in script, where no stylesheet can reach it: when motion is reduced, or
153
+ // stops being reduced, the mounted chart is updated with the new answer in its spec.
154
+ this._stopWatchingMotion = watchMotion(root, () => this._mountOrUpdate(false));
148
155
  return root;
149
156
  }
150
157
 
@@ -232,6 +239,7 @@ export class Chart extends Component {
232
239
  this._destroyed = true;
233
240
  const original = this._original;
234
241
  this._observer?.disconnect();
242
+ this._stopWatchingMotion?.();
235
243
  this._destroyHandle();
236
244
  super.destroy();
237
245
  if (!this._createdRoot && original) {
@@ -277,6 +285,7 @@ export class Chart extends Component {
277
285
  ...themeChartOptions(/** @type {HTMLElement} */ (this.el)),
278
286
  ...cloneChartValue(this._chartOptions)
279
287
  },
288
+ reducedMotion: prefersReducedMotion(this.el),
280
289
  onSelect: (detail) => this.emit('select', detail)
281
290
  };
282
291
  }
@@ -386,6 +395,9 @@ function chartJsConfig(spec, defaults, getInstance) {
386
395
  ...cloneChartValue(defaults),
387
396
  ...supplied
388
397
  };
398
+ // Reduced motion wins over any animation the application configured: a person who asked for
399
+ // less motion gets it from every chart, not only from the ones that remembered to ask.
400
+ if (spec.reducedMotion) options.animation = false;
389
401
  const originalClick = options.onClick;
390
402
  options.onClick = (event, elements, chart) => {
391
403
  if (typeof originalClick === 'function') originalClick(event, elements, chart);
@@ -11,6 +11,7 @@
11
11
  padding-inline: var(--zx-control-pad-x);
12
12
  font: inherit;
13
13
  font-size: var(--zx-control-font-size);
14
+ font-weight: var(--zx-control-font-weight);
14
15
  cursor: pointer;
15
16
  }
16
17
 
@@ -18,9 +19,16 @@
18
19
  background: var(--zx-color-bg-hover);
19
20
  }
20
21
 
22
+ /* Pressed is a state the button is in, so the button itself goes neutral — the current fill, a
23
+ stronger border, the full text colour — and only the check glyph, which is the mark, keeps the
24
+ accent, as a checkbox would. */
21
25
  .zx-check-button[aria-pressed="true"] {
22
- border-color: var(--zx-color-accent);
23
- background: var(--zx-color-bg-selected);
26
+ border-color: var(--zx-color-border-strong);
27
+ background: var(--zx-color-bg-current);
28
+ color: var(--zx-color-text);
29
+ }
30
+
31
+ .zx-check-button__on {
24
32
  color: var(--zx-color-accent);
25
33
  }
26
34
 
@@ -7,7 +7,9 @@
7
7
  }
8
8
 
9
9
  .zx-checklist__label {
10
- font-weight: 650;
10
+ color: var(--zx-color-text-muted);
11
+ font-size: var(--zx-text-sm);
12
+ font-weight: var(--zx-weight-medium);
11
13
  }
12
14
 
13
15
  .zx-checklist__search {
@@ -31,10 +33,12 @@
31
33
  display: flex;
32
34
  align-items: center;
33
35
  gap: var(--zx-space-2);
34
- min-block-size: var(--zx-control-height);
36
+ /* The same row as a listbox option. */
37
+ min-block-size: calc(var(--zx-control-height) - 2px);
35
38
  border-radius: var(--zx-radius-sm);
36
39
  cursor: pointer;
37
40
  padding: var(--zx-space-1) var(--zx-space-2);
41
+ line-height: 1.35;
38
42
  }
39
43
 
40
44
  .zx-checklist__item:hover {
@@ -45,8 +49,11 @@
45
49
  background: var(--zx-color-bg-selected);
46
50
  }
47
51
 
52
+ /* The base control height is for text fields; a checkbox that inherited it would make every row
53
+ a field tall. */
48
54
  .zx-checklist__item input {
49
55
  flex: none;
56
+ min-block-size: 0;
50
57
  accent-color: var(--zx-color-accent);
51
58
  }
52
59
 
@@ -13,6 +13,9 @@ import { matchItems } from '../select/filter.js';
13
13
  * @property {boolean} [search=true] Whether the search field is shown.
14
14
  * @property {number} [height=280] Maximum list height in pixels.
15
15
  * @property {boolean} [defaultChecked=false] Fallback initial checked state.
16
+ * @property {boolean} [hideLabel=false] Whether the heading is left out. The group keeps the
17
+ * heading text as its accessible name (`aria-label`); a Field labelling the checklist names it
18
+ * instead, which is why the `checklist` field type sets this.
16
19
  * @property {(() => Promise<ChecklistItem[]>|ChecklistItem[])|null} [load=null] Async item loader.
17
20
  * @property {Record<string, string>|Record<string, Record<string, string>>} [msg] Localized messages.
18
21
  * @property {(event: CustomEvent<ChecklistChangeDetail>) => void} [onchange] Change listener.
@@ -40,6 +43,7 @@ export class Checklist extends Component {
40
43
  search: true,
41
44
  height: 280,
42
45
  defaultChecked: false,
46
+ hideLabel: false,
43
47
  load: null
44
48
  };
45
49
 
@@ -53,15 +57,16 @@ export class Checklist extends Component {
53
57
  const root = /** @type {HTMLElement} */ (this.el ?? h('div'));
54
58
  this.el = root;
55
59
  if (!this._createdRoot) this._originalChildren = Array.from(root.childNodes);
56
- for (const name of ['role', 'aria-labelledby', 'data-state']) {
60
+ for (const name of ['role', 'aria-label', 'aria-labelledby', 'data-state']) {
57
61
  this._originalAttributes.set(name, root.getAttribute(name));
58
62
  }
59
63
 
64
+ const labelText = this._message('checklist.label', 'Checklist');
60
65
  const labelId = uid('zx-checklist-label');
61
- const title = h('div', {
66
+ const title = this.options.hideLabel ? null : h('div', {
62
67
  class: 'zx-checklist__label',
63
68
  id: labelId
64
- }, this._message('checklist.label', 'Checklist'));
69
+ }, labelText);
65
70
  const search = h('input', {
66
71
  ref: 'search',
67
72
  class: 'zx-checklist__search',
@@ -76,9 +81,10 @@ export class Checklist extends Component {
76
81
  class: 'zx-checklist__list'
77
82
  });
78
83
  list.style.maxBlockSize = `${Math.max(0, Number(this.options.height) || 0)}px`;
79
- root.replaceChildren(title, search, list);
84
+ root.replaceChildren(...[title, search, list].filter(Boolean));
80
85
  root.setAttribute('role', 'group');
81
- root.setAttribute('aria-labelledby', labelId);
86
+ if (title) root.setAttribute('aria-labelledby', labelId);
87
+ else root.setAttribute('aria-label', labelText);
82
88
 
83
89
  this.listen(search, 'input', () => this.search(/** @type {HTMLInputElement} */ (search).value));
84
90
  this.listen(list, 'change', (event) => {
@@ -7,7 +7,9 @@ import { Checklist } from './checklist.js';
7
7
  */
8
8
  export function registerChecklistFieldAdapter() {
9
9
  Field.register('checklist', (field, options) => {
10
- const checklist = field.own(new Checklist(null, { ...(options.props ?? {}) }));
10
+ // The Field's label names the list (`aria-labelledby`), so the checklist draws no heading of
11
+ // its own under it.
12
+ const checklist = field.own(new Checklist(null, { hideLabel: true, ...(options.props ?? {}) }));
11
13
  let disabled = false;
12
14
  let setting = false;
13
15
  const syncDisabled = () => {
@@ -17,17 +17,18 @@
17
17
  min-block-size: var(--zx-control-height);
18
18
  padding-inline: var(--zx-space-3);
19
19
  border-block-end: 1px solid var(--zx-color-border);
20
- background: var(--zx-color-bg-subtle);
20
+ background: var(--zx-color-bg-muted);
21
21
  color: var(--zx-color-text-muted);
22
22
  font-size: var(--zx-text-xs);
23
23
  }
24
24
 
25
+ /* The language is an identifier (`json`, `sql`), so it is shown as one — in the mono face, as
26
+ written — rather than shouted in letter-spaced capitals. */
25
27
  .zx-code-editor__language {
26
28
  overflow: hidden;
27
- font-weight: 600;
28
- letter-spacing: 0.04em;
29
+ font-family: var(--zx-font-mono);
30
+ font-weight: var(--zx-weight-medium);
29
31
  text-overflow: ellipsis;
30
- text-transform: uppercase;
31
32
  text-wrap: nowrap;
32
33
  }
33
34
 
@@ -44,7 +45,7 @@
44
45
  padding: var(--zx-space-3);
45
46
  border: 0;
46
47
  border-radius: 0;
47
- outline: 0;
48
+ outline-color: transparent;
48
49
  background: transparent;
49
50
  box-shadow: none;
50
51
  color: var(--zx-color-text);
@@ -59,7 +60,7 @@
59
60
  }
60
61
 
61
62
  .zx-code-editor[data-readonly="true"] {
62
- background: var(--zx-color-bg-subtle);
63
+ background: var(--zx-color-bg-muted);
63
64
  }
64
65
 
65
66
  .zx-code-editor[data-disabled="true"] {
@@ -19,10 +19,17 @@ import { h } from '../../core/dom.js';
19
19
  * @property {(event: CustomEvent<{value:string}>) => void} [onchange] Change listener.
20
20
  */
21
21
 
22
+ /** Keys that only modify the next one: pressing them is not the keystroke that ends a Tab release. */
23
+ const MODIFIER_KEYS = new Set(['Shift', 'Control', 'Alt', 'Meta', 'AltGraph', 'CapsLock', 'Fn', 'OS']);
24
+
22
25
  /**
23
26
  * Dependency-free source editor for structured text, Markdown, JSON and code. It deliberately uses
24
27
  * a native textarea: values remain text-safe, browser undo and IME behavior stay intact, and Tab /
25
28
  * Shift+Tab provide predictable indentation without a third-party editor runtime.
29
+ *
30
+ * Capturing Tab is what a code surface has to do, and it would be a keyboard trap if nothing undid
31
+ * it: Escape releases the capture until the next keystroke in the editor, so Escape then Tab (or
32
+ * Shift+Tab) moves focus out of the editor as it would leave any other field.
26
33
  * @fires CodeEditor#input
27
34
  * @fires CodeEditor#change
28
35
  * @extends {Component<CodeEditorOptions>}
@@ -81,8 +88,11 @@ export class CodeEditor extends Component {
81
88
  root.dataset.readonly = String(Boolean(this.options.readOnly));
82
89
  root.dataset.disabled = String(Boolean(this.options.disabled));
83
90
  root.replaceChildren(this._header, this._input);
91
+ /** Set by Escape: the next Tab moves focus instead of indenting. */
92
+ this._tabReleased = false;
84
93
 
85
94
  this.listen(this._input, 'keydown', (event) => this._handleKeydown(event));
95
+ this.listen(this._input, 'blur', () => { this._tabReleased = false; });
86
96
  this.listen(this._input, 'input', () => {
87
97
  this._updatePosition();
88
98
  this.emit('input', { value: this.getValue() });
@@ -139,7 +149,15 @@ export class CodeEditor extends Component {
139
149
 
140
150
  /** @param {KeyboardEvent} event @returns {void} */
141
151
  _handleKeydown(event) {
142
- if (event.key !== 'Tab' || event.altKey || event.ctrlKey || event.metaKey ||
152
+ if (event.isComposing || MODIFIER_KEYS.has(event.key)) return;
153
+ if (event.key === 'Escape') {
154
+ this._tabReleased = true;
155
+ return;
156
+ }
157
+ // Any other keystroke ends the release, the Tab it was for included.
158
+ const released = this._tabReleased;
159
+ this._tabReleased = false;
160
+ if (released || event.key !== 'Tab' || event.altKey || event.ctrlKey || event.metaKey ||
143
161
  this._input.readOnly || this._input.disabled) return;
144
162
  const indent = String(this.options.indent ?? '\t');
145
163
  if (!indent) return;
@@ -16,9 +16,15 @@ export function registerCodeEditorFieldAdapter() {
16
16
  value: options.value
17
17
  }));
18
18
  let setting = false;
19
- editor.on('input', (event) => {
19
+ // The textarea's native `change` bubbles out of the editor after the editor has reported the
20
+ // same commit; without this the Field would emit `change` twice per edit.
21
+ field.listen(editor.el, 'change', (event) => event.stopImmediatePropagation());
22
+ // `change` on commit (the editor's own, when the textarea loses focus after an edit); every
23
+ // keystroke as `input`.
24
+ editor.on('change', (event) => {
20
25
  if (!setting) field.emit('change', { value: event.detail.value });
21
26
  });
27
+ editor.on('input', (event) => field.emit('input', { value: event.detail.value }));
22
28
  return {
23
29
  el: editor.el,
24
30
  get: () => editor.getValue(),
@@ -28,7 +34,8 @@ export function registerCodeEditorFieldAdapter() {
28
34
  finally { setting = false; }
29
35
  },
30
36
  focus: () => editor.focus(),
31
- setDisabled: (value) => (value ? editor.disable() : editor.enable())
37
+ setDisabled: (value) => (value ? editor.disable() : editor.enable()),
38
+ setReadonly: (value) => { editor.setReadOnly(value); }
32
39
  };
33
40
  });
34
41
  }
@@ -21,12 +21,16 @@
21
21
  align-items: center;
22
22
  gap: var(--zx-space-2);
23
23
  inline-size: 100%;
24
- min-block-size: var(--zx-control-height);
25
- border: 1px solid transparent;
24
+ /* A menu row sits a little under a control so a list of them reads lighter than a column of
25
+ fields; the radius is the smallest step, inset inside the panel's own. */
26
+ min-block-size: calc(var(--zx-control-height) - 2px);
27
+ border: 0;
28
+ border-radius: var(--zx-radius-sm);
26
29
  background: transparent;
27
- padding-inline: var(--zx-control-pad-x);
30
+ padding: var(--zx-space-1) var(--zx-space-2);
28
31
  color: inherit;
29
32
  font: inherit;
33
+ line-height: 1.35;
30
34
  text-align: start;
31
35
  text-decoration: none;
32
36
  }
@@ -117,8 +121,9 @@
117
121
  box-shadow: inset 0 0 0 2px var(--zx-color-bg-raised);
118
122
  }
119
123
 
124
+ /* A section label is a caption, never taller than the rows it introduces. */
120
125
  .zx-context-menu__heading {
121
- padding: var(--zx-space-2) var(--zx-control-pad-x) var(--zx-space-1);
126
+ padding: var(--zx-space-1) var(--zx-space-2);
122
127
  color: var(--zx-color-text-muted);
123
128
  font-size: var(--zx-text-xs);
124
129
  font-weight: 600;
@@ -9,7 +9,11 @@ import {
9
9
  import { Dropdown } from '../dropdown/dropdown.js';
10
10
 
11
11
  /** @typedef {import('../../internal/menu-items.js').MenuItem} MenuItem */
12
- /** @typedef {import('../../internal/menu-items.js').MenuEntry} ContextMenuEntry */
12
+ /**
13
+ * A context menu's entries: the shared menu model without `choice` rows, which a menu that closes
14
+ * on every activation cannot hold.
15
+ * @typedef {import('../../internal/menu-items.js').MenuItem|import('../../internal/menu-items.js').MenuHeading|import('../../internal/menu-items.js').MenuSeparator|'-'} ContextMenuEntry
16
+ */
13
17
 
14
18
  /**
15
19
  * @typedef {Object} ContextMenuOptions
@@ -90,6 +94,8 @@ export class ContextMenu extends Component {
90
94
  scope: resolved
91
95
  });
92
96
  this.#panel = this.#dropdown.getPanel();
97
+ // Escape arrives through the overlay stack; closing returns focus where the menu came from.
98
+ this.#dropdown._configure({ onEscape: () => this.close(), tabReach: false });
93
99
  this.#panel.classList.add('zx-context-menu');
94
100
  this.#panel.setAttribute('role', 'menu');
95
101
 
@@ -268,7 +274,7 @@ export class ContextMenu extends Component {
268
274
  /** @param {Event} event @returns {void} */
269
275
  #onMenuKeydown(event) {
270
276
  const key = /** @type {KeyboardEvent} */ (event);
271
- if (key.key === 'Escape' || key.key === 'Tab') {
277
+ if (key.key === 'Tab') {
272
278
  event.preventDefault();
273
279
  this.close();
274
280
  return;
@@ -1,4 +1,8 @@
1
+ /* Positioned so the hidden status line below is placed inside the button. Without a positioned
2
+ ancestor it would hang off the page instead, adding scrollable overflow that focus() can scroll
3
+ into view. */
1
4
  .zx-copy-button {
5
+ position: relative;
2
6
  display: inline-flex;
3
7
  align-items: center;
4
8
  gap: var(--zx-space-2);
@@ -19,6 +23,8 @@
19
23
  */
20
24
  .zx-copy-button__status {
21
25
  position: absolute;
26
+ inset-block-start: 0;
27
+ inset-inline-start: 0;
22
28
  clip-path: inset(50%);
23
29
  inline-size: 1px;
24
30
  block-size: 1px;
@@ -1,18 +1,23 @@
1
1
  .zx-copy-input {
2
+ /* Holds the visually hidden label below, which would otherwise hang off the page. */
3
+ position: relative;
2
4
  display: flex;
3
5
  flex-direction: column;
4
6
  gap: var(--zx-space-1);
5
7
  inline-size: 100%;
6
8
  }
7
9
 
10
+ /* The same quiet label a Field draws, so a copy field sits in a form without standing out. */
8
11
  .zx-copy-input__label {
9
- color: var(--zx-color-text);
10
- font-size: var(--zx-control-font-size);
11
- font-weight: 500;
12
+ color: var(--zx-color-text-muted);
13
+ font-size: var(--zx-text-sm);
14
+ font-weight: var(--zx-weight-medium);
12
15
  }
13
16
 
14
17
  .zx-copy-input[data-hide-label] .zx-copy-input__label {
15
18
  position: absolute;
19
+ inset-block-start: 0;
20
+ inset-inline-start: 0;
16
21
  clip-path: inset(50%);
17
22
  inline-size: 1px;
18
23
  block-size: 1px;
@@ -49,7 +54,7 @@
49
54
  }
50
55
 
51
56
  .zx-copy-input__field:focus-visible {
52
- outline: 0;
57
+ outline-color: transparent;
53
58
  box-shadow: none;
54
59
  }
55
60
 
@@ -28,18 +28,19 @@
28
28
  .zx-data-filter__label {
29
29
  color: var(--zx-color-text-muted);
30
30
  font-size: var(--zx-text-sm);
31
- font-weight: 650;
31
+ font-weight: var(--zx-weight-medium);
32
32
  }
33
33
 
34
34
  .zx-data-filter__badge {
35
35
  align-self: center;
36
36
  min-inline-size: var(--zx-control-height);
37
37
  border-radius: var(--zx-radius-full);
38
- background: var(--zx-color-bg-selected);
39
- color: var(--zx-color-accent);
38
+ background: var(--zx-color-bg-current);
39
+ color: var(--zx-color-text);
40
40
  padding: var(--zx-space-1) var(--zx-space-2);
41
41
  font-size: var(--zx-text-sm);
42
- font-weight: 650;
42
+ font-variant-numeric: tabular-nums;
43
+ font-weight: var(--zx-weight-medium);
43
44
  text-align: center;
44
45
  }
45
46
 
@@ -20,7 +20,7 @@ import {
20
20
  * @property {Array<string|((row: DataFilterRow) => unknown)>} [fields] Row fields combined with OR.
21
21
  * @property {(row: DataFilterRow) => unknown} [get] Custom value accessor.
22
22
  * @property {Array<unknown|DataFilterOption>} [options] Explicit options; omitted derives distinct values.
23
- * @property {string} [emptyLabel] Empty option label.
23
+ * @property {string} [emptyLabel] Empty option label; omitted resolves message `dataFilter.all`, "All".
24
24
  */
25
25
 
26
26
  /**
@@ -48,7 +48,7 @@ import {
48
48
  * @property {FilterDefinition[]} [filters=[]] Declarative filters.
49
49
  * @property {DataFilterRow[]} [data=[]] Rows to filter.
50
50
  * @property {boolean} [autoApply=true] Apply and emit whenever a control changes.
51
- * @property {string|null} [clearLabel=null] Clear button label; null resolves `dataFilter.clear`.
51
+ * @property {string|null} [clearLabel=null] Clear button label; null resolves message `dataFilter.clear`, "Clear".
52
52
  * @property {(event: CustomEvent<DataFilterEventDetail>) => void} [onfilter]
53
53
  */
54
54