@zeyos/zx 5.0.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 (154) hide show
  1. package/CHANGELOG.md +164 -0
  2. package/README.md +2 -1
  3. package/dist/types/components/account-menu/account-menu.d.ts +8 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +9 -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 +288 -14
  7. package/dist/types/components/calendar/calendar.d.ts +67 -5
  8. package/dist/types/components/card-view/card-view.d.ts +7 -4
  9. package/dist/types/components/card-view/record-card.d.ts +11 -0
  10. package/dist/types/components/context-menu/context-menu.d.ts +6 -2
  11. package/dist/types/components/data-filter/data-filter.d.ts +4 -4
  12. package/dist/types/components/dropdown/dropdown.d.ts +22 -7
  13. package/dist/types/components/field/field.d.ts +30 -0
  14. package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
  15. package/dist/types/components/fieldset/fieldset.d.ts +7 -0
  16. package/dist/types/components/file-list/file-list.d.ts +35 -3
  17. package/dist/types/components/filter/filter.d.ts +127 -3
  18. package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
  19. package/dist/types/components/launcher/launcher.d.ts +8 -0
  20. package/dist/types/components/list-toolbar/list-toolbar.d.ts +15 -6
  21. package/dist/types/components/menu-button/menu-button.d.ts +6 -0
  22. package/dist/types/components/message/message.d.ts +14 -1
  23. package/dist/types/components/modal/modal.d.ts +30 -0
  24. package/dist/types/components/module-bar/module-bar.d.ts +395 -0
  25. package/dist/types/components/pagination/pagination.d.ts +17 -3
  26. package/dist/types/components/select/select.d.ts +6 -0
  27. package/dist/types/components/sheet/sheet.d.ts +15 -0
  28. package/dist/types/components/table/table.d.ts +265 -17
  29. package/dist/types/components/view/record-view.d.ts +59 -2
  30. package/dist/types/components/view-settings/view-settings.d.ts +879 -0
  31. package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
  32. package/dist/types/core/component.d.ts +12 -2
  33. package/dist/types/core/i18n.d.ts +27 -3
  34. package/dist/types/core/keyboard.d.ts +98 -1
  35. package/dist/types/core/messages.d.ts +382 -0
  36. package/dist/types/core/overlay-host.d.ts +28 -1
  37. package/dist/types/core/overlay-stack.d.ts +260 -0
  38. package/dist/types/index.d.ts +5 -2
  39. package/dist/types/internal/app-rail.d.ts +38 -0
  40. package/dist/types/internal/menu-items.d.ts +71 -3
  41. package/dist/types/zeyos/index.d.ts +1 -0
  42. package/dist/types/zeyos/launcher.d.ts +9 -0
  43. package/dist/types/zeyos/module-bar.d.ts +176 -0
  44. package/dist/types/zeyos/query.d.ts +31 -5
  45. package/dist/types/zeyos/table.d.ts +245 -8
  46. package/dist/types/zeyos/view.d.ts +16 -2
  47. package/dist/zx-zeyos.esm.js +4422 -430
  48. package/dist/zx-zeyos.esm.min.js +5 -5
  49. package/dist/zx.css +1417 -134
  50. package/dist/zx.esm.js +6986 -830
  51. package/dist/zx.esm.min.js +8 -8
  52. package/dist/zx.global.js +6986 -830
  53. package/dist/zx.global.min.js +8 -8
  54. package/dist/zx.min.css +1 -1
  55. package/docs/api.json +794 -17
  56. package/docs/llms.md +503 -63
  57. package/docs/llms.txt +6 -2
  58. package/package.json +1 -1
  59. package/src/components/account-menu/account-menu.js +12 -0
  60. package/src/components/activity-list/activity-list.js +27 -1
  61. package/src/components/app-icon/app-icon.css +13 -2
  62. package/src/components/app-icon/app-icon.js +11 -1
  63. package/src/components/app-rail/app-rail.css +19 -0
  64. package/src/components/app-sidebar/app-sidebar.css +204 -3
  65. package/src/components/app-sidebar/app-sidebar.js +646 -40
  66. package/src/components/breadcrumb/breadcrumb.css +1 -1
  67. package/src/components/calendar/calendar.css +1 -1
  68. package/src/components/calendar/calendar.js +26 -52
  69. package/src/components/card/card.css +2 -2
  70. package/src/components/card-view/card-view.css +5 -5
  71. package/src/components/card-view/card-view.js +13 -9
  72. package/src/components/card-view/record-card.js +14 -3
  73. package/src/components/code-editor/code-editor.css +1 -1
  74. package/src/components/code-editor/field-adapter.js +9 -2
  75. package/src/components/context-menu/context-menu.js +8 -2
  76. package/src/components/copy/copy-input.css +1 -1
  77. package/src/components/data-filter/data-filter.js +2 -2
  78. package/src/components/date-picker/time-picker.css +2 -2
  79. package/src/components/dock/dock.css +5 -4
  80. package/src/components/dropdown/dropdown.js +157 -72
  81. package/src/components/field/field.js +33 -3
  82. package/src/components/fieldset/external-adapter.js +363 -0
  83. package/src/components/fieldset/fieldset.js +10 -59
  84. package/src/components/file-list/file-list.js +144 -21
  85. package/src/components/filter/filter.css +86 -2
  86. package/src/components/filter/filter.js +300 -8
  87. package/src/components/filter-panel/filter-panel.js +149 -32
  88. package/src/components/finder/finder.css +1 -1
  89. package/src/components/kanban-view/kanban-view.css +3 -3
  90. package/src/components/kanban-view/kanban-view.js +5 -2
  91. package/src/components/launcher/launcher.css +1 -1
  92. package/src/components/launcher/launcher.js +29 -3
  93. package/src/components/list-toolbar/list-toolbar.js +39 -13
  94. package/src/components/master-panel/master-panel.js +0 -1
  95. package/src/components/menu-button/menu-button.css +63 -0
  96. package/src/components/menu-button/menu-button.js +97 -11
  97. package/src/components/message/message.js +60 -20
  98. package/src/components/modal/modal.css +1 -1
  99. package/src/components/modal/modal.js +66 -13
  100. package/src/components/module-bar/module-bar.css +486 -0
  101. package/src/components/module-bar/module-bar.js +1074 -0
  102. package/src/components/multi-value-editor/multi-value-editor.css +1 -1
  103. package/src/components/number-field/field-adapter.js +2 -1
  104. package/src/components/pagination/pagination.css +5 -5
  105. package/src/components/pagination/pagination.js +20 -4
  106. package/src/components/panel/panel.css +3 -2
  107. package/src/components/questionnaire/questionnaire.css +4 -4
  108. package/src/components/rating/field-adapter.js +2 -1
  109. package/src/components/rating/rating.css +1 -1
  110. package/src/components/search/search.css +4 -1
  111. package/src/components/select/select.js +9 -0
  112. package/src/components/sheet/sheet.css +1 -1
  113. package/src/components/sheet/sheet.js +74 -32
  114. package/src/components/slider/field-adapter.js +6 -1
  115. package/src/components/slider/slider.css +2 -2
  116. package/src/components/split-view/split-view.css +1 -1
  117. package/src/components/stat-tile/stat-tile.css +1 -1
  118. package/src/components/stepper/stepper.css +1 -1
  119. package/src/components/tabbox/tabbox.css +9 -5
  120. package/src/components/table/table.css +86 -11
  121. package/src/components/table/table.js +628 -57
  122. package/src/components/tag-picker/field-adapter.js +2 -1
  123. package/src/components/tooltip/tooltip.js +35 -17
  124. package/src/components/tree/tree.css +1 -1
  125. package/src/components/view/record-view.css +1 -1
  126. package/src/components/view/record-view.js +178 -18
  127. package/src/components/view-settings/view-settings.css +285 -0
  128. package/src/components/view-settings/view-settings.js +1830 -0
  129. package/src/components/workspace-switcher/workspace-switcher.css +188 -0
  130. package/src/components/workspace-switcher/workspace-switcher.js +463 -0
  131. package/src/core/component.js +88 -17
  132. package/src/core/fontawesome.js +5 -1
  133. package/src/core/i18n.js +47 -4
  134. package/src/core/icons.js +12 -2
  135. package/src/core/keyboard.js +206 -11
  136. package/src/core/messages.js +427 -0
  137. package/src/core/overlay-host.js +64 -9
  138. package/src/core/overlay-stack.js +487 -0
  139. package/src/core/position.js +22 -3
  140. package/src/index.js +5 -2
  141. package/src/internal/app-rail.js +134 -53
  142. package/src/internal/menu-items.js +61 -3
  143. package/src/zeyos/form.js +5 -3
  144. package/src/zeyos/index.js +3 -0
  145. package/src/zeyos/launcher.js +8 -3
  146. package/src/zeyos/module-bar.js +181 -0
  147. package/src/zeyos/query.js +44 -13
  148. package/src/zeyos/table.js +220 -80
  149. package/src/zeyos/view.js +43 -93
  150. package/styles/base.css +16 -1
  151. package/styles/print.css +1 -1
  152. package/styles/tokens/dark.css +4 -0
  153. package/styles/tokens/semantic.css +56 -28
  154. package/styles/zx.css +3 -0
