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.
- package/index.ts +18 -0
- package/package.json +1 -1
- package/src/components/badge/_styles.scss +123 -115
- package/src/components/badge/api.ts +57 -59
- package/src/components/badge/badge.ts +16 -2
- package/src/components/badge/config.ts +65 -11
- package/src/components/badge/constants.ts +22 -12
- package/src/components/badge/features.ts +44 -40
- package/src/components/badge/types.ts +42 -30
- package/src/components/bottom-app-bar/_styles.scss +103 -0
- package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
- package/src/components/bottom-app-bar/config.ts +73 -0
- package/src/components/bottom-app-bar/index.ts +11 -0
- package/src/components/bottom-app-bar/types.ts +108 -0
- package/src/components/button/_styles.scss +0 -66
- package/src/components/button/api.ts +5 -0
- package/src/components/button/button.ts +0 -2
- package/src/components/button/config.ts +5 -0
- package/src/components/button/constants.ts +0 -6
- package/src/components/button/index.ts +2 -2
- package/src/components/button/types.ts +7 -7
- package/src/components/card/_styles.scss +67 -25
- package/src/components/card/api.ts +54 -3
- package/src/components/card/card.ts +25 -6
- package/src/components/card/config.ts +189 -22
- package/src/components/card/constants.ts +20 -19
- package/src/components/card/content.ts +299 -2
- package/src/components/card/features.ts +158 -4
- package/src/components/card/index.ts +31 -9
- package/src/components/card/types.ts +166 -15
- package/src/components/checkbox/_styles.scss +0 -2
- package/src/components/chip/chip.ts +1 -9
- package/src/components/chip/constants.ts +0 -10
- package/src/components/chip/index.ts +1 -1
- package/src/components/chip/types.ts +1 -4
- package/src/components/datepicker/_styles.scss +358 -0
- package/src/components/datepicker/api.ts +272 -0
- package/src/components/datepicker/config.ts +144 -0
- package/src/components/datepicker/constants.ts +98 -0
- package/src/components/datepicker/datepicker.ts +346 -0
- package/src/components/datepicker/index.ts +9 -0
- package/src/components/datepicker/render.ts +452 -0
- package/src/components/datepicker/types.ts +268 -0
- package/src/components/datepicker/utils.ts +290 -0
- package/src/components/dialog/_styles.scss +174 -128
- package/src/components/dialog/api.ts +48 -13
- package/src/components/dialog/config.ts +9 -5
- package/src/components/dialog/dialog.ts +6 -3
- package/src/components/dialog/features.ts +290 -130
- package/src/components/dialog/types.ts +7 -4
- package/src/components/divider/_styles.scss +57 -0
- package/src/components/divider/config.ts +81 -0
- package/src/components/divider/divider.ts +37 -0
- package/src/components/divider/features.ts +207 -0
- package/src/components/divider/index.ts +5 -0
- package/src/components/divider/types.ts +55 -0
- package/src/components/extended-fab/_styles.scss +267 -0
- package/src/components/extended-fab/api.ts +141 -0
- package/src/components/extended-fab/config.ts +108 -0
- package/src/components/extended-fab/constants.ts +36 -0
- package/src/components/extended-fab/extended-fab.ts +125 -0
- package/src/components/extended-fab/index.ts +4 -0
- package/src/components/extended-fab/types.ts +287 -0
- package/src/components/fab/_styles.scss +225 -0
- package/src/components/fab/api.ts +97 -0
- package/src/components/fab/config.ts +94 -0
- package/src/components/fab/constants.ts +41 -0
- package/src/components/fab/fab.ts +67 -0
- package/src/components/fab/index.ts +4 -0
- package/src/components/fab/types.ts +234 -0
- package/src/components/navigation/_styles.scss +1 -0
- package/src/components/navigation/api.ts +78 -50
- package/src/components/navigation/features/items.ts +280 -0
- package/src/components/navigation/nav-item.ts +72 -23
- package/src/components/navigation/navigation.ts +54 -2
- package/src/components/navigation/types.ts +210 -188
- package/src/components/progress/_styles.scss +0 -65
- package/src/components/progress/config.ts +1 -2
- package/src/components/progress/constants.ts +0 -14
- package/src/components/progress/index.ts +1 -1
- package/src/components/progress/progress.ts +1 -4
- package/src/components/progress/types.ts +1 -4
- package/src/components/radios/_styles.scss +0 -45
- package/src/components/radios/api.ts +85 -60
- package/src/components/radios/config.ts +1 -2
- package/src/components/radios/constants.ts +0 -9
- package/src/components/radios/index.ts +1 -1
- package/src/components/radios/radio.ts +34 -11
- package/src/components/radios/radios.ts +2 -1
- package/src/components/radios/types.ts +1 -7
- package/src/components/search/_styles.scss +306 -0
- package/src/components/search/api.ts +203 -0
- package/src/components/search/config.ts +87 -0
- package/src/components/search/constants.ts +21 -0
- package/src/components/search/features/index.ts +4 -0
- package/src/components/search/features/search.ts +718 -0
- package/src/components/search/features/states.ts +165 -0
- package/src/components/search/features/structure.ts +198 -0
- package/src/components/search/index.ts +10 -0
- package/src/components/search/search.ts +52 -0
- package/src/components/search/types.ts +163 -0
- package/src/components/segmented-button/_styles.scss +117 -0
- package/src/components/segmented-button/config.ts +67 -0
- package/src/components/segmented-button/constants.ts +42 -0
- package/src/components/segmented-button/index.ts +4 -0
- package/src/components/segmented-button/segment.ts +155 -0
- package/src/components/segmented-button/segmented-button.ts +250 -0
- package/src/components/segmented-button/types.ts +219 -0
- package/src/components/slider/_styles.scss +221 -168
- package/src/components/slider/accessibility.md +59 -0
- package/src/components/slider/api.ts +41 -120
- package/src/components/slider/config.ts +51 -49
- package/src/components/slider/features/handlers.ts +495 -0
- package/src/components/slider/features/index.ts +1 -2
- package/src/components/slider/features/slider.ts +66 -84
- package/src/components/slider/features/states.ts +195 -0
- package/src/components/slider/features/structure.ts +141 -184
- package/src/components/slider/features/ui.ts +150 -201
- package/src/components/slider/index.ts +2 -11
- package/src/components/slider/slider.ts +9 -12
- package/src/components/slider/types.ts +39 -24
- package/src/components/switch/_styles.scss +0 -2
- package/src/components/tabs/_styles.scss +346 -154
- package/src/components/tabs/api.ts +178 -400
- package/src/components/tabs/config.ts +46 -52
- package/src/components/tabs/constants.ts +85 -8
- package/src/components/tabs/features.ts +403 -0
- package/src/components/tabs/index.ts +60 -3
- package/src/components/tabs/indicator.ts +285 -0
- package/src/components/tabs/responsive.ts +144 -0
- package/src/components/tabs/scroll-indicators.ts +149 -0
- package/src/components/tabs/state.ts +186 -0
- package/src/components/tabs/tab-api.ts +258 -0
- package/src/components/tabs/tab.ts +255 -0
- package/src/components/tabs/tabs.ts +50 -31
- package/src/components/tabs/types.ts +332 -128
- package/src/components/tabs/utils.ts +107 -0
- package/src/components/textfield/_styles.scss +0 -98
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/constants.ts +0 -14
- package/src/components/textfield/index.ts +2 -2
- package/src/components/textfield/textfield.ts +0 -2
- package/src/components/textfield/types.ts +1 -4
- package/src/components/timepicker/README.md +277 -0
- package/src/components/timepicker/_styles.scss +451 -0
- package/src/components/timepicker/api.ts +632 -0
- package/src/components/timepicker/clockdial.ts +482 -0
- package/src/components/timepicker/config.ts +130 -0
- package/src/components/timepicker/constants.ts +138 -0
- package/src/components/timepicker/index.ts +8 -0
- package/src/components/timepicker/render.ts +613 -0
- package/src/components/timepicker/timepicker.ts +117 -0
- package/src/components/timepicker/types.ts +336 -0
- package/src/components/timepicker/utils.ts +241 -0
- package/src/components/top-app-bar/_styles.scss +225 -0
- package/src/components/top-app-bar/config.ts +83 -0
- package/src/components/top-app-bar/index.ts +11 -0
- package/src/components/top-app-bar/top-app-bar.ts +316 -0
- package/src/components/top-app-bar/types.ts +140 -0
- package/src/core/build/_ripple.scss +6 -6
- package/src/core/build/ripple.ts +72 -95
- package/src/core/compose/component.ts +1 -1
- package/src/core/compose/features/badge.ts +79 -0
- package/src/core/compose/features/icon.ts +3 -1
- package/src/core/compose/features/index.ts +3 -1
- package/src/core/compose/features/ripple.ts +4 -1
- package/src/core/compose/features/textlabel.ts +26 -2
- package/src/core/dom/create.ts +5 -0
- package/src/index.ts +9 -0
- package/src/styles/abstract/_theme.scss +115 -3
- package/src/styles/themes/_autumn.scss +21 -0
- package/src/styles/themes/_base-theme.scss +61 -0
- package/src/styles/themes/_baseline.scss +58 -0
- package/src/styles/themes/_bluekhaki.scss +125 -0
- package/src/styles/themes/_brownbeige.scss +125 -0
- package/src/styles/themes/_browngreen.scss +125 -0
- package/src/styles/themes/_forest.scss +6 -0
- package/src/styles/themes/_greenbeige.scss +125 -0
- package/src/styles/themes/_material.scss +125 -0
- package/src/styles/themes/_ocean.scss +6 -0
- package/src/styles/themes/_sageivory.scss +125 -0
- package/src/styles/themes/_spring.scss +6 -0
- package/src/styles/themes/_summer.scss +5 -0
- package/src/styles/themes/_sunset.scss +5 -0
- package/src/styles/themes/_tealcaramel.scss +125 -0
- package/src/styles/themes/_winter.scss +6 -0
- package/src/components/card/actions.ts +0 -48
- package/src/components/card/header.ts +0 -88
- package/src/components/card/media.ts +0 -52
- package/src/components/navigation/features/items.js +0 -192
- package/src/components/slider/features/appearance.ts +0 -94
- package/src/components/slider/features/disabled.ts +0 -43
- package/src/components/slider/features/events.ts +0 -164
- package/src/components/slider/features/interactions.ts +0 -261
- package/src/components/slider/features/keyboard.ts +0 -112
- package/src/core/collection/adapters/mongodb.js +0 -232
|
@@ -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 './constants';
|
|
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
|
+
};
|