@zeyos/zx 4.3.4 → 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 (231) hide show
  1. package/CHANGELOG.md +438 -0
  2. package/README.md +49 -10
  3. package/dist/types/components/activity-item/activity-item.d.ts +351 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +217 -0
  5. package/dist/types/components/app-sidebar/app-sidebar.d.ts +33 -3
  6. package/dist/types/components/avatar/avatar.d.ts +46 -7
  7. package/dist/types/components/badge/badge.d.ts +21 -1
  8. package/dist/types/components/button/button.d.ts +23 -0
  9. package/dist/types/components/calendar/calendar.d.ts +19 -0
  10. package/dist/types/components/card/card.d.ts +7 -0
  11. package/dist/types/components/card-view/card-view.d.ts +11 -3
  12. package/dist/types/components/card-view/record-card.d.ts +7 -1
  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 +149 -0
  16. package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
  17. package/dist/types/components/context-menu/context-menu.d.ts +18 -2
  18. package/dist/types/components/date-picker/date-picker.d.ts +8 -0
  19. package/dist/types/components/date-picker/time-picker.d.ts +7 -0
  20. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  21. package/dist/types/components/datebox/datebox.d.ts +7 -2
  22. package/dist/types/components/dialog/dialog.d.ts +75 -6
  23. package/dist/types/components/dropdown/dropdown.d.ts +2 -2
  24. package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
  25. package/dist/types/components/field/field.d.ts +24 -2
  26. package/dist/types/components/fieldset/fieldset.d.ts +19 -1
  27. package/dist/types/components/file-item/file-item.d.ts +262 -0
  28. package/dist/types/components/file-list/file-list.d.ts +171 -0
  29. package/dist/types/components/filter/filter.d.ts +91 -36
  30. package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
  31. package/dist/types/components/form/form.d.ts +12 -1
  32. package/dist/types/components/kanban-view/kanban-card.d.ts +146 -0
  33. package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
  34. package/dist/types/components/launcher/launcher.d.ts +278 -26
  35. package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
  36. package/dist/types/components/menu-button/menu-button.d.ts +37 -42
  37. package/dist/types/components/message/message.d.ts +126 -17
  38. package/dist/types/components/modal/modal.d.ts +61 -2
  39. package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
  40. package/dist/types/components/panel/panel.d.ts +7 -0
  41. package/dist/types/components/search/search.d.ts +7 -0
  42. package/dist/types/components/select/color.d.ts +12 -0
  43. package/dist/types/components/select/glyphs.d.ts +124 -0
  44. package/dist/types/components/select/select.d.ts +15 -3
  45. package/dist/types/components/sort-control/sort-control.d.ts +105 -0
  46. package/dist/types/components/stat-tile/stat-tile.d.ts +288 -0
  47. package/dist/types/components/tabbox/tabbox.d.ts +16 -0
  48. package/dist/types/components/table/table.d.ts +190 -1
  49. package/dist/types/components/table-view/table-view.d.ts +11 -5
  50. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  51. package/dist/types/components/timebox/timebox.d.ts +7 -0
  52. package/dist/types/components/tree/tree.d.ts +13 -0
  53. package/dist/types/components/value-list/value-list.d.ts +8 -0
  54. package/dist/types/components/view/record-view.d.ts +96 -5
  55. package/dist/types/core/breakpoint.d.ts +1 -1
  56. package/dist/types/core/icons.d.ts +37 -22
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +15 -1
  59. package/dist/types/index.d.ts +16 -3
  60. package/dist/types/internal/app-rail.d.ts +10 -0
  61. package/dist/types/internal/menu-items.d.ts +134 -0
  62. package/dist/types/zeyos/connect.d.ts +6 -0
  63. package/dist/types/zeyos/form.d.ts +50 -9
  64. package/dist/types/zeyos/index.d.ts +1 -1
  65. package/dist/types/zeyos/query.d.ts +79 -0
  66. package/dist/types/zeyos/schema.d.ts +24 -1
  67. package/dist/types/zeyos/table.d.ts +100 -14
  68. package/dist/types/zeyos/view.d.ts +10 -0
  69. package/dist/zx-zeyos.esm.js +2206 -489
  70. package/dist/zx-zeyos.esm.min.js +56 -1
  71. package/dist/zx.css +3322 -724
  72. package/dist/zx.esm.js +12244 -5809
  73. package/dist/zx.esm.min.js +58 -3
  74. package/dist/zx.global.js +12246 -5811
  75. package/dist/zx.global.min.js +58 -3
  76. package/dist/zx.min.css +1 -1
  77. package/docs/api.json +1683 -146
  78. package/docs/llms.md +777 -110
  79. package/docs/llms.txt +14 -8
  80. package/package.json +1 -1
  81. package/src/components/account-menu/account-menu.css +35 -10
  82. package/src/components/activity-item/activity-item.css +137 -0
  83. package/src/components/activity-item/activity-item.js +602 -0
  84. package/src/components/activity-list/activity-list.css +99 -0
  85. package/src/components/activity-list/activity-list.js +409 -0
  86. package/src/components/app-icon/app-icon.css +1 -1
  87. package/src/components/app-rail/app-rail.css +191 -22
  88. package/src/components/app-sidebar/app-sidebar.css +349 -24
  89. package/src/components/app-sidebar/app-sidebar.js +86 -11
  90. package/src/components/aurora/aurora.css +5 -3
  91. package/src/components/avatar/avatar.css +32 -7
  92. package/src/components/avatar/avatar.js +72 -8
  93. package/src/components/badge/badge.css +54 -2
  94. package/src/components/badge/badge.js +29 -2
  95. package/src/components/button/button.css +74 -8
  96. package/src/components/button/button.js +44 -1
  97. package/src/components/calendar/calendar.css +46 -46
  98. package/src/components/calendar/calendar.js +33 -3
  99. package/src/components/card/card.css +2 -2
  100. package/src/components/card/card.js +12 -1
  101. package/src/components/card-view/card-view.css +10 -7
  102. package/src/components/card-view/card-view.js +25 -9
  103. package/src/components/card-view/record-card.js +7 -3
  104. package/src/components/chart/chart.css +4 -1
  105. package/src/components/chart/chart.js +12 -0
  106. package/src/components/check-button/check-button.css +10 -2
  107. package/src/components/checklist/checklist.css +9 -2
  108. package/src/components/checklist/checklist.js +11 -5
  109. package/src/components/checklist/field-adapter.js +3 -1
  110. package/src/components/code-editor/code-editor.css +68 -0
  111. package/src/components/code-editor/code-editor.js +306 -0
  112. package/src/components/code-editor/field-adapter.js +34 -0
  113. package/src/components/context-menu/context-menu.css +87 -5
  114. package/src/components/context-menu/context-menu.js +22 -30
  115. package/src/components/copy/copy-button.css +6 -0
  116. package/src/components/copy/copy-input.css +8 -3
  117. package/src/components/data-filter/data-filter.css +5 -4
  118. package/src/components/date-picker/date-picker.css +6 -6
  119. package/src/components/date-picker/date-picker.js +22 -8
  120. package/src/components/date-picker/date-range-picker.css +3 -3
  121. package/src/components/date-picker/month-picker.js +3 -3
  122. package/src/components/date-picker/time-picker.css +1 -1
  123. package/src/components/date-picker/time-picker.js +20 -8
  124. package/src/components/datebox/date-range-box.css +79 -2
  125. package/src/components/datebox/date-range-box.js +13 -2
  126. package/src/components/datebox/datebox.css +79 -2
  127. package/src/components/datebox/datebox.js +13 -2
  128. package/src/components/dialog/dialog.css +26 -0
  129. package/src/components/dialog/dialog.js +174 -70
  130. package/src/components/dropdown/dropdown.css +1 -1
  131. package/src/components/dropdown/dropdown.js +7 -5
  132. package/src/components/empty-state/empty-state.css +2 -2
  133. package/src/components/entity-ref/entity-ref.css +182 -0
  134. package/src/components/entity-ref/entity-ref.js +496 -0
  135. package/src/components/field/field.css +28 -9
  136. package/src/components/field/field.js +68 -26
  137. package/src/components/field-upload/field-upload.css +1 -1
  138. package/src/components/fieldset/fieldset.css +5 -3
  139. package/src/components/fieldset/fieldset.js +28 -5
  140. package/src/components/file-item/file-item.css +88 -0
  141. package/src/components/file-item/file-item.js +505 -0
  142. package/src/components/file-list/file-list.css +51 -0
  143. package/src/components/file-list/file-list.js +320 -0
  144. package/src/components/filter/filter.css +27 -1
  145. package/src/components/filter/filter.js +205 -47
  146. package/src/components/filter-panel/filter-panel.css +104 -0
  147. package/src/components/filter-panel/filter-panel.js +964 -0
  148. package/src/components/finder/finder.css +15 -13
  149. package/src/components/form/form.css +5 -4
  150. package/src/components/form/form.js +11 -2
  151. package/src/components/grid/grid.css +1 -1
  152. package/src/components/groupbox/groupbox.css +1 -1
  153. package/src/components/kanban-view/kanban-card.js +247 -0
  154. package/src/components/kanban-view/kanban-view.css +226 -8
  155. package/src/components/kanban-view/kanban-view.js +255 -12
  156. package/src/components/launcher/launcher.css +107 -25
  157. package/src/components/launcher/launcher.js +557 -63
  158. package/src/components/list-toolbar/list-toolbar.css +194 -0
  159. package/src/components/list-toolbar/list-toolbar.js +851 -0
  160. package/src/components/master-panel/master-panel.css +2 -0
  161. package/src/components/menu-button/menu-button.css +87 -5
  162. package/src/components/menu-button/menu-button.js +86 -46
  163. package/src/components/message/message.css +38 -5
  164. package/src/components/message/message.js +230 -17
  165. package/src/components/modal/modal.css +15 -0
  166. package/src/components/modal/modal.js +159 -3
  167. package/src/components/navigation-bar/navigation-bar.css +95 -11
  168. package/src/components/navigation-bar/navigation-bar.js +171 -4
  169. package/src/components/number-field/field-adapter.js +1 -0
  170. package/src/components/number-field/number-field.css +70 -0
  171. package/src/components/number-field/number-field.js +2 -0
  172. package/src/components/pagination/pagination.css +8 -6
  173. package/src/components/panel/panel.css +1 -1
  174. package/src/components/panel/panel.js +13 -2
  175. package/src/components/rating/rating.css +7 -0
  176. package/src/components/search/search.js +17 -5
  177. package/src/components/select/color.js +22 -0
  178. package/src/components/select/field-adapter.js +6 -1
  179. package/src/components/select/glyphs.js +165 -0
  180. package/src/components/select/select.css +138 -45
  181. package/src/components/select/select.js +86 -47
  182. package/src/components/sheet/sheet.css +11 -8
  183. package/src/components/slider/slider.css +16 -2
  184. package/src/components/slider/slider.js +1 -1
  185. package/src/components/sort-control/sort-control.css +20 -0
  186. package/src/components/sort-control/sort-control.js +140 -0
  187. package/src/components/stat-tile/stat-tile.css +208 -0
  188. package/src/components/stat-tile/stat-tile.js +601 -0
  189. package/src/components/tabbox/tabbox.css +32 -27
  190. package/src/components/tabbox/tabbox.js +17 -2
  191. package/src/components/table/table.css +155 -31
  192. package/src/components/table/table.js +386 -33
  193. package/src/components/table-view/table-view.css +8 -2
  194. package/src/components/table-view/table-view.js +21 -6
  195. package/src/components/tag-picker/tag-picker.css +86 -6
  196. package/src/components/tag-picker/tag-picker.js +25 -8
  197. package/src/components/timebox/timebox.css +22 -2
  198. package/src/components/timebox/timebox.js +18 -5
  199. package/src/components/toggle/toggle.css +12 -1
  200. package/src/components/toolbar/toolbar.js +2 -2
  201. package/src/components/tooltip/tooltip.css +4 -0
  202. package/src/components/tooltip/tooltip.js +10 -3
  203. package/src/components/tree/tree.css +25 -16
  204. package/src/components/tree/tree.js +31 -0
  205. package/src/components/value-list/value-list.css +3 -3
  206. package/src/components/value-list/value-list.js +22 -6
  207. package/src/components/view/record-view.css +5 -0
  208. package/src/components/view/record-view.js +100 -7
  209. package/src/core/breakpoint.js +1 -1
  210. package/src/core/icons.js +259 -57
  211. package/src/core/motion.js +147 -0
  212. package/src/core/overlay-host.js +21 -2
  213. package/src/index.js +30 -3
  214. package/src/internal/app-rail.js +18 -1
  215. package/src/internal/menu-items.js +142 -0
  216. package/src/zeyos/connect.js +6 -2
  217. package/src/zeyos/form.js +67 -27
  218. package/src/zeyos/index.js +1 -1
  219. package/src/zeyos/query.js +153 -0
  220. package/src/zeyos/schema.js +144 -27
  221. package/src/zeyos/table.js +68 -5
  222. package/src/zeyos/view.js +6 -0
  223. package/styles/base.css +56 -0
  224. package/styles/icons.css +11 -1
  225. package/styles/print.css +192 -0
  226. package/styles/tokens/dark.css +113 -37
  227. package/styles/tokens/global.css +121 -27
  228. package/styles/tokens/semantic.css +100 -31
  229. package/styles/tokens/themes.css +7 -6
  230. package/styles/zx.css +11 -1
  231. package/styles/tokens/density.css +0 -9
