@kupola/kupola 1.2.0 → 1.3.1

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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +330 -286
  3. package/adapters/axios.d.ts +34 -0
  4. package/adapters/axios.js +122 -0
  5. package/adapters/navios-http.d.ts +110 -0
  6. package/adapters/navios-http.js +151 -0
  7. package/dist/css/accessibility.css +119 -0
  8. package/dist/css/animations.css +224 -0
  9. package/dist/css/brand-themes.css +300 -0
  10. package/dist/css/colors_and_type.css +441 -0
  11. package/dist/css/components-ext.css +4165 -0
  12. package/dist/css/components.css +1483 -0
  13. package/dist/css/responsive.css +697 -0
  14. package/dist/css/scaffold.css +145 -0
  15. package/dist/css/states.css +316 -0
  16. package/dist/css/theme-dark.css +296 -0
  17. package/dist/css/theme-light.css +296 -0
  18. package/dist/css/utilities.css +171 -0
  19. package/dist/kupola.cjs.js +219 -161
  20. package/dist/kupola.cjs.js.map +1 -1
  21. package/dist/kupola.esm.js +6643 -5709
  22. package/dist/kupola.esm.js.map +1 -1
  23. package/dist/kupola.umd.js +219 -161
  24. package/dist/kupola.umd.js.map +1 -1
  25. package/dist/plugins/vite-plugin-kupola.js +120 -0
  26. package/dist/types/kupola.d.ts +421 -323
  27. package/js/calendar.js +334 -25
  28. package/js/carousel.js +182 -48
  29. package/js/collapse.js +148 -34
  30. package/js/color-picker.js +416 -108
  31. package/js/component.js +8 -19
  32. package/js/countdown.js +9 -8
  33. package/js/data-bind.js +73 -16
  34. package/js/datepicker.js +488 -110
  35. package/js/depends.js +710 -0
  36. package/js/dialog.js +4 -2
  37. package/js/drawer.js +172 -8
  38. package/js/dropdown.js +272 -17
  39. package/js/dynamic-tags.js +156 -40
  40. package/js/fileupload.js +9 -8
  41. package/js/form.js +280 -254
  42. package/js/global-events.js +281 -188
  43. package/js/heatmap.js +10 -7
  44. package/js/i18n.js +18 -10
  45. package/js/icons.js +141 -161
  46. package/js/image-preview.js +146 -2
  47. package/js/initializer.js +113 -71
  48. package/js/kupola-core.js +123 -45
  49. package/js/kupola-lifecycle.js +13 -11
  50. package/js/message.js +8 -1
  51. package/js/modal.js +207 -59
  52. package/js/notification.js +8 -1
  53. package/js/numberinput.js +9 -8
  54. package/js/pagination.js +263 -0
  55. package/js/registry.js +29 -12
  56. package/js/select.js +482 -27
  57. package/js/slide-captcha.js +11 -2
  58. package/js/slider.js +442 -25
  59. package/js/statcard.js +9 -7
  60. package/js/table.js +1210 -0
  61. package/js/tag.js +268 -14
  62. package/js/theme.js +14 -43
  63. package/js/timepicker.js +335 -66
  64. package/js/tooltip.js +317 -86
  65. package/js/utils.js +6 -2
  66. package/js/validation.js +6 -2
  67. package/js/virtual-list.js +11 -7
  68. package/js/web-components.js +288 -0
  69. package/package.json +77 -67
  70. package/plugins/vite-plugin-kupola.js +120 -0
  71. package/types/kupola.d.ts +421 -323
  72. package/CHANGELOG.md +0 -130
  73. package/INTEGRATION.md +0 -440
  74. package/PROJECT_SUMMARY.md +0 -312
  75. package/SKILL.md +0 -572
  76. package/dist/utils/utils/Kupola.cs +0 -77
  77. package/dist/utils/utils/Kupola.java +0 -104
  78. package/dist/utils/utils/kupola.go +0 -120
  79. package/dist/utils/utils/kupola.js +0 -63
  80. package/dist/utils/utils/kupola.py +0 -1392
  81. package/dist/utils/utils/kupola.rb +0 -69
  82. package/js/composition-api.js +0 -458
  83. package/js/error-handler.js +0 -181
  84. package/js/http.js +0 -419
  85. package/js/kupola-devtools.js +0 -598
  86. package/js/performance.js +0 -250
  87. package/js/router.js +0 -396
  88. package/js/security.js +0 -189
  89. package/js/test-utils.js +0 -251
  90. package/templates/base.html +0 -30
  91. package/templates/base_dashboard.html +0 -99
  92. package/utils/Kupola.cs +0 -77
  93. package/utils/Kupola.java +0 -104
  94. package/utils/kupola.go +0 -120
  95. package/utils/kupola.js +0 -63
  96. package/utils/kupola.py +0 -1392
  97. package/utils/kupola.rb +0 -69
