ezfw-core 1.0.0

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 (154) hide show
  1. package/components/EzBaseComponent.ts +648 -0
  2. package/components/EzComponent.ts +89 -0
  3. package/components/EzInput.module.scss +183 -0
  4. package/components/EzInput.ts +104 -0
  5. package/components/EzLabel.ts +22 -0
  6. package/components/EzOutlet.ts +181 -0
  7. package/components/HtmlWrapper.ts +305 -0
  8. package/components/avatar/EzAvatar.module.scss +200 -0
  9. package/components/avatar/EzAvatar.ts +130 -0
  10. package/components/badge/EzBadge.module.scss +202 -0
  11. package/components/badge/EzBadge.ts +77 -0
  12. package/components/button/EzButton.module.scss +402 -0
  13. package/components/button/EzButton.ts +175 -0
  14. package/components/button/EzButtonGroup.ts +48 -0
  15. package/components/card/EzCard.module.scss +71 -0
  16. package/components/card/EzCard.ts +120 -0
  17. package/components/chart/EzBarChart.ts +47 -0
  18. package/components/chart/EzChart.module.scss +14 -0
  19. package/components/chart/EzChart.ts +279 -0
  20. package/components/chart/EzDoughnutChart.ts +47 -0
  21. package/components/chart/EzLineChart.ts +53 -0
  22. package/components/checkbox/EzCheckbox.module.scss +145 -0
  23. package/components/checkbox/EzCheckbox.ts +115 -0
  24. package/components/dataview/EzDataView.module.scss +115 -0
  25. package/components/dataview/EzDataView.ts +355 -0
  26. package/components/dataview/modes/EzDataViewCards.ts +322 -0
  27. package/components/dataview/modes/EzDataViewGrid.ts +76 -0
  28. package/components/datepicker/EzDatePicker.module.scss +348 -0
  29. package/components/datepicker/EzDatePicker.ts +519 -0
  30. package/components/dialog/EzDialog.module.scss +180 -0
  31. package/components/dropdown/EzDropdown.module.scss +107 -0
  32. package/components/dropdown/EzDropdown.ts +235 -0
  33. package/components/feed/EzActivityFeed.module.scss +90 -0
  34. package/components/feed/EzActivityFeed.ts +78 -0
  35. package/components/form/EzForm.ts +364 -0
  36. package/components/form/EzValidators.test.js +421 -0
  37. package/components/form/EzValidators.ts +202 -0
  38. package/components/grid/EzGrid.scss +88 -0
  39. package/components/grid/EzGrid.ts +1085 -0
  40. package/components/grid/EzGridContainer.ts +104 -0
  41. package/components/grid/body/EzGridBody.scss +283 -0
  42. package/components/grid/body/EzGridBody.ts +549 -0
  43. package/components/grid/body/EzGridCell.ts +211 -0
  44. package/components/grid/body/EzGridRow.ts +196 -0
  45. package/components/grid/filter/EzGridFilters.scss +78 -0
  46. package/components/grid/filter/EzGridFilters.ts +285 -0
  47. package/components/grid/footer/EzGridFooter.scss +136 -0
  48. package/components/grid/footer/EzGridFooter.ts +448 -0
  49. package/components/grid/header/EzGridHeader.scss +199 -0
  50. package/components/grid/header/EzGridHeader.ts +430 -0
  51. package/components/grid/query/EzGridQuery.ts +81 -0
  52. package/components/grid/state/EzGridColumns.ts +155 -0
  53. package/components/grid/state/EzGridController.ts +470 -0
  54. package/components/grid/state/EzGridLifecycle.ts +136 -0
  55. package/components/grid/state/EzGridNormalizers.test.js +273 -0
  56. package/components/grid/state/EzGridNormalizers.ts +162 -0
  57. package/components/grid/state/EzGridParts.ts +233 -0
  58. package/components/grid/state/EzGridPersistence.ts +140 -0
  59. package/components/grid/state/EzGridRemote.test.js +573 -0
  60. package/components/grid/state/EzGridRemote.ts +335 -0
  61. package/components/grid/state/EzGridSelection.ts +231 -0
  62. package/components/grid/state/EzGridSort.ts +286 -0
  63. package/components/grid/title/EzGridActionBar.ts +98 -0
  64. package/components/grid/title/EzGridTitle.ts +114 -0
  65. package/components/grid/title/EzGridTitleBar.scss +65 -0
  66. package/components/grid/title/EzGridTitleBar.ts +87 -0
  67. package/components/grid/types.ts +607 -0
  68. package/components/panel/EzPanel.module.scss +133 -0
  69. package/components/panel/EzPanel.ts +147 -0
  70. package/components/radio/EzRadio.module.scss +190 -0
  71. package/components/radio/EzRadio.ts +149 -0
  72. package/components/select/EzSelect.module.scss +153 -0
  73. package/components/select/EzSelect.ts +238 -0
  74. package/components/skeleton/EzSkeleton.module.scss +95 -0
  75. package/components/skeleton/EzSkeleton.ts +70 -0
  76. package/components/store/EzStore.ts +344 -0
  77. package/components/switch/EzSwitch.module.scss +164 -0
  78. package/components/switch/EzSwitch.ts +117 -0
  79. package/components/tabs/EzTabPanel.module.scss +181 -0
  80. package/components/tabs/EzTabPanel.ts +402 -0
  81. package/components/textarea/EzTextarea.module.scss +131 -0
  82. package/components/textarea/EzTextarea.ts +161 -0
  83. package/components/timepicker/EzTimePicker.module.scss +282 -0
  84. package/components/timepicker/EzTimePicker.ts +540 -0
  85. package/components/toast/EzToast.module.scss +291 -0
  86. package/components/tooltip/EzTooltip.module.scss +124 -0
  87. package/components/tooltip/EzTooltip.ts +153 -0
  88. package/core/EzComponentTypes.ts +693 -0
  89. package/core/EzError.ts +63 -0
  90. package/core/EzModel.ts +268 -0
  91. package/core/EzTypes.ts +328 -0
  92. package/core/eventBus.ts +284 -0
  93. package/core/ez.ts +617 -0
  94. package/core/loader.ts +725 -0
  95. package/core/renderer.ts +1010 -0
  96. package/core/router.ts +490 -0
  97. package/core/services.ts +124 -0
  98. package/core/state.ts +142 -0
  99. package/core/utils.ts +81 -0
  100. package/package.json +51 -0
  101. package/services/RouteUI.js +17 -0
  102. package/services/crypto.js +64 -0
  103. package/services/dialog.js +222 -0
  104. package/services/fetchApi.js +63 -0
  105. package/services/firebase.js +30 -0
  106. package/services/toast.js +214 -0
  107. package/template/doc/EzDocs.js +15 -0
  108. package/template/doc/EzDocs.module.scss +627 -0
  109. package/template/doc/EzDocsController.js +164 -0
  110. package/template/doc/data/activityfeed/EzActivityFeedDoc.js +42 -0
  111. package/template/doc/data/avatar/EzAvatarDoc.js +71 -0
  112. package/template/doc/data/badge/EzBadgeDoc.js +92 -0
  113. package/template/doc/data/button/EzButtonDoc.js +77 -0
  114. package/template/doc/data/buttongroup/EzButtonGroupDoc.js +102 -0
  115. package/template/doc/data/card/EzCardDoc.js +39 -0
  116. package/template/doc/data/chart/EzChartDoc.js +60 -0
  117. package/template/doc/data/checkbox/EzCheckboxDoc.js +67 -0
  118. package/template/doc/data/component/EzComponentDoc.js +34 -0
  119. package/template/doc/data/cssmodules/CSSModulesDoc.js +70 -0
  120. package/template/doc/data/datepicker/EzDatePickerDoc.js +126 -0
  121. package/template/doc/data/dialog/EzDialogDoc.js +217 -0
  122. package/template/doc/data/dropdown/EzDropdownDoc.js +178 -0
  123. package/template/doc/data/form/EzFormDoc.js +90 -0
  124. package/template/doc/data/grid/EzGridDoc.js +99 -0
  125. package/template/doc/data/input/EzInputDoc.js +92 -0
  126. package/template/doc/data/label/EzLabelDoc.js +40 -0
  127. package/template/doc/data/model/EzModelDoc.js +53 -0
  128. package/template/doc/data/outlet/EzOutletDoc.js +63 -0
  129. package/template/doc/data/panel/EzPanelDoc.js +214 -0
  130. package/template/doc/data/radio/EzRadioDoc.js +174 -0
  131. package/template/doc/data/router/EzRouterDoc.js +75 -0
  132. package/template/doc/data/select/EzSelectDoc.js +37 -0
  133. package/template/doc/data/skeleton/EzSkeletonDoc.js +149 -0
  134. package/template/doc/data/switch/EzSwitchDoc.js +82 -0
  135. package/template/doc/data/tabpanel/EzTabPanelDoc.js +44 -0
  136. package/template/doc/data/textarea/EzTextareaDoc.js +131 -0
  137. package/template/doc/data/timepicker/EzTimePickerDoc.js +107 -0
  138. package/template/doc/data/tooltip/EzTooltipDoc.js +193 -0
  139. package/template/doc/data/validators/EzValidatorsDoc.js +37 -0
  140. package/template/doc/sidebar/EzDocsSidebar.js +32 -0
  141. package/template/doc/sidebar/category/EzDocsCategory.js +33 -0
  142. package/template/doc/sidebar/item/EzDocsComponentItem.js +24 -0
  143. package/template/doc/viewer/EzDocsViewer.js +18 -0
  144. package/template/doc/viewer/codepanel/EzDocsCodePanel.js +51 -0
  145. package/template/doc/viewer/content/EzDocsContent.js +315 -0
  146. package/template/doc/viewer/header/EzDocsViewerHeader.js +46 -0
  147. package/template/doc/viewer/showcase/EzDocsShowcase.js +59 -0
  148. package/template/doc/viewer/showcase/EzDocsShowcaseSection.js +25 -0
  149. package/template/doc/viewer/showcase/EzDocsVariantItem.js +29 -0
  150. package/template/doc/welcome/EzDocsWelcome.js +48 -0
  151. package/themes/ez-theme.scss +179 -0
  152. package/themes/nature-fresh.scss +169 -0
  153. package/types/global.d.ts +21 -0
  154. package/utils/cssModules.js +81 -0
