@keenthemes/ktui 1.0.19 → 1.0.21
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 +690 -166
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +165 -31
- package/examples/image-input/file-upload-example.html +189 -0
- package/examples/select/remote-data_.html +5 -0
- package/examples/select/test-optimizations.html +227 -0
- package/examples/select/test-remote-search.html +151 -0
- package/examples/sticky/README.md +158 -0
- package/examples/sticky/debug-sticky.html +144 -0
- package/examples/sticky/test-runner.html +175 -0
- package/examples/sticky/test-sticky-logic.js +369 -0
- package/examples/sticky/test-sticky-positioning.html +386 -0
- package/examples/toast/example.html +52 -0
- package/lib/cjs/components/component.js +59 -5
- package/lib/cjs/components/component.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-sort.js +4 -0
- package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +79 -12
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/image-input/image-input.js +10 -2
- package/lib/cjs/components/image-input/image-input.js.map +1 -1
- package/lib/cjs/components/select/combobox.js +50 -20
- package/lib/cjs/components/select/combobox.js.map +1 -1
- package/lib/cjs/components/select/config.js +1 -0
- package/lib/cjs/components/select/config.js.map +1 -1
- package/lib/cjs/components/select/dropdown.js +4 -2
- package/lib/cjs/components/select/dropdown.js.map +1 -1
- package/lib/cjs/components/select/index.js.map +1 -1
- package/lib/cjs/components/select/option.js +2 -1
- package/lib/cjs/components/select/option.js.map +1 -1
- package/lib/cjs/components/select/remote.js +50 -50
- package/lib/cjs/components/select/remote.js.map +1 -1
- package/lib/cjs/components/select/search.js +15 -5
- package/lib/cjs/components/select/search.js.map +1 -1
- package/lib/cjs/components/select/select.js +273 -32
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.js +3 -1
- package/lib/cjs/components/select/tags.js.map +1 -1
- package/lib/cjs/components/select/templates.js +6 -0
- package/lib/cjs/components/select/templates.js.map +1 -1
- package/lib/cjs/components/select/utils.js +23 -10
- package/lib/cjs/components/select/utils.js.map +1 -1
- package/lib/cjs/components/stepper/stepper.js +59 -12
- package/lib/cjs/components/stepper/stepper.js.map +1 -1
- package/lib/cjs/components/sticky/sticky.js +52 -14
- package/lib/cjs/components/sticky/sticky.js.map +1 -1
- package/lib/esm/components/component.js +59 -5
- package/lib/esm/components/component.js.map +1 -1
- package/lib/esm/components/datatable/datatable-sort.js +4 -0
- package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
- package/lib/esm/components/datatable/datatable.js +78 -12
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/image-input/image-input.js +10 -2
- package/lib/esm/components/image-input/image-input.js.map +1 -1
- package/lib/esm/components/select/combobox.js +50 -20
- package/lib/esm/components/select/combobox.js.map +1 -1
- package/lib/esm/components/select/config.js +1 -0
- package/lib/esm/components/select/config.js.map +1 -1
- package/lib/esm/components/select/dropdown.js +4 -2
- package/lib/esm/components/select/dropdown.js.map +1 -1
- package/lib/esm/components/select/index.js +1 -1
- package/lib/esm/components/select/index.js.map +1 -1
- package/lib/esm/components/select/option.js +2 -1
- package/lib/esm/components/select/option.js.map +1 -1
- package/lib/esm/components/select/remote.js +50 -50
- package/lib/esm/components/select/remote.js.map +1 -1
- package/lib/esm/components/select/search.js +16 -6
- package/lib/esm/components/select/search.js.map +1 -1
- package/lib/esm/components/select/select.js +273 -32
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.js +3 -1
- package/lib/esm/components/select/tags.js.map +1 -1
- package/lib/esm/components/select/templates.js +6 -0
- package/lib/esm/components/select/templates.js.map +1 -1
- package/lib/esm/components/select/utils.js +23 -10
- package/lib/esm/components/select/utils.js.map +1 -1
- package/lib/esm/components/stepper/stepper.js +59 -12
- package/lib/esm/components/stepper/stepper.js.map +1 -1
- package/lib/esm/components/sticky/sticky.js +52 -14
- package/lib/esm/components/sticky/sticky.js.map +1 -1
- package/package.json +2 -2
- package/src/components/component.ts +19 -4
- package/src/components/datatable/datatable-sort.ts +6 -0
- package/src/components/datatable/datatable.ts +98 -15
- package/src/components/datatable/types.ts +5 -1
- package/src/components/image-input/image-input.ts +11 -2
- package/src/components/image-input/types.ts +1 -0
- package/src/components/input/input-group.css +1 -1
- package/src/components/input/input.css +1 -1
- package/src/components/scrollable/scrollable.css +3 -3
- package/src/components/select/combobox.ts +84 -34
- package/src/components/select/config.ts +2 -0
- package/src/components/select/dropdown.ts +20 -11
- package/src/components/select/index.ts +6 -1
- package/src/components/select/option.ts +7 -6
- package/src/components/select/remote.ts +51 -52
- package/src/components/select/search.ts +59 -44
- package/src/components/select/select.css +26 -17
- package/src/components/select/select.ts +472 -101
- package/src/components/select/tags.ts +9 -3
- package/src/components/select/templates.ts +10 -0
- package/src/components/select/utils.ts +55 -20
- package/src/components/select/variants.css +0 -1
- package/src/components/stepper/stepper.ts +2 -2
- package/src/components/sticky/sticky.ts +47 -16
- package/src/components/sticky/types.ts +3 -0
- package/src/components/table/table.css +1 -1
- package/src/components/textarea/textarea.css +1 -1
- package/src/components/toast/toast.css +84 -47
- package/src/components/toast/types.ts +3 -0
|
@@ -26,9 +26,15 @@ export class KTSelectCombobox {
|
|
|
26
26
|
|
|
27
27
|
const displayElement = select.getDisplayElement(); // KTSelect's main display element for combobox
|
|
28
28
|
|
|
29
|
-
this._searchInputElement = displayElement.querySelector(
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
this._searchInputElement = displayElement.querySelector(
|
|
30
|
+
'input[data-kt-select-search]',
|
|
31
|
+
);
|
|
32
|
+
this._clearButtonElement = displayElement.querySelector(
|
|
33
|
+
'[data-kt-select-clear-button]',
|
|
34
|
+
);
|
|
35
|
+
this._valuesContainerElement = displayElement.querySelector(
|
|
36
|
+
'[data-kt-select-combobox-values]',
|
|
37
|
+
);
|
|
32
38
|
|
|
33
39
|
this._boundInputHandler = this._handleComboboxInput.bind(this);
|
|
34
40
|
this._boundClearHandler = this._handleClearButtonClick.bind(this);
|
|
@@ -42,7 +48,7 @@ export class KTSelectCombobox {
|
|
|
42
48
|
this.updateDisplay(this._select.getSelectedOptions());
|
|
43
49
|
} else {
|
|
44
50
|
// For tags or displayTemplate, the input should be clear for typing.
|
|
45
|
-
|
|
51
|
+
this._searchInputElement.value = '';
|
|
46
52
|
}
|
|
47
53
|
this._toggleClearButtonVisibility(this._searchInputElement.value);
|
|
48
54
|
// this._select.showAllOptions(); // showAllOptions might be too broad, filtering is managed by typing.
|
|
@@ -56,11 +62,18 @@ export class KTSelectCombobox {
|
|
|
56
62
|
*/
|
|
57
63
|
private _attachEventListeners(): void {
|
|
58
64
|
this._removeEventListeners();
|
|
59
|
-
if (this._searchInputElement) {
|
|
60
|
-
|
|
65
|
+
if (this._searchInputElement) {
|
|
66
|
+
// Ensure element exists
|
|
67
|
+
this._searchInputElement.addEventListener(
|
|
68
|
+
'input',
|
|
69
|
+
this._boundInputHandler,
|
|
70
|
+
);
|
|
61
71
|
}
|
|
62
72
|
if (this._clearButtonElement) {
|
|
63
|
-
this._clearButtonElement.addEventListener(
|
|
73
|
+
this._clearButtonElement.addEventListener(
|
|
74
|
+
'click',
|
|
75
|
+
this._boundClearHandler,
|
|
76
|
+
);
|
|
64
77
|
}
|
|
65
78
|
}
|
|
66
79
|
|
|
@@ -69,10 +82,16 @@ export class KTSelectCombobox {
|
|
|
69
82
|
*/
|
|
70
83
|
private _removeEventListeners(): void {
|
|
71
84
|
if (this._searchInputElement) {
|
|
72
|
-
this._searchInputElement.removeEventListener(
|
|
85
|
+
this._searchInputElement.removeEventListener(
|
|
86
|
+
'input',
|
|
87
|
+
this._boundInputHandler,
|
|
88
|
+
);
|
|
73
89
|
}
|
|
74
90
|
if (this._clearButtonElement) {
|
|
75
|
-
this._clearButtonElement.removeEventListener(
|
|
91
|
+
this._clearButtonElement.removeEventListener(
|
|
92
|
+
'click',
|
|
93
|
+
this._boundClearHandler,
|
|
94
|
+
);
|
|
76
95
|
}
|
|
77
96
|
}
|
|
78
97
|
|
|
@@ -85,7 +104,8 @@ export class KTSelectCombobox {
|
|
|
85
104
|
|
|
86
105
|
this._toggleClearButtonVisibility(query);
|
|
87
106
|
|
|
88
|
-
if (!(this._select as any).isDropdownOpen()) {
|
|
107
|
+
if (!(this._select as any).isDropdownOpen()) {
|
|
108
|
+
// Use public getter
|
|
89
109
|
this._select.openDropdown();
|
|
90
110
|
}
|
|
91
111
|
// For single select without displayTemplate, if user types, they are essentially clearing the previous selection text
|
|
@@ -127,7 +147,11 @@ export class KTSelectCombobox {
|
|
|
127
147
|
if (!this._clearButtonElement) return;
|
|
128
148
|
const hasSelectedItems = this._select.getSelectedOptions().length > 0;
|
|
129
149
|
|
|
130
|
-
if (
|
|
150
|
+
if (
|
|
151
|
+
inputValue.length > 0 ||
|
|
152
|
+
(hasSelectedItems &&
|
|
153
|
+
(this._config.multiple || this._config.displayTemplate))
|
|
154
|
+
) {
|
|
131
155
|
this._clearButtonElement.classList.remove('hidden');
|
|
132
156
|
} else {
|
|
133
157
|
this._clearButtonElement.classList.add('hidden');
|
|
@@ -138,7 +162,9 @@ export class KTSelectCombobox {
|
|
|
138
162
|
* Filter options for combobox based on input query
|
|
139
163
|
*/
|
|
140
164
|
private _filterOptionsForCombobox(query: string): void {
|
|
141
|
-
const options = Array.from(
|
|
165
|
+
const options = Array.from(
|
|
166
|
+
this._select.getOptionsElement(),
|
|
167
|
+
) as HTMLElement[];
|
|
142
168
|
const config = this._select.getConfig();
|
|
143
169
|
const dropdownElement = this._select.getDropdownElement();
|
|
144
170
|
filterOptions(options, query, config, dropdownElement);
|
|
@@ -160,41 +186,65 @@ export class KTSelectCombobox {
|
|
|
160
186
|
this._valuesContainerElement.innerHTML = '';
|
|
161
187
|
}
|
|
162
188
|
|
|
163
|
-
if (this._config.tags && this._valuesContainerElement) {
|
|
164
|
-
|
|
189
|
+
if (this._config.tags && this._valuesContainerElement) {
|
|
190
|
+
// Combobox + Tags
|
|
191
|
+
selectedOptions.forEach((value) => {
|
|
165
192
|
// Ensure value is properly escaped for querySelector
|
|
166
|
-
const optionElement = this._select
|
|
193
|
+
const optionElement = this._select
|
|
194
|
+
.getElement()
|
|
195
|
+
.querySelector(
|
|
196
|
+
`option[value="${CSS.escape(value)}"]`,
|
|
197
|
+
) as HTMLOptionElement;
|
|
167
198
|
if (optionElement) {
|
|
168
199
|
const tagElement = defaultTemplates.tag(optionElement, this._config);
|
|
169
200
|
this._valuesContainerElement.appendChild(tagElement);
|
|
170
201
|
}
|
|
171
202
|
});
|
|
172
203
|
this._searchInputElement.value = ''; // Input field is for typing new searches
|
|
173
|
-
this._searchInputElement.placeholder =
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
204
|
+
this._searchInputElement.placeholder =
|
|
205
|
+
selectedOptions.length > 0
|
|
206
|
+
? ''
|
|
207
|
+
: this._config.placeholder || 'Select...';
|
|
208
|
+
} else if (this._config.displayTemplate && this._valuesContainerElement) {
|
|
209
|
+
// Combobox + DisplayTemplate (no tags)
|
|
210
|
+
this._valuesContainerElement.innerHTML =
|
|
211
|
+
this._select.renderDisplayTemplateForSelected(selectedOptions);
|
|
177
212
|
this._searchInputElement.value = ''; // Input field is for typing new searches
|
|
178
|
-
this._searchInputElement.placeholder =
|
|
179
|
-
|
|
180
|
-
|
|
213
|
+
this._searchInputElement.placeholder =
|
|
214
|
+
selectedOptions.length > 0
|
|
215
|
+
? ''
|
|
216
|
+
: this._config.placeholder || 'Select...';
|
|
217
|
+
} else if (this._config.multiple && this._valuesContainerElement) {
|
|
218
|
+
// Combobox + Multiple (no tags, no display template)
|
|
181
219
|
// For simplicity, join text. A proper tag implementation would be more complex here.
|
|
182
|
-
this._valuesContainerElement.innerHTML = selectedOptions
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
220
|
+
this._valuesContainerElement.innerHTML = selectedOptions
|
|
221
|
+
.map((value) => {
|
|
222
|
+
const optionEl = this._select
|
|
223
|
+
.getElement()
|
|
224
|
+
.querySelector(`option[value="${CSS.escape(value)}"]`);
|
|
225
|
+
return optionEl ? optionEl.textContent : '';
|
|
226
|
+
})
|
|
227
|
+
.join(', '); // Basic comma separation
|
|
186
228
|
this._searchInputElement.value = '';
|
|
187
|
-
this._searchInputElement.placeholder =
|
|
188
|
-
|
|
189
|
-
|
|
229
|
+
this._searchInputElement.placeholder =
|
|
230
|
+
selectedOptions.length > 0
|
|
231
|
+
? ''
|
|
232
|
+
: this._config.placeholder || 'Select...';
|
|
233
|
+
} else if (!this._config.multiple && selectedOptions.length > 0) {
|
|
234
|
+
// Single select combobox: display selected option's text in the input
|
|
190
235
|
const selectedValue = selectedOptions[0];
|
|
191
|
-
const optionElement = this._select
|
|
192
|
-
|
|
236
|
+
const optionElement = this._select
|
|
237
|
+
.getElement()
|
|
238
|
+
.querySelector(`option[value="${CSS.escape(selectedValue)}"]`);
|
|
239
|
+
this._searchInputElement.value = optionElement
|
|
240
|
+
? optionElement.textContent || ''
|
|
241
|
+
: '';
|
|
193
242
|
// placeholder is implicitly handled by input value for single select
|
|
194
|
-
|
|
195
|
-
|
|
243
|
+
} else {
|
|
244
|
+
// No selection or not fitting above categories (e.g. single select, no items)
|
|
196
245
|
this._searchInputElement.value = '';
|
|
197
|
-
this._searchInputElement.placeholder =
|
|
246
|
+
this._searchInputElement.placeholder =
|
|
247
|
+
this._config.placeholder || 'Select...';
|
|
198
248
|
// _valuesContainerElement is already cleared if it exists
|
|
199
249
|
}
|
|
200
250
|
this._toggleClearButtonVisibility(this._searchInputElement.value);
|
|
@@ -57,6 +57,7 @@ export const DefaultConfig: KTSelectConfigInterface = {
|
|
|
57
57
|
// Multi-Select Display
|
|
58
58
|
selectAllText: 'Select all', // Text for the "Select All" option (if implemented)
|
|
59
59
|
clearAllText: 'Clear all', // Text for the "Clear All" option (if implemented)
|
|
60
|
+
enableSelectAll: false, // Enable/disable "Select All" button for multi-select
|
|
60
61
|
showSelectedCount: true, // Show the number of selected options in multi-select mode
|
|
61
62
|
renderSelected: null, // Custom function to render the selected value(s) in the display area
|
|
62
63
|
|
|
@@ -105,6 +106,7 @@ export interface KTSelectConfigInterface {
|
|
|
105
106
|
// Multi-Select Display
|
|
106
107
|
selectAllText?: string;
|
|
107
108
|
clearAllText?: string;
|
|
109
|
+
enableSelectAll?: boolean;
|
|
108
110
|
showSelectedCount?: boolean;
|
|
109
111
|
renderSelected?: (selectedOptions: any[]) => string; // Assuming any[] for now, adjust based on your option data structure
|
|
110
112
|
|
|
@@ -106,9 +106,7 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
106
106
|
|
|
107
107
|
if (this._config.disabled) {
|
|
108
108
|
if (this._config.debug)
|
|
109
|
-
console.log(
|
|
110
|
-
'KTSelectDropdown._handleToggleClick: select is disabled',
|
|
111
|
-
);
|
|
109
|
+
console.log('KTSelectDropdown._handleToggleClick: select is disabled');
|
|
112
110
|
return;
|
|
113
111
|
}
|
|
114
112
|
|
|
@@ -251,9 +249,7 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
251
249
|
public open(): void {
|
|
252
250
|
if (this._config.disabled) {
|
|
253
251
|
if (this._config.debug)
|
|
254
|
-
console.log(
|
|
255
|
-
'KTSelectDropdown.open: select is disabled, not opening',
|
|
256
|
-
);
|
|
252
|
+
console.log('KTSelectDropdown.open: select is disabled, not opening');
|
|
257
253
|
return;
|
|
258
254
|
}
|
|
259
255
|
if (this._isOpen || this._isTransitioning) return;
|
|
@@ -279,17 +275,26 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
279
275
|
}
|
|
280
276
|
|
|
281
277
|
// Consider the dropdown's current z-index if it's already set and higher
|
|
282
|
-
const currentDropdownZIndexStr = KTDom.getCssProp(
|
|
278
|
+
const currentDropdownZIndexStr = KTDom.getCssProp(
|
|
279
|
+
this._dropdownElement,
|
|
280
|
+
'z-index',
|
|
281
|
+
);
|
|
283
282
|
if (currentDropdownZIndexStr && currentDropdownZIndexStr !== 'auto') {
|
|
284
283
|
const currentDropdownZIndex = parseInt(currentDropdownZIndexStr);
|
|
285
|
-
if (
|
|
284
|
+
if (
|
|
285
|
+
!isNaN(currentDropdownZIndex) &&
|
|
286
|
+
currentDropdownZIndex > (zIndexToApply || 0)
|
|
287
|
+
) {
|
|
286
288
|
zIndexToApply = currentDropdownZIndex;
|
|
287
289
|
}
|
|
288
290
|
}
|
|
289
291
|
|
|
290
292
|
// Ensure dropdown is above elements within its original toggle's parent context
|
|
291
293
|
const toggleParentContextZindex = KTDom.getHighestZindex(this._element); // _element is the select wrapper
|
|
292
|
-
if (
|
|
294
|
+
if (
|
|
295
|
+
toggleParentContextZindex !== null &&
|
|
296
|
+
toggleParentContextZindex >= (zIndexToApply || 0)
|
|
297
|
+
) {
|
|
293
298
|
zIndexToApply = toggleParentContextZindex + 1;
|
|
294
299
|
}
|
|
295
300
|
|
|
@@ -379,7 +384,9 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
379
384
|
|
|
380
385
|
KTDom.transitionEnd(this._dropdownElement, completeTransition);
|
|
381
386
|
|
|
382
|
-
if (
|
|
387
|
+
if (
|
|
388
|
+
KTDom.getCssProp(this._dropdownElement, 'transition-duration') === '0s'
|
|
389
|
+
) {
|
|
383
390
|
completeTransition();
|
|
384
391
|
}
|
|
385
392
|
}
|
|
@@ -422,7 +429,9 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
422
429
|
private _resolveDropdownContainer(): HTMLElement | null {
|
|
423
430
|
const containerSelector = this._config.dropdownContainer;
|
|
424
431
|
if (containerSelector && containerSelector !== 'body') {
|
|
425
|
-
const containerElement = document.querySelector(
|
|
432
|
+
const containerElement = document.querySelector(
|
|
433
|
+
containerSelector,
|
|
434
|
+
) as HTMLElement | null;
|
|
426
435
|
if (!containerElement && this._config.debug) {
|
|
427
436
|
console.warn(
|
|
428
437
|
`KTSelectDropdown: dropdownContainer selector "${containerSelector}" not found. Dropdown will remain in its default position.`,
|
|
@@ -9,5 +9,10 @@ export { KTSelectCombobox } from './combobox';
|
|
|
9
9
|
export { KTSelectSearch } from './search';
|
|
10
10
|
export { KTSelectTags } from './tags';
|
|
11
11
|
export { KTSelectDropdown } from './dropdown';
|
|
12
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
filterOptions,
|
|
14
|
+
FocusManager,
|
|
15
|
+
EventManager,
|
|
16
|
+
TypeToSearchBuffer,
|
|
17
|
+
} from './utils';
|
|
13
18
|
export { KTSelectConfigInterface, KTSelectOption } from './config';
|
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import KTComponent from '../component';
|
|
7
|
-
import {
|
|
8
|
-
KTSelectConfigInterface,
|
|
9
|
-
} from './config';
|
|
7
|
+
import { KTSelectConfigInterface } from './config';
|
|
10
8
|
import { defaultTemplates } from './templates';
|
|
11
9
|
|
|
12
10
|
export class KTSelectOption extends KTComponent {
|
|
@@ -15,7 +13,7 @@ export class KTSelectOption extends KTComponent {
|
|
|
15
13
|
protected override readonly _config: KTSelectConfigInterface; // Holds option-specific data from data-kt-*
|
|
16
14
|
private _globalConfig: KTSelectConfigInterface; // Main select's config
|
|
17
15
|
|
|
18
|
-
constructor(element: HTMLElement, config?: KTSelectConfigInterface
|
|
16
|
+
constructor(element: HTMLElement, config?: KTSelectConfigInterface) {
|
|
19
17
|
super();
|
|
20
18
|
|
|
21
19
|
// Always initialize a new option instance
|
|
@@ -30,13 +28,16 @@ export class KTSelectOption extends KTComponent {
|
|
|
30
28
|
// Ensure optionsConfig is initialized
|
|
31
29
|
if (this._globalConfig) {
|
|
32
30
|
this._globalConfig.optionsConfig = this._globalConfig.optionsConfig || {};
|
|
33
|
-
this._globalConfig.optionsConfig[(element as HTMLInputElement).value] =
|
|
31
|
+
this._globalConfig.optionsConfig[(element as HTMLInputElement).value] =
|
|
32
|
+
this._config;
|
|
34
33
|
// console.log('[KTSelectOption] Populating _globalConfig.optionsConfig for value', (element as HTMLInputElement).value, 'with:', JSON.parse(JSON.stringify(this._config)));
|
|
35
34
|
// console.log('[KTSelectOption] _globalConfig.optionsConfig is now:', JSON.parse(JSON.stringify(this._globalConfig.optionsConfig)));
|
|
36
35
|
} else {
|
|
37
36
|
// Handle case where _globalConfig might be undefined, though constructor expects it.
|
|
38
37
|
// This might indicate a need to ensure config is always passed or has a default.
|
|
39
|
-
console.warn(
|
|
38
|
+
console.warn(
|
|
39
|
+
'KTSelectOption: _globalConfig is undefined during constructor.',
|
|
40
|
+
);
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// Don't store in KTData to avoid Singleton pattern issues
|
|
@@ -261,16 +261,13 @@ export class KTSelectRemote {
|
|
|
261
261
|
const labelField = this._config.dataFieldText || 'title';
|
|
262
262
|
|
|
263
263
|
if (this._config.debug)
|
|
264
|
-
console.log(
|
|
265
|
-
`Mapping fields: value=${valueField}, label=${labelField}`,
|
|
266
|
-
);
|
|
264
|
+
console.log(`Mapping fields: value=${valueField}, label=${labelField}`);
|
|
267
265
|
if (this._config.debug)
|
|
268
266
|
console.log('Item data:', JSON.stringify(item).substring(0, 200) + '...'); // Trimmed for readability
|
|
269
267
|
|
|
270
|
-
// Extract values using
|
|
268
|
+
// Extract values using improved getValue function
|
|
271
269
|
const getValue = (obj: any, path: string): any => {
|
|
272
|
-
if (!path) return null;
|
|
273
|
-
if (!obj) return null;
|
|
270
|
+
if (!path || !obj) return null;
|
|
274
271
|
|
|
275
272
|
try {
|
|
276
273
|
// Handle dot notation to access nested properties
|
|
@@ -296,7 +293,7 @@ export class KTSelectRemote {
|
|
|
296
293
|
? typeof result === 'object'
|
|
297
294
|
? JSON.stringify(result).substring(0, 50)
|
|
298
295
|
: String(result).substring(0, 50)
|
|
299
|
-
|
|
296
|
+
: 'null'
|
|
300
297
|
}`,
|
|
301
298
|
);
|
|
302
299
|
|
|
@@ -307,64 +304,66 @@ export class KTSelectRemote {
|
|
|
307
304
|
}
|
|
308
305
|
};
|
|
309
306
|
|
|
310
|
-
//
|
|
307
|
+
// Get ID and ensure it's a string
|
|
311
308
|
let id = getValue(item, valueField);
|
|
312
|
-
|
|
313
|
-
// Ensure id is always a proper string
|
|
314
309
|
if (id === null || id === undefined) {
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
console.log(`Using id.value as fallback: ${id}`);
|
|
325
|
-
} else if (item.id) {
|
|
326
|
-
id = String(item.id);
|
|
327
|
-
if (this._config.debug)
|
|
328
|
-
console.log(`Using direct item.id as fallback: ${id}`);
|
|
329
|
-
} else {
|
|
330
|
-
// If no ID found at all, use the title instead (will be extracted below)
|
|
331
|
-
if (this._config.debug)
|
|
332
|
-
console.log(`No ID found, will use title as fallback`);
|
|
333
|
-
id = null;
|
|
310
|
+
// Try common fallback fields for ID
|
|
311
|
+
const fallbackFields = ['id', 'value', 'key', 'pk'];
|
|
312
|
+
for (const field of fallbackFields) {
|
|
313
|
+
if (item[field] !== null && item[field] !== undefined) {
|
|
314
|
+
id = String(item[field]);
|
|
315
|
+
if (this._config.debug)
|
|
316
|
+
console.log(`Using fallback field '${field}' for ID: ${id}`);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
334
319
|
}
|
|
335
|
-
} else if (typeof id === 'object') {
|
|
336
|
-
// If ID is an object, log the issue and set to null to use title fallback
|
|
337
|
-
console.warn(
|
|
338
|
-
`ID for path ${valueField} is an object, will use title fallback instead`,
|
|
339
|
-
);
|
|
340
|
-
id = null;
|
|
341
320
|
} else {
|
|
342
|
-
// Otherwise, ensure it's a string
|
|
343
321
|
id = String(id);
|
|
344
|
-
if (this._config.debug) console.log(`Final ID value: ${id}`);
|
|
345
322
|
}
|
|
346
323
|
|
|
347
|
-
//
|
|
324
|
+
// If still no ID, generate one
|
|
325
|
+
if (!id) {
|
|
326
|
+
id = `option-${Math.random().toString(36).substr(2, 9)}`;
|
|
327
|
+
if (this._config.debug) console.log(`Generated fallback ID: ${id}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Get label with proper fallbacks
|
|
348
331
|
let title = getValue(item, labelField);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
332
|
+
if (!title) {
|
|
333
|
+
// Try common fallback fields for title
|
|
334
|
+
const fallbackFields = [
|
|
335
|
+
'name',
|
|
336
|
+
'title',
|
|
337
|
+
'label',
|
|
338
|
+
'text',
|
|
339
|
+
'displayName',
|
|
340
|
+
'description',
|
|
341
|
+
];
|
|
342
|
+
for (const field of fallbackFields) {
|
|
343
|
+
if (item[field] !== null && item[field] !== undefined) {
|
|
344
|
+
title = String(item[field]);
|
|
345
|
+
if (this._config.debug)
|
|
346
|
+
console.log(`Using fallback field '${field}' for title: ${title}`);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
title = String(title);
|
|
352
|
+
}
|
|
352
353
|
|
|
353
|
-
// If
|
|
354
|
+
// If still no title, use ID as fallback
|
|
354
355
|
if (!title) {
|
|
355
|
-
|
|
356
|
-
if (item.name) title = String(item.name);
|
|
357
|
-
else if (item.title) title = String(item.title);
|
|
358
|
-
else if (item.label) title = String(item.label);
|
|
359
|
-
else if (item.text) title = String(item.text);
|
|
356
|
+
title = `Option ${id}`;
|
|
360
357
|
if (this._config.debug)
|
|
361
|
-
console.log(
|
|
358
|
+
console.log(`Using ID as fallback title: ${title}`);
|
|
362
359
|
}
|
|
363
360
|
|
|
364
|
-
// Create the option object with
|
|
365
|
-
const result = {
|
|
366
|
-
id: id
|
|
367
|
-
title: title
|
|
361
|
+
// Create the option object with consistent structure
|
|
362
|
+
const result: KTSelectOptionData = {
|
|
363
|
+
id: id,
|
|
364
|
+
title: title,
|
|
365
|
+
selected: Boolean(item.selected),
|
|
366
|
+
disabled: Boolean(item.disabled),
|
|
368
367
|
};
|
|
369
368
|
|
|
370
369
|
if (this._config.debug)
|
|
@@ -6,11 +6,7 @@
|
|
|
6
6
|
import { KTSelectConfigInterface } from './config';
|
|
7
7
|
import { KTSelect } from './select';
|
|
8
8
|
import { defaultTemplates } from './templates';
|
|
9
|
-
import {
|
|
10
|
-
filterOptions,
|
|
11
|
-
FocusManager,
|
|
12
|
-
EventManager,
|
|
13
|
-
} from './utils';
|
|
9
|
+
import { filterOptions, FocusManager, EventManager } from './utils';
|
|
14
10
|
|
|
15
11
|
export class KTSelectSearch {
|
|
16
12
|
private _select: KTSelect;
|
|
@@ -63,7 +59,7 @@ export class KTSelectSearch {
|
|
|
63
59
|
this._eventManager.addListener(
|
|
64
60
|
this._searchInput,
|
|
65
61
|
'keydown',
|
|
66
|
-
this._handleSearchKeyDown.bind(this)
|
|
62
|
+
this._handleSearchKeyDown.bind(this),
|
|
67
63
|
);
|
|
68
64
|
|
|
69
65
|
// Add blur event listener to ensure highlights are cleared when focus is lost
|
|
@@ -96,18 +92,20 @@ export class KTSelectSearch {
|
|
|
96
92
|
}
|
|
97
93
|
|
|
98
94
|
// Listen for dropdown close to reset options - ATTACH TO WRAPPER
|
|
99
|
-
this._select
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
95
|
+
this._select
|
|
96
|
+
.getWrapperElement()
|
|
97
|
+
.addEventListener('dropdown.close', () => {
|
|
98
|
+
this._focusManager.resetFocus();
|
|
99
|
+
// If clearSearchOnClose is false and there's a value, the search term and filtered state should persist.
|
|
100
|
+
// KTSelect's closeDropdown method already calls this._searchModule.clearSearch() (which clears highlights)
|
|
101
|
+
// and conditionally clears the input value based on KTSelect's config.clearSearchOnClose.
|
|
102
|
+
// This listener in search.ts seems to unconditionally clear everything.
|
|
103
|
+
// For now, keeping its original behavior:
|
|
104
|
+
this.clearSearch(); // Clears highlights from current options
|
|
105
|
+
this._searchInput.value = ''; // Clears the search input field
|
|
106
|
+
this._resetAllOptions(); // Shows all options, restores original text, removes highlights
|
|
107
|
+
this._clearNoResultsMessage(); // Clears any "no results" message
|
|
108
|
+
});
|
|
111
109
|
|
|
112
110
|
// Clear highlights when an option is selected - ATTACH TO ORIGINAL SELECT (standard 'change' event)
|
|
113
111
|
this._select.getElement().addEventListener('change', () => {
|
|
@@ -121,29 +119,32 @@ export class KTSelectSearch {
|
|
|
121
119
|
});
|
|
122
120
|
|
|
123
121
|
// Consolidated 'dropdown.show' event listener - ATTACH TO WRAPPER
|
|
124
|
-
this._select
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
122
|
+
this._select
|
|
123
|
+
.getWrapperElement()
|
|
124
|
+
.addEventListener('dropdown.show', () => {
|
|
125
|
+
this._focusManager.resetFocus(); // Always clear previous focus state
|
|
126
|
+
|
|
127
|
+
if (this._searchInput?.value) {
|
|
128
|
+
// If there's an existing search term:
|
|
129
|
+
// 1. Re-filter options. This ensures the display (hidden/visible) is correct
|
|
130
|
+
// and "no results" message is handled if query yields nothing.
|
|
131
|
+
this._filterOptions(this._searchInput.value);
|
|
132
|
+
} else {
|
|
133
|
+
// If search input is empty:
|
|
134
|
+
// 1. Reset all options to their full, unfiltered, original state.
|
|
135
|
+
this._resetAllOptions(); // Shows all, clears highlights from options, restores original text
|
|
136
|
+
// 2. Clear any "no results" message.
|
|
137
|
+
this._clearNoResultsMessage();
|
|
138
|
+
}
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
140
|
+
// Handle autofocus for the search input (this was one of the original separate listeners)
|
|
141
|
+
if (this._select.getConfig().searchAutofocus) {
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
this._searchInput?.focus(); // Focus search input
|
|
144
|
+
}, 50); // Delay to ensure dropdown is visible
|
|
145
|
+
}
|
|
146
|
+
this._select.updateSelectAllButtonState();
|
|
147
|
+
});
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
}
|
|
@@ -164,6 +165,11 @@ export class KTSelectSearch {
|
|
|
164
165
|
const key = event.key;
|
|
165
166
|
|
|
166
167
|
switch (key) {
|
|
168
|
+
case ' ': // Spacebar
|
|
169
|
+
// Do nothing, allow space to be typed into the input
|
|
170
|
+
// Stop propagation to prevent parent handlers from processing this event
|
|
171
|
+
event.stopPropagation();
|
|
172
|
+
break;
|
|
167
173
|
case 'ArrowDown':
|
|
168
174
|
event.preventDefault();
|
|
169
175
|
this._focusManager.focusNext();
|
|
@@ -223,8 +229,10 @@ export class KTSelectSearch {
|
|
|
223
229
|
* This is typically called before applying new filters/highlights.
|
|
224
230
|
*/
|
|
225
231
|
private _restoreOptionContentsBeforeFilter(): void {
|
|
226
|
-
const options = Array.from(
|
|
227
|
-
|
|
232
|
+
const options = Array.from(
|
|
233
|
+
this._select.getOptionsElement(),
|
|
234
|
+
) as HTMLElement[];
|
|
235
|
+
options.forEach((option) => {
|
|
228
236
|
const value = option.getAttribute('data-value');
|
|
229
237
|
if (value && this._originalOptionContents.has(value)) {
|
|
230
238
|
const originalContent = this._originalOptionContents.get(value)!;
|
|
@@ -289,9 +297,15 @@ export class KTSelectSearch {
|
|
|
289
297
|
// Restore original content before filtering, so highlighting is applied fresh.
|
|
290
298
|
this._restoreOptionContentsBeforeFilter();
|
|
291
299
|
|
|
292
|
-
const visibleCount = filterOptions(
|
|
293
|
-
|
|
300
|
+
const visibleCount = filterOptions(
|
|
301
|
+
options,
|
|
302
|
+
query,
|
|
303
|
+
config,
|
|
304
|
+
dropdownElement,
|
|
305
|
+
(count) => this._handleNoResults(count),
|
|
294
306
|
);
|
|
307
|
+
|
|
308
|
+
this._select.updateSelectAllButtonState();
|
|
295
309
|
}
|
|
296
310
|
|
|
297
311
|
/**
|
|
@@ -324,6 +338,7 @@ export class KTSelectSearch {
|
|
|
324
338
|
});
|
|
325
339
|
|
|
326
340
|
this._clearNoResultsMessage(); // Ensure no results message is cleared when resetting
|
|
341
|
+
this._select.updateSelectAllButtonState();
|
|
327
342
|
}
|
|
328
343
|
|
|
329
344
|
private _handleNoResults(visibleOptionsCount: number) {
|