@keenthemes/ktui 1.0.11 → 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 (123) hide show
  1. package/dist/ktui.js +1687 -1517
  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 +39 -0
  7. package/examples/select/combobox-icons_.html +59 -0
  8. package/examples/select/combobox_.html +46 -0
  9. package/examples/select/country.html +43 -0
  10. package/examples/select/description.html +53 -0
  11. package/examples/select/disable-option.html +37 -0
  12. package/examples/select/disable-select.html +35 -0
  13. package/examples/select/icon-multiple.html +50 -0
  14. package/examples/select/icon.html +48 -0
  15. package/examples/select/max-selection.html +38 -0
  16. package/examples/select/modal.html +72 -0
  17. package/examples/select/multiple.html +40 -0
  18. package/examples/select/placeholder.html +40 -0
  19. package/examples/select/remote-data_.html +32 -0
  20. package/examples/select/search.html +57 -0
  21. package/examples/select/sizes.html +94 -0
  22. package/examples/select/tags-icons_.html +58 -0
  23. package/examples/select/tags-selected_.html +59 -0
  24. package/examples/select/tags_.html +58 -0
  25. package/examples/select/template-customization.html +62 -0
  26. package/examples/toast/example.html +427 -0
  27. package/lib/cjs/components/datatable/datatable.js +23 -7
  28. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  29. package/lib/cjs/components/select/combobox.js +94 -141
  30. package/lib/cjs/components/select/combobox.js.map +1 -1
  31. package/lib/cjs/components/select/config.js +4 -18
  32. package/lib/cjs/components/select/config.js.map +1 -1
  33. package/lib/cjs/components/select/dropdown.js +35 -138
  34. package/lib/cjs/components/select/dropdown.js.map +1 -1
  35. package/lib/cjs/components/select/index.js +2 -1
  36. package/lib/cjs/components/select/index.js.map +1 -1
  37. package/lib/cjs/components/select/option.js +56 -6
  38. package/lib/cjs/components/select/option.js.map +1 -1
  39. package/lib/cjs/components/select/remote.js +1 -37
  40. package/lib/cjs/components/select/remote.js.map +1 -1
  41. package/lib/cjs/components/select/search.js +147 -128
  42. package/lib/cjs/components/select/search.js.map +1 -1
  43. package/lib/cjs/components/select/select.js +332 -391
  44. package/lib/cjs/components/select/select.js.map +1 -1
  45. package/lib/cjs/components/select/tags.js +38 -56
  46. package/lib/cjs/components/select/tags.js.map +1 -1
  47. package/lib/cjs/components/select/templates.js +168 -200
  48. package/lib/cjs/components/select/templates.js.map +1 -1
  49. package/lib/cjs/components/select/types.js +0 -12
  50. package/lib/cjs/components/select/types.js.map +1 -1
  51. package/lib/cjs/components/select/utils.js +187 -339
  52. package/lib/cjs/components/select/utils.js.map +1 -1
  53. package/lib/cjs/components/toast/index.js +10 -0
  54. package/lib/cjs/components/toast/index.js.map +1 -0
  55. package/lib/cjs/components/toast/toast.js +543 -0
  56. package/lib/cjs/components/toast/toast.js.map +1 -0
  57. package/lib/cjs/components/toast/types.js +7 -0
  58. package/lib/cjs/components/toast/types.js.map +1 -0
  59. package/lib/cjs/index.js +5 -1
  60. package/lib/cjs/index.js.map +1 -1
  61. package/lib/esm/components/datatable/datatable.js +23 -7
  62. package/lib/esm/components/datatable/datatable.js.map +1 -1
  63. package/lib/esm/components/select/combobox.js +95 -142
  64. package/lib/esm/components/select/combobox.js.map +1 -1
  65. package/lib/esm/components/select/config.js +3 -17
  66. package/lib/esm/components/select/config.js.map +1 -1
  67. package/lib/esm/components/select/dropdown.js +35 -138
  68. package/lib/esm/components/select/dropdown.js.map +1 -1
  69. package/lib/esm/components/select/index.js +1 -1
  70. package/lib/esm/components/select/index.js.map +1 -1
  71. package/lib/esm/components/select/option.js +56 -6
  72. package/lib/esm/components/select/option.js.map +1 -1
  73. package/lib/esm/components/select/remote.js +1 -37
  74. package/lib/esm/components/select/remote.js.map +1 -1
  75. package/lib/esm/components/select/search.js +148 -129
  76. package/lib/esm/components/select/search.js.map +1 -1
  77. package/lib/esm/components/select/select.js +333 -392
  78. package/lib/esm/components/select/select.js.map +1 -1
  79. package/lib/esm/components/select/tags.js +38 -56
  80. package/lib/esm/components/select/tags.js.map +1 -1
  81. package/lib/esm/components/select/templates.js +167 -199
  82. package/lib/esm/components/select/templates.js.map +1 -1
  83. package/lib/esm/components/select/types.js +1 -11
  84. package/lib/esm/components/select/types.js.map +1 -1
  85. package/lib/esm/components/select/utils.js +184 -336
  86. package/lib/esm/components/select/utils.js.map +1 -1
  87. package/lib/esm/components/toast/index.js +6 -0
  88. package/lib/esm/components/toast/index.js.map +1 -0
  89. package/lib/esm/components/toast/toast.js +540 -0
  90. package/lib/esm/components/toast/toast.js.map +1 -0
  91. package/lib/esm/components/toast/types.js +6 -0
  92. package/lib/esm/components/toast/types.js.map +1 -0
  93. package/lib/esm/index.js +3 -0
  94. package/lib/esm/index.js.map +1 -1
  95. package/package.json +14 -9
  96. package/src/components/alert/alert.css +15 -2
  97. package/src/components/datatable/datatable.ts +25 -17
  98. package/src/components/input/input.css +3 -1
  99. package/src/components/link/link.css +2 -2
  100. package/src/components/scrollable/scrollable.css +9 -5
  101. package/src/components/select/combobox.ts +96 -192
  102. package/src/components/select/config.ts +35 -36
  103. package/src/components/select/dropdown.ts +43 -155
  104. package/src/components/select/index.ts +1 -1
  105. package/src/components/select/option.ts +50 -10
  106. package/src/components/select/remote.ts +2 -42
  107. package/src/components/select/search.ts +159 -158
  108. package/src/components/select/select.css +137 -18
  109. package/src/components/select/select.ts +354 -506
  110. package/src/components/select/tags.ts +37 -60
  111. package/src/components/select/templates.ts +254 -328
  112. package/src/components/select/types.ts +0 -10
  113. package/src/components/select/utils.ts +190 -416
  114. package/src/components/textarea/textarea.css +2 -1
  115. package/src/components/toast/index.ts +7 -0
  116. package/src/components/toast/toast.css +60 -0
  117. package/src/components/toast/toast.ts +605 -0
  118. package/src/components/toast/types.ts +169 -0
  119. package/src/index.ts +4 -0
  120. package/styles/main.css +3 -0
  121. package/styles/vars.css +138 -0
  122. package/styles.css +1 -0
  123. package/webpack.config.js +6 -1
