mtrl 0.2.6 → 0.2.8

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 (226) hide show
  1. package/demo/build.ts +349 -0
  2. package/demo/index.html +110 -0
  3. package/demo/main.js +448 -0
  4. package/demo/styles.css +239 -0
  5. package/index.ts +18 -0
  6. package/package.json +14 -3
  7. package/server.ts +86 -0
  8. package/src/components/badge/api.ts +70 -63
  9. package/src/components/badge/badge.ts +16 -2
  10. package/src/components/badge/config.ts +66 -13
  11. package/src/components/badge/features.ts +51 -42
  12. package/src/components/badge/index.ts +27 -2
  13. package/src/components/badge/types.ts +62 -30
  14. package/src/components/bottom-app-bar/bottom-app-bar.ts +154 -0
  15. package/src/components/bottom-app-bar/config.ts +29 -0
  16. package/src/components/bottom-app-bar/index.ts +17 -0
  17. package/src/components/bottom-app-bar/types.ts +114 -0
  18. package/src/components/button/api.ts +5 -0
  19. package/src/components/button/button.ts +0 -1
  20. package/src/components/button/config.ts +6 -2
  21. package/src/components/button/index.ts +10 -2
  22. package/src/components/button/types.ts +20 -2
  23. package/src/components/card/card.ts +13 -25
  24. package/src/components/card/config.ts +83 -30
  25. package/src/components/card/content.ts +8 -10
  26. package/src/components/card/features.ts +4 -3
  27. package/src/components/card/index.ts +29 -2
  28. package/src/components/card/types.ts +33 -22
  29. package/src/components/checkbox/config.ts +3 -4
  30. package/src/components/checkbox/index.ts +1 -2
  31. package/src/components/checkbox/types.ts +12 -3
  32. package/src/components/chip/api.ts +170 -221
  33. package/src/components/chip/chip.ts +34 -302
  34. package/src/components/chip/config.ts +1 -2
  35. package/src/components/chip/index.ts +10 -2
  36. package/src/components/chip/types.ts +224 -35
  37. package/src/components/datepicker/api.ts +265 -0
  38. package/src/components/datepicker/config.ts +141 -0
  39. package/src/components/datepicker/datepicker.ts +341 -0
  40. package/src/components/datepicker/index.ts +12 -0
  41. package/src/components/datepicker/render.ts +450 -0
  42. package/src/components/datepicker/types.ts +397 -0
  43. package/src/components/datepicker/utils.ts +289 -0
  44. package/src/components/dialog/api.ts +55 -21
  45. package/src/components/dialog/config.ts +12 -9
  46. package/src/components/dialog/dialog.ts +6 -3
  47. package/src/components/dialog/features.ts +345 -151
  48. package/src/components/dialog/index.ts +38 -8
  49. package/src/components/dialog/types.ts +40 -14
  50. package/src/components/divider/config.ts +81 -0
  51. package/src/components/divider/divider.ts +37 -0
  52. package/src/components/divider/features.ts +207 -0
  53. package/src/components/divider/index.ts +9 -0
  54. package/src/components/divider/types.ts +55 -0
  55. package/src/components/extended-fab/api.ts +141 -0
  56. package/src/components/extended-fab/config.ts +112 -0
  57. package/src/components/extended-fab/extended-fab.ts +125 -0
  58. package/src/components/extended-fab/index.ts +9 -0
  59. package/src/components/extended-fab/types.ts +304 -0
  60. package/src/components/fab/api.ts +97 -0
  61. package/src/components/fab/config.ts +93 -0
  62. package/src/components/fab/fab.ts +67 -0
  63. package/src/components/fab/index.ts +9 -0
  64. package/src/components/fab/types.ts +251 -0
  65. package/src/components/list/config.ts +4 -5
  66. package/src/components/list/features.ts +6 -7
  67. package/src/components/list/index.ts +7 -9
  68. package/src/components/list/list-item.ts +12 -13
  69. package/src/components/list/types.ts +50 -5
  70. package/src/components/list/utils.ts +30 -3
  71. package/src/components/menu/features/items-manager.ts +9 -9
  72. package/src/components/menu/features/positioning.ts +7 -7
  73. package/src/components/menu/features/visibility.ts +7 -7
  74. package/src/components/menu/index.ts +7 -9
  75. package/src/components/menu/menu-item.ts +6 -6
  76. package/src/components/menu/menu.ts +22 -0
  77. package/src/components/menu/types.ts +29 -10
  78. package/src/components/menu/utils.ts +67 -0
  79. package/src/components/navigation/api.ts +78 -50
  80. package/src/components/navigation/config.ts +22 -10
  81. package/src/components/navigation/features/items.ts +284 -0
  82. package/src/components/navigation/index.ts +0 -6
  83. package/src/components/navigation/nav-item.ts +70 -33
  84. package/src/components/navigation/navigation.ts +53 -3
  85. package/src/components/navigation/types.ts +117 -70
  86. package/src/components/progress/api.ts +2 -3
  87. package/src/components/progress/config.ts +2 -3
  88. package/src/components/progress/index.ts +0 -1
  89. package/src/components/progress/progress.ts +1 -2
  90. package/src/components/progress/types.ts +186 -33
  91. package/src/components/radios/config.ts +1 -1
  92. package/src/components/radios/index.ts +0 -1
  93. package/src/components/radios/types.ts +0 -7
  94. package/src/components/search/api.ts +203 -0
  95. package/src/components/search/config.ts +86 -0
  96. package/src/components/search/features/index.ts +4 -0
  97. package/src/components/search/features/search.ts +717 -0
  98. package/src/components/search/features/states.ts +169 -0
  99. package/src/components/search/features/structure.ts +197 -0
  100. package/src/components/search/index.ts +7 -0
  101. package/src/components/search/search.ts +52 -0
  102. package/src/components/search/types.ts +175 -0
  103. package/src/components/segmented-button/config.ts +80 -0
  104. package/src/components/segmented-button/index.ts +4 -0
  105. package/src/components/segmented-button/segment.ts +154 -0
  106. package/src/components/segmented-button/segmented-button.ts +249 -0
  107. package/src/components/segmented-button/types.ts +254 -0
  108. package/src/components/slider/accessibility.md +5 -5
  109. package/src/components/slider/api.ts +41 -120
  110. package/src/components/slider/config.ts +51 -47
  111. package/src/components/slider/features/handlers.ts +495 -0
  112. package/src/components/slider/features/index.ts +1 -2
  113. package/src/components/slider/features/slider.ts +66 -84
  114. package/src/components/slider/features/states.ts +195 -0
  115. package/src/components/slider/features/structure.ts +136 -206
  116. package/src/components/slider/features/ui.ts +145 -206
  117. package/src/components/slider/index.ts +2 -11
  118. package/src/components/slider/slider.ts +9 -12
  119. package/src/components/slider/types.ts +67 -26
  120. package/src/components/snackbar/config.ts +2 -3
  121. package/src/components/snackbar/constants.ts +0 -32
  122. package/src/components/snackbar/index.ts +0 -1
  123. package/src/components/snackbar/position.ts +9 -1
  124. package/src/components/snackbar/types.ts +122 -46
  125. package/src/components/switch/config.ts +2 -3
  126. package/src/components/switch/index.ts +0 -1
  127. package/src/components/switch/types.ts +3 -2
  128. package/src/components/tabs/config.ts +3 -4
  129. package/src/components/tabs/features.ts +4 -2
  130. package/src/components/tabs/index.ts +0 -15
  131. package/src/components/tabs/indicator.ts +73 -13
  132. package/src/components/tabs/tab-api.ts +12 -4
  133. package/src/components/tabs/tab.ts +18 -6
  134. package/src/components/tabs/types.ts +23 -5
  135. package/src/components/textfield/config.ts +2 -3
  136. package/src/components/textfield/index.ts +0 -1
  137. package/src/components/textfield/types.ts +17 -3
  138. package/src/components/timepicker/README.md +277 -0
  139. package/src/components/timepicker/api.ts +632 -0
  140. package/src/components/timepicker/clockdial.ts +482 -0
  141. package/src/components/timepicker/config.ts +228 -0
  142. package/src/components/timepicker/index.ts +3 -0
  143. package/src/components/timepicker/render.ts +613 -0
  144. package/src/components/timepicker/timepicker.ts +117 -0
  145. package/src/components/timepicker/types.ts +336 -0
  146. package/src/components/timepicker/utils.ts +241 -0
  147. package/src/components/tooltip/api.ts +1 -1
  148. package/src/components/tooltip/config.ts +27 -6
  149. package/src/components/tooltip/index.ts +0 -1
  150. package/src/components/tooltip/types.ts +13 -3
  151. package/src/components/top-app-bar/config.ts +83 -0
  152. package/src/components/top-app-bar/index.ts +11 -0
  153. package/src/components/top-app-bar/top-app-bar.ts +316 -0
  154. package/src/components/top-app-bar/types.ts +140 -0
  155. package/src/core/build/_ripple.scss +6 -6
  156. package/src/core/build/ripple.ts +72 -95
  157. package/src/core/compose/features/icon.ts +3 -1
  158. package/src/core/compose/features/ripple.ts +4 -1
  159. package/src/core/compose/features/textlabel.ts +23 -2
  160. package/src/core/dom/create.ts +5 -0
  161. package/src/index.ts +9 -0
  162. package/src/styles/abstract/_theme.scss +9 -1
  163. package/src/styles/components/_badge.scss +182 -0
  164. package/src/styles/components/_bottom-app-bar.scss +103 -0
  165. package/src/{components/button/_styles.scss → styles/components/_button.scss} +0 -10
  166. package/src/{components/checkbox/_styles.scss → styles/components/_checkbox.scss} +0 -2
  167. package/src/styles/components/_datepicker.scss +358 -0
  168. package/src/styles/components/_dialog.scss +259 -0
  169. package/src/styles/components/_divider.scss +57 -0
  170. package/src/styles/components/_extended-fab.scss +267 -0
  171. package/src/styles/components/_fab.scss +225 -0
  172. package/src/{components/navigation/_styles.scss → styles/components/_navigation.scss} +1 -0
  173. package/src/styles/components/_search.scss +306 -0
  174. package/src/styles/components/_segmented-button.scss +117 -0
  175. package/src/{components/slider/_styles.scss → styles/components/_slider.scss} +83 -24
  176. package/src/{components/switch/_styles.scss → styles/components/_switch.scss} +0 -2
  177. package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +95 -33
  178. package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +70 -67
  179. package/src/styles/components/_timepicker.scss +451 -0
  180. package/src/styles/components/_top-app-bar.scss +225 -0
  181. package/src/styles/main.scss +98 -49
  182. package/src/styles/themes/_autumn.scss +21 -0
  183. package/src/styles/themes/_base-theme.scss +61 -0
  184. package/src/styles/themes/_baseline.scss +58 -0
  185. package/src/styles/themes/_bluekhaki.scss +125 -0
  186. package/src/styles/themes/_brownbeige.scss +125 -0
  187. package/src/styles/themes/_browngreen.scss +125 -0
  188. package/src/styles/themes/_forest.scss +6 -0
  189. package/src/styles/themes/_greenbeige.scss +125 -0
  190. package/src/styles/themes/_material.scss +125 -0
  191. package/src/styles/themes/_ocean.scss +6 -0
  192. package/src/styles/themes/_sageivory.scss +125 -0
  193. package/src/styles/themes/_spring.scss +6 -0
  194. package/src/styles/themes/_summer.scss +5 -0
  195. package/src/styles/themes/_sunset.scss +5 -0
  196. package/src/styles/themes/_tealcaramel.scss +125 -0
  197. package/src/styles/themes/_winter.scss +6 -0
  198. package/src/components/badge/_styles.scss +0 -174
  199. package/src/components/badge/constants.ts +0 -30
  200. package/src/components/button/constants.ts +0 -11
  201. package/src/components/card/constants.ts +0 -84
  202. package/src/components/dialog/_styles.scss +0 -213
  203. package/src/components/dialog/constants.ts +0 -32
  204. package/src/components/menu/constants.ts +0 -154
  205. package/src/components/navigation/constants.ts +0 -200
  206. package/src/components/navigation/features/items.js +0 -192
  207. package/src/components/progress/constants.ts +0 -29
  208. package/src/components/slider/features/appearance.ts +0 -94
  209. package/src/components/slider/features/disabled.ts +0 -68
  210. package/src/components/slider/features/events.ts +0 -164
  211. package/src/components/slider/features/interactions.ts +0 -396
  212. package/src/components/slider/features/keyboard.ts +0 -233
  213. package/src/components/switch/constants.ts +0 -80
  214. package/src/components/tabs/constants.ts +0 -89
  215. package/src/core/collection/adapters/mongodb.js +0 -232
  216. /package/src/{components/card/_styles.scss → styles/components/_card.scss} +0 -0
  217. /package/src/{components/carousel/_styles.scss → styles/components/_carousel.scss} +0 -0
  218. /package/src/{components/chip/_styles.scss → styles/components/_chip.scss} +0 -0
  219. /package/src/{components/list/_styles.scss → styles/components/_list.scss} +0 -0
  220. /package/src/{components/menu/_styles.scss → styles/components/_menu.scss} +0 -0
  221. /package/src/{components/progress/_styles.scss → styles/components/_progress.scss} +0 -0
  222. /package/src/{components/radios/_styles.scss → styles/components/_radios.scss} +0 -0
  223. /package/src/{components/sheet/_styles.scss → styles/components/_sheet.scss} +0 -0
  224. /package/src/{components/snackbar/_styles.scss → styles/components/_snackbar.scss} +0 -0
  225. /package/src/{components/tooltip/_styles.scss → styles/components/_tooltip.scss} +0 -0
  226. /package/src/styles/utilities/{_color.scss → _colors.scss} +0 -0
