@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.
Files changed (92) hide show
  1. package/dist/ktui.js +668 -685
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +5822 -0
  5. package/examples/select/avatar.html +47 -0
  6. package/examples/select/basic-usage.html +10 -14
  7. package/examples/select/{test.html → combobox-icons_.html} +13 -48
  8. package/examples/select/country.html +43 -0
  9. package/examples/select/description.html +25 -41
  10. package/examples/select/disable-option.html +10 -16
  11. package/examples/select/disable-select.html +7 -6
  12. package/examples/select/icon-multiple.html +23 -31
  13. package/examples/select/icon.html +20 -30
  14. package/examples/select/max-selection.html +8 -9
  15. package/examples/select/modal.html +19 -17
  16. package/examples/select/multiple.html +11 -13
  17. package/examples/select/placeholder.html +9 -12
  18. package/examples/select/search.html +30 -22
  19. package/examples/select/sizes.html +94 -0
  20. package/examples/select/template-customization.html +0 -3
  21. package/lib/cjs/components/component.js +1 -1
  22. package/lib/cjs/components/component.js.map +1 -1
  23. package/lib/cjs/components/datatable/datatable.js +1 -1
  24. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  25. package/lib/cjs/components/select/combobox.js +96 -61
  26. package/lib/cjs/components/select/combobox.js.map +1 -1
  27. package/lib/cjs/components/select/config.js +1 -3
  28. package/lib/cjs/components/select/config.js.map +1 -1
  29. package/lib/cjs/components/select/dropdown.js +32 -96
  30. package/lib/cjs/components/select/dropdown.js.map +1 -1
  31. package/lib/cjs/components/select/option.js +53 -20
  32. package/lib/cjs/components/select/option.js.map +1 -1
  33. package/lib/cjs/components/select/search.js +146 -97
  34. package/lib/cjs/components/select/search.js.map +1 -1
  35. package/lib/cjs/components/select/select.js +174 -120
  36. package/lib/cjs/components/select/select.js.map +1 -1
  37. package/lib/cjs/components/select/tags.js +0 -26
  38. package/lib/cjs/components/select/tags.js.map +1 -1
  39. package/lib/cjs/components/select/templates.js +130 -103
  40. package/lib/cjs/components/select/templates.js.map +1 -1
  41. package/lib/cjs/components/select/utils.js +33 -132
  42. package/lib/cjs/components/select/utils.js.map +1 -1
  43. package/lib/cjs/helpers/dom.js +0 -24
  44. package/lib/cjs/helpers/dom.js.map +1 -1
  45. package/lib/esm/components/component.js +1 -1
  46. package/lib/esm/components/component.js.map +1 -1
  47. package/lib/esm/components/datatable/datatable.js +1 -1
  48. package/lib/esm/components/datatable/datatable.js.map +1 -1
  49. package/lib/esm/components/select/combobox.js +96 -61
  50. package/lib/esm/components/select/combobox.js.map +1 -1
  51. package/lib/esm/components/select/config.js +1 -3
  52. package/lib/esm/components/select/config.js.map +1 -1
  53. package/lib/esm/components/select/dropdown.js +32 -96
  54. package/lib/esm/components/select/dropdown.js.map +1 -1
  55. package/lib/esm/components/select/option.js +53 -20
  56. package/lib/esm/components/select/option.js.map +1 -1
  57. package/lib/esm/components/select/search.js +146 -97
  58. package/lib/esm/components/select/search.js.map +1 -1
  59. package/lib/esm/components/select/select.js +174 -120
  60. package/lib/esm/components/select/select.js.map +1 -1
  61. package/lib/esm/components/select/tags.js +0 -26
  62. package/lib/esm/components/select/tags.js.map +1 -1
  63. package/lib/esm/components/select/templates.js +130 -103
  64. package/lib/esm/components/select/templates.js.map +1 -1
  65. package/lib/esm/components/select/utils.js +32 -130
  66. package/lib/esm/components/select/utils.js.map +1 -1
  67. package/lib/esm/helpers/dom.js +0 -24
  68. package/lib/esm/helpers/dom.js.map +1 -1
  69. package/package.json +9 -6
  70. package/src/components/component.ts +0 -4
  71. package/src/components/datatable/datatable.ts +1 -1
  72. package/src/components/input/input.css +1 -1
  73. package/src/components/scrollable/scrollable.css +9 -5
  74. package/src/components/select/combobox.ts +99 -88
  75. package/src/components/select/config.ts +2 -8
  76. package/src/components/select/dropdown.ts +43 -108
  77. package/src/components/select/option.ts +44 -25
  78. package/src/components/select/search.ts +158 -117
  79. package/src/components/select/select.css +97 -27
  80. package/src/components/select/select.ts +181 -127
  81. package/src/components/select/tags.ts +1 -27
  82. package/src/components/select/templates.ts +194 -131
  83. package/src/components/select/utils.ts +30 -166
  84. package/src/helpers/dom.ts +0 -30
  85. package/webpack.config.js +6 -1
  86. package/examples/select/combobox-icons.html +0 -58
  87. package/examples/select/icon-description.html +0 -56
  88. /package/examples/select/{combobox.html → combobox_.html} +0 -0
  89. /package/examples/select/{remote-data.html → remote-data_.html} +0 -0
  90. /package/examples/select/{tags-icons.html → tags-icons_.html} +0 -0
  91. /package/examples/select/{tags-selected.html → tags-selected_.html} +0 -0
  92. /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.innerHTML = defaultTemplates.error({
174
+ optionsContainer.appendChild(defaultTemplates.error({
173
175
  ...this._config,
174
176
  errorMessage: message,
175
- });
177
+ }));
176
178
  break;
