mtrl 0.2.5 → 0.2.7

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 (196) hide show
  1. package/index.ts +18 -0
  2. package/package.json +1 -1
  3. package/src/components/badge/_styles.scss +123 -115
  4. package/src/components/badge/api.ts +57 -59
  5. package/src/components/badge/badge.ts +16 -2
  6. package/src/components/badge/config.ts +65 -11
  7. package/src/components/badge/constants.ts +22 -12
  8. package/src/components/badge/features.ts +44 -40
  9. package/src/components/badge/types.ts +42 -30
  10. package/src/components/bottom-app-bar/_styles.scss +103 -0
  11. package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
  12. package/src/components/bottom-app-bar/config.ts +73 -0
  13. package/src/components/bottom-app-bar/index.ts +11 -0
  14. package/src/components/bottom-app-bar/types.ts +108 -0
  15. package/src/components/button/_styles.scss +0 -66
  16. package/src/components/button/api.ts +5 -0
  17. package/src/components/button/button.ts +0 -2
  18. package/src/components/button/config.ts +5 -0
  19. package/src/components/button/constants.ts +0 -6
  20. package/src/components/button/index.ts +2 -2
  21. package/src/components/button/types.ts +7 -7
  22. package/src/components/card/_styles.scss +67 -25
  23. package/src/components/card/api.ts +54 -3
  24. package/src/components/card/card.ts +25 -6
  25. package/src/components/card/config.ts +189 -22
  26. package/src/components/card/constants.ts +20 -19
  27. package/src/components/card/content.ts +299 -2
  28. package/src/components/card/features.ts +158 -4
  29. package/src/components/card/index.ts +31 -9
  30. package/src/components/card/types.ts +166 -15
  31. package/src/components/checkbox/_styles.scss +0 -2
  32. package/src/components/chip/chip.ts +1 -9
  33. package/src/components/chip/constants.ts +0 -10
  34. package/src/components/chip/index.ts +1 -1
  35. package/src/components/chip/types.ts +1 -4
  36. package/src/components/datepicker/_styles.scss +358 -0
  37. package/src/components/datepicker/api.ts +272 -0
  38. package/src/components/datepicker/config.ts +144 -0
  39. package/src/components/datepicker/constants.ts +98 -0
  40. package/src/components/datepicker/datepicker.ts +346 -0
  41. package/src/components/datepicker/index.ts +9 -0
  42. package/src/components/datepicker/render.ts +452 -0
  43. package/src/components/datepicker/types.ts +268 -0
  44. package/src/components/datepicker/utils.ts +290 -0
  45. package/src/components/dialog/_styles.scss +174 -128
  46. package/src/components/dialog/api.ts +48 -13
  47. package/src/components/dialog/config.ts +9 -5
  48. package/src/components/dialog/dialog.ts +6 -3
  49. package/src/components/dialog/features.ts +290 -130
  50. package/src/components/dialog/types.ts +7 -4
  51. package/src/components/divider/_styles.scss +57 -0
  52. package/src/components/divider/config.ts +81 -0
  53. package/src/components/divider/divider.ts +37 -0
  54. package/src/components/divider/features.ts +207 -0
  55. package/src/components/divider/index.ts +5 -0
  56. package/src/components/divider/types.ts +55 -0
  57. package/src/components/extended-fab/_styles.scss +267 -0
  58. package/src/components/extended-fab/api.ts +141 -0
  59. package/src/components/extended-fab/config.ts +108 -0
  60. package/src/components/extended-fab/constants.ts +36 -0
  61. package/src/components/extended-fab/extended-fab.ts +125 -0
  62. package/src/components/extended-fab/index.ts +4 -0
  63. package/src/components/extended-fab/types.ts +287 -0
  64. package/src/components/fab/_styles.scss +225 -0
  65. package/src/components/fab/api.ts +97 -0
  66. package/src/components/fab/config.ts +94 -0
  67. package/src/components/fab/constants.ts +41 -0
  68. package/src/components/fab/fab.ts +67 -0
  69. package/src/components/fab/index.ts +4 -0
  70. package/src/components/fab/types.ts +234 -0
  71. package/src/components/navigation/_styles.scss +1 -0
  72. package/src/components/navigation/api.ts +78 -50
  73. package/src/components/navigation/features/items.ts +280 -0
  74. package/src/components/navigation/nav-item.ts +72 -23
  75. package/src/components/navigation/navigation.ts +54 -2
  76. package/src/components/navigation/types.ts +210 -188
  77. package/src/components/progress/_styles.scss +0 -65
  78. package/src/components/progress/config.ts +1 -2
  79. package/src/components/progress/constants.ts +0 -14
  80. package/src/components/progress/index.ts +1 -1
  81. package/src/components/progress/progress.ts +1 -4
  82. package/src/components/progress/types.ts +1 -4
  83. package/src/components/radios/_styles.scss +0 -45
  84. package/src/components/radios/api.ts +85 -60
  85. package/src/components/radios/config.ts +1 -2
  86. package/src/components/radios/constants.ts +0 -9
  87. package/src/components/radios/index.ts +1 -1
  88. package/src/components/radios/radio.ts +34 -11
  89. package/src/components/radios/radios.ts +2 -1
  90. package/src/components/radios/types.ts +1 -7
  91. package/src/components/search/_styles.scss +306 -0
  92. package/src/components/search/api.ts +203 -0
  93. package/src/components/search/config.ts +87 -0
  94. package/src/components/search/constants.ts +21 -0
  95. package/src/components/search/features/index.ts +4 -0
  96. package/src/components/search/features/search.ts +718 -0
  97. package/src/components/search/features/states.ts +165 -0
  98. package/src/components/search/features/structure.ts +198 -0
  99. package/src/components/search/index.ts +10 -0
  100. package/src/components/search/search.ts +52 -0
  101. package/src/components/search/types.ts +163 -0
  102. package/src/components/segmented-button/_styles.scss +117 -0
  103. package/src/components/segmented-button/config.ts +67 -0
  104. package/src/components/segmented-button/constants.ts +42 -0
  105. package/src/components/segmented-button/index.ts +4 -0
  106. package/src/components/segmented-button/segment.ts +155 -0
  107. package/src/components/segmented-button/segmented-button.ts +250 -0
  108. package/src/components/segmented-button/types.ts +219 -0
  109. package/src/components/slider/_styles.scss +221 -168
  110. package/src/components/slider/accessibility.md +59 -0
  111. package/src/components/slider/api.ts +41 -120
  112. package/src/components/slider/config.ts +51 -49
  113. package/src/components/slider/features/handlers.ts +495 -0
  114. package/src/components/slider/features/index.ts +1 -2
  115. package/src/components/slider/features/slider.ts +66 -84
  116. package/src/components/slider/features/states.ts +195 -0
  117. package/src/components/slider/features/structure.ts +141 -184
  118. package/src/components/slider/features/ui.ts +150 -201
  119. package/src/components/slider/index.ts +2 -11
  120. package/src/components/slider/slider.ts +9 -12
  121. package/src/components/slider/types.ts +39 -24
  122. package/src/components/switch/_styles.scss +0 -2
  123. package/src/components/tabs/_styles.scss +346 -154
  124. package/src/components/tabs/api.ts +178 -400
  125. package/src/components/tabs/config.ts +46 -52
  126. package/src/components/tabs/constants.ts +85 -8
  127. package/src/components/tabs/features.ts +403 -0
  128. package/src/components/tabs/index.ts +60 -3
  129. package/src/components/tabs/indicator.ts +285 -0
  130. package/src/components/tabs/responsive.ts +144 -0
  131. package/src/components/tabs/scroll-indicators.ts +149 -0
  132. package/src/components/tabs/state.ts +186 -0
  133. package/src/components/tabs/tab-api.ts +258 -0
  134. package/src/components/tabs/tab.ts +255 -0
  135. package/src/components/tabs/tabs.ts +50 -31
  136. package/src/components/tabs/types.ts +332 -128
  137. package/src/components/tabs/utils.ts +107 -0
  138. package/src/components/textfield/_styles.scss +0 -98
  139. package/src/components/textfield/config.ts +2 -3
  140. package/src/components/textfield/constants.ts +0 -14
  141. package/src/components/textfield/index.ts +2 -2
  142. package/src/components/textfield/textfield.ts +0 -2
  143. package/src/components/textfield/types.ts +1 -4
  144. package/src/components/timepicker/README.md +277 -0
  145. package/src/components/timepicker/_styles.scss +451 -0
  146. package/src/components/timepicker/api.ts +632 -0
  147. package/src/components/timepicker/clockdial.ts +482 -0
  148. package/src/components/timepicker/config.ts +130 -0
  149. package/src/components/timepicker/constants.ts +138 -0
  150. package/src/components/timepicker/index.ts +8 -0
  151. package/src/components/timepicker/render.ts +613 -0
  152. package/src/components/timepicker/timepicker.ts +117 -0
  153. package/src/components/timepicker/types.ts +336 -0
  154. package/src/components/timepicker/utils.ts +241 -0
  155. package/src/components/top-app-bar/_styles.scss +225 -0
  156. package/src/components/top-app-bar/config.ts +83 -0
  157. package/src/components/top-app-bar/index.ts +11 -0
  158. package/src/components/top-app-bar/top-app-bar.ts +316 -0
  159. package/src/components/top-app-bar/types.ts +140 -0
  160. package/src/core/build/_ripple.scss +6 -6
  161. package/src/core/build/ripple.ts +72 -95
  162. package/src/core/compose/component.ts +1 -1
  163. package/src/core/compose/features/badge.ts +79 -0
  164. package/src/core/compose/features/icon.ts +3 -1
  165. package/src/core/compose/features/index.ts +3 -1
  166. package/src/core/compose/features/ripple.ts +4 -1
  167. package/src/core/compose/features/textlabel.ts +26 -2
  168. package/src/core/dom/create.ts +5 -0
  169. package/src/index.ts +9 -0
  170. package/src/styles/abstract/_theme.scss +115 -3
  171. package/src/styles/themes/_autumn.scss +21 -0
  172. package/src/styles/themes/_base-theme.scss +61 -0
  173. package/src/styles/themes/_baseline.scss +58 -0
  174. package/src/styles/themes/_bluekhaki.scss +125 -0
  175. package/src/styles/themes/_brownbeige.scss +125 -0
  176. package/src/styles/themes/_browngreen.scss +125 -0
  177. package/src/styles/themes/_forest.scss +6 -0
  178. package/src/styles/themes/_greenbeige.scss +125 -0
  179. package/src/styles/themes/_material.scss +125 -0
  180. package/src/styles/themes/_ocean.scss +6 -0
  181. package/src/styles/themes/_sageivory.scss +125 -0
  182. package/src/styles/themes/_spring.scss +6 -0
  183. package/src/styles/themes/_summer.scss +5 -0
  184. package/src/styles/themes/_sunset.scss +5 -0
  185. package/src/styles/themes/_tealcaramel.scss +125 -0
  186. package/src/styles/themes/_winter.scss +6 -0
  187. package/src/components/card/actions.ts +0 -48
  188. package/src/components/card/header.ts +0 -88
  189. package/src/components/card/media.ts +0 -52
  190. package/src/components/navigation/features/items.js +0 -192
  191. package/src/components/slider/features/appearance.ts +0 -94
  192. package/src/components/slider/features/disabled.ts +0 -43
  193. package/src/components/slider/features/events.ts +0 -164
  194. package/src/components/slider/features/interactions.ts +0 -261
  195. package/src/components/slider/features/keyboard.ts +0 -112
  196. package/src/core/collection/adapters/mongodb.js +0 -232
