@keenthemes/ktui 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ktui.js +668 -685
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +5822 -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 +19 -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 +1 -1
- 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 +1 -3
- 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 +174 -120
- 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 -103
- 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 +1 -1
- 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 +1 -3
- 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 +174 -120
- 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 -103
- 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 +1 -1
- package/src/components/input/input.css +1 -1
- package/src/components/scrollable/scrollable.css +9 -5
- package/src/components/select/combobox.ts +99 -88
- package/src/components/select/config.ts +2 -8
- 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 +97 -27
- package/src/components/select/select.ts +181 -127
- package/src/components/select/tags.ts +1 -27
- package/src/components/select/templates.ts +194 -131
- package/src/components/select/utils.ts +30 -166
- 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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { KTSelectConfigInterface } from './config';
|
|
7
7
|
import { KTSelect } from './select';
|
|
8
8
|
import { filterOptions, renderTemplateString, stringToElement } from './utils';
|
|
9
|
+
import { defaultTemplates } from './templates';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* KTSelectCombobox - Handles combobox-specific functionality for KTSelect
|
|
@@ -17,57 +18,36 @@ export class KTSelectCombobox {
|
|
|
17
18
|
private _clearButtonElement: HTMLElement | null;
|
|
18
19
|
private _boundInputHandler: (event: Event) => void;
|
|
19
20
|
private _boundClearHandler: (event: MouseEvent) => void;
|
|
20
|
-
private _valuesContainerElement: HTMLElement | null;
|
|
21
|
+
private _valuesContainerElement: HTMLElement | null; // For tags or displayTemplate output
|
|
21
22
|
|
|
22
23
|
constructor(select: KTSelect) {
|
|
23
24
|
this._select = select;
|
|
24
25
|
this._config = select.getConfig();
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
const displayElement = select.getValueDisplayElement();
|
|
28
|
-
|
|
29
|
-
// Find the input element - either it's the display element itself or a child
|
|
30
|
-
this._searchInputElement =
|
|
31
|
-
displayElement.tagName === 'INPUT'
|
|
32
|
-
? (displayElement as HTMLInputElement)
|
|
33
|
-
: displayElement.querySelector('input[data-kt-select-search]');
|
|
34
|
-
|
|
35
|
-
// Find the clear button robustly
|
|
36
|
-
let clearButtonContainer: HTMLElement | null = null;
|
|
37
|
-
if (displayElement.tagName === 'DIV') {
|
|
38
|
-
clearButtonContainer = displayElement;
|
|
39
|
-
} else if (displayElement.tagName === 'INPUT') {
|
|
40
|
-
clearButtonContainer = displayElement.parentElement as HTMLElement;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
this._clearButtonElement = clearButtonContainer
|
|
44
|
-
? clearButtonContainer.querySelector('[data-kt-select-clear-button]')
|
|
45
|
-
: null;
|
|
27
|
+
const displayElement = select.getDisplayElement(); // KTSelect's main display element for combobox
|
|
46
28
|
|
|
47
|
-
this.
|
|
29
|
+
this._searchInputElement = displayElement.querySelector('input[data-kt-select-search]');
|
|
30
|
+
this._clearButtonElement = displayElement.querySelector('[data-kt-select-clear-button]');
|
|
31
|
+
this._valuesContainerElement = displayElement.querySelector('[data-kt-select-combobox-values]');
|
|
48
32
|
|
|
49
|
-
// Create bound handler references to allow proper cleanup
|
|
50
33
|
this._boundInputHandler = this._handleComboboxInput.bind(this);
|
|
51
34
|
this._boundClearHandler = this._handleClearButtonClick.bind(this);
|
|
52
35
|
|
|
53
|
-
// Attach event listeners
|
|
54
36
|
this._attachEventListeners();
|
|
55
37
|
|
|
56
|
-
// Reset combobox search state when dropdown closes
|
|
57
38
|
this._select.getElement().addEventListener('dropdown.close', () => {
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
this.
|
|
39
|
+
// When dropdown closes, if not multi-select and not using displayTemplate,
|
|
40
|
+
// ensure input shows the selected value or placeholder.
|
|
41
|
+
if (!this._config.multiple && !this._config.displayTemplate) {
|
|
42
|
+
this.updateDisplay(this._select.getSelectedOptions());
|
|
43
|
+
} else {
|
|
44
|
+
// For tags or displayTemplate, the input should be clear for typing.
|
|
45
|
+
this._searchInputElement.value = '';
|
|
46
|
+
}
|
|
47
|
+
this._toggleClearButtonVisibility(this._searchInputElement.value);
|
|
48
|
+
// this._select.showAllOptions(); // showAllOptions might be too broad, filtering is managed by typing.
|
|
61
49
|
});
|
|
62
50
|
|
|
63
|
-
// When selection changes, update the input value to the selected option's text
|
|
64
|
-
// this._select.getElement().addEventListener('change', () => {
|
|
65
|
-
// // Only update the input value, do not reset the filter or show all options
|
|
66
|
-
// const selectedValues = this._select.getSelectedOptions();
|
|
67
|
-
// const content = this._select.renderDisplayTemplateForSelected(selectedValues);
|
|
68
|
-
// this._valuesContainerElement?.append(stringToElement(content));
|
|
69
|
-
// });
|
|
70
|
-
|
|
71
51
|
if (this._config.debug) console.log('KTSelectCombobox initialized');
|
|
72
52
|
}
|
|
73
53
|
|
|
@@ -75,25 +55,13 @@ export class KTSelectCombobox {
|
|
|
75
55
|
* Attach event listeners specific to combobox
|
|
76
56
|
*/
|
|
77
57
|
private _attachEventListeners(): void {
|
|
78
|
-
// First remove any existing listeners to prevent duplicates
|
|
79
58
|
this._removeEventListeners();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Add clear button click event listener
|
|
59
|
+
if (this._searchInputElement) { // Ensure element exists
|
|
60
|
+
this._searchInputElement.addEventListener('input', this._boundInputHandler);
|
|
61
|
+
}
|
|
85
62
|
if (this._clearButtonElement) {
|
|
86
|
-
this._clearButtonElement.addEventListener(
|
|
87
|
-
'click',
|
|
88
|
-
this._boundClearHandler,
|
|
89
|
-
);
|
|
63
|
+
this._clearButtonElement.addEventListener('click', this._boundClearHandler);
|
|
90
64
|
}
|
|
91
|
-
|
|
92
|
-
if (this._config.debug)
|
|
93
|
-
console.log(
|
|
94
|
-
'Combobox event listeners attached to:',
|
|
95
|
-
this._searchInputElement,
|
|
96
|
-
);
|
|
97
65
|
}
|
|
98
66
|
|
|
99
67
|
/**
|
|
@@ -101,17 +69,10 @@ export class KTSelectCombobox {
|
|
|
101
69
|
*/
|
|
102
70
|
private _removeEventListeners(): void {
|
|
103
71
|
if (this._searchInputElement) {
|
|
104
|
-
this._searchInputElement.removeEventListener(
|
|
105
|
-
'input',
|
|
106
|
-
this._boundInputHandler,
|
|
107
|
-
);
|
|
72
|
+
this._searchInputElement.removeEventListener('input', this._boundInputHandler);
|
|
108
73
|
}
|
|
109
|
-
|
|
110
74
|
if (this._clearButtonElement) {
|
|
111
|
-
this._clearButtonElement.removeEventListener(
|
|
112
|
-
'click',
|
|
113
|
-
this._boundClearHandler,
|
|
114
|
-
);
|
|
75
|
+
this._clearButtonElement.removeEventListener('click', this._boundClearHandler);
|
|
115
76
|
}
|
|
116
77
|
}
|
|
117
78
|
|
|
@@ -120,19 +81,23 @@ export class KTSelectCombobox {
|
|
|
120
81
|
*/
|
|
121
82
|
private _handleComboboxInput(event: Event): void {
|
|
122
83
|
const inputElement = event.target as HTMLInputElement;
|
|
123
|
-
const query = inputElement.value
|
|
84
|
+
const query = inputElement.value;
|
|
124
85
|
|
|
125
|
-
|
|
86
|
+
this._toggleClearButtonVisibility(query);
|
|
126
87
|
|
|
127
|
-
|
|
128
|
-
// this._toggleClearButtonVisibility(query);
|
|
129
|
-
|
|
130
|
-
// If dropdown isn't open, open it when user starts typing
|
|
131
|
-
if (!(this._select as any)._dropdownIsOpen) {
|
|
88
|
+
if (!(this._select as any).isDropdownOpen()) { // Use public getter
|
|
132
89
|
this._select.openDropdown();
|
|
133
90
|
}
|
|
91
|
+
// For single select without displayTemplate, if user types, they are essentially clearing the previous selection text
|
|
92
|
+
// The actual selection state isn't cleared until they pick a new option or clear explicitly.
|
|
93
|
+
// For multi-select or with displayTemplate, the input is purely for search.
|
|
94
|
+
if (this._config.multiple || this._config.displayTemplate) {
|
|
95
|
+
// Values are in _valuesContainerElement, input is for search
|
|
96
|
+
} else {
|
|
97
|
+
// Single select, no displayTemplate: If user types, it implies they might be changing selection.
|
|
98
|
+
// We don't clear the actual _select state here, just the visual in input.
|
|
99
|
+
}
|
|
134
100
|
|
|
135
|
-
// Filter options based on input
|
|
136
101
|
this._filterOptionsForCombobox(query);
|
|
137
102
|
}
|
|
138
103
|
|
|
@@ -143,35 +108,26 @@ export class KTSelectCombobox {
|
|
|
143
108
|
event.preventDefault();
|
|
144
109
|
event.stopPropagation();
|
|
145
110
|
|
|
146
|
-
// Clear the input
|
|
147
111
|
this._searchInputElement.value = '';
|
|
148
|
-
|
|
149
|
-
// Hide the clear button
|
|
150
|
-
// this._toggleClearButtonVisibility('');
|
|
151
|
-
|
|
152
|
-
// Show all options and open dropdown
|
|
153
|
-
this._select.showAllOptions();
|
|
154
|
-
this._select.openDropdown();
|
|
155
|
-
|
|
156
|
-
// Clear the current selection
|
|
157
|
-
this._select.clearSelection();
|
|
158
|
-
|
|
159
|
-
// Clear the combobox values container if present (for displayTemplate)
|
|
112
|
+
this._toggleClearButtonVisibility('');
|
|
160
113
|
if (this._valuesContainerElement) {
|
|
161
114
|
this._valuesContainerElement.innerHTML = '';
|
|
162
115
|
}
|
|
163
|
-
|
|
164
|
-
//
|
|
116
|
+
this._select.clearSelection(); // This will also trigger updateSelectedOptionDisplay
|
|
117
|
+
this._select.showAllOptions(); // Show all options after clearing
|
|
118
|
+
this._select.openDropdown();
|
|
165
119
|
this._searchInputElement.focus();
|
|
166
120
|
}
|
|
167
121
|
|
|
168
122
|
/**
|
|
169
|
-
* Toggle clear button visibility based on input value
|
|
123
|
+
* Toggle clear button visibility based on input value and selection state.
|
|
124
|
+
* Clear button should be visible if there's text in input OR if there are selected items (for multi/displayTemplate modes).
|
|
170
125
|
*/
|
|
171
|
-
private _toggleClearButtonVisibility(
|
|
126
|
+
private _toggleClearButtonVisibility(inputValue: string): void {
|
|
172
127
|
if (!this._clearButtonElement) return;
|
|
128
|
+
const hasSelectedItems = this._select.getSelectedOptions().length > 0;
|
|
173
129
|
|
|
174
|
-
if (
|
|
130
|
+
if (inputValue.length > 0 || (hasSelectedItems && (this._config.multiple || this._config.displayTemplate))) {
|
|
175
131
|
this._clearButtonElement.classList.remove('hidden');
|
|
176
132
|
} else {
|
|
177
133
|
this._clearButtonElement.classList.add('hidden');
|
|
@@ -182,11 +138,66 @@ export class KTSelectCombobox {
|
|
|
182
138
|
* Filter options for combobox based on input query
|
|
183
139
|
*/
|
|
184
140
|
private _filterOptionsForCombobox(query: string): void {
|
|
185
|
-
// Use the same filter logic as KTSelectSearch
|
|
186
141
|
const options = Array.from(this._select.getOptionsElement()) as HTMLElement[];
|
|
187
142
|
const config = this._select.getConfig();
|
|
188
143
|
const dropdownElement = this._select.getDropdownElement();
|
|
189
144
|
filterOptions(options, query, config, dropdownElement);
|
|
145
|
+
// After filtering, focusManager in KTSelectSearch (if search is also enabled there)
|
|
146
|
+
// or the main FocusManager should adjust focus if needed.
|
|
147
|
+
// For combobox, this filtering is the primary search mechanism.
|
|
148
|
+
// We might need to tell select's focus manager to focus first option.
|
|
149
|
+
(this._select as any)._focusManager.focusFirst(); // Consider if this is always right
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Updates the combobox display (input field or values container) based on selection.
|
|
154
|
+
*/
|
|
155
|
+
public updateDisplay(selectedOptions: string[]): void {
|
|
156
|
+
if (!this._searchInputElement) return;
|
|
157
|
+
|
|
158
|
+
// Always clear the values container first if it exists
|
|
159
|
+
if (this._valuesContainerElement) {
|
|
160
|
+
this._valuesContainerElement.innerHTML = '';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (this._config.tags && this._valuesContainerElement) { // Combobox + Tags
|
|
164
|
+
selectedOptions.forEach(value => {
|
|
165
|
+
// Ensure value is properly escaped for querySelector
|
|
166
|
+
const optionElement = this._select.getElement().querySelector(`option[value="${CSS.escape(value)}"]`) as HTMLOptionElement;
|
|
167
|
+
if (optionElement) {
|
|
168
|
+
const tagElement = defaultTemplates.tag(optionElement, this._config);
|
|
169
|
+
this._valuesContainerElement.appendChild(tagElement);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
this._searchInputElement.value = ''; // Input field is for typing new searches
|
|
173
|
+
this._searchInputElement.placeholder = selectedOptions.length > 0 ? '' : (this._config.placeholder || 'Select...');
|
|
174
|
+
|
|
175
|
+
} else if (this._config.displayTemplate && this._valuesContainerElement) { // Combobox + DisplayTemplate (no tags)
|
|
176
|
+
this._valuesContainerElement.innerHTML = this._select.renderDisplayTemplateForSelected(selectedOptions);
|
|
177
|
+
this._searchInputElement.value = ''; // Input field is for typing new searches
|
|
178
|
+
this._searchInputElement.placeholder = selectedOptions.length > 0 ? '' : (this._config.placeholder || 'Select...');
|
|
179
|
+
|
|
180
|
+
} else if (this._config.multiple && this._valuesContainerElement) { // Combobox + Multiple (no tags, no display template)
|
|
181
|
+
// For simplicity, join text. A proper tag implementation would be more complex here.
|
|
182
|
+
this._valuesContainerElement.innerHTML = selectedOptions.map(value => {
|
|
183
|
+
const optionEl = this._select.getElement().querySelector(`option[value="${CSS.escape(value)}"]`);
|
|
184
|
+
return optionEl ? optionEl.textContent : '';
|
|
185
|
+
}).join(', '); // Basic comma separation
|
|
186
|
+
this._searchInputElement.value = '';
|
|
187
|
+
this._searchInputElement.placeholder = selectedOptions.length > 0 ? '' : (this._config.placeholder || 'Select...');
|
|
188
|
+
|
|
189
|
+
} else if (!this._config.multiple && selectedOptions.length > 0) { // Single select combobox: display selected option's text in the input
|
|
190
|
+
const selectedValue = selectedOptions[0];
|
|
191
|
+
const optionElement = this._select.getElement().querySelector(`option[value="${CSS.escape(selectedValue)}"]`);
|
|
192
|
+
this._searchInputElement.value = optionElement ? optionElement.textContent || '' : '';
|
|
193
|
+
// placeholder is implicitly handled by input value for single select
|
|
194
|
+
|
|
195
|
+
} else { // No selection or not fitting above categories (e.g. single select, no items)
|
|
196
|
+
this._searchInputElement.value = '';
|
|
197
|
+
this._searchInputElement.placeholder = this._config.placeholder || 'Select...';
|
|
198
|
+
// _valuesContainerElement is already cleared if it exists
|
|
199
|
+
}
|
|
200
|
+
this._toggleClearButtonVisibility(this._searchInputElement.value);
|
|
190
201
|
}
|
|
191
202
|
|
|
192
203
|
/**
|
|
@@ -42,7 +42,6 @@ export const DefaultConfig: KTSelectConfigInterface = {
|
|
|
42
42
|
// Selection Behavior
|
|
43
43
|
multiple: false, // Enable/disable multi-select
|
|
44
44
|
maxSelections: null, // Maximum number of selections allowed in multi-select mode (null for unlimited)
|
|
45
|
-
closeOnSelect: false, // Close the dropdown after selecting an option (single-select only)
|
|
46
45
|
disabled: false, // Disable the select component
|
|
47
46
|
isRequired: false, // Make selection required
|
|
48
47
|
|
|
@@ -53,7 +52,6 @@ export const DefaultConfig: KTSelectConfigInterface = {
|
|
|
53
52
|
searchMinLength: 0, // Minimum characters required to trigger search
|
|
54
53
|
searchMaxItems: 50, // Maximum number of search results to display
|
|
55
54
|
searchNotFoundText: 'No results found', // Text to display when no search results are found
|
|
56
|
-
searchHighlight: true, // Highlight matching search terms within the options
|
|
57
55
|
clearSearchOnClose: true, // Clear search input when dropdown closes
|
|
58
56
|
|
|
59
57
|
// Multi-Select Display
|
|
@@ -88,7 +86,6 @@ export interface KTSelectConfigInterface {
|
|
|
88
86
|
// Selection Behavior
|
|
89
87
|
multiple?: boolean;
|
|
90
88
|
maxSelections?: number | null;
|
|
91
|
-
closeOnSelect?: boolean;
|
|
92
89
|
disabled?: boolean;
|
|
93
90
|
isRequired?: boolean;
|
|
94
91
|
|
|
@@ -99,7 +96,6 @@ export interface KTSelectConfigInterface {
|
|
|
99
96
|
searchMinLength?: number;
|
|
100
97
|
searchMaxItems?: number;
|
|
101
98
|
searchNotFoundText?: string;
|
|
102
|
-
searchHighlight?: boolean;
|
|
103
99
|
searchDebounce?: number;
|
|
104
100
|
searchParam?: string;
|
|
105
101
|
clearSearchOnClose?: boolean;
|
|
@@ -154,7 +150,6 @@ export interface KTSelectConfigInterface {
|
|
|
154
150
|
loadMoreClass?: string;
|
|
155
151
|
wrapperClass?: string;
|
|
156
152
|
errorClass?: string;
|
|
157
|
-
highlightClass?: string;
|
|
158
153
|
|
|
159
154
|
// New Config
|
|
160
155
|
tags?: boolean;
|
|
@@ -164,19 +159,17 @@ export interface KTSelectConfigInterface {
|
|
|
164
159
|
placeholderTemplate?: string;
|
|
165
160
|
displaySeparator?: string;
|
|
166
161
|
displayTemplate?: string;
|
|
167
|
-
displayToggle?: boolean;
|
|
168
162
|
displayMaxSelected?: number;
|
|
169
163
|
optionTemplate?: string;
|
|
170
164
|
optionClass?: string;
|
|
171
165
|
tagTemplate?: string;
|
|
172
|
-
displayToggleClass?: string;
|
|
173
|
-
displayToggleTemplate?: string;
|
|
174
166
|
|
|
175
167
|
templates?: Partial<typeof coreTemplateStrings>;
|
|
176
168
|
|
|
177
169
|
dropdownTemplate?: string;
|
|
178
170
|
|
|
179
171
|
// Option Configuration
|
|
172
|
+
config?: KTSelectConfigInterface; // config from data-kt-select-config attribute
|
|
180
173
|
optionsConfig?: Record<string, KTSelectConfigInterface>;
|
|
181
174
|
}
|
|
182
175
|
|
|
@@ -200,6 +193,7 @@ export class KTSelectState {
|
|
|
200
193
|
return {
|
|
201
194
|
...DefaultConfig,
|
|
202
195
|
...config,
|
|
196
|
+
...config.config,
|
|
203
197
|
};
|
|
204
198
|
}
|
|
205
199
|
|
|
@@ -13,13 +13,14 @@ import KTData from '../../helpers/data';
|
|
|
13
13
|
import KTComponent from '../component';
|
|
14
14
|
import { KTSelectConfigInterface } from './config';
|
|
15
15
|
import { FocusManager, EventManager } from './utils';
|
|
16
|
+
import { KTSelect } from './select'; // Added import
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* KTSelectDropdown
|
|
19
20
|
*
|
|
20
21
|
* A specialized dropdown implementation for the KTSelect component.
|
|
21
22
|
* This module handles the dropdown functionality for the select component,
|
|
22
|
-
* including positioning
|
|
23
|
+
* including positioning and showing/hiding.
|
|
23
24
|
*/
|
|
24
25
|
export class KTSelectDropdown extends KTComponent {
|
|
25
26
|
protected override readonly _name: string = 'select-dropdown';
|
|
@@ -36,6 +37,7 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
36
37
|
private _popperInstance: PopperInstance | null = null;
|
|
37
38
|
private _eventManager: EventManager;
|
|
38
39
|
private _focusManager: FocusManager;
|
|
40
|
+
private _ktSelectInstance: KTSelect; // Added instance variable
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* Constructor
|
|
@@ -49,6 +51,7 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
49
51
|
toggleElement: HTMLElement,
|
|
50
52
|
dropdownElement: HTMLElement,
|
|
51
53
|
config: KTSelectConfigInterface,
|
|
54
|
+
ktSelectInstance: KTSelect, // Added parameter
|
|
52
55
|
) {
|
|
53
56
|
super();
|
|
54
57
|
|
|
@@ -56,6 +59,7 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
56
59
|
this._toggleElement = toggleElement;
|
|
57
60
|
this._dropdownElement = dropdownElement;
|
|
58
61
|
this._config = config;
|
|
62
|
+
this._ktSelectInstance = ktSelectInstance; // Assign instance
|
|
59
63
|
this._eventManager = new EventManager();
|
|
60
64
|
this._focusManager = new FocusManager(
|
|
61
65
|
dropdownElement,
|
|
@@ -92,7 +96,20 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
92
96
|
event.preventDefault();
|
|
93
97
|
event.stopPropagation();
|
|
94
98
|
|
|
95
|
-
this.
|
|
99
|
+
if (this._config.disabled) {
|
|
100
|
+
if (this._config.debug)
|
|
101
|
+
console.log(
|
|
102
|
+
'KTSelectDropdown._handleToggleClick: select is disabled',
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Call KTSelect's methods
|
|
108
|
+
if (this._ktSelectInstance.isDropdownOpen()) {
|
|
109
|
+
this._ktSelectInstance.closeDropdown();
|
|
110
|
+
} else {
|
|
111
|
+
this._ktSelectInstance.openDropdown();
|
|
112
|
+
}
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
/**
|
|
@@ -107,7 +124,8 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
107
124
|
!this._element.contains(target) &&
|
|
108
125
|
!this._dropdownElement.contains(target)
|
|
109
126
|
) {
|
|
110
|
-
|
|
127
|
+
// Call KTSelect's closeDropdown method
|
|
128
|
+
this._ktSelectInstance.closeDropdown();
|
|
111
129
|
}
|
|
112
130
|
}
|
|
113
131
|
|
|
@@ -219,81 +237,50 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
219
237
|
}
|
|
220
238
|
}
|
|
221
239
|
|
|
222
|
-
/**
|
|
223
|
-
* Toggle the dropdown
|
|
224
|
-
*/
|
|
225
|
-
public toggle(): void {
|
|
226
|
-
if (this._config.disabled) {
|
|
227
|
-
if (this._config.debug) console.log('KTSelectDropdown.toggle: select is disabled, not toggling');
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
if (this._config.debug)
|
|
231
|
-
console.log('KTSelectDropdown.toggle called - isOpen:', this._isOpen);
|
|
232
|
-
|
|
233
|
-
if (this._isTransitioning) {
|
|
234
|
-
if (this._config.debug)
|
|
235
|
-
console.log('KTSelectDropdown.toggle - ignoring during transition');
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (this._isOpen) {
|
|
240
|
-
this.close();
|
|
241
|
-
} else {
|
|
242
|
-
this.open();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
240
|
/**
|
|
247
241
|
* Open the dropdown
|
|
248
242
|
*/
|
|
249
243
|
public open(): void {
|
|
250
244
|
if (this._config.disabled) {
|
|
251
|
-
if (this._config.debug)
|
|
245
|
+
if (this._config.debug)
|
|
246
|
+
console.log(
|
|
247
|
+
'KTSelectDropdown.open: select is disabled, not opening',
|
|
248
|
+
);
|
|
252
249
|
return;
|
|
253
250
|
}
|
|
254
251
|
if (this._isOpen || this._isTransitioning) return;
|
|
255
252
|
|
|
256
|
-
// Fire before show event
|
|
257
|
-
const beforeShowEvent = new CustomEvent('kt.select.dropdown.show', {
|
|
258
|
-
bubbles: true,
|
|
259
|
-
cancelable: true,
|
|
260
|
-
});
|
|
261
|
-
this._element.dispatchEvent(beforeShowEvent);
|
|
262
|
-
|
|
263
|
-
if (beforeShowEvent.defaultPrevented) return;
|
|
264
|
-
|
|
265
253
|
// Begin opening transition
|
|
266
254
|
this._isTransitioning = true;
|
|
267
255
|
|
|
268
|
-
// Set initial styles
|
|
256
|
+
// Set initial styles
|
|
269
257
|
this._dropdownElement.classList.remove('hidden');
|
|
270
258
|
this._dropdownElement.style.opacity = '0';
|
|
271
259
|
|
|
272
260
|
// Set dropdown width
|
|
273
261
|
this._setDropdownWidth();
|
|
274
262
|
|
|
275
|
-
//
|
|
263
|
+
// Reflow
|
|
276
264
|
KTDom.reflow(this._dropdownElement);
|
|
277
265
|
|
|
278
|
-
// Apply z-index
|
|
266
|
+
// Apply z-index
|
|
279
267
|
if (this._config.dropdownZindex) {
|
|
280
268
|
this._dropdownElement.style.zIndex =
|
|
281
269
|
this._config.dropdownZindex.toString();
|
|
282
270
|
} else {
|
|
283
|
-
// Auto-calculate z-index
|
|
284
271
|
const parentZindex = KTDom.getHighestZindex(this._element);
|
|
285
272
|
if (parentZindex) {
|
|
286
273
|
this._dropdownElement.style.zIndex = (parentZindex + 1).toString();
|
|
287
274
|
}
|
|
288
275
|
}
|
|
289
276
|
|
|
290
|
-
// Initialize popper
|
|
277
|
+
// Initialize popper
|
|
291
278
|
this._initPopper();
|
|
292
279
|
|
|
293
|
-
// Add active classes
|
|
280
|
+
// Add active classes for visual state
|
|
294
281
|
this._dropdownElement.classList.add('open');
|
|
295
282
|
this._toggleElement.classList.add('active');
|
|
296
|
-
|
|
283
|
+
// ARIA attributes will be handled by KTSelect
|
|
297
284
|
|
|
298
285
|
// Start transition
|
|
299
286
|
this._dropdownElement.style.opacity = '1';
|
|
@@ -302,36 +289,10 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
302
289
|
KTDom.transitionEnd(this._dropdownElement, () => {
|
|
303
290
|
this._isTransitioning = false;
|
|
304
291
|
this._isOpen = true;
|
|
305
|
-
|
|
306
|
-
// Focus the first item if search is enabled
|
|
307
|
-
if (this._config.enableSearch) {
|
|
308
|
-
const searchInput = this._dropdownElement.querySelector(
|
|
309
|
-
'input[type="search"]',
|
|
310
|
-
);
|
|
311
|
-
if (searchInput) {
|
|
312
|
-
(searchInput as HTMLInputElement).focus();
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Fire after show event
|
|
317
|
-
const afterShowEvent = new CustomEvent('kt.select.dropdown.shown', {
|
|
318
|
-
bubbles: true,
|
|
319
|
-
});
|
|
320
|
-
this._element.dispatchEvent(afterShowEvent);
|
|
292
|
+
// Focus and events will be handled by KTSelect
|
|
321
293
|
});
|
|
322
294
|
}
|
|
323
295
|
|
|
324
|
-
/**
|
|
325
|
-
* Focus the first option in the dropdown
|
|
326
|
-
*/
|
|
327
|
-
private _focusFirstOption(): void {
|
|
328
|
-
const firstOption = this._focusManager.getVisibleOptions()[0];
|
|
329
|
-
if (firstOption) {
|
|
330
|
-
this._focusManager.applyFocus(firstOption);
|
|
331
|
-
this._focusManager.scrollIntoView(firstOption);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
296
|
/**
|
|
336
297
|
* Close the dropdown
|
|
337
298
|
*/
|
|
@@ -352,42 +313,23 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
352
313
|
return;
|
|
353
314
|
}
|
|
354
315
|
|
|
355
|
-
//
|
|
356
|
-
const beforeHideEvent = new CustomEvent('kt.select.dropdown.hide', {
|
|
357
|
-
bubbles: true,
|
|
358
|
-
cancelable: true,
|
|
359
|
-
});
|
|
360
|
-
this._element.dispatchEvent(beforeHideEvent);
|
|
361
|
-
|
|
362
|
-
if (beforeHideEvent.defaultPrevented) {
|
|
363
|
-
if (this._config.debug)
|
|
364
|
-
console.log(
|
|
365
|
-
'KTSelectDropdown.close - canceling due to defaultPrevented on beforeHideEvent',
|
|
366
|
-
);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
316
|
+
// Events and ARIA will be handled by KTSelect
|
|
369
317
|
|
|
370
318
|
if (this._config.debug)
|
|
371
319
|
console.log('KTSelectDropdown.close - starting transition');
|
|
372
|
-
// Begin closing transition
|
|
373
320
|
this._isTransitioning = true;
|
|
374
321
|
|
|
375
|
-
// Start transition
|
|
376
322
|
this._dropdownElement.style.opacity = '0';
|
|
377
323
|
|
|
378
|
-
// Use a combination of transition end and a fallback timer
|
|
379
324
|
let transitionComplete = false;
|
|
380
|
-
|
|
381
|
-
// Set a fixed-duration fallback in case the transition event doesn't fire
|
|
382
325
|
const fallbackTimer = setTimeout(() => {
|
|
383
326
|
if (!transitionComplete) {
|
|
384
327
|
if (this._config.debug)
|
|
385
328
|
console.log('KTSelectDropdown.close - fallback timer triggered');
|
|
386
329
|
completeTransition();
|
|
387
330
|
}
|
|
388
|
-
}, 300);
|
|
331
|
+
}, 300);
|
|
389
332
|
|
|
390
|
-
// Setup the transition end function
|
|
391
333
|
const completeTransition = () => {
|
|
392
334
|
if (transitionComplete) return;
|
|
393
335
|
transitionComplete = true;
|
|
@@ -395,35 +337,28 @@ export class KTSelectDropdown extends KTComponent {
|
|
|
395
337
|
|
|
396
338
|
if (this._config.debug)
|
|
397
339
|
console.log('KTSelectDropdown.close - transition ended');
|
|
398
|
-
|
|
340
|
+
|
|
399
341
|
this._dropdownElement.classList.add('hidden');
|
|
400
342
|
this._dropdownElement.classList.remove('open');
|
|
401
343
|
this._toggleElement.classList.remove('active');
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
// Reset styles - replace display: none with adding hidden class
|
|
405
|
-
this._dropdownElement.classList.add('hidden');
|
|
406
|
-
this._dropdownElement.style.opacity = '';
|
|
407
|
-
this._dropdownElement.style.zIndex = '';
|
|
344
|
+
// ARIA attributes will be handled by KTSelect
|
|
408
345
|
|
|
409
|
-
// Destroy popper
|
|
410
346
|
this._destroyPopper();
|
|
411
347
|
|
|
412
|
-
// Update state
|
|
413
348
|
this._isTransitioning = false;
|
|
414
349
|
this._isOpen = false;
|
|
415
350
|
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
bubbles: true,
|
|
419
|
-
});
|
|
420
|
-
this._element.dispatchEvent(afterHideEvent);
|
|
351
|
+
// Events will be handled by KTSelect
|
|
352
|
+
|
|
421
353
|
if (this._config.debug)
|
|
422
|
-
console.log('KTSelectDropdown.close -
|
|
354
|
+
console.log('KTSelectDropdown.close - visual part complete');
|
|
423
355
|
};
|
|
424
356
|
|
|
425
|
-
// Handle transition end via the utility but also have the fallback
|
|
426
357
|
KTDom.transitionEnd(this._dropdownElement, completeTransition);
|
|
358
|
+
|
|
359
|
+
if (KTDom.getCssProp(this._dropdownElement, 'transition-duration') === '0s') {
|
|
360
|
+
completeTransition();
|
|
361
|
+
}
|
|
427
362
|
}
|
|
428
363
|
|
|
429
364
|
/**
|