package/js/timepicker.js CHANGED
@@ -1,28 +1,53 @@
1
1
  class Timepicker {
2
- constructor(element) {
2
+ constructor(element, options = {}) {
3
3
  this.element = element;
4
4
  this.input = element.querySelector('input');
5
5
  this.inputWrap = element.querySelector('.ds-timepicker__input-wrap');
6
6
  this.panelEl = null;
7
7
  this.scope = `timepicker-${Math.random().toString(36).substr(2, 9)}`;
8
8
 
9
+ // Options
10
+ this.showSeconds = options.showSeconds || element.hasAttribute('data-timepicker-seconds');
11
+ this.use12Hour = options.use12Hour || element.hasAttribute('data-timepicker-12h');
12
+ this.hourStep = options.hourStep || parseInt(element.getAttribute('data-timepicker-hour-step')) || 1;
13
+ this.minuteStep = options.minuteStep || parseInt(element.getAttribute('data-timepicker-minute-step')) || 5;
14
+ this.secondStep = options.secondStep || parseInt(element.getAttribute('data-timepicker-second-step')) || 5;
15
+ this.minTime = options.minTime || element.getAttribute('data-timepicker-min') || null; // "HH:MM" or "HH:MM:SS"
16
+ this.maxTime = options.maxTime || element.getAttribute('data-timepicker-max') || null;
17
+ this.disabledTime = options.disabledTime || null; // function(hour, minute, second) => boolean
18
+ this.placeholder = options.placeholder || element.getAttribute('data-timepicker-placeholder') || '';
19
+ this.clearable = options.clearable || element.hasAttribute('data-timepicker-clear');
20
+ this.onChange = options.onChange || null;
21
+
22
+ this.selectedHour = 12;
23
+ this.selectedMinute = 0;
24
+ this.selectedSecond = 0;
25
+ this.isPM = false;
26
+
9
27
  this._inputWrapClickHandler = null;
10
28
  this._documentClickHandler = null;
11
29
  this._documentClickListener = null;
12
30
  this._resizeHandler = null;
13
31
  this._resizeListener = null;
14
-
15
- this.selectedHour = 12;
16
- this.selectedMinute = 0;
32
+ this._keydownHandler = null;
17
33
  }
18
34
 
19
35
  init() {
20
36
  if (this.element.__kupolaInitialized) return;
21
37
 
38
+ if (this.placeholder && this.input) {
39
+ this.input.placeholder = this.placeholder;
40
+ }
41
+
42
+ // Parse initial value
43
+ if (this.input && this.input.value) {
44
+ this._parseInputValue();
45
+ }
46
+
22
47
  this._inputWrapClickHandler = (e) => {
23
48
  e.stopPropagation();
24
49
  if (this.panelEl && this.panelEl.style.display === 'block') {
25
- this.panelEl.style.display = 'none';
50
+ this.hideTimepicker();
26
51
  } else {
27
52
  this.showTimepicker();
28
53
  }
@@ -40,9 +65,79 @@ class Timepicker {
40
65
  this._resizeHandler = () => this.resizeHandler();
41
66
  }
42
67
 
68
+ this._keydownHandler = (e) => {
69
+ if (e.key === 'Escape' && this.panelEl && this.panelEl.style.display === 'block') {
70
+ this.hideTimepicker();
71
+ }
72
+ };
73
+ document.addEventListener('keydown', this._keydownHandler);
74
+
43
75
  this.element.__kupolaInitialized = true;
44
76
  }
45
77
 
78
+ _parseInputValue() {
79
+ const val = this.input.value.trim();
80
+ if (!val) return;
81
+
82
+ // Try 12h format: "2:30 PM"
83
+ const ampmMatch = val.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(AM|PM|am|pm)$/i);
84
+ if (ampmMatch) {
85
+ this.selectedHour = parseInt(ampmMatch[1]) % 12;
86
+ if (ampmMatch[4].toUpperCase() === 'PM') this.selectedHour += 12;
87
+ this.selectedMinute = parseInt(ampmMatch[2]);
88
+ this.selectedSecond = ampmMatch[3] ? parseInt(ampmMatch[3]) : 0;
89
+ return;
90
+ }
91
+
92
+ // 24h format: "14:30" or "14:30:45"
93
+ const parts = val.split(':');
94
+ if (parts.length >= 2) {
95
+ this.selectedHour = parseInt(parts[0]) || 0;
96
+ this.selectedMinute = parseInt(parts[1]) || 0;
97
+ this.selectedSecond = parts[2] ? parseInt(parts[2]) || 0 : 0;
98
+ }
99
+ }
100
+
101
+ _isTimeDisabled(hour, minute, second) {
102
+ if (this.disabledTime) return this.disabledTime(hour, minute, second);
103
+
104
+ const timeVal = hour * 3600 + minute * 60 + second;
105
+
106
+ if (this.minTime) {
107
+ const minParts = this.minTime.split(':');
108
+ const minVal = parseInt(minParts[0]) * 3600 + parseInt(minParts[1]) * 60 + (parseInt(minParts[2]) || 0);
109
+ if (timeVal < minVal) return true;
110
+ }
111
+
112
+ if (this.maxTime) {
113
+ const maxParts = this.maxTime.split(':');
114
+ const maxVal = parseInt(maxParts[0]) * 3600 + parseInt(maxParts[1]) * 60 + (parseInt(maxParts[2]) || 0);
115
+ if (timeVal > maxVal) return true;
116
+ }
117
+
118
+ return false;
119
+ }
120
+
121
+ _formatTime() {
122
+ let h = this.selectedHour;
123
+ let m = this.selectedMinute;
124
+ let s = this.selectedSecond;
125
+
126
+ if (this.use12Hour) {
127
+ const period = h >= 12 ? 'PM' : 'AM';
128
+ h = h % 12 || 12;
129
+ if (this.showSeconds) {
130
+ return `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')} ${period}`;
131
+ }
132
+ return `${h}:${String(m).padStart(2, '0')} ${period}`;
133
+ }
134
+
135
+ if (this.showSeconds) {
136
+ return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
137
+ }
138
+ return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
139
+ }
140
+
46
141
  calculatePosition() {
47
142
  if (!this.panelEl) return;
48
143
 
@@ -69,108 +164,256 @@ class Timepicker {
69
164
  showTimepicker() {
70
165
  if (this.panelEl) {
71
166
  this.panelEl.style.display = 'block';
167
+ this._syncPanelSelection();
72
168
  this.calculatePosition();
73
169
  return;
74
170
  }
75
171
 
76
172
  this.panelEl = document.createElement('div');
77
173
  this.panelEl.className = 'ds-timepicker__panel';
174
+
175
+ // Build panel HTML
176
+ let sectionsHTML = '';
177
+
178
+ // Hour section
179
+ sectionsHTML += `<div class="ds-timepicker__section">
180
+ <div class="ds-timepicker__section-label">Hour</div>
181
+ <div class="ds-timepicker__grid ds-timepicker__grid--hour" data-type="hour"></div>
182
+ </div>`;
183
+
184
+ // Minute section
185
+ sectionsHTML += `<div class="ds-timepicker__section">
186
+ <div class="ds-timepicker__section-label">Min</div>
187
+ <div class="ds-timepicker__grid ds-timepicker__grid--minute" data-type="minute"></div>
188
+ </div>`;
189
+
190
+ // Second section (optional)
191
+ if (this.showSeconds) {
192
+ sectionsHTML += `<div class="ds-timepicker__section">
193
+ <div class="ds-timepicker__section-label">Sec</div>
194
+ <div class="ds-timepicker__grid ds-timepicker__grid--second" data-type="second"></div>
195
+ </div>`;
196
+ }
197
+
198
+ // AM/PM section (optional)
199
+ if (this.use12Hour) {
200
+ sectionsHTML += `<div class="ds-timepicker__section ds-timepicker__section--ampm">
201
+ <div class="ds-timepicker__grid ds-timepicker__grid--ampm" data-type="ampm"></div>
202
+ </div>`;
203
+ }
204
+
78
205
  this.panelEl.innerHTML = `
79
206
  <div class="ds-timepicker__header">
80
207
  <div class="ds-timepicker__display">
81
- <span class="ds-timepicker__display-hour">12</span>
208
+ <span class="ds-timepicker__display-hour">${String(this.selectedHour).padStart(2, '0')}</span>
82
209
  <span class="ds-timepicker__separator">:</span>
83
- <span class="ds-timepicker__display-minute">00</span>
84
- </div>
85
- </div>
86
- <div class="ds-timepicker__body">
87
- <div class="ds-timepicker__section">
88
- <div class="ds-timepicker__section-label">Hour</div>
89
- <div class="ds-timepicker__grid ds-timepicker__grid--hour" data-type="hour"></div>
90
- </div>
91
- <div class="ds-timepicker__section">
92
- <div class="ds-timepicker__section-label">Minute</div>
93
- <div class="ds-timepicker__grid ds-timepicker__grid--minute" data-type="minute"></div>
210
+ <span class="ds-timepicker__display-minute">${String(this.selectedMinute).padStart(2, '0')}</span>
211
+ ${this.showSeconds ? '<span class="ds-timepicker__separator">:</span><span class="ds-timepicker__display-second">' + String(this.selectedSecond).padStart(2, '0') + '</span>' : ''}
212
+ ${this.use12Hour ? '<span class="ds-timepicker__display-ampm">' + (this.selectedHour >= 12 ? 'PM' : 'AM') + '</span>' : ''}
94
213
  </div>
95
214
  </div>
215
+ <div class="ds-timepicker__body">${sectionsHTML}</div>
216
+ ${this.clearable ? '<div class="ds-timepicker__footer"><button class="ds-timepicker__clear-btn" type="button">Clear</button></div>' : ''}
96
217
  `;
218
+
97
219
  this.element.appendChild(this.panelEl);
98
220
 
99
- if (this.input.value) {
100
- const timeParts = this.input.value.split(':');
101
- if (timeParts.length >= 2) {
102
- this.selectedHour = parseInt(timeParts[0]) || 0;
103
- this.selectedMinute = parseInt(timeParts[1]) || 0;
221
+ // Populate grids
222
+ this._populateHourGrid();
223
+ this._populateMinuteGrid();
224
+ if (this.showSeconds) this._populateSecondGrid();
225
+ if (this.use12Hour) this._populateAmPmGrid();
226
+
227
+ // Clear button
228
+ if (this.clearable) {
229
+ const clearBtn = this.panelEl.querySelector('.ds-timepicker__clear-btn');
230
+ if (clearBtn) {
231
+ clearBtn.addEventListener('click', (e) => {
232
+ e.stopPropagation();
233
+ this.input.value = '';
234
+ this.hideTimepicker();
235
+ this.input.dispatchEvent(new Event('change'));
236
+ });
104
237
  }
105
238
  }
106
239
 
107
- const displayHour = this.panelEl.querySelector('.ds-timepicker__display-hour');
108
- const displayMinute = this.panelEl.querySelector('.ds-timepicker__display-minute');
109
-
110
- function updateDisplay() {
111
- displayHour.textContent = String(this.selectedHour).padStart(2, '0');
112
- displayMinute.textContent = String(this.selectedMinute).padStart(2, '0');
113
- }
240
+ this.panelEl.addEventListener('click', (e) => e.stopPropagation());
114
241
 
115
- function updateInput() {
116
- this.input.value = `${String(this.selectedHour).padStart(2, '0')}:${String(this.selectedMinute).padStart(2, '0')}`;
117
- }
242
+ this._syncPanelSelection();
118
243
 
119
- function selectTimeAndClose() {
120
- updateInput.call(this);
121
- this.panelEl.style.display = 'none';
122
- this.input.dispatchEvent(new Event('change'));
123
- }
244
+ setTimeout(() => {
245
+ this.calculatePosition();
246
+ // Scroll selected items into view
247
+ this._scrollToSelection();
248
+ }, 0);
249
+ }
250
+
251
+ _populateHourGrid() {
252
+ const grid = this.panelEl.querySelector('[data-type="hour"]');
253
+ if (!grid) return;
124
254
 
125
- const hourGrid = this.panelEl.querySelector('[data-type="hour"]');
126
- const minuteGrid = this.panelEl.querySelector('[data-type="minute"]');
255
+ const max = this.use12Hour ? 12 : 24;
256
+ const start = this.use12Hour ? 1 : 0;
127
257
 
128
- for (let i = 0; i < 24; i++) {
258
+ for (let i = start; i < (this.use12Hour ? 13 : 24); i += this.hourStep) {
129
259
  const btn = document.createElement('button');
260
+ btn.type = 'button';
130
261
  btn.className = 'ds-timepicker__item';
131
262
  btn.textContent = String(i).padStart(2, '0');
132
- if (i === this.selectedHour) btn.classList.add('is-selected');
263
+ btn.dataset.value = i;
264
+
133
265
  btn.addEventListener('click', () => {
134
- hourGrid.querySelectorAll('.ds-timepicker__item').forEach(b => b.classList.remove('is-selected'));
135
- btn.classList.add('is-selected');
136
- this.selectedHour = i;
137
- updateDisplay.call(this);
138
- selectTimeAndClose.call(this);
266
+ let hour = i;
267
+ if (this.use12Hour) {
268
+ if (i === 12) hour = this.isPM ? 12 : 0;
269
+ else hour = this.isPM ? i + 12 : i;
270
+ }
271
+ this.selectedHour = hour;
272
+ this._updateDisplay();
273
+ this._syncGridSelection('hour', i);
274
+ this._confirmSelection();
139
275
  });
140
- hourGrid.appendChild(btn);
276
+
277
+ grid.appendChild(btn);
141
278
  }
279
+ }
280
+
281
+ _populateMinuteGrid() {
282
+ const grid = this.panelEl.querySelector('[data-type="minute"]');
283
+ if (!grid) return;
142
284
 
143
- for (let i = 0; i < 60; i += 5) {
285
+ for (let i = 0; i < 60; i += this.minuteStep) {
144
286
  const btn = document.createElement('button');
287
+ btn.type = 'button';
145
288
  btn.className = 'ds-timepicker__item';
146
289
  btn.textContent = String(i).padStart(2, '0');
147
- if (i === this.selectedMinute) btn.classList.add('is-selected');
290
+ btn.dataset.value = i;
291
+
148
292
  btn.addEventListener('click', () => {
149
- minuteGrid.querySelectorAll('.ds-timepicker__item').forEach(b => b.classList.remove('is-selected'));
150
- btn.classList.add('is-selected');
151
293
  this.selectedMinute = i;
152
- updateDisplay.call(this);
153
- selectTimeAndClose.call(this);
294
+ this._updateDisplay();
295
+ this._syncGridSelection('minute', i);
296
+ this._confirmSelection();
154
297
  });
155
- minuteGrid.appendChild(btn);
298
+
299
+ grid.appendChild(btn);
156
300
  }
301
+ }
302
+
303
+ _populateSecondGrid() {
304
+ const grid = this.panelEl.querySelector('[data-type="second"]');
305
+ if (!grid) return;
157
306
 
158
- this.panelEl.addEventListener('click', (e) => {
159
- e.stopPropagation();
307
+ for (let i = 0; i < 60; i += this.secondStep) {
308
+ const btn = document.createElement('button');
309
+ btn.type = 'button';
310
+ btn.className = 'ds-timepicker__item';
311
+ btn.textContent = String(i).padStart(2, '0');
312
+ btn.dataset.value = i;
313
+
314
+ btn.addEventListener('click', () => {
315
+ this.selectedSecond = i;
316
+ this._updateDisplay();
317
+ this._syncGridSelection('second', i);
318
+ this._confirmSelection();
319
+ });
320
+
321
+ grid.appendChild(btn);
322
+ }
323
+ }
324
+
325
+ _populateAmPmGrid() {
326
+ const grid = this.panelEl.querySelector('[data-type="ampm"]');
327
+ if (!grid) return;
328
+
329
+ ['AM', 'PM'].forEach(label => {
330
+ const btn = document.createElement('button');
331
+ btn.type = 'button';
332
+ btn.className = 'ds-timepicker__item';
333
+ btn.textContent = label;
334
+ btn.dataset.value = label;
335
+
336
+ btn.addEventListener('click', () => {
337
+ this.isPM = label === 'PM';
338
+ if (this.isPM && this.selectedHour < 12) this.selectedHour += 12;
339
+ if (!this.isPM && this.selectedHour >= 12) this.selectedHour -= 12;
340
+ this._updateDisplay();
341
+ grid.querySelectorAll('.ds-timepicker__item').forEach(b => b.classList.remove('is-selected'));
342
+ btn.classList.add('is-selected');
343
+ this._confirmSelection();
344
+ });
345
+
346
+ grid.appendChild(btn);
160
347
  });
348
+ }
349
+
350
+ _updateDisplay() {
351
+ if (!this.panelEl) return;
161
352
 
162
- updateDisplay.call(this);
163
- updateInput.call(this);
353
+ const displayHour = this.panelEl.querySelector('.ds-timepicker__display-hour');
354
+ const displayMinute = this.panelEl.querySelector('.ds-timepicker__display-minute');
355
+ const displaySecond = this.panelEl.querySelector('.ds-timepicker__display-second');
356
+ const displayAmPm = this.panelEl.querySelector('.ds-timepicker__display-ampm');
164
357
 
165
- setTimeout(() => {
166
- this.calculatePosition();
167
- }, 0);
358
+ if (displayHour) {
359
+ const h = this.use12Hour ? (this.selectedHour % 12 || 12) : this.selectedHour;
360
+ displayHour.textContent = String(h).padStart(2, '0');
361
+ }
362
+ if (displayMinute) displayMinute.textContent = String(this.selectedMinute).padStart(2, '0');
363
+ if (displaySecond) displaySecond.textContent = String(this.selectedSecond).padStart(2, '0');
364
+ if (displayAmPm) displayAmPm.textContent = this.selectedHour >= 12 ? 'PM' : 'AM';
365
+ }
366
+
367
+ _syncPanelSelection() {
368
+ if (!this.panelEl) return;
369
+
370
+ // Sync hour grid
371
+ const hourVal = this.use12Hour ? (this.selectedHour % 12 || 12) : this.selectedHour;
372
+ this._syncGridSelection('hour', hourVal);
373
+ this._syncGridSelection('minute', this.selectedMinute);
374
+ this._syncGridSelection('second', this.selectedSecond);
375
+
376
+ // Sync AM/PM
377
+ const ampmGrid = this.panelEl.querySelector('[data-type="ampm"]');
378
+ if (ampmGrid) {
379
+ ampmGrid.querySelectorAll('.ds-timepicker__item').forEach(b => {
380
+ b.classList.toggle('is-selected', (b.dataset.value === 'PM') === (this.selectedHour >= 12));
381
+ });
382
+ }
383
+
384
+ this._updateDisplay();
385
+ }
386
+
387
+ _syncGridSelection(type, value) {
388
+ const grid = this.panelEl.querySelector(`[data-type="${type}"]`);
389
+ if (!grid) return;
390
+ grid.querySelectorAll('.ds-timepicker__item').forEach(b => {
391
+ b.classList.toggle('is-selected', parseInt(b.dataset.value) === value);
392
+ });
393
+ }
394
+
395
+ _scrollToSelection() {
396
+ ['hour', 'minute', 'second'].forEach(type => {
397
+ const grid = this.panelEl.querySelector(`[data-type="${type}"]`);
398
+ if (!grid) return;
399
+ const selected = grid.querySelector('.is-selected');
400
+ if (selected) selected.scrollIntoView({ block: 'center' });
401
+ });
402
+ }
403
+
404
+ _confirmSelection() {
405
+ this.input.value = this._formatTime();
168
406
  }
169
407
 
170
408
  hideTimepicker(e) {
171
409
  if (this.panelEl && this.panelEl.style.display === 'block') {
172
410
  if (!this.element.contains(e.target)) {
173
411
  this.panelEl.style.display = 'none';
412
+ this.input.value = this._formatTime();
413
+ this.input.dispatchEvent(new Event('change'));
414
+ if (this.onChange) {
415
+ this.onChange({ hour: this.selectedHour, minute: this.selectedMinute, second: this.selectedSecond, timeStr: this._formatTime() });
416
+ }
174
417
  }
175
418
  }
176
419
  }
@@ -181,6 +424,20 @@ class Timepicker {
181
424
  }
182
425
  }
183
426
 
427
+ // Public API
428
+ setTime(hour, minute, second) {
429
+ this.selectedHour = Math.max(0, Math.min(23, hour));
430
+ this.selectedMinute = Math.max(0, Math.min(59, minute));
431
+ this.selectedSecond = Math.max(0, Math.min(59, second || 0));
432
+ this.isPM = this.selectedHour >= 12;
433
+ if (this.input) this.input.value = this._formatTime();
434
+ if (this.panelEl) this._syncPanelSelection();
435
+ }
436
+
437
+ getTime() {
438
+ return { hour: this.selectedHour, minute: this.selectedMinute, second: this.selectedSecond };
439
+ }
440
+
184
441
  destroy() {
185
442
  if (!this.element.__kupolaInitialized) return;
186
443
 
@@ -200,6 +457,8 @@ class Timepicker {
200
457
  window.removeEventListener('resize', this._resizeHandler);
201
458
  }
202
459
 
460
+ if (this._keydownHandler) document.removeEventListener('keydown', this._keydownHandler);
461
+
203
462
  if (this.panelEl) {
204
463
  this.panelEl.remove();
205
464
  this.panelEl = null;
@@ -210,12 +469,13 @@ class Timepicker {
210
469
  this._documentClickListener = null;
211
470
  this._resizeHandler = null;
212
471
  this._resizeListener = null;
472
+ this._keydownHandler = null;
213
473
  this.element.__kupolaInitialized = false;
214
474
  }
215
475
  }
216
476
 
217
- function initTimepicker(element) {
218
- const picker = new Timepicker(element);
477
+ function initTimepicker(element, options) {
478
+ const picker = new Timepicker(element, options);
219
479
  picker.init();
220
480
  element._kupolaTimepicker = picker;
221
481
  }
@@ -233,6 +493,15 @@ function cleanupTimepicker(element) {
233
493
  }
234
494
  }
235
495
 
236
- if (window.kupolaInitializer) {
237
- window.kupolaInitializer.register('timepicker', initTimepicker, cleanupTimepicker);
238
- }
496
+ export { Timepicker, initTimepicker, initTimepickers, cleanupTimepicker };
497
+
498
+ if (typeof window !== 'undefined') {
499
+ window.Timepicker = Timepicker;
500
+ window.initTimepicker = initTimepicker;
501
+ window.initTimepickers = initTimepickers;
502
+ window.cleanupTimepicker = cleanupTimepicker;
503
+
504
+ if (window.kupolaInitializer) {
505
+ window.kupolaInitializer.register('timepicker', initTimepicker, cleanupTimepicker);
506
+ }
507
+ }