@@ -58,7 +58,7 @@
58
58
  }
59
59
 
60
60
  .zx-breadcrumb__link:focus-visible {
61
- outline: 0;
61
+ outline-color: transparent;
62
62
  box-shadow: var(--zx-focus-ring);
63
63
  }
64
64
 
@@ -118,7 +118,7 @@
118
118
  .zx-calendar__more:focus-visible,
119
119
  .zx-calendar__month-week-number:focus-visible {
120
120
  z-index: 4;
121
- outline: none;
121
+ outline-color: transparent;
122
122
  box-shadow: var(--zx-focus-ring);
123
123
  }
124
124
 
@@ -47,7 +47,9 @@ import {
47
47
  * @property {CalendarReader} [eventEditable='editable'] Per-event move reader.
48
48
  * @property {CalendarReader} [eventDurationEditable='durationEditable'] Per-event resize reader.
49
49
  * @property {'milliseconds'|'seconds'} [dateUnit='milliseconds'] Unit for numeric event dates.
50
- * @property {Record<string, string>} [msg] Localized component messages.
50
+ * @property {Record<string, string>} [msg] Per-instance messages, keyed `calendar.*` (`calendar.today`,
51
+ * `calendar.next`, …). The bare keys of earlier versions (`today`, `next`) are still read, after the
52
+ * namespaced key, until the next major version.
51
53
  * @property {(event: CalendarEvent) => Node|string|null} [renderEvent] Optional safe event-content renderer.
52
54
  * @property {(event: CustomEvent<{events: CalendarEvent[]}>) => void} [oneventschange] Programmatic event-data listener.
53
55
  * @property {(event: CustomEvent<{view: CalendarView, oldView: CalendarView}>) => void} [onviewchange] View listener.
@@ -73,31 +75,6 @@ import {
73
75
 
74
76
  /** @typedef {{silent?: boolean}} CalendarSetOptions */
75
77
 
76
- const DEFAULT_MESSAGES = {
77
- today: 'Today',
78
- new: 'New',
79
- agenda: 'Agenda',
80
- day: 'Day',
81
- week: 'Week',
82
- month: 'Month',
83
- year: 'Year',
84
- previous: 'Previous period',
85
- next: 'Next period',
86
- allDay: 'All day',
87
- noEvents: 'No events',
88
- more: '+%1 more',
89
- loading: 'Loading calendar…',
90
- move: 'Move %1',
91
- resize: 'Resize %1',
92
- moveStarted: 'Moving %1. Use arrow keys, Space to drop, or Escape to cancel.',
93
- resizeStarted: 'Resizing %1. Use arrow keys, Space to drop, or Escape to cancel.',
94
- editPreview: '%1, %2 to %3',
95
- editCancelled: 'Calendar edit cancelled.',
96
- editCommitted: '%1 updated.',
97
- calendar: 'Calendar',
98
- weekNumber: 'Week %1'
99
- };
100
-
101
78
  /**
102
79
  * Accessible, dependency-free agenda/day/week/month/year scheduling surface.
103
80
  * @fires Calendar#viewchange
@@ -147,8 +124,7 @@ export class Calendar extends Component {
147
124
  eventLocation: 'location',
148
125
  eventEditable: 'editable',
149
126
  eventDurationEditable: 'durationEditable',
150
- dateUnit: 'milliseconds',
151
- msg: DEFAULT_MESSAGES
127
+ dateUnit: 'milliseconds'
152
128
  };
153
129
 
154
130
  /** @returns {HTMLElement} */
@@ -178,9 +154,9 @@ export class Calendar extends Component {
178
154
  this._nowTimer = null;
179
155
 
180
156
  const navigation = h('div', { class: 'zx-calendar__navigation' },
181
- this._toolbarButton('previous', this.msg('previous'), 'chevron-left'),
182
- this._toolbarButton('today', this.msg('today')),
183
- this._toolbarButton('next', this.msg('next'), 'chevron-right'));
157
+ this._toolbarButton('previous', this.msg('calendar.previous'), 'chevron-left'),
158
+ this._toolbarButton('today', this.msg('calendar.today')),
159
+ this._toolbarButton('next', this.msg('calendar.next'), 'chevron-right'));
184
160
  const heading = h('h2', {
185
161
  ref: 'heading',
186
162
  class: 'zx-calendar__heading',
@@ -196,14 +172,14 @@ export class Calendar extends Component {
196
172
  class: 'zx-calendar__view-button',
197
173
  dataset: { calendarAction: 'view', view },
198
174
  disabled: this.options.disabled
199
- }, this.msg(view))));
175
+ }, this.msg(`calendar.${view}`))));
200
176
  const commands = h('div', { class: 'zx-calendar__commands' }, views,
201
177
  h('button', {
202
178
  type: 'button',
203
179
  class: 'zx-calendar__new',
204
180
  dataset: { calendarAction: 'new' },
205
181
  disabled: this.options.disabled
206
- }, icon('plus'), h('span', {}, this.msg('new'))));
182
+ }, icon('plus'), h('span', {}, this.msg('calendar.new'))));
207
183
  const toolbar = h('header', { class: 'zx-calendar__toolbar' }, navigation, heading, commands);
