@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/select.js CHANGED
@@ -1,5 +1,5 @@
1
1
  class Select {
2
- constructor(element) {
2
+ constructor(element, options = {}) {
3
3
  this.element = element;
4
4
  this.trigger = element.querySelector('.ds-select__trigger');
5
5
  this.valueEl = element.querySelector('.ds-select__value') || element.querySelector('.ds-select__trigger span');
@@ -8,43 +8,122 @@ class Select {
8
8
  this.icon = element.querySelector('.ds-select__icon');
9
9
  this.scope = `select-${Math.random().toString(36).substr(2, 9)}`;
10
10
 
11
+ // Options
12
+ this.multiple = options.multiple || element.hasAttribute('data-select-multiple');
13
+ this.searchable = options.searchable || element.hasAttribute('data-select-search');
14
+ this.clearable = options.clearable || element.hasAttribute('data-select-clear');
15
+ this.placeholder = options.placeholder || element.getAttribute('data-select-placeholder') || '';
16
+ this.disabled = options.disabled || element.hasAttribute('data-select-disabled');
17
+ this.maxSelection = options.maxSelection || parseInt(element.getAttribute('data-select-max')) || Infinity;
18
+ this.remoteMethod = options.remoteMethod || null;
19
+ this.onChange = options.onChange || null;
20
+
21
+ // State
22
+ this.isOpen = false;
23
+ this.selectedValues = new Set();
24
+ this.allOptions = [];
25
+ this.filteredOptions = [];
26
+ this.focusIndex = -1;
27
+ this.searchInput = null;
28
+ this.clearBtn = null;
29
+ this.tagsWrap = null;
30
+
11
31
  this._triggerClickHandler = null;
12
32
  this._documentClickHandler = null;
13
33
  this._documentClickListener = null;
14
34
  this._optionClickHandler = null;
35
+ this._keydownHandler = null;
15
36
  }
16
37
 
17
38
  init() {
18
39
  if (!this.trigger || !this.optionsEl) return;
19
40
  if (this.element.__kupolaInitialized) return;
20
41
 
42
+ // Collect existing options
43
+ this._collectOptions();
44
+
45
+ // Build search input if searchable
46
+ if (this.searchable) {
47
+ this._createSearchInput();
48
+ }
49
+
50
+ // Build clear button if clearable
51
+ if (this.clearable) {
52
+ this._createClearButton();
53
+ }
54
+
55
+ // Build tags container for multiple mode
56
+ if (this.multiple) {
57
+ this._createTagsWrap();
58
+ }
59
+
60
+ // Set placeholder
61
+ if (this.placeholder && this.valueEl) {
62
+ this.valueEl.setAttribute('data-placeholder', this.placeholder);
63
+ if (!this.selectedValues.size) {
64
+ this.valueEl.classList.add('ds-select__value--placeholder');
65
+ this.valueEl.textContent = this.placeholder;
66
+ }
67
+ }
68
+
69
+ // Option click handler
21
70
  this._optionClickHandler = (e) => {
22
71
  const option = e.currentTarget;
72
+ if (option.classList.contains('is-disabled')) return;
73
+
23
74
  const value = option.getAttribute('data-value');
24
- if (this.nativeSelect) {
25
- this.nativeSelect.value = value;
26
- }
27
- this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(o => o.classList.remove('is-selected'));
28
- option.classList.add('is-selected');
29
- this.updateValue(option.textContent.trim());
30
- this.hideOptions();
31
- if (this.nativeSelect) {
32
- this.nativeSelect.dispatchEvent(new Event('change'));
75
+
76
+ if (this.multiple) {
77
+ this._toggleMultiOption(value, option);
78
+ } else {
79
+ this._selectSingleOption(value, option);
33
80
  }
34
81
  };
35
82
 
36
- this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(option => {
37
- option.addEventListener('click', this._optionClickHandler);
38
- option._selectOptionClickHandler = this._optionClickHandler;
39
- });
83
+ this._bindOptionClicks();
40
84
 
85
+ // Trigger click
41
86
  this._triggerClickHandler = (e) => {
42
87
  e.stopPropagation();
43
- this.showOptions();
88
+ if (this.disabled) return;
89
+ this.toggleOptions();
44
90
  };
45
91
 
46
92
  this.trigger.addEventListener('click', this._triggerClickHandler);
47
93
 
94
+ // Keyboard navigation
95
+ this._keydownHandler = (e) => {
96
+ if (!this.isOpen || this.disabled) return;
97
+ const items = this._getVisibleOptions();
98
+ if (!items.length) return;
99
+
100
+ switch (e.key) {
101
+ case 'ArrowDown':
102
+ e.preventDefault();
103
+ this.focusIndex = Math.min(this.focusIndex + 1, items.length - 1);
104
+ this._focusOption(items);
105
+ break;
106
+ case 'ArrowUp':
107
+ e.preventDefault();
108
+ this.focusIndex = Math.max(this.focusIndex - 1, 0);
109
+ this._focusOption(items);
110
+ break;
111
+ case 'Enter':
112
+ e.preventDefault();
113
+ if (this.focusIndex >= 0 && items[this.focusIndex]) {
114
+ items[this.focusIndex].click();
115
+ }
116
+ break;
117
+ case 'Escape':
118
+ e.preventDefault();
119
+ this.hideOptions();
120
+ this.trigger.focus();
121
+ break;
122
+ }
123
+ };
124
+ document.addEventListener('keydown', this._keydownHandler);
125
+
126
+ // Document click to close
48
127
  this._documentClickHandler = (e) => {
49
128
  if (!this.element.contains(e.target)) {
50
129
  this.hideOptions();
@@ -57,31 +136,389 @@ class Select {
57
136
  document.addEventListener('click', this._documentClickHandler);
58
137
  }
59
138
 
139
+ // Restore selected state from native select or data attributes
140
+ this._restoreSelectedState();
141
+
60
142
  this.optionsEl.style.display = 'none';
61
143
  this.element.__kupolaInitialized = true;
62
144
  }
63
145
 
146
+ _collectOptions() {
147
+ this.allOptions = [];
148
+ this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(opt => {
149
+ this.allOptions.push({
150
+ el: opt,
151
+ value: opt.getAttribute('data-value'),
152
+ text: opt.textContent.trim(),
153
+ group: opt.closest('.ds-select__group')?.getAttribute('data-group') || '',
154
+ disabled: opt.classList.contains('is-disabled')
155
+ });
156
+ });
157
+ this.filteredOptions = [...this.allOptions];
158
+ }
159
+
160
+ _createSearchInput() {
161
+ this.searchInput = document.createElement('input');
162
+ this.searchInput.className = 'ds-select__search';
163
+ this.searchInput.type = 'text';
164
+ this.searchInput.placeholder = 'Search...';
165
+ this.searchInput.setAttribute('autocomplete', 'off');
166
+
167
+ this.searchInput.addEventListener('input', () => this._handleSearch());
168
+ this.searchInput.addEventListener('click', (e) => e.stopPropagation());
169
+
170
+ // Insert at top of options
171
+ this.optionsEl.insertBefore(this.searchInput, this.optionsEl.firstChild);
172
+ }
173
+
174
+ _createClearButton() {
175
+ this.clearBtn = document.createElement('button');
176
+ this.clearBtn.className = 'ds-select__clear';
177
+ this.clearBtn.type = 'button';
178
+ this.clearBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
179
+ this.clearBtn.style.display = 'none';
180
+
181
+ this.clearBtn.addEventListener('click', (e) => {
182
+ e.stopPropagation();
183
+ this.clear();
184
+ });
185
+
186
+ // Insert into trigger area
187
+ const triggerParent = this.trigger.querySelector('.ds-select__value') || this.trigger;
188
+ triggerParent.parentNode.insertBefore(this.clearBtn, triggerParent.nextSibling);
189
+ }
190
+
191
+ _createTagsWrap() {
192
+ this.tagsWrap = document.createElement('div');
193
+ this.tagsWrap.className = 'ds-select__tags';
194
+
195
+ const triggerParent = this.valueEl || this.trigger;
196
+ triggerParent.parentNode.insertBefore(this.tagsWrap, triggerParent.nextSibling);
197
+ }
198
+
199
+ _handleSearch() {
200
+ const query = this.searchInput.value.toLowerCase().trim();
201
+
202
+ if (this.remoteMethod) {
203
+ this.remoteMethod(query, (options) => {
204
+ this._renderRemoteOptions(options);
205
+ });
206
+ return;
207
+ }
208
+
209
+ this.filteredOptions = this.allOptions.filter(opt =>
210
+ opt.text.toLowerCase().includes(query)
211
+ );
212
+
213
+ // Hide non-matching options
214
+ this.allOptions.forEach(opt => {
215
+ const visible = this.filteredOptions.includes(opt);
216
+ opt.el.style.display = visible ? '' : 'none';
217
+ });
218
+
219
+ // Show/hide group headers
220
+ this.optionsEl.querySelectorAll('.ds-select__group-title').forEach(title => {
221
+ const group = title.getAttribute('data-group');
222
+ const hasVisible = this.filteredOptions.some(opt => opt.group === group);
223
+ title.style.display = hasVisible ? '' : 'none';
224
+ });
225
+
226
+ this.focusIndex = -1;
227
+ }
228
+
229
+ _renderRemoteOptions(options) {
230
+ this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(opt => {
231
+ if (opt._selectOptionClickHandler) {
232
+ opt.removeEventListener('click', opt._selectOptionClickHandler);
233
+ }
234
+ opt.remove();
235
+ });
236
+
237
+ options.forEach(opt => {
238
+ const el = document.createElement('div');
239
+ el.className = 'ds-select__option';
240
+ el.setAttribute('data-value', opt.value);
241
+ el.textContent = opt.text || opt.label;
242
+ if (opt.disabled) el.classList.add('is-disabled');
243
+ if (this.selectedValues.has(opt.value)) el.classList.add('is-selected');
244
+
245
+ el.addEventListener('click', this._optionClickHandler);
246
+ el._selectOptionClickHandler = this._optionClickHandler;
247
+
248
+ this.optionsEl.appendChild(el);
249
+ });
250
+
251
+ this.allOptions = options.map(opt => ({
252
+ el: this.optionsEl.querySelector(`[data-value="${opt.value}"]`),
253
+ value: opt.value,
254
+ text: opt.text || opt.label,
255
+ group: '',
256
+ disabled: !!opt.disabled
257
+ }));
258
+ this.filteredOptions = [...this.allOptions];
259
+ }
260
+
261
+ _selectSingleOption(value, optionEl) {
262
+ // Deselect all
263
+ this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(o => o.classList.remove('is-selected'));
264
+ optionEl.classList.add('is-selected');
265
+
266
+ this.selectedValues.clear();
267
+ this.selectedValues.add(value);
268
+
269
+ this.updateValue(optionEl.textContent.trim());
270
+ this._syncNativeSelect();
271
+ this.hideOptions();
272
+ this._updateClearBtn();
273
+ this._fireChange();
274
+ }
275
+
276
+ _toggleMultiOption(value, optionEl) {
277
+ if (this.selectedValues.has(value)) {
278
+ this.selectedValues.delete(value);
279
+ optionEl.classList.remove('is-selected');
280
+ } else {
281
+ if (this.selectedValues.size >= this.maxSelection) return;
282
+ this.selectedValues.add(value);
283
+ optionEl.classList.add('is-selected');
284
+ }
285
+
286
+ this._updateTags();
287
+ this._updateValueDisplay();
288
+ this._syncNativeSelect();
289
+ this._updateClearBtn();
290
+ this._fireChange();
291
+ }
292
+
293
+ _updateTags() {
294
+ if (!this.tagsWrap) return;
295
+ this.tagsWrap.innerHTML = '';
296
+
297
+ this.selectedValues.forEach(value => {
298
+ const opt = this.allOptions.find(o => o.value === value);
299
+ if (!opt) return;
300
+
301
+ const tag = document.createElement('span');
302
+ tag.className = 'ds-select__tag';
303
+ tag.textContent = opt.text;
304
+
305
+ const removeBtn = document.createElement('button');
306
+ removeBtn.className = 'ds-select__tag-close';
307
+ removeBtn.type = 'button';
308
+ removeBtn.innerHTML = '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
309
+ removeBtn.addEventListener('click', (e) => {
310
+ e.stopPropagation();
311
+ this.selectedValues.delete(value);
312
+ const optEl = this.optionsEl.querySelector(`[data-value="${value}"]`);
313
+ if (optEl) optEl.classList.remove('is-selected');
314
+ this._updateTags();
315
+ this._updateValueDisplay();
316
+ this._syncNativeSelect();
317
+ this._updateClearBtn();
318
+ this._fireChange();
319
+ });
320
+
321
+ tag.appendChild(removeBtn);
322
+ this.tagsWrap.appendChild(tag);
323
+ });
324
+ }
325
+
326
+ _updateValueDisplay() {
327
+ if (!this.valueEl) return;
328
+
329
+ if (this.multiple) {
330
+ const count = this.selectedValues.size;
331
+ if (count === 0) {
332
+ this.valueEl.textContent = this.placeholder || '';
333
+ this.valueEl.classList.add('ds-select__value--placeholder');
334
+ } else {
335
+ this.valueEl.textContent = `Selected ${count}`;
336
+ this.valueEl.classList.remove('ds-select__value--placeholder');
337
+ }
338
+ // Hide trigger text when tags are shown
339
+ if (this.tagsWrap) {
340
+ this.valueEl.style.display = count > 0 ? 'none' : '';
341
+ }
342
+ } else {
343
+ if (this.selectedValues.size === 0) {
344
+ this.valueEl.textContent = this.placeholder || '';
345
+ this.valueEl.classList.add('ds-select__value--placeholder');
346
+ }
347
+ }
348
+ }
349
+
350
+ _updateClearBtn() {
351
+ if (!this.clearBtn) return;
352
+ this.clearBtn.style.display = this.selectedValues.size > 0 ? '' : 'none';
353
+ }
354
+
355
+ _syncNativeSelect() {
356
+ if (!this.nativeSelect) return;
357
+ if (this.multiple) {
358
+ Array.from(this.nativeSelect.options).forEach(opt => {
359
+ opt.selected = this.selectedValues.has(opt.value);
360
+ });
361
+ } else {
362
+ this.nativeSelect.value = Array.from(this.selectedValues)[0] || '';
363
+ }
364
+ }
365
+
366
+ _fireChange() {
367
+ if (this.nativeSelect) {
368
+ this.nativeSelect.dispatchEvent(new Event('change', { bubbles: true }));
369
+ }
370
+
371
+ const values = this.multiple ? Array.from(this.selectedValues) : Array.from(this.selectedValues)[0];
372
+
373
+ if (this.onChange) {
374
+ this.onChange({ values, selectedOptions: this.getSelectedOptions() });
375
+ }
376
+
377
+ this.element.dispatchEvent(new CustomEvent('kupola:select-change', {
378
+ detail: { values, selectedOptions: this.getSelectedOptions() },
379
+ bubbles: true
380
+ }));
381
+ }
382
+
383
+ _restoreSelectedState() {
384
+ if (this.nativeSelect) {
385
+ if (this.multiple) {
386
+ Array.from(this.nativeSelect.selectedOptions).forEach(opt => {
387
+ this.selectedValues.add(opt.value);
388
+ const el = this.optionsEl.querySelector(`[data-value="${opt.value}"]`);
389
+ if (el) el.classList.add('is-selected');
390
+ });
391
+ this._updateTags();
392
+ this._updateValueDisplay();
393
+ } else if (this.nativeSelect.value) {
394
+ this.selectedValues.add(this.nativeSelect.value);
395
+ const el = this.optionsEl.querySelector(`[data-value="${this.nativeSelect.value}"]`);
396
+ if (el) {
397
+ el.classList.add('is-selected');
398
+ this.updateValue(el.textContent.trim());
399
+ }
400
+ }
401
+ }
402
+ this._updateClearBtn();
403
+ }
404
+
405
+ _bindOptionClicks() {
406
+ this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(option => {
407
+ option.addEventListener('click', this._optionClickHandler);
408
+ option._selectOptionClickHandler = this._optionClickHandler;
409
+ });
410
+ }
411
+
412
+ _getVisibleOptions() {
413
+ return Array.from(this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item'))
414
+ .filter(el => el.style.display !== 'none' && !el.classList.contains('is-disabled'));
415
+ }
416
+
417
+ _focusOption(items) {
418
+ items.forEach(item => item.classList.remove('is-focused'));
419
+ if (items[this.focusIndex]) {
420
+ items[this.focusIndex].classList.add('is-focused');
421
+ items[this.focusIndex].scrollIntoView({ block: 'nearest' });
422
+ }
423
+ }
424
+
64
425
  updateValue(text) {
65
426
  if (this.valueEl) {
66
427
  this.valueEl.textContent = text || this.valueEl.textContent;
428
+ this.valueEl.classList.remove('ds-select__value--placeholder');
67
429
  }
68
430
  }
69
431
 
70
432
  showOptions() {
71
- const isOpen = this.optionsEl.style.display === 'block';
72
- this.optionsEl.style.display = isOpen ? 'none' : 'block';
73
- if (this.icon) {
74
- this.icon.style.transform = isOpen ? 'rotate(0deg)' : 'rotate(180deg)';
433
+ if (this.disabled || this.isOpen) return;
434
+ this.isOpen = true;
435
+ this.optionsEl.style.display = 'block';
436
+ if (this.icon) this.icon.style.transform = 'rotate(180deg)';
437
+ this.element.classList.add('is-open');
438
+ this.focusIndex = -1;
439
+
440
+ if (this.searchInput) {
441
+ setTimeout(() => this.searchInput.focus(), 50);
75
442
  }
76
- this.element.classList.toggle('is-open', !isOpen);
77
443
  }
78
444
 
79
445
  hideOptions() {
446
+ if (!this.isOpen) return;
447
+ this.isOpen = false;
80
448
  this.optionsEl.style.display = 'none';
81
- if (this.icon) {
82
- this.icon.style.transform = 'rotate(0deg)';
83
- }
449
+ if (this.icon) this.icon.style.transform = 'rotate(0deg)';
84
450
  this.element.classList.remove('is-open');
451
+
452
+ // Clear search
453
+ if (this.searchInput) {
454
+ this.searchInput.value = '';
455
+ this._handleSearch();
456
+ }
457
+
458
+ // Clear focus
459
+ this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(o => o.classList.remove('is-focused'));
460
+ }
461
+
462
+ toggleOptions() {
463
+ if (this.isOpen) {
464
+ this.hideOptions();
465
+ } else {
466
+ this.showOptions();
467
+ }
468
+ }
469
+
470
+ clear() {
471
+ this.selectedValues.clear();
472
+ this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(o => o.classList.remove('is-selected'));
473
+ this._updateTags();
474
+ this._updateValueDisplay();
475
+ this._syncNativeSelect();
476
+ this._updateClearBtn();
477
+ this._fireChange();
478
+ }
479
+
480
+ getSelectedOptions() {
481
+ return Array.from(this.selectedValues).map(value => {
482
+ const opt = this.allOptions.find(o => o.value === value);
483
+ return opt ? { value: opt.value, text: opt.text } : { value, text: '' };
484
+ });
485
+ }
486
+
487
+ getValue() {
488
+ return this.multiple ? Array.from(this.selectedValues) : Array.from(this.selectedValues)[0] || '';
489
+ }
490
+
491
+ setValue(value) {
492
+ if (this.multiple && Array.isArray(value)) {
493
+ this.selectedValues.clear();
494
+ value.forEach(v => this.selectedValues.add(v));
495
+ this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(o => {
496
+ o.classList.toggle('is-selected', this.selectedValues.has(o.getAttribute('data-value')));
497
+ });
498
+ this._updateTags();
499
+ this._updateValueDisplay();
500
+ } else {
501
+ this.selectedValues.clear();
502
+ this.selectedValues.add(value);
503
+ this.optionsEl.querySelectorAll('.ds-select__option, .ds-select__item').forEach(o => {
504
+ const isSelected = o.getAttribute('data-value') === value;
505
+ o.classList.toggle('is-selected', isSelected);
506
+ if (isSelected) this.updateValue(o.textContent.trim());
507
+ });
508
+ }
509
+ this._syncNativeSelect();
510
+ this._updateClearBtn();
511
+ }
512
+
513
+ enable() {
514
+ this.disabled = false;
515
+ this.element.removeAttribute('data-select-disabled');
516
+ }
517
+
518
+ disable() {
519
+ this.disabled = true;
520
+ this.element.setAttribute('data-select-disabled', '');
521
+ this.hideOptions();
85
522
  }
86
523
 
87
524
  destroy() {
@@ -99,22 +536,30 @@ class Select {
99
536
  });
100
537
  }
101
538
 
539
+ if (this._keydownHandler) document.removeEventListener('keydown', this._keydownHandler);
540
+
102
541
  if (this._documentClickListener && this._documentClickListener.unsubscribe) {
103
542
  this._documentClickListener.unsubscribe();
104
543
  } else if (this._documentClickHandler) {
105
544
  document.removeEventListener('click', this._documentClickHandler);
106
545
  }
107
546
 
547
+ // Remove created elements
548
+ if (this.searchInput) this.searchInput.remove();
549
+ if (this.clearBtn) this.clearBtn.remove();
550
+ if (this.tagsWrap) this.tagsWrap.remove();
551
+
108
552
  this._documentClickHandler = null;
109
553
  this._documentClickListener = null;
110
554
  this._triggerClickHandler = null;
111
555
  this._optionClickHandler = null;
556
+ this._keydownHandler = null;
112
557
  this.element.__kupolaInitialized = false;
113
558
  }
114
559
  }
115
560
 
116
- function initSelect(element) {
117
- const select = new Select(element);
561
+ function initSelect(element, options) {
562
+ const select = new Select(element, options);
118
563
  select.init();
119
564
  element._kupolaSelect = select;
120
565
  }
@@ -138,6 +583,16 @@ function cleanupAllSelects() {
138
583
  });
139
584
  }
140
585
 
141
- if (window.kupolaInitializer) {
142
- window.kupolaInitializer.register('select', initSelect, cleanupSelect);
586
+ export { Select, initSelect, initSelects, cleanupSelect, cleanupAllSelects };
587
+
588
+ if (typeof window !== 'undefined') {
589
+ window.Select = Select;
590
+ window.initSelect = initSelect;
591
+ window.initSelects = initSelects;
592
+ window.cleanupSelect = cleanupSelect;
593
+ window.cleanupAllSelects = cleanupAllSelects;
594
+
595
+ if (window.kupolaInitializer) {
596
+ window.kupolaInitializer.register('select', initSelect, cleanupSelect);
597
+ }
143
598
  }
@@ -499,6 +499,15 @@ function cleanupAllSlideCaptchas() {
499
499
  });
500
500
  }
501
501
 
502
- if (typeof module !== 'undefined' && module.exports) {
503
- module.exports = { initSlideCaptchas, cleanupSlideCaptcha, cleanupAllSlideCaptchas };
502
+ export { SlideCaptcha, initSlideCaptchas, cleanupSlideCaptcha, cleanupAllSlideCaptchas };
503
+
504
+ if (typeof window !== 'undefined') {
505
+ window.SlideCaptcha = SlideCaptcha;
506
+ window.initSlideCaptchas = initSlideCaptchas;
507
+ window.cleanupSlideCaptcha = cleanupSlideCaptcha;
508
+ window.cleanupAllSlideCaptchas = cleanupAllSlideCaptchas;
509
+
510
+ if (window.kupolaInitializer) {
511
+ window.kupolaInitializer.register('slide-captcha', initSlideCaptchas, cleanupAllSlideCaptchas);
512
+ }
504
513
  }