@keenthemes/ktui 1.0.12 → 1.0.13
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 +668 -685
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +5822 -0
- package/examples/select/avatar.html +47 -0
- package/examples/select/basic-usage.html +10 -14
- package/examples/select/{test.html → combobox-icons_.html} +13 -48
- package/examples/select/country.html +43 -0
- package/examples/select/description.html +25 -41
- package/examples/select/disable-option.html +10 -16
- package/examples/select/disable-select.html +7 -6
- package/examples/select/icon-multiple.html +23 -31
- package/examples/select/icon.html +20 -30
- package/examples/select/max-selection.html +8 -9
- package/examples/select/modal.html +19 -17
- package/examples/select/multiple.html +11 -13
- package/examples/select/placeholder.html +9 -12
- package/examples/select/search.html +30 -22
- package/examples/select/sizes.html +94 -0
- package/examples/select/template-customization.html +0 -3
- package/lib/cjs/components/component.js +1 -1
- package/lib/cjs/components/component.js.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +1 -1
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/select/combobox.js +96 -61
- package/lib/cjs/components/select/combobox.js.map +1 -1
- package/lib/cjs/components/select/config.js +1 -3
- package/lib/cjs/components/select/config.js.map +1 -1
- package/lib/cjs/components/select/dropdown.js +32 -96
- package/lib/cjs/components/select/dropdown.js.map +1 -1
- package/lib/cjs/components/select/option.js +53 -20
- package/lib/cjs/components/select/option.js.map +1 -1
- package/lib/cjs/components/select/search.js +146 -97
- package/lib/cjs/components/select/search.js.map +1 -1
- package/lib/cjs/components/select/select.js +174 -120
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.js +0 -26
- package/lib/cjs/components/select/tags.js.map +1 -1
- package/lib/cjs/components/select/templates.js +130 -103
- package/lib/cjs/components/select/templates.js.map +1 -1
- package/lib/cjs/components/select/utils.js +33 -132
- package/lib/cjs/components/select/utils.js.map +1 -1
- package/lib/cjs/helpers/dom.js +0 -24
- package/lib/cjs/helpers/dom.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 +1 -1
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/select/combobox.js +96 -61
- package/lib/esm/components/select/combobox.js.map +1 -1
- package/lib/esm/components/select/config.js +1 -3
- package/lib/esm/components/select/config.js.map +1 -1
- package/lib/esm/components/select/dropdown.js +32 -96
- package/lib/esm/components/select/dropdown.js.map +1 -1
- package/lib/esm/components/select/option.js +53 -20
- package/lib/esm/components/select/option.js.map +1 -1
- package/lib/esm/components/select/search.js +146 -97
- package/lib/esm/components/select/search.js.map +1 -1
- package/lib/esm/components/select/select.js +174 -120
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.js +0 -26
- package/lib/esm/components/select/tags.js.map +1 -1
- package/lib/esm/components/select/templates.js +130 -103
- package/lib/esm/components/select/templates.js.map +1 -1
- package/lib/esm/components/select/utils.js +32 -130
- package/lib/esm/components/select/utils.js.map +1 -1
- package/lib/esm/helpers/dom.js +0 -24
- package/lib/esm/helpers/dom.js.map +1 -1
- package/package.json +9 -6
- package/src/components/component.ts +0 -4
- package/src/components/datatable/datatable.ts +1 -1
- package/src/components/input/input.css +1 -1
- package/src/components/scrollable/scrollable.css +9 -5
- package/src/components/select/combobox.ts +99 -88
- package/src/components/select/config.ts +2 -8
- package/src/components/select/dropdown.ts +43 -108
- package/src/components/select/option.ts +44 -25
- package/src/components/select/search.ts +158 -117
- package/src/components/select/select.css +97 -27
- package/src/components/select/select.ts +181 -127
- package/src/components/select/tags.ts +1 -27
- package/src/components/select/templates.ts +194 -131
- package/src/components/select/utils.ts +30 -166
- package/src/helpers/dom.ts +0 -30
- package/webpack.config.js +6 -1
- package/examples/select/combobox-icons.html +0 -58
- package/examples/select/icon-description.html +0 -56
- /package/examples/select/{combobox.html → combobox_.html} +0 -0
- /package/examples/select/{remote-data.html → remote-data_.html} +0 -0
- /package/examples/select/{tags-icons.html → tags-icons_.html} +0 -0
- /package/examples/select/{tags-selected.html → tags-selected_.html} +0 -0
- /package/examples/select/{tags.html → tags_.html} +0 -0
|
@@ -37,7 +37,6 @@ export class KTSelect extends KTComponent {
|
|
|
37
37
|
private _displayElement: HTMLElement;
|
|
38
38
|
private _dropdownContentElement: HTMLElement;
|
|
39
39
|
private _searchInputElement: HTMLInputElement | null;
|
|
40
|
-
private _valueDisplayElement: HTMLElement;
|
|
41
40
|
private _options: NodeListOf<HTMLElement>;
|
|
42
41
|
|
|
43
42
|
// State
|
|
@@ -167,21 +166,23 @@ export class KTSelect extends KTComponent {
|
|
|
167
166
|
);
|
|
168
167
|
if (!optionsContainer) return;
|
|
169
168
|
|
|
169
|
+
// Clear previous messages
|
|
170
|
+
optionsContainer.innerHTML = '';
|
|
171
|
+
|
|
170
172
|
switch (type) {
|
|
171
173
|
case 'error':
|
|
172
|
-
optionsContainer.
|
|
174
|
+
optionsContainer.appendChild(defaultTemplates.error({
|
|
173
175
|
...this._config,
|
|
174
176
|
errorMessage: message,
|
|
175
|
-
});
|
|
177
|
+
}));
|
|
176
178
|
break;
|
|
177
179
|
case 'loading':
|
|
178
|
-
optionsContainer.
|
|
180
|
+
optionsContainer.appendChild(defaultTemplates.loading(
|
|
179
181
|
this._config,
|
|
180
182
|
message || 'Loading...',
|
|
181
|
-
)
|
|
183
|
+
));
|
|
182
184
|
break;
|
|
183
185
|
case 'empty':
|
|
184
|
-
optionsContainer.innerHTML = '';
|
|
185
186
|
optionsContainer.appendChild(defaultTemplates.empty(this._config));
|
|
186
187
|
break;
|
|
187
188
|
}
|
|
@@ -397,6 +398,7 @@ export class KTSelect extends KTComponent {
|
|
|
397
398
|
this._displayElement,
|
|
398
399
|
this._dropdownContentElement,
|
|
399
400
|
this._config,
|
|
401
|
+
this, // Pass the KTSelect instance to KTSelectDropdown
|
|
400
402
|
);
|
|
401
403
|
|
|
402
404
|
// Update display and set ARIA attributes
|
|
@@ -408,13 +410,6 @@ export class KTSelect extends KTComponent {
|
|
|
408
410
|
this._attachEventListeners();
|
|
409
411
|
}
|
|
410
412
|
|
|
411
|
-
/**
|
|
412
|
-
* Initialize options HTML from data
|
|
413
|
-
*/
|
|
414
|
-
// private _initializeOptionsHtml() {
|
|
415
|
-
// this._generateOptionsHtml(this._element);
|
|
416
|
-
// }
|
|
417
|
-
|
|
418
413
|
/**
|
|
419
414
|
* Creates the HTML structure for the select component
|
|
420
415
|
*/
|
|
@@ -514,10 +509,6 @@ export class KTSelect extends KTComponent {
|
|
|
514
509
|
this._searchInputElement = this._displayElement as HTMLInputElement;
|
|
515
510
|
}
|
|
516
511
|
|
|
517
|
-
this._valueDisplayElement = this._wrapperElement.querySelector(
|
|
518
|
-
`[data-kt-select-value]`,
|
|
519
|
-
) as HTMLElement;
|
|
520
|
-
|
|
521
512
|
this._options = this._wrapperElement.querySelectorAll(
|
|
522
513
|
`[data-kt-select-option]`,
|
|
523
514
|
) as NodeListOf<HTMLElement>;
|
|
@@ -537,17 +528,10 @@ export class KTSelect extends KTComponent {
|
|
|
537
528
|
this._handleDropdownOptionClick.bind(this),
|
|
538
529
|
);
|
|
539
530
|
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
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));
|
|
531
|
+
// Attach centralized keyboard handler to the wrapper element.
|
|
532
|
+
// Events from focusable children like _displayElement or _searchInputElement (if present) will bubble up.
|
|
533
|
+
if (this._wrapperElement) {
|
|
534
|
+
this._wrapperElement.addEventListener('keydown', this._handleKeyboardEvent.bind(this));
|
|
551
535
|
}
|
|
552
536
|
}
|
|
553
537
|
|
|
@@ -729,28 +713,6 @@ export class KTSelect extends KTComponent {
|
|
|
729
713
|
* ========================================================================
|
|
730
714
|
*/
|
|
731
715
|
|
|
732
|
-
/**
|
|
733
|
-
* Toggle dropdown visibility
|
|
734
|
-
* @deprecated
|
|
735
|
-
*/
|
|
736
|
-
public toggleDropdown() {
|
|
737
|
-
if (this._config.disabled) {
|
|
738
|
-
if (this._config.debug) console.log('toggleDropdown: select is disabled, not opening');
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
if (this._config.debug) console.log('toggleDropdown called');
|
|
742
|
-
if (this._dropdownModule) {
|
|
743
|
-
// Always use the dropdown module's state to determine whether to open or close
|
|
744
|
-
if (this._dropdownModule.isOpen()) {
|
|
745
|
-
if (this._config.debug) console.log('Dropdown is open, closing...');
|
|
746
|
-
this.closeDropdown();
|
|
747
|
-
} else {
|
|
748
|
-
if (this._config.debug) console.log('Dropdown is closed, opening...');
|
|
749
|
-
this.openDropdown();
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
716
|
/**
|
|
755
717
|
* Open the dropdown
|
|
756
718
|
*/
|
|
@@ -791,17 +753,6 @@ export class KTSelect extends KTComponent {
|
|
|
791
753
|
this._dispatchEvent('show');
|
|
792
754
|
this._fireEvent('show');
|
|
793
755
|
|
|
794
|
-
// Focus search input if configured and exists
|
|
795
|
-
if (
|
|
796
|
-
this._config.enableSearch &&
|
|
797
|
-
this._config.searchAutofocus &&
|
|
798
|
-
this._searchInputElement
|
|
799
|
-
) {
|
|
800
|
-
setTimeout(() => {
|
|
801
|
-
this._searchInputElement.focus();
|
|
802
|
-
}, 50);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
756
|
// Update ARIA states
|
|
806
757
|
this._setAriaAttributes();
|
|
807
758
|
|
|
@@ -830,15 +781,15 @@ export class KTSelect extends KTComponent {
|
|
|
830
781
|
if (this._config.debug)
|
|
831
782
|
console.log('Closing dropdown via dropdownModule...');
|
|
832
783
|
|
|
833
|
-
// Clear search input
|
|
784
|
+
// Clear search input if the dropdown is closing
|
|
834
785
|
if (this._searchModule && this._searchInputElement) {
|
|
835
786
|
// Clear search input if configured to do so
|
|
836
787
|
if (this._config.clearSearchOnClose) {
|
|
837
788
|
this._searchInputElement.value = '';
|
|
838
789
|
}
|
|
839
790
|
|
|
840
|
-
//
|
|
841
|
-
this._searchModule.
|
|
791
|
+
// Clear search input when dropdown closes
|
|
792
|
+
this._searchModule.clearSearch();
|
|
842
793
|
}
|
|
843
794
|
|
|
844
795
|
// Set our internal flag to match what we're doing
|
|
@@ -878,11 +829,12 @@ export class KTSelect extends KTComponent {
|
|
|
878
829
|
const selectedOptions = this.getSelectedOptions();
|
|
879
830
|
if (selectedOptions.length === 0) return;
|
|
880
831
|
|
|
881
|
-
//
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
832
|
+
// Iterate through selected options and focus the first one that is visible
|
|
833
|
+
for (const value of selectedOptions) {
|
|
834
|
+
if (this._focusManager && this._focusManager.focusOptionByValue(value)) {
|
|
835
|
+
break; // Stop after focusing the first found selected and visible option
|
|
836
|
+
}
|
|
837
|
+
}
|
|
886
838
|
}
|
|
887
839
|
|
|
888
840
|
/**
|
|
@@ -954,38 +906,89 @@ export class KTSelect extends KTComponent {
|
|
|
954
906
|
*/
|
|
955
907
|
public updateSelectedOptionDisplay() {
|
|
956
908
|
const selectedOptions = this.getSelectedOptions();
|
|
909
|
+
const tagsEnabled = this._config.tags && this._tagsModule;
|
|
910
|
+
const valueDisplayEl = this.getValueDisplayElement();
|
|
957
911
|
|
|
958
|
-
|
|
959
|
-
|
|
912
|
+
if (tagsEnabled) {
|
|
913
|
+
// Tags module will render tags if selectedOptions > 0, or clear them if selectedOptions === 0.
|
|
960
914
|
this._tagsModule.updateTagsDisplay(selectedOptions);
|
|
961
|
-
return;
|
|
962
915
|
}
|
|
963
916
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
917
|
+
// Guard against valueDisplayEl being null due to template modifications
|
|
918
|
+
if (!valueDisplayEl) {
|
|
919
|
+
if (this._config.debug) {
|
|
920
|
+
console.warn('KTSelect: Value display element is null. Cannot update display or placeholder. Check template for [data-kt-select-value].');
|
|
921
|
+
}
|
|
922
|
+
return; // Nothing to display on if the element is missing
|
|
923
|
+
}
|
|
968
924
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
925
|
+
// 1. Custom render function takes highest precedence
|
|
926
|
+
if (typeof this._config.renderSelected === 'function') {
|
|
927
|
+
valueDisplayEl.innerHTML = this._config.renderSelected(selectedOptions);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
972
930
|
|
|
931
|
+
// 2. Custom displayTemplate string
|
|
932
|
+
// Check if a custom display template string is provided directly in config or via config.templates
|
|
933
|
+
const customDisplayTemplateString = this._config.displayTemplate || (this._config.templates && this._config.templates.display);
|
|
934
|
+
if (customDisplayTemplateString) {
|
|
935
|
+
// If tags are enabled and items are selected, the tags module handles display, so clear the main display area.
|
|
936
|
+
if (tagsEnabled && selectedOptions.length > 0) {
|
|
937
|
+
valueDisplayEl.innerHTML = '';
|
|
973
938
|
} else {
|
|
974
|
-
|
|
939
|
+
// Otherwise, render the custom display template.
|
|
940
|
+
// If no options are selected, renderDisplayTemplateForSelected should handle showing a placeholder if the template supports it,
|
|
941
|
+
// or we might need a separate placeholder rendering for custom templates if they don't handle empty selection.
|
|
942
|
+
// For now, assume renderDisplayTemplateForSelected handles it or shows selected text.
|
|
943
|
+
valueDisplayEl.innerHTML = this.renderDisplayTemplateForSelected(selectedOptions);
|
|
944
|
+
}
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
975
947
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
content = this.renderDisplayTemplateForSelected(selectedValues);
|
|
979
|
-
} else {
|
|
980
|
-
// If no displayTemplate is provided, use the default comma-separated list of selected options
|
|
981
|
-
content = this.getSelectedOptionsText();
|
|
982
|
-
}
|
|
948
|
+
// 3. Default template behavior (no custom function or string template)
|
|
949
|
+
const textContainer = valueDisplayEl.querySelector('[data-kt-text-container="true"]') as HTMLElement;
|
|
983
950
|
|
|
984
|
-
|
|
951
|
+
if (tagsEnabled && selectedOptions.length > 0) {
|
|
952
|
+
// Tags are active and have content, clear the text container if it exists,
|
|
953
|
+
// or the whole display if it doesn't (though it should with default template).
|
|
954
|
+
if (textContainer) {
|
|
955
|
+
textContainer.innerHTML = '';
|
|
956
|
+
} else {
|
|
957
|
+
valueDisplayEl.innerHTML = ''; // Fallback: clear entire display area
|
|
958
|
+
}
|
|
959
|
+
} else if (selectedOptions.length === 0) {
|
|
960
|
+
// No options selected: display placeholder text in the text container.
|
|
961
|
+
const placeholderTemplate = defaultTemplates.placeholder(this._config);
|
|
962
|
+
const placeholderText = placeholderTemplate.textContent || ''; // Get text from placeholder element
|
|
963
|
+
if (textContainer) {
|
|
964
|
+
textContainer.innerHTML = placeholderText;
|
|
965
|
+
} else {
|
|
966
|
+
// Fallback: If no text container, replace children of valueDisplayEl with the placeholder element itself.
|
|
967
|
+
// This ensures the placeholder (which might have its own structure/classes) is displayed.
|
|
968
|
+
valueDisplayEl.replaceChildren(placeholderTemplate);
|
|
969
|
+
}
|
|
970
|
+
} else {
|
|
971
|
+
// Options are selected, and tags are not enabled (or no selected options for tags):
|
|
972
|
+
// Render normal selected text in the text container.
|
|
973
|
+
const content = this.getSelectedOptionsText();
|
|
974
|
+
if (textContainer) {
|
|
975
|
+
textContainer.innerHTML = content;
|
|
976
|
+
} else {
|
|
977
|
+
valueDisplayEl.innerHTML = content; // Fallback: set content on whole display area
|
|
985
978
|
}
|
|
986
979
|
}
|
|
987
980
|
}
|
|
988
981
|
|
|
982
|
+
/**
|
|
983
|
+
* Check if an option was originally disabled in the HTML
|
|
984
|
+
*/
|
|
985
|
+
private _isOptionOriginallyDisabled(value: string): boolean {
|
|
986
|
+
const originalOption = Array.from(this._element.querySelectorAll('option')).find(
|
|
987
|
+
(opt) => opt.value === value
|
|
988
|
+
) as HTMLOptionElement;
|
|
989
|
+
return originalOption ? originalOption.disabled : false;
|
|
990
|
+
}
|
|
991
|
+
|
|
989
992
|
/**
|
|
990
993
|
* Update CSS classes for selected options
|
|
991
994
|
*/
|
|
@@ -1007,17 +1010,23 @@ export class KTSelect extends KTComponent {
|
|
|
1007
1010
|
allOptions.forEach((option) => {
|
|
1008
1011
|
const optionValue = option.getAttribute('data-value');
|
|
1009
1012
|
if (!optionValue) return;
|
|
1013
|
+
|
|
1010
1014
|
const isSelected = selectedValues.includes(optionValue);
|
|
1015
|
+
const isOriginallyDisabled = this._isOptionOriginallyDisabled(optionValue);
|
|
1016
|
+
|
|
1011
1017
|
if (isSelected) {
|
|
1012
1018
|
option.classList.add('selected');
|
|
1013
1019
|
option.setAttribute('aria-selected', 'true');
|
|
1020
|
+
// Selected options should not be visually hidden or disabled by maxSelections logic
|
|
1014
1021
|
option.classList.remove('hidden');
|
|
1015
1022
|
option.classList.remove('disabled');
|
|
1016
1023
|
option.removeAttribute('aria-disabled');
|
|
1017
1024
|
} else {
|
|
1018
1025
|
option.classList.remove('selected');
|
|
1019
1026
|
option.setAttribute('aria-selected', 'false');
|
|
1020
|
-
|
|
1027
|
+
|
|
1028
|
+
// An option should be disabled if it was originally disabled OR if maxSelections is reached
|
|
1029
|
+
if (isOriginallyDisabled || maxReached) {
|
|
1021
1030
|
option.classList.add('disabled');
|
|
1022
1031
|
option.setAttribute('aria-disabled', 'true');
|
|
1023
1032
|
} else {
|
|
@@ -1085,18 +1094,6 @@ export class KTSelect extends KTComponent {
|
|
|
1085
1094
|
* ========================================================================
|
|
1086
1095
|
*/
|
|
1087
1096
|
|
|
1088
|
-
/**
|
|
1089
|
-
* Handle display element click
|
|
1090
|
-
* @deprecated
|
|
1091
|
-
*/
|
|
1092
|
-
private _handleDropdownClick(event: Event) {
|
|
1093
|
-
if (this._config.debug)
|
|
1094
|
-
console.log('Display element clicked', event.target);
|
|
1095
|
-
event.preventDefault();
|
|
1096
|
-
event.stopPropagation(); // Prevent event bubbling
|
|
1097
|
-
this.toggleDropdown();
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
1097
|
/**
|
|
1101
1098
|
* Handle click within the dropdown
|
|
1102
1099
|
*/
|
|
@@ -1145,6 +1142,13 @@ export class KTSelect extends KTComponent {
|
|
|
1145
1142
|
|
|
1146
1143
|
if (this._config.debug) console.log('Option clicked:', optionValue);
|
|
1147
1144
|
|
|
1145
|
+
// If in single-select mode and the clicked option is already selected, just close the dropdown.
|
|
1146
|
+
if (!this._config.multiple && this._state.isSelected(optionValue)) {
|
|
1147
|
+
if (this._config.debug) console.log('Single select mode: clicked already selected option. Closing dropdown.');
|
|
1148
|
+
this.closeDropdown();
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1148
1152
|
// Use toggleSelection instead of _selectOption to prevent re-rendering
|
|
1149
1153
|
this.toggleSelection(optionValue);
|
|
1150
1154
|
}
|
|
@@ -1221,7 +1225,14 @@ export class KTSelect extends KTComponent {
|
|
|
1221
1225
|
* Get value display element
|
|
1222
1226
|
*/
|
|
1223
1227
|
public getValueDisplayElement() {
|
|
1224
|
-
return this.
|
|
1228
|
+
return this._displayElement;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Get wrapper element
|
|
1233
|
+
*/
|
|
1234
|
+
public getWrapperElement(): HTMLElement {
|
|
1235
|
+
return this._wrapperElement;
|
|
1225
1236
|
}
|
|
1226
1237
|
|
|
1227
1238
|
/**
|
|
@@ -1253,7 +1264,7 @@ export class KTSelect extends KTComponent {
|
|
|
1253
1264
|
// Otherwise, remove just the display property
|
|
1254
1265
|
option.setAttribute(
|
|
1255
1266
|
'style',
|
|
1256
|
-
styleAttr
|
|
1267
|
+
styleAttr?.replace(/display:\s*[^;]+;?/gi, '')?.trim(),
|
|
1257
1268
|
);
|
|
1258
1269
|
}
|
|
1259
1270
|
}
|
|
@@ -1265,7 +1276,7 @@ export class KTSelect extends KTComponent {
|
|
|
1265
1276
|
this._searchInputElement.value = '';
|
|
1266
1277
|
// If we have a search module, clear any search filtering
|
|
1267
1278
|
if (this._searchModule) {
|
|
1268
|
-
this._searchModule.
|
|
1279
|
+
this._searchModule.clearSearch();
|
|
1269
1280
|
}
|
|
1270
1281
|
}
|
|
1271
1282
|
}
|
|
@@ -1297,7 +1308,7 @@ export class KTSelect extends KTComponent {
|
|
|
1297
1308
|
// Get current selection state
|
|
1298
1309
|
const isSelected = this._state.isSelected(value);
|
|
1299
1310
|
if (this._config.debug)
|
|
1300
|
-
console.log(`toggleSelection called for value: ${value}, isSelected: ${isSelected}, multiple: ${this._config.multiple}
|
|
1311
|
+
console.log(`toggleSelection called for value: ${value}, isSelected: ${isSelected}, multiple: ${this._config.multiple}`);
|
|
1301
1312
|
|
|
1302
1313
|
// If already selected in single select mode, do nothing (can't deselect in single select)
|
|
1303
1314
|
if (isSelected && !this._config.multiple) {
|
|
@@ -1309,9 +1320,9 @@ export class KTSelect extends KTComponent {
|
|
|
1309
1320
|
if (this._config.debug)
|
|
1310
1321
|
console.log(`Toggling selection for option: ${value}, currently selected: ${isSelected}`);
|
|
1311
1322
|
|
|
1312
|
-
// Ensure any search
|
|
1323
|
+
// Ensure any search input is cleared when selection changes
|
|
1313
1324
|
if (this._searchModule) {
|
|
1314
|
-
this._searchModule.
|
|
1325
|
+
this._searchModule.clearSearch();
|
|
1315
1326
|
}
|
|
1316
1327
|
|
|
1317
1328
|
// Toggle the selection in the state
|
|
@@ -1341,14 +1352,13 @@ export class KTSelect extends KTComponent {
|
|
|
1341
1352
|
this._updateSelectedOptionClass();
|
|
1342
1353
|
|
|
1343
1354
|
// For single select mode, always close the dropdown after selection
|
|
1344
|
-
// For multiple select mode, only close if closeOnSelect is true
|
|
1345
1355
|
if (!this._config.multiple) {
|
|
1346
1356
|
if (this._config.debug)
|
|
1347
1357
|
console.log('About to call closeDropdown() for single select mode - always close after selection');
|
|
1348
1358
|
this.closeDropdown();
|
|
1349
|
-
} else
|
|
1359
|
+
} else {
|
|
1350
1360
|
if (this._config.debug)
|
|
1351
|
-
console.log('About to call closeDropdown() for multiple select
|
|
1361
|
+
console.log('About to call closeDropdown() for multiple select');
|
|
1352
1362
|
this.closeDropdown();
|
|
1353
1363
|
}
|
|
1354
1364
|
|
|
@@ -1476,9 +1486,9 @@ export class KTSelect extends KTComponent {
|
|
|
1476
1486
|
// Update options in the dropdown
|
|
1477
1487
|
this._updateSearchResults(items);
|
|
1478
1488
|
|
|
1479
|
-
// Refresh the search module
|
|
1480
|
-
if (this._searchModule
|
|
1481
|
-
this._searchModule.
|
|
1489
|
+
// Refresh the search module to update focus and cache
|
|
1490
|
+
if (this._searchModule) {
|
|
1491
|
+
this._searchModule.refreshAfterSearch();
|
|
1482
1492
|
}
|
|
1483
1493
|
})
|
|
1484
1494
|
.catch((error) => {
|
|
@@ -1606,20 +1616,49 @@ export class KTSelect extends KTComponent {
|
|
|
1606
1616
|
* Centralized keyboard event handler for all select modes
|
|
1607
1617
|
*/
|
|
1608
1618
|
private _handleKeyboardEvent(event: KeyboardEvent) {
|
|
1619
|
+
// If the event target is the search input and the event was already handled (defaultPrevented),
|
|
1620
|
+
// then return early to avoid duplicate processing by this broader handler.
|
|
1621
|
+
if (event.target === this._searchInputElement && event.defaultPrevented) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1609
1625
|
const isOpen = this._dropdownIsOpen;
|
|
1610
1626
|
const config = this._config;
|
|
1611
1627
|
const focusManager = this._focusManager;
|
|
1612
1628
|
const buffer = this._typeToSearchBuffer;
|
|
1613
1629
|
|
|
1614
|
-
//
|
|
1630
|
+
// If the event target is the search input, let it handle most typing keys naturally.
|
|
1631
|
+
if (event.target === this._searchInputElement) {
|
|
1632
|
+
// Allow navigation keys like ArrowDown, ArrowUp, Escape, Enter (for search/selection) to be handled by the logic below.
|
|
1633
|
+
// For other keys (characters, space, backspace, delete), let the input field process them.
|
|
1634
|
+
if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp' &&
|
|
1635
|
+
event.key !== 'Escape' && event.key !== 'Enter' && event.key !== 'Tab' &&
|
|
1636
|
+
event.key !== 'Home' && event.key !== 'End') {
|
|
1637
|
+
// If it's a character key and we are NOT type-to-searching (because search has focus)
|
|
1638
|
+
// then let the input field handle it for its own value.
|
|
1639
|
+
// The search module's 'input' event will handle filtering based on the input's value.
|
|
1640
|
+
buffer.clear(); // Clear type-to-search buffer when typing in search field
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
// For Enter specifically in search input, we might want to select the focused option or submit search.
|
|
1644
|
+
// This is handled later in the switch.
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// Ignore modifier keys (except for specific combinations if added later)
|
|
1615
1648
|
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
|
1616
1649
|
|
|
1617
|
-
// Type-to-search: only for single char keys
|
|
1618
|
-
if (event.key.length === 1 && !event.repeat && !event.key.match(/\s/)) {
|
|
1650
|
+
// Type-to-search: only for single char keys, when search input does not have focus
|
|
1651
|
+
if (event.key.length === 1 && !event.repeat && !event.key.match(/\s/) && document.activeElement !== this._searchInputElement) {
|
|
1619
1652
|
buffer.push(event.key);
|
|
1620
1653
|
const str = buffer.getBuffer();
|
|
1654
|
+
if (isOpen) {
|
|
1621
1655
|
focusManager.focusByString(str);
|
|
1622
|
-
|
|
1656
|
+
} else {
|
|
1657
|
+
// If closed, type-to-search could potentially open and select.
|
|
1658
|
+
// For now, let's assume it only works when open or opens it first.
|
|
1659
|
+
// Or, we could find the matching option and set it directly without opening.
|
|
1660
|
+
}
|
|
1661
|
+
return; // Type-to-search handles the event
|
|
1623
1662
|
}
|
|
1624
1663
|
|
|
1625
1664
|
switch (event.key) {
|
|
@@ -1650,18 +1689,29 @@ export class KTSelect extends KTComponent {
|
|
|
1650
1689
|
case 'Enter':
|
|
1651
1690
|
case ' ': // Space
|
|
1652
1691
|
if (isOpen) {
|
|
1653
|
-
const
|
|
1654
|
-
if (
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
if (
|
|
1659
|
-
|
|
1660
|
-
|
|
1692
|
+
const focusedOptionEl = this._focusManager.getFocusedOption();
|
|
1693
|
+
if (focusedOptionEl) {
|
|
1694
|
+
const val = focusedOptionEl.dataset.value;
|
|
1695
|
+
// If single select, and the item is already selected, just close.
|
|
1696
|
+
if (val !== undefined && !this._config.multiple && this._state.isSelected(val)) {
|
|
1697
|
+
if (this._config.debug) console.log('Enter on already selected item in single-select mode. Closing.');
|
|
1698
|
+
this.closeDropdown();
|
|
1699
|
+
event.preventDefault();
|
|
1700
|
+
break;
|
|
1661
1701
|
}
|
|
1662
1702
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1703
|
+
|
|
1704
|
+
// Proceed with selection if not handled above
|
|
1705
|
+
this.selectFocusedOption();
|
|
1706
|
+
|
|
1707
|
+
// Close dropdown if configured to do so (for new selections)
|
|
1708
|
+
if (!this._config.multiple) {
|
|
1709
|
+
// This will also be true for the case handled above, but closeDropdown is idempotent.
|
|
1710
|
+
// However, the break above prevents this from being reached for that specific case.
|
|
1711
|
+
this.closeDropdown();
|
|
1712
|
+
}
|
|
1713
|
+
event.preventDefault(); // Prevent form submission or other default actions
|
|
1714
|
+
break;
|
|
1665
1715
|
} else {
|
|
1666
1716
|
this.openDropdown();
|
|
1667
1717
|
}
|
|
@@ -1707,4 +1757,8 @@ export class KTSelect extends KTComponent {
|
|
|
1707
1757
|
));
|
|
1708
1758
|
return contentArray.join(displaySeparator);
|
|
1709
1759
|
}
|
|
1760
|
+
|
|
1761
|
+
public getDisplayElement(): HTMLElement {
|
|
1762
|
+
return this._displayElement;
|
|
1763
|
+
}
|
|
1710
1764
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { KTSelectConfigInterface } from './config';
|
|
7
7
|
import { KTSelect } from './select';
|
|
8
8
|
import { defaultTemplates } from './templates';
|
|
9
|
-
import { EventManager
|
|
9
|
+
import { EventManager } from './utils';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* KTSelectTags - Handles tags-specific functionality for KTSelect
|
|
@@ -83,32 +83,6 @@ export class KTSelectTags {
|
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/**
|
|
87
|
-
* Get the label/text for an option by its value
|
|
88
|
-
*/
|
|
89
|
-
private _getOptionLabel(optionValue: string): string {
|
|
90
|
-
// First look for an option element in the dropdown with matching value
|
|
91
|
-
const optionElements = this._select.getOptionsElement();
|
|
92
|
-
for (const option of Array.from(optionElements)) {
|
|
93
|
-
if ((option as HTMLElement).dataset.value === optionValue) {
|
|
94
|
-
return (option as HTMLElement).textContent?.trim() || optionValue;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// If not found in dropdown, look in original select element
|
|
99
|
-
const originalOptions = this._select
|
|
100
|
-
.getElement()
|
|
101
|
-
.querySelectorAll('option');
|
|
102
|
-
for (const option of Array.from(originalOptions)) {
|
|
103
|
-
if ((option as HTMLOptionElement).value === optionValue) {
|
|
104
|
-
return (option as HTMLOptionElement).textContent?.trim() || optionValue;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// If still not found, return the value itself
|
|
109
|
-
return optionValue;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
86
|
/**
|
|
113
87
|
* Remove a tag and its selection
|
|
114
88
|
*/
|