208
184
  const body = h('div', { ref: 'body', class: 'zx-calendar__body' });
209
185
  const loading = h('div', {
@@ -211,7 +187,7 @@ export class Calendar extends Component {
211
187
  class: 'zx-calendar__loading',
212
188
  role: 'status',
213
189
  hidden: true
214
- }, this.msg('loading'));
190
+ }, this.msg('calendar.loading'));
215
191
  const status = h('div', {
216
192
  ref: 'status',
217
193
  class: 'zx-calendar__status',
@@ -220,7 +196,7 @@ export class Calendar extends Component {
220
196
  ariaAtomic: 'true'
221
197
  });
222
198
  root.replaceChildren(toolbar, body, loading, status);
223
- root.setAttribute('aria-label', this.msg('calendar'));
199
+ root.setAttribute('aria-label', this.msg('calendar.label'));
224
200
  root.dataset.view = this._view;
225
201
  root.dataset.state = 'ready';
226
202
 
@@ -470,7 +446,7 @@ export class Calendar extends Component {
470
446
  }, this._format(day, { weekday: 'long', day: 'numeric', month: 'long' })));
471
447
  const entries = h('div', { class: 'zx-calendar__agenda-events', role: 'gridcell' });
472
448
  if (!events.length) {
473
- entries.append(h('p', { class: 'zx-calendar__agenda-empty' }, this.msg('noEvents')));
449
+ entries.append(h('p', { class: 'zx-calendar__agenda-empty' }, this.msg('calendar.noEvents')));
474
450
  } else {
475
451
  for (const event of events) entries.append(this._eventElement(event, { variant: 'agenda', day }));
476
452
  }
