@keenthemes/ktui 1.0.10 → 1.0.12

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 (139) hide show
  1. package/README.md +2 -2
  2. package/dist/ktui.js +1283 -1100
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/examples/select/basic-usage.html +43 -0
  6. package/examples/select/combobox-icons.html +58 -0
  7. package/examples/select/combobox.html +46 -0
  8. package/examples/select/description.html +69 -0
  9. package/examples/select/disable-option.html +43 -0
  10. package/examples/select/disable-select.html +34 -0
  11. package/examples/select/icon-description.html +56 -0
  12. package/examples/select/icon-multiple.html +58 -0
  13. package/examples/select/icon.html +58 -0
  14. package/examples/select/max-selection.html +39 -0
  15. package/examples/select/modal.html +70 -0
  16. package/examples/select/multiple.html +42 -0
  17. package/examples/select/placeholder.html +43 -0
  18. package/examples/select/remote-data.html +32 -0
  19. package/examples/select/search.html +49 -0
  20. package/examples/select/tags-icons.html +58 -0
  21. package/examples/select/tags-selected.html +59 -0
  22. package/examples/select/tags.html +58 -0
  23. package/examples/select/template-customization.html +65 -0
  24. package/examples/select/test.html +94 -0
  25. package/examples/toast/example.html +427 -0
  26. package/lib/cjs/components/component.js +1 -1
  27. package/lib/cjs/components/component.js.map +1 -1
  28. package/lib/cjs/components/datatable/datatable.js +22 -6
  29. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  30. package/lib/cjs/components/modal/modal.js +0 -4
  31. package/lib/cjs/components/modal/modal.js.map +1 -1
  32. package/lib/cjs/components/select/combobox.js +38 -120
  33. package/lib/cjs/components/select/combobox.js.map +1 -1
  34. package/lib/cjs/components/select/config.js +4 -16
  35. package/lib/cjs/components/select/config.js.map +1 -1
  36. package/lib/cjs/components/select/dropdown.js +10 -49
  37. package/lib/cjs/components/select/dropdown.js.map +1 -1
  38. package/lib/cjs/components/select/index.js +2 -1
  39. package/lib/cjs/components/select/index.js.map +1 -1
  40. package/lib/cjs/components/select/option.js +21 -4
  41. package/lib/cjs/components/select/option.js.map +1 -1
  42. package/lib/cjs/components/select/remote.js +1 -37
  43. package/lib/cjs/components/select/remote.js.map +1 -1
  44. package/lib/cjs/components/select/search.js +11 -41
  45. package/lib/cjs/components/select/search.js.map +1 -1
  46. package/lib/cjs/components/select/select.js +213 -326
  47. package/lib/cjs/components/select/select.js.map +1 -1
  48. package/lib/cjs/components/select/tags.js +39 -31
  49. package/lib/cjs/components/select/tags.js.map +1 -1
  50. package/lib/cjs/components/select/templates.js +120 -179
  51. package/lib/cjs/components/select/templates.js.map +1 -1
  52. package/lib/cjs/components/select/types.js +0 -12
  53. package/lib/cjs/components/select/types.js.map +1 -1
  54. package/lib/cjs/components/select/utils.js +204 -257
  55. package/lib/cjs/components/select/utils.js.map +1 -1
  56. package/lib/cjs/components/toast/index.js +10 -0
  57. package/lib/cjs/components/toast/index.js.map +1 -0
  58. package/lib/cjs/components/toast/toast.js +543 -0
  59. package/lib/cjs/components/toast/toast.js.map +1 -0
  60. package/lib/cjs/components/toast/types.js +7 -0
  61. package/lib/cjs/components/toast/types.js.map +1 -0
  62. package/lib/cjs/helpers/dom.js +24 -0
  63. package/lib/cjs/helpers/dom.js.map +1 -1
  64. package/lib/cjs/index.js +5 -1
  65. package/lib/cjs/index.js.map +1 -1
  66. package/lib/esm/components/component.js +1 -1
  67. package/lib/esm/components/component.js.map +1 -1
  68. package/lib/esm/components/datatable/datatable.js +22 -6
  69. package/lib/esm/components/datatable/datatable.js.map +1 -1
  70. package/lib/esm/components/modal/modal.js +0 -4
  71. package/lib/esm/components/modal/modal.js.map +1 -1
  72. package/lib/esm/components/select/combobox.js +39 -121
  73. package/lib/esm/components/select/combobox.js.map +1 -1
  74. package/lib/esm/components/select/config.js +3 -15
  75. package/lib/esm/components/select/config.js.map +1 -1
  76. package/lib/esm/components/select/dropdown.js +10 -49
  77. package/lib/esm/components/select/dropdown.js.map +1 -1
  78. package/lib/esm/components/select/index.js +1 -1
  79. package/lib/esm/components/select/index.js.map +1 -1
  80. package/lib/esm/components/select/option.js +21 -4
  81. package/lib/esm/components/select/option.js.map +1 -1
  82. package/lib/esm/components/select/remote.js +1 -37
  83. package/lib/esm/components/select/remote.js.map +1 -1
  84. package/lib/esm/components/select/search.js +12 -42
  85. package/lib/esm/components/select/search.js.map +1 -1
  86. package/lib/esm/components/select/select.js +214 -327
  87. package/lib/esm/components/select/select.js.map +1 -1
  88. package/lib/esm/components/select/tags.js +39 -31
  89. package/lib/esm/components/select/tags.js.map +1 -1
  90. package/lib/esm/components/select/templates.js +119 -178
  91. package/lib/esm/components/select/templates.js.map +1 -1
  92. package/lib/esm/components/select/types.js +1 -11
  93. package/lib/esm/components/select/types.js.map +1 -1
  94. package/lib/esm/components/select/utils.js +201 -255
  95. package/lib/esm/components/select/utils.js.map +1 -1
  96. package/lib/esm/components/toast/index.js +6 -0
  97. package/lib/esm/components/toast/index.js.map +1 -0
  98. package/lib/esm/components/toast/toast.js +540 -0
  99. package/lib/esm/components/toast/toast.js.map +1 -0
  100. package/lib/esm/components/toast/types.js +6 -0
  101. package/lib/esm/components/toast/types.js.map +1 -0
  102. package/lib/esm/helpers/dom.js +24 -0
  103. package/lib/esm/helpers/dom.js.map +1 -1
  104. package/lib/esm/index.js +3 -0
  105. package/lib/esm/index.js.map +1 -1
  106. package/package.json +8 -6
  107. package/src/components/alert/alert.css +20 -2
  108. package/src/components/badge/badge.css +5 -0
  109. package/src/components/component.ts +4 -0
  110. package/src/components/datatable/datatable.ts +24 -16
  111. package/src/components/drawer/drawer.css +1 -1
  112. package/src/components/input/input.css +3 -1
  113. package/src/components/link/link.css +2 -2
  114. package/src/components/modal/modal.css +18 -2
  115. package/src/components/modal/modal.ts +0 -5
  116. package/src/components/select/combobox.ts +42 -149
  117. package/src/components/select/config.ts +38 -33
  118. package/src/components/select/dropdown.ts +8 -55
  119. package/src/components/select/index.ts +1 -1
  120. package/src/components/select/option.ts +28 -7
  121. package/src/components/select/remote.ts +2 -42
  122. package/src/components/select/search.ts +14 -54
  123. package/src/components/select/select.css +49 -0
  124. package/src/components/select/select.ts +231 -437
  125. package/src/components/select/tags.ts +40 -37
  126. package/src/components/select/templates.ts +166 -303
  127. package/src/components/select/types.ts +0 -10
  128. package/src/components/select/utils.ts +214 -304
  129. package/src/components/table/table.css +1 -1
  130. package/src/components/textarea/textarea.css +2 -1
  131. package/src/components/toast/index.ts +7 -0
  132. package/src/components/toast/toast.css +60 -0
  133. package/src/components/toast/toast.ts +605 -0
  134. package/src/components/toast/types.ts +169 -0
  135. package/src/helpers/dom.ts +30 -0
  136. package/src/index.ts +4 -0
  137. package/styles/main.css +3 -0
  138. package/styles/vars.css +138 -0
  139. package/styles.css +1 -0