@@ -0,0 +1,613 @@
1
+ // src/components/timepicker/render.ts
2
+
3
+ import { TimePickerConfig, TimeValue, TIME_PICKER_TYPE, TIME_PICKER_ORIENTATION, TIME_FORMAT, TIME_PERIOD } from './types';
4
+ import {
5
+ DIAL_CONSTANTS,
6
+ TIME_CONSTANTS,
7
+ DEFAULT_CLOCK_ICON,
8
+ DEFAULT_KEYBOARD_ICON,
9
+ SELECTORS
10
+ } from './constants';
11
+ import { padZero, convertTo12Hour } from './utils';
12
+ import { renderClockDial, getTimeValueFromClick } from './clockdial';
13
+
14
+ /**
15
+ * Renders the time picker dialog
16
+ * @param {HTMLElement} container - Dialog container element
17
+ * @param {TimeValue} timeValue - Current time value
18
+ * @param {TimePickerConfig} config - Time picker configuration
19
+ * @param {Function} onTimeChange - Optional callback when time changes
20
+ */
21
+ export const renderTimePicker = (
22
+ container: HTMLElement,
23
+ timeValue: TimeValue,
24
+ config: TimePickerConfig,
25
+ onTimeChange?: (key: 'hours' | 'minutes' | 'seconds', value: number) => void
26
+ ): void => {
27
+ // Clear container content
28
+ container.innerHTML = '';
29
+
30
+ // Create title if provided
31
+ if (config.title) {
32
+ const title = document.createElement('div');
33
+ title.className = `${config.prefix}-time-picker-title`;
34
+ title.textContent = config.title;
35
+ title.id = `${config.prefix}-time-picker-title`;
36
+ container.appendChild(title);
37
+ }
38
+
39
+ // Create content container
40
+ const content = document.createElement('div');
41
+ content.className = `${config.prefix}-time-picker-content`;
42
+ container.appendChild(content);
43
+
44
+ // Create time input container (same for both modes)
45
+ const inputContainer = document.createElement('div');
46
+ inputContainer.className = `${config.prefix}-time-picker-input-container`;
47
+ content.appendChild(inputContainer);
48
+
49
+ // Determine display hours based on format
50
+ const { hours: displayHours } = config.format === TIME_FORMAT.MILITARY
51
+ ? { hours: timeValue.hours }
52
+ : convertTo12Hour(timeValue.hours);
53
+
54
+ // Create hours input field
55
+ const hoursInputContainer = document.createElement('div');
56
+ hoursInputContainer.className = `${config.prefix}-time-picker-time-input-field`;
57
+
58
+ const hoursInput = document.createElement('input');
59
+ hoursInput.type = 'number';
60
+ hoursInput.className = `${config.prefix}-time-picker-hours`;
61
+ hoursInput.min = config.format === TIME_FORMAT.MILITARY ? '0' : '1';
62
+ hoursInput.max = config.format === TIME_FORMAT.MILITARY ? '23' : '12';
63
+ hoursInput.value = padZero(displayHours);
64
+ hoursInput.setAttribute('data-type', 'hour');
65
+ hoursInput.setAttribute('inputmode', 'numeric');
66
+ hoursInput.setAttribute('pattern', '[0-9]*');
67
+
68
+ hoursInputContainer.appendChild(hoursInput);
69
+ inputContainer.appendChild(hoursInputContainer);
70
+
71
+ // Create separator
72
+ const separator = document.createElement('div');
73
+ separator.className = `${config.prefix}-time-picker-separator`;
74
+ separator.textContent = ':';
75
+ inputContainer.appendChild(separator);
76
+
77
+ // Create minutes input field
78
+ const minutesInputContainer = document.createElement('div');
79
+ minutesInputContainer.className = `${config.prefix}-time-picker-time-input-field`;
80
+
81
+ const minutesInput = document.createElement('input');
82
+ minutesInput.type = 'number';
83
+ minutesInput.className = `${config.prefix}-time-picker-minutes`;
84
+ minutesInput.min = '0';
85
+ minutesInput.max = '59';
86
+ minutesInput.value = padZero(timeValue.minutes);
87
+ minutesInput.setAttribute('data-type', 'minute');
88
+ minutesInput.setAttribute('inputmode', 'numeric');
89
+ minutesInput.setAttribute('pattern', '[0-9]*');
90
+
91
+ minutesInputContainer.appendChild(minutesInput);
92
+ inputContainer.appendChild(minutesInputContainer);
93
+
94
+ // Add seconds if enabled
95
+ let secondsInput;
96
+ if (config.showSeconds) {
97
+ const secondsSeparator = document.createElement('div');
98
+ secondsSeparator.className = `${config.prefix}-time-picker-separator`;
99
+ secondsSeparator.textContent = ':';
100
+ inputContainer.appendChild(secondsSeparator);
101
+
102
+ const secondsInputContainer = document.createElement('div');
103
+ secondsInputContainer.className = `${config.prefix}-time-picker-time-input-field`;
104
+
105
+ secondsInput = document.createElement('input');
106
+ secondsInput.type = 'number';
107
+ secondsInput.className = `${config.prefix}-time-picker-seconds`;
108
+ secondsInput.min = '0';
109
+ secondsInput.max = '59';
110
+ secondsInput.value = padZero(timeValue.seconds || 0);
111
+ secondsInput.setAttribute('data-type', 'second');
112
+ secondsInput.setAttribute('inputmode', 'numeric');
113
+ secondsInput.setAttribute('pattern', '[0-9]*');
114
+
115
+ const secondsLabel = document.createElement('label');
116
+ secondsLabel.className = `${config.prefix}-time-picker-input-label`;
117
+ secondsLabel.textContent = 'Second';
118
+
119
+ secondsInputContainer.appendChild(secondsInput);
120
+ secondsInputContainer.appendChild(secondsLabel);
121
+ inputContainer.appendChild(secondsInputContainer);
122
+ }
123
+
124
+ // Add period selector for 12-hour format
125
+ if (config.format === TIME_FORMAT.AMPM) {
126
+ const periodContainer = document.createElement('div');
127
+ periodContainer.className = `${config.prefix}-time-picker-period`;
128
+
129
+ const amPeriod = document.createElement('div');
130
+ amPeriod.className = `${config.prefix}-time-picker-period-am ${timeValue.period === TIME_PERIOD.AM ? `${config.prefix}-time-picker-period--selected` : ''}`;
131
+ amPeriod.textContent = TIME_PERIOD.AM;
132
+ amPeriod.setAttribute('role', 'button');
133
+ amPeriod.setAttribute('tabindex', '0');
134
+ amPeriod.setAttribute('aria-pressed', timeValue.period === TIME_PERIOD.AM ? 'true' : 'false');
135
+
136
+ const pmPeriod = document.createElement('div');
137
+ pmPeriod.className = `${config.prefix}-time-picker-period-pm ${timeValue.period === TIME_PERIOD.PM ? `${config.prefix}-time-picker-period--selected` : ''}`;
138
+ pmPeriod.textContent = TIME_PERIOD.PM;
139
+ pmPeriod.setAttribute('role', 'button');
140
+ pmPeriod.setAttribute('tabindex', '0');
141
+ pmPeriod.setAttribute('aria-pressed', timeValue.period === TIME_PERIOD.PM ? 'true' : 'false');
142
+
143
+ periodContainer.appendChild(amPeriod);
144
+ periodContainer.appendChild(pmPeriod);
145
+ inputContainer.appendChild(periodContainer);
146
+ }
147
+
148
+ // Create canvas-based dial container (shown/hidden based on mode)
149
+ const dialContainer = document.createElement('div');
150
+ dialContainer.className = `${config.prefix}-time-picker-dial`;
151
+ dialContainer.style.display = config.type === TIME_PICKER_TYPE.DIAL ? 'block' : 'none';
152
+ content.appendChild(dialContainer);
153
+
154
+ // Create canvas element
155
+ const canvas = document.createElement('canvas');
156
+ canvas.className = `${config.prefix}-time-picker-dial-canvas`;
157
+ canvas.width = DIAL_CONSTANTS.DIAMETER;
158
+ canvas.height = DIAL_CONSTANTS.DIAMETER;
159
+ canvas.style.width = `${DIAL_CONSTANTS.DIAMETER}px`;
160
+ canvas.style.height = `${DIAL_CONSTANTS.DIAMETER}px`;
161
+ dialContainer.appendChild(canvas);
162
+
163
+ // Create actions container
164
+ const actions = document.createElement('div');
165
+ actions.className = `${config.prefix}-time-picker-actions`;
166
+ container.appendChild(actions);
167
+
168
+ // Create type toggle button
169
+ const toggleTypeButton = document.createElement('button');
170
+ toggleTypeButton.className = `${config.prefix}-time-picker-toggle-type`;
171
+ toggleTypeButton.setAttribute('aria-label', config.type === TIME_PICKER_TYPE.DIAL
172
+ ? 'Switch to keyboard input'
173
+ : 'Switch to dial selector');
174
+ toggleTypeButton.innerHTML = config.type === TIME_PICKER_TYPE.DIAL
175
+ ? config.keyboardIcon || DEFAULT_KEYBOARD_ICON
176
+ : config.clockIcon || DEFAULT_CLOCK_ICON;
177
+ actions.appendChild(toggleTypeButton);
178
+
179
+ // Create action buttons container
180
+ const actionButtons = document.createElement('div');
181
+ actionButtons.className = `${config.prefix}-time-picker-action-buttons`;
182
+ actions.appendChild(actionButtons);
183
+
184
+ // Create cancel button
185
+ const cancelButton = document.createElement('button');
186
+ cancelButton.className = `${config.prefix}-time-picker-cancel`;
187
+ cancelButton.textContent = config.cancelText || 'Cancel';
188
+ cancelButton.setAttribute('type', 'button');
189
+ actionButtons.appendChild(cancelButton);
190
+
191
+ // Create confirm button
192
+ const confirmButton = document.createElement('button');
193
+ confirmButton.className = `${config.prefix}-time-picker-confirm`;
194
+ confirmButton.textContent = config.confirmText || 'OK';
195
+ confirmButton.setAttribute('type', 'button');
196
+ actionButtons.appendChild(confirmButton);
197
+
198
+ // Track active selector for clock dial
199
+ let activeSelector: 'hour' | 'minute' | 'second' = 'hour';
200
+
201
+ // Render initial clock dial if in dial mode
202
+ if (config.type === TIME_PICKER_TYPE.DIAL) {
203
+ setTimeout(() => {
204
+ renderClockDial(canvas, timeValue, {
205
+ type: config.type,
206
+ format: config.format,
207
+ showSeconds: config.showSeconds,
208
+ prefix: config.prefix,
209
+ activeSelector
210
+ });
211
+ }, 0);
212
+ }
213
+
214
+ // Add event listener for toggle button
215
+ toggleTypeButton.addEventListener('click', () => {
216
+ // Toggle dial visibility
217
+ if (dialContainer.style.display === 'none') {
218
+ // Switch to dial mode
219
+ dialContainer.style.display = 'block';
220
+ toggleTypeButton.innerHTML = config.keyboardIcon || DEFAULT_KEYBOARD_ICON;
221
+ toggleTypeButton.setAttribute('aria-label', 'Switch to keyboard input');
222
+
223
+ // Set focus on dial
224
+ setTimeout(() => {
225
+ canvas.focus();
226
+
227
+ // Apply active state to hour input by default
228
+ const hourElements = document.querySelectorAll(`.${config.prefix}-time-picker-hours`);
229
+ hourElements.forEach(el => el.setAttribute('data-active', 'true'));
230
+
231
+ const minuteElements = document.querySelectorAll(`.${config.prefix}-time-picker-minutes`);
232
+ minuteElements.forEach(el => el.setAttribute('data-active', 'false'));
233
+
234
+ const secondElements = document.querySelectorAll(`.${config.prefix}-time-picker-seconds`);
235
+ secondElements.forEach(el => el.setAttribute('data-active', 'false'));
236
+
237
+ // Render clock dial
238
+ renderClockDial(canvas, timeValue, {
239
+ type: TIME_PICKER_TYPE.DIAL,
240
+ format: config.format,
241
+ showSeconds: config.showSeconds,
242
+ prefix: config.prefix,
243
+ activeSelector: 'hour'
244
+ });
245
+ }, 50);
246
+ } else {
247
+ // Switch to input mode
248
+ dialContainer.style.display = 'none';
249
+ toggleTypeButton.innerHTML = config.clockIcon || DEFAULT_CLOCK_ICON;
250
+ toggleTypeButton.setAttribute('aria-label', 'Switch to dial selector');
251
+
252
+ // Focus on hours input
253
+ setTimeout(() => {
254
+ hoursInput.focus();
255
+ hoursInput.select();
256
+ }, 50);
257
+ }
258
+ });
259
+
260
+ // Handle input time changes
261
+ const handleInputChange = (e: Event) => {
262
+ const target = e.target as HTMLInputElement;
263
+ const type = target.getAttribute('data-type');
264
+ const value = target.value;
265
+
266
+ // Skip processing if the field is empty (user might be in the middle of typing)
267
+ if (value === '') return;
268
+
269
+ const numValue = parseInt(value, 10);
270
+ if (isNaN(numValue)) return;
271
+
272
+ if (type === 'hour') {
273
+ let newHours = numValue;
274
+
275
+ // Handle hour constraints
276
+ if (config.format === TIME_FORMAT.AMPM) {
277
+ // Special handling for 12-hour format
278
+ if (numValue < 0) newHours = 1;
279
+ if (numValue > 12) newHours = 12;
280
+
281
+ // Convert to 24h format internally
282
+ if (timeValue.period === 'PM' && newHours !== 12) {
283
+ newHours += 12;
284
+ } else if (timeValue.period === 'AM' && newHours === 12) {
285
+ newHours = 0;
286
+ }
287
+ } else {
288
+ // 24-hour format validation
289
+ if (numValue < 0) newHours = 0;
290
+ if (numValue > 23) newHours = 23;
291
+ }
292
+
293
+ timeValue.hours = newHours;
294
+
295
+ // Set this field as active for the dial
296
+ activeSelector = 'hour';
297
+
298
+ // Update active states for visualization
299
+ hoursInput.setAttribute('data-active', 'true');
300
+ minutesInput.setAttribute('data-active', 'false');
301
+ if (secondsInput) secondsInput.setAttribute('data-active', 'false');
302
+
303
+ // Always update the dial regardless of visibility
304
+ renderClockDial(canvas, timeValue, {
305
+ type: TIME_PICKER_TYPE.DIAL,
306
+ format: config.format,
307
+ showSeconds: config.showSeconds,
308
+ prefix: config.prefix,
309
+ activeSelector
310
+ });
311
+
312
+ if (onTimeChange) {
313
+ onTimeChange('hours', newHours);
314
+ }
315
+ } else if (type === 'minute') {
316
+ let newMinutes = numValue;
317
+
318
+ // Handle minute constraints
319
+ if (numValue < 0) newMinutes = 0;
320
+ if (numValue > 59) newMinutes = 59;
321
+
322
+ timeValue.minutes = newMinutes;
323
+
324
+ // Set this field as active for the dial
325
+ activeSelector = 'minute';
326
+
327
+ // Update active states for visualization
328
+ hoursInput.setAttribute('data-active', 'false');
329
+ minutesInput.setAttribute('data-active', 'true');
330
+ if (secondsInput) secondsInput.setAttribute('data-active', 'false');
331
+
332
+ // Always update the dial regardless of visibility
333
+ renderClockDial(canvas, timeValue, {
334
+ type: TIME_PICKER_TYPE.DIAL,
335
+ format: config.format,
336
+ showSeconds: config.showSeconds,
337
+ prefix: config.prefix,
338
+ activeSelector
339
+ });
340
+
341
+ if (onTimeChange) {
342
+ onTimeChange('minutes', newMinutes);
343
+ }
344
+ } else if (type === 'second') {
345
+ let newSeconds = numValue;
346
+
347
+ // Handle second constraints
348
+ if (numValue < 0) newSeconds = 0;
349
+ if (numValue > 59) newSeconds = 59;
350
+
351
+ timeValue.seconds = newSeconds;
352
+
353
+ // Set this field as active for the dial
354
+ activeSelector = 'second';
355
+
356
+ // Update active states for visualization
357
+ hoursInput.setAttribute('data-active', 'false');
358
+ minutesInput.setAttribute('data-active', 'false');
359
+ if (secondsInput) secondsInput.setAttribute('data-active', 'true');
360
+
361
+ // Always update the dial regardless of visibility
362
+ renderClockDial(canvas, timeValue, {
363
+ type: TIME_PICKER_TYPE.DIAL,
364
+ format: config.format,
365
+ showSeconds: config.showSeconds,
366
+ prefix: config.prefix,
367
+ activeSelector
368
+ });
369
+
370
+ if (onTimeChange) {
371
+ onTimeChange('seconds', newSeconds);
372
+ }
373
+ }
374
+ };
375
+
376
+ // Event handlers for input fields
377
+ hoursInput.addEventListener('input', handleInputChange);
378
+ minutesInput.addEventListener('input', handleInputChange);
379
+ if (secondsInput) {
380
+ secondsInput.addEventListener('input', handleInputChange);
381
+ }
382
+
383
+ // Set up keyboard navigation
384
+ hoursInput.addEventListener('keyup', (e) => {
385
+ if (e.key === 'Enter') {
386
+ minutesInput.focus();
387
+ minutesInput.select();
388
+ }
389
+ });
390
+
391
+ minutesInput.addEventListener('keyup', (e) => {
392
+ if (e.key === 'Enter') {
393
+ if (config.showSeconds && secondsInput) {
394
+ secondsInput.focus();
395
+ secondsInput.select();
396
+ } else {
397
+ confirmButton.focus();
398
+ }
399
+ }
400
+ });
401
+
402
+ if (secondsInput) {
403
+ secondsInput.addEventListener('keyup', (e) => {
404
+ if (e.key === 'Enter') {
405
+ confirmButton.focus();
406
+ }
407
+ });
408
+ }
409
+
410
+ // Handle period selection (AM/PM)
411
+ const handlePeriodChange = (period: TIME_PERIOD) => {
412
+ if (timeValue.period !== period) {
413
+ const oldPeriod = timeValue.period;
414
+ timeValue.period = period;
415
+
416
+ // Adjust hours when switching between AM/PM
417
+ if (oldPeriod === TIME_PERIOD.AM && period === TIME_PERIOD.PM) {
418
+ if (timeValue.hours < 12) {
419
+ timeValue.hours += 12;
420
+ }
421
+ } else if (oldPeriod === TIME_PERIOD.PM && period === TIME_PERIOD.AM) {
422
+ if (timeValue.hours >= 12) {
423
+ timeValue.hours -= 12;
424
+ }
425
+ }
426
+
427
+ // Update display for 12-hour format
428
+ if (config.format === TIME_FORMAT.AMPM) {
429
+ const displayHours = timeValue.hours === 0 ? 12 : (timeValue.hours > 12 ? timeValue.hours - 12 : timeValue.hours);
430
+ hoursInput.value = padZero(displayHours);
431
+ }
432
+
433
+ // Update period selectors
434
+ document.querySelectorAll(`.${config.prefix}-time-picker-period-am, .${config.prefix}-time-picker-period-pm`)
435
+ .forEach(el => {
436
+ el.classList.remove(`${config.prefix}-time-picker-period--selected`);
437
+ el.setAttribute('aria-pressed', 'false');
438
+ });
439
+
440
+ const selectedPeriod = document.querySelector(`.${config.prefix}-time-picker-period-${period.toLowerCase()}`);
441
+ if (selectedPeriod) {
442
+ selectedPeriod.classList.add(`${config.prefix}-time-picker-period--selected`);
443
+ selectedPeriod.setAttribute('aria-pressed', 'true');
444
+ }
445
+
446
+ // Update dial if visible
447
+ if (dialContainer.style.display === 'block') {
448
+ renderClockDial(canvas, timeValue, {
449
+ type: TIME_PICKER_TYPE.DIAL,
450
+ format: config.format,
451
+ showSeconds: config.showSeconds,
452
+ prefix: config.prefix,
453
+ activeSelector
454
+ });
455
+ }
456
+
457
+ if (onTimeChange) {
458
+ onTimeChange('hours', timeValue.hours);
459
+ }
460
+ }
461
+ };
462
+
463
+ // Add event listeners for period selectors
464
+ if (config.format === TIME_FORMAT.AMPM) {
465
+ const amPeriodElement = document.querySelector(`.${config.prefix}-time-picker-period-am`);
466
+ const pmPeriodElement = document.querySelector(`.${config.prefix}-time-picker-period-pm`);
467
+
468
+ if (amPeriodElement) {
469
+ amPeriodElement.addEventListener('click', () => {
470
+ handlePeriodChange(TIME_PERIOD.AM);
471
+ });
472
+ }
473
+
474
+ if (pmPeriodElement) {
475
+ pmPeriodElement.addEventListener('click', () => {
476
+ handlePeriodChange(TIME_PERIOD.PM);
477
+ });
478
+ }
479
+ }
480
+
481
+ // Set up the clock dial interaction
482
+ if (config.type === TIME_PICKER_TYPE.DIAL || dialContainer.style.display === 'block') {
483
+ // Handle clock dial
484
+ canvas.addEventListener('click', (event) => {
485
+ const rect = canvas.getBoundingClientRect();
486
+ const x = event.clientX - rect.left;
487
+ const y = event.clientY - rect.top;
488
+
489
+ const selectedValue = getTimeValueFromClick(canvas, x, y, {
490
+ type: TIME_PICKER_TYPE.DIAL,
491
+ format: config.format,
492
+ showSeconds: config.showSeconds,
493
+ prefix: config.prefix,
494
+ activeSelector
495
+ });
496
+
497
+ if (selectedValue !== null) {
498
+ if (activeSelector === 'hour') {
499
+ let newHours = selectedValue;
500
+
501
+ // Adjust for 12-hour format
502
+ if (config.format === TIME_FORMAT.AMPM) {
503
+ if (timeValue.period === TIME_PERIOD.PM && selectedValue !== 12) {
504
+ newHours += 12;
505
+ } else if (timeValue.period === TIME_PERIOD.AM && selectedValue === 12) {
506
+ newHours = 0;
507
+ }
508
+ }
509
+
510
+ timeValue.hours = newHours;
511
+
512
+ // Update input display
513
+ if (config.format === TIME_FORMAT.AMPM) {
514
+ const displayHours = newHours === 0 ? 12 : (newHours > 12 ? newHours - 12 : newHours);
515
+ hoursInput.value = padZero(displayHours);
516
+ } else {
517
+ hoursInput.value = padZero(newHours);
518
+ }
519
+
520
+ if (onTimeChange) {
521
+ onTimeChange('hours', newHours);
522
+ }
523
+ } else if (activeSelector === 'minute') {
524
+ timeValue.minutes = selectedValue;
525
+ minutesInput.value = padZero(selectedValue);
526
+
527
+ if (onTimeChange) {
528
+ onTimeChange('minutes', selectedValue);
529
+ }
530
+ } else if (activeSelector === 'second' && secondsInput) {
531
+ timeValue.seconds = selectedValue;
532
+ secondsInput.value = padZero(selectedValue);
533
+
534
+ if (onTimeChange) {
535
+ onTimeChange('seconds', selectedValue);
536
+ }
537
+ }
538
+
539
+ // Update dial
540
+ renderClockDial(canvas, timeValue, {
541
+ type: TIME_PICKER_TYPE.DIAL,
542
+ format: config.format,
543
+ showSeconds: config.showSeconds,
544
+ prefix: config.prefix,
545
+ activeSelector
546
+ });
547
+ }
548
+ });
549
+
550
+ // Setup clicking on input fields to change active selector in dial mode
551
+ hoursInput.addEventListener('click', () => {
552
+ if (dialContainer.style.display === 'block') {
553
+ activeSelector = 'hour';
554
+
555
+ // Update active states
556
+ hoursInput.setAttribute('data-active', 'true');
557
+ minutesInput.setAttribute('data-active', 'false');
558
+ if (secondsInput) secondsInput.setAttribute('data-active', 'false');
559
+
560
+ // Update dial
561
+ renderClockDial(canvas, timeValue, {
562
+ type: TIME_PICKER_TYPE.DIAL,
563
+ format: config.format,
564
+ showSeconds: config.showSeconds,
565
+ prefix: config.prefix,
566
+ activeSelector
567
+ });
568
+ }
569
+ });
570
+
571
+ minutesInput.addEventListener('click', () => {
572
+ if (dialContainer.style.display === 'block') {
573
+ activeSelector = 'minute';
574
+
575
+ // Update active states
576
+ hoursInput.setAttribute('data-active', 'false');
577
+ minutesInput.setAttribute('data-active', 'true');
578
+ if (secondsInput) secondsInput.setAttribute('data-active', 'false');
579
+
580
+ // Update dial
581
+ renderClockDial(canvas, timeValue, {
582
+ type: TIME_PICKER_TYPE.DIAL,
583
+ format: config.format,
584
+ showSeconds: config.showSeconds,
585
+ prefix: config.prefix,
586
+ activeSelector
587
+ });
588
+ }
589
+ });
590
+
591
+ if (secondsInput) {
592
+ secondsInput.addEventListener('click', () => {
593
+ if (dialContainer.style.display === 'block') {
594
+ activeSelector = 'second';
595
+
596
+ // Update active states
597
+ hoursInput.setAttribute('data-active', 'false');
598
+ minutesInput.setAttribute('data-active', 'false');
599
+ secondsInput.setAttribute('data-active', 'true');
600
+
601
+ // Update dial
602
+ renderClockDial(canvas, timeValue, {
603
+ type: TIME_PICKER_TYPE.DIAL,
604
+ format: config.format,
605
+ showSeconds: config.showSeconds,
606
+ prefix: config.prefix,
607
+ activeSelector
608
+ });
609
+ }
610
+ });
611
+ }
612
+ }
613
+ };