@@ -29,8 +29,8 @@
29
29
  max-inline-size: 100%;
30
30
  padding-inline: var(--zx-space-2);
31
31
  border: 1px solid var(--zx-color-border);
32
- border-radius: var(--zx-radius-full);
33
- background: var(--zx-color-bg-selected);
32
+ border-radius: var(--zx-radius-md);
33
+ background: var(--zx-color-bg-muted);
34
34
  color: var(--zx-color-text);
35
35
  cursor: grab;
36
36
  }
@@ -50,7 +50,7 @@
50
50
  place-items: center;
51
51
  min-inline-size: 1.25rem;
52
52
  min-block-size: 1.25rem;
53
- border-radius: var(--zx-radius-full);
53
+ border-radius: var(--zx-radius-sm);
54
54
  color: var(--zx-color-text-muted);
55
55
  cursor: pointer;
56
56
  }
@@ -1,5 +1,6 @@
1
1
  import { Component } from '../../core/component.js';
2
2
  import { h } from '../../core/dom.js';
3
+ import { printf } from '../../core/i18n.js';
3
4
  import { uid } from '../../core/util.js';
4
5
 
5
6
  /**
@@ -49,14 +50,14 @@ export class ValueList extends Component {
49
50
  this._list = h('ul', {
50
51
  class: 'zx-value-list__list',
51
52
  role: 'listbox',
52
- ariaLabel: 'Values',
53
+ ariaLabel: this._message('valueList.values', 'Values'),
53
54
  ariaOrientation: 'horizontal'
54
55
  });
55
56
  this._input = h('input', {
56
57
  class: 'zx-value-list__input',
57
58
  type: 'text',
58
59
  placeholder: String(this.options.placeholder ?? ''),
59
- ariaLabel: 'Add value',
60
+ ariaLabel: this._message('valueList.add', 'Add value'),
60
61
  ariaDescribedby: instructionsId
61
62
  });
62
63
  this._status = h('div', {
@@ -67,7 +68,8 @@ export class ValueList extends Component {
67
68
  this._instructions = h('span', {
68
69
  class: 'zx-value-list__instructions',
69
70
  id: instructionsId
70
- }, 'Press Enter to add. Focus a value and press Ctrl plus Left or Right to reorder.');
71
+ }, this._message('valueList.instructions',
72
+ 'Press Enter to add. Focus a value and press Ctrl plus Left or Right to reorder.'));
71
73
  this._ownedNodes.push(this._list, this._input, this._status, this._instructions);
72
74
  root.append(this._list, this._input, this._status, this._instructions);
73
75
 
@@ -120,13 +122,14 @@ export class ValueList extends Component {
120
122
  const normalized = String(value ?? '').trim();
121
123
  if (!normalized) return false;
122
124
  if (this.options.unique && this._values.includes(normalized)) {
123
- this._showError('This value already exists.');
125
+ this._showError(this._message('valueList.duplicate', 'This value already exists.'));
124
126
  return false;
125
127
  }
126
128
  if (typeof this.options.validate === 'function') {
127
129
  const result = this.options.validate(normalized);
128
130
  if (result !== true) {
129
- this._showError(typeof result === 'string' ? result : 'This value is not valid.');
131
+ this._showError(typeof result === 'string' ? result
132
+ : this._message('valueList.invalid', 'This value is not valid.'));
130
133
  return false;
131
134
  }
132
135
  }
@@ -196,7 +199,8 @@ export class ValueList extends Component {
196
199
  draggable: Boolean(this.options.sortable) && !this._disabled,
197
200
  ariaSelected: 'false',
198
201
  ariaDisabled: String(this._disabled),
199
- ariaLabel: this.options.deletable ? `${value}. Press Delete to remove.` : value,
202
+ ariaLabel: this.options.deletable
203
+ ? this._message('valueList.removable', '%1. Press Delete to remove.', value) : value,
200
204
  dataset: { index }
201
205
  }, h('span', { class: 'zx-value-list__value' }, value), this.options.deletable ? h('span', {
202
206
  class: 'zx-value-list__remove',
@@ -302,6 +306,18 @@ export class ValueList extends Component {
302
306
  this._input.setAttribute('aria-invalid', 'true');
303
307
  }
304
308
 
309
+ /**
310
+ * Resolves a message through the host translator, falling back to the built-in English text.
311
+ * @param {string} key Message key.
312
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
313
+ * @param {...unknown} args Interpolation values.
314
+ * @returns {string}
315
+ */
316
+ _message(key, fallback, ...args) {
317
+ const message = this.msg(key, ...args);
318
+ return message === key ? printf(fallback, args) : message;
319
+ }
320
+
305
321
  /** @returns {void} */
