@keenthemes/ktui 1.2.5 → 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.
- package/README.md +14 -5
- package/dist/ktui.js +1538 -786
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +85 -5
- package/lib/cjs/components/datatable/datatable-checkbox.d.ts +37 -1
- package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-checkbox.js +143 -156
- package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-column-utils.d.ts +30 -0
- package/lib/cjs/components/datatable/datatable-column-utils.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-column-utils.js +42 -0
- package/lib/cjs/components/datatable/datatable-column-utils.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-contracts.d.ts +2 -4
- package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-defaults.d.ts +20 -0
- package/lib/cjs/components/datatable/datatable-defaults.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-defaults.js +193 -0
- package/lib/cjs/components/datatable/datatable-defaults.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts +7 -0
- package/lib/cjs/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-layout-plugin.js +338 -0
- package/lib/cjs/components/datatable/datatable-layout-plugin.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-local-provider.d.ts +2 -2
- package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-local-provider.js +85 -27
- package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js +13 -13
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-registry.d.ts +18 -0
- package/lib/cjs/components/datatable/datatable-registry.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-registry.js +66 -0
- package/lib/cjs/components/datatable/datatable-registry.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-remote-provider.js +1 -2
- package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-search-handler.d.ts +10 -0
- package/lib/cjs/components/datatable/datatable-search-handler.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-search-handler.js +65 -0
- package/lib/cjs/components/datatable/datatable-search-handler.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-sort.d.ts +31 -4
- package/lib/cjs/components/datatable/datatable-sort.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-sort.js +86 -58
- package/lib/cjs/components/datatable/datatable-sort.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-spinner.d.ts +30 -0
- package/lib/cjs/components/datatable/datatable-spinner.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-spinner.js +54 -0
- package/lib/cjs/components/datatable/datatable-spinner.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-state-persistence.d.ts +19 -0
- package/lib/cjs/components/datatable/datatable-state-persistence.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-state-persistence.js +59 -0
- package/lib/cjs/components/datatable/datatable-state-persistence.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +2 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-table-renderer.js +75 -16
- package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-utils.d.ts +10 -0
- package/lib/cjs/components/datatable/datatable-utils.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-utils.js +15 -0
- package/lib/cjs/components/datatable/datatable-utils.js.map +1 -0
- package/lib/cjs/components/datatable/datatable.d.ts +35 -34
- package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +233 -497
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/datatable/index.d.ts +1 -1
- package/lib/cjs/components/datatable/index.d.ts.map +1 -1
- package/lib/cjs/components/datatable/types.d.ts +127 -11
- package/lib/cjs/components/datatable/types.d.ts.map +1 -1
- package/lib/cjs/index.d.ts +1 -1
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +6 -0
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.d.ts +37 -1
- package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.js +142 -155
- package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
- package/lib/esm/components/datatable/datatable-column-utils.d.ts +30 -0
- package/lib/esm/components/datatable/datatable-column-utils.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-column-utils.js +38 -0
- package/lib/esm/components/datatable/datatable-column-utils.js.map +1 -0
- package/lib/esm/components/datatable/datatable-contracts.d.ts +2 -4
- package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-defaults.d.ts +20 -0
- package/lib/esm/components/datatable/datatable-defaults.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-defaults.js +190 -0
- package/lib/esm/components/datatable/datatable-defaults.js.map +1 -0
- package/lib/esm/components/datatable/datatable-layout-plugin.d.ts +7 -0
- package/lib/esm/components/datatable/datatable-layout-plugin.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-layout-plugin.js +334 -0
- package/lib/esm/components/datatable/datatable-layout-plugin.js.map +1 -0
- package/lib/esm/components/datatable/datatable-local-provider.d.ts +2 -2
- package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-local-provider.js +85 -27
- package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -1
- package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-pagination-renderer.js +13 -13
- package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
- package/lib/esm/components/datatable/datatable-registry.d.ts +18 -0
- package/lib/esm/components/datatable/datatable-registry.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-registry.js +63 -0
- package/lib/esm/components/datatable/datatable-registry.js.map +1 -0
- package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-remote-provider.js +1 -2
- package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
- package/lib/esm/components/datatable/datatable-search-handler.d.ts +10 -0
- package/lib/esm/components/datatable/datatable-search-handler.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-search-handler.js +62 -0
- package/lib/esm/components/datatable/datatable-search-handler.js.map +1 -0
- package/lib/esm/components/datatable/datatable-sort.d.ts +31 -4
- package/lib/esm/components/datatable/datatable-sort.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-sort.js +85 -57
- package/lib/esm/components/datatable/datatable-sort.js.map +1 -1
- package/lib/esm/components/datatable/datatable-spinner.d.ts +30 -0
- package/lib/esm/components/datatable/datatable-spinner.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-spinner.js +51 -0
- package/lib/esm/components/datatable/datatable-spinner.js.map +1 -0
- package/lib/esm/components/datatable/datatable-state-persistence.d.ts +19 -0
- package/lib/esm/components/datatable/datatable-state-persistence.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-state-persistence.js +55 -0
- package/lib/esm/components/datatable/datatable-state-persistence.js.map +1 -0
- package/lib/esm/components/datatable/datatable-table-renderer.d.ts +2 -0
- package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-table-renderer.js +75 -16
- package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -1
- package/lib/esm/components/datatable/datatable-utils.d.ts +10 -0
- package/lib/esm/components/datatable/datatable-utils.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-utils.js +12 -0
- package/lib/esm/components/datatable/datatable-utils.js.map +1 -0
- package/lib/esm/components/datatable/datatable.d.ts +35 -34
- package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable.js +235 -499
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/datatable/index.d.ts +1 -1
- package/lib/esm/components/datatable/index.d.ts.map +1 -1
- package/lib/esm/components/datatable/types.d.ts +127 -11
- package/lib/esm/components/datatable/types.d.ts.map +1 -1
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/index.d.ts.map +1 -1
- package/lib/esm/index.js +6 -0
- package/lib/esm/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/ktui/SKILL.md +711 -0
- package/skills/ktui-datatable/SKILL.md +302 -0
- package/skills/ktui-install/SKILL.md +150 -0
- package/skills/ktui-select/SKILL.md +271 -0
- package/src/components/__tests__/component.test.ts +347 -0
- package/src/components/collapse/collapse.css +2 -2
- package/src/components/datatable/__tests__/architecture-boundaries.test.ts +56 -8
- package/src/components/datatable/__tests__/currency-sort.test.ts +25 -28
- package/src/components/datatable/__tests__/datatable-checkbox.test.ts +527 -0
- package/src/components/datatable/__tests__/datatable-column-utils.test.ts +117 -0
- package/src/components/datatable/__tests__/datatable-defaults.test.ts +57 -0
- package/src/components/datatable/__tests__/datatable-finalize-extended.test.ts +361 -0
- package/src/components/datatable/__tests__/datatable-fixed-layout.test.ts +427 -0
- package/src/components/datatable/__tests__/datatable-improvements.test.ts +484 -0
- package/src/components/datatable/__tests__/datatable-pagination-extended.test.ts +508 -0
- package/src/components/datatable/__tests__/datatable-public-api.test.ts +269 -0
- package/src/components/datatable/__tests__/datatable-registry.test.ts +172 -0
- package/src/components/datatable/__tests__/datatable-remote-provider.test.ts +468 -0
- package/src/components/datatable/__tests__/datatable-search-handler.test.ts +124 -0
- package/src/components/datatable/__tests__/datatable-sort-extended.test.ts +417 -0
- package/src/components/datatable/__tests__/datatable-spinner.test.ts +95 -0
- package/src/components/datatable/__tests__/datatable-table-renderer-extended.test.ts +425 -0
- package/src/components/datatable/__tests__/datatable-types.test.ts +117 -0
- package/src/components/datatable/__tests__/datatable-utils.test.ts +52 -0
- package/src/components/datatable/__tests__/locked-layout.test.ts +257 -0
- package/src/components/datatable/__tests__/multi-row-headers.test.ts +7 -7
- package/src/components/datatable/__tests__/pagination-reset.test.ts +147 -6
- package/src/components/datatable/__tests__/race-conditions.test.ts +11 -11
- package/src/components/datatable/__tests__/setup.ts +12 -4
- package/src/components/datatable/datatable-checkbox.ts +139 -143
- package/src/components/datatable/datatable-column-utils.ts +63 -0
- package/src/components/datatable/datatable-contracts.ts +2 -3
- package/src/components/datatable/datatable-defaults.ts +204 -0
- package/src/components/datatable/datatable-layout-plugin.ts +459 -0
- package/src/components/datatable/datatable-local-provider.ts +106 -35
- package/src/components/datatable/datatable-pagination-renderer.ts +13 -15
- package/src/components/datatable/datatable-registry.ts +89 -0
- package/src/components/datatable/datatable-remote-provider.ts +1 -3
- package/src/components/datatable/datatable-search-handler.ts +97 -0
- package/src/components/datatable/datatable-sort.ts +111 -66
- package/src/components/datatable/datatable-spinner.ts +103 -0
- package/src/components/datatable/datatable-state-persistence.ts +67 -0
- package/src/components/datatable/datatable-table-renderer.ts +81 -18
- package/src/components/datatable/datatable-utils.ts +12 -0
- package/src/components/datatable/datatable.css +98 -0
- package/src/components/datatable/datatable.ts +288 -583
- package/src/components/datatable/index.ts +8 -0
- package/src/components/datatable/types.ts +157 -23
- package/src/helpers/__tests__/dom.test.ts +776 -0
- package/src/helpers/__tests__/utils.test.ts +332 -0
- package/src/index.ts +15 -0
- package/skills/ktui-components/SKILL.md +0 -41
- package/skills/ktui-theming/SKILL.md +0 -50
- 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
|
}
|