@kupola/kupola 1.2.0 → 1.4.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 (111) 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/css/components-ext.css +157 -0
  8. package/css/components.css +69 -0
  9. package/css/theme-dark.css +5 -5
  10. package/css/utilities.css +119 -1
  11. package/dist/css/accessibility.css +119 -0
  12. package/dist/css/animations.css +224 -0
  13. package/dist/css/brand-themes.css +300 -0
  14. package/dist/css/colors_and_type.css +441 -0
  15. package/dist/css/components-ext.css +4165 -0
  16. package/dist/css/components.css +1483 -0
  17. package/dist/css/kupola.css +1 -12
  18. package/dist/css/kupola.min.css +1 -0
  19. package/dist/css/responsive.css +697 -0
  20. package/dist/css/scaffold.css +145 -0
  21. package/dist/css/states.css +316 -0
  22. package/dist/css/theme-dark.css +296 -0
  23. package/dist/css/theme-light.css +296 -0
  24. package/dist/css/utilities.css +171 -0
  25. package/dist/icons.svg +284 -0
  26. package/dist/kupola.cjs.js +17409 -257
  27. package/dist/kupola.cjs.js.map +1 -1
  28. package/dist/kupola.css +9000 -0
  29. package/dist/kupola.esm.js +17183 -7597
  30. package/dist/kupola.esm.js.map +1 -1
  31. package/dist/kupola.min.css +1 -0
  32. package/dist/kupola.min.js +2 -0
  33. package/dist/kupola.min.js.map +1 -0
  34. package/dist/kupola.umd.js +17415 -257
  35. package/dist/kupola.umd.js.map +1 -1
  36. package/dist/plugins/vite-plugin-kupola.js +120 -0
  37. package/dist/types/kupola.d.ts +421 -323
  38. package/js/calendar.js +334 -25
  39. package/js/carousel.js +182 -48
  40. package/js/collapse.js +148 -34
  41. package/js/color-picker.js +416 -108
  42. package/js/component.js +8 -19
  43. package/js/countdown.js +9 -8
  44. package/js/data-bind.js +73 -16
  45. package/js/datepicker.js +488 -110
  46. package/js/depends.js +710 -0
  47. package/js/dialog.js +4 -2
  48. package/js/drawer.js +172 -8
  49. package/js/dropdown.js +272 -17
  50. package/js/dynamic-tags.js +156 -40
  51. package/js/fileupload.js +9 -8
  52. package/js/form.js +280 -254
  53. package/js/global-events.js +281 -188
  54. package/js/heatmap.js +10 -7
  55. package/js/i18n.js +18 -10
  56. package/js/icons.js +141 -161
  57. package/js/image-preview.js +146 -2
  58. package/js/initializer.js +151 -71
  59. package/js/kupola-core.js +123 -45
  60. package/js/kupola-lifecycle.js +13 -11
  61. package/js/message.js +8 -1
  62. package/js/modal.js +207 -59
  63. package/js/notification.js +8 -1
  64. package/js/numberinput.js +9 -8
  65. package/js/pagination.js +263 -0
  66. package/js/registry.js +29 -12
  67. package/js/select.js +482 -27
  68. package/js/slide-captcha.js +11 -2
  69. package/js/slider.js +442 -25
  70. package/js/statcard.js +9 -7
  71. package/js/table.js +1210 -0
  72. package/js/tag.js +268 -14
  73. package/js/theme-standalone.js +88 -0
  74. package/js/theme.js +14 -43
  75. package/js/timepicker.js +335 -66
  76. package/js/tooltip.js +317 -86
  77. package/js/utils.js +6 -2
  78. package/js/validation.js +6 -2
  79. package/js/virtual-list.js +11 -7
  80. package/js/web-components.js +288 -0
  81. package/package.json +81 -67
  82. package/plugins/vite-plugin-kupola.js +120 -0
  83. package/scripts/build-css.cjs +113 -0
  84. package/scripts/build-svg-sprite.cjs +107 -0
  85. package/types/kupola.d.ts +421 -323
  86. package/CHANGELOG.md +0 -130
  87. package/INTEGRATION.md +0 -440
  88. package/PROJECT_SUMMARY.md +0 -312
  89. package/SKILL.md +0 -572
  90. package/dist/utils/utils/Kupola.cs +0 -77
  91. package/dist/utils/utils/Kupola.java +0 -104
  92. package/dist/utils/utils/kupola.go +0 -120
  93. package/dist/utils/utils/kupola.js +0 -63
  94. package/dist/utils/utils/kupola.py +0 -1392
  95. package/dist/utils/utils/kupola.rb +0 -69
  96. package/js/composition-api.js +0 -458
  97. package/js/error-handler.js +0 -181
  98. package/js/http.js +0 -419
  99. package/js/kupola-devtools.js +0 -598
  100. package/js/performance.js +0 -250
  101. package/js/router.js +0 -396
  102. package/js/security.js +0 -189
  103. package/js/test-utils.js +0 -251
  104. package/templates/base.html +0 -30
  105. package/templates/base_dashboard.html +0 -99
  106. package/utils/Kupola.cs +0 -77
  107. package/utils/Kupola.java +0 -104
  108. package/utils/kupola.go +0 -120
  109. package/utils/kupola.js +0 -63
  110. package/utils/kupola.py +0 -1392
  111. 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
+ }