@@ -576,7 +552,7 @@ export class Calendar extends Component {
576
552
  /** @param {Date[]} days @returns {HTMLElement} */
577
553
  _renderAllDayRail(days) {
578
554
  const rail = h('div', { class: 'zx-calendar__all-day' },
579
- h('span', { class: 'zx-calendar__all-day-label' }, this.msg('allDay')));
555
+ h('span', { class: 'zx-calendar__all-day-label' }, this.msg('calendar.allDay')));
580
556
  const layer = h('div', {
581
557
  class: 'zx-calendar__all-day-grid',
582
558
  role: 'row',
@@ -625,7 +601,7 @@ export class Calendar extends Component {
625
601
  type: 'button',
626
602
  class: 'zx-calendar__month-week-number',
627
603
  dataset: { calendarAction: 'week', date: calendarDayKey(start) },
628
- ariaLabel: this.msg('weekNumber', isoWeek(start)),
604
+ ariaLabel: this.msg('calendar.weekNumber', isoWeek(start)),
629
605
  disabled: this.options.disabled
630
606
  }, String(isoWeek(start))));
631
607
  const days = h('div', { class: 'zx-calendar__month-days' });
@@ -666,7 +642,7 @@ export class Calendar extends Component {
666
642
  dataset: { calendarAction: 'more', date: calendarDayKey(day), count: String(count) },
667
643
  style: { gridColumn: String(index + 1), gridRow: String(eventLimit + 1) },
668
644
  disabled: this.options.disabled
669
- }, this.msg('more', count)));
645
+ }, this.msg('calendar.more', count)));
670
646
  }