@@ -3,10 +3,10 @@
3
3
  * Copyright 2025 by Keenthemes Inc
4
4
  */
5
5
 
6
+ import { KTSelectConfigInterface } from './config';
6
7
  import { KTSelect } from './select';
7
8
  import { defaultTemplates } from './templates';
8
9
  import {
9
- handleDropdownKeyNavigation,
10
10
  filterOptions,
11
11
  FocusManager,
12
12
  EventManager,
@@ -17,10 +17,9 @@ export class KTSelectSearch {
17
17
  private _searchInput: HTMLInputElement;
18
18
  private _noResultsElement: HTMLElement | null = null;
19
19
  private _originalOptionContents = new Map<string, string>();
20
- private _boundKeyNavHandler: (event: KeyboardEvent) => void;
21
20
  private _eventManager: EventManager;
22
21
  private _focusManager: FocusManager;
23
- private _config: import('./config').KTSelectConfigInterface;
22
+ private _config: KTSelectConfigInterface;
24
23
 
25
24
  // Public handler for search input (made public for event binding)
26
25
  public handleSearchInput: (...args: any[]) => void;
@@ -34,7 +33,6 @@ export class KTSelectSearch {
34
33
  '[data-kt-select-option]',
35
34
  select.getConfig(),
36
35
  );
37
- this._boundKeyNavHandler = this._handleKeyboardNavigation.bind(this);
38
36
  this.handleSearchInput = this._handleSearchInput.bind(this);
39
37
  this._config = select.getConfig();
40
38
  this._cacheOriginalOptionContents();
@@ -54,20 +52,27 @@ export class KTSelectSearch {
54
52
  // First remove any existing listeners to prevent duplicates
55
53
  this._removeEventListeners();
56
54
 
57
- // Add the event listener
55
+ // Add the input event listener for filtering
58
56
  this._eventManager.addListener(
59
57
  this._searchInput,
60
58
  'input',
61
59
  this.handleSearchInput,
62
60
  );
63
61
 
62
+ // Add keydown event listener for navigation, selection, and escape
63
+ this._eventManager.addListener(
64
+ this._searchInput,
65
+ 'keydown',
66
+ this._handleSearchKeyDown.bind(this)
67
+ );
68
+
64
69
  // Add blur event listener to ensure highlights are cleared when focus is lost
65
70
  this._eventManager.addListener(this._searchInput, 'blur', () => {
66
71
  // Small delay to prevent race conditions with selection
67
72
  setTimeout(() => {
68
73
  if (!this._searchInput.value) {
69
74
  this._resetAllOptions();
70
- this.clearSearchHighlights();
75
+ this.clearSearch();
71
76
  }
72
77
  }, 100);
73
78
  });
@@ -90,56 +95,50 @@ export class KTSelectSearch {
90
95
  });
91
96
  }
92
97
 
93
- // Add keyboard navigation for search results
94
- // This is stopping event propagation to prevent conflicts
95
- this._eventManager.addListener(
96
- this._searchInput,
97
- 'keydown',
98
- this._boundKeyNavHandler,
99
- );
100
-
101
- // Listen for dropdown close to reset options if search is empty
102
- this._select.getElement().addEventListener('dropdown.close', () => {
98
+ // Listen for dropdown close to reset options - ATTACH TO WRAPPER
99
+ this._select.getWrapperElement().addEventListener('dropdown.close', () => {
103
100
  this._focusManager.resetFocus();
104
- // Always clear highlights when dropdown closes
105
- this.clearSearchHighlights();
106
- if (!this._searchInput.value) {
107
- this._resetAllOptions();
108
- }
109
- // Clear the search input when dropdown closes if configured
110
- if (this._select.getConfig().clearSearchOnClose) {
111
- this._searchInput.value = '';
112
- }
101
+ // If clearSearchOnClose is false and there's a value, the search term and filtered state should persist.
102
+ // KTSelect's closeDropdown method already calls this._searchModule.clearSearch() (which clears highlights)
103
+ // and conditionally clears the input value based on KTSelect's config.clearSearchOnClose.
104
+ // This listener in search.ts seems to unconditionally clear everything.
105
+ // For now, keeping its original behavior:
106
+ this.clearSearch(); // Clears highlights from current options
107
+ this._searchInput.value = ''; // Clears the search input field
108
+ this._resetAllOptions(); // Shows all options, restores original text, removes highlights
109
+ this._clearNoResultsMessage(); // Clears any "no results" message
113
110
  });
114
111
 
115
- // Clear highlights when an option is selected
112
+ // Clear highlights when an option is selected - ATTACH TO ORIGINAL SELECT (standard 'change' event)
116
113
  this._select.getElement().addEventListener('change', () => {
117
- this.clearSearchHighlights();
114
+ this.clearSearch();
118
115
 
119
116
  // Close dropdown if configured to do so
120
- if (
121
- this._select.getConfig().closeOnSelect &&
122
- !this._select.getConfig().multiple
123
- ) {
124
- this._select.closeDropdown();
125
- }
117
+ this._select.closeDropdown();
126
118
  });
127
119
 
128
- // Autofocus on search input
129
- if (this._select.getConfig().searchAutofocus) {
130
- this._select.getElement().addEventListener('dropdown.show', () => {
131
- setTimeout(() => {
132
- // Add slight delay to ensure the dropdown and search input are visible
133
- this._searchInput?.focus();
134
- }, 50);
135
- });
136
- }
120
+ // Consolidated 'dropdown.show' event listener - ATTACH TO WRAPPER
121
+ this._select.getWrapperElement().addEventListener('dropdown.show', () => {
122
+ this._focusManager.resetFocus(); // Always clear previous focus state
123
+
124
+ if (this._searchInput?.value) {
125
+ // If there's an existing search term:
126
+ // 1. Re-filter options. This ensures the display (hidden/visible) is correct
127
+ // and "no results" message is handled if query yields nothing.
128
+ this._filterOptions(this._searchInput.value);
129
+ } else {
130
+ // If search input is empty:
131
+ // 1. Reset all options to their full, unfiltered, original state.
132
+ this._resetAllOptions(); // Shows all, clears highlights from options, restores original text
133
+ // 2. Clear any "no results" message.
134
+ this._clearNoResultsMessage();
135
+ }
137
136
 
138
- // Listen for explicit dropdown open event to clear highlights if needed
139
- this._select.getElement().addEventListener('dropdown.show', () => {
140
- // If search input is empty, ensure highlights are cleared on open
141
- if (!this._searchInput?.value) {
142
- this.clearSearchHighlights();
137
+ // Handle autofocus for the search input (this was one of the original separate listeners)
138
+ if (this._select.getConfig().searchAutofocus) {
139
+ setTimeout(() => {
140
+ this._searchInput?.focus(); // Focus search input
141
+ }, 50); // Delay to ensure dropdown is visible
143
142
  }
144
143
  });
145
144
  }
@@ -156,54 +155,44 @@ export class KTSelectSearch {
156
155
  }
157
156
 
158
157
  /**
159
- * Handle keyboard navigation for search results
158
+ * Handles keydown events on the search input for navigation and actions.
160
159
  */
161
- private _handleKeyboardNavigation(event: KeyboardEvent) {
162
- // Stop propagation to prevent multiple handlers from firing
163
- event.stopPropagation();
164
-
165
- if (this._config.debug) console.log('Search module keydown:', event.key);
166
-
167
- const visibleOptions = this._focusManager.getVisibleOptions();
168
- if (visibleOptions.length === 0) return;
169
-
170
- // Use the shared keyboard navigation handler with custom callbacks
171
- handleDropdownKeyNavigation(
172
- event,
173
- this._select,
174
- {
175
- multiple: this._select.getConfig().multiple,
176
- closeOnSelect: this._select.getConfig().closeOnSelect,
177
- },
178
- {
179
- onArrowDown: () => this._focusManager.focusNext(),
180
- onArrowUp: () => this._focusManager.focusPrevious(),
181
- onEnter: () => this._selectFocusedOption(),
182
- onClose: () => {
183
- if (event.key === 'Escape') {
184
- this.clearSearchHighlights();
160
+ private _handleSearchKeyDown(event: KeyboardEvent): void {
161
+ const key = event.key;
162
+
163
+ switch (key) {
164
+ case 'ArrowDown':
165
+ event.preventDefault();
166
+ this._focusManager.focusNext();
167
+ break;
168
+ case 'ArrowUp':
169
+ event.preventDefault();
170
+ this._focusManager.focusPrevious();
171
+ break;
172
+ case 'Enter':
173
+ event.preventDefault();
174
+ // Always attempt to select the first available option in the list.
175
+ // focusFirst() finds, focuses, and returns the first visible, non-disabled option.
176
+ const firstAvailableOption = this._focusManager.focusFirst();
177
+
178
+ if (firstAvailableOption) {
179
+ const optionValue = firstAvailableOption.getAttribute('data-value');
180
+ if (optionValue) {
181
+ this._select.toggleSelection(optionValue);
182
+ // KTSelect.toggleSelection handles closing the dropdown based on config.closeOnSelect and config.multiple
185
183
  }
186
- },
187
- },
188
- );
189
- }
190
-
191
- /**
192
- * Select the currently focused option
193
- */
194
- private _selectFocusedOption() {
195
- const focusedOption = this._focusManager.getFocusedOption();
196
-
197
- if (focusedOption) {
198
- const optionValue = focusedOption.getAttribute('data-value');
199
-
200
- if (optionValue) {
201
- // Ensure highlights are cleared before selection
202
- this.clearSearchHighlights();
203
-
204
- // Trigger the selection in the main select component
205
- this._select['_selectOption'](optionValue);
206
- }
184
+ }
185
+ break;
186
+ case 'Escape':
187
+ event.preventDefault();
188
+ this._searchInput.value = '';
189
+ this.clearSearch();
190
+ this._resetAllOptions();
191
+ this._clearNoResultsMessage();
192
+ this._focusManager.focusFirst();
193
+ break;
194
+ default:
195
+ break;
207
196
  }
208
197
  }
209
198
 
@@ -214,46 +203,71 @@ export class KTSelectSearch {
214
203
  private _cacheOriginalOptionContents() {
215
204
  // Wait for options to be initialized
216
205
  setTimeout(() => {
206
+ this._originalOptionContents.clear(); // Clear before re-caching
217
207
  const options = Array.from(this._select.getOptionsElement());
218
208
  options.forEach((option) => {
219
209
  const value = option.getAttribute('data-value');
220
210
  if (value) {
211
+ // Store the full innerHTML as the original content
221
212
  this._originalOptionContents.set(value, option.innerHTML);
222
213
  }
223
214
  });
224
215
  }, 0);
225
216
  }
226
217
 
218
+ /**
219
+ * Restores the innerHTML of all options from the cache if they have been modified.
220
+ * This is typically called before applying new filters/highlights.
221
+ */
222
+ private _restoreOptionContentsBeforeFilter(): void {
223
+ const options = Array.from(this._select.getOptionsElement()) as HTMLElement[];
224
+ options.forEach(option => {
225
+ const value = option.getAttribute('data-value');
226
+ if (value && this._originalOptionContents.has(value)) {
227
+ const originalContent = this._originalOptionContents.get(value)!;
228
+ // Only restore if current content is different, to avoid unnecessary DOM manipulation
229
+ if (option.innerHTML !== originalContent) {
230
+ option.innerHTML = originalContent;
231
+ }
232
+ }
233
+ });
234
+ }
235
+
227
236
  private _handleSearchInput(event: Event) {
228
- const query = (event.target as HTMLInputElement).value.toLowerCase();
237
+ const query = (event.target as HTMLInputElement).value;
229
238
  const config = this._select.getConfig();
230
239
 
231
240
  // Reset focused option when search changes
232
241
  this._focusManager.resetFocus();
233
242
 
234
- // If search query is empty, clear all highlights
243
+ // Restore original content for all options before filtering/highlighting again
244
+ this._restoreOptionContentsBeforeFilter();
245
+
235
246
  if (query.trim() === '') {
236
- this.clearSearchHighlights();
247
+ this._resetAllOptions();
248
+ this._focusManager.focusFirst(); // Focus first option when search is cleared
249
+ return;
237
250
  }
238
251
 
239
- // For remote search, we don't filter locally
240
- // The KTSelect component will handle the remote search
252
+ // For remote search, KTSelect component handles it.
253
+ // KTSelect will call refreshAfterSearch on this module when remote data is updated.
241
254
  if (config.remote && config.searchParam) {
242
- // If query is too short, reset all options to visible state
243
255
  if (query.length < config.searchMinLength) {
244
256
  this._resetAllOptions();
245
257
  this._clearNoResultsMessage();
258
+ this._focusManager.focusFirst(); // Focus first if query too short
246
259
  }
247
- // Otherwise, let KTSelect handle remote search
248
260
  return;
249
261
  }
250
262
 
251
263
  // For local search
252
264
  if (query.length >= config.searchMinLength) {
253
265
  this._filterOptions(query);
266
+ this._focusManager.focusFirst(); // Focus first visible option after local filtering
254
267
  } else {
255
268
  this._resetAllOptions();
256
269
  this._clearNoResultsMessage();
270
+ this._focusManager.focusFirst(); // Focus first if query too short and not remote
257
271
  }
258
272
  }
259
273
 
@@ -269,22 +283,12 @@ export class KTSelectSearch {
269
283
  this._cacheOriginalOptionContents();
270
284
  }
271
285
 
272
- // Use the shared filterOptions utility
273
- filterOptions(options, query, config, dropdownElement, (visibleCount) =>
274
- this._handleNoResults(visibleCount),
275
- );
276
-
277
- // Apply specialized text highlighting if needed
278
- if (config.searchHighlight && query.trim() !== '') {
279
- this._applyHighlightToDisplay(query);
280
- }
281
- }
286
+ // Restore original content before filtering, so highlighting is applied fresh.
287
+ this._restoreOptionContentsBeforeFilter();
282
288
 
283
- /**
284
- * Apply highlighting to the display element for multi-select
285
- */
286
- private _applyHighlightToDisplay(query: string) {
287
- // Implementation for display highlighting
289
+ const visibleCount = filterOptions(options, query, config, dropdownElement, (count) =>
290
+ this._handleNoResults(count),
291
+ );
288
292
  }
289
293
 
290
294
  /**
@@ -296,44 +300,31 @@ export class KTSelectSearch {
296
300
  this._select.getOptionsElement(),
297
301
  ) as HTMLElement[];
298
302
 
299
- // Cache original option HTML if not already cached
303
+ // Ensure the cache is populated if it's somehow empty here
300
304
  if (this._originalOptionContents.size === 0) {
301
305
  this._cacheOriginalOptionContents();
302
306
  }
303
307
 
304
308
  options.forEach((option) => {
305
- // Remove the hidden class
306
309
  option.classList.remove('hidden');
310
+ if (option.style.display === 'none') option.style.display = ''; // Ensure visible
307
311
 
308
312
  // Restore original HTML content (remove highlights)
309
313
  const value = option.getAttribute('data-value');
310
314
  if (value && this._originalOptionContents.has(value)) {
311
- option.innerHTML = this._originalOptionContents.get(value);
312
- }
313
-
314
- // Remove any display styling
315
- if (
316
- option.hasAttribute('style') &&
317
- option.getAttribute('style').includes('display:')
318
- ) {
319
- const styleAttr = option.getAttribute('style');
320
- if (
321
- styleAttr.trim() === 'display: none;' ||
322
- styleAttr.trim() === 'display: block;'
323
- ) {
324
- option.removeAttribute('style');
325
- } else {
326
- option.setAttribute(
327
- 'style',
328
- styleAttr.replace(/display:\s*[^;]+;?/gi, '').trim(),
329
- );
315
+ const originalContent = this._originalOptionContents.get(value)!;
316
+ // Only update if different, to minimize DOM changes
317
+ if (option.innerHTML !== originalContent) {
318
+ option.innerHTML = originalContent;
330
319
  }
331
320
  }
332
321
  });
322
+
323
+ this._clearNoResultsMessage(); // Ensure no results message is cleared when resetting
333
324
  }
334
325
 
335
326
  private _handleNoResults(visibleOptionsCount: number) {
336
- if (visibleOptionsCount === 0 && this._searchInput.value.trim() !== '') {
327
+ if (visibleOptionsCount === 0 && this._searchInput?.value?.trim() !== '') {
337
328
  this._showNoResultsMessage();
338
329
  } else {
339
330
  this._clearNoResultsMessage();
@@ -344,11 +335,11 @@ export class KTSelectSearch {
344
335
  this._clearNoResultsMessage();
345
336
 
346
337
  const config = this._select.getConfig();
347
- this._noResultsElement = defaultTemplates.noResults(config);
338
+ this._noResultsElement = defaultTemplates.empty(config);
348
339
 
349
340
  const dropdownElement = this._select.getDropdownElement();
350
341
  const optionsContainer = dropdownElement.querySelector(
351
- '[data-kt-select-options-container]',
342
+ '[data-kt-select-options]',
352
343
  );
353
344
  if (optionsContainer) {
354
345
  optionsContainer.appendChild(this._noResultsElement);
@@ -368,28 +359,27 @@ export class KTSelectSearch {
368
359
  * Public method to explicitly clear all search highlights
369
360
  * This is called when search is reset or selection changes
370
361
  */
371
- public clearSearchHighlights() {
362
+ public clearSearch() {
372
363
  // Restore original option content (removes highlighting)
373
- const options = Array.from(
364
+ const optionsToClear = Array.from(
374
365
  this._select.getOptionsElement(),
375
366
  ) as HTMLElement[];
376
367
 
377
- options.forEach((option) => {
368
+ // Ensure cache is available
369
+ if (this._originalOptionContents.size === 0 && optionsToClear.length > 0) {
370
+ this._cacheOriginalOptionContents();
371
+ }
372
+
373
+ optionsToClear.forEach((option) => {
378
374
  const value = option.getAttribute('data-value');
379
375
  if (value && this._originalOptionContents.has(value)) {
380
- option.innerHTML = this._originalOptionContents.get(value);
376
+ const originalContent = this._originalOptionContents.get(value)!;
377
+ // Only restore if different
378
+ if (option.innerHTML !== originalContent) {
379
+ option.innerHTML = originalContent;
380
+ }
381
381
  }
382
382
  });
383
-
384
- // Also clear highlights from the display element
385
- this._clearDisplayHighlights();
386
- }
387
-
388
- /**
389
- * Clear any highlights from the display element (selected values)
390
- */
391
- private _clearDisplayHighlights() {
392
- // Implementation for clearing display highlights
393
383
  }
394
384
 
395
385
  /**
@@ -398,11 +388,11 @@ export class KTSelectSearch {
398
388
  public refreshOptionCache(): void {
399
389
  // Re-cache all option contents
400
390
  this._originalOptionContents.clear();
401
- const options = Array.from(
391
+ const currentOptions = Array.from(
402
392
  this._select.getOptionsElement(),
403
393
  ) as HTMLElement[];
404
394
 
405
- options.forEach((option) => {
395
+ currentOptions.forEach((option) => {
406
396
  const value = option.getAttribute('data-value');
407
397
  if (value) {
408
398
  this._originalOptionContents.set(value, option.innerHTML);
@@ -410,6 +400,16 @@ export class KTSelectSearch {
410
400
  });
411
401
  }
412
402
 
403
+ /**
404
+ * Called after search (local or remote via KTSelect) to reset focus.
405
+ */
406
+ public refreshAfterSearch(): void {
407
+ this._focusManager.resetFocus();
408
+ this._focusManager.focusFirst();
409
+ // Re-cache original contents as options might have changed (especially after remote search)
410
+ this.refreshOptionCache();
411
+ }
412
+
413
413
  /**
414
414
  * Clean up all resources used by the search module
415
415
  */
@@ -418,13 +418,14 @@ export class KTSelectSearch {
418
418
  this._removeEventListeners();
419
419
 
420
420
  // Clear all references
421
- this._focusManager.dispose();
422
- this._eventManager.removeAllListeners(null);
421
+ if (this._focusManager) {
422
+ this._focusManager.dispose();
423
+ }
423
424
 
424
425
  // Clear cached content
425
426
  this._originalOptionContents.clear();
426
427
 
427
428
  // Clear highlight elements
428
- this.clearSearchHighlights();
429
+ this.clearSearch();
429
430
  }
430
431
  }