@zeyos/zx 4.3.4 → 4.4.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 (128) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +7 -0
  3. package/dist/types/components/activity-item/activity-item.d.ts +351 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +195 -0
  5. package/dist/types/components/app-sidebar/app-sidebar.d.ts +28 -0
  6. package/dist/types/components/button/button.d.ts +8 -0
  7. package/dist/types/components/calendar/calendar.d.ts +19 -0
  8. package/dist/types/components/card/card.d.ts +7 -0
  9. package/dist/types/components/card-view/card-view.d.ts +11 -3
  10. package/dist/types/components/card-view/record-card.d.ts +7 -1
  11. package/dist/types/components/code-editor/code-editor.d.ts +160 -0
  12. package/dist/types/components/code-editor/field-adapter.d.ts +5 -0
  13. package/dist/types/components/context-menu/context-menu.d.ts +18 -2
  14. package/dist/types/components/date-picker/date-picker.d.ts +8 -0
  15. package/dist/types/components/date-picker/time-picker.d.ts +7 -0
  16. package/dist/types/components/dialog/dialog.d.ts +29 -0
  17. package/dist/types/components/entity-ref/entity-ref.d.ts +331 -0
  18. package/dist/types/components/file-item/file-item.d.ts +234 -0
  19. package/dist/types/components/file-list/file-list.d.ts +171 -0
  20. package/dist/types/components/filter/filter.d.ts +91 -36
  21. package/dist/types/components/filter-panel/filter-panel.d.ts +449 -0
  22. package/dist/types/components/kanban-view/kanban-card.d.ts +138 -0
  23. package/dist/types/components/kanban-view/kanban-view.d.ts +170 -4
  24. package/dist/types/components/launcher/launcher.d.ts +19 -12
  25. package/dist/types/components/list-toolbar/list-toolbar.d.ts +599 -0
  26. package/dist/types/components/menu-button/menu-button.d.ts +4 -37
  27. package/dist/types/components/message/message.d.ts +7 -0
  28. package/dist/types/components/modal/modal.d.ts +48 -0
  29. package/dist/types/components/navigation-bar/navigation-bar.d.ts +105 -21
  30. package/dist/types/components/panel/panel.d.ts +7 -0
  31. package/dist/types/components/search/search.d.ts +7 -0
  32. package/dist/types/components/select/select.d.ts +4 -0
  33. package/dist/types/components/sort-control/sort-control.d.ts +105 -0
  34. package/dist/types/components/stat-tile/stat-tile.d.ts +277 -0
  35. package/dist/types/components/tabbox/tabbox.d.ts +7 -0
  36. package/dist/types/components/table/table.d.ts +144 -0
  37. package/dist/types/components/table-view/table-view.d.ts +11 -5
  38. package/dist/types/components/timebox/timebox.d.ts +7 -0
  39. package/dist/types/components/value-list/value-list.d.ts +8 -0
  40. package/dist/types/components/view/record-view.d.ts +96 -5
  41. package/dist/types/core/icons.d.ts +17 -0
  42. package/dist/types/index.d.ts +11 -0
  43. package/dist/types/internal/app-rail.d.ts +10 -0
  44. package/dist/types/internal/menu-items.d.ts +134 -0
  45. package/dist/zx-zeyos.esm.js +616 -77
  46. package/dist/zx-zeyos.esm.min.js +8 -1
  47. package/dist/zx.css +2085 -238
  48. package/dist/zx.esm.js +6704 -1542
  49. package/dist/zx.esm.min.js +10 -3
  50. package/dist/zx.global.js +6704 -1542
  51. package/dist/zx.global.min.js +10 -3
  52. package/dist/zx.min.css +1 -1
  53. package/docs/api.json +1479 -127
  54. package/docs/llms.md +315 -10
  55. package/docs/llms.txt +11 -5
  56. package/package.json +1 -1
  57. package/src/components/activity-item/activity-item.css +137 -0
  58. package/src/components/activity-item/activity-item.js +599 -0
  59. package/src/components/activity-list/activity-list.css +64 -0
  60. package/src/components/activity-list/activity-list.js +375 -0
  61. package/src/components/app-rail/app-rail.css +227 -19
  62. package/src/components/app-sidebar/app-sidebar.css +414 -24
  63. package/src/components/app-sidebar/app-sidebar.js +65 -5
  64. package/src/components/button/button.css +8 -0
  65. package/src/components/button/button.js +6 -1
  66. package/src/components/calendar/calendar.js +33 -3
  67. package/src/components/card/card.js +12 -1
  68. package/src/components/card-view/card-view.css +1 -0
  69. package/src/components/card-view/card-view.js +25 -9
  70. package/src/components/card-view/record-card.js +7 -3
  71. package/src/components/code-editor/code-editor.css +67 -0
  72. package/src/components/code-editor/code-editor.js +288 -0
  73. package/src/components/code-editor/field-adapter.js +34 -0
  74. package/src/components/context-menu/context-menu.css +80 -3
  75. package/src/components/context-menu/context-menu.js +22 -30
  76. package/src/components/date-picker/date-picker.js +22 -8
  77. package/src/components/date-picker/month-picker.js +3 -3
  78. package/src/components/date-picker/time-picker.js +20 -8
  79. package/src/components/dialog/dialog.js +88 -66
  80. package/src/components/dropdown/dropdown.js +2 -2
  81. package/src/components/entity-ref/entity-ref.css +182 -0
  82. package/src/components/entity-ref/entity-ref.js +496 -0
  83. package/src/components/file-item/file-item.css +88 -0
  84. package/src/components/file-item/file-item.js +371 -0
  85. package/src/components/file-list/file-list.css +51 -0
  86. package/src/components/file-list/file-list.js +320 -0
  87. package/src/components/filter/filter.css +27 -1
  88. package/src/components/filter/filter.js +199 -45
  89. package/src/components/filter-panel/filter-panel.css +104 -0
  90. package/src/components/filter-panel/filter-panel.js +964 -0
  91. package/src/components/kanban-view/kanban-card.js +239 -0
  92. package/src/components/kanban-view/kanban-view.css +194 -0
  93. package/src/components/kanban-view/kanban-view.js +251 -8
  94. package/src/components/launcher/launcher.js +59 -27
  95. package/src/components/list-toolbar/list-toolbar.css +189 -0
  96. package/src/components/list-toolbar/list-toolbar.js +851 -0
  97. package/src/components/menu-button/menu-button.css +80 -3
  98. package/src/components/menu-button/menu-button.js +21 -37
  99. package/src/components/message/message.css +5 -2
  100. package/src/components/message/message.js +13 -2
  101. package/src/components/modal/modal.css +15 -0
  102. package/src/components/modal/modal.js +141 -0
  103. package/src/components/navigation-bar/navigation-bar.css +77 -2
  104. package/src/components/navigation-bar/navigation-bar.js +171 -4
  105. package/src/components/panel/panel.js +13 -2
  106. package/src/components/search/search.js +17 -5
  107. package/src/components/select/field-adapter.js +4 -1
  108. package/src/components/select/select.js +3 -0
  109. package/src/components/sort-control/sort-control.css +17 -0
  110. package/src/components/sort-control/sort-control.js +140 -0
  111. package/src/components/stat-tile/stat-tile.css +213 -0
  112. package/src/components/stat-tile/stat-tile.js +590 -0
  113. package/src/components/tabbox/tabbox.js +12 -1
  114. package/src/components/table/table.css +51 -2
  115. package/src/components/table/table.js +298 -19
  116. package/src/components/table-view/table-view.css +8 -2
  117. package/src/components/table-view/table-view.js +21 -6
  118. package/src/components/tag-picker/tag-picker.css +3 -1
  119. package/src/components/timebox/timebox.js +18 -5
  120. package/src/components/value-list/value-list.js +22 -6
  121. package/src/components/view/record-view.css +5 -0
  122. package/src/components/view/record-view.js +100 -7
  123. package/src/core/icons.js +74 -5
  124. package/src/index.js +25 -0
  125. package/src/internal/app-rail.js +19 -1
  126. package/src/internal/menu-items.js +142 -0
  127. package/styles/print.css +192 -0
  128. package/styles/zx.css +11 -0