@@ -0,0 +1,540 @@
1
+ import styles from './EzTimePicker.module.scss';
2
+ import { cx } from '../../utils/cssModules.js';
3
+ import { EzBaseComponent, EzBaseComponentConfig } from '../EzBaseComponent.js';
4
+
5
+ const cls = cx(styles);
6
+
7
+ type Period = 'AM' | 'PM';
8
+
9
+ export interface EzTimePickerConfig extends EzBaseComponentConfig {
10
+ placeholder?: string;
11
+ format?: string;
12
+ displayFormat?: string;
13
+ use12Hour?: boolean;
14
+ minuteStep?: number;
15
+ showSeconds?: boolean;
16
+ min?: string | null;
17
+ max?: string | null;
18
+ onTimeChange?: (value: string | null) => void;
19
+ value?: string | null;
20
+ size?: 'sm' | 'lg';
21
+ disabled?: boolean;
22
+ name?: string;
23
+ formData?: string;
24
+ }
25
+
26
+ export class EzTimePicker extends EzBaseComponent {
27
+ declare config: EzTimePickerConfig;
28
+ declare el: HTMLElement;
29
+
30
+ placeholder: string;
31
+ format: string;
32
+ displayFormat: string;
33
+ use12Hour: boolean;
34
+ minuteStep: number;
35
+ showSeconds: boolean;
36
+ min: string | null;
37
+ max: string | null;
38
+ onTimeChange: ((value: string | null) => void) | null;
39
+ value: string | null;
40
+
41
+ private _isOpen: boolean = false;
42
+ private _selectedHour: number = 0;
43
+ private _selectedMinute: number = 0;
44
+ private _selectedSecond: number = 0;
45
+ private _selectedPeriod: Period = 'AM';
46
+
47
+ private _trigger: HTMLElement | null = null;
48
+ private _input: HTMLInputElement | null = null;
49
+ private _error: HTMLElement | null = null;
50
+ private _dropdown: HTMLElement | null = null;
51
+ private _outsideClickHandler: ((e: MouseEvent) => void) | null = null;
52
+
53
+ constructor(config: EzTimePickerConfig = {}) {
54
+ super(config);
55
+
56
+ this.placeholder = config.placeholder || 'Select time...';
57
+ this.format = config.format || 'HH:mm';
58
+ this.displayFormat = config.displayFormat || config.format || 'HH:mm';
59
+ this.use12Hour = config.use12Hour || false;
60
+ this.minuteStep = config.minuteStep || 1;
61
+ this.showSeconds = config.showSeconds || false;
62
+ this.min = config.min || null;
63
+ this.max = config.max || null;
64
+ this.onTimeChange = config.onTimeChange || null;
65
+ this.value = config.value ?? null;
66
+
67
+ this._parseValue();
68
+ }
69
+
70
+ private _parseValue(): void {
71
+ if (!this.value) {
72
+ const now = new Date();
73
+ this._selectedHour = now.getHours();
74
+ this._selectedMinute = Math.floor(now.getMinutes() / this.minuteStep) * this.minuteStep;
75
+ this._selectedSecond = 0;
76
+ this._selectedPeriod = this._selectedHour >= 12 ? 'PM' : 'AM';
77
+ return;
78
+ }
79
+
80
+ const parts = this.value.split(':');
81
+ this._selectedHour = parseInt(parts[0], 10) || 0;
82
+ this._selectedMinute = parseInt(parts[1], 10) || 0;
83
+ this._selectedSecond = parseInt(parts[2], 10) || 0;
84
+ this._selectedPeriod = this._selectedHour >= 12 ? 'PM' : 'AM';
85
+ }
86
+
87
+ render(): HTMLElement {
88
+ const cfg = this.config;
89
+
90
+ if (cfg.name && cfg.formData) {
91
+ this.config.bind = `${cfg.formData}.${cfg.name}`;
92
+ }
93
+
94
+ this.el = document.createElement('div');
95
+ this.el.className = cls('timepicker', cfg.size, cfg.disabled && 'disabled');
96
+
97
+ this._trigger = document.createElement('div');
98
+ this._trigger.className = cls('trigger');
99
+
100
+ this._input = document.createElement('input');
101
+ this._input.type = 'text';
102
+ this._input.className = cls('input');
103
+ this._input.placeholder = this.placeholder;
104
+ this._input.readOnly = true;
105
+
106
+ if (cfg.name) {
107
+ this._input.setAttribute('data-ez-field', cfg.name);
108
+ }
109
+ if (cfg.disabled) {
110
+ this._input.disabled = true;
111
+ }
112
+
113
+ const icon = document.createElement('i');
114
+ icon.className = cls('icon') + ' fa-solid fa-clock';
115
+
116
+ this._trigger.appendChild(this._input);
117
+ this._trigger.appendChild(icon);
118
+ this.el.appendChild(this._trigger);
119
+
120
+ this._error = document.createElement('div');
121
+ this._error.className = cls('fieldError');
122
+ this.el.appendChild(this._error);
123
+
124
+ this._dropdown = document.createElement('div');
125
+ this._dropdown.className = cls('dropdown');
126
+ this._dropdown.style.opacity = '0';
127
+ this._dropdown.style.visibility = 'hidden';
128
+ document.body.appendChild(this._dropdown);
129
+
130
+ this._updateDisplay();
131
+ this._bindEvents();
132
+
133
+ return this.el;
134
+ }
135
+
136
+ private _updateDisplay(): void {
137
+ if (!this._input) return;
138
+ if (this.value) {
139
+ this._input.value = this._formatTime(this.value);
140
+ } else {
141
+ this._input.value = '';
142
+ }
143
+ }
144
+
145
+ private _formatTime(timeStr: string): string {
146
+ if (!timeStr) return '';
147
+
148
+ const parts = timeStr.split(':');
149
+ let hour = parseInt(parts[0], 10);
150
+ const minute = parts[1] || '00';
151
+ const second = parts[2] || '00';
152
+
153
+ if (this.use12Hour) {
154
+ const period = hour >= 12 ? 'PM' : 'AM';
155
+ hour = hour % 12 || 12;
156
+ const hourStr = hour.toString().padStart(2, '0');
157
+ if (this.showSeconds) {
158
+ return `${hourStr}:${minute}:${second} ${period}`;
159
+ }
160
+ return `${hourStr}:${minute} ${period}`;
161
+ }
162
+
163
+ const hourStr = hour.toString().padStart(2, '0');
164
+ if (this.showSeconds) {
165
+ return `${hourStr}:${minute}:${second}`;
166
+ }
167
+ return `${hourStr}:${minute}`;
168
+ }
169
+
170
+ private _buildTimePicker(): void {
171
+ if (!this._dropdown) return;
172
+ this._dropdown.innerHTML = '';
173
+
174
+ const container = document.createElement('div');
175
+ container.className = cls('pickerContainer');
176
+
177
+ const hoursCol = this._buildColumn('hour', this.use12Hour ? 12 : 24);
178
+ container.appendChild(hoursCol);
179
+
180
+ const sep1 = document.createElement('div');
181
+ sep1.className = cls('separator');
182
+ sep1.textContent = ':';
183
+ container.appendChild(sep1);
184
+
185
+ const minutesCol = this._buildColumn('minute', 60);
186
+ container.appendChild(minutesCol);
187
+
188
+ if (this.showSeconds) {
189
+ const sep2 = document.createElement('div');
190
+ sep2.className = cls('separator');
191
+ sep2.textContent = ':';
192
+ container.appendChild(sep2);
193
+
194
+ const secondsCol = this._buildColumn('second', 60);
195
+ container.appendChild(secondsCol);
196
+ }
197
+
198
+ if (this.use12Hour) {
199
+ const periodCol = this._buildPeriodColumn();
200
+ container.appendChild(periodCol);
201
+ }
202
+
203
+ this._dropdown.appendChild(container);
204
+
205
+ const footer = document.createElement('div');
206
+ footer.className = cls('footer');
207
+
208
+ const nowBtn = document.createElement('button');
209
+ nowBtn.type = 'button';
210
+ nowBtn.className = cls('nowBtn');
211
+ nowBtn.textContent = 'Now';
212
+ nowBtn.addEventListener('click', (e) => {
213
+ e.stopPropagation();
214
+ this._selectNow();
215
+ });
216
+
217
+ const clearBtn = document.createElement('button');
218
+ clearBtn.type = 'button';
219
+ clearBtn.className = cls('clearBtn');
220
+ clearBtn.textContent = 'Clear';
221
+ clearBtn.addEventListener('click', (e) => {
222
+ e.stopPropagation();
223
+ this._clearValue();
224
+ });
225
+
226
+ const okBtn = document.createElement('button');
227
+ okBtn.type = 'button';
228
+ okBtn.className = cls('okBtn');
229
+ okBtn.textContent = 'OK';
230
+ okBtn.addEventListener('click', (e) => {
231
+ e.stopPropagation();
232
+ this._confirmSelection();
233
+ });
234
+
235
+ footer.appendChild(clearBtn);
236
+ footer.appendChild(nowBtn);
237
+ footer.appendChild(okBtn);
238
+ this._dropdown.appendChild(footer);
239
+
240
+ this._scrollToSelected();
241
+ }
242
+
243
+ private _buildColumn(type: 'hour' | 'minute' | 'second', count: number): HTMLElement {
244
+ const col = document.createElement('div');
245
+ col.className = cls('column');
246
+ col.dataset.type = type;
247
+
248
+ const list = document.createElement('div');
249
+ list.className = cls('columnList');
250
+
251
+ let start = 0;
252
+ let step = 1;
253
+
254
+ if (type === 'hour' && this.use12Hour) {
255
+ start = 1;
256
+ count = 12;
257
+ }
258
+ if (type === 'minute') {
259
+ step = this.minuteStep;
260
+ }
261
+
262
+ const end = type === 'hour' && this.use12Hour ? 13 : count;
263
+
264
+ for (let i = start; i < end; i += step) {
265
+ const item = document.createElement('div');
266
+ item.className = cls('columnItem');
267
+ item.dataset.value = String(i);
268
+ item.textContent = i.toString().padStart(2, '0');
269
+
270
+ let isSelected = false;
271
+ if (type === 'hour') {
272
+ if (this.use12Hour) {
273
+ const displayHour = this._selectedHour % 12 || 12;
274
+ isSelected = i === displayHour;
275
+ } else {
276
+ isSelected = i === this._selectedHour;
277
+ }
278
+ } else if (type === 'minute') {
279
+ isSelected = i === this._selectedMinute;
280
+ } else if (type === 'second') {
281
+ isSelected = i === this._selectedSecond;
282
+ }
283
+
284
+ if (isSelected) {
285
+ item.classList.add(cls('selected'));
286
+ }
287
+
288
+ if (this._isTimeDisabled(type, i)) {
289
+ item.classList.add(cls('disabled'));
290
+ } else {
291
+ item.addEventListener('click', (e) => {
292
+ e.stopPropagation();
293
+ this._selectValue(type, i);
294
+ });
295
+ }
296
+
297
+ list.appendChild(item);
298
+ }
299
+
300
+ col.appendChild(list);
301
+ return col;
302
+ }
303
+
304
+ private _buildPeriodColumn(): HTMLElement {
305
+ const col = document.createElement('div');
306
+ col.className = cls('column', 'periodColumn');
307
+
308
+ const list = document.createElement('div');
309
+ list.className = cls('columnList');
310
+
311
+ (['AM', 'PM'] as Period[]).forEach(period => {
312
+ const item = document.createElement('div');
313
+ item.className = cls('columnItem', 'periodItem');
314
+ item.dataset.value = period;
315
+ item.textContent = period;
316
+
317
+ if (period === this._selectedPeriod) {
318
+ item.classList.add(cls('selected'));
319
+ }
320
+
321
+ item.addEventListener('click', (e) => {
322
+ e.stopPropagation();
323
+ this._selectPeriod(period);
324
+ });
325
+
326
+ list.appendChild(item);
327
+ });
328
+
329
+ col.appendChild(list);
330
+ return col;
331
+ }
332
+
333
+ private _isTimeDisabled(_type: string, _value: number): boolean {
334
+ return false;
335
+ }
336
+
337
+ private _selectValue(type: 'hour' | 'minute' | 'second', value: number): void {
338
+ if (type === 'hour') {
339
+ if (this.use12Hour) {
340
+ if (this._selectedPeriod === 'PM' && value !== 12) {
341
+ this._selectedHour = value + 12;
342
+ } else if (this._selectedPeriod === 'AM' && value === 12) {
343
+ this._selectedHour = 0;
344
+ } else {
345
+ this._selectedHour = value;
346
+ }
347
+ } else {
348
+ this._selectedHour = value;
349
+ }
350
+ } else if (type === 'minute') {
351
+ this._selectedMinute = value;
352
+ } else if (type === 'second') {
353
+ this._selectedSecond = value;
354
+ }
355
+
356
+ this._buildTimePicker();
357
+ }
358
+
359
+ private _selectPeriod(period: Period): void {
360
+ const oldPeriod = this._selectedPeriod;
361
+ this._selectedPeriod = period;
362
+
363
+ if (oldPeriod !== period) {
364
+ if (period === 'PM' && this._selectedHour < 12) {
365
+ this._selectedHour += 12;
366
+ } else if (period === 'AM' && this._selectedHour >= 12) {
367
+ this._selectedHour -= 12;
368
+ }
369
+ }
370
+
371
+ this._buildTimePicker();
372
+ }
373
+
374
+ private _selectNow(): void {
375
+ const now = new Date();
376
+ this._selectedHour = now.getHours();
377
+ this._selectedMinute = Math.floor(now.getMinutes() / this.minuteStep) * this.minuteStep;
378
+ this._selectedSecond = now.getSeconds();
379
+ this._selectedPeriod = this._selectedHour >= 12 ? 'PM' : 'AM';
380
+
381
+ this._confirmSelection();
382
+ }
383
+
384
+ private _clearValue(): void {
385
+ this.value = null;
386
+ this._updateDisplay();
387
+ this._close();
388
+
389
+ if (this.onTimeChange) {
390
+ this.onTimeChange(null);
391
+ }
392
+ }
393
+
394
+ private _confirmSelection(): void {
395
+ const hour = this._selectedHour.toString().padStart(2, '0');
396
+ const minute = this._selectedMinute.toString().padStart(2, '0');
397
+ const second = this._selectedSecond.toString().padStart(2, '0');
398
+
399
+ if (this.showSeconds) {
400
+ this.value = `${hour}:${minute}:${second}`;
401
+ } else {
402
+ this.value = `${hour}:${minute}`;
403
+ }
404
+
405
+ this._updateDisplay();
406
+ this._close();
407
+
408
+ if (this.onTimeChange) {
409
+ this.onTimeChange(this.value);
410
+ }
411
+ }
412
+
413
+ private _scrollToSelected(): void {
414
+ if (!this._dropdown) return;
415
+
416
+ setTimeout(() => {
417
+ this._dropdown!.querySelectorAll(`.${cls('column')}`).forEach(col => {
418
+ const selected = col.querySelector(`.${cls('selected')}`);
419
+ if (selected) {
420
+ const list = col.querySelector(`.${cls('columnList')}`) as HTMLElement | null;
421
+ if (list) {
422
+ const itemHeight = (selected as HTMLElement).offsetHeight;
423
+ const scrollTop = (selected as HTMLElement).offsetTop - (list.offsetHeight / 2) + (itemHeight / 2);
424
+ list.scrollTop = Math.max(0, scrollTop);
425
+ }
426
+ }
427
+ });
428
+ }, 0);
429
+ }
430
+
431
+ private _bindEvents(): void {
432
+ if (!this._trigger || !this._input) return;
433
+
434
+ this._trigger.addEventListener('click', (e) => {
435
+ if (this.config.disabled) return;
436
+ e.stopPropagation();
437
+ this._isOpen ? this._close() : this._open();
438
+ });
439
+
440
+ this._outsideClickHandler = (e: MouseEvent) => {
441
+ if (!this.el.contains(e.target as Node) && !this._dropdown?.contains(e.target as Node)) {
442
+ this._close();
443
+ }
444
+ };
445
+ document.addEventListener('click', this._outsideClickHandler);
446
+
447
+ this._input.addEventListener('keydown', (e) => {
448
+ if (e.key === 'Escape') {
449
+ this._close();
450
+ } else if (e.key === 'Enter' || e.key === ' ') {
451
+ e.preventDefault();
452
+ this._isOpen ? this._close() : this._open();
453
+ }
454
+ });
455
+ }
456
+
457
+ private _open(): void {
458
+ if (this._isOpen) return;
459
+ this._isOpen = true;
460
+
461
+ this._parseValue();
462
+ this._buildTimePicker();
463
+ this.el.classList.add(cls('isOpen'));
464
+ this._positionDropdown();
465
+
466
+ if (this._dropdown) {
467
+ this._dropdown.style.opacity = '1';
468
+ this._dropdown.style.visibility = 'visible';
469
+ }
470
+ }
471
+
472
+ private _positionDropdown(): void {
473
+ if (!this._trigger || !this._dropdown) return;
474
+
475
+ const rect = this._trigger.getBoundingClientRect();
476
+ const dropdownHeight = 280;
477
+ const viewportHeight = window.innerHeight;
478
+ const spaceBelow = viewportHeight - rect.bottom;
479
+
480
+ this._dropdown.style.position = 'fixed';
481
+ this._dropdown.style.left = `${rect.left}px`;
482
+ this._dropdown.style.width = `${Math.max(rect.width, 200)}px`;
483
+ this._dropdown.style.zIndex = '9999';
484
+
485
+ if (spaceBelow < dropdownHeight && rect.top > spaceBelow) {
486
+ this._dropdown.style.bottom = `${viewportHeight - rect.top + 4}px`;
487
+ this._dropdown.style.top = 'auto';
488
+ } else {
489
+ this._dropdown.style.top = `${rect.bottom + 4}px`;
490
+ this._dropdown.style.bottom = 'auto';
491
+ }
492
+ }
493
+
494
+ private _close(): void {
495
+ if (!this._isOpen) return;
496
+ this._isOpen = false;
497
+ this.el.classList.remove(cls('isOpen'));
498
+
499
+ if (this._dropdown) {
500
+ this._dropdown.style.opacity = '0';
501
+ this._dropdown.style.visibility = 'hidden';
502
+ }
503
+ }
504
+
505
+ setValue(value: string | null): void {
506
+ this.value = value;
507
+ this._parseValue();
508
+ this._updateDisplay();
509
+ }
510
+
511
+ getValue(): string | null {
512
+ return this.value;
513
+ }
514
+
515
+ showError(message: string): void {
516
+ this.el.classList.add(cls('hasError'));
517
+ if (this._error) {
518
+ this._error.classList.add(cls('fieldErrorVisible'));
519
+ this._error.textContent = message;
520
+ }
521
+ }
522
+
523
+ clearError(): void {
524
+ this.el.classList.remove(cls('hasError'));
525
+ if (this._error) {
526
+ this._error.classList.remove(cls('fieldErrorVisible'));
527
+ this._error.textContent = '';
528
+ }
529
+ }
530
+
531
+ destroy(): void {
532
+ if (this._outsideClickHandler) {
533
+ document.removeEventListener('click', this._outsideClickHandler);
534
+ }
535
+ if (this._dropdown && this._dropdown.parentNode) {
536
+ this._dropdown.parentNode.removeChild(this._dropdown);
537
+ }
538
+ super.destroy();
539
+ }
540
+ }