@keenthemes/ktui 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ktui.js +690 -166
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +165 -31
- package/examples/image-input/file-upload-example.html +189 -0
- package/examples/select/remote-data_.html +5 -0
- package/examples/select/test-optimizations.html +227 -0
- package/examples/select/test-remote-search.html +151 -0
- package/examples/sticky/README.md +158 -0
- package/examples/sticky/debug-sticky.html +144 -0
- package/examples/sticky/test-runner.html +175 -0
- package/examples/sticky/test-sticky-logic.js +369 -0
- package/examples/sticky/test-sticky-positioning.html +386 -0
- package/examples/toast/example.html +52 -0
- package/lib/cjs/components/component.js +59 -5
- package/lib/cjs/components/component.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-sort.js +4 -0
- package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +79 -12
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/image-input/image-input.js +10 -2
- package/lib/cjs/components/image-input/image-input.js.map +1 -1
- package/lib/cjs/components/select/combobox.js +50 -20
- package/lib/cjs/components/select/combobox.js.map +1 -1
- package/lib/cjs/components/select/config.js +1 -0
- package/lib/cjs/components/select/config.js.map +1 -1
- package/lib/cjs/components/select/dropdown.js +4 -2
- package/lib/cjs/components/select/dropdown.js.map +1 -1
- package/lib/cjs/components/select/index.js.map +1 -1
- package/lib/cjs/components/select/option.js +2 -1
- package/lib/cjs/components/select/option.js.map +1 -1
- package/lib/cjs/components/select/remote.js +50 -50
- package/lib/cjs/components/select/remote.js.map +1 -1
- package/lib/cjs/components/select/search.js +15 -5
- package/lib/cjs/components/select/search.js.map +1 -1
- package/lib/cjs/components/select/select.js +273 -32
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.js +3 -1
- package/lib/cjs/components/select/tags.js.map +1 -1
- package/lib/cjs/components/select/templates.js +6 -0
- package/lib/cjs/components/select/templates.js.map +1 -1
- package/lib/cjs/components/select/utils.js +23 -10
- package/lib/cjs/components/select/utils.js.map +1 -1
- package/lib/cjs/components/stepper/stepper.js +59 -12
- package/lib/cjs/components/stepper/stepper.js.map +1 -1
- package/lib/cjs/components/sticky/sticky.js +52 -14
- package/lib/cjs/components/sticky/sticky.js.map +1 -1
- package/lib/esm/components/component.js +59 -5
- package/lib/esm/components/component.js.map +1 -1
- package/lib/esm/components/datatable/datatable-sort.js +4 -0
- package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
- package/lib/esm/components/datatable/datatable.js +78 -12
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/image-input/image-input.js +10 -2
- package/lib/esm/components/image-input/image-input.js.map +1 -1
- package/lib/esm/components/select/combobox.js +50 -20
- package/lib/esm/components/select/combobox.js.map +1 -1
- package/lib/esm/components/select/config.js +1 -0
- package/lib/esm/components/select/config.js.map +1 -1
- package/lib/esm/components/select/dropdown.js +4 -2
- package/lib/esm/components/select/dropdown.js.map +1 -1
- package/lib/esm/components/select/index.js +1 -1
- package/lib/esm/components/select/index.js.map +1 -1
- package/lib/esm/components/select/option.js +2 -1
- package/lib/esm/components/select/option.js.map +1 -1
- package/lib/esm/components/select/remote.js +50 -50
- package/lib/esm/components/select/remote.js.map +1 -1
- package/lib/esm/components/select/search.js +16 -6
- package/lib/esm/components/select/search.js.map +1 -1
- package/lib/esm/components/select/select.js +273 -32
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.js +3 -1
- package/lib/esm/components/select/tags.js.map +1 -1
- package/lib/esm/components/select/templates.js +6 -0
- package/lib/esm/components/select/templates.js.map +1 -1
- package/lib/esm/components/select/utils.js +23 -10
- package/lib/esm/components/select/utils.js.map +1 -1
- package/lib/esm/components/stepper/stepper.js +59 -12
- package/lib/esm/components/stepper/stepper.js.map +1 -1
- package/lib/esm/components/sticky/sticky.js +52 -14
- package/lib/esm/components/sticky/sticky.js.map +1 -1
- package/package.json +2 -2
- package/src/components/component.ts +19 -4
- package/src/components/datatable/datatable-sort.ts +6 -0
- package/src/components/datatable/datatable.ts +98 -15
- package/src/components/datatable/types.ts +5 -1
- package/src/components/image-input/image-input.ts +11 -2
- package/src/components/image-input/types.ts +1 -0
- package/src/components/input/input-group.css +1 -1
- package/src/components/input/input.css +1 -1
- package/src/components/scrollable/scrollable.css +3 -3
- package/src/components/select/combobox.ts +84 -34
- package/src/components/select/config.ts +2 -0
- package/src/components/select/dropdown.ts +20 -11
- package/src/components/select/index.ts +6 -1
- package/src/components/select/option.ts +7 -6
- package/src/components/select/remote.ts +51 -52
- package/src/components/select/search.ts +59 -44
- package/src/components/select/select.css +26 -17
- package/src/components/select/select.ts +472 -101
- package/src/components/select/tags.ts +9 -3
- package/src/components/select/templates.ts +10 -0
- package/src/components/select/utils.ts +55 -20
- package/src/components/select/variants.css +0 -1
- package/src/components/stepper/stepper.ts +2 -2
- package/src/components/sticky/sticky.ts +47 -16
- package/src/components/sticky/types.ts +3 -0
- package/src/components/table/table.css +1 -1
- package/src/components/textarea/textarea.css +1 -1
- package/src/components/toast/toast.css +84 -47
- package/src/components/toast/types.ts +3 -0
|
@@ -39,6 +39,9 @@ export class KTSelect extends KTComponent {
|
|
|
39
39
|
private _searchInputElement: HTMLInputElement | null;
|
|
40
40
|
private _options: NodeListOf<HTMLElement>;
|
|
41
41
|
|
|
42
|
+
// Cached DOM references for performance
|
|
43
|
+
private _optionsContainer: HTMLElement | null = null;
|
|
44
|
+
|
|
42
45
|
// State
|
|
43
46
|
private _dropdownIsOpen: boolean = false;
|
|
44
47
|
private _state: KTSelectState;
|
|
@@ -48,6 +51,8 @@ export class KTSelect extends KTComponent {
|
|
|
48
51
|
private _tagsModule: KTSelectTags | null = null;
|
|
49
52
|
private _dropdownModule: KTSelectDropdown | null = null;
|
|
50
53
|
private _loadMoreIndicator: HTMLElement | null = null;
|
|
54
|
+
private _selectAllButton: HTMLElement | null = null;
|
|
55
|
+
private _selectAllButtonToggle: HTMLButtonElement | null = null;
|
|
51
56
|
private _focusManager: FocusManager;
|
|
52
57
|
private _eventManager: EventManager;
|
|
53
58
|
private _typeToSearchBuffer: TypeToSearchBuffer = new TypeToSearchBuffer();
|
|
@@ -102,6 +107,11 @@ export class KTSelect extends KTComponent {
|
|
|
102
107
|
if (this._config.debug)
|
|
103
108
|
console.log('Initializing remote data with URL:', this._config.dataUrl);
|
|
104
109
|
|
|
110
|
+
// For remote data, we need to create the HTML structure first
|
|
111
|
+
// so that the component can be properly initialized
|
|
112
|
+
this._createHtmlStructure();
|
|
113
|
+
this._setupElementReferences();
|
|
114
|
+
|
|
105
115
|
// Show loading state
|
|
106
116
|
this._renderLoadingState();
|
|
107
117
|
|
|
@@ -123,7 +133,12 @@ export class KTSelect extends KTComponent {
|
|
|
123
133
|
|
|
124
134
|
if (this._config.debug)
|
|
125
135
|
console.log('Generating options HTML from remote data');
|
|
126
|
-
|
|
136
|
+
|
|
137
|
+
// Update the dropdown to show the new options
|
|
138
|
+
this._updateDropdownWithNewOptions();
|
|
139
|
+
|
|
140
|
+
// Complete the component setup with the fetched data
|
|
141
|
+
this._completeRemoteSetup();
|
|
127
142
|
|
|
128
143
|
// Add pagination "Load More" button if needed
|
|
129
144
|
if (this._config.pagination && this._remoteModule.hasMorePages()) {
|
|
@@ -154,6 +169,145 @@ export class KTSelect extends KTComponent {
|
|
|
154
169
|
options.forEach((option) => option.remove());
|
|
155
170
|
}
|
|
156
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Unified method to render options in dropdown - eliminates code duplication
|
|
174
|
+
*/
|
|
175
|
+
private _renderOptionsInDropdown(
|
|
176
|
+
optionsData: KTSelectOptionData[] | HTMLOptionElement[],
|
|
177
|
+
clearContainer: boolean = true,
|
|
178
|
+
): void {
|
|
179
|
+
if (!this._dropdownContentElement) return;
|
|
180
|
+
|
|
181
|
+
// Use cached options container for better performance
|
|
182
|
+
const optionsContainer =
|
|
183
|
+
this._optionsContainer ||
|
|
184
|
+
this._dropdownContentElement.querySelector('[data-kt-select-options]');
|
|
185
|
+
if (!optionsContainer) return;
|
|
186
|
+
|
|
187
|
+
// Clear container if requested
|
|
188
|
+
if (clearContainer) {
|
|
189
|
+
optionsContainer.innerHTML = '';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Use DocumentFragment for efficient DOM manipulation
|
|
193
|
+
const fragment = document.createDocumentFragment();
|
|
194
|
+
|
|
195
|
+
// Process options data
|
|
196
|
+
optionsData.forEach((optionData) => {
|
|
197
|
+
let optionElement: HTMLOptionElement;
|
|
198
|
+
|
|
199
|
+
// Handle different input types
|
|
200
|
+
if (optionData instanceof HTMLOptionElement) {
|
|
201
|
+
// Skip empty placeholder options
|
|
202
|
+
if (optionData.value === '' && optionData.textContent.trim() === '') {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
optionElement = optionData;
|
|
206
|
+
} else {
|
|
207
|
+
// Handle KTSelectOptionData objects - cast to ensure type safety
|
|
208
|
+
const dataItem = optionData as KTSelectOptionData;
|
|
209
|
+
optionElement = document.createElement('option');
|
|
210
|
+
optionElement.value = dataItem.id || '';
|
|
211
|
+
optionElement.textContent = dataItem.title || '';
|
|
212
|
+
|
|
213
|
+
if (dataItem.selected) {
|
|
214
|
+
optionElement.setAttribute('selected', 'selected');
|
|
215
|
+
}
|
|
216
|
+
if (dataItem.disabled) {
|
|
217
|
+
optionElement.setAttribute('disabled', 'disabled');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Create KTSelectOption instance for proper rendering
|
|
222
|
+
const selectOption = new KTSelectOption(optionElement, this._config);
|
|
223
|
+
const renderedOption = selectOption.render();
|
|
224
|
+
|
|
225
|
+
// Add to fragment for batch DOM operation
|
|
226
|
+
fragment.appendChild(renderedOption);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Batch append all options at once
|
|
230
|
+
optionsContainer.appendChild(fragment);
|
|
231
|
+
|
|
232
|
+
// Update options NodeList
|
|
233
|
+
this._options = this._wrapperElement.querySelectorAll(
|
|
234
|
+
'[data-kt-select-option]',
|
|
235
|
+
) as NodeListOf<HTMLElement>;
|
|
236
|
+
|
|
237
|
+
if (this._config.debug) {
|
|
238
|
+
console.log(`Rendered ${optionsData.length} options in dropdown`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Update dropdown with new options from the original select element
|
|
244
|
+
*/
|
|
245
|
+
private _updateDropdownWithNewOptions() {
|
|
246
|
+
// Get all options from the original select element
|
|
247
|
+
const options = Array.from(this._element.querySelectorAll('option'));
|
|
248
|
+
|
|
249
|
+
// Use unified renderer
|
|
250
|
+
this._renderOptionsInDropdown(options, true);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Complete the setup for remote data after HTML structure is created
|
|
255
|
+
*/
|
|
256
|
+
private _completeRemoteSetup() {
|
|
257
|
+
// Initialize options
|
|
258
|
+
this._preSelectOptions(this._element);
|
|
259
|
+
|
|
260
|
+
// Apply disabled state if needed
|
|
261
|
+
this._applyInitialDisabledState();
|
|
262
|
+
|
|
263
|
+
// Initialize search if enabled
|
|
264
|
+
if (this._config.enableSearch) {
|
|
265
|
+
this._initializeSearchModule();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Initialize combobox if enabled
|
|
269
|
+
if (this._config.combobox) {
|
|
270
|
+
this._comboboxModule = new KTSelectCombobox(this);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Initialize tags if enabled
|
|
274
|
+
if (this._config.tags) {
|
|
275
|
+
this._tagsModule = new KTSelectTags(this);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Initialize focus manager after dropdown element is created
|
|
279
|
+
this._focusManager = new FocusManager(
|
|
280
|
+
this._dropdownContentElement,
|
|
281
|
+
'[data-kt-select-option]',
|
|
282
|
+
this._config,
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Initialize dropdown module after all elements are created
|
|
286
|
+
this._dropdownModule = new KTSelectDropdown(
|
|
287
|
+
this._wrapperElement,
|
|
288
|
+
this._displayElement,
|
|
289
|
+
this._dropdownContentElement,
|
|
290
|
+
this._config,
|
|
291
|
+
this, // Pass the KTSelect instance to KTSelectDropdown
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Update display and set ARIA attributes
|
|
295
|
+
this._updateDisplayAndAriaAttributes();
|
|
296
|
+
this.updateSelectedOptionDisplay();
|
|
297
|
+
this._setAriaAttributes();
|
|
298
|
+
|
|
299
|
+
// Update select all button state
|
|
300
|
+
this.updateSelectAllButtonState();
|
|
301
|
+
|
|
302
|
+
// Focus the first selected option or first option if nothing selected
|
|
303
|
+
this._focusSelectedOption();
|
|
304
|
+
|
|
305
|
+
// Attach event listeners after all modules are initialized
|
|
306
|
+
this._attachEventListeners();
|
|
307
|
+
|
|
308
|
+
this._observeNativeSelect();
|
|
309
|
+
}
|
|
310
|
+
|
|
157
311
|
/**
|
|
158
312
|
* Helper to show a dropdown message (error, loading, noResults)
|
|
159
313
|
*/
|
|
@@ -172,19 +326,22 @@ export class KTSelect extends KTComponent {
|
|
|
172
326
|
|
|
173
327
|
switch (type) {
|
|
174
328
|
case 'error':
|
|
175
|
-
optionsContainer.appendChild(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
329
|
+
optionsContainer.appendChild(
|
|
330
|
+
defaultTemplates.error({
|
|
331
|
+
...this._config,
|
|
332
|
+
errorMessage: message,
|
|
333
|
+
}),
|
|
334
|
+
);
|
|
179
335
|
break;
|
|
180
336
|
case 'loading':
|
|
181
|
-
optionsContainer.appendChild(
|
|
182
|
-
this._config,
|
|
183
|
-
|
|
184
|
-
));
|
|
337
|
+
optionsContainer.appendChild(
|
|
338
|
+
defaultTemplates.loading(this._config, message || 'Loading...'),
|
|
339
|
+
);
|
|
185
340
|
break;
|
|
186
341
|
case 'empty':
|
|
187
|
-
optionsContainer.appendChild(
|
|
342
|
+
optionsContainer.appendChild(
|
|
343
|
+
defaultTemplates.searchEmpty(this._config),
|
|
344
|
+
);
|
|
188
345
|
break;
|
|
189
346
|
}
|
|
190
347
|
}
|
|
@@ -405,6 +562,12 @@ export class KTSelect extends KTComponent {
|
|
|
405
562
|
this.updateSelectedOptionDisplay();
|
|
406
563
|
this._setAriaAttributes();
|
|
407
564
|
|
|
565
|
+
// Update select all button state
|
|
566
|
+
this.updateSelectAllButtonState();
|
|
567
|
+
|
|
568
|
+
// Focus the first selected option or first option if nothing selected
|
|
569
|
+
this._focusSelectedOption();
|
|
570
|
+
|
|
408
571
|
// Attach event listeners after all modules are initialized
|
|
409
572
|
this._attachEventListeners();
|
|
410
573
|
|
|
@@ -428,18 +591,22 @@ export class KTSelect extends KTComponent {
|
|
|
428
591
|
// Move classes from original select to wrapper and display elements
|
|
429
592
|
if (this._element.classList.length > 0) {
|
|
430
593
|
const originalClasses = Array.from(this._element.classList);
|
|
431
|
-
const displaySpecificClasses = [
|
|
594
|
+
const displaySpecificClasses = [
|
|
595
|
+
'kt-select',
|
|
596
|
+
'kt-select-sm',
|
|
597
|
+
'kt-select-lg',
|
|
598
|
+
];
|
|
432
599
|
|
|
433
600
|
const classesForWrapper = originalClasses.filter(
|
|
434
|
-
(className) => !displaySpecificClasses.includes(className)
|
|
601
|
+
(className) => !displaySpecificClasses.includes(className),
|
|
435
602
|
);
|
|
436
603
|
if (classesForWrapper.length > 0) {
|
|
437
604
|
wrapperElement.classList.add(...classesForWrapper);
|
|
438
605
|
}
|
|
439
606
|
|
|
440
607
|
// Move display-specific classes to display element
|
|
441
|
-
const classesForDisplay = originalClasses.filter(
|
|
442
|
-
|
|
608
|
+
const classesForDisplay = originalClasses.filter((className) =>
|
|
609
|
+
displaySpecificClasses.includes(className),
|
|
443
610
|
);
|
|
444
611
|
if (classesForDisplay.length > 0) {
|
|
445
612
|
displayElement.classList.add(...classesForDisplay);
|
|
@@ -460,10 +627,16 @@ export class KTSelect extends KTComponent {
|
|
|
460
627
|
dropdownElement.appendChild(searchElement);
|
|
461
628
|
}
|
|
462
629
|
|
|
630
|
+
// Add select all button if needed
|
|
631
|
+
if (this._config.multiple && this._config.enableSelectAll) {
|
|
632
|
+
const selectAllElement = defaultTemplates.selectAll(this._config);
|
|
633
|
+
dropdownElement.appendChild(selectAllElement);
|
|
634
|
+
}
|
|
635
|
+
|
|
463
636
|
// Create options container using template
|
|
464
637
|
const optionsContainer = defaultTemplates.options(this._config);
|
|
465
638
|
|
|
466
|
-
// Add each option directly to the container
|
|
639
|
+
// Add each option directly to the container (only if options exist)
|
|
467
640
|
options.forEach((optionElement) => {
|
|
468
641
|
// Skip empty placeholder options (only if BOTH value AND text are empty)
|
|
469
642
|
// This allows options with empty value but visible text to display in dropdown
|
|
@@ -499,6 +672,14 @@ export class KTSelect extends KTComponent {
|
|
|
499
672
|
private _setupElementReferences() {
|
|
500
673
|
this._wrapperElement = this._element.nextElementSibling as HTMLElement;
|
|
501
674
|
|
|
675
|
+
// Safety check - ensure wrapper element exists
|
|
676
|
+
if (!this._wrapperElement) {
|
|
677
|
+
console.error(
|
|
678
|
+
'KTSelect: Wrapper element not found. HTML structure may not be created properly.',
|
|
679
|
+
);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
502
683
|
// Get display element
|
|
503
684
|
this._displayElement = this._wrapperElement.querySelector(
|
|
504
685
|
`[data-kt-select-display]`,
|
|
@@ -510,8 +691,11 @@ export class KTSelect extends KTComponent {
|
|
|
510
691
|
) as HTMLElement;
|
|
511
692
|
|
|
512
693
|
if (!this._dropdownContentElement) {
|
|
513
|
-
console.
|
|
514
|
-
|
|
694
|
+
console.error(
|
|
695
|
+
'KTSelect: Dropdown content element not found',
|
|
696
|
+
this._wrapperElement,
|
|
697
|
+
);
|
|
698
|
+
return;
|
|
515
699
|
}
|
|
516
700
|
|
|
517
701
|
// Get search input element - this is used for the search functionality
|
|
@@ -524,6 +708,15 @@ export class KTSelect extends KTComponent {
|
|
|
524
708
|
this._searchInputElement = this._displayElement as HTMLInputElement;
|
|
525
709
|
}
|
|
526
710
|
|
|
711
|
+
this._selectAllButton = this._wrapperElement.querySelector(
|
|
712
|
+
'[data-kt-select-select-all]',
|
|
713
|
+
) as HTMLElement;
|
|
714
|
+
|
|
715
|
+
// Cache the options container for performance
|
|
716
|
+
this._optionsContainer = this._dropdownContentElement.querySelector(
|
|
717
|
+
'[data-kt-select-options]',
|
|
718
|
+
) as HTMLElement;
|
|
719
|
+
|
|
527
720
|
this._options = this._wrapperElement.querySelectorAll(
|
|
528
721
|
`[data-kt-select-option]`,
|
|
529
722
|
) as NodeListOf<HTMLElement>;
|
|
@@ -543,10 +736,25 @@ export class KTSelect extends KTComponent {
|
|
|
543
736
|
this._handleDropdownOptionClick.bind(this),
|
|
544
737
|
);
|
|
545
738
|
|
|
739
|
+
if (this._selectAllButton) {
|
|
740
|
+
this._selectAllButtonToggle =
|
|
741
|
+
this._selectAllButton.querySelector('button');
|
|
742
|
+
if (this._selectAllButtonToggle) {
|
|
743
|
+
this._eventManager.addListener(
|
|
744
|
+
this._selectAllButtonToggle,
|
|
745
|
+
'click',
|
|
746
|
+
this._handleSelectAllClick.bind(this),
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
546
751
|
// Attach centralized keyboard handler to the wrapper element.
|
|
547
752
|
// Events from focusable children like _displayElement or _searchInputElement (if present) will bubble up.
|
|
548
753
|
if (this._wrapperElement) {
|
|
549
|
-
this._wrapperElement.addEventListener(
|
|
754
|
+
this._wrapperElement.addEventListener(
|
|
755
|
+
'keydown',
|
|
756
|
+
this._handleKeyboardEvent.bind(this),
|
|
757
|
+
);
|
|
550
758
|
}
|
|
551
759
|
}
|
|
552
760
|
|
|
@@ -637,9 +845,7 @@ export class KTSelect extends KTComponent {
|
|
|
637
845
|
|
|
638
846
|
// Log the extracted values for debugging
|
|
639
847
|
if (this._config.debug)
|
|
640
|
-
console.log(
|
|
641
|
-
`Option: value=${value}, label=${label}`,
|
|
642
|
-
);
|
|
848
|
+
console.log(`Option: value=${value}, label=${label}`);
|
|
643
849
|
|
|
644
850
|
// Set option attributes
|
|
645
851
|
optionElement.value = value;
|
|
@@ -714,7 +920,8 @@ export class KTSelect extends KTComponent {
|
|
|
714
920
|
*/
|
|
715
921
|
public openDropdown() {
|
|
716
922
|
if (this._config.disabled) {
|
|
717
|
-
if (this._config.debug)
|
|
923
|
+
if (this._config.debug)
|
|
924
|
+
console.log('openDropdown: select is disabled, not opening');
|
|
718
925
|
return;
|
|
719
926
|
}
|
|
720
927
|
if (this._config.debug)
|
|
@@ -752,6 +959,9 @@ export class KTSelect extends KTComponent {
|
|
|
752
959
|
// Update ARIA states
|
|
753
960
|
this._setAriaAttributes();
|
|
754
961
|
|
|
962
|
+
// Update select all button state
|
|
963
|
+
this.updateSelectAllButtonState();
|
|
964
|
+
|
|
755
965
|
// Focus the first selected option or first option if nothing selected
|
|
756
966
|
this._focusSelectedOption();
|
|
757
967
|
}
|
|
@@ -845,7 +1055,8 @@ export class KTSelect extends KTComponent {
|
|
|
845
1055
|
private _selectOption(value: string) {
|
|
846
1056
|
// Prevent selection if the option is disabled (in dropdown or original select)
|
|
847
1057
|
if (this._isOptionDisabled(value)) {
|
|
848
|
-
if (this._config.debug)
|
|
1058
|
+
if (this._config.debug)
|
|
1059
|
+
console.log('_selectOption: Option is disabled, ignoring selection');
|
|
849
1060
|
return;
|
|
850
1061
|
}
|
|
851
1062
|
|
|
@@ -913,7 +1124,9 @@ export class KTSelect extends KTComponent {
|
|
|
913
1124
|
// Guard against valueDisplayEl being null due to template modifications
|
|
914
1125
|
if (!valueDisplayEl) {
|
|
915
1126
|
if (this._config.debug) {
|
|
916
|
-
console.warn(
|
|
1127
|
+
console.warn(
|
|
1128
|
+
'KTSelect: Value display element is null. Cannot update display or placeholder. Check template for [data-kt-select-value].',
|
|
1129
|
+
);
|
|
917
1130
|
}
|
|
918
1131
|
return; // Nothing to display on if the element is missing
|
|
919
1132
|
}
|
|
@@ -936,7 +1149,9 @@ export class KTSelect extends KTComponent {
|
|
|
936
1149
|
// Tags are not enabled AND options are selected: render normal text display.
|
|
937
1150
|
let content = '';
|
|
938
1151
|
if (this._config.displayTemplate) {
|
|
939
|
-
content = this.renderDisplayTemplateForSelected(
|
|
1152
|
+
content = this.renderDisplayTemplateForSelected(
|
|
1153
|
+
this.getSelectedOptions(),
|
|
1154
|
+
);
|
|
940
1155
|
} else {
|
|
941
1156
|
content = this.getSelectedOptionsText();
|
|
942
1157
|
}
|
|
@@ -950,9 +1165,9 @@ export class KTSelect extends KTComponent {
|
|
|
950
1165
|
* Check if an option was originally disabled in the HTML
|
|
951
1166
|
*/
|
|
952
1167
|
private _isOptionOriginallyDisabled(value: string): boolean {
|
|
953
|
-
const originalOption = Array.from(
|
|
954
|
-
(
|
|
955
|
-
) as HTMLOptionElement;
|
|
1168
|
+
const originalOption = Array.from(
|
|
1169
|
+
this._element.querySelectorAll('option'),
|
|
1170
|
+
).find((opt) => opt.value === value) as HTMLOptionElement;
|
|
956
1171
|
return originalOption ? originalOption.disabled : false;
|
|
957
1172
|
}
|
|
958
1173
|
|
|
@@ -979,7 +1194,8 @@ export class KTSelect extends KTComponent {
|
|
|
979
1194
|
if (!optionValue) return;
|
|
980
1195
|
|
|
981
1196
|
const isSelected = selectedValues.includes(optionValue);
|
|
982
|
-
const isOriginallyDisabled =
|
|
1197
|
+
const isOriginallyDisabled =
|
|
1198
|
+
this._isOptionOriginallyDisabled(optionValue);
|
|
983
1199
|
|
|
984
1200
|
if (isSelected) {
|
|
985
1201
|
option.classList.add('selected');
|
|
@@ -1013,6 +1229,9 @@ export class KTSelect extends KTComponent {
|
|
|
1013
1229
|
this.updateSelectedOptionDisplay();
|
|
1014
1230
|
this._updateSelectedOptionClass();
|
|
1015
1231
|
|
|
1232
|
+
// Update select all button state
|
|
1233
|
+
this.updateSelectAllButtonState();
|
|
1234
|
+
|
|
1016
1235
|
// Dispatch change event
|
|
1017
1236
|
this._dispatchEvent('change');
|
|
1018
1237
|
this._fireEvent('change');
|
|
@@ -1111,7 +1330,10 @@ export class KTSelect extends KTComponent {
|
|
|
1111
1330
|
|
|
1112
1331
|
// If in single-select mode and the clicked option is already selected, just close the dropdown.
|
|
1113
1332
|
if (!this._config.multiple && this._state.isSelected(optionValue)) {
|
|
1114
|
-
if (this._config.debug)
|
|
1333
|
+
if (this._config.debug)
|
|
1334
|
+
console.log(
|
|
1335
|
+
'Single select mode: clicked already selected option. Closing dropdown.',
|
|
1336
|
+
);
|
|
1115
1337
|
this.closeDropdown();
|
|
1116
1338
|
return;
|
|
1117
1339
|
}
|
|
@@ -1268,24 +1490,31 @@ export class KTSelect extends KTComponent {
|
|
|
1268
1490
|
public toggleSelection(value: string): void {
|
|
1269
1491
|
// Prevent selection if the option is disabled (in dropdown or original select)
|
|
1270
1492
|
if (this._isOptionDisabled(value)) {
|
|
1271
|
-
if (this._config.debug)
|
|
1493
|
+
if (this._config.debug)
|
|
1494
|
+
console.log('toggleSelection: Option is disabled, ignoring selection');
|
|
1272
1495
|
return;
|
|
1273
1496
|
}
|
|
1274
1497
|
|
|
1275
1498
|
// Get current selection state
|
|
1276
1499
|
const isSelected = this._state.isSelected(value);
|
|
1277
1500
|
if (this._config.debug)
|
|
1278
|
-
console.log(
|
|
1501
|
+
console.log(
|
|
1502
|
+
`toggleSelection called for value: ${value}, isSelected: ${isSelected}, multiple: ${this._config.multiple}`,
|
|
1503
|
+
);
|
|
1279
1504
|
|
|
1280
1505
|
// If already selected in single select mode, do nothing (can't deselect in single select)
|
|
1281
1506
|
if (isSelected && !this._config.multiple) {
|
|
1282
1507
|
if (this._config.debug)
|
|
1283
|
-
console.log(
|
|
1508
|
+
console.log(
|
|
1509
|
+
'Early return from toggleSelection - already selected in single select mode',
|
|
1510
|
+
);
|
|
1284
1511
|
return;
|
|
1285
1512
|
}
|
|
1286
1513
|
|
|
1287
1514
|
if (this._config.debug)
|
|
1288
|
-
console.log(
|
|
1515
|
+
console.log(
|
|
1516
|
+
`Toggling selection for option: ${value}, currently selected: ${isSelected}`,
|
|
1517
|
+
);
|
|
1289
1518
|
|
|
1290
1519
|
// Ensure any search input is cleared when selection changes
|
|
1291
1520
|
if (this._searchModule) {
|
|
@@ -1322,12 +1551,17 @@ export class KTSelect extends KTComponent {
|
|
|
1322
1551
|
// For multiple select mode, keep the dropdown open to allow multiple selections
|
|
1323
1552
|
if (!this._config.multiple) {
|
|
1324
1553
|
if (this._config.debug)
|
|
1325
|
-
console.log(
|
|
1554
|
+
console.log(
|
|
1555
|
+
'About to call closeDropdown() for single select mode - always close after selection',
|
|
1556
|
+
);
|
|
1326
1557
|
this.closeDropdown();
|
|
1327
1558
|
} else {
|
|
1328
1559
|
if (this._config.debug)
|
|
1329
|
-
console.log(
|
|
1560
|
+
console.log(
|
|
1561
|
+
'Multiple select mode - keeping dropdown open for additional selections',
|
|
1562
|
+
);
|
|
1330
1563
|
// Don't close dropdown in multiple select mode to allow multiple selections
|
|
1564
|
+
this.updateSelectAllButtonState();
|
|
1331
1565
|
}
|
|
1332
1566
|
|
|
1333
1567
|
// Dispatch custom change event with additional data
|
|
@@ -1431,6 +1665,8 @@ export class KTSelect extends KTComponent {
|
|
|
1431
1665
|
|
|
1432
1666
|
// Check if the query is long enough
|
|
1433
1667
|
if (query.length < (this._config.searchMinLength || 0)) {
|
|
1668
|
+
// Restore original options if query is too short
|
|
1669
|
+
this._restoreOriginalOptions();
|
|
1434
1670
|
return;
|
|
1435
1671
|
}
|
|
1436
1672
|
|
|
@@ -1458,6 +1694,7 @@ export class KTSelect extends KTComponent {
|
|
|
1458
1694
|
if (this._searchModule) {
|
|
1459
1695
|
this._searchModule.refreshAfterSearch();
|
|
1460
1696
|
}
|
|
1697
|
+
this.updateSelectAllButtonState();
|
|
1461
1698
|
})
|
|
1462
1699
|
.catch((error) => {
|
|
1463
1700
|
console.error('Error updating search results:', error);
|
|
@@ -1470,7 +1707,7 @@ export class KTSelect extends KTComponent {
|
|
|
1470
1707
|
console.error('Error fetching search results:', error);
|
|
1471
1708
|
this._renderSearchErrorState(
|
|
1472
1709
|
this._remoteModule.getErrorMessage() ||
|
|
1473
|
-
|
|
1710
|
+
'Failed to load search results',
|
|
1474
1711
|
);
|
|
1475
1712
|
});
|
|
1476
1713
|
}, this._config.searchDebounce || 300);
|
|
@@ -1503,6 +1740,42 @@ export class KTSelect extends KTComponent {
|
|
|
1503
1740
|
*/
|
|
1504
1741
|
private _renderSearchErrorState(message: string) {
|
|
1505
1742
|
this._showDropdownMessage('error', message);
|
|
1743
|
+
|
|
1744
|
+
// Restore original options after error with a delay
|
|
1745
|
+
setTimeout(() => {
|
|
1746
|
+
this._restoreOriginalOptions();
|
|
1747
|
+
}, 2000);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
/**
|
|
1751
|
+
* Restore original options when search is cleared
|
|
1752
|
+
*/
|
|
1753
|
+
private _restoreOriginalOptions() {
|
|
1754
|
+
if (!this._dropdownContentElement || !this._originalOptionsHtml) return;
|
|
1755
|
+
|
|
1756
|
+
// Use cached options container for better performance
|
|
1757
|
+
const optionsContainer =
|
|
1758
|
+
this._optionsContainer ||
|
|
1759
|
+
this._dropdownContentElement.querySelector('[data-kt-select-options]');
|
|
1760
|
+
if (!optionsContainer) return;
|
|
1761
|
+
|
|
1762
|
+
// Restore original options
|
|
1763
|
+
optionsContainer.innerHTML = this._originalOptionsHtml;
|
|
1764
|
+
|
|
1765
|
+
// Update options NodeList
|
|
1766
|
+
this._options = this._wrapperElement.querySelectorAll(
|
|
1767
|
+
'[data-kt-select-option]',
|
|
1768
|
+
) as NodeListOf<HTMLElement>;
|
|
1769
|
+
|
|
1770
|
+
// Refresh search module
|
|
1771
|
+
if (this._searchModule) {
|
|
1772
|
+
this._searchModule.refreshAfterSearch();
|
|
1773
|
+
}
|
|
1774
|
+
this.updateSelectAllButtonState();
|
|
1775
|
+
|
|
1776
|
+
if (this._config.debug) {
|
|
1777
|
+
console.log('Restored original options after search clear');
|
|
1778
|
+
}
|
|
1506
1779
|
}
|
|
1507
1780
|
|
|
1508
1781
|
/**
|
|
@@ -1512,40 +1785,27 @@ export class KTSelect extends KTComponent {
|
|
|
1512
1785
|
private _updateSearchResults(items: KTSelectOptionData[]) {
|
|
1513
1786
|
if (!this._dropdownContentElement) return;
|
|
1514
1787
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1788
|
+
// Use cached options container for better performance
|
|
1789
|
+
const optionsContainer =
|
|
1790
|
+
this._optionsContainer ||
|
|
1791
|
+
this._dropdownContentElement.querySelector('[data-kt-select-options]');
|
|
1518
1792
|
if (!optionsContainer) return;
|
|
1519
1793
|
|
|
1520
|
-
//
|
|
1521
|
-
optionsContainer.innerHTML = '';
|
|
1522
|
-
|
|
1794
|
+
// Handle empty results
|
|
1523
1795
|
if (items.length === 0) {
|
|
1524
|
-
|
|
1796
|
+
optionsContainer.innerHTML = '';
|
|
1525
1797
|
const noResultsElement = defaultTemplates.searchEmpty(this._config);
|
|
1526
1798
|
optionsContainer.appendChild(noResultsElement);
|
|
1527
1799
|
return;
|
|
1528
1800
|
}
|
|
1529
1801
|
|
|
1530
|
-
//
|
|
1531
|
-
|
|
1532
|
-
// Create option for the original select
|
|
1533
|
-
const selectOption = document.createElement('option');
|
|
1534
|
-
selectOption.value = item.id;
|
|
1535
|
-
|
|
1536
|
-
// Add to dropdown container
|
|
1537
|
-
optionsContainer.appendChild(selectOption);
|
|
1538
|
-
});
|
|
1802
|
+
// Use unified renderer for search results
|
|
1803
|
+
this._renderOptionsInDropdown(items, true);
|
|
1539
1804
|
|
|
1540
1805
|
// Add pagination "Load More" button if needed
|
|
1541
1806
|
if (this._config.pagination && this._remoteModule.hasMorePages()) {
|
|
1542
1807
|
this._addLoadMoreButton();
|
|
1543
1808
|
}
|
|
1544
|
-
|
|
1545
|
-
// Update options NodeList
|
|
1546
|
-
this._options = this._wrapperElement.querySelectorAll(
|
|
1547
|
-
`[data-kt-select-option]`,
|
|
1548
|
-
) as NodeListOf<HTMLElement>;
|
|
1549
1809
|
}
|
|
1550
1810
|
|
|
1551
1811
|
/**
|
|
@@ -1558,10 +1818,14 @@ export class KTSelect extends KTComponent {
|
|
|
1558
1818
|
public getSelectedOptionsText(): string {
|
|
1559
1819
|
const selectedValues = this.getSelectedOptions();
|
|
1560
1820
|
const displaySeparator = this._config.displaySeparator || ', ';
|
|
1561
|
-
const texts = selectedValues
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1821
|
+
const texts = selectedValues
|
|
1822
|
+
.map((value) => {
|
|
1823
|
+
const option = Array.from(this._options).find(
|
|
1824
|
+
(opt) => opt.getAttribute('data-value') === value,
|
|
1825
|
+
);
|
|
1826
|
+
return option?.getAttribute('data-text') || '';
|
|
1827
|
+
})
|
|
1828
|
+
.filter(Boolean);
|
|
1565
1829
|
return texts.join(displaySeparator);
|
|
1566
1830
|
}
|
|
1567
1831
|
|
|
@@ -1570,12 +1834,15 @@ export class KTSelect extends KTComponent {
|
|
|
1570
1834
|
*/
|
|
1571
1835
|
private _isOptionDisabled(value: string): boolean {
|
|
1572
1836
|
const dropdownOption = Array.from(this._options).find(
|
|
1573
|
-
(opt) => opt.getAttribute('data-value') === value
|
|
1837
|
+
(opt) => opt.getAttribute('data-value') === value,
|
|
1574
1838
|
);
|
|
1575
|
-
const isDropdownDisabled =
|
|
1576
|
-
|
|
1577
|
-
(
|
|
1578
|
-
|
|
1839
|
+
const isDropdownDisabled =
|
|
1840
|
+
dropdownOption &&
|
|
1841
|
+
(dropdownOption.classList.contains('disabled') ||
|
|
1842
|
+
dropdownOption.getAttribute('aria-disabled') === 'true');
|
|
1843
|
+
const selectOption = Array.from(
|
|
1844
|
+
this._element.querySelectorAll('option'),
|
|
1845
|
+
).find((opt) => opt.value === value) as HTMLOptionElement;
|
|
1579
1846
|
const isNativeDisabled = selectOption && selectOption.disabled;
|
|
1580
1847
|
return Boolean(isDropdownDisabled || isNativeDisabled);
|
|
1581
1848
|
}
|
|
@@ -1599,9 +1866,16 @@ export class KTSelect extends KTComponent {
|
|
|
1599
1866
|
if (event.target === this._searchInputElement) {
|
|
1600
1867
|
// Allow navigation keys like ArrowDown, ArrowUp, Escape, Enter (for search/selection) to be handled by the logic below.
|
|
1601
1868
|
// For other keys (characters, space, backspace, delete), let the input field process them.
|
|
1602
|
-
if (
|
|
1603
|
-
|
|
1604
|
-
|
|
1869
|
+
if (
|
|
1870
|
+
event.key !== 'ArrowDown' &&
|
|
1871
|
+
event.key !== 'ArrowUp' &&
|
|
1872
|
+
event.key !== 'Escape' &&
|
|
1873
|
+
event.key !== 'Enter' &&
|
|
1874
|
+
event.key !== 'Tab' &&
|
|
1875
|
+
event.key !== 'Home' &&
|
|
1876
|
+
event.key !== 'End' &&
|
|
1877
|
+
event.key !== ' '
|
|
1878
|
+
) {
|
|
1605
1879
|
// If it's a character key and we are NOT type-to-searching (because search has focus)
|
|
1606
1880
|
// then let the input field handle it for its own value.
|
|
1607
1881
|
// The search module's 'input' event will handle filtering based on the input's value.
|
|
@@ -1616,11 +1890,16 @@ export class KTSelect extends KTComponent {
|
|
|
1616
1890
|
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
|
1617
1891
|
|
|
1618
1892
|
// Type-to-search: only for single char keys, when search input does not have focus
|
|
1619
|
-
if (
|
|
1893
|
+
if (
|
|
1894
|
+
event.key.length === 1 &&
|
|
1895
|
+
!event.repeat &&
|
|
1896
|
+
!event.key.match(/\s/) &&
|
|
1897
|
+
document.activeElement !== this._searchInputElement
|
|
1898
|
+
) {
|
|
1620
1899
|
buffer.push(event.key);
|
|
1621
1900
|
const str = buffer.getBuffer();
|
|
1622
1901
|
if (isOpen) {
|
|
1623
|
-
|
|
1902
|
+
focusManager.focusByString(str);
|
|
1624
1903
|
} else {
|
|
1625
1904
|
// If closed, type-to-search could potentially open and select.
|
|
1626
1905
|
// For now, let's assume it only works when open or opens it first.
|
|
@@ -1661,8 +1940,15 @@ export class KTSelect extends KTComponent {
|
|
|
1661
1940
|
if (focusedOptionEl) {
|
|
1662
1941
|
const val = focusedOptionEl.dataset.value;
|
|
1663
1942
|
// If single select, and the item is already selected, just close.
|
|
1664
|
-
if (
|
|
1665
|
-
|
|
1943
|
+
if (
|
|
1944
|
+
val !== undefined &&
|
|
1945
|
+
!this._config.multiple &&
|
|
1946
|
+
this._state.isSelected(val)
|
|
1947
|
+
) {
|
|
1948
|
+
if (this._config.debug)
|
|
1949
|
+
console.log(
|
|
1950
|
+
'Enter on already selected item in single-select mode. Closing.',
|
|
1951
|
+
);
|
|
1666
1952
|
this.closeDropdown();
|
|
1667
1953
|
event.preventDefault();
|
|
1668
1954
|
break;
|
|
@@ -1700,30 +1986,39 @@ export class KTSelect extends KTComponent {
|
|
|
1700
1986
|
}
|
|
1701
1987
|
|
|
1702
1988
|
public renderDisplayTemplateForSelected(selectedValues: string[]): string {
|
|
1703
|
-
const optionsConfig = this._config.optionsConfig as any || {};
|
|
1989
|
+
const optionsConfig = (this._config.optionsConfig as any) || {};
|
|
1704
1990
|
const displaySeparator = this._config.displaySeparator || ', ';
|
|
1705
|
-
const contentArray = Array.from(
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1991
|
+
const contentArray = Array.from(
|
|
1992
|
+
new Set(
|
|
1993
|
+
selectedValues
|
|
1994
|
+
.map((value) => {
|
|
1995
|
+
const option = Array.from(this._options).find(
|
|
1996
|
+
(opt) => opt.getAttribute('data-value') === value,
|
|
1997
|
+
);
|
|
1998
|
+
if (!option) return '';
|
|
1999
|
+
|
|
2000
|
+
let displayTemplate = this._config.displayTemplate;
|
|
2001
|
+
const text = option.getAttribute('data-text') || '';
|
|
2002
|
+
|
|
2003
|
+
// Replace all {{varname}} in option.innerHTML with values from _config
|
|
2004
|
+
Object.entries(optionsConfig[value] || {}).forEach(([key, val]) => {
|
|
2005
|
+
if (['string', 'number', 'boolean'].includes(typeof val)) {
|
|
2006
|
+
displayTemplate = displayTemplate.replace(
|
|
2007
|
+
new RegExp(`{{${key}}}`, 'g'),
|
|
2008
|
+
String(val),
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
});
|
|
1719
2012
|
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
2013
|
+
return renderTemplateString(displayTemplate, {
|
|
2014
|
+
selectedCount: selectedValues.length || 0,
|
|
2015
|
+
selectedTexts: this.getSelectedOptionsText() || '',
|
|
2016
|
+
text,
|
|
2017
|
+
});
|
|
2018
|
+
})
|
|
2019
|
+
.filter(Boolean),
|
|
2020
|
+
),
|
|
2021
|
+
);
|
|
1727
2022
|
return contentArray.join(displaySeparator);
|
|
1728
2023
|
}
|
|
1729
2024
|
|
|
@@ -1741,7 +2036,10 @@ export class KTSelect extends KTComponent {
|
|
|
1741
2036
|
if (mutation.type === 'childList') {
|
|
1742
2037
|
// Option(s) added or removed
|
|
1743
2038
|
needsRebuild = true;
|
|
1744
|
-
} else if (
|
|
2039
|
+
} else if (
|
|
2040
|
+
mutation.type === 'attributes' &&
|
|
2041
|
+
mutation.target instanceof HTMLOptionElement
|
|
2042
|
+
) {
|
|
1745
2043
|
if (mutation.attributeName === 'selected') {
|
|
1746
2044
|
needsSelectionSync = true;
|
|
1747
2045
|
}
|
|
@@ -1768,7 +2066,9 @@ export class KTSelect extends KTComponent {
|
|
|
1768
2066
|
private _rebuildOptionsFromNative() {
|
|
1769
2067
|
// Remove and rebuild the custom dropdown options from the native select
|
|
1770
2068
|
if (this._dropdownContentElement) {
|
|
1771
|
-
const optionsContainer = this._dropdownContentElement.querySelector(
|
|
2069
|
+
const optionsContainer = this._dropdownContentElement.querySelector(
|
|
2070
|
+
'[data-kt-select-options]',
|
|
2071
|
+
);
|
|
1772
2072
|
if (optionsContainer) {
|
|
1773
2073
|
optionsContainer.innerHTML = '';
|
|
1774
2074
|
const options = Array.from(this._element.querySelectorAll('option'));
|
|
@@ -1784,7 +2084,9 @@ export class KTSelect extends KTComponent {
|
|
|
1784
2084
|
optionsContainer.appendChild(renderedOption);
|
|
1785
2085
|
});
|
|
1786
2086
|
// Update internal references
|
|
1787
|
-
this._options = this._wrapperElement.querySelectorAll(
|
|
2087
|
+
this._options = this._wrapperElement.querySelectorAll(
|
|
2088
|
+
'[data-kt-select-option]',
|
|
2089
|
+
) as NodeListOf<HTMLElement>;
|
|
1788
2090
|
}
|
|
1789
2091
|
}
|
|
1790
2092
|
// Sync selection after rebuilding
|
|
@@ -1795,9 +2097,78 @@ export class KTSelect extends KTComponent {
|
|
|
1795
2097
|
|
|
1796
2098
|
private _syncSelectionFromNative() {
|
|
1797
2099
|
// Sync internal state from the native select's selected options
|
|
1798
|
-
const selected = Array.from(
|
|
1799
|
-
|
|
2100
|
+
const selected = Array.from(
|
|
2101
|
+
this._element.querySelectorAll('option:checked'),
|
|
2102
|
+
).map((opt) => (opt as HTMLOptionElement).value);
|
|
2103
|
+
this._state.setSelectedOptions(
|
|
2104
|
+
this._config.multiple ? selected : selected[0] || '',
|
|
2105
|
+
);
|
|
2106
|
+
this.updateSelectedOptionDisplay();
|
|
2107
|
+
this._updateSelectedOptionClass();
|
|
2108
|
+
this.updateSelectAllButtonState();
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
private _handleSelectAllClick(event: Event): void {
|
|
2112
|
+
event.preventDefault();
|
|
2113
|
+
event.stopPropagation();
|
|
2114
|
+
|
|
2115
|
+
const visibleOptions = this._focusManager
|
|
2116
|
+
.getVisibleOptions()
|
|
2117
|
+
.filter((opt) => opt.getAttribute('aria-disabled') !== 'true');
|
|
2118
|
+
if (visibleOptions.length === 0) return;
|
|
2119
|
+
|
|
2120
|
+
const visibleValues = visibleOptions.map(
|
|
2121
|
+
(opt) => opt.dataset.value as string,
|
|
2122
|
+
);
|
|
2123
|
+
const selectedValues = new Set(this.getSelectedOptions());
|
|
2124
|
+
const isAllSelected = visibleOptions.every((opt) =>
|
|
2125
|
+
selectedValues.has(opt.dataset.value as string),
|
|
2126
|
+
);
|
|
2127
|
+
|
|
2128
|
+
if (isAllSelected) {
|
|
2129
|
+
// Deselect all visible
|
|
2130
|
+
visibleValues.forEach((value) => selectedValues.delete(value));
|
|
2131
|
+
} else {
|
|
2132
|
+
// Select all visible
|
|
2133
|
+
visibleValues.forEach((value) => selectedValues.add(value));
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
this._state.setSelectedOptions(Array.from(selectedValues));
|
|
1800
2137
|
this.updateSelectedOptionDisplay();
|
|
1801
2138
|
this._updateSelectedOptionClass();
|
|
2139
|
+
this.updateSelectAllButtonState();
|
|
2140
|
+
|
|
2141
|
+
this._dispatchEvent('change');
|
|
2142
|
+
this._fireEvent('change');
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
public updateSelectAllButtonState(): void {
|
|
2146
|
+
if (
|
|
2147
|
+
!this._config.multiple ||
|
|
2148
|
+
!this._config.enableSelectAll ||
|
|
2149
|
+
!this._selectAllButtonToggle
|
|
2150
|
+
) {
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
const visibleOptions = this._focusManager
|
|
2155
|
+
.getVisibleOptions()
|
|
2156
|
+
.filter((opt) => opt.getAttribute('aria-disabled') !== 'true');
|
|
2157
|
+
|
|
2158
|
+
if (visibleOptions.length === 0) {
|
|
2159
|
+
this._selectAllButton.style.display = 'none';
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
this._selectAllButton.style.display = '';
|
|
2164
|
+
|
|
2165
|
+
const selectedValues = new Set(this.getSelectedOptions());
|
|
2166
|
+
const isAllSelected = visibleOptions.every((opt) =>
|
|
2167
|
+
selectedValues.has(opt.dataset.value as string),
|
|
2168
|
+
);
|
|
2169
|
+
|
|
2170
|
+
this._selectAllButtonToggle.textContent = isAllSelected
|
|
2171
|
+
? this._config.clearAllText
|
|
2172
|
+
: this._config.selectAllText;
|
|
1802
2173
|
}
|
|
1803
2174
|
}
|