@keenthemes/ktui 1.0.12 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/ktui.js +738 -700
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/dist/styles.css +5824 -0
  5. package/examples/select/avatar.html +47 -0
  6. package/examples/select/basic-usage.html +10 -14
  7. package/examples/select/{test.html → combobox-icons_.html} +13 -48
  8. package/examples/select/country.html +43 -0
  9. package/examples/select/description.html +25 -41
  10. package/examples/select/disable-option.html +10 -16
  11. package/examples/select/disable-select.html +7 -6
  12. package/examples/select/icon-multiple.html +23 -31
  13. package/examples/select/icon.html +20 -30
  14. package/examples/select/max-selection.html +8 -9
  15. package/examples/select/modal.html +16 -17
  16. package/examples/select/multiple.html +11 -13
  17. package/examples/select/placeholder.html +9 -12
  18. package/examples/select/search.html +30 -22
  19. package/examples/select/sizes.html +94 -0
  20. package/examples/select/template-customization.html +0 -3
  21. package/lib/cjs/components/component.js +1 -1
  22. package/lib/cjs/components/component.js.map +1 -1
  23. package/lib/cjs/components/datatable/datatable.js +14 -11
  24. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  25. package/lib/cjs/components/select/combobox.js +96 -61
  26. package/lib/cjs/components/select/combobox.js.map +1 -1
  27. package/lib/cjs/components/select/config.js +13 -8
  28. package/lib/cjs/components/select/config.js.map +1 -1
  29. package/lib/cjs/components/select/dropdown.js +32 -96
  30. package/lib/cjs/components/select/dropdown.js.map +1 -1
  31. package/lib/cjs/components/select/option.js +53 -20
  32. package/lib/cjs/components/select/option.js.map +1 -1
  33. package/lib/cjs/components/select/search.js +146 -97
  34. package/lib/cjs/components/select/search.js.map +1 -1
  35. package/lib/cjs/components/select/select.js +219 -118
  36. package/lib/cjs/components/select/select.js.map +1 -1
  37. package/lib/cjs/components/select/tags.js +0 -26
  38. package/lib/cjs/components/select/tags.js.map +1 -1
  39. package/lib/cjs/components/select/templates.js +130 -105
  40. package/lib/cjs/components/select/templates.js.map +1 -1
  41. package/lib/cjs/components/select/utils.js +33 -132
  42. package/lib/cjs/components/select/utils.js.map +1 -1
  43. package/lib/cjs/helpers/dom.js +0 -24
  44. package/lib/cjs/helpers/dom.js.map +1 -1
  45. package/lib/esm/components/component.js +1 -1
  46. package/lib/esm/components/component.js.map +1 -1
  47. package/lib/esm/components/datatable/datatable.js +14 -11
  48. package/lib/esm/components/datatable/datatable.js.map +1 -1
  49. package/lib/esm/components/select/combobox.js +96 -61
  50. package/lib/esm/components/select/combobox.js.map +1 -1
  51. package/lib/esm/components/select/config.js +13 -8
  52. package/lib/esm/components/select/config.js.map +1 -1
  53. package/lib/esm/components/select/dropdown.js +32 -96
  54. package/lib/esm/components/select/dropdown.js.map +1 -1
  55. package/lib/esm/components/select/option.js +53 -20
  56. package/lib/esm/components/select/option.js.map +1 -1
  57. package/lib/esm/components/select/search.js +146 -97
  58. package/lib/esm/components/select/search.js.map +1 -1
  59. package/lib/esm/components/select/select.js +219 -118
  60. package/lib/esm/components/select/select.js.map +1 -1
  61. package/lib/esm/components/select/tags.js +0 -26
  62. package/lib/esm/components/select/tags.js.map +1 -1
  63. package/lib/esm/components/select/templates.js +130 -105
  64. package/lib/esm/components/select/templates.js.map +1 -1
  65. package/lib/esm/components/select/utils.js +32 -130
  66. package/lib/esm/components/select/utils.js.map +1 -1
  67. package/lib/esm/helpers/dom.js +0 -24
  68. package/lib/esm/helpers/dom.js.map +1 -1
  69. package/package.json +9 -6
  70. package/src/components/component.ts +0 -4
  71. package/src/components/datatable/datatable.ts +14 -11
  72. package/src/components/input/input.css +1 -1
  73. package/src/components/scrollable/scrollable.css +9 -5
  74. package/src/components/select/combobox.ts +98 -87
  75. package/src/components/select/config.ts +16 -13
  76. package/src/components/select/dropdown.ts +43 -108
  77. package/src/components/select/option.ts +44 -25
  78. package/src/components/select/search.ts +158 -117
  79. package/src/components/select/select.css +99 -27
  80. package/src/components/select/select.ts +236 -128
  81. package/src/components/select/tags.ts +1 -27
  82. package/src/components/select/templates.ts +191 -132
  83. package/src/components/select/utils.ts +30 -166
  84. package/src/components/toast/toast.css +1 -1
  85. package/src/helpers/dom.ts +0 -30
  86. package/webpack.config.js +6 -1
  87. package/examples/select/combobox-icons.html +0 -58
  88. package/examples/select/icon-description.html +0 -56
  89. /package/examples/select/{combobox.html → combobox_.html} +0 -0
  90. /package/examples/select/{remote-data.html → remote-data_.html} +0 -0
  91. /package/examples/select/{tags-icons.html → tags-icons_.html} +0 -0
  92. /package/examples/select/{tags-selected.html → tags-selected_.html} +0 -0
  93. /package/examples/select/{tags.html → tags_.html} +0 -0