@@ -0,0 +1,632 @@
1
+ // src/components/timePicker/api.ts
2
+
3
+ import {
4
+ TimePickerComponent,
5
+ TimePickerConfig,
6
+ TimeValue,
7
+ TIME_PICKER_TYPE,
8
+ TIME_PICKER_ORIENTATION,
9
+ TIME_FORMAT,
10
+ TIME_PERIOD
11
+ } from './types';
12
+ import { EVENTS, SELECTORS } from './config';
13
+ import { formatTime, padZero } from './utils';
14
+ import { renderTimePicker } from './render';
15
+ import { renderClockDial, getTimeValueFromClick } from './clockdial';
16
+
17
+ interface ApiOptions {
18
+ events: {
19
+ on: (event: string, handler: Function) => any;
20
+ off: (event: string, handler: Function) => any;
21
+ emit: (event: string, data?: any) => any;
22
+ };
23
+ lifecycle: {
24
+ destroy: () => void;
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Creates the API for TimePicker component
30
+ *
31
+ * @param baseComponent - Base component with element and events
32
+ * @param modalElement - Modal overlay element
33
+ * @param dialogElement - Dialog content element
34
+ * @param timeValue - Current time value
35
+ * @param config - Component configuration
36
+ * @param options - API options with events and lifecycle methods
37
+ * @returns TimePicker component API
38
+ */
39
+ export const createTimePickerAPI = (
40
+ baseComponent: any,
41
+ modalElement: HTMLElement,
42
+ dialogElement: HTMLElement,
43
+ timeValue: TimeValue,
44
+ config: TimePickerConfig,
45
+ options: ApiOptions
46
+ ): TimePickerComponent => {
47
+ // Track open state
48
+ let isOpen = !!config.isOpen;
49
+
50
+ // Create event handlers
51
+ const handleKeydown = (event: KeyboardEvent) => {
52
+ if (event.key === 'Escape') {
53
+ timePickerAPI.close();
54
+ options.events.emit(EVENTS.CANCEL);
55
+ }
56
+ };
57
+
58
+ const handleClickOutside = (event: MouseEvent) => {
59
+ if (event.target === modalElement) {
60
+ timePickerAPI.close();
61
+ }
62
+ };
63
+
64
+ // Create time picker API
65
+ const timePickerAPI: TimePickerComponent = {
66
+ element: baseComponent.element,
67
+ modalElement,
68
+ dialogElement,
69
+ isOpen,
70
+
71
+ open() {
72
+ if (isOpen) return this;
73
+
74
+ // Show modal
75
+ modalElement.style.display = 'block';
76
+
77
+ // Add the active class to trigger animations
78
+ // We need to force a reflow before adding the active class for the transition to work
79
+ void modalElement.offsetWidth; // Force reflow
80
+ modalElement.classList.add('active');
81
+ dialogElement.classList.add('active');
82
+
83
+ // Add event listeners
84
+ document.addEventListener('keydown', handleKeydown);
85
+ modalElement.addEventListener('click', handleClickOutside);
86
+
87
+ // Update state
88
+ isOpen = true;
89
+ baseComponent.element.classList.add(`${config.prefix}-time-picker--open`);
90
+
91
+ // Force re-render to ensure canvas is drawn after dialog is visible
92
+ // This ensures the canvas has proper dimensions for rendering
93
+ setTimeout(() => {
94
+ renderTimePicker(dialogElement, timeValue, config);
95
+ }, 50);
96
+
97
+ // Emit open event
98
+ options.events.emit(EVENTS.OPEN);
99
+
100
+ // Call onOpen callback if provided
101
+ if (config.onOpen) {
102
+ config.onOpen();
103
+ }
104
+
105
+ return this;
106
+ },
107
+
108
+ close() {
109
+ if (!isOpen) return this;
110
+
111
+ // Remove active classes to trigger fade-out transition
112
+ modalElement.classList.remove('active');
113
+ dialogElement.classList.remove('active');
114
+
115
+ // Use setTimeout to let the transition finish before hiding completely
116
+ setTimeout(() => {
117
+ // Hide modal
118
+ modalElement.style.display = 'none';
119
+ }, 300); // Match the transition duration
120
+
121
+ // Remove event listeners
122
+ document.removeEventListener('keydown', handleKeydown);
123
+ modalElement.removeEventListener('click', handleClickOutside);
124
+
125
+ // Update state
126
+ isOpen = false;
127
+ baseComponent.element.classList.remove(`${config.prefix}-time-picker--open`);
128
+
129
+ // Emit close event
130
+ options.events.emit(EVENTS.CLOSE);
131
+
132
+ // Call onClose callback if provided
133
+ if (config.onClose) {
134
+ config.onClose();
135
+ }
136
+
137
+ return this;
138
+ },
139
+
140
+ toggle() {
141
+ return isOpen ? this.close() : this.open();
142
+ },
143
+
144
+ getValue() {
145
+ return formatTime(timeValue, config.format === TIME_FORMAT.MILITARY);
146
+ },
147
+
148
+ getTimeObject() {
149
+ return { ...timeValue };
150
+ },
151
+
152
+ setValue(time: string) {
153
+ try {
154
+ // Parse time string (throw error if invalid)
155
+ const parsedTime = time.split(':');
156
+ const hours = parseInt(parsedTime[0], 10);
157
+ const minutes = parseInt(parsedTime[1], 10);
158
+ const seconds = parsedTime[2] ? parseInt(parsedTime[2], 10) : 0;
159
+
160
+ if (
161
+ isNaN(hours) || hours < 0 || hours > 23 ||
162
+ isNaN(minutes) || minutes < 0 || minutes > 59 ||
163
+ isNaN(seconds) || seconds < 0 || seconds > 59
164
+ ) {
165
+ throw new Error('Invalid time format. Use HH:MM or HH:MM:SS (24-hour format).');
166
+ }
167
+
168
+ // Update time value
169
+ timeValue.hours = hours;
170
+ timeValue.minutes = minutes;
171
+ timeValue.seconds = seconds;
172
+ timeValue.period = hours >= 12 ? 'PM' : 'AM';
173
+
174
+ // Re-render time picker
175
+ renderTimePicker(dialogElement, timeValue, config);
176
+
177
+ // Emit change event
178
+ options.events.emit(EVENTS.CHANGE, this.getValue());
179
+
180
+ // Call onChange callback if provided
181
+ if (config.onChange) {
182
+ config.onChange(this.getValue());
183
+ }
184
+ } catch (error) {
185
+ console.error('Error setting time value:', error);
186
+ }
187
+
188
+ return this;
189
+ },
190
+
191
+ setType(type: TIME_PICKER_TYPE) {
192
+ if (config.type === type) return this;
193
+
194
+ // Update config
195
+ config.type = type;
196
+
197
+ // Update class
198
+ dialogElement.classList.remove(
199
+ `${config.prefix}-time-picker-dialog--${TIME_PICKER_TYPE.DIAL}`,
200
+ `${config.prefix}-time-picker-dialog--${TIME_PICKER_TYPE.INPUT}`
201
+ );
202
+ dialogElement.classList.add(`${config.prefix}-time-picker-dialog--${type}`);
203
+
204
+ // Re-render time picker
205
+ renderTimePicker(dialogElement, timeValue, config);
206
+
207
+ return this;
208
+ },
209
+
210
+ getType() {
211
+ return config.type;
212
+ },
213
+
214
+ setFormat(format: TIME_FORMAT) {
215
+ if (config.format === format) return this;
216
+
217
+ // Update config
218
+ config.format = format;
219
+
220
+ // Update class
221
+ dialogElement.classList.remove(
222
+ `${config.prefix}-time-picker-dialog--${TIME_FORMAT.AMPM}`,
223
+ `${config.prefix}-time-picker-dialog--${TIME_FORMAT.MILITARY}`
224
+ );
225
+ dialogElement.classList.add(`${config.prefix}-time-picker-dialog--${format}`);
226
+
227
+ // Adjust time value if needed
228
+ if (format === TIME_FORMAT.MILITARY) {
229
+ // No need to change the hours, just ensure period is set correctly
230
+ timeValue.period = timeValue.hours >= 12 ? 'PM' : 'AM';
231
+ } else {
232
+ // Convert 24h to 12h display (though internally we keep 24h)
233
+ timeValue.period = timeValue.hours >= 12 ? 'PM' : 'AM';
234
+ }
235
+
236
+ // Re-render time picker
237
+ renderTimePicker(dialogElement, timeValue, config);
238
+
239
+ // Emit change event
240
+ options.events.emit(EVENTS.CHANGE, this.getValue());
241
+
242
+ return this;
243
+ },
244
+
245
+ getFormat() {
246
+ return config.format;
247
+ },
248
+
249
+ setOrientation(orientation: TIME_PICKER_ORIENTATION) {
250
+ if (config.orientation === orientation) return this;
251
+
252
+ // Update config
253
+ config.orientation = orientation;
254
+
255
+ // Update class
256
+ dialogElement.classList.remove(
257
+ `${config.prefix}-time-picker-dialog--${TIME_PICKER_ORIENTATION.VERTICAL}`,
258
+ `${config.prefix}-time-picker-dialog--${TIME_PICKER_ORIENTATION.HORIZONTAL}`
259
+ );
260
+ dialogElement.classList.add(`${config.prefix}-time-picker-dialog--${orientation}`);
261
+
262
+ // Re-render time picker
263
+ renderTimePicker(dialogElement, timeValue, config);
264
+
265
+ return this;
266
+ },
267
+
268
+ getOrientation() {
269
+ return config.orientation;
270
+ },
271
+
272
+ setTitle(title: string) {
273
+ config.title = title;
274
+
275
+ // Update title element if it exists
276
+ const titleElement = dialogElement.querySelector(SELECTORS.TITLE);
277
+ if (titleElement) {
278
+ titleElement.textContent = title;
279
+ } else {
280
+ // Re-render to add title
281
+ renderTimePicker(dialogElement, timeValue, config);
282
+ }
283
+
284
+ return this;
285
+ },
286
+
287
+ getTitle() {
288
+ return config.title || '';
289
+ },
290
+
291
+ destroy() {
292
+ // Close if open
293
+ if (isOpen) {
294
+ this.close();
295
+ }
296
+
297
+ // Remove from DOM
298
+ if (modalElement && modalElement.parentNode) {
299
+ modalElement.parentNode.removeChild(modalElement);
300
+ }
301
+
302
+ // Call lifecycle destroy
303
+ options.lifecycle.destroy();
304
+ },
305
+
306
+ on(event: string, handler: Function) {
307
+ options.events.on(event, handler);
308
+ return this;
309
+ },
310
+
311
+ off(event: string, handler: Function) {
312
+ options.events.off(event, handler);
313
+ return this;
314
+ }
315
+ };
316
+
317
+ // Set up event handlers for the time picker dialog
318
+ dialogElement.addEventListener('click', (event) => {
319
+ const target = event.target as HTMLElement;
320
+
321
+ // Handle cancel button click
322
+ if (target.closest(SELECTORS.CANCEL_BUTTON)) {
323
+ timePickerAPI.close();
324
+ options.events.emit(EVENTS.CANCEL);
325
+
326
+ // Call onCancel callback if provided
327
+ if (config.onCancel) {
328
+ config.onCancel();
329
+ }
330
+ }
331
+
332
+ // Handle confirm button click
333
+ if (target.closest(SELECTORS.CONFIRM_BUTTON)) {
334
+ timePickerAPI.close();
335
+ options.events.emit(EVENTS.CONFIRM, timePickerAPI.getValue());
336
+
337
+ // Call onConfirm callback if provided
338
+ if (config.onConfirm) {
339
+ config.onConfirm(timePickerAPI.getValue());
340
+ }
341
+ }
342
+
343
+ // Handle toggle type button click (switch between dial and input)
344
+ if (target.closest(SELECTORS.TOGGLE_TYPE_BUTTON)) {
345
+ const newType = config.type === TIME_PICKER_TYPE.DIAL
346
+ ? TIME_PICKER_TYPE.INPUT
347
+ : TIME_PICKER_TYPE.DIAL;
348
+ timePickerAPI.setType(newType);
349
+ }
350
+
351
+ if (target.closest(SELECTORS.PERIOD_AM)) {
352
+ if (timeValue.period !== TIME_PERIOD.AM) {
353
+ timeValue.period = TIME_PERIOD.AM;
354
+ if (timeValue.hours >= 12) {
355
+ timeValue.hours -= 12;
356
+ }
357
+ renderTimePicker(dialogElement, timeValue, config);
358
+ options.events.emit(EVENTS.CHANGE, timePickerAPI.getValue());
359
+
360
+ // Call onChange callback if provided
361
+ if (config.onChange) {
362
+ config.onChange(timePickerAPI.getValue());
363
+ }
364
+ }
365
+ }
366
+
367
+ if (target.closest(SELECTORS.PERIOD_PM)) {
368
+ if (timeValue.period !== TIME_PERIOD.PM) {
369
+ timeValue.period = TIME_PERIOD.PM;
370
+ if (timeValue.hours < 12) {
371
+ timeValue.hours += 12;
372
+ }
373
+ renderTimePicker(dialogElement, timeValue, config);
374
+ options.events.emit(EVENTS.CHANGE, timePickerAPI.getValue());
375
+
376
+ // Call onChange callback if provided
377
+ if (config.onChange) {
378
+ config.onChange(timePickerAPI.getValue());
379
+ }
380
+ }
381
+ }
382
+
383
+ // Handle canvas dial click
384
+ if (target.closest(SELECTORS.DIAL_CANVAS)) {
385
+ if (config.type === TIME_PICKER_TYPE.DIAL) {
386
+ const canvas = target as HTMLCanvasElement;
387
+ const rect = canvas.getBoundingClientRect();
388
+ const x = event.clientX - rect.left;
389
+ const y = event.clientY - rect.top;
390
+
391
+ // Determine active selector
392
+ let activeSelector: 'hour' | 'minute' | 'second' = 'hour';
393
+ const hoursEl = dialogElement.querySelector(SELECTORS.HOURS_INPUT);
394
+ const minutesEl = dialogElement.querySelector(SELECTORS.MINUTES_INPUT);
395
+ const secondsEl = dialogElement.querySelector(SELECTORS.SECONDS_INPUT);
396
+
397
+ if (hoursEl && hoursEl.getAttribute('data-active') === 'true') {
398
+ activeSelector = 'hour';
399
+ } else if (minutesEl && minutesEl.getAttribute('data-active') === 'true') {
400
+ activeSelector = 'minute';
401
+ } else if (config.showSeconds && secondsEl && secondsEl.getAttribute('data-active') === 'true') {
402
+ activeSelector = 'second';
403
+ }
404
+
405
+ // Get the time value from the click position
406
+ const selectedValue = getTimeValueFromClick(canvas, x, y, {
407
+ type: config.type,
408
+ format: config.format,
409
+ showSeconds: config.showSeconds,
410
+ prefix: config.prefix,
411
+ activeSelector
412
+ });
413
+
414
+ if (selectedValue !== null) {
415
+ if (activeSelector === 'hour') {
416
+ let newHours = selectedValue;
417
+
418
+ // Adjust for 12-hour format if needed
419
+ if (config.format === TIME_FORMAT.AMPM) {
420
+ // Convert to 24-hour format internally
421
+ if (timeValue.period === TIME_PERIOD.PM && selectedValue !== 12) {
422
+ newHours += 12;
423
+ } else if (timeValue.period === TIME_PERIOD.AM && selectedValue === 12) {
424
+ newHours = 0;
425
+ }
426
+ }
427
+
428
+ if (timeValue.hours !== newHours) {
429
+ timeValue.hours = newHours;
430
+
431
+ // Update display time
432
+ const hoursDisplay = hoursEl as HTMLElement;
433
+ if (hoursDisplay) {
434
+ hoursDisplay.textContent = padZero(config.format === TIME_FORMAT.MILITARY
435
+ ? newHours
436
+ : (newHours % 12 || 12));
437
+ }
438
+
439
+ // Directly update the canvas
440
+ const canvasElement = dialogElement.querySelector(SELECTORS.DIAL_CANVAS) as HTMLCanvasElement;
441
+ if (canvasElement) {
442
+ renderClockDial(canvasElement, timeValue, {
443
+ type: config.type,
444
+ format: config.format,
445
+ showSeconds: config.showSeconds,
446
+ prefix: config.prefix,
447
+ activeSelector
448
+ });
449
+ }
450
+
451
+ options.events.emit(EVENTS.CHANGE, timePickerAPI.getValue());
452
+
453
+ // Call onChange callback if provided
454
+ if (config.onChange) {
455
+ config.onChange(timePickerAPI.getValue());
456
+ }
457
+ }
458
+ } else if (activeSelector === 'minute') {
459
+ if (timeValue.minutes !== selectedValue) {
460
+ timeValue.minutes = selectedValue;
461
+
462
+ // Update display time
463
+ const minutesDisplay = minutesEl as HTMLElement;
464
+ if (minutesDisplay) {
465
+ minutesDisplay.textContent = padZero(selectedValue);
466
+ }
467
+
468
+ // Directly update the canvas
469
+ const canvasElement = dialogElement.querySelector(SELECTORS.DIAL_CANVAS) as HTMLCanvasElement;
470
+ if (canvasElement) {
471
+ renderClockDial(canvasElement, timeValue, {
472
+ type: config.type,
473
+ format: config.format,
474
+ showSeconds: config.showSeconds,
475
+ prefix: config.prefix,
476
+ activeSelector
477
+ });
478
+ }
479
+
480
+ options.events.emit(EVENTS.CHANGE, timePickerAPI.getValue());
481
+
482
+ // Call onChange callback if provided
483
+ if (config.onChange) {
484
+ config.onChange(timePickerAPI.getValue());
485
+ }
486
+ }
487
+ } else if (activeSelector === 'second' && config.showSeconds) {
488
+ if (timeValue.seconds !== selectedValue) {
489
+ timeValue.seconds = selectedValue;
490
+
491
+ // Update display time
492
+ const secondsDisplay = secondsEl as HTMLElement;
493
+ if (secondsDisplay) {
494
+ secondsDisplay.textContent = padZero(selectedValue);
495
+ }
496
+
497
+ // Directly update the canvas
498
+ const canvasElement = dialogElement.querySelector(SELECTORS.DIAL_CANVAS) as HTMLCanvasElement;
499
+ if (canvasElement) {
500
+ renderClockDial(canvasElement, timeValue, {
501
+ type: config.type,
502
+ format: config.format,
503
+ showSeconds: config.showSeconds,
504
+ prefix: config.prefix,
505
+ activeSelector
506
+ });
507
+ }
508
+
509
+ options.events.emit(EVENTS.CHANGE, timePickerAPI.getValue());
510
+
511
+ // Call onChange callback if provided
512
+ if (config.onChange) {
513
+ config.onChange(timePickerAPI.getValue());
514
+ }
515
+ }
516
+ }
517
+ }
518
+ }
519
+ }
520
+ });
521
+
522
+ // Set up input handling for input type time picker
523
+ const handleInputChange = (event: Event) => {
524
+ const target = event.target as HTMLInputElement;
525
+ const inputType = target.getAttribute('data-type');
526
+ const value = parseInt(target.value, 10);
527
+
528
+ if (isNaN(value)) return;
529
+
530
+ if (inputType === 'hour') {
531
+ let newHours = value;
532
+
533
+ // Handle hour constraints
534
+ if (config.format === TIME_FORMAT.AMPM) {
535
+ if (value < 1) newHours = 12;
536
+ if (value > 12) newHours = 1;
537
+
538
+ // Convert to 24h format internally
539
+ if (timeValue.period === 'PM' && newHours !== 12) {
540
+ newHours += 12;
541
+ } else if (timeValue.period === 'AM' && newHours === 12) {
542
+ newHours = 0;
543
+ }
544
+ } else {
545
+ if (value < 0) newHours = 0;
546
+ if (value > 23) newHours = 23;
547
+ }
548
+
549
+ timeValue.hours = newHours;
550
+ } else if (inputType === 'minute') {
551
+ let newMinutes = value;
552
+
553
+ // Handle minute constraints
554
+ if (newMinutes < 0) newMinutes = 0;
555
+ if (newMinutes > 59) newMinutes = 59;
556
+
557
+ timeValue.minutes = newMinutes;
558
+ } else if (inputType === 'second') {
559
+ let newSeconds = value;
560
+
561
+ // Handle second constraints
562
+ if (newSeconds < 0) newSeconds = 0;
563
+ if (newSeconds > 59) newSeconds = 59;
564
+
565
+ timeValue.seconds = newSeconds;
566
+ }
567
+
568
+ // Re-render time picker with updated values
569
+ renderTimePicker(dialogElement, timeValue, config);
570
+
571
+ // Emit change event
572
+ options.events.emit(EVENTS.CHANGE, timePickerAPI.getValue());
573
+
574
+ // Call onChange callback if provided
575
+ if (config.onChange) {
576
+ config.onChange(timePickerAPI.getValue());
577
+ }
578
+ };
579
+
580
+ // Add event delegation for input fields
581
+ dialogElement.addEventListener('change', (event) => {
582
+ const target = event.target as HTMLElement;
583
+ if (
584
+ target.matches(SELECTORS.HOURS_INPUT) ||
585
+ target.matches(SELECTORS.MINUTES_INPUT) ||
586
+ target.matches(SELECTORS.SECONDS_INPUT)
587
+ ) {
588
+ handleInputChange(event);
589
+ }
590
+ });
591
+
592
+ // Add event delegation for input keyup
593
+ dialogElement.addEventListener('keyup', (event) => {
594
+ const target = event.target as HTMLElement;
595
+ if (
596
+ target.matches(SELECTORS.HOURS_INPUT) ||
597
+ target.matches(SELECTORS.MINUTES_INPUT) ||
598
+ target.matches(SELECTORS.SECONDS_INPUT)
599
+ ) {
600
+ if (event.key === 'Enter') {
601
+ handleInputChange(event);
602
+
603
+ // Move focus to next input or confirm button
604
+ if (target.matches(SELECTORS.HOURS_INPUT)) {
605
+ const minutesInput = dialogElement.querySelector(SELECTORS.MINUTES_INPUT);
606
+ if (minutesInput) {
607
+ (minutesInput as HTMLElement).focus();
608
+ }
609
+ } else if (target.matches(SELECTORS.MINUTES_INPUT)) {
610
+ if (config.showSeconds) {
611
+ const secondsInput = dialogElement.querySelector(SELECTORS.SECONDS_INPUT);
612
+ if (secondsInput) {
613
+ (secondsInput as HTMLElement).focus();
614
+ }
615
+ } else {
616
+ const confirmButton = dialogElement.querySelector(SELECTORS.CONFIRM_BUTTON);
617
+ if (confirmButton) {
618
+ (confirmButton as HTMLElement).focus();
619
+ }
620
+ }
621
+ } else if (target.matches(SELECTORS.SECONDS_INPUT)) {
622
+ const confirmButton = dialogElement.querySelector(SELECTORS.CONFIRM_BUTTON);
623
+ if (confirmButton) {
624
+ (confirmButton as HTMLElement).focus();
625
+ }
626
+ }
627
+ }
628
+ }
629
+ });
630
+
631
+ return timePickerAPI;
632
+ };