@@ -4,12 +4,14 @@
4
4
  * Copyright 2025 by Keenthemes Inc
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.EventManager = exports.FocusManager = void 0;
7
+ exports.TypeToSearchBuffer = exports.EventManager = exports.FocusManager = exports.highlightTextInElementDebounced = void 0;
8
8
  exports.formatCurrency = formatCurrency;
9
9
  exports.filterOptions = filterOptions;
10
10
  exports.highlightTextInElement = highlightTextInElement;
11
- exports.handleDropdownKeyNavigation = handleDropdownKeyNavigation;
12
11
  exports.debounce = debounce;
12
+ exports.renderTemplateString = renderTemplateString;
13
+ exports.stringToElement = stringToElement;
14
+ // utils.ts
13
15
  var templates_1 = require("./templates");
14
16
  /**
15
17
  * Format a number as a currency string
@@ -50,6 +52,17 @@ function filterOptions(options, query, config, dropdownElement, onVisibleCount)
50
52
  option.setAttribute('style', styleAttr.replace(/display:\s*[^;]+;?/gi, '').trim());
51
53
  }
52
54
  }
55
+ // Clear highlights by restoring original text content
56
+ if (option.dataset && option.dataset.originalText) {
57
+ option.innerHTML = option.dataset.originalText;
58
+ }
59
+ else {
60
+ option.innerHTML = option.textContent || '';
61
+ }
62
+ // Remove the cache if present
63
+ if (option.dataset && option.dataset.originalText) {
64
+ delete option.dataset.originalText;
65
+ }
53
66
  visibleOptionsCount++;
54
67
  }
55
68
  // Call the callback with the visible count if provided
@@ -63,6 +76,8 @@ function filterOptions(options, query, config, dropdownElement, onVisibleCount)
63
76
  var option = options_2[_b];
64
77
  var optionText = ((_a = option.textContent) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
65
78
  var isMatch = optionText.includes(query.toLowerCase());
79
+ // Check if option is disabled
80
+ var isDisabled = option.classList.contains('disabled') || option.getAttribute('aria-disabled') === 'true';
66
81
  if (isMatch || query.trim() === '') {
67
82
  // Show option by removing the hidden class and any display inline styles
68
83
  option.classList.remove('hidden');
@@ -79,22 +94,11 @@ function filterOptions(options, query, config, dropdownElement, onVisibleCount)
79
94
  }
80
95
  }
81
96
  visibleOptionsCount++;
82
- // Apply highlighting if needed - but preserve the option structure
83
- if (isMatch && config.searchHighlight && query.trim() !== '') {
84
- // Clone option elements that contain icons or descriptions
85
- var hasIcon = option.querySelector('[data-kt-select-option-icon]') !== null;
86
- var hasDescription = option.querySelector('[data-kt-select-option-description]') !== null;
87
- if (hasIcon || hasDescription) {
88
- // Only highlight the text part without changing structure
89
- var titleElement = option.querySelector('[data-kt-option-title]');
90
- if (titleElement) {
91
- highlightTextInElement(titleElement, query, config);
92
- }
93
- }
94
- else {
95
- // Simple option with just text - standard highlighting
96
- highlightTextInElement(option, query, config);
97
+ if (config.searchHighlight && query.trim() !== '') {
98
+ if (option.dataset && !option.dataset.originalText) {
99
+ option.dataset.originalText = option.innerHTML;
97
100
  }
101
+ (0, exports.highlightTextInElementDebounced)(option, query, config);
98
102
  }
99
103
  }
100
104
  else {
@@ -131,37 +135,39 @@ function highlightTextInElement(element, query, config) {
131
135
  if (!element || !query || query.trim() === '')
132
136
  return;
133
137
  var queryLower = query.toLowerCase();
134
- function walk(node) {
135
- var _a;
136
- if (node.nodeType === Node.TEXT_NODE) {
137
- var text = node.nodeValue || '';
138
- var textLower = text.toLowerCase();
139
- var matchIndex = textLower.indexOf(queryLower);
140
- if (matchIndex !== -1) {
141
- var before = text.slice(0, matchIndex);
142
- var match = text.slice(matchIndex, matchIndex + query.length);
143
- var after = text.slice(matchIndex + query.length);
144
- var frag = document.createDocumentFragment();
145
- if (before)
146
- frag.appendChild(document.createTextNode(before));
147
- // Use the highlight template, which returns an HTMLElement
148
- var highlightSpan = templates_1.defaultTemplates.highlight(config, match);
149
- frag.appendChild(highlightSpan);
150
- if (after)
151
- frag.appendChild(document.createTextNode(after));
152
- (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(frag, node);
153
- // Only highlight the first occurrence in this node
154
- }
155
- }
156
- else if (node.nodeType === Node.ELEMENT_NODE) {
157
- // Don't re-highlight already highlighted nodes
158
- if (node.classList.contains('highlight'))
159
- return;
160
- Array.from(node.childNodes).forEach(walk);
161
- }
138
+ var text = element.textContent || '';
139
+ if (!text)
140
+ return;
141
+ // Escape regex special characters in query
142
+ var escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
143
+ var regex = new RegExp(escapedQuery, 'gi');
144
+ // Replace all matches with the highlight template
145
+ var lastIndex = 0;
146
+ var result = '';
147
+ var match;
148
+ var matches = [];
149
+ while ((match = regex.exec(text)) !== null) {
150
+ matches.push({ start: match.index, end: regex.lastIndex });
151
+ }
152
+ if (matches.length === 0) {
153
+ element.innerHTML = text;
154
+ return;
155
+ }
156
+ for (var i = 0; i < matches.length; i++) {
157
+ var _a = matches[i], start = _a.start, end = _a.end;
158
+ // Add text before match
159
+ result += text.slice(lastIndex, start);
160
+ // Add highlighted match using template
161
+ var highlighted = templates_1.defaultTemplates.highlight(config, text.slice(start, end)).outerHTML;
162
+ result += highlighted;
163
+ lastIndex = end;
162
164
  }
163
- walk(element);
165
+ // Add remaining text
166
+ result += text.slice(lastIndex);
167
+ element.innerHTML = result;
164
168
  }
169
+ // Debounced version for performance
170
+ exports.highlightTextInElementDebounced = debounce(highlightTextInElement, 100);
165
171
  /**
166
172
  * Focus manager for keyboard navigation
167
173
  * Consolidates redundant focus management logic into shared functions
@@ -170,16 +176,14 @@ var FocusManager = /** @class */ (function () {
170
176
  function FocusManager(element, optionsSelector, config) {
171
177
  if (optionsSelector === void 0) { optionsSelector = '[data-kt-select-option]'; }
172
178
  this._focusedOptionIndex = null;
179
+ this._onFocusChange = null;
173
180
  this._element = element;
174
181
  this._optionsSelector = optionsSelector;
175
182
  this._eventManager = new EventManager();
176
- // Use config values if provided, otherwise fallback to defaults
177
- this._focusClass = (config === null || config === void 0 ? void 0 : config.focusClass) || 'option-focused';
178
- this._hoverClass = (config === null || config === void 0 ? void 0 : config.hoverClass) || 'hovered';
179
- this._bgClass = (config === null || config === void 0 ? void 0 : config.bgClass) || 'bg-blue-50';
180
- this._fontClass = (config === null || config === void 0 ? void 0 : config.fontClass) || 'font-medium';
181
183
  // Add click handler to update focus state when options are clicked
182
184
  this._setupOptionClickHandlers();
185
+ this._focusClass = 'focus'; // or whatever your intended class is
186
+ this._hoverClass = 'hover'; // or your intended class
183
187
  }
184
188
  /**
185
189
  * Set up click handlers for all options to update focus state
@@ -191,15 +195,6 @@ var FocusManager = /** @class */ (function () {
191
195
  var target = e.target;
192
196
  var optionElement = target.closest(_this._optionsSelector);
193
197
  if (optionElement) {
194
- // First clear all focus
195
- _this.resetFocus();
196
- // Then update the focused index based on the clicked option
197
- var options = _this.getVisibleOptions();
198
- var clickedIndex = options.indexOf(optionElement);
199
- if (clickedIndex >= 0) {
200
- _this._focusedOptionIndex = clickedIndex;
201
- _this.applyFocus(options[clickedIndex]);
202
- }
203
198
  }
204
199
  });
205
200
  };
@@ -221,24 +216,88 @@ var FocusManager = /** @class */ (function () {
221
216
  });
222
217
  };