@@ -16,15 +16,19 @@
16
16
  }
17
17
 
18
18
  .zx-context-menu__item {
19
- display: flex;
19
+ display: grid;
20
+ grid-template-columns: auto minmax(0, 1fr) auto;
20
21
  align-items: center;
21
22
  gap: var(--zx-space-2);
22
23
  inline-size: 100%;
23
24
  min-block-size: var(--zx-control-height);
24
- border-color: transparent;
25
+ border: 1px solid transparent;
25
26
  background: transparent;
26
27
  padding-inline: var(--zx-control-pad-x);
28
+ color: inherit;
29
+ font: inherit;
27
30
  text-align: start;
31
+ text-decoration: none;
28
32
  }
29
33
 
30
34
  .zx-context-menu__item:hover,
@@ -42,11 +46,84 @@
42
46
  opacity: 0.65;
43
47
  }
44
48
 
45
- .zx-context-menu__icon {
49
+ .zx-context-menu__leading,
50
+ .zx-context-menu__meta {
46
51
  display: inline-flex;
52
+ align-items: center;
53
+ gap: var(--zx-space-2);
47
54
  flex: none;
48
55
  }
49
56
 
57
+ .zx-context-menu__meta {
58
+ justify-content: flex-end;
59
+ }
60
+
61
+ .zx-context-menu__leading {
62
+ justify-content: center;
63
+ inline-size: var(--zx-space-4);
64
+ }
65
+
66
+ .zx-context-menu__body {
67
+ display: flex;
68
+ min-inline-size: 0;
69
+ flex-direction: column;
70
+ }
71
+
72
+ .zx-context-menu__label,
73
+ .zx-context-menu__description {
74
+ overflow: hidden;
75
+ text-overflow: ellipsis;
76
+ white-space: nowrap;
77
+ }
78
+
79
+ .zx-context-menu__description,
80
+ .zx-context-menu__shortcut,
81
+ .zx-context-menu__adornment {
82
+ color: var(--zx-color-text-muted);
83
+ font-size: var(--zx-text-xs);
84
+ }
85
+
86
+ .zx-context-menu__badge {
87
+ min-inline-size: var(--zx-space-4);
88
+ border-radius: var(--zx-radius-full);
89
+ background: var(--zx-color-bg-muted);
90
+ padding-inline: var(--zx-space-1);
91
+ color: var(--zx-color-text-muted);
92
+ font-size: var(--zx-text-xs);
93
+ text-align: center;
94
+ }
95
+
96
+ .zx-context-menu__shortcut {
97
+ border: 0;
98
+ background: transparent;
99
+ padding: 0;
100
+ font-family: var(--zx-font-mono);
101
+ }
102
+
103
+ .zx-context-menu__check {
104
+ inline-size: var(--zx-space-3);
105
+ block-size: var(--zx-space-3);
106
+ border: 1px solid var(--zx-color-border-strong);
107
+ border-radius: var(--zx-radius-sm);
108
+ }
109
+
110
+ .zx-context-menu__check[data-check-kind="radio"] {
111
+ border-radius: var(--zx-radius-full);
112
+ }
113
+
114
+ .zx-context-menu__item[aria-checked="true"] .zx-context-menu__check {
115
+ border-color: var(--zx-color-accent);
116
+ background: var(--zx-color-accent);
117
+ box-shadow: inset 0 0 0 2px var(--zx-color-bg-raised);
118
+ }
119
+
120
+ .zx-context-menu__heading {
121
+ padding: var(--zx-space-2) var(--zx-control-pad-x) var(--zx-space-1);
122
+ color: var(--zx-color-text-muted);
123
+ font-size: var(--zx-text-xs);
124
+ font-weight: 600;
125
+ }
126
+
50
127
  .zx-context-menu__separator {
51
128
  block-size: 1px;
52
129
  margin: var(--zx-space-1) var(--zx-space-2);
@@ -1,11 +1,15 @@
1
1
  import { Component } from '../../core/component.js';
2
2
  import { h, resolveElement } from '../../core/dom.js';
3
- import { icon } from '../../core/icons.js';
4
3
  import { rovingTabindex, typeahead } from '../../core/keyboard.js';
4
+ import {
5
+ isMenuItem,
6
+ MENU_ITEM_SELECTOR,
7
+ renderMenuEntries
8
+ } from '../../internal/menu-items.js';
5
9
  import { Dropdown } from '../dropdown/dropdown.js';
6
10
 
7
- /** @typedef {import('../menu-button/menu-button.js').MenuItem} MenuItem */
8
- /** @typedef {MenuItem|'-'} ContextMenuEntry */
11
+ /** @typedef {import('../../internal/menu-items.js').MenuItem} MenuItem */
12
+ /** @typedef {import('../../internal/menu-items.js').MenuEntry} ContextMenuEntry */
9
13
 
10
14
  /**
11
15
  * @typedef {Object} ContextMenuOptions
@@ -22,8 +26,6 @@ import { Dropdown } from '../dropdown/dropdown.js';
22
26
  * @property {(event: CustomEvent<Record<string, never>>) => void} [onclose] Close listener.
23
27
  */
24
28
 
25
- const MENU_ITEM_SELECTOR = '[role="menuitem"]';
26
-
27
29
  /**
28
30
  * Right-click menu for a region, implementing the APG menu pattern.
29
31
  *
@@ -107,7 +109,7 @@ export class ContextMenu extends Component {
107
109
  this.listen(this.#target, 'keydown', (event) => this.#onTargetKeydown(event));
108
110
  this.listen(this.#panel, 'click', (event) => {
109
111
  const item = event.target.closest?.(MENU_ITEM_SELECTOR);
110
- if (item && this.#panel.contains(item)) this.#select(item);
112
+ if (item && this.#panel.contains(item)) this.#select(item, event);
111
113
  });
112
114
  this.listen(this.#panel, 'keydown', (event) => this.#onMenuKeydown(event));
113
115
  }
@@ -127,25 +129,7 @@ export class ContextMenu extends Component {
127
129
  */
128
130
  setItems(items) {
129
131
  this.#items = Array.isArray(items) ? items.slice() : [];
130
- this.#panel.replaceChildren();
131
- this.#items.forEach((item, index) => {
132
- if (item === '-') {
133
- this.#panel.append(h('div', { class: 'zx-context-menu__separator', role: 'separator' }));
134
- return;
135
- }
136
- const children = [];
137
- if (item.icon) children.push(h('span', { class: 'zx-context-menu__icon' }, icon(item.icon)));
138
- children.push(h('span', { class: 'zx-context-menu__label' }, String(item.label ?? '')));
139
- this.#panel.append(h('button', {
140
- class: 'zx-context-menu__item',
141
- type: 'button',
142
- role: 'menuitem',
143
- tabindex: '-1',
144
- 'data-menu-item': String(index),
145
- 'data-danger': item.danger ? 'true' : null,
146
- ariaDisabled: item.disabled ? 'true' : null
147
- }, children));
148
- });
132
+ this.#panel.replaceChildren(...renderMenuEntries(this.#items, 'context-menu'));
149
133
  return this;
