@keenthemes/ktui 1.2.6 → 1.2.7

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 (190) hide show
  1. package/README.md +14 -5
  2. package/dist/ktui.js +3775 -2298
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +25 -5
  6. package/lib/cjs/components/datatable/datatable-checkbox.d.ts +37 -1
  7. package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
  8. package/lib/cjs/components/datatable/datatable-checkbox.js +143 -156
  9. package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
  10. package/lib/cjs/components/datatable/datatable-column-utils.d.ts +30 -0
  11. package/lib/cjs/components/datatable/datatable-column-utils.d.ts.map +1 -0
  12. package/lib/cjs/components/datatable/datatable-column-utils.js +42 -0
  13. package/lib/cjs/components/datatable/datatable-column-utils.js.map +1 -0
  14. package/lib/cjs/components/datatable/datatable-contracts.d.ts +2 -4
  15. package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -1
  16. package/lib/cjs/components/datatable/datatable-defaults.d.ts +20 -0
  17. package/lib/cjs/components/datatable/datatable-defaults.d.ts.map +1 -0
  18. package/lib/cjs/components/datatable/datatable-defaults.js +193 -0
  19. package/lib/cjs/components/datatable/datatable-defaults.js.map +1 -0
  20. package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts.map +1 -1
  21. package/lib/cjs/components/datatable/datatable-layout-plugin.js +11 -1
  22. package/lib/cjs/components/datatable/datatable-layout-plugin.js.map +1 -1
  23. package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -1
  24. package/lib/cjs/components/datatable/datatable-local-provider.js +80 -24
  25. package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
  26. package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  27. package/lib/cjs/components/datatable/datatable-pagination-renderer.js +3 -2
  28. package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
  29. package/lib/cjs/components/datatable/datatable-registry.d.ts +18 -0
  30. package/lib/cjs/components/datatable/datatable-registry.d.ts.map +1 -0
  31. package/lib/cjs/components/datatable/datatable-registry.js +66 -0
  32. package/lib/cjs/components/datatable/datatable-registry.js.map +1 -0
  33. package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  34. package/lib/cjs/components/datatable/datatable-remote-provider.js +1 -2
  35. package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
  36. package/lib/cjs/components/datatable/datatable-search-handler.d.ts +10 -0
  37. package/lib/cjs/components/datatable/datatable-search-handler.d.ts.map +1 -0
  38. package/lib/cjs/components/datatable/datatable-search-handler.js +65 -0
  39. package/lib/cjs/components/datatable/datatable-search-handler.js.map +1 -0
  40. package/lib/cjs/components/datatable/datatable-sort.d.ts +31 -4
  41. package/lib/cjs/components/datatable/datatable-sort.d.ts.map +1 -1
  42. package/lib/cjs/components/datatable/datatable-sort.js +86 -58
  43. package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
  44. package/lib/cjs/components/datatable/datatable-spinner.d.ts +30 -0
  45. package/lib/cjs/components/datatable/datatable-spinner.d.ts.map +1 -0
  46. package/lib/cjs/components/datatable/datatable-spinner.js +54 -0
  47. package/lib/cjs/components/datatable/datatable-spinner.js.map +1 -0
  48. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts +19 -0
  49. package/lib/cjs/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  50. package/lib/cjs/components/datatable/datatable-state-persistence.js +59 -0
  51. package/lib/cjs/components/datatable/datatable-state-persistence.js.map +1 -0
  52. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +2 -0
  53. package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  54. package/lib/cjs/components/datatable/datatable-table-renderer.js +75 -16
  55. package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
  56. package/lib/cjs/components/datatable/datatable-utils.d.ts +10 -0
  57. package/lib/cjs/components/datatable/datatable-utils.d.ts.map +1 -0
  58. package/lib/cjs/components/datatable/datatable-utils.js +15 -0
  59. package/lib/cjs/components/datatable/datatable-utils.js.map +1 -0
  60. package/lib/cjs/components/datatable/datatable.d.ts +26 -34
  61. package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
  62. package/lib/cjs/components/datatable/datatable.js +155 -492
  63. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  64. package/lib/cjs/components/datatable/index.d.ts +1 -1
  65. package/lib/cjs/components/datatable/index.d.ts.map +1 -1
  66. package/lib/cjs/components/datatable/types.d.ts +100 -11
  67. package/lib/cjs/components/datatable/types.d.ts.map +1 -1
  68. package/lib/cjs/index.d.ts +1 -1
  69. package/lib/cjs/index.d.ts.map +1 -1
  70. package/lib/cjs/index.js +6 -0
  71. package/lib/cjs/index.js.map +1 -1
  72. package/lib/esm/components/datatable/datatable-checkbox.d.ts +37 -1
  73. package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
  74. package/lib/esm/components/datatable/datatable-checkbox.js +142 -155
  75. package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
  76. package/lib/esm/components/datatable/datatable-column-utils.d.ts +30 -0
  77. package/lib/esm/components/datatable/datatable-column-utils.d.ts.map +1 -0
  78. package/lib/esm/components/datatable/datatable-column-utils.js +38 -0
  79. package/lib/esm/components/datatable/datatable-column-utils.js.map +1 -0
  80. package/lib/esm/components/datatable/datatable-contracts.d.ts +2 -4
  81. package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
  82. package/lib/esm/components/datatable/datatable-defaults.d.ts +20 -0
  83. package/lib/esm/components/datatable/datatable-defaults.d.ts.map +1 -0
  84. package/lib/esm/components/datatable/datatable-defaults.js +190 -0
  85. package/lib/esm/components/datatable/datatable-defaults.js.map +1 -0
  86. package/lib/esm/components/datatable/datatable-layout-plugin.d.ts.map +1 -1
  87. package/lib/esm/components/datatable/datatable-layout-plugin.js +11 -1
  88. package/lib/esm/components/datatable/datatable-layout-plugin.js.map +1 -1
  89. package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
  90. package/lib/esm/components/datatable/datatable-local-provider.js +80 -24
  91. package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
  92. package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
  93. package/lib/esm/components/datatable/datatable-pagination-renderer.js +3 -2
  94. package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
  95. package/lib/esm/components/datatable/datatable-registry.d.ts +18 -0
  96. package/lib/esm/components/datatable/datatable-registry.d.ts.map +1 -0
  97. package/lib/esm/components/datatable/datatable-registry.js +63 -0
  98. package/lib/esm/components/datatable/datatable-registry.js.map +1 -0
  99. package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
  100. package/lib/esm/components/datatable/datatable-remote-provider.js +1 -2
  101. package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
  102. package/lib/esm/components/datatable/datatable-search-handler.d.ts +10 -0
  103. package/lib/esm/components/datatable/datatable-search-handler.d.ts.map +1 -0
  104. package/lib/esm/components/datatable/datatable-search-handler.js +62 -0
  105. package/lib/esm/components/datatable/datatable-search-handler.js.map +1 -0
  106. package/lib/esm/components/datatable/datatable-sort.d.ts +31 -4
  107. package/lib/esm/components/datatable/datatable-sort.d.ts.map +1 -1
  108. package/lib/esm/components/datatable/datatable-sort.js +85 -57
  109. package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
  110. package/lib/esm/components/datatable/datatable-spinner.d.ts +30 -0
  111. package/lib/esm/components/datatable/datatable-spinner.d.ts.map +1 -0
  112. package/lib/esm/components/datatable/datatable-spinner.js +51 -0
  113. package/lib/esm/components/datatable/datatable-spinner.js.map +1 -0
  114. package/lib/esm/components/datatable/datatable-state-persistence.d.ts +19 -0
  115. package/lib/esm/components/datatable/datatable-state-persistence.d.ts.map +1 -0
  116. package/lib/esm/components/datatable/datatable-state-persistence.js +55 -0
  117. package/lib/esm/components/datatable/datatable-state-persistence.js.map +1 -0
  118. package/lib/esm/components/datatable/datatable-table-renderer.d.ts +2 -0
  119. package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
  120. package/lib/esm/components/datatable/datatable-table-renderer.js +75 -16
  121. package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
  122. package/lib/esm/components/datatable/datatable-utils.d.ts +10 -0
  123. package/lib/esm/components/datatable/datatable-utils.d.ts.map +1 -0
  124. package/lib/esm/components/datatable/datatable-utils.js +12 -0
  125. package/lib/esm/components/datatable/datatable-utils.js.map +1 -0
  126. package/lib/esm/components/datatable/datatable.d.ts +26 -34
  127. package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
  128. package/lib/esm/components/datatable/datatable.js +157 -494
  129. package/lib/esm/components/datatable/datatable.js.map +1 -1
  130. package/lib/esm/components/datatable/index.d.ts +1 -1
  131. package/lib/esm/components/datatable/index.d.ts.map +1 -1
  132. package/lib/esm/components/datatable/types.d.ts +100 -11
  133. package/lib/esm/components/datatable/types.d.ts.map +1 -1
  134. package/lib/esm/index.d.ts +1 -1
  135. package/lib/esm/index.d.ts.map +1 -1
  136. package/lib/esm/index.js +6 -0
  137. package/lib/esm/index.js.map +1 -1
  138. package/package.json +5 -1
  139. package/skills/ktui/SKILL.md +711 -0
  140. package/skills/ktui-datatable/SKILL.md +302 -0
  141. package/skills/ktui-install/SKILL.md +150 -0
  142. package/skills/ktui-select/SKILL.md +271 -0
  143. package/src/components/__tests__/component.test.ts +347 -0
  144. package/src/components/collapse/collapse.css +2 -2
  145. package/src/components/datatable/__tests__/architecture-boundaries.test.ts +56 -8
  146. package/src/components/datatable/__tests__/currency-sort.test.ts +25 -28
  147. package/src/components/datatable/__tests__/datatable-checkbox.test.ts +527 -0
  148. package/src/components/datatable/__tests__/datatable-column-utils.test.ts +117 -0
  149. package/src/components/datatable/__tests__/datatable-defaults.test.ts +57 -0
  150. package/src/components/datatable/__tests__/datatable-finalize-extended.test.ts +361 -0
  151. package/src/components/datatable/__tests__/datatable-fixed-layout.test.ts +427 -0
  152. package/src/components/datatable/__tests__/datatable-improvements.test.ts +484 -0
  153. package/src/components/datatable/__tests__/datatable-pagination-extended.test.ts +508 -0
  154. package/src/components/datatable/__tests__/datatable-public-api.test.ts +269 -0
  155. package/src/components/datatable/__tests__/datatable-registry.test.ts +172 -0
  156. package/src/components/datatable/__tests__/datatable-remote-provider.test.ts +468 -0
  157. package/src/components/datatable/__tests__/datatable-search-handler.test.ts +124 -0
  158. package/src/components/datatable/__tests__/datatable-sort-extended.test.ts +417 -0
  159. package/src/components/datatable/__tests__/datatable-spinner.test.ts +95 -0
  160. package/src/components/datatable/__tests__/datatable-table-renderer-extended.test.ts +425 -0
  161. package/src/components/datatable/__tests__/datatable-types.test.ts +117 -0
  162. package/src/components/datatable/__tests__/datatable-utils.test.ts +52 -0
  163. package/src/components/datatable/__tests__/multi-row-headers.test.ts +7 -7
  164. package/src/components/datatable/__tests__/pagination-reset.test.ts +129 -6
  165. package/src/components/datatable/__tests__/race-conditions.test.ts +11 -11
  166. package/src/components/datatable/__tests__/setup.ts +12 -4
  167. package/src/components/datatable/datatable-checkbox.ts +144 -145
  168. package/src/components/datatable/datatable-column-utils.ts +63 -0
  169. package/src/components/datatable/datatable-contracts.ts +2 -3
  170. package/src/components/datatable/datatable-defaults.ts +204 -0
  171. package/src/components/datatable/datatable-layout-plugin.ts +11 -1
  172. package/src/components/datatable/datatable-local-provider.ts +91 -28
  173. package/src/components/datatable/datatable-pagination-renderer.ts +3 -2
  174. package/src/components/datatable/datatable-registry.ts +89 -0
  175. package/src/components/datatable/datatable-remote-provider.ts +1 -3
  176. package/src/components/datatable/datatable-search-handler.ts +97 -0
  177. package/src/components/datatable/datatable-sort.ts +111 -66
  178. package/src/components/datatable/datatable-spinner.ts +103 -0
  179. package/src/components/datatable/datatable-state-persistence.ts +67 -0
  180. package/src/components/datatable/datatable-table-renderer.ts +81 -18
  181. package/src/components/datatable/datatable-utils.ts +12 -0
  182. package/src/components/datatable/datatable.ts +191 -580
  183. package/src/components/datatable/index.ts +3 -0
  184. package/src/components/datatable/types.ts +124 -23
  185. package/src/helpers/__tests__/dom.test.ts +776 -0
  186. package/src/helpers/__tests__/utils.test.ts +332 -0
  187. package/src/index.ts +10 -0
  188. package/skills/ktui-components/SKILL.md +0 -41
  189. package/skills/ktui-theming/SKILL.md +0 -50
  190. package/src/components/datatable/datatable-event-adapter.ts +0 -21
