@adia-ai/web-components 0.6.32 → 0.6.34

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 (164) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/components/accordion/accordion.css +2 -2
  3. package/components/action-list/action-list.css +2 -2
  4. package/components/agent-artifact/agent-artifact.css +31 -31
  5. package/components/agent-feedback-bar/agent-feedback-bar.css +10 -10
  6. package/components/agent-questions/agent-questions.css +57 -57
  7. package/components/agent-reasoning/agent-reasoning.css +62 -62
  8. package/components/agent-suggestions/agent-suggestions.css +4 -4
  9. package/components/agent-trace/agent-trace.css +53 -53
  10. package/components/alert/alert.css +41 -41
  11. package/components/avatar/avatar.css +27 -27
  12. package/components/badge/badge.css +27 -27
  13. package/components/block/block.css +16 -16
  14. package/components/breadcrumb/breadcrumb.css +23 -23
  15. package/components/button/button.css +101 -91
  16. package/components/calendar-grid/calendar-grid.a2ui.json +136 -0
  17. package/components/calendar-grid/calendar-grid.css +226 -0
  18. package/components/calendar-grid/calendar-grid.d.ts +37 -0
  19. package/components/calendar-grid/calendar-grid.js +17 -0
  20. package/components/calendar-grid/calendar-grid.yaml +116 -0
  21. package/components/calendar-grid/class.js +300 -0
  22. package/components/calendar-picker/calendar-picker.css +139 -139
  23. package/components/canvas/canvas.css +12 -12
  24. package/components/card/card.css +83 -83
  25. package/components/chart/chart.css +224 -224
  26. package/components/chart-legend/chart-legend.css +26 -26
  27. package/components/check/check.css +40 -40
  28. package/components/code/code.css +125 -125
  29. package/components/col/col.css +15 -15
  30. package/components/color-picker/color-picker.css +55 -55
  31. package/components/combobox/class.js +861 -0
  32. package/components/combobox/combobox.a2ui.json +363 -0
  33. package/components/combobox/combobox.css +244 -0
  34. package/components/combobox/combobox.d.ts +113 -0
  35. package/components/combobox/combobox.examples.md +59 -0
  36. package/components/combobox/combobox.js +17 -0
  37. package/components/combobox/combobox.test.js +181 -0
  38. package/components/combobox/combobox.yaml +369 -0
  39. package/components/command/command.css +90 -90
  40. package/components/date-range-picker/class.js +775 -0
  41. package/components/date-range-picker/date-range-picker.a2ui.json +300 -0
  42. package/components/date-range-picker/date-range-picker.css +178 -0
  43. package/components/date-range-picker/date-range-picker.d.ts +82 -0
  44. package/components/date-range-picker/date-range-picker.examples.md +37 -0
  45. package/components/date-range-picker/date-range-picker.js +17 -0
  46. package/components/date-range-picker/date-range-picker.test.js +387 -0
  47. package/components/date-range-picker/date-range-picker.yaml +285 -0
  48. package/components/datetime-picker/class.js +706 -0
  49. package/components/datetime-picker/datetime-picker.a2ui.json +334 -0
  50. package/components/datetime-picker/datetime-picker.css +150 -0
  51. package/components/datetime-picker/datetime-picker.d.ts +86 -0
  52. package/components/datetime-picker/datetime-picker.examples.md +46 -0
  53. package/components/datetime-picker/datetime-picker.js +17 -0
  54. package/components/datetime-picker/datetime-picker.test.js +454 -0
  55. package/components/datetime-picker/datetime-picker.yaml +332 -0
  56. package/components/demo-toggle/demo-toggle.css +27 -27
  57. package/components/description-list/description-list.css +18 -18
  58. package/components/divider/divider.css +24 -24
  59. package/components/embed/embed.css +6 -6
  60. package/components/empty-state/empty-state.css +27 -27
  61. package/components/feed/feed.css +12 -12
  62. package/components/field/field.css +37 -28
  63. package/components/field/field.test.js +32 -0
  64. package/components/fields/fields.css +5 -5
  65. package/components/grid/grid.css +5 -5
  66. package/components/heatmap/heatmap.css +63 -63
  67. package/components/icon/icon.css +12 -12
  68. package/components/image/image.css +14 -14
  69. package/components/index.js +8 -0
  70. package/components/input/input.css +66 -66
  71. package/components/inspector/inspector.css +6 -6
  72. package/components/integration-card/class.js +410 -0
  73. package/components/integration-card/integration-card.a2ui.json +268 -0
  74. package/components/integration-card/integration-card.css +169 -0
  75. package/components/integration-card/integration-card.d.ts +63 -0
  76. package/components/integration-card/integration-card.examples.md +41 -0
  77. package/components/integration-card/integration-card.js +17 -0
  78. package/components/integration-card/integration-card.test.js +306 -0
  79. package/components/integration-card/integration-card.yaml +280 -0
  80. package/components/kbd/kbd.css +32 -32
  81. package/components/link/link.css +12 -12
  82. package/components/list/list.css +8 -8
  83. package/components/list-window/class.js +688 -0
  84. package/components/list-window/list-window.a2ui.json +277 -0
  85. package/components/list-window/list-window.css +124 -0
  86. package/components/list-window/list-window.d.ts +84 -0
  87. package/components/list-window/list-window.examples.md +73 -0
  88. package/components/list-window/list-window.js +17 -0
  89. package/components/list-window/list-window.test.js +303 -0
  90. package/components/list-window/list-window.yaml +270 -0
  91. package/components/menu/menu.css +8 -8
  92. package/components/modal/modal.css +43 -43
  93. package/components/nav/nav.css +40 -40
  94. package/components/nav-group/nav-group.css +52 -52
  95. package/components/nav-item/nav-item.css +44 -44
  96. package/components/noodles/noodles.css +31 -31
  97. package/components/option-card/option-card.css +69 -69
  98. package/components/otp-input/otp-input.css +30 -30
  99. package/components/page/page.css +18 -18
  100. package/components/pagination/pagination.css +61 -61
  101. package/components/pane/pane.css +57 -57
  102. package/components/pipeline-status/pipeline-status.css +65 -65
  103. package/components/popover/popover.css +17 -17
  104. package/components/progress/progress.css +23 -23
  105. package/components/progress-row/progress-row.css +17 -17
  106. package/components/radio/radio.css +39 -39
  107. package/components/range/range.css +55 -55
  108. package/components/rating/rating.css +28 -28
  109. package/components/richtext/richtext.css +133 -133
  110. package/components/row/row.css +19 -19
  111. package/components/search/search.css +5 -5
  112. package/components/segment/segment.css +24 -24
  113. package/components/segmented/segmented.css +25 -25
  114. package/components/select/select.css +84 -84
  115. package/components/skeleton/skeleton.css +14 -14
  116. package/components/slider/slider.css +46 -46
  117. package/components/spinner/class.js +69 -0
  118. package/components/spinner/spinner.a2ui.json +197 -0
  119. package/components/spinner/spinner.css +165 -0
  120. package/components/spinner/spinner.d.ts +26 -0
  121. package/components/spinner/spinner.examples.md +26 -0
  122. package/components/spinner/spinner.js +17 -0
  123. package/components/spinner/spinner.test.js +234 -0
  124. package/components/spinner/spinner.yaml +230 -0
  125. package/components/stack/stack.css +11 -11
  126. package/components/stat/stat.css +25 -25
  127. package/components/step-progress/step-progress.css +20 -20
  128. package/components/stepper/stepper.css +29 -29
  129. package/components/stream/stream.css +12 -12
  130. package/components/swatch/swatch.css +68 -68
  131. package/components/swiper/swiper.css +57 -57
  132. package/components/switch/switch.css +52 -52
  133. package/components/table/class.js +9 -0
  134. package/components/table/table.a2ui.json +1 -1
  135. package/components/table/table.css +162 -162
  136. package/components/table/table.d.ts +1 -1
  137. package/components/table/table.test.js +53 -0
  138. package/components/table/table.yaml +13 -1
  139. package/components/table-toolbar/table-toolbar.css +32 -32
  140. package/components/tabs/tabs.css +51 -51
  141. package/components/tag/tag.css +48 -48
  142. package/components/text/text.css +44 -44
  143. package/components/textarea/textarea.css +46 -46
  144. package/components/time-picker/class.js +693 -0
  145. package/components/time-picker/time-picker.a2ui.json +267 -0
  146. package/components/time-picker/time-picker.css +122 -0
  147. package/components/time-picker/time-picker.d.ts +75 -0
  148. package/components/time-picker/time-picker.examples.md +35 -0
  149. package/components/time-picker/time-picker.js +17 -0
  150. package/components/time-picker/time-picker.test.js +287 -0
  151. package/components/time-picker/time-picker.yaml +256 -0
  152. package/components/timeline/timeline.css +50 -50
  153. package/components/toast/toast.css +58 -58
  154. package/components/toggle-group/toggle-group.css +6 -6
  155. package/components/toggle-scheme/toggle-scheme.css +2 -2
  156. package/components/toolbar/toolbar.css +17 -17
  157. package/components/tooltip/tooltip.css +2 -2
  158. package/components/tree/tree.css +37 -37
  159. package/components/upload/upload.css +49 -49
  160. package/dist/icons-manifest.js +3 -3
  161. package/dist/web-components.min.css +1 -1
  162. package/dist/web-components.min.js +121 -83
  163. package/package.json +1 -1
  164. package/styles/components.css +8 -0
