@keenthemes/ktui 1.0.12 → 1.0.14

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 (93) hide show
  1. package/dist/ktui.js +738 -700
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +5824 -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 +16 -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 +14 -11
  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 +13 -8
  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 +219 -118
  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 -105
  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 +14 -11
  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 +13 -8
  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 +219 -118
  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 -105
  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 +14 -11
  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 +98 -87
  75. package/src/components/select/config.ts +16 -13
  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 +99 -27
  80. package/src/components/select/select.ts +236 -128
  81. package/src/components/select/tags.ts +1 -27
  82. package/src/components/select/templates.ts +191 -132
  83. package/src/components/select/utils.ts +30 -166
  84. package/src/components/toast/toast.css +1 -1
  85. package/src/helpers/dom.ts +0 -30
  86. package/webpack.config.js +6 -1
  87. package/examples/select/combobox-icons.html +0 -58
  88. package/examples/select/icon-description.html +0 -56
  89. /package/examples/select/{combobox.html → combobox_.html} +0 -0
  90. /package/examples/select/{remote-data.html → remote-data_.html} +0 -0
  91. /package/examples/select/{tags-icons.html → tags-icons_.html} +0 -0
  92. /package/examples/select/{tags-selected.html → tags-selected_.html} +0 -0
  93. /package/examples/select/{tags.html → tags_.html} +0 -0