306
322
  destroy() {
307
323
  for (const node of this._ownedNodes ?? []) node.remove();
@@ -17,6 +17,11 @@
17
17
  margin-inline-start: auto;
18
18
  }
19
19
 
20
+ /* Mounted into a host toolbar the view does not own: the host decides where it sits. */
21
+ .zx-record-view__field-controls[data-mounted="external"] {
22
+ margin-inline-start: 0;
23
+ }
24
+
20
25
  .zx-record-view__field-controls > summary {
21
26
  display: inline-flex;
22
27
  align-items: center;
@@ -1,7 +1,8 @@
1
1
  // @ts-check
2
2
  import { Component } from '../../core/component.js';
3
- import { h, restoreTarget, snapshotTarget } from '../../core/dom.js';
3
+ import { h, resolveElement, restoreTarget, snapshotTarget } from '../../core/dom.js';
4
4
  import { icon } from '../../core/icons.js';
5
+ import { printf } from '../../core/i18n.js';
5
6
  import { sortRows } from '../table/sort.js';
6
7
 
7
8
  /** @typedef {Record<string, any>} ViewRecord */
@@ -31,6 +32,23 @@ import { sortRows } from '../table/sort.js';
31
32
  * @property {ViewSortDirection} dir Sort direction.
32
33
  */
33
34
 
35
+ /**
36
+ * Trigger customisation for the shared field chooser. Passing this object in place of `true` keeps
37
+ * the control and its state model exactly as they are; only its wording and mount point change.
38
+ * @typedef {Object} ViewFieldControlsConfig
39
+ * @property {string} [label] Trigger label. Omitted resolves `recordView.fields`, English `Fields`.
40
+ * @property {Element|string|null} [target=null] Element or selector the disclosure is mounted into
41
+ * instead of the view's own toolbar. An unresolvable target falls back to the view.
42
+ */
43
+
44
+ /**
45
+ * @typedef {Object} ViewFieldControlsSettings
46
+ * @property {boolean} enabled Whether a concrete view renders the chooser at all.
47
+ * @property {boolean} custom Whether the option was given as a `ViewFieldControlsConfig` object.
48
+ * @property {string|null} label Configured trigger label, or null to resolve the message.
49
+ * @property {Element|string|null} target Configured mount point, before resolution.
50
+ */
51
+
34
52
  /**
35
53
  * @typedef {Object} RecordViewState
36
54
  * @property {1} version State schema version.
@@ -50,7 +68,10 @@ import { sortRows } from '../table/sort.js';
50
68
  * @property {unknown[]} [selection=[]] Initially selected record ids.
51
69
  * @property {string[]} [fieldOrder=[]] Field ids in preferred order.
52
70
  * @property {string[]} [hiddenFields=[]] Initially hidden field ids.
53
- * @property {boolean} [fieldControls=true] Whether concrete views show the shared field chooser.
71
+ * @property {boolean|ViewFieldControlsConfig} [fieldControls=true] Whether concrete views show the
72
+ * shared field chooser. `true` renders it in the view's own toolbar and `false` omits it; a
73
+ * `ViewFieldControlsConfig` object renders the same control with a translated `label` and, with
74
+ * `target`, mounts it into a host element such as a list toolbar.
54
75
  * @property {string|Node|(() => string|Node)|null} [emptyText=null] Empty-result content.
55
76
  * @property {(event: CustomEvent<Record<string, unknown>>) => void} [onrecordclick]
56
77
  * @property {(event: CustomEvent<Record<string, unknown>>) => void} [onrecorddblclick]
@@ -117,6 +138,7 @@ export class RecordView extends Component {
117
138
  this._recordViewDestroyed = true;
118
139
  const root = this.el;
119
140
  const snapshot = this._recordViewOriginal;
141
+ this._removeViewFieldControls();
120
142
  super.destroy();
121
143
  if (snapshot && root) restoreTarget(root, snapshot);
122
144
  }
@@ -139,6 +161,9 @@ export class RecordView extends Component {
139
161
  this._viewSelectionAnchor = null;
140
162
  this._viewFieldControls = null;
141
163
  this._viewFieldList = null;
164
+ this._viewFieldSettings = normalizeViewFieldControls(this.options.fieldControls);
165
+ // Resolved once, here, so the mount point cannot drift between render and destroy.
166
+ this._viewFieldControlsTarget = resolveElement(this._viewFieldSettings.target);
142
167
  this._viewInitialized = true;
143
168
  if (this._viewSort && this.options.sortMode === 'local') this._sortViewData();
144
169
  this._setInitialSelection(this.options.selection);
@@ -467,13 +492,15 @@ export class RecordView extends Component {
467
492
  /**
468
493
  * Builds the shared show/hide and ordering disclosure for a concrete view toolbar.
469
494
  * @protected
470
- * @param {string} [label='Fields'] Trigger label.
495
+ * @param {string|null} [label=null] Explicit trigger label. Omitted resolves the configured
496
+ * `fieldControls.label` and then the `recordView.fields` message.
471
497
  * @returns {HTMLElement} Disclosure element.
472
498
  */
473
- _createViewFieldControls(label = 'Fields') {
499
+ _createViewFieldControls(label = null) {
500
+ const text = label ?? this._viewFieldControlsLabel();
474
501
  const list = h('div', { class: 'zx-record-view__field-list' });
475
502
  const details = h('details', { class: 'zx-record-view__field-controls' },
476
- h('summary', { class: 'zx-button zx-button--sm' }, icon('fields', { size: 13 }), label),
503
+ h('summary', { class: 'zx-btn', dataset: { size: 'sm', kind: 'ghost' } }, icon('fields'), text),
477
504
  list);
478
505
  this._viewFieldControls = details;
479
506
  this._viewFieldList = list;
@@ -498,6 +525,58 @@ export class RecordView extends Component {
498
525
  return details;
499
526
  }
500
527
 
528
+ /**
529
+ * Builds the shared field disclosure and mounts it where `fieldControls` asks for it: the host
530
+ * element named by `target`, or the view's own toolbar when no target resolves.
531
+ * @protected
532
+ * @param {Element} toolbar The view's own toolbar.
533
+ * @param {string|null} [label=null] Explicit trigger label, for views that own their wording.
534
+ * @returns {HTMLElement} The mounted disclosure.
535
+ */
536
+ _mountViewFieldControls(toolbar, label = null) {
537
+ const controls = this._createViewFieldControls(label);
538
+ const target = this._viewFieldControlsTarget;
539
+ if (target) controls.dataset.mounted = 'external';
540
+ (target ?? toolbar).append(controls);
541
+ return controls;
542
+ }
543
+
544
+ /**
545
+ * Removes the shared field disclosure from wherever it was mounted. An external `target` keeps
546
+ * every node it had before the view was created.
547
+ * @protected
548
+ * @returns {void}
549
+ */
550
+ _removeViewFieldControls() {
551
+ this._viewFieldControls?.remove?.();
552
+ this._viewFieldControls = null;
553
+ this._viewFieldList = null;
554
+ this._viewFieldControlsTarget = null;
555
+ }
556
+
557
+ /**
558
+ * Resolves the field-chooser trigger label: the configured one, else the translated message.
559
+ * @protected
560
+ * @returns {string} Trigger label.
561
+ */
562
+ _viewFieldControlsLabel() {
563
+ const settings = this._viewFieldSettings ?? normalizeViewFieldControls(this.options.fieldControls);
564
+ return settings.label ?? this._message('recordView.fields', 'Fields');
565
+ }
566
+
567
+ /**
568
+ * Resolves a message through the host translator, falling back to the built-in English text.
569
+ * @protected
570
+ * @param {string} key Message key.
571
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
572
+ * @param {...unknown} args Interpolation values.
573
+ * @returns {string} Translated or built-in text.
574
+ */
575
+ _message(key, fallback, ...args) {
576
+ const message = this.msg(key, ...args);
577
+ return message === key ? printf(fallback, args) : message;
578
+ }
579
+
501
580
  /** Concrete renderers override this hook. @protected @param {string} _reason Refresh reason. @returns {void} */
502
581
  _refreshView(_reason) {}
503
582
 
@@ -567,14 +646,14 @@ export class RecordView extends Component {
567
646
  const up = h('button', {
568
647
  class: 'zx-record-view__field-move',
569
648
  type: 'button',
570
- ariaLabel: `Move ${field.label} up`,
649
+ ariaLabel: this._message('recordView.moveFieldUp', 'Move %1 up', field.label),
571
650
  disabled: index === 0,
572
651
  dataset: { viewFieldAction: 'up', fieldId: field.id }
573
652
  }, icon('chevron-up', { size: 11 }));
574
653
  const down = h('button', {
575
654
  class: 'zx-record-view__field-move',
576
655
  type: 'button',
577
- ariaLabel: `Move ${field.label} down`,
656
+ ariaLabel: this._message('recordView.moveFieldDown', 'Move %1 down', field.label),
578
657
  disabled: index === fields.length - 1,
579
658
  dataset: { viewFieldAction: 'down', fieldId: field.id }
580
659
  }, icon('chevron-down', { size: 11 }));
@@ -593,6 +672,20 @@ export class RecordView extends Component {
593
672
  }
594
673
  }
595
674
 
675
+ /**
676
+ * Reads the `fieldControls` option in its three shapes. `true` and `false` keep their exact
677
+ * meaning; an object additionally carries the trigger label and the host element to mount into.
678
+ * @param {boolean|ViewFieldControlsConfig|null|undefined} value Raw option value.
679
+ * @returns {ViewFieldControlsSettings} Normalized settings.
680
+ */
681
+ export function normalizeViewFieldControls(value) {
682
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
683
+ const label = value.label == null ? null : String(value.label);
684
+ return { enabled: true, custom: true, label, target: value.target ?? null };
685
+ }
686
+ return { enabled: Boolean(value), custom: false, label: null, target: null };
687
+ }
688
+
596
689
  /**
597
690
  * Validates and clones field descriptors.
598
691
  * @param {ViewField[]} fields Fields.
@@ -6,7 +6,7 @@
6
6
  * used inside a media query, so a `--zx-bp-*` token would be a value no stylesheet could read.
7
7
  * Zx's own layout utilities avoid the problem entirely — `.zx-grid` reflows on intrinsic track
8
8
  * widths, not on breakpoints — which leaves this module for the cases only script can decide:
9
- * rendering a Table as cards below a width, collapsing a MasterPanel, choosing a chart density.
9
+ * rendering a Table as cards below a width, collapsing a MasterPanel, choosing how many labels a chart shows.
10
10
  * @module zx/core/breakpoint
11
11
  */
12
12