@@ -0,0 +1,287 @@
1
+ /**
2
+ * time-picker-ui — SPEC-043
3
+ *
4
+ * Coverage:
5
+ * - Stamp shape (precision × hour-cycle)
6
+ * - Segment text sync from value
7
+ * - Arrow Up / Down increments
8
+ * - Page Up / Down + Home / End
9
+ * - Tab equivalents (ArrowLeft / ArrowRight)
10
+ * - Digit typing + auto-advance
11
+ * - Form participation (name + ISO value)
12
+ * - min / max → invalid event
13
+ * - disabled / readonly behavior
14
+ * - precision toggle re-stamps without losing hh/mm
15
+ */
16
+
17
+ import { describe, it, expect, beforeEach } from 'vitest';
18
+ import '../../core/element.js';
19
+ import './time-picker.js';
20
+
21
+ const tick = () => new Promise((r) => queueMicrotask(r));
22
+
23
+ function mount(html) {
24
+ const wrap = document.createElement('div');
25
+ wrap.innerHTML = html;
26
+ document.body.appendChild(wrap);
27
+ return wrap.firstElementChild;
28
+ }
29
+
30
+ function segText(el, key) {
31
+ return el.querySelector(`[data-segment="${key}"]`)?.textContent || '';
32
+ }
33
+
34
+ function fireKey(el, key) {
35
+ el.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }));
36
+ }
37
+
38
+ describe('time-picker-ui', () => {
39
+ beforeEach(() => { document.body.innerHTML = ''; });
40
+
41
+ it('renders with empty segments when no value is set', async () => {
42
+ const el = mount('<time-picker-ui></time-picker-ui>');
43
+ await tick();
44
+ expect(el.querySelector('[data-segment="hour"]')).not.toBeNull();
45
+ expect(el.querySelector('[data-segment="minute"]')).not.toBeNull();
46
+ expect(segText(el, 'hour')).toBe('');
47
+ expect(segText(el, 'minute')).toBe('');
48
+ });
49
+
50
+ it('populates hour/minute segments from value="09:30"', async () => {
51
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
52
+ await tick();
53
+ expect(segText(el, 'hour')).toBe('09');
54
+ expect(segText(el, 'minute')).toBe('30');
55
+ });
56
+
57
+ it('precision="second" stamps a seconds segment + separator', async () => {
58
+ const el = mount('<time-picker-ui precision="second" value="14:30:45"></time-picker-ui>');
59
+ await tick();
60
+ expect(el.querySelector('[data-segment="second"]')).not.toBeNull();
61
+ expect(segText(el, 'second')).toBe('45');
62
+ });
63
+
64
+ it('hour-cycle="h12" stamps a meridiem segment + 12h display', async () => {
65
+ const el = mount('<time-picker-ui hour-cycle="h12" value="14:30"></time-picker-ui>');
66
+ await tick();
67
+ const meridiem = el.querySelector('[data-segment="meridiem"]');
68
+ expect(meridiem).not.toBeNull();
69
+ expect(segText(el, 'hour')).toBe('02');
70
+ expect(segText(el, 'meridiem')).toBe('PM');
71
+ });
72
+
73
+ it('hour-cycle="h12" displays midnight as 12 AM', async () => {
74
+ const el = mount('<time-picker-ui hour-cycle="h12" value="00:15"></time-picker-ui>');
75
+ await tick();
76
+ expect(segText(el, 'hour')).toBe('12');
77
+ expect(segText(el, 'meridiem')).toBe('AM');
78
+ });
79
+
80
+ it('Arrow Up on hour segment increments by 1', async () => {
81
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
82
+ await tick();
83
+ const hour = el.querySelector('[data-segment="hour"]');
84
+ hour.focus();
85
+ fireKey(hour, 'ArrowUp');
86
+ await tick();
87
+ expect(el.value).toBe('10:30');
88
+ });
89
+
90
+ it('Arrow Down on minute segment decrements by step/60 minutes', async () => {
91
+ const el = mount('<time-picker-ui value="09:30" step="60"></time-picker-ui>');
92
+ await tick();
93
+ const min = el.querySelector('[data-segment="minute"]');
94
+ min.focus();
95
+ fireKey(min, 'ArrowDown');
96
+ await tick();
97
+ expect(el.value).toBe('09:29');
98
+ });
99
+
100
+ it('step="900" increments minute by 15 on Arrow Up', async () => {
101
+ const el = mount('<time-picker-ui value="09:30" step="900"></time-picker-ui>');
102
+ await tick();
103
+ const min = el.querySelector('[data-segment="minute"]');
104
+ min.focus();
105
+ fireKey(min, 'ArrowUp');
106
+ await tick();
107
+ expect(el.value).toBe('09:45');
108
+ });
109
+
110
+ it('Page Up increments by 10× the small step', async () => {
111
+ const el = mount('<time-picker-ui value="09:00"></time-picker-ui>');
112
+ await tick();
113
+ const min = el.querySelector('[data-segment="minute"]');
114
+ min.focus();
115
+ fireKey(min, 'PageUp');
116
+ await tick();
117
+ expect(el.value).toBe('09:10');
118
+ });
119
+
120
+ it('Home jumps to segment min; End jumps to segment max', async () => {
121
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
122
+ await tick();
123
+ const hour = el.querySelector('[data-segment="hour"]');
124
+ hour.focus();
125
+ fireKey(hour, 'Home');
126
+ await tick();
127
+ expect(el.value).toBe('00:30');
128
+ fireKey(hour, 'End');
129
+ await tick();
130
+ expect(el.value).toBe('23:30');
131
+ });
132
+
133
+ it('ArrowRight moves focus to next segment; ArrowLeft to previous', async () => {
134
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
135
+ await tick();
136
+ const hour = el.querySelector('[data-segment="hour"]');
137
+ const minute = el.querySelector('[data-segment="minute"]');
138
+ hour.focus();
139
+ fireKey(hour, 'ArrowRight');
140
+ await tick();
141
+ expect(document.activeElement).toBe(minute);
142
+ fireKey(minute, 'ArrowLeft');
143
+ await tick();
144
+ expect(document.activeElement).toBe(hour);
145
+ });
146
+
147
+ it('disabled prop blocks Arrow-key step', async () => {
148
+ const el = mount('<time-picker-ui value="09:30" disabled></time-picker-ui>');
149
+ await tick();
150
+ const hour = el.querySelector('[data-segment="hour"]');
151
+ fireKey(hour, 'ArrowUp');
152
+ await tick();
153
+ expect(el.value).toBe('09:30');
154
+ });
155
+
156
+ it('readonly prop blocks edits but keeps segments focusable', async () => {
157
+ const el = mount('<time-picker-ui value="09:30" readonly></time-picker-ui>');
158
+ await tick();
159
+ const hour = el.querySelector('[data-segment="hour"]');
160
+ expect(hour.getAttribute('tabindex')).toBe('0');
161
+ expect(hour.contentEditable).toBe('false');
162
+ hour.focus();
163
+ fireKey(hour, 'ArrowUp');
164
+ await tick();
165
+ expect(el.value).toBe('09:30');
166
+ });
167
+
168
+ it('min violation fires invalid + does not mutate value', async () => {
169
+ const el = mount('<time-picker-ui value="08:00" min="08:00" max="18:00"></time-picker-ui>');
170
+ await tick();
171
+ let captured = null;
172
+ el.addEventListener('invalid', (e) => { captured = e; });
173
+ const hour = el.querySelector('[data-segment="hour"]');
174
+ hour.focus();
175
+ fireKey(hour, 'ArrowDown');
176
+ await tick();
177
+ expect(captured).not.toBeNull();
178
+ expect(captured.detail.reason).toBe('min');
179
+ expect(el.value).toBe('08:00');
180
+ });
181
+
182
+ it('max violation fires invalid', async () => {
183
+ const el = mount('<time-picker-ui value="18:00" min="08:00" max="18:00"></time-picker-ui>');
184
+ await tick();
185
+ let captured = null;
186
+ el.addEventListener('invalid', (e) => { captured = e; });
187
+ const hour = el.querySelector('[data-segment="hour"]');
188
+ hour.focus();
189
+ fireKey(hour, 'ArrowUp');
190
+ await tick();
191
+ expect(captured).not.toBeNull();
192
+ expect(captured.detail.reason).toBe('max');
193
+ expect(el.value).toBe('18:00');
194
+ });
195
+
196
+ it('emits change with detail.segment on Arrow step', async () => {
197
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
198
+ await tick();
199
+ let captured = null;
200
+ el.addEventListener('change', (e) => { captured = e; });
201
+ const hour = el.querySelector('[data-segment="hour"]');
202
+ hour.focus();
203
+ fireKey(hour, 'ArrowUp');
204
+ await tick();
205
+ expect(captured).not.toBeNull();
206
+ expect(captured.detail).toEqual({ value: '10:30', segment: 'hour' });
207
+ });
208
+
209
+ it('precision="minute" → "second" re-stamps without losing hh/mm', async () => {
210
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
211
+ await tick();
212
+ expect(el.querySelector('[data-segment="second"]')).toBeNull();
213
+ el.precision = 'second';
214
+ await tick();
215
+ expect(el.querySelector('[data-segment="second"]')).not.toBeNull();
216
+ expect(segText(el, 'hour')).toBe('09');
217
+ expect(segText(el, 'minute')).toBe('30');
218
+ });
219
+
220
+ it('aria-valuemax matches segment max (h23 → 23 for hour)', async () => {
221
+ const el = mount('<time-picker-ui hour-cycle="h23"></time-picker-ui>');
222
+ await tick();
223
+ const hour = el.querySelector('[data-segment="hour"]');
224
+ expect(hour.getAttribute('aria-valuemax')).toBe('23');
225
+ });
226
+
227
+ it('aria-valuemax for hour is 12 in h12 cycle', async () => {
228
+ const el = mount('<time-picker-ui hour-cycle="h12"></time-picker-ui>');
229
+ await tick();
230
+ const hour = el.querySelector('[data-segment="hour"]');
231
+ expect(hour.getAttribute('aria-valuemax')).toBe('12');
232
+ });
233
+
234
+ it('aria-valuenow + aria-valuetext are populated from value', async () => {
235
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
236
+ await tick();
237
+ const hour = el.querySelector('[data-segment="hour"]');
238
+ const minute = el.querySelector('[data-segment="minute"]');
239
+ expect(hour.getAttribute('aria-valuenow')).toBe('9');
240
+ expect(minute.getAttribute('aria-valuenow')).toBe('30');
241
+ expect(hour.getAttribute('aria-valuetext')).toContain('hour');
242
+ expect(minute.getAttribute('aria-valuetext')).toContain('minute');
243
+ });
244
+
245
+ it('host role="group" + aria-label default', async () => {
246
+ const el = mount('<time-picker-ui></time-picker-ui>');
247
+ await tick();
248
+ expect(el.getAttribute('role')).toBe('group');
249
+ expect(el.hasAttribute('aria-label')).toBe(true);
250
+ });
251
+
252
+ it('clear() resets value to empty', async () => {
253
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
254
+ await tick();
255
+ el.clear();
256
+ await tick();
257
+ expect(el.value).toBe('');
258
+ expect(segText(el, 'hour')).toBe('');
259
+ });
260
+
261
+ it('stepUp() increments programmatically', async () => {
262
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
263
+ await tick();
264
+ el.stepUp('hour');
265
+ await tick();
266
+ expect(el.value).toBe('10:30');
267
+ });
268
+
269
+ it('value setter syncs segments', async () => {
270
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
271
+ await tick();
272
+ el.value = '11:45';
273
+ await tick();
274
+ expect(segText(el, 'hour')).toBe('11');
275
+ expect(segText(el, 'minute')).toBe('45');
276
+ });
277
+
278
+ it('focusing a segment sets [editing] + [segment="…"] on host', async () => {
279
+ const el = mount('<time-picker-ui value="09:30"></time-picker-ui>');
280
+ await tick();
281
+ const minute = el.querySelector('[data-segment="minute"]');
282
+ minute.focus();
283
+ await tick();
284
+ expect(el.hasAttribute('editing')).toBe(true);
285
+ expect(el.getAttribute('segment')).toBe('minute');
286
+ });
287
+ });
@@ -0,0 +1,256 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: UITimePicker
3
+ tag: time-picker-ui
4
+ status: stable
5
+ component: TimePicker
6
+ category: input
7
+ version: 1
8
+ description: >-
9
+ Time-of-day picker with discrete segments (hour, minute, optional second,
10
+ optional AM/PM). Each segment follows the WAI-ARIA Spinbutton pattern with
11
+ Arrow-key increment/decrement, Page Up/Down for larger jumps, Home/End for
12
+ segment min/max, and Tab to move between segments. Form-associated input
13
+ emitting ISO 8601 time string ("HH:mm" or "HH:mm:ss") via change events.
14
+ Per ADR-0025 no native form controls — segments are contenteditable spans
15
+ inside a custom host. SPEC-043.
16
+ props:
17
+ name:
18
+ description: Form field name for FormData submission
19
+ type: string
20
+ default: ''
21
+ value:
22
+ description: Current time value as ISO 8601 ("HH:mm" or "HH:mm:ss"). Empty when no value selected.
23
+ type: string
24
+ default: ''
25
+ min:
26
+ description: Earliest selectable time as ISO 8601. Empty disables the constraint.
27
+ type: string
28
+ default: ''
29
+ reflect: true
30
+ max:
31
+ description: Latest selectable time as ISO 8601. Empty disables the constraint.
32
+ type: string
33
+ default: ''
34
+ reflect: true
35
+ step:
36
+ description: Per-Arrow-keypress increment in seconds. 60 = minute precision (default), 900 = 15-minute, 1 = second precision.
37
+ type: number
38
+ default: 60
39
+ reflect: true
40
+ precision:
41
+ description: Whether the seconds segment is exposed. `minute` (default) hides seconds; `second` adds the seconds segment + separator.
42
+ type: string
43
+ default: minute
44
+ enum:
45
+ - minute
46
+ - second
47
+ reflect: true
48
+ hourcycle:
49
+ description: |
50
+ Hour cycle. Empty (default) derives from the document locale — most
51
+ en-* locales resolve to `h23`. `h12` forces a 12-hour cycle with an
52
+ AM/PM (meridiem) segment; `h23` forces 24-hour cycle.
53
+ type: string
54
+ default: ''
55
+ enum:
56
+ - ''
57
+ - h12
58
+ - h23
59
+ reflect: true
60
+ attribute: hour-cycle
61
+ placeholder:
62
+ description: Per-segment placeholder text shown when a segment is empty. Defaults to "--".
63
+ type: string
64
+ default: '--'
65
+ disabled:
66
+ description: Disables interaction; dims the control
67
+ type: boolean
68
+ default: false
69
+ reflect: true
70
+ readonly:
71
+ description: Prevents editing while keeping the field focusable. Segments stay focusable for inspection.
72
+ type: boolean
73
+ default: false
74
+ reflect: true
75
+ required:
76
+ description: Marks the field as required for form validation. Sets aria-required.
77
+ type: boolean
78
+ default: false
79
+ reflect: true
80
+ locale:
81
+ description: BCP-47 locale tag used to derive hour-cycle when `hour-cycle` is empty. Falls back to `<html lang>` then to browser default.
82
+ type: string
83
+ default: ''
84
+ events:
85
+ change:
86
+ description: Fired when the value commits (segment edit completes via Arrow / Tab / blur).
87
+ detail:
88
+ value:
89
+ type: string
90
+ description: Current value as ISO 8601 ("HH:mm" or "HH:mm:ss").
91
+ segment:
92
+ type: string
93
+ description: Which segment commit fired the event (`hour` | `minute` | `second` | `meridiem`).
94
+ input:
95
+ description: Fired on intermediate segment edits before commit.
96
+ detail:
97
+ value:
98
+ type: string
99
+ description: In-flight value as ISO 8601.
100
+ invalid:
101
+ description: Fired when a constraint is violated (min/max range, parse error).
102
+ detail:
103
+ value:
104
+ type: string
105
+ description: Value that failed validation.
106
+ reason:
107
+ type: string
108
+ description: Why it failed (`min` | `max` | `parse`).
109
+ enum:
110
+ - min
111
+ - max
112
+ - parse
113
+ slots:
114
+ prefix:
115
+ description: Leading content (e.g. an `<icon-ui name="clock">`); sits before the hour segment.
116
+ suffix:
117
+ description: Trailing content (e.g. a timezone label); sits after the last segment.
118
+ states:
119
+ - name: idle
120
+ description: Default, ready for interaction.
121
+ - name: editing
122
+ description: One segment is the focus target.
123
+ attribute: editing
124
+ - name: disabled
125
+ description: Non-interactive; dimmed.
126
+ attribute: disabled
127
+ - name: focused
128
+ description: Keyboard focus ring on the host.
129
+ selector: :focus-within
130
+ traits:
131
+ - focusable
132
+ tokens:
133
+ --time-picker-bg:
134
+ description: Host background color.
135
+ --time-picker-fg:
136
+ description: Host text color.
137
+ --time-picker-fg-muted:
138
+ description: Muted text color (empty placeholder + separator).
139
+ --time-picker-border:
140
+ description: Host border color.
141
+ --time-picker-radius:
142
+ description: Host border radius.
143
+ --time-picker-px:
144
+ description: Host inline padding.
145
+ --time-picker-py:
146
+ description: Host block padding.
147
+ --time-picker-segment-bg-focus:
148
+ description: Focused segment background.
149
+ --time-picker-segment-fg-focus:
150
+ description: Focused segment text color.
151
+ --time-picker-separator-fg:
152
+ description: Separator (`:`) text color.
153
+ requiredIcons: []
154
+ a2ui:
155
+ rules:
156
+ - >-
157
+ `<time-picker-ui>` is the canonical standalone time-of-day picker.
158
+ Per ADR-0025 NEVER wrap a native `<input type="time">` — segments
159
+ are contenteditable spans + ElementInternals provides form
160
+ participation.
161
+ - >-
162
+ `value` MUST be ISO 8601 time `HH:mm` or `HH:mm:ss` (24-hour),
163
+ or empty string. Localized formats (e.g. "9:30 AM") are not
164
+ accepted; display formatting is derived from `hour-cycle`
165
+ regardless of how the value is stored.
166
+ - >-
167
+ `step` is in seconds. 60 = minute precision (default); 900 =
168
+ 15-minute precision (meeting-time common); 1 = second precision
169
+ (requires `precision="second"`).
170
+ - >-
171
+ `precision="second"` exposes the seconds segment AND emits
172
+ `HH:mm:ss`. Default `precision="minute"` emits `HH:mm`.
173
+ - >-
174
+ `hour-cycle` overrides locale-derived behavior. Set explicitly
175
+ (`h12` / `h23`) when the surface needs a specific cycle (cron
176
+ editors, log queries, system surfaces).
177
+ - >-
178
+ For datetime selection use `<datetime-picker-ui>` (SPEC-038) —
179
+ it composes this primitive as its time pane.
180
+ anti_patterns:
181
+ - wrong: |
182
+ {"component": "TimePicker", "value": "9:30 AM"}
183
+ why: |
184
+ Localized format is not a valid `value`. The contract requires ISO
185
+ 8601 time (`HH:mm` or `HH:mm:ss`, 24-hour). The display format is
186
+ derived from `hourCycle` regardless of how the value is stored.
187
+ fix: |
188
+ {"component": "TimePicker", "value": "09:30"}
189
+ - wrong: |
190
+ {"component": "TimePicker", "value": "2026-05-24T09:30"}
191
+ why: |
192
+ Datetime string is invalid for TimePicker — the date portion is
193
+ not accepted. For datetime selection use the DatetimePicker
194
+ component.
195
+ fix: |
196
+ {"component": "DatetimePicker", "value": "2026-05-24T09:30"}
197
+ - wrong: |
198
+ {"component": "TimePicker", "children": [
199
+ {"component": "Input", "type": "time"}
200
+ ]}
201
+ why: |
202
+ Nesting a native time input violates ADR-0025 and bypasses
203
+ ElementInternals form participation.
204
+ fix: |
205
+ {"component": "TimePicker", "name": "field-name"}
206
+ examples:
207
+ - name: meeting-time-15min-step
208
+ description: Time-of-day picker with 15-minute step and business-hours bounds.
209
+ a2ui: |
210
+ [
211
+ {"id": "root", "component": "Field", "label": "Start time",
212
+ "children": ["t"]},
213
+ {"id": "t", "component": "TimePicker",
214
+ "name": "start-time", "value": "09:30",
215
+ "min": "08:00", "max": "18:00", "step": 900}
216
+ ]
217
+ - name: log-cursor-second-precision
218
+ description: Time picker with second precision for a log-cursor query.
219
+ a2ui: |
220
+ [
221
+ {"id": "root", "component": "Field", "label": "Cursor",
222
+ "children": ["t"]},
223
+ {"id": "t", "component": "TimePicker",
224
+ "name": "cursor", "precision": "second", "step": 1,
225
+ "value": "14:30:45"}
226
+ ]
227
+ - name: alarm-12h-cycle
228
+ description: Forced 12-hour cycle with AM/PM segment for an alarm picker.
229
+ a2ui: |
230
+ [
231
+ {"id": "root", "component": "Field", "label": "Alarm",
232
+ "children": ["t"]},
233
+ {"id": "t", "component": "TimePicker",
234
+ "name": "alarm", "hourCycle": "h12", "value": "07:00"}
235
+ ]
236
+ keywords:
237
+ - time
238
+ - timepicker
239
+ - time-picker
240
+ - clock
241
+ - hour
242
+ - minute
243
+ - spinbutton
244
+ synonyms:
245
+ tags:
246
+ - TimeInput
247
+ - TimePicker
248
+ time:
249
+ - clock
250
+ - hour
251
+ - minute
252
+ - alarm
253
+ related:
254
+ - CalendarPicker
255
+ - Field
256
+ - Input