@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.
- package/dist/ktui.js +738 -700
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +5824 -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 +16 -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 +14 -11
- 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 +13 -8
- 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 +219 -118
- 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 -105
- 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 +14 -11
- 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 +13 -8
- 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 +219 -118
- 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 -105
- 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 +14 -11
- package/src/components/input/input.css +1 -1
- package/src/components/scrollable/scrollable.css +9 -5
- package/src/components/select/combobox.ts +98 -87
- package/src/components/select/config.ts +16 -13
- 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 +99 -27
- package/src/components/select/select.ts +236 -128
- package/src/components/select/tags.ts +1 -27
- package/src/components/select/templates.ts +191 -132
- package/src/components/select/utils.ts +30 -166
- package/src/components/toast/toast.css +1 -1
- 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
|
@@ -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
|
-
|
|
31
|
-
this._globalConfig
|
|
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
|
|
39
|
-
return this.
|
|
45
|
+
public get id(): string {
|
|
46
|
+
return this.getHTMLOptionElement().value;
|
|
40
47
|
}
|
|
41
48
|
|
|
42
|
-
public
|
|
43
|
-
|
|
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
|
-
|
|
53
|
+
public getHTMLOptionElement(): HTMLOptionElement {
|
|
54
|
+
return this._element as HTMLOptionElement;
|
|
55
|
+
}
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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
|
|
91
|
-
this._select.
|
|
98
|
+
// Listen for dropdown close to reset options - ATTACH TO WRAPPER
|
|
99
|
+
this._select.getWrapperElement().addEventListener('dropdown.close', () => {
|
|
92
100
|
this._focusManager.resetFocus();
|
|
93
|
-
|
|
94
|
-
this.
|
|
95
|
-
|
|
96
|
-
|
|
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.
|
|
114
|
+
this.clearSearch();
|
|
102
115
|
|
|
103
116
|
// Close dropdown if configured to do so
|
|
104
|
-
|
|
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
|
-
//
|
|
113
|
-
|
|
114
|
-
this.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
*
|
|
158
|
+
* Handles keydown events on the search input for navigation and actions.
|
|
144
159
|
*/
|
|
145
|
-
private
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
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
|
-
//
|
|
243
|
+
// Restore original content for all options before filtering/highlighting again
|
|
244
|
+
this._restoreOptionContentsBeforeFilter();
|
|
245
|
+
|
|
186
246
|
if (query.trim() === '') {
|
|
187
|
-
this.
|
|
247
|
+
this._resetAllOptions();
|
|
248
|
+
this._focusManager.focusFirst(); // Focus first option when search is cleared
|
|
249
|
+
return;
|
|
188
250
|
}
|
|
189
251
|
|
|
190
|
-
// For remote search,
|
|
191
|
-
//
|
|
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
|
-
//
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
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
|
|
362
|
+
public clearSearch() {
|
|
323
363
|
// Restore original option content (removes highlighting)
|
|
324
|
-
const
|
|
364
|
+
const optionsToClear = Array.from(
|
|
325
365
|
this._select.getOptionsElement(),
|
|
326
366
|
) as HTMLElement[];
|
|
327
367
|
|
|
328
|
-
|
|
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
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
391
|
+
const currentOptions = Array.from(
|
|
362
392
|
this._select.getOptionsElement(),
|
|
363
393
|
) as HTMLElement[];
|
|
364
394
|
|
|
365
|
-
|
|
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
|
|
382
|
-
|
|
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.
|
|
429
|
+
this.clearSearch();
|
|
389
430
|
}
|
|
390
431
|
}
|