@keenthemes/ktui 1.0.11 → 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 (129) hide show
  1. package/dist/ktui.js +1283 -1096
  2. package/dist/ktui.min.js +1 -1
  3. package/dist/ktui.min.js.map +1 -1
  4. package/examples/select/basic-usage.html +43 -0
  5. package/examples/select/combobox-icons.html +58 -0
  6. package/examples/select/combobox.html +46 -0
  7. package/examples/select/description.html +69 -0
  8. package/examples/select/disable-option.html +43 -0
  9. package/examples/select/disable-select.html +34 -0
  10. package/examples/select/icon-description.html +56 -0
  11. package/examples/select/icon-multiple.html +58 -0
  12. package/examples/select/icon.html +58 -0
  13. package/examples/select/max-selection.html +39 -0
  14. package/examples/select/modal.html +70 -0
  15. package/examples/select/multiple.html +42 -0
  16. package/examples/select/placeholder.html +43 -0
  17. package/examples/select/remote-data.html +32 -0
  18. package/examples/select/search.html +49 -0
  19. package/examples/select/tags-icons.html +58 -0
  20. package/examples/select/tags-selected.html +59 -0
  21. package/examples/select/tags.html +58 -0
  22. package/examples/select/template-customization.html +65 -0
  23. package/examples/select/test.html +94 -0
  24. package/examples/toast/example.html +427 -0
  25. package/lib/cjs/components/component.js +1 -1
  26. package/lib/cjs/components/component.js.map +1 -1
  27. package/lib/cjs/components/datatable/datatable.js +22 -6
  28. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  29. package/lib/cjs/components/select/combobox.js +38 -120
  30. package/lib/cjs/components/select/combobox.js.map +1 -1
  31. package/lib/cjs/components/select/config.js +4 -16
  32. package/lib/cjs/components/select/config.js.map +1 -1
  33. package/lib/cjs/components/select/dropdown.js +10 -49
  34. package/lib/cjs/components/select/dropdown.js.map +1 -1
  35. package/lib/cjs/components/select/index.js +2 -1
  36. package/lib/cjs/components/select/index.js.map +1 -1
  37. package/lib/cjs/components/select/option.js +21 -4
  38. package/lib/cjs/components/select/option.js.map +1 -1
  39. package/lib/cjs/components/select/remote.js +1 -37
  40. package/lib/cjs/components/select/remote.js.map +1 -1
  41. package/lib/cjs/components/select/search.js +11 -41
  42. package/lib/cjs/components/select/search.js.map +1 -1
  43. package/lib/cjs/components/select/select.js +213 -326
  44. package/lib/cjs/components/select/select.js.map +1 -1
  45. package/lib/cjs/components/select/tags.js +39 -31
  46. package/lib/cjs/components/select/tags.js.map +1 -1
  47. package/lib/cjs/components/select/templates.js +120 -179
  48. package/lib/cjs/components/select/templates.js.map +1 -1
  49. package/lib/cjs/components/select/types.js +0 -12
  50. package/lib/cjs/components/select/types.js.map +1 -1
  51. package/lib/cjs/components/select/utils.js +204 -257
  52. package/lib/cjs/components/select/utils.js.map +1 -1
  53. package/lib/cjs/components/toast/index.js +10 -0
  54. package/lib/cjs/components/toast/index.js.map +1 -0
  55. package/lib/cjs/components/toast/toast.js +543 -0
  56. package/lib/cjs/components/toast/toast.js.map +1 -0
  57. package/lib/cjs/components/toast/types.js +7 -0
  58. package/lib/cjs/components/toast/types.js.map +1 -0
  59. package/lib/cjs/helpers/dom.js +24 -0
  60. package/lib/cjs/helpers/dom.js.map +1 -1
  61. package/lib/cjs/index.js +5 -1
  62. package/lib/cjs/index.js.map +1 -1
  63. package/lib/esm/components/component.js +1 -1
  64. package/lib/esm/components/component.js.map +1 -1
  65. package/lib/esm/components/datatable/datatable.js +22 -6
  66. package/lib/esm/components/datatable/datatable.js.map +1 -1
  67. package/lib/esm/components/select/combobox.js +39 -121
  68. package/lib/esm/components/select/combobox.js.map +1 -1
  69. package/lib/esm/components/select/config.js +3 -15
  70. package/lib/esm/components/select/config.js.map +1 -1
  71. package/lib/esm/components/select/dropdown.js +10 -49
  72. package/lib/esm/components/select/dropdown.js.map +1 -1
  73. package/lib/esm/components/select/index.js +1 -1
  74. package/lib/esm/components/select/index.js.map +1 -1
  75. package/lib/esm/components/select/option.js +21 -4
  76. package/lib/esm/components/select/option.js.map +1 -1
  77. package/lib/esm/components/select/remote.js +1 -37
  78. package/lib/esm/components/select/remote.js.map +1 -1
  79. package/lib/esm/components/select/search.js +12 -42
  80. package/lib/esm/components/select/search.js.map +1 -1
  81. package/lib/esm/components/select/select.js +214 -327
  82. package/lib/esm/components/select/select.js.map +1 -1
  83. package/lib/esm/components/select/tags.js +39 -31
  84. package/lib/esm/components/select/tags.js.map +1 -1
  85. package/lib/esm/components/select/templates.js +119 -178
  86. package/lib/esm/components/select/templates.js.map +1 -1
  87. package/lib/esm/components/select/types.js +1 -11
  88. package/lib/esm/components/select/types.js.map +1 -1
  89. package/lib/esm/components/select/utils.js +201 -255
  90. package/lib/esm/components/select/utils.js.map +1 -1
  91. package/lib/esm/components/toast/index.js +6 -0
  92. package/lib/esm/components/toast/index.js.map +1 -0
  93. package/lib/esm/components/toast/toast.js +540 -0
  94. package/lib/esm/components/toast/toast.js.map +1 -0
  95. package/lib/esm/components/toast/types.js +6 -0
  96. package/lib/esm/components/toast/types.js.map +1 -0
  97. package/lib/esm/helpers/dom.js +24 -0
  98. package/lib/esm/helpers/dom.js.map +1 -1
  99. package/lib/esm/index.js +3 -0
  100. package/lib/esm/index.js.map +1 -1
  101. package/package.json +8 -6
  102. package/src/components/alert/alert.css +15 -2
  103. package/src/components/component.ts +4 -0
  104. package/src/components/datatable/datatable.ts +24 -16
  105. package/src/components/input/input.css +3 -1
  106. package/src/components/link/link.css +2 -2
  107. package/src/components/select/combobox.ts +42 -149
  108. package/src/components/select/config.ts +38 -33
  109. package/src/components/select/dropdown.ts +8 -55
  110. package/src/components/select/index.ts +1 -1
  111. package/src/components/select/option.ts +28 -7
  112. package/src/components/select/remote.ts +2 -42
  113. package/src/components/select/search.ts +14 -54
  114. package/src/components/select/select.css +49 -0
  115. package/src/components/select/select.ts +231 -437
  116. package/src/components/select/tags.ts +40 -37
  117. package/src/components/select/templates.ts +166 -303
  118. package/src/components/select/types.ts +0 -10
  119. package/src/components/select/utils.ts +214 -304
  120. package/src/components/textarea/textarea.css +2 -1
  121. package/src/components/toast/index.ts +7 -0
  122. package/src/components/toast/toast.css +60 -0
  123. package/src/components/toast/toast.ts +605 -0
  124. package/src/components/toast/types.ts +169 -0
  125. package/src/helpers/dom.ts +30 -0
  126. package/src/index.ts +4 -0
  127. package/styles/main.css +3 -0
  128. package/styles/vars.css +138 -0
  129. package/styles.css +1 -0