223
218
  /**
224
- * Focus the next visible option
219
+ * Focus the first visible option
225
220
  */
226
- FocusManager.prototype.focusNext = function () {
221
+ FocusManager.prototype.focusFirst = function () {
227
222
  var options = this.getVisibleOptions();
228
223
  if (options.length === 0)
229
224
  return null;
230
- this.resetFocus();
231
- if (this._focusedOptionIndex === null) {
232
- this._focusedOptionIndex = 0;
225
+ for (var i = 0; i < options.length; i++) {
226
+ var option = options[i];
227
+ if (!option.classList.contains('disabled') && option.getAttribute('aria-disabled') !== 'true') {
228
+ this.resetFocus();
229
+ this._focusedOptionIndex = i;
230
+ this.applyFocus(option);
231
+ this.scrollIntoView(option);
232
+ return option;
233
+ }
233
234
  }
234
- else {
235
- this._focusedOptionIndex =
236
- (this._focusedOptionIndex + 1) % options.length;
235
+ return null;
236
+ };
237
+ /**
238
+ * Focus the last visible option
239
+ */
240
+ FocusManager.prototype.focusLast = function () {
241
+ var options = this.getVisibleOptions();
242
+ if (options.length === 0)
243
+ return null;
244
+ for (var i = options.length - 1; i >= 0; i--) {
245
+ var option = options[i];
246
+ if (!option.classList.contains('disabled') && option.getAttribute('aria-disabled') !== 'true') {
247
+ this.resetFocus();
248
+ this._focusedOptionIndex = i;
249
+ this.applyFocus(option);
250
+ this.scrollIntoView(option);
251
+ return option;
252
+ }
253
+ }
254
+ return null;
255
+ };
256
+ /**
257
+ * Focus the next visible option that matches the search string
258
+ */
259
+ FocusManager.prototype.focusByString = function (str) {
260
+ var _a, _b, _c;
261
+ var options = this.getVisibleOptions();
262
+ if (options.length === 0)
263
+ return null;
264
+ var lowerStr = str.toLowerCase();
265
+ var startIdx = ((_a = this._focusedOptionIndex) !== null && _a !== void 0 ? _a : -1) + 1;
266
+ for (var i = 0; i < options.length; i++) {
267
+ var idx = (startIdx + i) % options.length;
268
+ var option = options[idx];
269
+ if (!option.classList.contains('disabled') &&
270
+ option.getAttribute('aria-disabled') !== 'true' &&
271
+ (((_b = option.textContent) === null || _b === void 0 ? void 0 : _b.toLowerCase().startsWith(lowerStr)) || ((_c = option.dataset.value) === null || _c === void 0 ? void 0 : _c.toLowerCase().startsWith(lowerStr)))) {
272
+ this._focusedOptionIndex = idx;
273
+ this.applyFocus(option);
274
+ this.scrollIntoView(option);
275
+ return option;
276
+ }
237
277
  }
238
- var option = options[this._focusedOptionIndex];
239
- this.applyFocus(option);
240
- this.scrollIntoView(option);
241
- return option;
278
+ return null;
279
+ };
280
+ /**
281
+ * Focus the next visible option
282
+ */
283
+ FocusManager.prototype.focusNext = function () {
284
+ var options = this.getVisibleOptions();
285
+ if (options.length === 0)
286
+ return null;
287
+ var idx = this._focusedOptionIndex === null ? 0 : (this._focusedOptionIndex + 1) % options.length;
288
+ var startIdx = idx;
289
+ do {
290
+ var option = options[idx];
291
+ if (!option.classList.contains('disabled') && option.getAttribute('aria-disabled') !== 'true') {
292
+ this.resetFocus();
293
+ this._focusedOptionIndex = idx;
294
+ this.applyFocus(option);
295
+ this.scrollIntoView(option);
296
+ return option;
297
+ }
298
+ idx = (idx + 1) % options.length;
299
+ } while (idx !== startIdx);
300
+ return null;
242
301
  };
243
302
  /**
244
303
  * Focus the previous visible option
@@ -247,18 +306,20 @@ var FocusManager = /** @class */ (function () {
247
306
  var options = this.getVisibleOptions();
248
307
  if (options.length === 0)
249
308
  return null;
250
- this.resetFocus();
251
- if (this._focusedOptionIndex === null) {
252
- this._focusedOptionIndex = options.length - 1;
253
- }
254
- else {
255
- this._focusedOptionIndex =
256
- (this._focusedOptionIndex - 1 + options.length) % options.length;
257
- }
258
- var option = options[this._focusedOptionIndex];
259
- this.applyFocus(option);
260
- this.scrollIntoView(option);
261
- return option;
309
+ var idx = this._focusedOptionIndex === null ? options.length - 1 : (this._focusedOptionIndex - 1 + options.length) % options.length;
310
+ var startIdx = idx;
311
+ do {
312
+ var option = options[idx];
313
+ if (!option.classList.contains('disabled') && option.getAttribute('aria-disabled') !== 'true') {
314
+ this.resetFocus();
315
+ this._focusedOptionIndex = idx;
316
+ this.applyFocus(option);
317
+ this.scrollIntoView(option);
318
+ return option;
319
+ }
320
+ idx = (idx - 1 + options.length) % options.length;
321
+ } while (idx !== startIdx);
322
+ return null;
262
323
  };
263
324
  /**
264
325
  * Apply focus to a specific option
@@ -266,27 +327,23 @@ var FocusManager = /** @class */ (function () {
266
327
  FocusManager.prototype.applyFocus = function (option) {
267
328
  if (!option)
268
329
  return;
269
- // Remove focus from all options first
330
+ if (option.classList.contains('disabled') || option.getAttribute('aria-disabled') === 'true') {
331
+ return;
332
+ }
270
333
  this.resetFocus();
271
- // Add focus to this option
272
334
  option.classList.add(this._focusClass);
273
335
  option.classList.add(this._hoverClass);
274
- option.classList.add(this._bgClass);
275
- option.classList.add(this._fontClass);
336
+ this._triggerFocusChange();
276
337
  };
277
338
  /**
278
339
  * Reset focus on all options
279
340
  */
280
341
  FocusManager.prototype.resetFocus = function () {
281
342
  var _this = this;
282
- // Find all elements with the focus classes
283
- var focusedElements = this._element.querySelectorAll(".".concat(this._focusClass, ", .").concat(this._hoverClass, ", .").concat(this._bgClass, ", .").concat(this._fontClass));
284
- // Remove classes from all elements
343
+ var focusedElements = this._element.querySelectorAll(".".concat(this._focusClass, ", .").concat(this._hoverClass));
344
+ // Remove focus and hover classes from all options
285
345
  focusedElements.forEach(function (element) {
286
- element.classList.remove(_this._focusClass);
287
- element.classList.remove(_this._hoverClass);
288
- element.classList.remove(_this._bgClass);
289
- element.classList.remove(_this._fontClass);
346
+ element.classList.remove(_this._focusClass, _this._hoverClass);
290
347
  });
291
348
  // Reset index if visible options have changed
292
349
  var visibleOptions = this.getVisibleOptions();
@@ -301,7 +358,7 @@ var FocusManager = /** @class */ (function () {
301
358
  FocusManager.prototype.scrollIntoView = function (option) {
302
359
  if (!option)
303
360
  return;
304
- var container = this._element.querySelector('[data-kt-select-options-container]');
361
+ var container = this._element.querySelector('[data-kt-select-options]');
305
362
  if (!container)
306
363
  return;
307
364
  var optionRect = option.getBoundingClientRect();
@@ -352,6 +409,17 @@ var FocusManager = /** @class */ (function () {
352
409
  FocusManager.prototype.setFocusedIndex = function (index) {
353
410
  this._focusedOptionIndex = index;
354
411
  };
412
+ /**
413
+ * Set a callback to be called when focus changes
414
+ */
415
+ FocusManager.prototype.setOnFocusChange = function (cb) {
416
+ this._onFocusChange = cb;
417
+ };
418
+ FocusManager.prototype._triggerFocusChange = function () {
419
+ if (this._onFocusChange) {
420
+ this._onFocusChange(this.getFocusedOption(), this._focusedOptionIndex);
421
+ }
422
+ };
355
423
  /**
356
424
  * Clean up event listeners
357
425
  */
@@ -363,166 +431,6 @@ var FocusManager = /** @class */ (function () {
363
431
  return FocusManager;
364
432
  }());
365
433
  exports.FocusManager = FocusManager;
366
- /**
367
- * Shared keyboard navigation handler for dropdown options
368
- * Can be used by both combobox and search modules
369
- */
370
- function handleDropdownKeyNavigation(event, select, config, callbacks) {
371
- try {
372
- // Get the dropdown state
373
- var isDropdownOpen = select._dropdownIsOpen;
374
- // Log the event to help debug
375
- var origin_1 = 'handleDropdownKeyNavigation';
376
- if (select.getConfig && select.getConfig().debug)
377
- console.log("[".concat(origin_1, "] Key: ").concat(event.key, ", Dropdown open: ").concat(isDropdownOpen));
378
- // Handle basic keyboard navigation
379
- switch (event.key) {
380
- case 'ArrowDown':
381
- if (!isDropdownOpen) {
382
- if (select.getConfig && select.getConfig().debug)
383
- console.log("[".concat(origin_1, "] Opening dropdown on ArrowDown"));
384
- select.openDropdown();
385
- // Focus the first option after opening
386
- setTimeout(function () {
387
- select._focusNextOption();
388
- }, 50);
389
- }
390
- else if (callbacks === null || callbacks === void 0 ? void 0 : callbacks.onArrowDown) {
391
- if (select.getConfig && select.getConfig().debug)
392
- console.log("[".concat(origin_1, "] Using custom onArrowDown callback"));
393
- callbacks.onArrowDown();
394
- }
395
- else {
396
- if (select.getConfig && select.getConfig().debug)
397
- console.log("[".concat(origin_1, "] Using default _focusNextOption"));
398
- var focusedOption = select._focusNextOption();
399
- // Ensure we have a focused option
400
- if (focusedOption) {
401
- if (select.getConfig && select.getConfig().debug)
402
- console.log("[".concat(origin_1, "] Focused next option:"), focusedOption);
403
- }
404
- }
405
- event.preventDefault();
406
- break;
407
- case 'ArrowUp':
408
- if (!isDropdownOpen) {
409
- if (select.getConfig && select.getConfig().debug)
410
- console.log("[".concat(origin_1, "] Opening dropdown on ArrowUp"));
411
- select.openDropdown();
412
- // Focus the last option after opening
413
- setTimeout(function () {
414
- select._focusPreviousOption();
415
- }, 50);
416
- }
417
- else if (callbacks === null || callbacks === void 0 ? void 0 : callbacks.onArrowUp) {
418
- if (select.getConfig && select.getConfig().debug)
419
- console.log("[".concat(origin_1, "] Using custom onArrowUp callback"));
420
- callbacks.onArrowUp();
421
- }
422
- else {
423
- if (select.getConfig && select.getConfig().debug)
424
- console.log("[".concat(origin_1, "] Using default _focusPreviousOption"));
425
- var focusedOption = select._focusPreviousOption();
426
- // Ensure we have a focused option
427
- if (focusedOption) {
428
- if (select.getConfig && select.getConfig().debug)
429
- console.log("[".concat(origin_1, "] Focused previous option:"), focusedOption);
430
- }
431
- }
432
- event.preventDefault();
433
- break;
434
- case 'Enter':
435
- // Prevent form submission
436
- event.preventDefault();
437
- if (isDropdownOpen) {
438
- if (select.getConfig && select.getConfig().debug)
439
- console.log("[".concat(origin_1, "] Enter pressed with dropdown open"));
440
- // For combobox mode, ensure we update the input value directly
441
- var isCombobox = select.getConfig().mode === 'combobox';
442
- var comboboxModule = select._comboboxModule;
443
- if (callbacks === null || callbacks === void 0 ? void 0 : callbacks.onEnter) {
444
- if (select.getConfig && select.getConfig().debug)
445
- console.log("[".concat(origin_1, "] Using custom onEnter callback"));
446
- callbacks.onEnter();
447
- }
448
- else {
449
- if (select.getConfig && select.getConfig().debug)
450
- console.log("[".concat(origin_1, "] Using default selectFocusedOption"));
451
- // Make sure there is a focused option before trying to select it
452
- if (select._focusManager &&
453
- select._focusManager.getFocusedOption()) {
454
- select.selectFocusedOption();
455
- }
456
- else {
457
- // If no option is focused, try to focus the first one
458
- var focusedOption = select._focusNextOption();
459
- // Only select if an option was successfully focused
460
- if (focusedOption) {
461
- select.selectFocusedOption();
462
- }
463
- }
464
- }
465
- // Close dropdown after selection if not multiple and closeOnSelect is true
466
- if (!config.multiple && config.closeOnSelect !== false) {
467
- if (select.getConfig && select.getConfig().debug)
468
- console.log("[".concat(origin_1, "] Closing dropdown after selection"));
469
- select.closeDropdown();
470
- }
471
- }
472
- else {
473
- // If dropdown is closed, open it on Enter
474
- if (select.getConfig && select.getConfig().debug)
475
- console.log("[".concat(origin_1, "] Opening dropdown on Enter"));
476
- select.openDropdown();
477
- // Focus the first option after opening
478
- setTimeout(function () {
479
- select._focusNextOption();
480
- }, 50);
481
- }
482
- break;
483
- case 'Tab':
484
- // Only handle tab if dropdown is open
485
- if (isDropdownOpen) {
486
- if (select.getConfig && select.getConfig().debug)
487
- console.log("[".concat(origin_1, "] Closing dropdown on Tab"));
488
- select.closeDropdown();
489
- if (callbacks === null || callbacks === void 0 ? void 0 : callbacks.onClose) {
490
- callbacks.onClose();
491
- }
492
- // Don't prevent default tab behavior - let it move focus naturally
493
- }
494
- break;
495
- case 'Escape':
496
- // Only handle escape if dropdown is open
497
- if (isDropdownOpen) {
498
- if (select.getConfig && select.getConfig().debug)
499
- console.log("[".concat(origin_1, "] Closing dropdown on Escape"));
500
- select.closeDropdown();
501
- if (callbacks === null || callbacks === void 0 ? void 0 : callbacks.onClose) {
502
- callbacks.onClose();
503
- }
504
- event.preventDefault(); // Prevent other escape handlers
505
- }
506
- break;
507
- case ' ': // Space key
508
- // If dropdown is closed, space should open it (but not if in combobox mode)
509
- if (!isDropdownOpen && !(select.getConfig().mode === 'combobox')) {
510
- if (select.getConfig && select.getConfig().debug)
511
- console.log("[".concat(origin_1, "] Opening dropdown on Space"));
512
- select.openDropdown();
513
- // Focus the first option after opening
514
- setTimeout(function () {
515
- select._focusNextOption();
516
- }, 50);
517
- event.preventDefault();
518
- }
519
- break;
520
- }
521
- }
522
- catch (error) {
523
- console.error('Error in keyboard navigation handler:', error);
524
- }
525
- }
526
434
  /**
527
435
  * Centralized event listener management
528
436
  */
@@ -579,7 +487,7 @@ var EventManager = /** @class */ (function () {
579
487
  // Go through each event type
580
488
  this._boundHandlers.forEach(function (eventMap, event) {
581
489
  // For each event type, go through each handler
582
- eventMap.forEach(function (boundHandler, originalHandler) {
490
+ eventMap.forEach(function (boundHandler) {
583
491
  element.removeEventListener(event, boundHandler);
584
492
  });
585
493
  });
@@ -603,4 +511,43 @@ function debounce(func, delay) {
603
511
  timeout = setTimeout(function () { return func.apply(void 0, args); }, delay);
604
512
  };
605
513
  }
514
+ /**
515
+ * Replaces all {{key}} in the template with the corresponding value from the data object.
516
+ * If a key is missing in data, replaces with an empty string.
517
+ */
518
+ function renderTemplateString(template, data) {
519
+ return template.replace(/{{(\w+)}}/g, function (_, key) {
520
+ return data[key] !== undefined && data[key] !== null ? String(data[key]) : '';
521
+ });
522
+ }
523
+ // Type-to-search buffer utility for keyboard navigation
524
+ var TypeToSearchBuffer = /** @class */ (function () {
525
+ function TypeToSearchBuffer(timeout) {
526
+ if (timeout === void 0) { timeout = 500; }
527
+ this.buffer = '';
528
+ this.lastTime = 0;
529
+ this.timeout = timeout;
530
+ }
531
+ TypeToSearchBuffer.prototype.push = function (char) {
532
+ var now = Date.now();
533
+ if (now - this.lastTime > this.timeout) {
534
+ this.buffer = '';
535
+ }
536
+ this.buffer += char;
537
+ this.lastTime = now;
538
+ };
539
+ TypeToSearchBuffer.prototype.getBuffer = function () {
540
+ return this.buffer;
541
+ };
542
+ TypeToSearchBuffer.prototype.clear = function () {
543
+ this.buffer = '';
544
+ };
545
+ return TypeToSearchBuffer;
546
+ }());
547
+ exports.TypeToSearchBuffer = TypeToSearchBuffer;
548
+ function stringToElement(html) {
549
+ var template = document.createElement('template');
550
+ template.innerHTML = html.trim();
551
+ return template.content.firstElementChild;
552
+ }
606
553
  //# sourceMappingURL=utils.js.map