671
647
  row.append(days, lanes);
672
648
  wrapper.append(row);
@@ -758,7 +734,7 @@ export class Calendar extends Component {
758
734
  type: 'button',
759
735
  class: 'zx-calendar__event-resize',
760
736
  dataset: { eventAction: 'resize', eventId: key },
761
- ariaLabel: this.msg('resize', event.title),
737
+ ariaLabel: this.msg('calendar.resize', event.title),
762
738
  disabled: this.options.disabled
763
739
  }, h('span', { ariaHidden: 'true' })));
764
740
  }
@@ -886,7 +862,7 @@ export class Calendar extends Component {
886
862
  };
887
863
  control.setAttribute('aria-grabbed', 'true');
888
864
  control.closest('.zx-calendar__event')?.setAttribute('data-state', 'grabbed');
889
- this._announce(this.msg(resize ? 'resizeStarted' : 'moveStarted', item.title));
865
+ this._announce(this.msg(resize ? 'calendar.resizeStarted' : 'calendar.moveStarted', item.title));
890
866
  return;
891
867
  }
892
868
  if (!this._keyboardEdit || String(this._keyboardEdit.event.id) !== String(item.id)) return;
@@ -919,7 +895,7 @@ export class Calendar extends Component {
919
895
  if (proposed.end > proposed.start) edit.event = proposed;
920
896
  }
921
897
  edit.control.closest('.zx-calendar__event')?.setAttribute('data-preview', this._eventTime(edit.event, edit.event.start));
922
- this._announce(this.msg('editPreview', edit.event.title,
898
+ this._announce(this.msg('calendar.editPreview', edit.event.title,
923
899
  this._dateTimeLabel(edit.event.start), this._dateTimeLabel(edit.event.end)));
924
900
  }
925
901
 
@@ -1065,7 +1041,7 @@ export class Calendar extends Component {
1065
1041
  }
1066
1042
  wrapper.dataset.preview = this._eventTime(proposed, proposed.start);
1067
1043
  }
