mtrl 0.2.6 → 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 +117 -109
- 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 -10
- package/src/components/button/api.ts +5 -0
- package/src/components/button/config.ts +5 -0
- package/src/components/button/types.ts +6 -0
- package/src/components/card/card.ts +13 -25
- package/src/components/card/config.ts +67 -22
- package/src/components/card/features.ts +3 -0
- package/src/components/card/types.ts +28 -0
- package/src/components/checkbox/_styles.scss +0 -2
- 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/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 +83 -24
- package/src/components/slider/accessibility.md +5 -5
- package/src/components/slider/api.ts +41 -120
- package/src/components/slider/config.ts +51 -47
- 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 +136 -206
- package/src/components/slider/features/ui.ts +145 -206
- 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 +94 -32
- package/src/components/tabs/features.ts +4 -2
- package/src/components/tabs/indicator.ts +73 -13
- package/src/components/tabs/types.ts +10 -2
- 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/features/icon.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 +9 -1
- 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/navigation/features/items.js +0 -192
- package/src/components/slider/features/appearance.ts +0 -94
- package/src/components/slider/features/disabled.ts +0 -68
- package/src/components/slider/features/events.ts +0 -164
- package/src/components/slider/features/interactions.ts +0 -396
- package/src/components/slider/features/keyboard.ts +0 -233
- 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
|
+
};
|