@keenthemes/ktui 1.1.1 → 1.1.3
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 +674 -225
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +13 -1
- package/lib/cjs/components/component.js +22 -0
- package/lib/cjs/components/component.js.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +7 -1
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/drawer/drawer.js +255 -9
- package/lib/cjs/components/drawer/drawer.js.map +1 -1
- package/lib/cjs/components/dropdown/dropdown.js +55 -8
- package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
- package/lib/cjs/components/select/combobox.js +0 -2
- package/lib/cjs/components/select/combobox.js.map +1 -1
- package/lib/cjs/components/select/config.js +4 -1
- package/lib/cjs/components/select/config.js.map +1 -1
- package/lib/cjs/components/select/dropdown.js +0 -16
- package/lib/cjs/components/select/dropdown.js.map +1 -1
- package/lib/cjs/components/select/remote.js +0 -40
- package/lib/cjs/components/select/remote.js.map +1 -1
- package/lib/cjs/components/select/search.js +93 -22
- package/lib/cjs/components/select/search.js.map +1 -1
- package/lib/cjs/components/select/select.js +180 -114
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.js +0 -2
- package/lib/cjs/components/select/tags.js.map +1 -1
- package/lib/cjs/components/sticky/sticky.js +44 -5
- package/lib/cjs/components/sticky/sticky.js.map +1 -1
- package/lib/cjs/helpers/data.js +8 -0
- package/lib/cjs/helpers/data.js.map +1 -1
- package/lib/cjs/helpers/event-handler.js +6 -5
- package/lib/cjs/helpers/event-handler.js.map +1 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/component.js +22 -0
- package/lib/esm/components/component.js.map +1 -1
- package/lib/esm/components/datatable/datatable.js +7 -1
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/drawer/drawer.js +255 -9
- package/lib/esm/components/drawer/drawer.js.map +1 -1
- package/lib/esm/components/dropdown/dropdown.js +55 -8
- package/lib/esm/components/dropdown/dropdown.js.map +1 -1
- package/lib/esm/components/select/combobox.js +0 -2
- package/lib/esm/components/select/combobox.js.map +1 -1
- package/lib/esm/components/select/config.js +4 -1
- package/lib/esm/components/select/config.js.map +1 -1
- package/lib/esm/components/select/dropdown.js +0 -16
- package/lib/esm/components/select/dropdown.js.map +1 -1
- package/lib/esm/components/select/remote.js +0 -40
- package/lib/esm/components/select/remote.js.map +1 -1
- package/lib/esm/components/select/search.js +93 -22
- package/lib/esm/components/select/search.js.map +1 -1
- package/lib/esm/components/select/select.js +180 -114
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.js +0 -2
- package/lib/esm/components/select/tags.js.map +1 -1
- package/lib/esm/components/sticky/sticky.js +44 -5
- package/lib/esm/components/sticky/sticky.js.map +1 -1
- package/lib/esm/helpers/data.js +8 -0
- package/lib/esm/helpers/data.js.map +1 -1
- package/lib/esm/helpers/event-handler.js +6 -5
- package/lib/esm/helpers/event-handler.js.map +1 -1
- package/lib/esm/index.js.map +1 -1
- package/package.json +6 -4
- package/src/components/component.ts +26 -0
- package/src/components/datatable/__tests__/race-conditions.test.ts +7 -7
- package/src/components/datatable/datatable.ts +8 -1
- package/src/components/drawer/drawer.ts +266 -10
- package/src/components/dropdown/dropdown.ts +63 -8
- package/src/components/select/__tests__/ux-behaviors.test.ts +997 -0
- package/src/components/select/combobox.ts +0 -1
- package/src/components/select/config.ts +7 -1
- package/src/components/select/dropdown.ts +0 -24
- package/src/components/select/remote.ts +0 -49
- package/src/components/select/search.ts +97 -24
- package/src/components/select/select.css +5 -1
- package/src/components/select/select.ts +211 -153
- package/src/components/select/tags.ts +0 -1
- package/src/components/sticky/sticky.ts +55 -5
- package/src/helpers/data.ts +10 -0
- package/src/helpers/event-handler.ts +7 -6
- package/src/index.ts +2 -0
|
@@ -52,7 +52,8 @@ export const DefaultConfig: KTSelectConfigInterface = {
|
|
|
52
52
|
searchMinLength: 0, // Minimum characters required to trigger search
|
|
53
53
|
searchMaxItems: 50, // Maximum number of search results to display
|
|
54
54
|
searchEmpty: 'No results', // Text to display when no search results are found
|
|
55
|
-
clearSearchOnClose:
|
|
55
|
+
clearSearchOnClose: false, // Clear search input when dropdown closes (default: false to persist search text)
|
|
56
|
+
closeOnEnter: true, // Close dropdown when Enter is pressed in search input
|
|
56
57
|
|
|
57
58
|
// Multi-Select Display
|
|
58
59
|
selectAllText: 'Select all', // Text for the "Select All" option (if implemented)
|
|
@@ -73,6 +74,8 @@ export const DefaultConfig: KTSelectConfigInterface = {
|
|
|
73
74
|
dropdownPreventOverflow: false,
|
|
74
75
|
dropdownStrategy: null,
|
|
75
76
|
dropdownWidth: null, // Custom width for dropdown (e.g., '300px'), null to match toggle element width
|
|
77
|
+
closeOnOtherOpen: true, // Close other open dropdowns when this one opens
|
|
78
|
+
dispatchGlobalEvents: true, // Dispatch events on document for global listeners (jQuery compatibility)
|
|
76
79
|
|
|
77
80
|
// New Config
|
|
78
81
|
dropdownTemplate: '',
|
|
@@ -102,6 +105,7 @@ export interface KTSelectConfigInterface {
|
|
|
102
105
|
searchDebounce?: number;
|
|
103
106
|
searchParam?: string;
|
|
104
107
|
clearSearchOnClose?: boolean;
|
|
108
|
+
closeOnEnter?: boolean;
|
|
105
109
|
|
|
106
110
|
// Multi-Select Display
|
|
107
111
|
selectAllText?: string;
|
|
@@ -144,6 +148,8 @@ export interface KTSelectConfigInterface {
|
|
|
144
148
|
dropdownPreventOverflow?: boolean;
|
|
145
149
|
dropdownStrategy?: 'fixed' | 'absolute';
|
|
146
150
|
dropdownWidth?: string | null; // Custom width for dropdown, null to match toggle element width
|
|
151
|
+
closeOnOtherOpen?: boolean;
|
|
152
|
+
dispatchGlobalEvents?: boolean;
|
|
147
153
|
|
|
148
154
|
// Styling
|
|
149
155
|
dropdownClass?: string;
|
|
@@ -113,8 +113,6 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
113
113
|
event.stopPropagation();
|
|
114
114
|
|
|
115
115
|
if (this._config.disabled) {
|
|
116
|
-
if (this._config.debug)
|
|
117
|
-
console.log('KTSelectDropdown._handleToggleClick: select is disabled');
|
|
118
116
|
return;
|
|
119
117
|
}
|
|
120
118
|
|
|
@@ -313,8 +311,6 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
313
311
|
*/
|
|
314
312
|
public open(): void {
|
|
315
313
|
if (this._config.disabled) {
|
|
316
|
-
if (this._config.debug)
|
|
317
|
-
console.log('KTSelectDropdown.open: select is disabled, not opening');
|
|
318
314
|
return;
|
|
319
315
|
}
|
|
320
316
|
if (this._isOpen || this._isTransitioning) return;
|
|
@@ -390,26 +386,12 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
390
386
|
* Close the dropdown
|
|
391
387
|
*/
|
|
392
388
|
public close(): void {
|
|
393
|
-
if (this._config.debug)
|
|
394
|
-
console.log(
|
|
395
|
-
'KTSelectDropdown.close called - isOpen:',
|
|
396
|
-
this._isOpen,
|
|
397
|
-
'isTransitioning:',
|
|
398
|
-
this._isTransitioning,
|
|
399
|
-
);
|
|
400
|
-
|
|
401
389
|
if (!this._isOpen || this._isTransitioning) {
|
|
402
|
-
if (this._config.debug)
|
|
403
|
-
console.log(
|
|
404
|
-
'KTSelectDropdown.close - early return: dropdown not open or is transitioning',
|
|
405
|
-
);
|
|
406
390
|
return;
|
|
407
391
|
}
|
|
408
392
|
|
|
409
393
|
// Events and ARIA will be handled by KTSelect
|
|
410
394
|
|
|
411
|
-
if (this._config.debug)
|
|
412
|
-
console.log('KTSelectDropdown.close - starting transition');
|
|
413
395
|
this._isTransitioning = true;
|
|
414
396
|
|
|
415
397
|
this._dropdownElement.style.opacity = '0';
|
|
@@ -417,8 +399,6 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
417
399
|
let transitionComplete = false;
|
|
418
400
|
const fallbackTimer = setTimeout(() => {
|
|
419
401
|
if (!transitionComplete) {
|
|
420
|
-
if (this._config.debug)
|
|
421
|
-
console.log('KTSelectDropdown.close - fallback timer triggered');
|
|
422
402
|
completeTransition();
|
|
423
403
|
}
|
|
424
404
|
}, 300);
|
|
@@ -428,8 +408,6 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
428
408
|
transitionComplete = true;
|
|
429
409
|
clearTimeout(fallbackTimer);
|
|
430
410
|
|
|
431
|
-
if (this._config.debug)
|
|
432
|
-
console.log('KTSelectDropdown.close - transition ended');
|
|
433
411
|
|
|
434
412
|
this._dropdownElement.classList.add('hidden');
|
|
435
413
|
this._dropdownElement.classList.remove('open');
|
|
@@ -443,8 +421,6 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
443
421
|
|
|
444
422
|
// Events will be handled by KTSelect
|
|
445
423
|
|
|
446
|
-
if (this._config.debug)
|
|
447
|
-
console.log('KTSelectDropdown.close - visual part complete');
|
|
448
424
|
};
|
|
449
425
|
|
|
450
426
|
KTDom.transitionEnd(this._dropdownElement, completeTransition);
|
|
@@ -50,8 +50,6 @@ export class KTSelectRemote {
|
|
|
50
50
|
|
|
51
51
|
let url = this._buildUrl(query, page);
|
|
52
52
|
|
|
53
|
-
if (this._config.debug) console.log('Fetching remote data from:', url);
|
|
54
|
-
|
|
55
53
|
// Dispatch search start event
|
|
56
54
|
this._dispatchEvent('remoteSearchStart');
|
|
57
55
|
|
|
@@ -148,32 +146,21 @@ export class KTSelectRemote {
|
|
|
148
146
|
*/
|
|
149
147
|
private _processData(data: any): KTSelectOptionData[] {
|
|
150
148
|
try {
|
|
151
|
-
if (this._config.debug) console.log('Processing API response:', data);
|
|
152
149
|
|
|
153
150
|
let processedData = data;
|
|
154
151
|
|
|
155
152
|
// Extract data from the API property if specified
|
|
156
153
|
if (this._config.apiDataProperty && data[this._config.apiDataProperty]) {
|
|
157
|
-
if (this._config.debug)
|
|
158
|
-
console.log(
|
|
159
|
-
`Extracting data from property: ${this._config.apiDataProperty}`,
|
|
160
|
-
);
|
|
161
154
|
|
|
162
155
|
// If pagination metadata is available, extract it
|
|
163
156
|
if (this._config.pagination) {
|
|
164
157
|
if (data.total_pages) {
|
|
165
158
|
this._totalPages = data.total_pages;
|
|
166
|
-
if (this._config.debug)
|
|
167
|
-
console.log(`Total pages found: ${this._totalPages}`);
|
|
168
159
|
}
|
|
169
160
|
if (data.total) {
|
|
170
161
|
this._totalPages = Math.ceil(
|
|
171
162
|
data.total / (this._config.paginationLimit || 10),
|
|
172
163
|
);
|
|
173
|
-
if (this._config.debug)
|
|
174
|
-
console.log(
|
|
175
|
-
`Calculated total pages: ${this._totalPages} from total: ${data.total}`,
|
|
176
|
-
);
|
|
177
164
|
}
|
|
178
165
|
}
|
|
179
166
|
|
|
@@ -186,10 +173,6 @@ export class KTSelectRemote {
|
|
|
186
173
|
return [];
|
|
187
174
|
}
|
|
188
175
|
|
|
189
|
-
if (this._config.debug)
|
|
190
|
-
console.log(
|
|
191
|
-
`Mapping ${processedData.length} items to KTSelectOptionData format`,
|
|
192
|
-
);
|
|
193
176
|
|
|
194
177
|
// Map data to KTSelectOptionData format
|
|
195
178
|
const mappedData = processedData.map((item: any): KTSelectOptionData => {
|
|
@@ -221,10 +204,6 @@ export class KTSelectRemote {
|
|
|
221
204
|
// If we found a value, verify it matches what was extracted
|
|
222
205
|
if (nestedValue !== null && nestedValue !== undefined) {
|
|
223
206
|
const expectedValue = String(nestedValue);
|
|
224
|
-
if (this._config.debug)
|
|
225
|
-
console.log(
|
|
226
|
-
`Data path verification for [${this._config.dataValueField}]: Expected: ${expectedValue}, Got: ${mappedItem.id}`,
|
|
227
|
-
);
|
|
228
207
|
|
|
229
208
|
if (mappedItem.id !== expectedValue && expectedValue) {
|
|
230
209
|
console.warn(
|
|
@@ -234,13 +213,9 @@ export class KTSelectRemote {
|
|
|
234
213
|
}
|
|
235
214
|
}
|
|
236
215
|
|
|
237
|
-
if (this._config.debug)
|
|
238
|
-
console.log(`Mapped item: ${JSON.stringify(mappedItem)}`);
|
|
239
216
|
return mappedItem;
|
|
240
217
|
});
|
|
241
218
|
|
|
242
|
-
if (this._config.debug)
|
|
243
|
-
console.log(`Returned ${mappedData.length} mapped items`);
|
|
244
219
|
return mappedData;
|
|
245
220
|
} catch (error) {
|
|
246
221
|
console.error('Error processing remote data:', error);
|
|
@@ -260,10 +235,6 @@ export class KTSelectRemote {
|
|
|
260
235
|
const valueField = this._config.dataValueField || 'id';
|
|
261
236
|
const labelField = this._config.dataFieldText || 'title';
|
|
262
237
|
|
|
263
|
-
if (this._config.debug)
|
|
264
|
-
console.log(`Mapping fields: value=${valueField}, label=${labelField}`);
|
|
265
|
-
if (this._config.debug)
|
|
266
|
-
console.log('Item data:', JSON.stringify(item).substring(0, 200) + '...'); // Trimmed for readability
|
|
267
238
|
|
|
268
239
|
// Extract values using improved getValue function
|
|
269
240
|
const getValue = (obj: any, path: string): any => {
|
|
@@ -285,17 +256,6 @@ export class KTSelectRemote {
|
|
|
285
256
|
result = result[part];
|
|
286
257
|
}
|
|
287
258
|
|
|
288
|
-
// Log the extraction result
|
|
289
|
-
if (this._config.debug)
|
|
290
|
-
console.log(
|
|
291
|
-
`Extracted [${path}] => ${
|
|
292
|
-
result !== null && result !== undefined
|
|
293
|
-
? typeof result === 'object'
|
|
294
|
-
? JSON.stringify(result).substring(0, 50)
|
|
295
|
-
: String(result).substring(0, 50)
|
|
296
|
-
: 'null'
|
|
297
|
-
}`,
|
|
298
|
-
);
|
|
299
259
|
|
|
300
260
|
return result;
|
|
301
261
|
} catch (error) {
|
|
@@ -312,8 +272,6 @@ export class KTSelectRemote {
|
|
|
312
272
|
for (const field of fallbackFields) {
|
|
313
273
|
if (item[field] !== null && item[field] !== undefined) {
|
|
314
274
|
id = String(item[field]);
|
|
315
|
-
if (this._config.debug)
|
|
316
|
-
console.log(`Using fallback field '${field}' for ID: ${id}`);
|
|
317
275
|
break;
|
|
318
276
|
}
|
|
319
277
|
}
|
|
@@ -324,7 +282,6 @@ export class KTSelectRemote {
|
|
|
324
282
|
// If still no ID, generate one
|
|
325
283
|
if (!id) {
|
|
326
284
|
id = `option-${Math.random().toString(36).substr(2, 9)}`;
|
|
327
|
-
if (this._config.debug) console.log(`Generated fallback ID: ${id}`);
|
|
328
285
|
}
|
|
329
286
|
|
|
330
287
|
// Get label with proper fallbacks
|
|
@@ -342,8 +299,6 @@ export class KTSelectRemote {
|
|
|
342
299
|
for (const field of fallbackFields) {
|
|
343
300
|
if (item[field] !== null && item[field] !== undefined) {
|
|
344
301
|
title = String(item[field]);
|
|
345
|
-
if (this._config.debug)
|
|
346
|
-
console.log(`Using fallback field '${field}' for title: ${title}`);
|
|
347
302
|
break;
|
|
348
303
|
}
|
|
349
304
|
}
|
|
@@ -354,8 +309,6 @@ export class KTSelectRemote {
|
|
|
354
309
|
// If still no title, use ID as fallback
|
|
355
310
|
if (!title) {
|
|
356
311
|
title = `Option ${id}`;
|
|
357
|
-
if (this._config.debug)
|
|
358
|
-
console.log(`Using ID as fallback title: ${title}`);
|
|
359
312
|
}
|
|
360
313
|
|
|
361
314
|
// Create the option object with consistent structure
|
|
@@ -366,8 +319,6 @@ export class KTSelectRemote {
|
|
|
366
319
|
disabled: Boolean(item.disabled),
|
|
367
320
|
};
|
|
368
321
|
|
|
369
|
-
if (this._config.debug)
|
|
370
|
-
console.log('Final mapped item:', JSON.stringify(result));
|
|
371
322
|
return result;
|
|
372
323
|
}
|
|
373
324
|
|
|
@@ -39,11 +39,6 @@ export class KTSelectSearch {
|
|
|
39
39
|
this._searchInput = this._select.getSearchInput();
|
|
40
40
|
|
|
41
41
|
if (this._searchInput) {
|
|
42
|
-
if (this._config.debug)
|
|
43
|
-
console.log(
|
|
44
|
-
'Initializing search module with input:',
|
|
45
|
-
this._searchInput,
|
|
46
|
-
);
|
|
47
42
|
|
|
48
43
|
// First remove any existing listeners to prevent duplicates
|
|
49
44
|
this._removeEventListeners();
|
|
@@ -96,24 +91,40 @@ export class KTSelectSearch {
|
|
|
96
91
|
.getWrapperElement()
|
|
97
92
|
.addEventListener('dropdown.close', () => {
|
|
98
93
|
this._focusManager.resetFocus();
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
94
|
+
const config = this._select.getConfig();
|
|
95
|
+
|
|
96
|
+
// Clear highlights from current options (always do this)
|
|
97
|
+
this.clearSearch();
|
|
98
|
+
|
|
99
|
+
// Respect clearSearchOnClose config option
|
|
100
|
+
if (config.clearSearchOnClose) {
|
|
101
|
+
// Clear the search input field
|
|
102
|
+
this._searchInput.value = '';
|
|
103
|
+
// Reset all options to their original state
|
|
104
|
+
this._resetAllOptions();
|
|
105
|
+
// Clear any "no results" message
|
|
106
|
+
this._clearNoResultsMessage();
|
|
107
|
+
} else {
|
|
108
|
+
// When clearSearchOnClose is false, preserve search text
|
|
109
|
+
// The search input value is already preserved by KTSelect's closeDropdown method
|
|
110
|
+
// Reset options visibility to show all (they will be re-filtered when dropdown reopens)
|
|
111
|
+
this._resetAllOptions();
|
|
112
|
+
// Clear any "no results" message
|
|
113
|
+
this._clearNoResultsMessage();
|
|
114
|
+
// Note: The search input value is preserved, so when dropdown reopens,
|
|
115
|
+
// the dropdown.show listener will detect it and re-filter options accordingly
|
|
116
|
+
}
|
|
108
117
|
});
|
|
109
118
|
|
|
110
119
|
// Clear highlights when an option is selected - ATTACH TO ORIGINAL SELECT (standard 'change' event)
|
|
111
120
|
this._select.getElement().addEventListener('change', () => {
|
|
112
121
|
this.clearSearch();
|
|
113
122
|
|
|
114
|
-
// Close dropdown only for single select mode
|
|
123
|
+
// Close dropdown only for single select mode, and only if closeOnEnter is not false
|
|
115
124
|
// Keep dropdown open for multiple select mode to allow additional selections
|
|
116
|
-
|
|
125
|
+
// Also respect closeOnEnter config when it's explicitly set to false
|
|
126
|
+
const config = this._select.getConfig();
|
|
127
|
+
if (!config.multiple && config.closeOnEnter !== false) {
|
|
117
128
|
this._select.closeDropdown();
|
|
118
129
|
}
|
|
119
130
|
});
|
|
@@ -137,11 +148,9 @@ export class KTSelectSearch {
|
|
|
137
148
|
this._clearNoResultsMessage();
|
|
138
149
|
}
|
|
139
150
|
|
|
140
|
-
// Handle autofocus for the search input
|
|
151
|
+
// Handle autofocus for the search input with retry mechanism
|
|
141
152
|
if (this._select.getConfig().searchAutofocus) {
|
|
142
|
-
|
|
143
|
-
this._searchInput?.focus(); // Focus search input
|
|
144
|
-
}, 50); // Delay to ensure dropdown is visible
|
|
153
|
+
this._focusSearchInputWithRetry();
|
|
145
154
|
}
|
|
146
155
|
this._select.updateSelectAllButtonState();
|
|
147
156
|
});
|
|
@@ -158,6 +167,58 @@ export class KTSelectSearch {
|
|
|
158
167
|
}
|
|
159
168
|
}
|
|
160
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Focus the search input with retry mechanism for reliability
|
|
172
|
+
* Retries up to 3 times with exponential backoff (50ms, 100ms, 200ms)
|
|
173
|
+
*/
|
|
174
|
+
private _focusSearchInputWithRetry(attempt: number = 0): void {
|
|
175
|
+
if (!this._searchInput) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const maxAttempts = 3;
|
|
180
|
+
const delays = [0, 50, 100, 200]; // Initial attempt + 3 retries
|
|
181
|
+
|
|
182
|
+
if (attempt > maxAttempts) {
|
|
183
|
+
if (this._config.debug) {
|
|
184
|
+
console.warn(
|
|
185
|
+
'KTSelect: Failed to focus search input after',
|
|
186
|
+
maxAttempts,
|
|
187
|
+
'attempts',
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const delay = delays[attempt] || 200;
|
|
194
|
+
const focusAttempt = () => {
|
|
195
|
+
try {
|
|
196
|
+
this._searchInput?.focus();
|
|
197
|
+
// Check if focus was successful
|
|
198
|
+
const isFocused = document.activeElement === this._searchInput || this._searchInput === document.activeElement;
|
|
199
|
+
if (isFocused) {
|
|
200
|
+
// Focus successful
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// Focus failed, retry if we haven't exceeded max attempts
|
|
204
|
+
if (attempt < maxAttempts) {
|
|
205
|
+
this._focusSearchInputWithRetry(attempt + 1);
|
|
206
|
+
}
|
|
207
|
+
} catch (error) {
|
|
208
|
+
// Focus failed with error, retry if we haven't exceeded max attempts
|
|
209
|
+
if (attempt < maxAttempts) {
|
|
210
|
+
this._focusSearchInputWithRetry(attempt + 1);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (delay === 0) {
|
|
216
|
+
focusAttempt();
|
|
217
|
+
} else {
|
|
218
|
+
setTimeout(focusAttempt, delay);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
161
222
|
/**
|
|
162
223
|
* Handles keydown events on the search input for navigation and actions.
|
|
163
224
|
*/
|
|
@@ -180,15 +241,27 @@ export class KTSelectSearch {
|
|
|
180
241
|
break;
|
|
181
242
|
case 'Enter':
|
|
182
243
|
event.preventDefault();
|
|
183
|
-
// Always attempt to select the first available option in the list.
|
|
184
|
-
// focusFirst() finds, focuses, and returns the first visible, non-disabled option.
|
|
185
244
|
const firstAvailableOption = this._focusManager.focusFirst();
|
|
186
245
|
|
|
187
246
|
if (firstAvailableOption) {
|
|
188
247
|
const optionValue = firstAvailableOption.getAttribute('data-value');
|
|
189
248
|
if (optionValue) {
|
|
190
|
-
this._select.
|
|
191
|
-
|
|
249
|
+
const config = this._select.getConfig();
|
|
250
|
+
const isAlreadySelected = !config.multiple && this._select.getSelectedOptions().includes(optionValue);
|
|
251
|
+
const shouldClose = !config.multiple && config.closeOnEnter !== false;
|
|
252
|
+
|
|
253
|
+
if (isAlreadySelected && shouldClose) {
|
|
254
|
+
this._select.closeDropdown();
|
|
255
|
+
} else {
|
|
256
|
+
this._select.toggleSelection(optionValue);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Focus display element after closing so user can press Enter again
|
|
260
|
+
if (shouldClose) {
|
|
261
|
+
setTimeout(() => {
|
|
262
|
+
this._select.getDisplayElement()?.focus();
|
|
263
|
+
}, 0);
|
|
264
|
+
}
|
|
192
265
|
}
|
|
193
266
|
}
|
|
194
267
|
break;
|
|
@@ -127,7 +127,11 @@
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
.kt-select-display:not([data-multiple='true']) {
|
|
130
|
-
@apply w-full
|
|
130
|
+
@apply w-full flex items-center min-w-0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.kt-select-display:not([data-multiple='true']) .kt-select-option-text {
|
|
134
|
+
@apply min-w-0 overflow-ellipsis truncate;
|
|
131
135
|
}
|
|
132
136
|
|
|
133
137
|
/* Enhanced Tag Styles */
|