1068
- this._announce(this.msg('editPreview', proposed.title,
1044
+ this._announce(this.msg('calendar.editPreview', proposed.title,
1069
1045
  this._dateTimeLabel(proposed.start), this._dateTimeLabel(proposed.end)));
1070
1046
  }
1071
1047
 
@@ -1255,7 +1231,7 @@ export class Calendar extends Component {
1255
1231
  edit.control.removeAttribute('aria-grabbed');
1256
1232
  edit.control.closest('.zx-calendar__event')?.removeAttribute('data-state');
1257
1233
  this._keyboardEdit = null;
1258
- this._announce(this.msg('editCancelled'));
1234
+ this._announce(this.msg('calendar.editCancelled'));
1259
1235
  this._renderView({ preserveFocus: true });
1260
1236
  }
1261
1237
 
@@ -1286,7 +1262,7 @@ export class Calendar extends Component {
1286
1262
  this._replaceNormalizedEvent(oldEvent);
1287
1263
  this._mutationVersions.delete(key);
1288
1264
  this._renderView({ preserveFocus: true });
1289
- this._announce(this.msg('editCancelled'));
1265
+ this._announce(this.msg('calendar.editCancelled'));
1290
1266
  return true;
1291
1267
  };
1292
1268
  const detail = {
@@ -1299,7 +1275,7 @@ export class Calendar extends Component {
1299
1275
  };
1300
1276
  const emitted = this.emit('eventchange', detail);
1301
1277
  if (emitted.defaultPrevented) revert();
1302
- else this._announce(this.msg('editCommitted', proposed.title));
1278
+ else this._announce(this.msg('calendar.editCommitted', proposed.title));
1303
1279
  }
1304
1280
 
1305
1281
  /** @param {CalendarEvent} event @returns {void} */
@@ -1364,11 +1340,11 @@ export class Calendar extends Component {
1364
1340
 
1365
1341
  /** @param {CalendarEvent} event @param {Date} day @returns {string} */
1366
1342
  _eventTime(event, day) {
1367
- if (event.allDay) return this.msg('allDay');
1343
+ if (event.allDay) return this.msg('calendar.allDay');
1368
1344
  if (calendarEventSpansDays(event)) {
1369
1345
  if (sameCalendarDay(event.start, day)) return `${formatDateTime(event.start, this.options.locale)} →`;
1370
1346
  if (sameCalendarDay(event.end, day)) return `→ ${formatDateTime(event.end, this.options.locale)}`;
1371
- return this.msg('allDay');
1347
+ return this.msg('calendar.allDay');
1372
1348
  }
1373
1349
  const start = formatTimeDate(event.start, this.options.locale);
1374
1350
  if (event.end <= event.start) return start;
@@ -1490,10 +1466,8 @@ export class Calendar extends Component {
1490
1466
  }
1491
1467
 
1492
1468
  /**
1493
- * Resolves a message through the host translator, falling back to the built-in English text.
1494
- *
1495
- * Distinct from `msg()`: the built-in `msg` option answers the calendar's own vocabulary, so a
1496
- * dotted key falls past it to the host translator, which is what these strings never had.
1469
+ * Resolves a message through `msg()`, falling back to the built-in English text. Redundant now
1470
+ * that `msg()` ends in the default catalogue; kept until the private copies are removed.
1497
1471
  * @param {string} key Message key.
1498
1472
  * @param {string} fallback Built-in text, with `%1`-style placeholders.
1499
1473
  * @param {...unknown} args Interpolation values.
@@ -51,7 +51,7 @@
51
51
  }
52
52
 
53
53
  .zx-card:has(.zx-card__primary:focus-visible) {
54
- outline: 0;
54
+ outline-color: transparent;
55
55
  box-shadow: var(--zx-focus-ring);
56
56
  }
57
57
 
@@ -129,7 +129,7 @@
129
129
  }
130
130
 
131
131
  .zx-card__primary:focus-visible {
132
- outline: 0;
132
+ outline-color: transparent;
133
133
  }
134
134
 
135
135
  .zx-card__actions,
@@ -38,7 +38,7 @@
38
38
  }
39
39
 
40
40
  .zx-card-view__sort select:focus-visible {
41
- outline: none;
41
+ outline-color: transparent;
42
42
  box-shadow: var(--zx-focus-ring);
43
43
  }
44
44
 
@@ -103,7 +103,7 @@
103
103
  }
104
104
 
105
105
  .zx-record-card:focus-visible {
106
- outline: none;
106
+ outline-color: transparent;
107
107
  box-shadow: var(--zx-focus-ring);
108
108
  }
109
109
 
@@ -179,7 +179,7 @@
179
179
  }
180
180
 
181
181
  .zx-record-card__selection input:focus-visible {
182
- outline: none;
182
+ outline-color: transparent;
183
183
  box-shadow: var(--zx-focus-ring);
184
184
  }
185
185
 
@@ -246,7 +246,7 @@
246
246
 
247
247
  .zx-record-card__primary:focus-visible {
248
248
  border-radius: var(--zx-radius-sm);
249
- outline: none;
249
+ outline-color: transparent;
250
250
  box-shadow: var(--zx-focus-ring);
251
251
  }
252
252
 
@@ -291,7 +291,7 @@
291
291
  }
292
292
 
293
293
  .zx-record-card__action:focus-visible {
294
- outline: none;
294
+ outline-color: transparent;
295
295
  box-shadow: var(--zx-focus-ring);
296
296
  }
297
297
 
@@ -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: {
@@ -45,7 +45,7 @@
45
45
  padding: var(--zx-space-3);
46
46
  border: 0;
47
47
  border-radius: 0;
48
- outline: 0;
48
+ outline-color: transparent;
49
49
  background: transparent;
50
50
  box-shadow: none;
51
51
  color: var(--zx-color-text);
@@ -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
  }
@@ -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;
@@ -54,7 +54,7 @@
54
54
  }
55
55
 
56
56
  .zx-copy-input__field:focus-visible {
57
- outline: 0;
57
+ outline-color: transparent;
58
58
  box-shadow: none;
59
59
  }
60
60
 
@@ -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
 
@@ -84,7 +84,7 @@
84
84
  }
85
85
 
86
86
  .zx-time-picker__clock-unit:focus-visible {
87
- outline: 0;
87
+ outline-color: transparent;
88
88
  box-shadow: var(--zx-focus-ring);
89
89
  }
90
90
 
@@ -146,7 +146,7 @@
146
146
  }
147
147
 
148
148
  .zx-time-picker__mark:focus-visible {
149
- outline: 0;
149
+ outline-color: transparent;
150
150
  box-shadow: var(--zx-focus-ring);
151
151
  }
152
152
 
@@ -83,7 +83,7 @@
83
83
  .zx-dock__toggle:disabled .zx-dock__chevron { visibility: hidden; }
84
84
 
85
85
  .zx-dock__toggle:focus-visible {
86
- outline: none;
86
+ outline-color: transparent;
87
87
  box-shadow: var(--zx-focus-ring);
88
88
  }
89
89
 
@@ -141,7 +141,7 @@
141
141
  }
142
142
 
143
143
  .zx-dock__tab:focus-visible {
144
- outline: none;
144
+ outline-color: transparent;
145
145
  box-shadow: var(--zx-focus-ring);
146
146
  }
147
147
 
@@ -172,7 +172,8 @@
172
172
  }
173
173
 
174
174
  .zx-dock__panel:focus-visible {
175
- outline: none;
175
+ outline-color: transparent;
176
+ outline-offset: -2px;
176
177
  box-shadow: inset var(--zx-focus-ring);
177
178
  }
178
179
 
@@ -222,7 +223,7 @@
222
223
  }
223
224
 
224
225
  .zx-dock__divider:focus-visible {
225
- outline: none;
226
+ outline-color: transparent;
226
227
  z-index: 1;
227
228
  box-shadow: var(--zx-focus-ring);
228
229
  }