@@ -11,14 +11,14 @@ import { renderTemplateString } from './utils';
11
11
  * Users can override any template by providing a matching key in the config.templates object.
12
12
  */
13
13
  export const coreTemplateStrings = {
14
- dropdown: `<div data-kt-select-dropdown class="kt-select-dropdown hidden {{class}}" style="z-index: {{zindex}};">{{content}}</div>`,
15
- options: `<ul role="listbox" aria-label="{{label}}" class="kt-select-options {{class}}" data-kt-select-options="true">{{content}}</ul>`,
16
- error: `<li class="kt-select-error" role="alert">{{content}}</li>`,
17
- highlight: `<span data-kt-select-highlight class="kt-select-highlight highlighted {{class}}">{{text}}</span>`,
14
+ dropdown: `<div data-kt-select-dropdown class="kt-select-dropdown hidden {{class}}" style="z-index: {{zindex}};"></div>`,
15
+ options: `<ul role="listbox" aria-label="{{label}}" class="kt-select-options {{class}}" data-kt-select-options="true"></ul>`,
16
+ error: `<li class="kt-select-error" role="alert"></li>`,
18
17
  wrapper: `<div data-kt-select-wrapper class="kt-select-wrapper {{class}}"></div>`,
19
18
  combobox: `
20
19
  <div data-kt-select-combobox data-kt-select-display class="kt-select-combobox {{class}}">
21
- <input class="kt-input kt-select-combobox-input" data-kt-select-search="true" data-kt-select-value="true" type="text" placeholder="{{placeholder}}" role="searchbox" aria-label="{{label}}" {{disabled}} />
20
+ <div data-kt-select-combobox-values="true" class="kt-select-combobox-values"></div>
21
+ <input class="kt-input kt-select-combobox-input" data-kt-select-search="true" type="text" placeholder="{{placeholder}}" role="searchbox" aria-label="{{label}}" {{disabled}} />
22
22
  <button type="button" data-kt-select-clear-button="true" class="kt-select-combobox-clear-btn" aria-label="Clear selection">
23
23
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
24
24
  <line x1="18" y1="6" x2="6" y2="18"></line>
@@ -27,20 +27,22 @@ export const coreTemplateStrings = {
27
27
  </button>
28
28
  </div>
29
29
  `,
30
+ placeholder: `<div data-kt-select-placeholder class="kt-select-placeholder {{class}}"></div>`,
30
31
  display: `
31
32
  <div data-kt-select-display class="kt-select-display {{class}}" tabindex="{{tabindex}}" role="button" data-selected="0" aria-haspopup="listbox" aria-expanded="false" aria-label="{{label}}" {{disabled}}>
32
- <div data-kt-select-value="true" class="kt-select-label">{{content}}</div>
33
+ <div class="kt-select-option-text" data-kt-text-container="true">{{text}}</div>
33
34
  </div>
34
35
  `,
35
- placeholder: `<div data-kt-select-placeholder class="kt-select-placeholder {{class}}">{{content}}</div>`,
36
- option: `<li data-kt-select-option data-value="{{value}}" data-text="{{text}}" class="kt-select-option {{class}}" role="option" {{selected}} {{disabled}}>{{content}}</li>`,
37
- search: `<div data-kt-select-search class="kt-select-search {{class}}"><input type="text" data-kt-select-search="true" placeholder="{{searchPlaceholder}}" class="kt-input kt-select-search-input" role="searchbox" aria-label="{{searchPlaceholder}}"/></div>`,
38
- empty: `<li data-kt-select-empty class="kt-select-no-result {{class}}" role="status">{{content}}</li>`,
39
- loading: `<li class="kt-select-loading {{class}}" role="status" aria-live="polite">{{content}}</li>`,
40
- tag: `<div data-kt-select-tag="true" class="kt-select-tag {{class}}">
41
- {{content}}
42
- </div>`,
43
- loadMore: `<li class="kt-select-load-more {{class}}" data-kt-select-load-more="true">{{content}}</li>`,
36
+ option: `
37
+ <li data-kt-select-option data-value="{{value}}" data-text="{{text}}" class="kt-select-option {{class}}" role="option" {{selected}} {{disabled}}>
38
+ <div class="kt-select-option-text" data-kt-text-container="true">{{text}}</div><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-3.5 ms-auto hidden text-primary kt-select-option-selected:block"><path d="M20 6 9 17l-5-5"/></svg>
39
+ </li>
40
+ `,
41
+ search: `<div data-kt-select-search class="kt-select-search {{class}}"><input type="text" data-kt-select-search="true" placeholder="{{searchPlaceholder}}" class="kt-input kt-input-ghost" role="searchbox" aria-label="{{searchPlaceholder}}"/></div>`,
42
+ empty: `<li data-kt-select-empty class="kt-select-no-result {{class}}" role="status"></li>`,
43
+ loading: `<li class="kt-select-loading {{class}}" role="status" aria-live="polite"></li>`,
44
+ tag: `<div data-kt-select-tag="true" class="kt-select-tag {{class}}"></div>`,
45
+ loadMore: `<li class="kt-select-load-more {{class}}" data-kt-select-load-more="true"></li>`,
44
46
  tagRemoveButton: `<button type="button" data-kt-select-remove-button class="kt-select-tag-remove" aria-label="Remove tag" tabindex="0"><svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="3" x2="9" y2="9"/><line x1="9" y1="3" x2="3" y2="9"/></svg></button>`,
45
47
  };