@@ -7,11 +7,9 @@ import {
7
7
  Instance as PopperInstance,
8
8
  createPopper,
9
9
  Placement,
10
- VirtualElement,
11
10
  } from '@popperjs/core';
12
11
  import KTDom from '../../helpers/dom';
13
12
  import KTData from '../../helpers/data';
14
- import KTEventHandler from '../../helpers/event-handler';
15
13
  import KTComponent from '../component';
16
14
  import { KTSelectConfigInterface } from './config';
17
15
  import { FocusManager, EventManager } from './utils';
@@ -79,13 +77,6 @@ export class KTSelectDropdown extends KTComponent {
79
77
  this._handleToggleClick.bind(this),
80
78
  );
81
79
 
82
- // Keyboard navigation
83
- this._eventManager.addListener(
84
- this._element,
85
- 'keydown',
86
- this._handleKeyDown.bind(this),
87
- );
88
-
89
80
  // Close on outside click
90
81
  this._eventManager.addListener(
91
82
  document as unknown as HTMLElement,
@@ -104,48 +95,6 @@ export class KTSelectDropdown extends KTComponent {
104
95
  this.toggle();
105
96
  }
106
97
 
107
- /**
108
- * Handle keyboard events
109
- */
110
- private _handleKeyDown(event: KeyboardEvent): void {
111
- if (!this._isOpen) return;
112
-
113
- switch (event.key) {
114
- case 'Escape':
115
- event.preventDefault();
116
- this.close();
117
- this._toggleElement.focus();
118
- break;
119
- case 'ArrowDown':
120
- event.preventDefault();
121
- this._focusManager.focusNext();
122
- break;
123
- case 'ArrowUp':
124
- event.preventDefault();
125
- this._focusManager.focusPrevious();
126
- break;
127
- case 'Home':
128
- event.preventDefault();
129
- // Focus first visible option
130
- const firstOption = this._focusManager.getVisibleOptions()[0];
131
- if (firstOption) {
132
- this._focusManager.applyFocus(firstOption);
133
- this._focusManager.scrollIntoView(firstOption);
134
- }
135
- break;
136
- case 'End':
137
- event.preventDefault();
138
- // Focus last visible option
139
- const visibleOptions = this._focusManager.getVisibleOptions();
140
- const lastOption = visibleOptions[visibleOptions.length - 1];
141
- if (lastOption) {
142
- this._focusManager.applyFocus(lastOption);
143
- this._focusManager.scrollIntoView(lastOption);
144
- }
145
- break;
146
- }
147
- }
148
-
149
98
  /**
150
99
  * Handle clicks outside the dropdown
151
100
  */
@@ -274,6 +223,10 @@ export class KTSelectDropdown extends KTComponent {
274
223
  * Toggle the dropdown
275
224
  */
276
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
+ }
277
230
  if (this._config.debug)
278
231
  console.log('KTSelectDropdown.toggle called - isOpen:', this._isOpen);
279
232
 
@@ -294,6 +247,10 @@ export class KTSelectDropdown extends KTComponent {
294
247
  * Open the dropdown
295
248
  */
296
249
  public open(): void {
250
+ if (this._config.disabled) {
251
+ if (this._config.debug) console.log('KTSelectDropdown.open: select is disabled, not opening');
252
+ return;
253
+ }
297
254
  if (this._isOpen || this._isTransitioning) return;
298
255
 
299
256
  // Fire before show event
@@ -353,11 +310,7 @@ export class KTSelectDropdown extends KTComponent {
353
310
  );
354
311
  if (searchInput) {
355
312
  (searchInput as HTMLInputElement).focus();
356
- } else {
357
- this._focusFirstOption();
358
313
  }
359
- } else {
360
- this._focusFirstOption();
361
314
  }
362
315
 
363
316
  // Fire after show event
@@ -9,5 +9,5 @@ export { KTSelectCombobox } from './combobox';
9
9
  export { KTSelectSearch } from './search';
10
10
  export { KTSelectTags } from './tags';
11
11
  export { KTSelectDropdown } from './dropdown';
12
- export { filterOptions, FocusManager, EventManager } from './utils';
12
+ export { filterOptions, FocusManager, EventManager, TypeToSearchBuffer } from './utils';
13
13
  export { KTSelectConfigInterface, KTSelectOption } from './config';
@@ -5,7 +5,6 @@
5
5
 
6
6
  import KTComponent from '../component';
7
7
  import {
8
- KTSelectOptionConfigInterface,
9
8
  KTSelectConfigInterface,
10
9
  } from './config';
11
10
  import { defaultTemplates } from './templates';
@@ -13,10 +12,10 @@ import { defaultTemplates } from './templates';
13
12
  export class KTSelectOption extends KTComponent {
14
13
  protected override readonly _name: string = 'select-option';
15
14
  protected override readonly _dataOptionPrefix: string = 'kt-'; // Use 'kt-' prefix to support data-kt-select-option attributes
16
- protected override readonly _config: KTSelectOptionConfigInterface;
15
+ protected override readonly _config: KTSelectConfigInterface;
17
16
  private _globalConfig: KTSelectConfigInterface;
18
17
 
19
- constructor(element: HTMLElement, config?: KTSelectConfigInterface) {
18
+ constructor(element: HTMLElement, config?: KTSelectConfigInterface,) {
20
19
  super();
21
20
 
22
21
  // Always initialize a new option instance
@@ -24,6 +23,13 @@ export class KTSelectOption extends KTComponent {
24
23
  this._buildConfig();
25
24
  this._globalConfig = config;
26
25
 
26
+ // Clean the config
27
+ this._config = (this._config as any)[''] || {};
28
+
29
+ // Add the option config to the global config
30
+ this._globalConfig.optionsConfig = this._globalConfig.optionsConfig || {};
31
+ this._globalConfig.optionsConfig[(element as HTMLInputElement).value] = this._config;
32
+
27
33
  // Don't store in KTData to avoid Singleton pattern issues
28
34
  // Each option should be a unique instance
29
35
  (element as any).instance = this;
@@ -35,9 +41,24 @@ export class KTSelectOption extends KTComponent {
35
41
 
36
42
  public render(): HTMLElement {
37
43
  const optionElement = this.getHTMLOptionElement();
38
- // Use the global config if available, or create a minimal valid config
39
- const config = this._globalConfig || { height: 250 };
40
- // Create a new option element every time
41
- return defaultTemplates.option(optionElement, config);
44
+
45
+ // Get the original template
46
+ let originalTemplate = this._globalConfig.optionTemplate;
47
+
48
+ if (this._globalConfig.optionTemplate) {
49
+ // Replace all {{varname}} in option.innerHTML with values from _config
50
+ Object.entries((this._config as any) || {}).forEach(([key, value]) => {
51
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
52
+ this._globalConfig.optionTemplate = this._globalConfig.optionTemplate.replace(new RegExp(`{{${key}}}`, 'g'), String(value));
53
+ }
54
+ });
55
+ }
56
+
57
+ let template = defaultTemplates.option(optionElement, this._globalConfig);
58
+
59
+ // Restore the original template
60
+ this._globalConfig.optionTemplate = originalTemplate;
61
+
62
+ return template;
42
63
  }
43
64
  }
@@ -259,12 +259,10 @@ export class KTSelectRemote {
259
259
  // Get the field mapping from config with fallbacks for common field names
260
260
  const valueField = this._config.dataValueField || 'id';
261
261
  const labelField = this._config.dataFieldText || 'title';
262
- const descriptionField = this._config.dataFieldDescription || 'description';
263
- const iconField = this._config.dataFieldIcon || 'icon';
264
262
 
265
263
  if (this._config.debug)
266
264
  console.log(
267
- `Mapping fields: value=${valueField}, label=${labelField}, description=${descriptionField}, icon=${iconField}`,
265
+ `Mapping fields: value=${valueField}, label=${labelField}`,
268
266
  );
269
267
  if (this._config.debug)
270
268
  console.log('Item data:', JSON.stringify(item).substring(0, 200) + '...'); // Trimmed for readability
@@ -298,7 +296,7 @@ export class KTSelectRemote {
298
296
  ? typeof result === 'object'
299
297
  ? JSON.stringify(result).substring(0, 50)
300
298
  : String(result).substring(0, 50)
301
- : 'null'
299
+ : 'null'
302
300
  }`,
303
301
  );
304
302
 
@@ -363,48 +361,10 @@ export class KTSelectRemote {
363
361
  console.log('After fallback checks, title:', title);
364
362
  }
365
363
 
366
- // Get description - make sure we don't pass null, undefined, or "null" string values
367
- let description = getValue(item, descriptionField);
368
- if (
369
- description === null ||
370
- description === undefined ||
371
- String(description) === 'null' ||
372
- String(description) === 'undefined'
373
- ) {
374
- description = null;
375
- } else {
376
- description = String(description);
377
- }
378
- if (this._config.debug)
379
- console.log(`Description field [${descriptionField}]:`, description);
380
-
381
- // Try to get an icon - make sure we don't pass null, undefined, or "null" string values
382
- let icon = getValue(item, iconField);
383
- if (
384
- icon === null ||
385
- icon === undefined ||
386
- String(icon) === 'null' ||
387
- String(icon) === 'undefined'
388
- ) {
389
- icon = null;
390
- } else {
391
- icon = String(icon);
392
- }
393
- if (this._config.debug) console.log(`Icon field [${iconField}]:`, icon);
394
-
395
- // If ID is null, use the title as fallback
396
- if (id === null || id === '') {
397
- id = title;
398
- if (this._config.debug)
399
- console.log(`Using title as fallback for ID: ${id}`);
400
- }
401
-
402
364
  // Create the option object with non-empty values
403
365
  const result = {
404
366
  id: id || title || 'id-' + Math.random().toString(36).substr(2, 9), // Ensure we always have an ID
405
367
  title: title || 'Unnamed option',
406
- description: description,
407
- icon: icon,
408
368
  };
409
369
 
410
370
  if (this._config.debug)
@@ -6,7 +6,6 @@
6
6
  import { KTSelect } from './select';
7
7
  import { defaultTemplates } from './templates';
8
8
  import {
9
- handleDropdownKeyNavigation,
10
9
  filterOptions,
11
10
  FocusManager,
12
11
  EventManager,
@@ -17,7 +16,6 @@ export class KTSelectSearch {
17
16
  private _searchInput: HTMLInputElement;
18
17
  private _noResultsElement: HTMLElement | null = null;
19
18
  private _originalOptionContents = new Map<string, string>();
20
- private _boundKeyNavHandler: (event: KeyboardEvent) => void;
21
19
  private _eventManager: EventManager;
22
20
  private _focusManager: FocusManager;
23
21
  private _config: import('./config').KTSelectConfigInterface;
@@ -34,7 +32,6 @@ export class KTSelectSearch {
34
32
  '[data-kt-select-option]',
35
33
  select.getConfig(),
36
34
  );
37
- this._boundKeyNavHandler = this._handleKeyboardNavigation.bind(this);
38
35
  this.handleSearchInput = this._handleSearchInput.bind(this);
39
36
  this._config = select.getConfig();
40
37
  this._cacheOriginalOptionContents();
@@ -90,26 +87,13 @@ export class KTSelectSearch {
90
87
  });
91
88
  }
92
89
 
93
- // Add keyboard navigation for search results
94
- // This is stopping event propagation to prevent conflicts
95
- this._eventManager.addListener(
96
- this._searchInput,
97
- 'keydown',
98
- this._boundKeyNavHandler,
99
- );
100
-
101
90
  // Listen for dropdown close to reset options if search is empty
102
91
  this._select.getElement().addEventListener('dropdown.close', () => {
103
92
  this._focusManager.resetFocus();
104
- // Always clear highlights when dropdown closes
105
93
  this.clearSearchHighlights();
106
- if (!this._searchInput.value) {
107
- this._resetAllOptions();
108
- }
109
- // Clear the search input when dropdown closes if configured
110
- if (this._select.getConfig().clearSearchOnClose) {
111
- this._searchInput.value = '';
112
- }
94
+ this._searchInput.value = '';
95
+ this._resetAllOptions();
96
+ this._clearNoResultsMessage();
113
97
  });
114
98
 
115
99
  // Clear highlights when an option is selected
@@ -155,39 +139,6 @@ export class KTSelectSearch {
155
139
  }
156
140
  }
157
141
 
158
- /**
159
- * Handle keyboard navigation for search results
160
- */
161
- private _handleKeyboardNavigation(event: KeyboardEvent) {
162
- // Stop propagation to prevent multiple handlers from firing
163
- event.stopPropagation();
164
-
165
- if (this._config.debug) console.log('Search module keydown:', event.key);
166
-
167
- const visibleOptions = this._focusManager.getVisibleOptions();
168
- if (visibleOptions.length === 0) return;
169
-
170
- // Use the shared keyboard navigation handler with custom callbacks
171
- handleDropdownKeyNavigation(
172
- event,
173
- this._select,
174
- {
175
- multiple: this._select.getConfig().multiple,
176
- closeOnSelect: this._select.getConfig().closeOnSelect,
177
- },
178
- {
179
- onArrowDown: () => this._focusManager.focusNext(),
180
- onArrowUp: () => this._focusManager.focusPrevious(),
181
- onEnter: () => this._selectFocusedOption(),
182
- onClose: () => {
183
- if (event.key === 'Escape') {
184
- this.clearSearchHighlights();
185
- }
186
- },
187
- },
188
- );
189
- }
190
-
191
142
  /**
192
143
  * Select the currently focused option
193
144
  */
@@ -344,11 +295,11 @@ export class KTSelectSearch {
344
295
  this._clearNoResultsMessage();
345
296
 
346
297
  const config = this._select.getConfig();
347
- this._noResultsElement = defaultTemplates.noResults(config);
298
+ this._noResultsElement = defaultTemplates.empty(config);
348
299
 
349
300
  const dropdownElement = this._select.getDropdownElement();
350
301
  const optionsContainer = dropdownElement.querySelector(
351
- '[data-kt-select-options-container]',
302
+ '[data-kt-select-options]',
352
303
  );
353
304
  if (optionsContainer) {
354
305
  optionsContainer.appendChild(this._noResultsElement);
@@ -390,6 +341,15 @@ export class KTSelectSearch {
390
341
  */
391
342
  private _clearDisplayHighlights() {
392
343
  // Implementation for clearing display highlights
344
+ const options = Array.from(
345
+ this._select.getOptionsElement(),
346
+ ) as HTMLElement[];
347
+
348
+ options.forEach((option) => {
349
+ if (option.dataset && !option.dataset.originalText) {
350
+ option.dataset.originalText = option.innerHTML;
351
+ }
352
+ });
393
353
  }
394
354
 
395
355
  /**
@@ -103,3 +103,52 @@
103
103
  @apply aria-invalid:border-destructive aria-invalid:ring-destructive/20;
104
104
  }
105
105
  }
106
+
107
+ /* Temporary styles */
108
+ .kt-select-option.focus {
109
+ @apply bg-accent text-accent-foreground;
110
+ }
111
+
112
+ .kt-select-option.hover {
113
+ @apply bg-accent text-accent-foreground;
114
+ }
115
+
116
+ @custom-variant kt-select-option-selected {
117
+ [data-kt-select-option].selected {
118
+ @slot;
119
+ }
120
+
121
+ [data-kt-select-option].selected & {
122
+ @slot;
123
+ }
124
+ }
125
+
126
+ @custom-variant kt-select-option-focused {
127
+ [data-kt-select-option].focused {
128
+ @slot;
129
+ }
130
+
131
+ [data-kt-select-option].focused & {
132
+ @slot;
133
+ }
134
+ }
135
+
136
+ @custom-variant kt-select-option-disabled {
137
+ [data-kt-select-option].disabled {
138
+ @slot;
139
+ }
140
+
141
+ [data-kt-select-option].disabled & {
142
+ @slot;
143
+ }
144
+ }
145
+
146
+ @custom-variant kt-select-option-hover {
147
+ [data-kt-select-option]:hover {
148
+ @slot;
149
+ }
150
+
151
+ [data-kt-select-option]:hover & {
152
+ @slot;
153
+ }
154
+ }