177
179
  case 'loading':
178
- optionsContainer.innerHTML = defaultTemplates.loading(
180
+ optionsContainer.appendChild(defaultTemplates.loading(
179
181
  this._config,
180
182
  message || 'Loading...',
181
- ).outerHTML;
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
- // Only attach click handler to display element
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));
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 and highlights if the dropdown is closing
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
- // Always clear the highlights when dropdown closes
841
- this._searchModule.clearSearchHighlights();
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
- // Get the first selected option element
882
- const firstSelectedValue = selectedOptions[0];
883
-
884
- // Use the FocusManager to focus on the option
885
- this._focusManager.focusOptionByValue(firstSelectedValue);
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
- // Tag mode: render tags if enabled
959
- if (this._config.tags && this._tagsModule) {
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
- if (typeof this._config.renderSelected === 'function') {
965
- // Use the custom renderSelected function if provided
966
- this._valueDisplayElement.innerHTML = this._config.renderSelected(selectedOptions);
967
- } else {
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
- if (selectedOptions.length === 0) {
970
- const placeholder = defaultTemplates.placeholder(this._config);
971
- this._valueDisplayElement.replaceChildren(placeholder);
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
- let content = '';
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
- if (this._config.displayTemplate) {
977
- const selectedValues = this.getSelectedOptions();
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
- this._valueDisplayElement.innerHTML = content;
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
- if (maxReached) {
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._valueDisplayElement;
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.replace(/display:\s*[^;]+;?/gi, '').trim(),
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.clearSearchHighlights();
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}, closeOnSelect: ${this._config.closeOnSelect}`);
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 highlights are cleared when selection changes
1323
+ // Ensure any search input is cleared when selection changes
1313
1324
  if (this._searchModule) {
1314
- this._searchModule.clearSearchHighlights();
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 if (this._config.closeOnSelect) {
1359
+ } else {
1350
1360
  if (this._config.debug)
1351
- console.log('About to call closeDropdown() for multiple select with closeOnSelect:true');
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's option cache if search is enabled
1480
- if (this._searchModule && this._config.enableSearch) {
1481
- this._searchModule.refreshOptionCache();
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
- // Ignore modifier keys
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
- return;
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 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
- }
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
- // Prevent form submit
1664
- event.preventDefault();
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, renderTemplateString } from './utils';
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
  */