46
48
 
@@ -68,9 +70,9 @@ export interface KTSelectTemplateInterface {
68
70
  /**
69
71
  * Renders an error message in the dropdown
70
72
  */
71
- error: (config: KTSelectConfigInterface & { errorMessage: string }) => string;
72
-
73
- highlight: (config: KTSelectConfigInterface, text: string) => HTMLElement;
73
+ error: (
74
+ config: KTSelectConfigInterface & { errorMessage: string },
75
+ ) => HTMLElement;
74
76
 
75
77
  // Main components
76
78
  wrapper: (config: KTSelectConfigInterface) => HTMLElement;
@@ -91,7 +93,10 @@ export interface KTSelectTemplateInterface {
91
93
  ) => HTMLElement;
92
94
 
93
95
  // Multi-select
94
- tag: (option: HTMLOptionElement, config: KTSelectConfigInterface) => HTMLElement;
96
+ tag: (
97
+ option: HTMLOptionElement,
98
+ config: KTSelectConfigInterface,
99
+ ) => HTMLElement;
95
100
 
96
101
  placeholder: (config: KTSelectConfigInterface) => HTMLElement;
97
102
  }
@@ -143,15 +148,6 @@ export function getTemplateStrings(
143
148
  * Default templates for KTSelect component
144
149
  */
145
150
  export const defaultTemplates: KTSelectTemplateInterface = {
146
- /**
147
- * Renders a highlighted text
148
- */
149
- highlight: (config: KTSelectConfigInterface, text: string) => {
150
- const template = getTemplateStrings(config).highlight;
151
- const html = template.replace('{{text}}', text).replace('{{class}}', config.highlightClass || '');
152
- return stringToElement(html);
153
- },
154
-
155
151
  /**
156
152
  * Renders the dropdown content
157
153
  */
@@ -159,17 +155,28 @@ export const defaultTemplates: KTSelectTemplateInterface = {
159
155
  config: KTSelectConfigInterface & { zindex?: number; content?: string },
160
156
  ) => {
161
157
  let template = getTemplateStrings(config).dropdown;
162
- let content = config.content || '';
158
+ // If a custom dropdownTemplate is provided, it's responsible for its own content.
159
+ // Otherwise, the base template is used, and content is appended later.
163
160
  if (config.dropdownTemplate) {
164
- content = renderTemplateString(config.dropdownTemplate, {
165
- zindex: config.zindex ? String(config.zindex) : '',
166
- content: config.content || '',
167
- class: config.dropdownClass || '',
168
- });
161
+ const renderedCustomTemplate = renderTemplateString(
162
+ config.dropdownTemplate,
163
+ {
164
+ zindex: config.zindex ? String(config.zindex) : '',
165
+ // content: config.content || '', // No longer pass content to custom template directly here
166
+ class: config.dropdownClass || '',
167
+ },
168
+ );
169
+ // The custom template IS the dropdown element
170
+ const customDropdownEl = stringToElement(renderedCustomTemplate);
171
+ if (config.zindex) customDropdownEl.style.zIndex = String(config.zindex);
172
+ if (config.dropdownClass)
173
+ customDropdownEl.classList.add(...config.dropdownClass.split(' '));
174
+ return customDropdownEl;
169
175
  }
176
+
170
177
  const html = template
171
178
  .replace('{{zindex}}', config.zindex ? String(config.zindex) : '')
172
- .replace('{{content}}', content)
179
+ // .replace('{{content}}', '') // Content is no longer part of the base template string
173
180
  .replace('{{class}}', config.dropdownClass || '');
174
181
  return stringToElement(html);
175
182
  },
@@ -182,7 +189,7 @@ export const defaultTemplates: KTSelectTemplateInterface = {
182
189
  const html = template
183
190
  .replace('{{label}}', config.label || 'Options')
184
191
  .replace('{{height}}', config.height ? String(config.height) : '250')
185
- .replace('{{options}}', config.options || '')
192
+ // .replace('{{options}}', '') // Options are now appended dynamically
186
193
  .replace('{{class}}', config.optionsClass || '');
187
194
  return stringToElement(html);
188
195
  },
@@ -191,32 +198,38 @@ export const defaultTemplates: KTSelectTemplateInterface = {
191
198
  * Renders the load more button for pagination
192
199
  */
193
200
  loadMore: (config: KTSelectConfigInterface): HTMLElement => {
194
- let html = getTemplateStrings(config).loadMore.replace(
195
- '{{loadMoreText}}',
196
- config.loadMoreText || 'Load more...',
197
- );
198
- return stringToElement(html);
201
+ let html = getTemplateStrings(config)
202
+ .loadMore // .replace('{{loadMoreText}}', config.loadMoreText || 'Load more...') // Content is no longer in template string
203
+ .replace('{{class}}', config.loadMoreClass || '');
204
+ const element = stringToElement(html);
205
+ element.textContent = config.loadMoreText || 'Load more...';
206
+ return element;
199
207
  },
200
208
  /**
201
209
  * Renders an error message in the dropdown
202
210
  */
203
211
  error: (
204
212
  config: KTSelectConfigInterface & { errorMessage: string },
205
- ): string => {
213
+ ): HTMLElement => {
214
+ // Changed return type to HTMLElement
206
215
  const template = getTemplateStrings(config).error;
207
- return template
208
- .replace('{{errorMessage}}', config.errorMessage || 'An error occurred')
216
+ const html = template
217
+ // .replace('{{errorMessage}}', config.errorMessage || 'An error occurred') // Content is no longer in template string
209
218
  .replace('{{class}}', config.errorClass || '');
219
+ const element = stringToElement(html);
220
+ element.textContent = config.errorMessage || 'An error occurred';
221
+ return element;
210
222
  },
223
+
211
224
  /**
212
225
  * Renders the main container for the select component
213
226
  */
214
227
  wrapper: (config: KTSelectConfigInterface): HTMLElement => {
215
- const html = getTemplateStrings(config).wrapper
216
- .replace('{{class}}', config.wrapperClass || '');
228
+ const html = getTemplateStrings(config).wrapper.replace(
229
+ '{{class}}',
230
+ config.wrapperClass || '',
231
+ );
217
232
  const element = stringToElement(html);
218
- element.setAttribute('data-kt-select-combobox', config.combobox ? 'true' : 'false');
219
- element.setAttribute('data-kt-select-tags', config.tags ? 'true' : 'false');
220
233
  return element;
221
234
  },
222
235
 
@@ -224,28 +237,21 @@ export const defaultTemplates: KTSelectTemplateInterface = {
224
237
  * Renders the display element (trigger) for the select
225
238
  */
226
239
  display: (config: KTSelectConfigInterface): HTMLElement => {
227
- if (config.combobox) {
228
- let html = getTemplateStrings(config)
229
- .combobox.replace(/{{placeholder}}/g, config.placeholder || 'Select...')
230
- .replace(
231
- /{{label}}/g,
232
- config.label || config.placeholder || 'Select...',
233
- )
234
- .replace('{{disabled}}', config.disabled ? 'disabled' : '')
235
- .replace('{{class}}', config.displayClass || '');
236
- return stringToElement(html);
237
- }
238
-
239
- let content = config.label || config.placeholder || 'Select...';
240
-
241
- let html = getTemplateStrings(config).display
242
- .replace('{{tabindex}}', config.disabled ? '-1' : '0')
240
+ let html = getTemplateStrings(config)
241
+ .display.replace('{{tabindex}}', config.disabled ? '-1' : '0')
243
242
  .replace('{{label}}', config.label || config.placeholder || 'Select...')
244
243
  .replace('{{disabled}}', config.disabled ? 'aria-disabled="true"' : '')
245
244
  .replace('{{placeholder}}', config.placeholder || 'Select...')
246
- .replace('{{class}}', config.displayClass || '')
247
- .replace('{{content}}', content);
248
- return stringToElement(html);
245
+ .replace('{{class}}', config.displayClass || '');
246
+
247
+ const element = stringToElement(html);
248
+
249
+ // Add data-multiple attribute if in multiple select mode
250
+ if (config.multiple) {
251
+ element.setAttribute('data-multiple', 'true');
252
+ }
253
+
254
+ return element;
249
255
  },
250
256
 
251
257
  /**
@@ -256,37 +262,71 @@ export const defaultTemplates: KTSelectTemplateInterface = {
256
262
  config: KTSelectConfigInterface,
257
263
  ): HTMLElement => {
258
264
  const isHtmlOption = option instanceof HTMLOptionElement;
265
+ let optionData: Record<string, any>;
266
+
267
+ if (isHtmlOption) {
268
+ // If it's a plain HTMLOptionElement, construct data similarly to how KTSelectOption would
269
+ // This branch might be less common if KTSelectOption instances are always used for rendering.
270
+ const el = option as HTMLOptionElement;
271
+ const textContent = el.textContent || '';
272
+ optionData = {
273
+ value: el.value,
274
+ text: textContent,
275
+ selected: el.selected,
276
+ disabled: el.disabled, // This captures original disabled state
277
+ content: textContent, // Default content to text
278
+ // Attempt to get custom config for this specific option value if available
279
+ ...(config.optionsConfig?.[el.value] || {}),
280
+ };
281
+ } else {
282
+ // If it's a KTSelectOption class instance (from './option')
283
+ // which should have the getOptionDataForTemplate method.
284
+ optionData = (
285
+ option as import('./option').KTSelectOption
286
+ ).getOptionDataForTemplate();
287
+ }
259
288
 
260
- const value = isHtmlOption ? option.value : (option as KTSelectOption).id;
261
- const text = isHtmlOption ? option.text : (option as KTSelectOption).title;
262
- const disabled = isHtmlOption
263
- ? option.disabled
264
- : (option as any).disabled === true;
265
- const selected = isHtmlOption
266
- ? option.selected
267
- : !!(option as KTSelectOption).selected;
289
+ let content = optionData.text; // Default content to option's text
268
290
 
269
- let content = text;
270
291
  if (config.optionTemplate) {
271
- // Use the user template to render the content, but only for {{content}}
272
- content = renderTemplateString(config.optionTemplate, {
273
- value,
274
- text,
275
- class: config.optionClass || '',
276
- selected: selected ? 'aria-selected="true"' : 'aria-selected="false"',
277
- disabled: disabled ? 'aria-disabled="true"' : '',
278
- content: text,
279
- });
292
+ // Use the user-provided template string, rendering with the full optionData.
293
+ // renderTemplateString will replace {{key}} with values from optionData.
294
+ content = renderTemplateString(config.optionTemplate, optionData);
295
+ } else {
296
+ content = optionData.text || optionData.content; // Prefer explicit text, fallback to content
280
297
  }
281
298
 
282
- const html = getTemplateStrings(config).option
283
- .replace('{{value}}', value)
284
- .replace('{{text}}', text)
285
- .replace('{{selected}}', selected ? 'aria-selected="true"' : 'aria-selected="false"')
286
- .replace('{{disabled}}', disabled ? 'aria-disabled="true"' : '')
287
- .replace('{{content}}', content)
288
- .replace('{{class}}', config.optionClass || '');
289
- return stringToElement(html);
299
+ // Use the core option template string as the base structure.
300
+ const baseTemplate = getTemplateStrings(config).option;
301
+
302
+ const optionClasses = [config.optionClass || ''];
303
+ if (optionData.disabled) {
304
+ optionClasses.push('disabled');
305
+ }
306
+
307
+ // Populate the base template for the <li> attributes.
308
+ // The actual display content (text or custom HTML) will be set on the inner span later.
309
+ const html = renderTemplateString(baseTemplate, {
310
+ ...optionData, // Pass all data for {{value}}, {{text}}, {{selected}}, {{disabled}}, etc.
311
+ class: optionClasses.join(' ').trim() || '',
312
+ selected: optionData.selected
313
+ ? 'aria-selected="true"'
314
+ : 'aria-selected="false"',
315
+ disabled: optionData.disabled ? 'aria-disabled="true"' : '',
316
+ content: content, // This is for the {{content}} placeholder within the option template string itself
317
+ });
318
+
319
+ const element = stringToElement(html);
320
+
321
+ // If a custom option template is provided, replace the element's innerHTML with the content.
322
+ if (config.optionTemplate) {
323
+ element.innerHTML = content;
324
+ }
325
+
326
+ // Ensure data-text attribute is set to the original, clean text for searching/filtering
327
+ element.setAttribute('data-text', optionData?.text?.trim() || '');
328
+
329
+ return element;
290
330
  },
291
331
 
292
332
  /**
@@ -306,13 +346,13 @@ export const defaultTemplates: KTSelectTemplateInterface = {
306
346
  * Renders the no results message
307
347
  */
308
348
  empty: (config: KTSelectConfigInterface): HTMLElement => {
309
- let html = getTemplateStrings(config)
310
- .empty.replace(
311
- '{{searchNotFoundText}}',
312
- config.searchNotFoundText || 'No results found',
313
- )
314
- .replace('{{class}}', config.emptyClass || '');
315
- return stringToElement(html);
349
+ let html = getTemplateStrings(config).empty.replace(
350
+ '{{class}}',
351
+ config.emptyClass || '',
352
+ );
353
+ const element = stringToElement(html);
354
+ element.textContent = config.searchNotFoundText || 'No results found';
355
+ return element;
316
356
  },
317
357
 
318
358
  /**
@@ -322,13 +362,13 @@ export const defaultTemplates: KTSelectTemplateInterface = {
322
362
  config: KTSelectConfigInterface,
323
363
  loadingMessage: string,
324
364
  ): HTMLElement => {
325
- let html = getTemplateStrings(config)
326
- .loading.replace(
327
- '{{loadingMessage}}',
328
- loadingMessage || 'Loading options...',
329
- )
330
- .replace('{{class}}', config.loadingClass || '');
331
- return stringToElement(html);
365
+ let html = getTemplateStrings(config).loading.replace(
366
+ '{{class}}',
367
+ config.loadingClass || '',
368
+ );
369
+ const element = stringToElement(html);
370
+ element.textContent = loadingMessage || 'Loading options...';
371
+ return element;
332
372
  },
333
373
 
334
374
  /**
@@ -339,45 +379,60 @@ export const defaultTemplates: KTSelectTemplateInterface = {
339
379
  config: KTSelectConfigInterface,
340
380
  ): HTMLElement => {
341
381
  let template = getTemplateStrings(config).tag;
342
- let content = option.title;
343
- if (config.tagTemplate) {
344
- let tagTemplate = config.tagTemplate;
345
-
346
- const text = option.getAttribute('data-text');
347
- const value = option.getAttribute('data-value');
382
+ let preparedContent = option.title; // Default content is the option's title
348
383
 
349
- // Replace all {{varname}} in option.innerHTML with values from _config
350
- Object.entries((config.optionsConfig as any)[value] || {}).forEach(([key, value]) => {
351
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
352
- tagTemplate = tagTemplate.replace(new RegExp(`{{${key}}}`, 'g'), String(value));
384
+ if (config.tagTemplate) {
385
+ let tagTemplateString = config.tagTemplate;
386
+ const optionValue = option.getAttribute('data-value') || option.value;
387
+
388
+ // Replace all {{varname}} in option.innerHTML with values from _config.optionsConfig
389
+ Object.entries(
390
+ (config.optionsConfig as any)?.[optionValue] || {},
391
+ ).forEach(([key, val]) => {
392
+ if (
393
+ typeof val === 'string' ||
394
+ typeof val === 'number' ||
395
+ typeof val === 'boolean'
396
+ ) {
397
+ tagTemplateString = tagTemplateString.replace(
398
+ new RegExp(`{{${key}}}`, 'g'),
399
+ String(val),
400
+ );
353
401
  }
354
402
  });
355
403
 
356
- content = renderTemplateString(tagTemplate, {
404
+ // Render the custom tag template with option data
405
+ preparedContent = renderTemplateString(tagTemplateString, {
357
406
  title: option.title,
358
407
  id: option.id,
359
- class: config.tagClass || '',
360
- content: option.innerHTML,
361
- text: option.innerText,
408
+ class: config.tagClass || '', // This class is for content, not the main tag div
409
+ // content: option.innerHTML, // Avoid direct innerHTML from option due to potential XSS
410
+ text: option.innerText || option.textContent || '',
411
+ value: optionValue,
362
412
  });
363
413
  }
364
414
 
365
- content += getTemplateStrings(config).tagRemoveButton;
415
+ // Append the remove button HTML string to the prepared content
416
+ preparedContent += getTemplateStrings(config).tagRemoveButton;
366
417
 
367
418
  const html = template
368
- .replace('{{title}}', option.title)
369
- .replace('{{id}}', option.id)
370
- .replace('{{content}}', content)
371
- .replace('{{class}}', config.tagClass || '');
372
- return stringToElement(html);
419
+ // .replace('{{title}}', option.title) // Title is part of preparedContent if using custom template
420
+ // .replace('{{id}}', option.id) // ID is part of preparedContent if using custom template
421
+ .replace('{{class}}', config.tagClass || ''); // Class for the main tag div
422
+
423
+ const element = stringToElement(html);
424
+ element.innerHTML = preparedContent; // Set the fully prepared content (text/HTML + remove button)
425
+ return element;
373
426
  },
374
427
 
375
428
  /**
376
429
  * Renders the placeholder for the select
377
430
  */
378
431
  placeholder: (config: KTSelectConfigInterface): HTMLElement => {
379
- let html = getTemplateStrings(config)
380
- .placeholder.replace('{{class}}', config.placeholderClass || '');
432
+ let html = getTemplateStrings(config).placeholder.replace(
433
+ '{{class}}',
434
+ config.placeholderClass || '',
435
+ );
381
436
 
382
437
  let content = config.placeholder || 'Select...';
383
438
 
@@ -386,9 +441,13 @@ export const defaultTemplates: KTSelectTemplateInterface = {
386
441
  placeholder: config.placeholder || 'Select...',
387
442
  class: config.placeholderClass || '',
388
443
  });
444
+ const element = stringToElement(html);
445
+ element.innerHTML = content; // For templates, content can be HTML
446
+ return element;
447
+ } else {
448
+ const element = stringToElement(html);
449
+ element.textContent = content; // For simple text, use textContent
450
+ return element;
389
451
  }
390
-
391
- html = html.replace('{{content}}', content);
392
- return stringToElement(html);
393
452
  },
394
453
  };