150
134
  }
151
135
 
@@ -292,20 +276,28 @@ export class ContextMenu extends Component {
292
276
  if (key.key === 'Enter' || key.key === ' ') {
293
277
  const item = /** @type {Element} */ (key.target).closest?.(MENU_ITEM_SELECTOR);
294
278
  if (!item || !this.#panel.contains(item)) return;
279
+ if (key.key === 'Enter' && item.tagName === 'A') return;
295
280
  event.preventDefault();
296
- this.#select(item);
281
+ /** @type {HTMLElement} */ (item).click();
297
282
  return;
298
283
  }
299
284
  this.#typeahead(key);
300
285
  }
301
286
 
302
- /** @param {Element} element @returns {void} */
303
- #select(element) {
287
+ /** @param {Element} element @param {Event|null} [event=null] @returns {void} */
288
+ #select(element, event = null) {
304
289
  const index = Number(element.getAttribute('data-menu-item'));
305
290
  const item = this.#items[index];
306
- if (!item || item === '-' || item.disabled) return;
291
+ if (!isMenuItem(item) || item.disabled) {
292
+ event?.preventDefault();
293
+ return;
294
+ }
307
295
  const context = this.#context;
308
- this.emit('select', { value: item.value, item, context });
296
+ const selected = this.emit('select', { value: item.value, item, context });
297
+ if (selected.defaultPrevented) {
298
+ event?.preventDefault();
299
+ return;
300
+ }
309
301
  try {
310
302
  item.onselect?.(item.value, item, this);
311
303
  } finally {
@@ -2,6 +2,7 @@ import { Component } from '../../core/component.js';
2
2
  import { addDays, addMonths, formatDate, getWeekStart, isSameDay, parseDate } from '../../core/date.js';
3
3
  import { h } from '../../core/dom.js';
4
4
  import { icon } from '../../core/icons.js';
5
+ import { printf } from '../../core/i18n.js';
5
6
  import { TimePicker } from './time-picker.js';
6
7
 
7
8
  /**
@@ -55,7 +56,7 @@ export class DatePicker extends Component {
55
56
  ref: 'previous',
56
57
  class: 'zx-date-picker__nav',
57
58
  type: 'button',
58
- ariaLabel: 'Previous month'
59
+ ariaLabel: this._message('datePicker.previousMonth', 'Previous month')
59
60
  }, icon('chevron-left')),
60
61
  h('button', {
61
62
  ref: 'heading',
@@ -68,7 +69,7 @@ export class DatePicker extends Component {
68
69
  ref: 'next',
69
70
  class: 'zx-date-picker__nav',
70
71
  type: 'button',
71
- ariaLabel: 'Next month'
72
+ ariaLabel: this._message('datePicker.nextMonth', 'Next month')
72
73
  }, icon('chevron-right'))
73
74
  ),
74
75
  h('div', {
@@ -81,14 +82,14 @@ export class DatePicker extends Component {
81
82
  ref: 'quickPrevious',
82
83
  class: 'zx-date-picker__nav',
83
84
  type: 'button',
84
- ariaLabel: 'Previous year'
85
+ ariaLabel: this._message('datePicker.previousYear', 'Previous year')
85
86
  }, icon('chevron-left')),
86
87
  h('span', { ref: 'quickYear', class: 'zx-date-picker__quick-year', ariaLive: 'polite' }),
87
88
  h('button', {
88
89
  ref: 'quickNext',
89
90
  class: 'zx-date-picker__nav',
90
91
  type: 'button',
91
- ariaLabel: 'Next year'
92
+ ariaLabel: this._message('datePicker.nextYear', 'Next year')
92
93
  }, icon('chevron-right'))
93
94
  ),
94
95
  h('div', { ref: 'quickGrid', class: 'zx-date-picker__quick-grid', role: 'grid' })),
@@ -96,7 +97,7 @@ export class DatePicker extends Component {
96
97
  ref: 'grid',
97
98
  class: 'zx-date-picker__grid',
98
99
  role: 'grid',
99
- ariaLabel: 'Calendar'
100
+ ariaLabel: this._message('datePicker.calendar', 'Calendar')
100
101
  },
101
102
  h('thead', { ref: 'weekdays' }),
102
103
  h('tbody', { ref: 'days' }))
@@ -203,7 +204,7 @@ export class DatePicker extends Component {
203
204
  row.append(h('th', {
204
205
  class: 'zx-date-picker__week-heading',
205
206
  scope: 'col',
206
- ariaLabel: 'Week number'
207
+ ariaLabel: this._message('datePicker.weekNumber', 'Week number')
207
208
  }, '#'));
208
209
  }
209
210
  const sunday = new Date(2023, 0, 1);
@@ -224,7 +225,8 @@ export class DatePicker extends Component {
224
225
  const offset = (first.getDay() - this._weekStart + 7) % 7;
225
226
  const start = addDays(first, -offset);
226
227
  this.refs.heading.textContent = formatDate(first, '%B %Y');
227
- this.refs.heading.setAttribute('aria-label', `Choose month and year, ${formatDate(first, '%B %Y')}`);
228
+ this.refs.heading.setAttribute('aria-label',
229
+ this._message('datePicker.chooseMonthYear', 'Choose month and year, %1', formatDate(first, '%B %Y')));
228
230
  const rows = [];
229
231
 
230
232
  for (let week = 0; week < 6; week += 1) {
@@ -234,7 +236,7 @@ export class DatePicker extends Component {
234
236
  row.append(h('th', {
235
237
  class: 'zx-date-picker__week',
236
238
  scope: 'row',
237
- ariaLabel: `Week ${isoWeekNumber(rowStart)}`
239
+ ariaLabel: this._message('datePicker.week', 'Week %1', isoWeekNumber(rowStart))
238
240
  }, String(isoWeekNumber(rowStart))));
239
241
  }
240
242
  for (let column = 0; column < 7; column += 1) {
@@ -450,6 +452,18 @@ export class DatePicker extends Component {
450
452
  _buttonForDate(date) {
451
453
  return this.refs.days.querySelector(`[data-date="${formatDate(date, '%Y-%m-%d')}"]`);
452
454
  }
455
+
456
+ /**
457
+ * Resolves a message through the host translator, falling back to the built-in English text.
458
+ * @param {string} key Message key.
459
+ * @param {string} fallback Built-in text, with `%1`-style placeholders.
460
+ * @param {...unknown} args Interpolation values.
461
+ * @returns {string}
462
+ */
463
+ _message(key, fallback, ...args) {
464
+ const message = this.msg(key, ...args);
465
+ return message === key ? printf(fallback, args) : message;
466
+ }
453
467
  }
454
468
 
455
469
  /**
@@ -47,21 +47,21 @@ export class MonthPicker extends DatePicker {
47
47
  ref: 'previousYear',
48
48
  class: 'zx-month-picker__nav',
49
49
  type: 'button',
50
- ariaLabel: 'Previous year'
50
+ ariaLabel: this._message('monthPicker.previousYear', 'Previous year')
51
51
  }, icon('chevron-left')),
52
52
  h('span', { ref: 'year', class: 'zx-month-picker__year', ariaLive: 'polite' }),
53
53
  h('button', {
54
54
  ref: 'nextYear',
55
55
  class: 'zx-month-picker__nav',
56
56
  type: 'button',
57
- ariaLabel: 'Next year'
57
+ ariaLabel: this._message('monthPicker.nextYear', 'Next year')
58
58
  }, icon('chevron-right'))
59
59
  ),
60
60
  h('div', {
61
61
  ref: 'grid',
62
62
  class: 'zx-month-picker__grid',
63
63
  role: 'grid',
64
- ariaLabel: 'Choose month'
64
+ ariaLabel: this._message('monthPicker.chooseMonth', 'Choose month')
65
65
  })
66
66
  );
67
67
  this._content = content;
@@ -48,15 +48,15 @@ export class TimePicker extends Component {
48
48
  const content = h('div', {
49
49
  class: 'zx-time-picker__segments',
50
50
  role: 'group',
51
- ariaLabel: 'Time'
51
+ ariaLabel: this._message('timePicker.label', 'Time')
52
52
  });
53
53
  this._content = content;
54
- this._addSpin(content, 'h', 'Hour', 23);
54
+ this._addSpin(content, 'h', this._message('timePicker.hour', 'Hour'), 23);
55
55
  content.append(h('span', { class: 'zx-time-picker__separator', ariaHidden: 'true' }, ':'));
56
- this._addSpin(content, 'm', 'Minute', 59);
56
+ this._addSpin(content, 'm', this._message('timePicker.minute', 'Minute'), 59);
57
57
  if (this.options.seconds) {
58
58
  content.append(h('span', { class: 'zx-time-picker__separator', ariaHidden: 'true' }, ':'));
59
- this._addSpin(content, 's', 'Second', 59);
59
+ this._addSpin(content, 's', this._message('timePicker.second', 'Second'), 59);
60
60
  }
61
61
  root.append(content);
62
62
 
@@ -66,7 +66,7 @@ export class TimePicker extends Component {
66
66
  ref: 'clockToggle',
67
67
  class: 'zx-icon-btn zx-time-picker__clock-toggle',
68
68
  type: 'button',
69
- ariaLabel: 'Pick the time on a clock',
69
+ ariaLabel: this._message('timePicker.clock', 'Pick the time on a clock'),
70
70
  ariaExpanded: 'false',
71
71
  ariaControls: clockId
72
72
  }, icon('clock', { size: 15 })));
@@ -223,7 +223,7 @@ export class TimePicker extends Component {
223
223
  ref: 'headHour',
224
224
  class: 'zx-time-picker__clock-unit',
225
225
  type: 'button',
226
- ariaLabel: 'Select the hour',
226
+ ariaLabel: this._message('timePicker.selectHour', 'Select the hour'),
227
227
  dataset: { unit: 'h' }
228
228
  }),
229
229
  h('span', { class: 'zx-time-picker__clock-colon', ariaHidden: 'true' }, ':'),
@@ -231,7 +231,7 @@ export class TimePicker extends Component {
231
231
  ref: 'headMinute',
232
232
  class: 'zx-time-picker__clock-unit',
233
233
  type: 'button',
234
- ariaLabel: 'Select the minutes',
234
+ ariaLabel: this._message('timePicker.selectMinutes', 'Select the minutes'),
235
235
  dataset: { unit: 'm' }
236
236
  })),
237
237
  h('div', { ref: 'dial', class: 'zx-time-picker__dial', role: 'radiogroup' },
@@ -290,7 +290,8 @@ export class TimePicker extends Component {
290
290
  this.refs.headMinute.textContent = pad(this._display.m);
291
291
  this.refs.headHour.setAttribute('aria-pressed', String(hours));
292
292
  this.refs.headMinute.setAttribute('aria-pressed', String(!hours));
293
- this.refs.dial.setAttribute('aria-label', hours ? 'Hour' : 'Minutes');
293
+ this.refs.dial.setAttribute('aria-label', hours
294
+ ? this._message('timePicker.hour', 'Hour') : this._message('timePicker.minutes', 'Minutes'));
294
295
  if (!this._clockOpen) return;
295
296
 
296
297
  const selected = hours ? this._display.h : this._display.m;
@@ -330,6 +331,17 @@ export class TimePicker extends Component {
330
331
  element.setAttribute('aria-checked', String(checked));
331
332
  element.tabIndex = checked ? 0 : -1;
332
333
  }
334
+
335
+ /**
336
+ * Resolves a message through the host translator, falling back to the built-in English text.
337
+ * @param {string} key Message key.
338
+ * @param {string} fallback English default.
339
+ * @returns {string}
340
+ */
341
+ _message(key, fallback) {
342
+ const message = this.msg(key);
343
+ return message === key ? fallback : message;
344
+ }
333
345
  }
334
346
 
335
347
  /**