@keenthemes/ktui 1.0.11 → 1.0.12
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 +1283 -1096
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/examples/select/basic-usage.html +43 -0
- package/examples/select/combobox-icons.html +58 -0
- package/examples/select/combobox.html +46 -0
- package/examples/select/description.html +69 -0
- package/examples/select/disable-option.html +43 -0
- package/examples/select/disable-select.html +34 -0
- package/examples/select/icon-description.html +56 -0
- package/examples/select/icon-multiple.html +58 -0
- package/examples/select/icon.html +58 -0
- package/examples/select/max-selection.html +39 -0
- package/examples/select/modal.html +70 -0
- package/examples/select/multiple.html +42 -0
- package/examples/select/placeholder.html +43 -0
- package/examples/select/remote-data.html +32 -0
- package/examples/select/search.html +49 -0
- package/examples/select/tags-icons.html +58 -0
- package/examples/select/tags-selected.html +59 -0
- package/examples/select/tags.html +58 -0
- package/examples/select/template-customization.html +65 -0
- package/examples/select/test.html +94 -0
- package/examples/toast/example.html +427 -0
- package/lib/cjs/components/component.js +1 -1
- package/lib/cjs/components/component.js.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +22 -6
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/select/combobox.js +38 -120
- package/lib/cjs/components/select/combobox.js.map +1 -1
- package/lib/cjs/components/select/config.js +4 -16
- package/lib/cjs/components/select/config.js.map +1 -1
- package/lib/cjs/components/select/dropdown.js +10 -49
- package/lib/cjs/components/select/dropdown.js.map +1 -1
- package/lib/cjs/components/select/index.js +2 -1
- package/lib/cjs/components/select/index.js.map +1 -1
- package/lib/cjs/components/select/option.js +21 -4
- package/lib/cjs/components/select/option.js.map +1 -1
- package/lib/cjs/components/select/remote.js +1 -37
- package/lib/cjs/components/select/remote.js.map +1 -1
- package/lib/cjs/components/select/search.js +11 -41
- package/lib/cjs/components/select/search.js.map +1 -1
- package/lib/cjs/components/select/select.js +213 -326
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.js +39 -31
- package/lib/cjs/components/select/tags.js.map +1 -1
- package/lib/cjs/components/select/templates.js +120 -179
- package/lib/cjs/components/select/templates.js.map +1 -1
- package/lib/cjs/components/select/types.js +0 -12
- package/lib/cjs/components/select/types.js.map +1 -1
- package/lib/cjs/components/select/utils.js +204 -257
- package/lib/cjs/components/select/utils.js.map +1 -1
- package/lib/cjs/components/toast/index.js +10 -0
- package/lib/cjs/components/toast/index.js.map +1 -0
- package/lib/cjs/components/toast/toast.js +543 -0
- package/lib/cjs/components/toast/toast.js.map +1 -0
- package/lib/cjs/components/toast/types.js +7 -0
- package/lib/cjs/components/toast/types.js.map +1 -0
- package/lib/cjs/helpers/dom.js +24 -0
- package/lib/cjs/helpers/dom.js.map +1 -1
- package/lib/cjs/index.js +5 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/component.js +1 -1
- package/lib/esm/components/component.js.map +1 -1
- package/lib/esm/components/datatable/datatable.js +22 -6
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/select/combobox.js +39 -121
- package/lib/esm/components/select/combobox.js.map +1 -1
- package/lib/esm/components/select/config.js +3 -15
- package/lib/esm/components/select/config.js.map +1 -1
- package/lib/esm/components/select/dropdown.js +10 -49
- 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 +21 -4
- package/lib/esm/components/select/option.js.map +1 -1
- package/lib/esm/components/select/remote.js +1 -37
- package/lib/esm/components/select/remote.js.map +1 -1
- package/lib/esm/components/select/search.js +12 -42
- package/lib/esm/components/select/search.js.map +1 -1
- package/lib/esm/components/select/select.js +214 -327
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.js +39 -31
- package/lib/esm/components/select/tags.js.map +1 -1
- package/lib/esm/components/select/templates.js +119 -178
- package/lib/esm/components/select/templates.js.map +1 -1
- package/lib/esm/components/select/types.js +1 -11
- package/lib/esm/components/select/types.js.map +1 -1
- package/lib/esm/components/select/utils.js +201 -255
- package/lib/esm/components/select/utils.js.map +1 -1
- package/lib/esm/components/toast/index.js +6 -0
- package/lib/esm/components/toast/index.js.map +1 -0
- package/lib/esm/components/toast/toast.js +540 -0
- package/lib/esm/components/toast/toast.js.map +1 -0
- package/lib/esm/components/toast/types.js +6 -0
- package/lib/esm/components/toast/types.js.map +1 -0
- package/lib/esm/helpers/dom.js +24 -0
- package/lib/esm/helpers/dom.js.map +1 -1
- package/lib/esm/index.js +3 -0
- package/lib/esm/index.js.map +1 -1
- package/package.json +8 -6
- package/src/components/alert/alert.css +15 -2
- package/src/components/component.ts +4 -0
- package/src/components/datatable/datatable.ts +24 -16
- package/src/components/input/input.css +3 -1
- package/src/components/link/link.css +2 -2
- package/src/components/select/combobox.ts +42 -149
- package/src/components/select/config.ts +38 -33
- package/src/components/select/dropdown.ts +8 -55
- package/src/components/select/index.ts +1 -1
- package/src/components/select/option.ts +28 -7
- package/src/components/select/remote.ts +2 -42
- package/src/components/select/search.ts +14 -54
- package/src/components/select/select.css +49 -0
- package/src/components/select/select.ts +231 -437
- package/src/components/select/tags.ts +40 -37
- package/src/components/select/templates.ts +166 -303
- package/src/components/select/types.ts +0 -10
- package/src/components/select/utils.ts +214 -304
- package/src/components/textarea/textarea.css +2 -1
- package/src/components/toast/index.ts +7 -0
- package/src/components/toast/toast.css +60 -0
- package/src/components/toast/toast.ts +605 -0
- package/src/components/toast/types.ts +169 -0
- package/src/helpers/dom.ts +30 -0
- package/src/index.ts +4 -0
- package/styles/main.css +3 -0
- package/styles/vars.css +138 -0
- package/styles.css +1 -0
|
@@ -18,13 +18,12 @@ import { defaultTemplates } from './templates';
|
|
|
18
18
|
import { KTSelectCombobox } from './combobox';
|
|
19
19
|
import { KTSelectDropdown } from './dropdown';
|
|
20
20
|
import {
|
|
21
|
-
handleDropdownKeyNavigation,
|
|
22
|
-
filterOptions,
|
|
23
21
|
FocusManager,
|
|
24
22
|
EventManager,
|
|
23
|
+
renderTemplateString,
|
|
24
|
+
TypeToSearchBuffer,
|
|
25
25
|
} from './utils';
|
|
26
26
|
import { KTSelectTags } from './tags';
|
|
27
|
-
import { SelectMode } from './types';
|
|
28
27
|
|
|
29
28
|
export class KTSelect extends KTComponent {
|
|
30
29
|
// Core properties
|
|
@@ -52,6 +51,7 @@ export class KTSelect extends KTComponent {
|
|
|
52
51
|
private _loadMoreIndicator: HTMLElement | null = null;
|
|
53
52
|
private _focusManager: FocusManager;
|
|
54
53
|
private _eventManager: EventManager;
|
|
54
|
+
private _typeToSearchBuffer: TypeToSearchBuffer = new TypeToSearchBuffer();
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Constructor: Initializes the select component
|
|
@@ -152,27 +152,18 @@ export class KTSelect extends KTComponent {
|
|
|
152
152
|
this._element.querySelectorAll('option:not([value=""])'),
|
|
153
153
|
);
|
|
154
154
|
options.forEach((option) => option.remove());
|
|
155
|
-
|
|
156
|
-
// Ensure we have at least an empty option
|
|
157
|
-
if (this._element.querySelectorAll('option').length === 0) {
|
|
158
|
-
const emptyOption = defaultTemplates.emptyOption({
|
|
159
|
-
...this._config,
|
|
160
|
-
placeholder: this._config.placeholder,
|
|
161
|
-
});
|
|
162
|
-
this._element.appendChild(emptyOption);
|
|
163
|
-
}
|
|
164
155
|
}
|
|
165
156
|
|
|
166
157
|
/**
|
|
167
158
|
* Helper to show a dropdown message (error, loading, noResults)
|
|
168
159
|
*/
|
|
169
160
|
private _showDropdownMessage(
|
|
170
|
-
type: 'error' | 'loading' | '
|
|
161
|
+
type: 'error' | 'loading' | 'empty',
|
|
171
162
|
message?: string,
|
|
172
163
|
) {
|
|
173
164
|
if (!this._dropdownContentElement) return;
|
|
174
165
|
const optionsContainer = this._dropdownContentElement.querySelector(
|
|
175
|
-
'[data-kt-select-options
|
|
166
|
+
'[data-kt-select-options]',
|
|
176
167
|
);
|
|
177
168
|
if (!optionsContainer) return;
|
|
178
169
|
|
|
@@ -189,9 +180,9 @@ export class KTSelect extends KTComponent {
|
|
|
189
180
|
message || 'Loading...',
|
|
190
181
|
).outerHTML;
|
|
191
182
|
break;
|
|
192
|
-
case '
|
|
183
|
+
case 'empty':
|
|
193
184
|
optionsContainer.innerHTML = '';
|
|
194
|
-
optionsContainer.appendChild(defaultTemplates.
|
|
185
|
+
optionsContainer.appendChild(defaultTemplates.empty(this._config));
|
|
195
186
|
break;
|
|
196
187
|
}
|
|
197
188
|
}
|
|
@@ -214,21 +205,6 @@ export class KTSelect extends KTComponent {
|
|
|
214
205
|
* @param message Error message
|
|
215
206
|
*/
|
|
216
207
|
private _renderErrorState(message: string) {
|
|
217
|
-
// Create error option if the select is empty
|
|
218
|
-
if (this._element.querySelectorAll('option').length <= 1) {
|
|
219
|
-
const loadingOptions = this._element.querySelectorAll(
|
|
220
|
-
'option[disabled]:not([value])',
|
|
221
|
-
);
|
|
222
|
-
loadingOptions.forEach((option) => option.remove());
|
|
223
|
-
|
|
224
|
-
// Use template function for error option instead of hardcoded element
|
|
225
|
-
const errorOption = defaultTemplates.errorOption({
|
|
226
|
-
...this._config,
|
|
227
|
-
errorMessage: message,
|
|
228
|
-
});
|
|
229
|
-
this._element.appendChild(errorOption);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
208
|
// If dropdown is already created, show error message there
|
|
233
209
|
this._showDropdownMessage('error', message);
|
|
234
210
|
|
|
@@ -255,7 +231,7 @@ export class KTSelect extends KTComponent {
|
|
|
255
231
|
|
|
256
232
|
// Add to dropdown
|
|
257
233
|
const optionsContainer = this._dropdownContentElement.querySelector(
|
|
258
|
-
'[data-kt-select-options
|
|
234
|
+
'[data-kt-select-options]',
|
|
259
235
|
);
|
|
260
236
|
if (optionsContainer) {
|
|
261
237
|
optionsContainer.appendChild(this._loadMoreIndicator);
|
|
@@ -337,54 +313,28 @@ export class KTSelect extends KTComponent {
|
|
|
337
313
|
if (!this._dropdownContentElement || !newItems.length) return;
|
|
338
314
|
|
|
339
315
|
const optionsContainer = this._dropdownContentElement.querySelector(
|
|
340
|
-
|
|
316
|
+
`[data-kt-select-options]`,
|
|
341
317
|
);
|
|
342
318
|
if (!optionsContainer) return;
|
|
343
319
|
|
|
344
320
|
// Get the load more button
|
|
345
321
|
const loadMoreButton = optionsContainer.querySelector(
|
|
346
|
-
|
|
322
|
+
`[data-kt-select-load-more]`,
|
|
347
323
|
);
|
|
348
324
|
|
|
349
325
|
// Process each new item
|
|
350
326
|
newItems.forEach((item) => {
|
|
351
327
|
// Create option for the original select
|
|
352
|
-
const selectOption =
|
|
353
|
-
...this._config,
|
|
354
|
-
placeholder: item.title || 'Unnamed option',
|
|
355
|
-
});
|
|
328
|
+
const selectOption = document.createElement('option');
|
|
356
329
|
selectOption.value = item.id || '';
|
|
357
330
|
|
|
358
|
-
// Add description and icon attributes if available and valid
|
|
359
|
-
if (
|
|
360
|
-
item.description &&
|
|
361
|
-
item.description !== 'null' &&
|
|
362
|
-
item.description !== 'undefined'
|
|
363
|
-
) {
|
|
364
|
-
selectOption.setAttribute(
|
|
365
|
-
'data-kt-select-option-description',
|
|
366
|
-
item.description,
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
if (item.icon && item.icon !== 'null' && item.icon !== 'undefined') {
|
|
370
|
-
selectOption.setAttribute('data-kt-select-option-icon', item.icon);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Add the option to the original select element
|
|
374
|
-
this._element.appendChild(selectOption);
|
|
375
|
-
|
|
376
|
-
// Create option element for the dropdown using the KTSelectOption class
|
|
377
|
-
// This ensures consistent option rendering
|
|
378
|
-
const ktOption = new KTSelectOption(selectOption, this._config);
|
|
379
|
-
const renderedOption = ktOption.render();
|
|
380
|
-
|
|
381
331
|
// Add to dropdown container
|
|
382
332
|
if (loadMoreButton) {
|
|
383
333
|
// Insert before the load more button
|
|
384
|
-
optionsContainer.insertBefore(
|
|
334
|
+
optionsContainer.insertBefore(selectOption, loadMoreButton);
|
|
385
335
|
} else {
|
|
386
336
|
// Append to the end
|
|
387
|
-
optionsContainer.appendChild(
|
|
337
|
+
optionsContainer.appendChild(selectOption);
|
|
388
338
|
}
|
|
389
339
|
});
|
|
390
340
|
|
|
@@ -413,7 +363,7 @@ export class KTSelect extends KTComponent {
|
|
|
413
363
|
this._initZIndex();
|
|
414
364
|
|
|
415
365
|
// Initialize options
|
|
416
|
-
this._initializeOptionsHtml();
|
|
366
|
+
// this._initializeOptionsHtml();
|
|
417
367
|
this._preSelectOptions(this._element);
|
|
418
368
|
|
|
419
369
|
// Apply disabled state if needed
|
|
@@ -425,12 +375,12 @@ export class KTSelect extends KTComponent {
|
|
|
425
375
|
}
|
|
426
376
|
|
|
427
377
|
// Initialize combobox if enabled
|
|
428
|
-
if (this._config.
|
|
378
|
+
if (this._config.combobox) {
|
|
429
379
|
this._comboboxModule = new KTSelectCombobox(this);
|
|
430
380
|
}
|
|
431
381
|
|
|
432
382
|
// Initialize tags if enabled
|
|
433
|
-
if (this._config.
|
|
383
|
+
if (this._config.tags) {
|
|
434
384
|
this._tagsModule = new KTSelectTags(this);
|
|
435
385
|
}
|
|
436
386
|
|
|
@@ -461,9 +411,9 @@ export class KTSelect extends KTComponent {
|
|
|
461
411
|
/**
|
|
462
412
|
* Initialize options HTML from data
|
|
463
413
|
*/
|
|
464
|
-
private _initializeOptionsHtml() {
|
|
465
|
-
|
|
466
|
-
}
|
|
414
|
+
// private _initializeOptionsHtml() {
|
|
415
|
+
// this._generateOptionsHtml(this._element);
|
|
416
|
+
// }
|
|
467
417
|
|
|
468
418
|
/**
|
|
469
419
|
* Creates the HTML structure for the select component
|
|
@@ -472,29 +422,36 @@ export class KTSelect extends KTComponent {
|
|
|
472
422
|
const options = Array.from(this._element.querySelectorAll('option'));
|
|
473
423
|
|
|
474
424
|
// Create wrapper and display elements
|
|
475
|
-
const wrapperElement = defaultTemplates.
|
|
425
|
+
const wrapperElement = defaultTemplates.wrapper(this._config);
|
|
426
|
+
|
|
476
427
|
const displayElement = defaultTemplates.display(this._config);
|
|
477
428
|
|
|
478
429
|
// Add the display element to the wrapper
|
|
479
430
|
wrapperElement.appendChild(displayElement);
|
|
480
431
|
|
|
432
|
+
// Move classes from original select to display element
|
|
433
|
+
if (this._element.classList.length > 0) {
|
|
434
|
+
displayElement.classList.add(...Array.from(this._element.classList));
|
|
435
|
+
this._element.className = '';
|
|
436
|
+
}
|
|
437
|
+
|
|
481
438
|
// Create an empty dropdown first (without options) using template
|
|
482
|
-
const dropdownElement = defaultTemplates.
|
|
439
|
+
const dropdownElement = defaultTemplates.dropdown({
|
|
483
440
|
...this._config,
|
|
484
441
|
zindex: this._config.dropdownZindex,
|
|
485
442
|
});
|
|
486
443
|
|
|
487
444
|
// Add search input if needed
|
|
488
|
-
|
|
489
|
-
const hasSearch = this._config.enableSearch && !isCombobox;
|
|
490
|
-
|
|
491
|
-
if (hasSearch) {
|
|
445
|
+
if (this._config.enableSearch) {
|
|
492
446
|
const searchElement = defaultTemplates.search(this._config);
|
|
493
447
|
dropdownElement.appendChild(searchElement);
|
|
494
448
|
}
|
|
495
449
|
|
|
496
450
|
// Create options container using template
|
|
497
|
-
const optionsContainer = defaultTemplates.
|
|
451
|
+
const optionsContainer = defaultTemplates.options(this._config);
|
|
452
|
+
|
|
453
|
+
// Clear the options container
|
|
454
|
+
optionsContainer.innerHTML = '';
|
|
498
455
|
|
|
499
456
|
// Add each option directly to the container
|
|
500
457
|
options.forEach((optionElement) => {
|
|
@@ -539,37 +496,24 @@ export class KTSelect extends KTComponent {
|
|
|
539
496
|
|
|
540
497
|
// Get dropdown content element - this is critical for dropdown functionality
|
|
541
498
|
this._dropdownContentElement = this._wrapperElement.querySelector(
|
|
542
|
-
`[data-kt-select-dropdown
|
|
499
|
+
`[data-kt-select-dropdown]`,
|
|
543
500
|
) as HTMLElement;
|
|
544
501
|
|
|
545
502
|
if (!this._dropdownContentElement) {
|
|
503
|
+
console.log(this._element)
|
|
546
504
|
console.error('Dropdown content element not found', this._wrapperElement);
|
|
547
505
|
}
|
|
548
506
|
|
|
549
507
|
// Get search input element - this is used for the search functionality
|
|
550
|
-
// First check if it's in dropdown, then check if it's in display (for combobox)
|
|
551
508
|
this._searchInputElement = this._dropdownContentElement.querySelector(
|
|
552
509
|
`[data-kt-select-search]`,
|
|
553
510
|
) as HTMLInputElement;
|
|
554
511
|
|
|
555
|
-
// If not found in dropdown, check if it's the display element itself
|
|
556
|
-
if (
|
|
557
|
-
!this._searchInputElement &&
|
|
558
|
-
this._config.mode === SelectMode.COMBOBOX
|
|
559
|
-
) {
|
|
512
|
+
// If not found in dropdown, check if it's the display element itself
|
|
513
|
+
if (!this._searchInputElement) {
|
|
560
514
|
this._searchInputElement = this._displayElement as HTMLInputElement;
|
|
561
515
|
}
|
|
562
516
|
|
|
563
|
-
if (this._config.debug)
|
|
564
|
-
console.log(
|
|
565
|
-
'Search input found:',
|
|
566
|
-
this._searchInputElement ? 'Yes' : 'No',
|
|
567
|
-
'Mode:',
|
|
568
|
-
this._config.mode,
|
|
569
|
-
'EnableSearch:',
|
|
570
|
-
this._config.enableSearch,
|
|
571
|
-
);
|
|
572
|
-
|
|
573
517
|
this._valueDisplayElement = this._wrapperElement.querySelector(
|
|
574
518
|
`[data-kt-select-value]`,
|
|
575
519
|
) as HTMLElement;
|
|
@@ -585,7 +529,6 @@ export class KTSelect extends KTComponent {
|
|
|
585
529
|
private _attachEventListeners() {
|
|
586
530
|
// Document level event listeners
|
|
587
531
|
document.addEventListener('click', this._handleDocumentClick.bind(this));
|
|
588
|
-
document.addEventListener('keydown', this._handleEscKey.bind(this));
|
|
589
532
|
|
|
590
533
|
// Dropdown option click events
|
|
591
534
|
this._eventManager.addListener(
|
|
@@ -595,24 +538,16 @@ export class KTSelect extends KTComponent {
|
|
|
595
538
|
);
|
|
596
539
|
|
|
597
540
|
// Only attach click handler to display element
|
|
598
|
-
this._eventManager.addListener(
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
);
|
|
603
|
-
|
|
604
|
-
//
|
|
605
|
-
|
|
606
|
-
if (
|
|
607
|
-
|
|
608
|
-
console.log(
|
|
609
|
-
'Attaching keyboard navigation to display element (non-combobox mode)',
|
|
610
|
-
);
|
|
611
|
-
this._eventManager.addListener(
|
|
612
|
-
this._displayElement,
|
|
613
|
-
'keydown',
|
|
614
|
-
this._handleDropdownKeyDown.bind(this),
|
|
615
|
-
);
|
|
541
|
+
// this._eventManager.addListener(
|
|
542
|
+
// this._wrapperElement,
|
|
543
|
+
// 'click',
|
|
544
|
+
// this._handleDropdownClick.bind(this),
|
|
545
|
+
// );
|
|
546
|
+
|
|
547
|
+
// Attach centralized keyboard handler
|
|
548
|
+
const keyboardTarget = this._searchInputElement || this._wrapperElement;
|
|
549
|
+
if (keyboardTarget) {
|
|
550
|
+
keyboardTarget.addEventListener('keydown', this._handleKeyboardEvent.bind(this));
|
|
616
551
|
}
|
|
617
552
|
}
|
|
618
553
|
|
|
@@ -701,75 +636,16 @@ export class KTSelect extends KTComponent {
|
|
|
701
636
|
extractedLabel !== null ? String(extractedLabel) : 'Unnamed option';
|
|
702
637
|
}
|
|
703
638
|
|
|
704
|
-
// Get description - skip if null, undefined, or "null" string
|
|
705
|
-
let description = null;
|
|
706
|
-
if (
|
|
707
|
-
item.description !== undefined &&
|
|
708
|
-
item.description !== null &&
|
|
709
|
-
String(item.description) !== 'null' &&
|
|
710
|
-
String(item.description) !== 'undefined'
|
|
711
|
-
) {
|
|
712
|
-
description = String(item.description);
|
|
713
|
-
} else if (this._config.dataFieldDescription) {
|
|
714
|
-
const extractedDesc = this._getValueByKey(
|
|
715
|
-
item,
|
|
716
|
-
this._config.dataFieldDescription,
|
|
717
|
-
);
|
|
718
|
-
if (
|
|
719
|
-
extractedDesc !== null &&
|
|
720
|
-
extractedDesc !== undefined &&
|
|
721
|
-
String(extractedDesc) !== 'null' &&
|
|
722
|
-
String(extractedDesc) !== 'undefined'
|
|
723
|
-
) {
|
|
724
|
-
description = String(extractedDesc);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// Get icon - skip if null, undefined, or "null" string
|
|
729
|
-
let icon = null;
|
|
730
|
-
if (
|
|
731
|
-
item.icon !== undefined &&
|
|
732
|
-
item.icon !== null &&
|
|
733
|
-
String(item.icon) !== 'null' &&
|
|
734
|
-
String(item.icon) !== 'undefined'
|
|
735
|
-
) {
|
|
736
|
-
icon = String(item.icon);
|
|
737
|
-
} else if (this._config.dataFieldIcon) {
|
|
738
|
-
const extractedIcon = this._getValueByKey(
|
|
739
|
-
item,
|
|
740
|
-
this._config.dataFieldIcon,
|
|
741
|
-
);
|
|
742
|
-
if (
|
|
743
|
-
extractedIcon !== null &&
|
|
744
|
-
extractedIcon !== undefined &&
|
|
745
|
-
String(extractedIcon) !== 'null' &&
|
|
746
|
-
String(extractedIcon) !== 'undefined'
|
|
747
|
-
) {
|
|
748
|
-
icon = String(extractedIcon);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
639
|
// Log the extracted values for debugging
|
|
753
640
|
if (this._config.debug)
|
|
754
641
|
console.log(
|
|
755
|
-
`Option: value=${value}, label=${label}
|
|
642
|
+
`Option: value=${value}, label=${label}`,
|
|
756
643
|
);
|
|
757
644
|
|
|
758
645
|
// Set option attributes
|
|
759
646
|
optionElement.value = value;
|
|
760
647
|
optionElement.textContent = label || 'Unnamed option';
|
|
761
648
|
|
|
762
|
-
if (description) {
|
|
763
|
-
optionElement.setAttribute(
|
|
764
|
-
'data-kt-select-option-description',
|
|
765
|
-
description,
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
if (icon) {
|
|
770
|
-
optionElement.setAttribute('data-kt-select-option-icon', icon);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
649
|
if (item.selected) {
|
|
774
650
|
optionElement.setAttribute('selected', 'selected');
|
|
775
651
|
}
|
|
@@ -855,8 +731,13 @@ export class KTSelect extends KTComponent {
|
|
|
855
731
|
|
|
856
732
|
/**
|
|
857
733
|
* Toggle dropdown visibility
|
|
734
|
+
* @deprecated
|
|
858
735
|
*/
|
|
859
736
|
public toggleDropdown() {
|
|
737
|
+
if (this._config.disabled) {
|
|
738
|
+
if (this._config.debug) console.log('toggleDropdown: select is disabled, not opening');
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
860
741
|
if (this._config.debug) console.log('toggleDropdown called');
|
|
861
742
|
if (this._dropdownModule) {
|
|
862
743
|
// Always use the dropdown module's state to determine whether to open or close
|
|
@@ -874,6 +755,10 @@ export class KTSelect extends KTComponent {
|
|
|
874
755
|
* Open the dropdown
|
|
875
756
|
*/
|
|
876
757
|
public openDropdown() {
|
|
758
|
+
if (this._config.disabled) {
|
|
759
|
+
if (this._config.debug) console.log('openDropdown: select is disabled, not opening');
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
877
762
|
if (this._config.debug)
|
|
878
763
|
console.log(
|
|
879
764
|
'openDropdown called, dropdownModule exists:',
|
|
@@ -1010,6 +895,12 @@ export class KTSelect extends KTComponent {
|
|
|
1010
895
|
* Select an option by value
|
|
1011
896
|
*/
|
|
1012
897
|
private _selectOption(value: string) {
|
|
898
|
+
// Prevent selection if the option is disabled (in dropdown or original select)
|
|
899
|
+
if (this._isOptionDisabled(value)) {
|
|
900
|
+
if (this._config.debug) console.log('_selectOption: Option is disabled, ignoring selection');
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
|
|
1013
904
|
// Get current selection state
|
|
1014
905
|
const isSelected = this._state.isSelected(value);
|
|
1015
906
|
|
|
@@ -1064,106 +955,37 @@ export class KTSelect extends KTComponent {
|
|
|
1064
955
|
public updateSelectedOptionDisplay() {
|
|
1065
956
|
const selectedOptions = this.getSelectedOptions();
|
|
1066
957
|
|
|
1067
|
-
if
|
|
958
|
+
// Tag mode: render tags if enabled
|
|
959
|
+
if (this._config.tags && this._tagsModule) {
|
|
960
|
+
this._tagsModule.updateTagsDisplay(selectedOptions);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (typeof this._config.renderSelected === 'function') {
|
|
1068
965
|
// Use the custom renderSelected function if provided
|
|
1069
|
-
this.
|
|
966
|
+
this._valueDisplayElement.innerHTML = this._config.renderSelected(selectedOptions);
|
|
1070
967
|
} else {
|
|
968
|
+
|
|
1071
969
|
if (selectedOptions.length === 0) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
} else if (this._config.multiple) {
|
|
1076
|
-
if (this._config.mode === SelectMode.TAGS) {
|
|
1077
|
-
// Use the tags module to render selected options as tags
|
|
1078
|
-
if (this._tagsModule) {
|
|
1079
|
-
this._tagsModule.updateTagsDisplay(selectedOptions);
|
|
1080
|
-
} else {
|
|
1081
|
-
// Fallback if tags module not initialized for some reason
|
|
1082
|
-
this._updateValueDisplay(selectedOptions.join(', '));
|
|
1083
|
-
}
|
|
1084
|
-
} else {
|
|
1085
|
-
// Render as comma-separated values
|
|
1086
|
-
const displayText = selectedOptions
|
|
1087
|
-
.map((option) => this._getOptionInnerHtml(option) || '')
|
|
1088
|
-
.join(', ');
|
|
1089
|
-
this._updateValueDisplay(displayText);
|
|
1090
|
-
}
|
|
970
|
+
const placeholder = defaultTemplates.placeholder(this._config);
|
|
971
|
+
this._valueDisplayElement.replaceChildren(placeholder);
|
|
972
|
+
|
|
1091
973
|
} else {
|
|
1092
|
-
|
|
1093
|
-
if (selectedOption) {
|
|
1094
|
-
const selectedText = this._getOptionInnerHtml(selectedOption);
|
|
1095
|
-
this._updateValueDisplay(selectedText);
|
|
974
|
+
let content = '';
|
|
1096
975
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
this._comboboxModule
|
|
1101
|
-
) {
|
|
1102
|
-
this._comboboxModule.updateSelectedValue(selectedText);
|
|
1103
|
-
}
|
|
976
|
+
if (this._config.displayTemplate) {
|
|
977
|
+
const selectedValues = this.getSelectedOptions();
|
|
978
|
+
content = this.renderDisplayTemplateForSelected(selectedValues);
|
|
1104
979
|
} else {
|
|
1105
|
-
|
|
980
|
+
// If no displayTemplate is provided, use the default comma-separated list of selected options
|
|
981
|
+
content = this.getSelectedOptionsText();
|
|
1106
982
|
}
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
983
|
|
|
1110
|
-
|
|
1111
|
-
this._updateDebugDisplays();
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
/**
|
|
1115
|
-
* Update the value display element
|
|
1116
|
-
*/
|
|
1117
|
-
private _updateValueDisplay(value: string) {
|
|
1118
|
-
if (this._config.mode === SelectMode.COMBOBOX) {
|
|
1119
|
-
// For combobox, we only update the hidden value element, not the input
|
|
1120
|
-
// The combobox module will handle updating the input value
|
|
1121
|
-
if (!this._comboboxModule) {
|
|
1122
|
-
(this._valueDisplayElement as HTMLInputElement).value = value;
|
|
984
|
+
this._valueDisplayElement.innerHTML = content;
|
|
1123
985
|
}
|
|
1124
|
-
} else {
|
|
1125
|
-
this._valueDisplayElement.innerHTML = value;
|
|
1126
986
|
}
|
|
1127
987
|
}
|
|
1128
988
|
|
|
1129
|
-
/**
|
|
1130
|
-
* Update debug displays if present
|
|
1131
|
-
*/
|
|
1132
|
-
private _updateDebugDisplays() {
|
|
1133
|
-
// Check if we're in a test environment with debug boxes
|
|
1134
|
-
const selectId = this.getElement().id;
|
|
1135
|
-
if (selectId) {
|
|
1136
|
-
const debugElement = document.getElementById(`${selectId}-value`);
|
|
1137
|
-
if (debugElement) {
|
|
1138
|
-
const selectedOptions = this.getSelectedOptions();
|
|
1139
|
-
|
|
1140
|
-
// Format display based on selection mode
|
|
1141
|
-
if (this._config.multiple) {
|
|
1142
|
-
// For multiple selection, show comma-separated list
|
|
1143
|
-
debugElement.textContent =
|
|
1144
|
-
selectedOptions.length > 0 ? selectedOptions.join(', ') : 'None';
|
|
1145
|
-
} else {
|
|
1146
|
-
// For single selection, show just the one value
|
|
1147
|
-
debugElement.textContent =
|
|
1148
|
-
selectedOptions.length > 0 ? selectedOptions[0] : 'None';
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
/**
|
|
1155
|
-
* Get option inner HTML content by option value
|
|
1156
|
-
*/
|
|
1157
|
-
private _getOptionInnerHtml(optionValue: string) {
|
|
1158
|
-
const option = Array.from(this._options).find(
|
|
1159
|
-
(opt) => opt.dataset.value === optionValue,
|
|
1160
|
-
);
|
|
1161
|
-
if (this._config.mode == SelectMode.COMBOBOX) {
|
|
1162
|
-
return option.textContent;
|
|
1163
|
-
}
|
|
1164
|
-
return option.innerHTML; // Get the entire HTML content of the option
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
989
|
/**
|
|
1168
990
|
* Update CSS classes for selected options
|
|
1169
991
|
*/
|
|
@@ -1215,19 +1037,6 @@ export class KTSelect extends KTComponent {
|
|
|
1215
1037
|
this.updateSelectedOptionDisplay();
|
|
1216
1038
|
this._updateSelectedOptionClass();
|
|
1217
1039
|
|
|
1218
|
-
// For combobox, also clear the input value
|
|
1219
|
-
if (this._config.mode === SelectMode.COMBOBOX) {
|
|
1220
|
-
if (this._searchInputElement) {
|
|
1221
|
-
this._searchInputElement.value = '';
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// If combobox has a clear button, hide it
|
|
1225
|
-
if (this._comboboxModule) {
|
|
1226
|
-
// The combobox module will handle hiding the clear button
|
|
1227
|
-
this._comboboxModule.resetInputValueToSelection();
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
1040
|
// Dispatch change event
|
|
1232
1041
|
this._dispatchEvent('change');
|
|
1233
1042
|
this._fireEvent('change');
|
|
@@ -1241,56 +1050,6 @@ export class KTSelect extends KTComponent {
|
|
|
1241
1050
|
this._state.setSelectedOptions(values);
|
|
1242
1051
|
}
|
|
1243
1052
|
|
|
1244
|
-
/**
|
|
1245
|
-
* ========================================================================
|
|
1246
|
-
* KEYBOARD NAVIGATION
|
|
1247
|
-
* ========================================================================
|
|
1248
|
-
*/
|
|
1249
|
-
|
|
1250
|
-
/**
|
|
1251
|
-
* Handle dropdown key down events for keyboard navigation
|
|
1252
|
-
* Only used for standard (non-combobox) dropdowns
|
|
1253
|
-
*/
|
|
1254
|
-
private _handleDropdownKeyDown(event: KeyboardEvent) {
|
|
1255
|
-
// Log event for debugging
|
|
1256
|
-
if (this._config.debug)
|
|
1257
|
-
console.log('Standard dropdown keydown:', event.key);
|
|
1258
|
-
|
|
1259
|
-
// Use the shared handler
|
|
1260
|
-
handleDropdownKeyNavigation(event, this, {
|
|
1261
|
-
multiple: this._config.multiple,
|
|
1262
|
-
closeOnSelect: this._config.closeOnSelect,
|
|
1263
|
-
});
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
/**
|
|
1267
|
-
* Focus next option in dropdown
|
|
1268
|
-
*/
|
|
1269
|
-
private _focusNextOption(): Element | null {
|
|
1270
|
-
return this._focusManager.focusNext();
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
/**
|
|
1274
|
-
* Focus previous option in dropdown
|
|
1275
|
-
*/
|
|
1276
|
-
private _focusPreviousOption(): Element | null {
|
|
1277
|
-
return this._focusManager.focusPrevious();
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
/**
|
|
1281
|
-
* Apply hover/focus state to focused option
|
|
1282
|
-
*/
|
|
1283
|
-
private _hoverFocusedOption(option: Element) {
|
|
1284
|
-
this._focusManager.applyFocus(option as HTMLElement);
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
/**
|
|
1288
|
-
* Scroll option into view when navigating
|
|
1289
|
-
*/
|
|
1290
|
-
private _scrollOptionIntoView(option: Element) {
|
|
1291
|
-
this._focusManager.scrollIntoView(option as HTMLElement);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
1053
|
/**
|
|
1295
1054
|
* Select the currently focused option
|
|
1296
1055
|
*/
|
|
@@ -1317,54 +1076,7 @@ export class KTSelect extends KTComponent {
|
|
|
1317
1076
|
if (selectedValue) {
|
|
1318
1077
|
this._selectOption(selectedValue);
|
|
1319
1078
|
}
|
|
1320
|
-
|
|
1321
|
-
// For combobox mode, update input value AFTER selection to ensure consistency
|
|
1322
|
-
if (this._config.mode === SelectMode.COMBOBOX && this._comboboxModule) {
|
|
1323
|
-
this._comboboxModule.updateSelectedValue(selectedText);
|
|
1324
|
-
// Also directly update the input value for immediate visual feedback
|
|
1325
|
-
if (this._searchInputElement) {
|
|
1326
|
-
this._searchInputElement.value = selectedText;
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
/**
|
|
1333
|
-
* ========================================================================
|
|
1334
|
-
* COMBOBOX SPECIFIC METHODS
|
|
1335
|
-
* ========================================================================
|
|
1336
|
-
*/
|
|
1337
|
-
|
|
1338
|
-
/**
|
|
1339
|
-
* Handle combobox input events
|
|
1340
|
-
*/
|
|
1341
|
-
private _handleComboboxInput(event: Event) {
|
|
1342
|
-
if (this._comboboxModule) {
|
|
1343
|
-
return;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
const inputElement = event.target as HTMLInputElement;
|
|
1347
|
-
const query = inputElement.value.toLowerCase();
|
|
1348
|
-
|
|
1349
|
-
// If dropdown isn't open, open it when user starts typing
|
|
1350
|
-
if (!this._dropdownIsOpen) {
|
|
1351
|
-
this.openDropdown();
|
|
1352
1079
|
}
|
|
1353
|
-
|
|
1354
|
-
// Filter options based on input
|
|
1355
|
-
this._filterOptionsForCombobox(query);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
/**
|
|
1359
|
-
* Filter options for combobox based on input query
|
|
1360
|
-
* Uses the shared filterOptions function
|
|
1361
|
-
*/
|
|
1362
|
-
private _filterOptionsForCombobox(query: string) {
|
|
1363
|
-
const options = Array.from(
|
|
1364
|
-
this._dropdownContentElement.querySelectorAll('[data-kt-select-option]'),
|
|
1365
|
-
) as HTMLElement[];
|
|
1366
|
-
|
|
1367
|
-
filterOptions(options, query, this._config, this._dropdownContentElement);
|
|
1368
1080
|
}
|
|
1369
1081
|
|
|
1370
1082
|
/**
|
|
@@ -1375,6 +1087,7 @@ export class KTSelect extends KTComponent {
|
|
|
1375
1087
|
|
|
1376
1088
|
/**
|
|
1377
1089
|
* Handle display element click
|
|
1090
|
+
* @deprecated
|
|
1378
1091
|
*/
|
|
1379
1092
|
private _handleDropdownClick(event: Event) {
|
|
1380
1093
|
if (this._config.debug)
|
|
@@ -1447,15 +1160,6 @@ export class KTSelect extends KTComponent {
|
|
|
1447
1160
|
}
|
|
1448
1161
|
}
|
|
1449
1162
|
|
|
1450
|
-
/**
|
|
1451
|
-
* Handle escape key press
|
|
1452
|
-
*/
|
|
1453
|
-
private _handleEscKey(event: KeyboardEvent) {
|
|
1454
|
-
if (event.key === 'Escape' && this._dropdownIsOpen) {
|
|
1455
|
-
this.closeDropdown();
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
1163
|
/**
|
|
1460
1164
|
* ========================================================================
|
|
1461
1165
|
* ACCESSIBILITY METHODS
|
|
@@ -1472,20 +1176,6 @@ export class KTSelect extends KTComponent {
|
|
|
1472
1176
|
);
|
|
1473
1177
|
}
|
|
1474
1178
|
|
|
1475
|
-
/**
|
|
1476
|
-
* Handle focus events
|
|
1477
|
-
*/
|
|
1478
|
-
private _handleFocus() {
|
|
1479
|
-
// Implementation pending
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
/**
|
|
1483
|
-
* Handle blur events
|
|
1484
|
-
*/
|
|
1485
|
-
private _handleBlur() {
|
|
1486
|
-
// Implementation pending
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
1179
|
/**
|
|
1490
1180
|
* ========================================================================
|
|
1491
1181
|
* PUBLIC API
|
|
@@ -1571,7 +1261,7 @@ export class KTSelect extends KTComponent {
|
|
|
1571
1261
|
});
|
|
1572
1262
|
|
|
1573
1263
|
// If search input exists, clear it
|
|
1574
|
-
if (this._searchInputElement
|
|
1264
|
+
if (this._searchInputElement) {
|
|
1575
1265
|
this._searchInputElement.value = '';
|
|
1576
1266
|
// If we have a search module, clear any search filtering
|
|
1577
1267
|
if (this._searchModule) {
|
|
@@ -1598,26 +1288,26 @@ export class KTSelect extends KTComponent {
|
|
|
1598
1288
|
* Toggle the selection of an option
|
|
1599
1289
|
*/
|
|
1600
1290
|
public toggleSelection(value: string): void {
|
|
1291
|
+
// Prevent selection if the option is disabled (in dropdown or original select)
|
|
1292
|
+
if (this._isOptionDisabled(value)) {
|
|
1293
|
+
if (this._config.debug) console.log('toggleSelection: Option is disabled, ignoring selection');
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1601
1297
|
// Get current selection state
|
|
1602
1298
|
const isSelected = this._state.isSelected(value);
|
|
1603
1299
|
if (this._config.debug)
|
|
1604
|
-
console.log(
|
|
1605
|
-
`toggleSelection called for value: ${value}, isSelected: ${isSelected}, multiple: ${this._config.multiple}, closeOnSelect: ${this._config.closeOnSelect}`,
|
|
1606
|
-
);
|
|
1300
|
+
console.log(`toggleSelection called for value: ${value}, isSelected: ${isSelected}, multiple: ${this._config.multiple}, closeOnSelect: ${this._config.closeOnSelect}`);
|
|
1607
1301
|
|
|
1608
1302
|
// If already selected in single select mode, do nothing (can't deselect in single select)
|
|
1609
1303
|
if (isSelected && !this._config.multiple) {
|
|
1610
1304
|
if (this._config.debug)
|
|
1611
|
-
console.log(
|
|
1612
|
-
'Early return from toggleSelection - already selected in single select mode',
|
|
1613
|
-
);
|
|
1305
|
+
console.log('Early return from toggleSelection - already selected in single select mode');
|
|
1614
1306
|
return;
|
|
1615
1307
|
}
|
|
1616
1308
|
|
|
1617
1309
|
if (this._config.debug)
|
|
1618
|
-
console.log(
|
|
1619
|
-
`Toggling selection for option: ${value}, currently selected: ${isSelected}`,
|
|
1620
|
-
);
|
|
1310
|
+
console.log(`Toggling selection for option: ${value}, currently selected: ${isSelected}`);
|
|
1621
1311
|
|
|
1622
1312
|
// Ensure any search highlights are cleared when selection changes
|
|
1623
1313
|
if (this._searchModule) {
|
|
@@ -1654,15 +1344,11 @@ export class KTSelect extends KTComponent {
|
|
|
1654
1344
|
// For multiple select mode, only close if closeOnSelect is true
|
|
1655
1345
|
if (!this._config.multiple) {
|
|
1656
1346
|
if (this._config.debug)
|
|
1657
|
-
console.log(
|
|
1658
|
-
'About to call closeDropdown() for single select mode - always close after selection',
|
|
1659
|
-
);
|
|
1347
|
+
console.log('About to call closeDropdown() for single select mode - always close after selection');
|
|
1660
1348
|
this.closeDropdown();
|
|
1661
1349
|
} else if (this._config.closeOnSelect) {
|
|
1662
1350
|
if (this._config.debug)
|
|
1663
|
-
console.log(
|
|
1664
|
-
'About to call closeDropdown() for multiple select with closeOnSelect:true',
|
|
1665
|
-
);
|
|
1351
|
+
console.log('About to call closeDropdown() for multiple select with closeOnSelect:true');
|
|
1666
1352
|
this.closeDropdown();
|
|
1667
1353
|
}
|
|
1668
1354
|
|
|
@@ -1806,7 +1492,7 @@ export class KTSelect extends KTComponent {
|
|
|
1806
1492
|
console.error('Error fetching search results:', error);
|
|
1807
1493
|
this._renderSearchErrorState(
|
|
1808
1494
|
this._remoteModule.getErrorMessage() ||
|
|
1809
|
-
|
|
1495
|
+
'Failed to load search results',
|
|
1810
1496
|
);
|
|
1811
1497
|
});
|
|
1812
1498
|
}, this._config.searchDebounce || 300);
|
|
@@ -1821,7 +1507,7 @@ export class KTSelect extends KTComponent {
|
|
|
1821
1507
|
private _renderSearchLoadingState() {
|
|
1822
1508
|
if (!this._originalOptionsHtml && this._dropdownContentElement) {
|
|
1823
1509
|
const optionsContainer = this._dropdownContentElement.querySelector(
|
|
1824
|
-
'[data-kt-select-options
|
|
1510
|
+
'[data-kt-select-options]',
|
|
1825
1511
|
);
|
|
1826
1512
|
if (optionsContainer) {
|
|
1827
1513
|
this._originalOptionsHtml = optionsContainer.innerHTML;
|
|
@@ -1849,7 +1535,7 @@ export class KTSelect extends KTComponent {
|
|
|
1849
1535
|
if (!this._dropdownContentElement) return;
|
|
1850
1536
|
|
|
1851
1537
|
const optionsContainer = this._dropdownContentElement.querySelector(
|
|
1852
|
-
'[data-kt-select-options
|
|
1538
|
+
'[data-kt-select-options]',
|
|
1853
1539
|
);
|
|
1854
1540
|
if (!optionsContainer) return;
|
|
1855
1541
|
|
|
@@ -1858,7 +1544,7 @@ export class KTSelect extends KTComponent {
|
|
|
1858
1544
|
|
|
1859
1545
|
if (items.length === 0) {
|
|
1860
1546
|
// Show no results message using template for consistency and customization
|
|
1861
|
-
const noResultsElement = defaultTemplates.
|
|
1547
|
+
const noResultsElement = defaultTemplates.empty(this._config);
|
|
1862
1548
|
optionsContainer.appendChild(noResultsElement);
|
|
1863
1549
|
return;
|
|
1864
1550
|
}
|
|
@@ -1866,27 +1552,11 @@ export class KTSelect extends KTComponent {
|
|
|
1866
1552
|
// Process each item individually to create options
|
|
1867
1553
|
items.forEach((item) => {
|
|
1868
1554
|
// Create option for the original select
|
|
1869
|
-
const selectOption =
|
|
1870
|
-
...this._config,
|
|
1871
|
-
placeholder: item.title,
|
|
1872
|
-
});
|
|
1555
|
+
const selectOption = document.createElement('option');
|
|
1873
1556
|
selectOption.value = item.id;
|
|
1874
|
-
if (item.description) {
|
|
1875
|
-
selectOption.setAttribute(
|
|
1876
|
-
'data-kt-select-option-description',
|
|
1877
|
-
item.description,
|
|
1878
|
-
);
|
|
1879
|
-
}
|
|
1880
|
-
if (item.icon) {
|
|
1881
|
-
selectOption.setAttribute('data-kt-select-option-icon', item.icon);
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
// Create option element for the dropdown
|
|
1885
|
-
const ktOption = new KTSelectOption(selectOption, this._config);
|
|
1886
|
-
const renderedOption = ktOption.render();
|
|
1887
1557
|
|
|
1888
1558
|
// Add to dropdown container
|
|
1889
|
-
optionsContainer.appendChild(
|
|
1559
|
+
optionsContainer.appendChild(selectOption);
|
|
1890
1560
|
});
|
|
1891
1561
|
|
|
1892
1562
|
// Add pagination "Load More" button if needed
|
|
@@ -1901,16 +1571,140 @@ export class KTSelect extends KTComponent {
|
|
|
1901
1571
|
}
|
|
1902
1572
|
|
|
1903
1573
|
/**
|
|
1904
|
-
*
|
|
1574
|
+
* Check if dropdown is open
|
|
1575
|
+
*/
|
|
1576
|
+
public isDropdownOpen(): boolean {
|
|
1577
|
+
return this._dropdownIsOpen;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
public getSelectedOptionsText(): string {
|
|
1581
|
+
const selectedValues = this.getSelectedOptions();
|
|
1582
|
+
const displaySeparator = this._config.displaySeparator || ', ';
|
|
1583
|
+
const texts = selectedValues.map(value => {
|
|
1584
|
+
const option = Array.from(this._options).find(opt => opt.getAttribute('data-value') === value);
|
|
1585
|
+
return option?.getAttribute('data-text') || '';
|
|
1586
|
+
}).filter(Boolean);
|
|
1587
|
+
return texts.join(displaySeparator);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/**
|
|
1591
|
+
* Check if an option is disabled (either in dropdown or original select)
|
|
1905
1592
|
*/
|
|
1906
|
-
|
|
1907
|
-
this.
|
|
1593
|
+
private _isOptionDisabled(value: string): boolean {
|
|
1594
|
+
const dropdownOption = Array.from(this._options).find(
|
|
1595
|
+
(opt) => opt.getAttribute('data-value') === value
|
|
1596
|
+
);
|
|
1597
|
+
const isDropdownDisabled = dropdownOption && (dropdownOption.classList.contains('disabled') || dropdownOption.getAttribute('aria-disabled') === 'true');
|
|
1598
|
+
const selectOption = Array.from(this._element.querySelectorAll('option')).find(
|
|
1599
|
+
(opt) => opt.value === value
|
|
1600
|
+
) as HTMLOptionElement;
|
|
1601
|
+
const isNativeDisabled = selectOption && selectOption.disabled;
|
|
1602
|
+
return Boolean(isDropdownDisabled || isNativeDisabled);
|
|
1908
1603
|
}
|
|
1909
1604
|
|
|
1910
1605
|
/**
|
|
1911
|
-
*
|
|
1606
|
+
* Centralized keyboard event handler for all select modes
|
|
1912
1607
|
*/
|
|
1913
|
-
|
|
1914
|
-
|
|
1608
|
+
private _handleKeyboardEvent(event: KeyboardEvent) {
|
|
1609
|
+
const isOpen = this._dropdownIsOpen;
|
|
1610
|
+
const config = this._config;
|
|
1611
|
+
const focusManager = this._focusManager;
|
|
1612
|
+
const buffer = this._typeToSearchBuffer;
|
|
1613
|
+
|
|
1614
|
+
// Ignore modifier keys
|
|
1615
|
+
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
|
1616
|
+
|
|
1617
|
+
// Type-to-search: only for single char keys
|
|
1618
|
+
if (event.key.length === 1 && !event.repeat && !event.key.match(/\s/)) {
|
|
1619
|
+
buffer.push(event.key);
|
|
1620
|
+
const str = buffer.getBuffer();
|
|
1621
|
+
focusManager.focusByString(str);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
switch (event.key) {
|
|
1626
|
+
case 'ArrowDown':
|
|
1627
|
+
event.preventDefault();
|
|
1628
|
+
if (!isOpen) {
|
|
1629
|
+
this.openDropdown();
|
|
1630
|
+
} else {
|
|
1631
|
+
focusManager.focusNext();
|
|
1632
|
+
}
|
|
1633
|
+
break;
|
|
1634
|
+
case 'ArrowUp':
|
|
1635
|
+
event.preventDefault();
|
|
1636
|
+
if (!isOpen) {
|
|
1637
|
+
this.openDropdown();
|
|
1638
|
+
} else {
|
|
1639
|
+
focusManager.focusPrevious();
|
|
1640
|
+
}
|
|
1641
|
+
break;
|
|
1642
|
+
case 'Home':
|
|
1643
|
+
event.preventDefault();
|
|
1644
|
+
if (isOpen) focusManager.focusFirst();
|
|
1645
|
+
break;
|
|
1646
|
+
case 'End':
|
|
1647
|
+
event.preventDefault();
|
|
1648
|
+
if (isOpen) focusManager.focusLast();
|
|
1649
|
+
break;
|
|
1650
|
+
case 'Enter':
|
|
1651
|
+
case ' ': // Space
|
|
1652
|
+
if (isOpen) {
|
|
1653
|
+
const focused = focusManager.getFocusedOption();
|
|
1654
|
+
if (focused) {
|
|
1655
|
+
const value = focused.dataset.value;
|
|
1656
|
+
if (value) {
|
|
1657
|
+
this.toggleSelection(value);
|
|
1658
|
+
if (!config.multiple && config.closeOnSelect) {
|
|
1659
|
+
this.closeDropdown();
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
// Prevent form submit
|
|
1664
|
+
event.preventDefault();
|
|
1665
|
+
} else {
|
|
1666
|
+
this.openDropdown();
|
|
1667
|
+
}
|
|
1668
|
+
break;
|
|
1669
|
+
case 'Escape':
|
|
1670
|
+
if (isOpen) {
|
|
1671
|
+
this.closeDropdown();
|
|
1672
|
+
(event.target as HTMLElement).blur();
|
|
1673
|
+
}
|
|
1674
|
+
break;
|
|
1675
|
+
case 'Tab':
|
|
1676
|
+
// Let Tab propagate for normal focus movement
|
|
1677
|
+
break;
|
|
1678
|
+
default:
|
|
1679
|
+
break;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
public renderDisplayTemplateForSelected(selectedValues: string[]): string {
|
|
1684
|
+
const optionsConfig = this._config.optionsConfig as any || {};
|
|
1685
|
+
const displaySeparator = this._config.displaySeparator || ', ';
|
|
1686
|
+
const contentArray = Array.from(new Set(
|
|
1687
|
+
selectedValues.map(value => {
|
|
1688
|
+
const option = Array.from(this._options).find(opt => opt.getAttribute('data-value') === value);
|
|
1689
|
+
if (!option) return '';
|
|
1690
|
+
|
|
1691
|
+
let displayTemplate = this._config.displayTemplate;
|
|
1692
|
+
const text = option.getAttribute('data-text') || '';
|
|
1693
|
+
|
|
1694
|
+
// Replace all {{varname}} in option.innerHTML with values from _config
|
|
1695
|
+
Object.entries(optionsConfig[value] || {}).forEach(([key, val]) => {
|
|
1696
|
+
if (["string", "number", "boolean"].includes(typeof val)) {
|
|
1697
|
+
displayTemplate = displayTemplate.replace(new RegExp(`{{${key}}}`, 'g'), String(val));
|
|
1698
|
+
}
|
|
1699
|
+
});
|
|
1700
|
+
|
|
1701
|
+
return renderTemplateString(displayTemplate, {
|
|
1702
|
+
selectedCount: selectedValues.length || 0,
|
|
1703
|
+
selectedTexts: this.getSelectedOptionsText() || '',
|
|
1704
|
+
text,
|
|
1705
|
+
});
|
|
1706
|
+
}).filter(Boolean)
|
|
1707
|
+
));
|
|
1708
|
+
return contentArray.join(displaySeparator);
|
|
1915
1709
|
}
|
|
1916
1710
|
}
|