@@ -12,53 +12,72 @@ import { defaultTemplates } from './templates';
12
12
  export class KTSelectOption extends KTComponent {
13
13
  protected override readonly _name: string = 'select-option';
14
14
  protected override readonly _dataOptionPrefix: string = 'kt-'; // Use 'kt-' prefix to support data-kt-select-option attributes
15
- protected override readonly _config: KTSelectConfigInterface;
16
- private _globalConfig: KTSelectConfigInterface;
15
+ protected override readonly _config: KTSelectConfigInterface; // Holds option-specific data from data-kt-*
16
+ private _globalConfig: KTSelectConfigInterface; // Main select's config
17
17
 
18
18
  constructor(element: HTMLElement, config?: KTSelectConfigInterface,) {
19
19
  super();
20
20
 
21
21
  // Always initialize a new option instance
22
22
  this._init(element);
23
- this._buildConfig();
24
23
  this._globalConfig = config;
24
+ this._buildConfig();
25
25
 
26
26
  // Clean the config
27
27
  this._config = (this._config as any)[''] || {};
28
28
 
29
29
  // Add the option config to the global config
30
- this._globalConfig.optionsConfig = this._globalConfig.optionsConfig || {};
31
- this._globalConfig.optionsConfig[(element as HTMLInputElement).value] = this._config;
30
+ // Ensure optionsConfig is initialized
31
+ if (this._globalConfig) {
32
+ this._globalConfig.optionsConfig = this._globalConfig.optionsConfig || {};
33
+ this._globalConfig.optionsConfig[(element as HTMLInputElement).value] = this._config;
34
+ } else {
35
+ // Handle case where _globalConfig might be undefined, though constructor expects it.
36
+ // This might indicate a need to ensure config is always passed or has a default.
37
+ console.warn('KTSelectOption: _globalConfig is undefined during constructor.');
38
+ }
32
39
 
33
40
  // Don't store in KTData to avoid Singleton pattern issues
34
41
  // Each option should be a unique instance
35
42
  (element as any).instance = this;
36
43
  }
37
44
 
38
- public getHTMLOptionElement(): HTMLOptionElement {
39
- return this._element as HTMLOptionElement;
45
+ public get id(): string {
46
+ return this.getHTMLOptionElement().value;
40
47
  }
41
48
 
42
- public render(): HTMLElement {
43
- const optionElement = this.getHTMLOptionElement();
44
-
45
- // Get the original template
46
- let originalTemplate = this._globalConfig.optionTemplate;
47
-
48
- if (this._globalConfig.optionTemplate) {
49
- // Replace all {{varname}} in option.innerHTML with values from _config
50
- Object.entries((this._config as any) || {}).forEach(([key, value]) => {
51
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
52
- this._globalConfig.optionTemplate = this._globalConfig.optionTemplate.replace(new RegExp(`{{${key}}}`, 'g'), String(value));
53
- }
54
- });
55
- }
49
+ public get title(): string {
50
+ return this.getHTMLOptionElement().textContent || '';
51
+ }
56
52
 
57
- let template = defaultTemplates.option(optionElement, this._globalConfig);
53
+ public getHTMLOptionElement(): HTMLOptionElement {
54
+ return this._element as HTMLOptionElement;
55
+ }
58
56
 
59
- // Restore the original template
60
- this._globalConfig.optionTemplate = originalTemplate;
57
+ /**
58
+ * Gathers all necessary data for rendering this option,
59
+ * including standard HTML attributes and custom data-kt-* attributes.
60
+ */
61
+ public getOptionDataForTemplate(): Record<string, any> {
62
+ const el = this.getHTMLOptionElement();
63
+ const text = el.textContent || '';
64
+ return {
65
+ // Custom data from data-kt-select-option attributes (parsed into this._config)
66
+ ...this._config,
67
+ // Standard HTMLOptionElement properties
68
+ value: el.value,
69
+ text: text, // Original text
70
+ selected: el.selected,
71
+ disabled: el.disabled,
72
+ // Provide 'content' for convenience in templates, defaulting to text.
73
+ // User's optionTemplate can then use {{content}} or specific fields like {{text}}, {{icon}}, etc.
74
+ content: text,
75
+ };
76
+ }
61
77
 
62
- return template;
78
+ public render(): HTMLElement {
79
+ // 'this' is the KTSelectOption instance.
80
+ // defaultTemplates.option will handle using this instance's data along with _globalConfig.
81
+ return defaultTemplates.option(this, this._globalConfig);
63
82
  }
64
83
  }
@@ -3,6 +3,7 @@
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 {
@@ -18,7 +19,7 @@ export class KTSelectSearch {
18
19
  private _originalOptionContents = new Map<string, string>();
19
20
  private _eventManager: EventManager;
20
21
  private _focusManager: FocusManager;
21
- private _config: import('./config').KTSelectConfigInterface;
22
+ private _config: KTSelectConfigInterface;
22
23
 
23
24
  // Public handler for search input (made public for event binding)
24
25
  public handleSearchInput: (...args: any[]) => void;
@@ -51,20 +52,27 @@ export class KTSelectSearch {
51
52
  // First remove any existing listeners to prevent duplicates
52
53
  this._removeEventListeners();
53
54
 
54
- // Add the event listener
55
+ // Add the input event listener for filtering
55
56
  this._eventManager.addListener(
56
57
  this._searchInput,
57
58
  'input',
58
59
  this.handleSearchInput,
59
60
  );
60
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
+
61
69
  // Add blur event listener to ensure highlights are cleared when focus is lost
62
70
  this._eventManager.addListener(this._searchInput, 'blur', () => {
63
71
  // Small delay to prevent race conditions with selection
64
72
  setTimeout(() => {
65
73
  if (!this._searchInput.value) {
66
74
  this._resetAllOptions();
67
- this.clearSearchHighlights();
75
+ this.clearSearch();
68
76
  }
69
77
  }, 100);
70
78
  });
@@ -87,43 +95,50 @@ export class KTSelectSearch {
87
95
  });
88
96
  }
89
97
 
90
- // Listen for dropdown close to reset options if search is empty
91
- this._select.getElement().addEventListener('dropdown.close', () => {
98
+ // Listen for dropdown close to reset options - ATTACH TO WRAPPER
99
+ this._select.getWrapperElement().addEventListener('dropdown.close', () => {
92
100
  this._focusManager.resetFocus();
93
- this.clearSearchHighlights();
94
- this._searchInput.value = '';
95
- this._resetAllOptions();
96
- this._clearNoResultsMessage();
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
97
110
  });
98
111
 
99
- // Clear highlights when an option is selected
112
+ // Clear highlights when an option is selected - ATTACH TO ORIGINAL SELECT (standard 'change' event)
100
113
  this._select.getElement().addEventListener('change', () => {
101
- this.clearSearchHighlights();
114
+ this.clearSearch();
102
115
 
103
116
  // Close dropdown if configured to do so
104
- if (
105
- this._select.getConfig().closeOnSelect &&
106
- !this._select.getConfig().multiple
107
- ) {
108
- this._select.closeDropdown();
109
- }
117
+ this._select.closeDropdown();
110
118
  });
111
119
 
112
- // Autofocus on search input
113
- if (this._select.getConfig().searchAutofocus) {
114
- this._select.getElement().addEventListener('dropdown.show', () => {
115
- setTimeout(() => {
116
- // Add slight delay to ensure the dropdown and search input are visible
117
- this._searchInput?.focus();
118
- }, 50);
119
- });
120
- }
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
+ }
121
136
 
122
- // Listen for explicit dropdown open event to clear highlights if needed
123
- this._select.getElement().addEventListener('dropdown.show', () => {
124
- // If search input is empty, ensure highlights are cleared on open
125
- if (!this._searchInput?.value) {
126
- 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
127
142
  }
128
143
  });
129
144
  }
@@ -140,21 +155,44 @@ export class KTSelectSearch {
140
155
  }
141
156
 
142
157
  /**
143
- * Select the currently focused option
158
+ * Handles keydown events on the search input for navigation and actions.
144
159
  */
145
- private _selectFocusedOption() {
146
- const focusedOption = this._focusManager.getFocusedOption();
147
-
148
- if (focusedOption) {
149
- const optionValue = focusedOption.getAttribute('data-value');
150
-
151
- if (optionValue) {
152
- // Ensure highlights are cleared before selection
153
- this.clearSearchHighlights();
154
-
155
- // Trigger the selection in the main select component
156
- this._select['_selectOption'](optionValue);
157
- }
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
183
+ }
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;
158
196
  }
159
197
  }
160
198
 
@@ -165,46 +203,71 @@ export class KTSelectSearch {
165
203
  private _cacheOriginalOptionContents() {
166
204
  // Wait for options to be initialized
167
205
  setTimeout(() => {
206
+ this._originalOptionContents.clear(); // Clear before re-caching
168
207
  const options = Array.from(this._select.getOptionsElement());
169
208
  options.forEach((option) => {
170
209
  const value = option.getAttribute('data-value');
171
210
  if (value) {
211
+ // Store the full innerHTML as the original content
172
212
  this._originalOptionContents.set(value, option.innerHTML);
173
213
  }
174
214
  });
175
215
  }, 0);
176
216
  }
177
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
+
178
236
  private _handleSearchInput(event: Event) {
179
- const query = (event.target as HTMLInputElement).value.toLowerCase();
237
+ const query = (event.target as HTMLInputElement).value;
180
238
  const config = this._select.getConfig();
181
239
 
182
240
  // Reset focused option when search changes
183
241
  this._focusManager.resetFocus();
184
242
 
185
- // If search query is empty, clear all highlights
243
+ // Restore original content for all options before filtering/highlighting again
244
+ this._restoreOptionContentsBeforeFilter();
245
+
186
246
  if (query.trim() === '') {
187
- this.clearSearchHighlights();
247
+ this._resetAllOptions();
248
+ this._focusManager.focusFirst(); // Focus first option when search is cleared
249
+ return;
188
250
  }
189
251
 
190
- // For remote search, we don't filter locally
191
- // 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.
192
254
  if (config.remote && config.searchParam) {
193
- // If query is too short, reset all options to visible state
194
255
  if (query.length < config.searchMinLength) {
195
256
  this._resetAllOptions();
196
257
  this._clearNoResultsMessage();
258
+ this._focusManager.focusFirst(); // Focus first if query too short
197
259
  }
198
- // Otherwise, let KTSelect handle remote search
199
260
  return;
200
261
  }
201
262
 
202
263
  // For local search
203
264
  if (query.length >= config.searchMinLength) {
204
265
  this._filterOptions(query);
266
+ this._focusManager.focusFirst(); // Focus first visible option after local filtering
205
267
  } else {
206
268
  this._resetAllOptions();
207
269
  this._clearNoResultsMessage();
270
+ this._focusManager.focusFirst(); // Focus first if query too short and not remote
208
271
  }
209
272
  }
210
273
 
@@ -220,22 +283,12 @@ export class KTSelectSearch {
220
283
  this._cacheOriginalOptionContents();
221
284
  }
222
285
 
223
- // Use the shared filterOptions utility
224
- filterOptions(options, query, config, dropdownElement, (visibleCount) =>
225
- this._handleNoResults(visibleCount),
226
- );
227
-
228
- // Apply specialized text highlighting if needed
229
- if (config.searchHighlight && query.trim() !== '') {
230
- this._applyHighlightToDisplay(query);
231
- }
232
- }
286
+ // Restore original content before filtering, so highlighting is applied fresh.
287
+ this._restoreOptionContentsBeforeFilter();
233
288
 
234
- /**
235
- * Apply highlighting to the display element for multi-select
236
- */
237
- private _applyHighlightToDisplay(query: string) {
238
- // Implementation for display highlighting
289
+ const visibleCount = filterOptions(options, query, config, dropdownElement, (count) =>
290
+ this._handleNoResults(count),
291
+ );
239
292
  }
240
293
 
241
294
  /**
@@ -247,44 +300,31 @@ export class KTSelectSearch {
247
300
  this._select.getOptionsElement(),
248
301
  ) as HTMLElement[];
249
302
 
250
- // Cache original option HTML if not already cached
303
+ // Ensure the cache is populated if it's somehow empty here
251
304
  if (this._originalOptionContents.size === 0) {
252
305
  this._cacheOriginalOptionContents();
253
306
  }
254
307
 
255
308
  options.forEach((option) => {
256
- // Remove the hidden class
257
309
  option.classList.remove('hidden');
310
+ if (option.style.display === 'none') option.style.display = ''; // Ensure visible
258
311
 
259
312
  // Restore original HTML content (remove highlights)
260
313
  const value = option.getAttribute('data-value');
261
314
  if (value && this._originalOptionContents.has(value)) {
262
- option.innerHTML = this._originalOptionContents.get(value);
263
- }
264
-
265
- // Remove any display styling
266
- if (
267
- option.hasAttribute('style') &&
268
- option.getAttribute('style').includes('display:')
269
- ) {
270
- const styleAttr = option.getAttribute('style');
271
- if (
272
- styleAttr.trim() === 'display: none;' ||
273
- styleAttr.trim() === 'display: block;'
274
- ) {
275
- option.removeAttribute('style');
276
- } else {
277
- option.setAttribute(
278
- 'style',
279
- styleAttr.replace(/display:\s*[^;]+;?/gi, '').trim(),
280
- );
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;
281
319
  }
282
320
  }
283
321
  });
322
+
323
+ this._clearNoResultsMessage(); // Ensure no results message is cleared when resetting
284
324
  }
285
325
 
286
326
  private _handleNoResults(visibleOptionsCount: number) {
287
- if (visibleOptionsCount === 0 && this._searchInput.value.trim() !== '') {
327
+ if (visibleOptionsCount === 0 && this._searchInput?.value?.trim() !== '') {
288
328
  this._showNoResultsMessage();
289
329
  } else {
290
330
  this._clearNoResultsMessage();
@@ -319,35 +359,25 @@ export class KTSelectSearch {
319
359
  * Public method to explicitly clear all search highlights
320
360
  * This is called when search is reset or selection changes
321
361
  */
322
- public clearSearchHighlights() {
362
+ public clearSearch() {
323
363
  // Restore original option content (removes highlighting)
324
- const options = Array.from(
364
+ const optionsToClear = Array.from(
325
365
  this._select.getOptionsElement(),
326
366
  ) as HTMLElement[];
327
367
 
328
- 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) => {
329
374
  const value = option.getAttribute('data-value');
330
375
  if (value && this._originalOptionContents.has(value)) {
331
- option.innerHTML = this._originalOptionContents.get(value);
332
- }
333
- });
334
-
335
- // Also clear highlights from the display element
336
- this._clearDisplayHighlights();
337
- }
338
-
339
- /**
340
- * Clear any highlights from the display element (selected values)
341
- */
342
- private _clearDisplayHighlights() {
343
- // Implementation for clearing display highlights
344
- const options = Array.from(
345
- this._select.getOptionsElement(),
346
- ) as HTMLElement[];
347
-
348
- options.forEach((option) => {
349
- if (option.dataset && !option.dataset.originalText) {
350
- option.dataset.originalText = option.innerHTML;
376
+ const originalContent = this._originalOptionContents.get(value)!;
377
+ // Only restore if different
378
+ if (option.innerHTML !== originalContent) {
379
+ option.innerHTML = originalContent;
380
+ }
351
381
  }
352
382
  });
353
383
  }
@@ -358,11 +388,11 @@ export class KTSelectSearch {
358
388
  public refreshOptionCache(): void {
359
389
  // Re-cache all option contents
360
390
  this._originalOptionContents.clear();
361
- const options = Array.from(
391
+ const currentOptions = Array.from(
362
392
  this._select.getOptionsElement(),
363
393
  ) as HTMLElement[];
364
394
 
365
- options.forEach((option) => {
395
+ currentOptions.forEach((option) => {
366
396
  const value = option.getAttribute('data-value');
367
397
  if (value) {
368
398
  this._originalOptionContents.set(value, option.innerHTML);
@@ -370,6 +400,16 @@ export class KTSelectSearch {
370
400
  });
371
401
  }
372
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
+
373
413
  /**
374
414
  * Clean up all resources used by the search module
375
415
  */
@@ -378,13 +418,14 @@ export class KTSelectSearch {
378
418
  this._removeEventListeners();
379
419
 
380
420
  // Clear all references
381
- this._focusManager.dispose();
382
- this._eventManager.removeAllListeners(null);
421
+ if (this._focusManager) {
422
+ this._focusManager.dispose();
423
+ }
383
424
 
384
425
  // Clear cached content
385
426
  this._originalOptionContents.clear();
386
427
 
387
428
  // Clear highlight elements
388
- this.clearSearchHighlights();
429
+ this.clearSearch();
389
430
  }
390
431
  }