@@ -0,0 +1,271 @@
1
+ ---
2
+ name: ktui-select
3
+ description: >
4
+ KtUI Select (KTSelect) — rich searchable multi-select dropdown replacing native select.
5
+ Tags, combobox, remote data, pagination, select-all, events, and programmatic API.
6
+ Use this skill when building, debugging, or customizing Select components.
7
+ ---
8
+
9
+ # KTSelect — AI Agent Reference
10
+
11
+ Full reference for the Select component in [KtUI](https://ktui.io).
12
+ Package: `@keenthemes/ktui`. Class: `KTSelect`. Root attribute: `data-kt-select`.
13
+
14
+ > **Always prefer [ktui.io/docs/select](https://ktui.io/docs/select) docs and examples over guessing markup or options.**
15
+
16
+ ---
17
+
18
+ ## 1. Basic Usage
19
+
20
+ ```html
21
+ <select data-kt-select="true" data-kt-select-enable-search="true">
22
+ <option value="">Select...</option>
23
+ <option value="1">Option 1</option>
24
+ <option value="2">Option 2</option>
25
+ </select>
26
+ ```
27
+
28
+ ```ts
29
+ import { KTSelect } from '@keenthemes/ktui';
30
+
31
+ const select = KTSelect.getInstance(el);
32
+ ```
33
+
34
+ ---
35
+
36
+ ## 2. Key Features
37
+
38
+ | Feature | Attribute | Description |
39
+ |---------|-----------|-------------|
40
+ | Search | `data-kt-select-enable-search="true"` | Adds search input in dropdown |
41
+ | Multi-select | `multiple` attribute on `<select>` | Allow multiple selections |
42
+ | Tags mode | `data-kt-select-tags="true"` | Shows selected items as removable tags |
43
+ | Combobox | `data-kt-select-combobox="true"` | Allows free text input |
44
+ | Remote data | `data-kt-select-remote="true"` | Load options from API |
45
+ | Remote URL | `data-kt-select-remote-url="..."` | API endpoint for remote data |
46
+ | Pagination | `data-kt-select-pagination="true"` | Adds "Load More" button |
47
+ | Placeholder | `data-kt-select-placeholder="Choose..."` | Placeholder text |
48
+ | Select all | `data-kt-select-select-all="true"` | Adds "Select All" button (multi-mode) |
49
+ | Close on Enter | `data-kt-select-close-on-enter="true"` | Close dropdown when Enter pressed |
50
+ | Close on other open | `data-kt-select-close-on-other-open="true"` | Close when another select opens |
51
+ | Search autofocus | `data-kt-select-search-autofocus="true"` | Focus search input on open |
52
+ | Dispatch global events | `data-kt-select-dispatch-global-events="true"` | Dispatch events on document |
53
+
54
+ ---
55
+
56
+ ## 3. Global Config
57
+
58
+ Set defaults for all future instances:
59
+
60
+ ```ts
61
+ KTSelect.config({
62
+ enableSearch: true,
63
+ searchPlaceholder: 'Type to search...',
64
+ dropdownZindex: 9999,
65
+ height: 300,
66
+ closeOnEnter: true,
67
+ closeOnOtherOpen: true,
68
+ searchAutofocus: true,
69
+ dispatchGlobalEvents: true,
70
+ });
71
+ ```
72
+
73
+ ---
74
+
75
+ ## 4. Programmatic API
76
+
77
+ ```ts
78
+ const select = KTSelect.getInstance(el);
79
+
80
+ // Dropdown control
81
+ select.openDropdown();
82
+ select.closeDropdown();
83
+ select.toggleSelection(value);
84
+
85
+ // Selection
86
+ select.getSelectedOptions(); // array of selected option objects
87
+ select.setSelectedOptions([opt]); // set selection programmatically
88
+ select.clearSelection(); // clear all
89
+
90
+ // Lifecycle
91
+ select.dispose();
92
+ ```
93
+
94
+ ### Instance management
95
+
96
+ | Static method | Returns |
97
+ |--------------|---------|
98
+ | `KTSelect.getInstance(el)` | Existing instance or `null` |
99
+ | `KTSelect.getOrCreateInstance(el, config?)` | Existing or new instance |
100
+ | `KTSelect.init()` | Scans DOM, creates instances |
101
+ | `KTSelect.config(opts)` | Set global defaults |
102
+
103
+ ---
104
+
105
+ ## 5. Events
106
+
107
+ Events fire through dual channel:
108
+ 1. **Internal callbacks** (`.on()`) — bare name
109
+ 2. **DOM CustomEvents** — dispatched on both element and document
110
+
111
+ ### Confirmed events (from source)
112
+
113
+ | Event | When |
114
+ |-------|------|
115
+ | `show` | Dropdown opening |
116
+ | `close` | Dropdown closing |
117
+ | `change` | Selection changed |
118
+ | `enabled` | Component enabled |
119
+ | `disabled` | Component disabled |
120
+ | `updated` | Options updated |
121
+ | `updateError` | Options update failed |
122
+ | `reloadStart` | Remote reload started |
123
+ | `reloadComplete` | Remote reload finished |
124
+ | `reloadError` | Remote reload failed |
125
+ | `refreshed` | Options refreshed |
126
+ | `refreshError` | Options refresh failed |
127
+
128
+ ### Namespaced events on document
129
+
130
+ ```ts
131
+ document.addEventListener('kt-select:change', (e) => {
132
+ console.log(e.detail.instance.getSelectedOptions());
133
+ });
134
+
135
+ document.addEventListener('kt-select:show', (e) => {
136
+ console.log('Select opened', e.detail.element);
137
+ });
138
+ ```
139
+
140
+ ### Element-level events
141
+
142
+ ```ts
143
+ el.addEventListener('change', (e) => {
144
+ console.log(e.detail.instance.getSelectedOptions());
145
+ });
146
+ ```
147
+
148
+ ### Event detail shape
149
+
150
+ ```ts
151
+ {
152
+ instance: KTSelect, // the select instance
153
+ element: HTMLElement, // the root element
154
+ payload: { ... } // event-specific data
155
+ }
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 6. Remote Data
161
+
162
+ ```html
163
+ <select data-kt-select="true"
164
+ data-kt-select-remote="true"
165
+ data-kt-select-remote-url="https://api.example.com/options"
166
+ data-kt-select-pagination="true">
167
+ </select>
168
+ ```
169
+
170
+ The remote provider fetches options from the URL. Use `pagination: true` for paginated APIs.
171
+
172
+ ---
173
+
174
+ ## 7. Tags Mode
175
+
176
+ ```html
177
+ <select data-kt-select="true"
178
+ data-kt-select-tags="true"
179
+ multiple>
180
+ <option value="1">Tag 1</option>
181
+ <option value="2">Tag 2</option>
182
+ <option value="3">Tag 3</option>
183
+ </select>
184
+ ```
185
+
186
+ Selected items appear as removable tag chips. Works with search and remote data.
187
+
188
+ ---
189
+
190
+ ## 8. Combobox Mode
191
+
192
+ ```html
193
+ <input type="text"
194
+ data-kt-select="true"
195
+ data-kt-select-combobox="true"
196
+ data-kt-select-remote="true"
197
+ data-kt-select-remote-url="https://api.example.com/search" />
198
+ ```
199
+
200
+ Allows free text input alongside selecting from dropdown options.
201
+
202
+ ---
203
+
204
+ ## 9. Select All
205
+
206
+ ```html
207
+ <select data-kt-select="true"
208
+ data-kt-select-select-all="true"
209
+ data-kt-select-enable-search="true"
210
+ multiple>
211
+ <option value="1">Option 1</option>
212
+ <option value="2">Option 2</option>
213
+ <option value="3">Option 3</option>
214
+ </select>
215
+ ```
216
+
217
+ Adds a "Select All" button at the top of the dropdown in multi-select mode.
218
+
219
+ ---
220
+
221
+ ## 10. Keyboard Navigation
222
+
223
+ | Key | Action |
224
+ |-----|--------|
225
+ | `Enter` | Select focused option, close dropdown (if `closeOnEnter` enabled) |
226
+ | `ArrowDown` | Focus next option |
227
+ | `ArrowUp` | Focus previous option |
228
+ | `Escape` | Close dropdown |
229
+ | `Backspace` | Remove last tag (tags mode) |
230
+
231
+ ---
232
+
233
+ ## 11. Architecture
234
+
235
+ Source: `src/components/select/`
236
+
237
+ | File | Purpose |
238
+ |------|---------|
239
+ | `select.ts` | Main class — `openDropdown()`, `closeDropdown()`, `clearSelection()`, `dispose()` |
240
+ | `dropdown.ts` | Dropdown positioning and visibility |
241
+ | `search.ts` | Search input filtering |
242
+ | `tags.ts` | Tags mode rendering and removal |
243
+ | `combobox.ts` | Free text input mode |
244
+ | `remote.ts` | Remote data provider |
245
+ | `option.ts` | Option element management |
246
+ | `templates.ts` | HTML template generation |
247
+ | `config.ts` | Default config constants |
248
+ | `utils.ts` | Shared utilities |
249
+ | `types.ts` | Type definitions |
250
+ | `index.ts` | Barrel exports |
251
+
252
+ ---
253
+
254
+ ## 12. Common Pitfalls
255
+
256
+ | Problem | Cause | Fix |
257
+ |---------|-------|-----|
258
+ | Select not searchable | Missing `enable-search` attribute | Add `data-kt-select-enable-search="true"` |
259
+ | No tags shown | Missing `tags` attribute | Add `data-kt-select-tags="true"` |
260
+ | Remote not loading | Missing remote attributes | Add both `data-kt-select-remote="true"` and `data-kt-select-remote-url="..."` |
261
+ | Dropdown wrong position | Container overflow hidden | Use `data-kt-dropdown-container="body"` to portal |
262
+ | Select All not appearing | Not in multi-mode | Add `multiple` attribute to `<select>` |
263
+ | Events not firing on document | `dispatchGlobalEvents` disabled | Enable via config or attribute |
264
+ | `shown`/`hide`/`hidden` events not working | Wrong event names | Use `show`, `close`, `change` (actual events from source) |
265
+
266
+ ---
267
+
268
+ ## 13. Documentation
269
+
270
+ - **Select docs:** [ktui.io/docs/select](https://ktui.io/docs/select)
271
+ - **Changelog:** [ktui.io/docs/changelog](https://ktui.io/docs/changelog)
@@ -0,0 +1,347 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import KTComponent from '../component';
3
+ import KTData from '../../helpers/data';
4
+
5
+ class TestComponent extends KTComponent {
6
+ protected _name = 'test';
7
+ protected _defaultConfig = {};
8
+ protected _config = {};
9
+
10
+ constructor(element: HTMLElement | null) {
11
+ super();
12
+ this._init(element);
13
+ }
14
+
15
+ public testFireEvent(eventType: string, payload?: object | null) {
16
+ return this._fireEvent(eventType, payload);
17
+ }
18
+
19
+ public testDispatchEvent(eventType: string, payload?: object | null) {
20
+ this._dispatchEvent(eventType, payload);
21
+ }
22
+
23
+ public testGetOption(name: string) {
24
+ return this._getOption(name);
25
+ }
26
+
27
+ public testBuildConfig(config: object = {}) {
28
+ this._buildConfig(config);
29
+ }
30
+
31
+ public testMergeConfig(config: object) {
32
+ this._mergeConfig(config);
33
+ }
34
+
35
+ public testGetGlobalConfig() {
36
+ return this._getGlobalConfig();
37
+ }
38
+
39
+ public testShouldSkipInit(element: HTMLElement) {
40
+ return this._shouldSkipInit(element);
41
+ }
42
+ }
43
+
44
+ describe('KTComponent', () => {
45
+ beforeEach(() => {
46
+ document.body.innerHTML = '';
47
+ });
48
+
49
+ describe('constructor / _init', () => {
50
+ it('stores element and generates namespace', () => {
51
+ const el = document.createElement('div');
52
+ document.body.appendChild(el);
53
+ const comp = new TestComponent(el);
54
+ expect(comp.getElement()).toBe(el);
55
+ });
56
+
57
+ it('sets data-kt-test-initialized attribute on element', () => {
58
+ const el = document.createElement('div');
59
+ document.body.appendChild(el);
60
+ new TestComponent(el);
61
+ expect(el.getAttribute('data-kt-test-initialized')).toBe('true');
62
+ });
63
+
64
+ it('stores instance in KTData', () => {
65
+ const el = document.createElement('div');
66
+ document.body.appendChild(el);
67
+ const comp = new TestComponent(el);
68
+ expect(KTData.get(el, 'test')).toBe(comp);
69
+ });
70
+
71
+ it('handles null element gracefully', () => {
72
+ const comp = new TestComponent(null);
73
+ expect(comp.getElement()).toBeNull();
74
+ });
75
+ });
76
+
77
+ describe('dispose', () => {
78
+ it('removes initialized attribute from element', () => {
79
+ const el = document.createElement('div');
80
+ document.body.appendChild(el);
81
+ const comp = new TestComponent(el);
82
+ comp.dispose();
83
+ expect(el.hasAttribute('data-kt-test-initialized')).toBe(false);
84
+ });
85
+
86
+ it('removes instance from KTData', () => {
87
+ const el = document.createElement('div');
88
+ document.body.appendChild(el);
89
+ const comp = new TestComponent(el);
90
+ comp.dispose();
91
+ expect(KTData.get(el, 'test')).toBeNull();
92
+ });
93
+
94
+ it('does nothing when element is null', () => {
95
+ const comp = new TestComponent(null);
96
+ expect(() => comp.dispose()).not.toThrow();
97
+ });
98
+ });
99
+
100
+ describe('on / _fireEvent', () => {
101
+ it('registers and fires event handler', async () => {
102
+ const el = document.createElement('div');
103
+ document.body.appendChild(el);
104
+ const comp = new TestComponent(el);
105
+ const handler = vi.fn();
106
+
107
+ comp.on('myEvent', handler);
108
+ await comp.testFireEvent('myEvent', { data: 123 });
109
+ expect(handler).toHaveBeenCalledWith({ data: 123 });
110
+ });
111
+
112
+ it('returns unique eventId from on()', () => {
113
+ const el = document.createElement('div');
114
+ document.body.appendChild(el);
115
+ const comp = new TestComponent(el);
116
+
117
+ const id1 = comp.on('evt', () => {});
118
+ const id2 = comp.on('evt', () => {});
119
+ expect(id1).not.toBe(id2);
120
+ });
121
+
122
+ it('fires multiple handlers for the same event', async () => {
123
+ const el = document.createElement('div');
124
+ document.body.appendChild(el);
125
+ const comp = new TestComponent(el);
126
+ const h1 = vi.fn();
127
+ const h2 = vi.fn();
128
+
129
+ comp.on('evt', h1);
130
+ comp.on('evt', h2);
131
+ await comp.testFireEvent('evt');
132
+ expect(h1).toHaveBeenCalled();
133
+ expect(h2).toHaveBeenCalled();
134
+ });
135
+
136
+ it('does not throw when no handlers registered', async () => {
137
+ const el = document.createElement('div');
138
+ document.body.appendChild(el);
139
+ const comp = new TestComponent(el);
140
+ await expect(comp.testFireEvent('nonexistent')).resolves.not.toThrow();
141
+ });
142
+
143
+ it('calls handlers with null when no payload', async () => {
144
+ const el = document.createElement('div');
145
+ document.body.appendChild(el);
146
+ const comp = new TestComponent(el);
147
+ const handler = vi.fn();
148
+
149
+ comp.on('evt', handler);
150
+ await comp.testFireEvent('evt');
151
+ expect(handler).toHaveBeenCalledWith(null);
152
+ });
153
+ });
154
+
155
+ describe('off', () => {
156
+ it('removes a specific event handler', async () => {
157
+ const el = document.createElement('div');
158
+ document.body.appendChild(el);
159
+ const comp = new TestComponent(el);
160
+ const handler = vi.fn();
161
+
162
+ const id = comp.on('evt', handler);
163
+ comp.off('evt', id);
164
+ await comp.testFireEvent('evt');
165
+ expect(handler).not.toHaveBeenCalled();
166
+ });
167
+
168
+ it('does not throw when removing non-existent handler', () => {
169
+ const el = document.createElement('div');
170
+ document.body.appendChild(el);
171
+ const comp = new TestComponent(el);
172
+ expect(() => comp.off('evt', 'nonexistent-id')).not.toThrow();
173
+ });
174
+ });
175
+
176
+ describe('_dispatchEvent', () => {
177
+ it('dispatches CustomEvent on element', () => {
178
+ const el = document.createElement('div');
179
+ document.body.appendChild(el);
180
+ const comp = new TestComponent(el);
181
+ const listener = vi.fn();
182
+ el.addEventListener('kt.test.myEvent', listener);
183
+
184
+ comp.testDispatchEvent('kt.test.myEvent', { foo: 'bar' });
185
+ expect(listener).toHaveBeenCalled();
186
+ const event = listener.mock.calls[0][0] as CustomEvent;
187
+ expect(event.detail).toEqual({ payload: { foo: 'bar' } });
188
+ expect(event.bubbles).toBe(true);
189
+ });
190
+
191
+ it('does not throw when element is null', () => {
192
+ const comp = new TestComponent(null);
193
+ expect(() =>
194
+ comp.testDispatchEvent('kt.test.evt'),
195
+ ).not.toThrow();
196
+ });
197
+
198
+ it('dispatches with null payload when not provided', () => {
199
+ const el = document.createElement('div');
200
+ document.body.appendChild(el);
201
+ const comp = new TestComponent(el);
202
+ const listener = vi.fn();
203
+ el.addEventListener('kt.test.evt', listener);
204
+
205
+ comp.testDispatchEvent('kt.test.evt');
206
+ expect(listener).toHaveBeenCalled();
207
+ const event = listener.mock.calls[0][0] as CustomEvent;
208
+ expect(event.detail).toEqual({ payload: null });
209
+ });
210
+ });
211
+
212
+ describe('getElement', () => {
213
+ it('returns element', () => {
214
+ const el = document.createElement('div');
215
+ document.body.appendChild(el);
216
+ const comp = new TestComponent(el);
217
+ expect(comp.getElement()).toBe(el);
218
+ });
219
+
220
+ it('returns null when not initialized', () => {
221
+ const comp = new TestComponent(null);
222
+ expect(comp.getElement()).toBeNull();
223
+ });
224
+ });
225
+
226
+ describe('_shouldSkipInit', () => {
227
+ it('returns false when element has no existing instance', () => {
228
+ const el = document.createElement('div');
229
+ document.body.appendChild(el);
230
+ const comp = new TestComponent(document.createElement('span'));
231
+ expect(comp.testShouldSkipInit(el)).toBe(false);
232
+ });
233
+
234
+ it('returns true when element has existing instance and is connected', () => {
235
+ const el = document.createElement('div');
236
+ document.body.appendChild(el);
237
+ new TestComponent(el); // first init
238
+ const comp2 = new TestComponent(document.createElement('span'));
239
+ expect(comp2.testShouldSkipInit(el)).toBe(true);
240
+ });
241
+
242
+ it('returns false when element has existing instance but is disconnected', () => {
243
+ const el = document.createElement('div');
244
+ // Don't append to document — element is disconnected
245
+ const comp1 = new TestComponent(el);
246
+ const comp2 = new TestComponent(document.createElement('span'));
247
+ // The old instance should be disposed, so this returns false (allowing reinit)
248
+ expect(comp2.testShouldSkipInit(el)).toBe(false);
249
+ });
250
+ });
251
+
252
+ describe('_getOption', () => {
253
+ it('returns config value', () => {
254
+ const el = document.createElement('div');
255
+ document.body.appendChild(el);
256
+ const comp = new TestComponent(el);
257
+ (comp as any)._config = { myOption: 'value' };
258
+ expect(comp.testGetOption('myOption')).toBe('value');
259
+ });
260
+
261
+ it('returns undefined for missing option', () => {
262
+ const el = document.createElement('div');
263
+ document.body.appendChild(el);
264
+ const comp = new TestComponent(el);
265
+ (comp as any)._config = {};
266
+ expect(comp.testGetOption('missing')).toBeUndefined();
267
+ });
268
+ });
269
+
270
+ describe('_buildConfig', () => {
271
+ it('merges default config, global config, data attributes, and passed config', () => {
272
+ const el = document.createElement('div');
273
+ el.setAttribute('data-kt-test-foo', 'bar');
274
+ document.body.appendChild(el);
275
+ const comp = new TestComponent(el);
276
+ (comp as any)._defaultConfig = { defaultOpt: 1 };
277
+
278
+ window.KTGlobalComponentsConfig = {
279
+ test: { globalOpt: 2 },
280
+ };
281
+
282
+ comp.testBuildConfig({ extraOpt: 3 });
283
+ const config = (comp as any)._config;
284
+ expect(config.defaultOpt).toBe(1);
285
+ expect(config.globalOpt).toBe(2);
286
+ expect(config.foo).toBe('bar');
287
+ expect(config.extraOpt).toBe(3);
288
+
289
+ delete (window as any).KTGlobalComponentsConfig;
290
+ });
291
+
292
+ it('does nothing when element is null', () => {
293
+ const comp = new TestComponent(null);
294
+ expect(() => comp.testBuildConfig()).not.toThrow();
295
+ });
296
+ });
297
+
298
+ describe('_mergeConfig', () => {
299
+ it('merges config into existing _config', () => {
300
+ const el = document.createElement('div');
301
+ document.body.appendChild(el);
302
+ const comp = new TestComponent(el);
303
+ (comp as any)._config = { a: 1 };
304
+ comp.testMergeConfig({ b: 2 });
305
+ expect((comp as any)._config).toEqual({ a: 1, b: 2 });
306
+ });
307
+
308
+ it('does nothing for empty config', () => {
309
+ const el = document.createElement('div');
310
+ document.body.appendChild(el);
311
+ const comp = new TestComponent(el);
312
+ (comp as any)._config = { a: 1 };
313
+ comp.testMergeConfig({});
314
+ expect((comp as any)._config).toEqual({ a: 1 });
315
+ });
316
+
317
+ it('does nothing for null config', () => {
318
+ const el = document.createElement('div');
319
+ document.body.appendChild(el);
320
+ const comp = new TestComponent(el);
321
+ (comp as any)._config = { a: 1 };
322
+ comp.testMergeConfig(null as unknown as object);
323
+ expect((comp as any)._config).toEqual({ a: 1 });
324
+ });
325
+ });
326
+
327
+ describe('_getGlobalConfig', () => {
328
+ it('returns global config for component name', () => {
329
+ const el = document.createElement('div');
330
+ document.body.appendChild(el);
331
+ const comp = new TestComponent(el);
332
+ window.KTGlobalComponentsConfig = {
333
+ test: { global: true },
334
+ };
335
+ expect(comp.testGetGlobalConfig()).toEqual({ global: true });
336
+ delete (window as any).KTGlobalComponentsConfig;
337
+ });
338
+
339
+ it('returns empty object when no global config', () => {
340
+ const el = document.createElement('div');
341
+ document.body.appendChild(el);
342
+ const comp = new TestComponent(el);
343
+ delete (window as any).KTGlobalComponentsConfig;
344
+ expect(comp.testGetGlobalConfig()).toEqual({});
345
+ });
346
+ });
347
+ });
@@ -4,11 +4,11 @@
4
4
  */
5
5
 
6
6
  @custom-variant kt-collapse-active {
7
- [data-kt-collapse-initialized].active & {
7
+ &[data-kt-collapse-initialized].active {
8
8
  @slot;
9
9
  }
10
10
 
11
- [data-kt-collapse-initialized].active {
11
+ [data-kt-collapse-initialized].active & {
12
12
  @slot;
13
13
  }
14
14
  }