@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,257 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { KTDataTable } from '../datatable';
|
|
3
|
+
|
|
4
|
+
type DataRow = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
status: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const rows: DataRow[] = [
|
|
11
|
+
{ id: '1', name: 'Alpha', status: 'Active' },
|
|
12
|
+
{ id: '2', name: 'Beta', status: 'Pending' },
|
|
13
|
+
{ id: '3', name: 'Gamma', status: 'Disabled' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const createDatatableFixture = () => {
|
|
17
|
+
const wrapper = document.createElement('div');
|
|
18
|
+
wrapper.className = 'kt-table-wrapper';
|
|
19
|
+
|
|
20
|
+
const container = document.createElement('div');
|
|
21
|
+
container.id = 'kt_datatable_locked_layout';
|
|
22
|
+
container.setAttribute('data-kt-datatable', 'true');
|
|
23
|
+
|
|
24
|
+
const table = document.createElement('table');
|
|
25
|
+
table.setAttribute('data-kt-datatable-table', 'true');
|
|
26
|
+
|
|
27
|
+
const thead = document.createElement('thead');
|
|
28
|
+
thead.innerHTML = `
|
|
29
|
+
<tr>
|
|
30
|
+
<th data-kt-datatable-column="id">ID</th>
|
|
31
|
+
<th data-kt-datatable-column="name">Name</th>
|
|
32
|
+
<th data-kt-datatable-column="status">Status</th>
|
|
33
|
+
</tr>
|
|
34
|
+
`;
|
|
35
|
+
table.appendChild(thead);
|
|
36
|
+
|
|
37
|
+
const tbody = document.createElement('tbody');
|
|
38
|
+
rows.forEach((row) => {
|
|
39
|
+
const tr = document.createElement('tr');
|
|
40
|
+
tr.innerHTML = `<td>${row.id}</td><td>${row.name}</td><td>${row.status}</td>`;
|
|
41
|
+
tbody.appendChild(tr);
|
|
42
|
+
});
|
|
43
|
+
table.appendChild(tbody);
|
|
44
|
+
|
|
45
|
+
wrapper.appendChild(table);
|
|
46
|
+
container.appendChild(wrapper);
|
|
47
|
+
|
|
48
|
+
const info = document.createElement('span');
|
|
49
|
+
info.setAttribute('data-kt-datatable-info', 'true');
|
|
50
|
+
const size = document.createElement('select');
|
|
51
|
+
size.setAttribute('data-kt-datatable-size', 'true');
|
|
52
|
+
const pagination = document.createElement('div');
|
|
53
|
+
pagination.setAttribute('data-kt-datatable-pagination', 'true');
|
|
54
|
+
|
|
55
|
+
container.appendChild(info);
|
|
56
|
+
container.appendChild(size);
|
|
57
|
+
container.appendChild(pagination);
|
|
58
|
+
document.body.appendChild(container);
|
|
59
|
+
|
|
60
|
+
const sizedCells = table.querySelectorAll<HTMLTableCellElement>('th, td');
|
|
61
|
+
sizedCells.forEach((cell) => {
|
|
62
|
+
Object.defineProperty(cell, 'getBoundingClientRect', {
|
|
63
|
+
value: () =>
|
|
64
|
+
({
|
|
65
|
+
width: 120,
|
|
66
|
+
height: 40,
|
|
67
|
+
top: 0,
|
|
68
|
+
left: 0,
|
|
69
|
+
right: 0,
|
|
70
|
+
bottom: 0,
|
|
71
|
+
x: 0,
|
|
72
|
+
y: 0,
|
|
73
|
+
toJSON: () => ({}),
|
|
74
|
+
}) as DOMRect,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return { container, table };
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
describe('KTDataTable locked layout plugin', () => {
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
vi.useFakeTimers();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('applies locked header, rows and columns in local mode and keeps them after sorting', async () => {
|
|
87
|
+
const { container, table } = createDatatableFixture();
|
|
88
|
+
|
|
89
|
+
const datatable = new KTDataTable<DataRow>(container, {
|
|
90
|
+
stateSave: false,
|
|
91
|
+
columns: {
|
|
92
|
+
id: { title: 'ID' },
|
|
93
|
+
name: { title: 'Name' },
|
|
94
|
+
status: { title: 'Status' },
|
|
95
|
+
},
|
|
96
|
+
lockedLayout: {
|
|
97
|
+
stickyHeader: true,
|
|
98
|
+
stickyRows: { top: 1, bottom: 1 },
|
|
99
|
+
stickyColumns: { left: ['id'], right: ['status'] },
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await vi.runAllTimersAsync();
|
|
104
|
+
|
|
105
|
+
expect(
|
|
106
|
+
table.querySelectorAll('.kt-datatable-locked-header').length,
|
|
107
|
+
).toBeGreaterThan(0);
|
|
108
|
+
expect(
|
|
109
|
+
table.querySelectorAll('.kt-datatable-locked-top-row').length,
|
|
110
|
+
).toBeGreaterThan(0);
|
|
111
|
+
expect(
|
|
112
|
+
table.querySelectorAll('.kt-datatable-locked-bottom-row').length,
|
|
113
|
+
).toBeGreaterThan(0);
|
|
114
|
+
expect(
|
|
115
|
+
table.querySelectorAll('.kt-datatable-locked-left').length,
|
|
116
|
+
).toBeGreaterThan(0);
|
|
117
|
+
expect(
|
|
118
|
+
table.querySelectorAll('.kt-datatable-locked-right').length,
|
|
119
|
+
).toBeGreaterThan(0);
|
|
120
|
+
|
|
121
|
+
datatable.sort('name');
|
|
122
|
+
await vi.runAllTimersAsync();
|
|
123
|
+
|
|
124
|
+
expect(
|
|
125
|
+
table.querySelectorAll('.kt-datatable-locked-cell').length,
|
|
126
|
+
).toBeGreaterThan(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('reapplies locked layout after remote fetch redraw', async () => {
|
|
130
|
+
const { container, table } = createDatatableFixture();
|
|
131
|
+
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
|
|
132
|
+
new Response(JSON.stringify({ data: rows, totalCount: rows.length }), {
|
|
133
|
+
status: 200,
|
|
134
|
+
headers: { 'Content-Type': 'application/json' },
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
new KTDataTable<DataRow>(container, {
|
|
139
|
+
stateSave: false,
|
|
140
|
+
apiEndpoint: 'https://example.test/datatable',
|
|
141
|
+
requestMethod: 'GET',
|
|
142
|
+
mapResponse: (data) => data,
|
|
143
|
+
lockedLayout: {
|
|
144
|
+
stickyHeader: true,
|
|
145
|
+
stickyColumns: { left: ['id'] },
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await vi.runAllTimersAsync();
|
|
150
|
+
|
|
151
|
+
expect(fetchMock).toHaveBeenCalled();
|
|
152
|
+
expect(
|
|
153
|
+
table.querySelectorAll('.kt-datatable-locked-header').length,
|
|
154
|
+
).toBeGreaterThan(0);
|
|
155
|
+
expect(
|
|
156
|
+
table.querySelectorAll('.kt-datatable-locked-left').length,
|
|
157
|
+
).toBeGreaterThan(0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('keeps pagination state when using locked columns in local mode', async () => {
|
|
161
|
+
const { container } = createDatatableFixture();
|
|
162
|
+
|
|
163
|
+
const datatable = new KTDataTable<DataRow>(container, {
|
|
164
|
+
stateSave: false,
|
|
165
|
+
pageSize: 2,
|
|
166
|
+
columns: {
|
|
167
|
+
id: { title: 'ID' },
|
|
168
|
+
name: { title: 'Name' },
|
|
169
|
+
status: { title: 'Status' },
|
|
170
|
+
},
|
|
171
|
+
lockedLayout: {
|
|
172
|
+
stickyHeader: true,
|
|
173
|
+
stickyColumns: { left: ['id'] },
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await vi.runAllTimersAsync();
|
|
178
|
+
expect(datatable.getState().totalPages).toBe(2);
|
|
179
|
+
|
|
180
|
+
datatable.goPage(2);
|
|
181
|
+
await vi.runAllTimersAsync();
|
|
182
|
+
expect(datatable.getState().page).toBe(2);
|
|
183
|
+
expect(datatable.getState().totalPages).toBe(2);
|
|
184
|
+
expect(datatable.getState().totalItems).toBe(rows.length);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('uses collapsed table borders for row-only locked layout', async () => {
|
|
188
|
+
const { container, table } = createDatatableFixture();
|
|
189
|
+
|
|
190
|
+
new KTDataTable<DataRow>(container, {
|
|
191
|
+
stateSave: false,
|
|
192
|
+
lockedLayout: {
|
|
193
|
+
stickyHeader: true,
|
|
194
|
+
stickyRows: { top: 1, bottom: 1 },
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await vi.runAllTimersAsync();
|
|
199
|
+
|
|
200
|
+
expect(table.classList.contains('kt-datatable-locked-layout')).toBe(true);
|
|
201
|
+
expect(
|
|
202
|
+
table.classList.contains('kt-datatable-locked-layout-separate'),
|
|
203
|
+
).toBe(false);
|
|
204
|
+
expect(table.style.borderCollapse).not.toBe('separate');
|
|
205
|
+
expect(
|
|
206
|
+
table.tHead?.classList.contains('kt-datatable-locked-header-section'),
|
|
207
|
+
).toBe(true);
|
|
208
|
+
const stickyHeaderCell = table.querySelector(
|
|
209
|
+
'th.kt-datatable-locked-header',
|
|
210
|
+
) as HTMLTableCellElement | null;
|
|
211
|
+
expect(stickyHeaderCell?.style.position).not.toBe('sticky');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('uses separate table borders when sticky columns are enabled', async () => {
|
|
215
|
+
const { container, table } = createDatatableFixture();
|
|
216
|
+
|
|
217
|
+
new KTDataTable<DataRow>(container, {
|
|
218
|
+
stateSave: false,
|
|
219
|
+
lockedLayout: {
|
|
220
|
+
stickyHeader: true,
|
|
221
|
+
stickyColumns: { left: ['id'] },
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await vi.runAllTimersAsync();
|
|
226
|
+
|
|
227
|
+
expect(
|
|
228
|
+
table.classList.contains('kt-datatable-locked-layout-separate'),
|
|
229
|
+
).toBe(true);
|
|
230
|
+
expect(table.style.borderCollapse).toBe('separate');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('does not set inline background color for locked header columns', async () => {
|
|
234
|
+
const { container, table } = createDatatableFixture();
|
|
235
|
+
|
|
236
|
+
new KTDataTable<DataRow>(container, {
|
|
237
|
+
stateSave: false,
|
|
238
|
+
columns: {
|
|
239
|
+
id: { title: 'ID' },
|
|
240
|
+
name: { title: 'Name' },
|
|
241
|
+
status: { title: 'Status' },
|
|
242
|
+
},
|
|
243
|
+
lockedLayout: {
|
|
244
|
+
stickyHeader: true,
|
|
245
|
+
stickyColumns: { left: ['id'] },
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
await vi.runAllTimersAsync();
|
|
250
|
+
|
|
251
|
+
const lockedHeaderCell = table.querySelector(
|
|
252
|
+
'th.kt-datatable-locked-left',
|
|
253
|
+
) as HTMLTableCellElement | null;
|
|
254
|
+
expect(lockedHeaderCell).not.toBeNull();
|
|
255
|
+
expect(lockedHeaderCell?.style.backgroundColor).toBe('');
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -30,23 +30,23 @@ describe('KTDataTable - Multi-row header column count', () => {
|
|
|
30
30
|
const thead = document.createElement('thead');
|
|
31
31
|
const row1 = document.createElement('tr');
|
|
32
32
|
row1.innerHTML = `
|
|
33
|
-
<th rowspan="2">Person</th>
|
|
33
|
+
<th rowspan="2" data-kt-datatable-column="person">Person</th>
|
|
34
34
|
<th colspan="3">Backlog</th>
|
|
35
35
|
<th colspan="3">Floater (2) 2025</th>
|
|
36
36
|
<th colspan="3">Floater (1) 2025</th>
|
|
37
37
|
<th colspan="3">CL2025</th>
|
|
38
38
|
<th colspan="3">LWP</th>
|
|
39
|
-
<th rowspan="2">Action</th>
|
|
39
|
+
<th rowspan="2" data-kt-datatable-column="action">Action</th>
|
|
40
40
|
`;
|
|
41
41
|
thead.appendChild(row1);
|
|
42
42
|
|
|
43
43
|
const row2 = document.createElement('tr');
|
|
44
44
|
row2.innerHTML = `
|
|
45
|
-
<th>Assigned</th><th>Used</th><th>Balance</th>
|
|
46
|
-
<th>Assigned</th><th>Used</th><th>Balance</th>
|
|
47
|
-
<th>Assigned</th><th>Used</th><th>Balance</th>
|
|
48
|
-
<th>Assigned</th><th>Used</th><th>Balance</th>
|
|
49
|
-
<th>Assigned</th><th>Used</th><th>Balance</th>
|
|
45
|
+
<th data-kt-datatable-column="assigned1">Assigned</th><th data-kt-datatable-column="used1">Used</th><th data-kt-datatable-column="balance1">Balance</th>
|
|
46
|
+
<th data-kt-datatable-column="assigned2">Assigned</th><th data-kt-datatable-column="used2">Used</th><th data-kt-datatable-column="balance2">Balance</th>
|
|
47
|
+
<th data-kt-datatable-column="assigned3">Assigned</th><th data-kt-datatable-column="used3">Used</th><th data-kt-datatable-column="balance3">Balance</th>
|
|
48
|
+
<th data-kt-datatable-column="assigned4">Assigned</th><th data-kt-datatable-column="used4">Used</th><th data-kt-datatable-column="balance4">Balance</th>
|
|
49
|
+
<th data-kt-datatable-column="assigned5">Assigned</th><th data-kt-datatable-column="used5">Used</th><th data-kt-datatable-column="balance5">Balance</th>
|
|
50
50
|
`;
|
|
51
51
|
thead.appendChild(row2);
|
|
52
52
|
tableElement.appendChild(thead);
|
|
@@ -98,6 +98,10 @@ describe('KTDataTable - Pagination Reset', () => {
|
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
beforeEach(() => {
|
|
101
|
+
// Dispose previous test's datatable to cancel in-flight async operations
|
|
102
|
+
if (datatable) {
|
|
103
|
+
try { datatable.dispose(); } catch { /* already disposed */ }
|
|
104
|
+
}
|
|
101
105
|
// Clear any existing elements
|
|
102
106
|
document.body.innerHTML = '';
|
|
103
107
|
vi.clearAllMocks();
|
|
@@ -313,7 +317,7 @@ describe('KTDataTable - Pagination Reset', () => {
|
|
|
313
317
|
expect(datatable.getState().page).toBe(1);
|
|
314
318
|
|
|
315
319
|
// Apply third filter (page should stay at 1)
|
|
316
|
-
datatable.setFilter({ column: 'id', type: 'numeric', value:
|
|
320
|
+
datatable.setFilter({ column: 'id', type: 'numeric', value: 10 });
|
|
317
321
|
expect(datatable.getState().page).toBe(1);
|
|
318
322
|
});
|
|
319
323
|
|
|
@@ -438,7 +442,17 @@ describe('KTDataTable - Pagination Reset', () => {
|
|
|
438
442
|
});
|
|
439
443
|
|
|
440
444
|
describe('Scenario: State persistence respects pagination reset', () => {
|
|
445
|
+
const isLocalStorageAvailable = (): boolean => {
|
|
446
|
+
try {
|
|
447
|
+
localStorage.getItem('test');
|
|
448
|
+
return true;
|
|
449
|
+
} catch {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
441
454
|
it('should save page 1 to state when search resets pagination', async () => {
|
|
455
|
+
if (!isLocalStorageAvailable()) return;
|
|
442
456
|
const { container } = createMockDataTable(25);
|
|
443
457
|
|
|
444
458
|
// Enable state saving with unique namespace
|
|
@@ -476,6 +490,7 @@ describe('KTDataTable - Pagination Reset', () => {
|
|
|
476
490
|
});
|
|
477
491
|
|
|
478
492
|
it('should save page 1 to state when filter resets pagination', () => {
|
|
493
|
+
if (!isLocalStorageAvailable()) return;
|
|
479
494
|
const { container } = createMockDataTable(25);
|
|
480
495
|
|
|
481
496
|
datatable = new KTDataTable(container, {
|
|
@@ -503,6 +518,7 @@ describe('KTDataTable - Pagination Reset', () => {
|
|
|
503
518
|
});
|
|
504
519
|
|
|
505
520
|
it('should restore to page 1 with active search on reload', async () => {
|
|
521
|
+
if (!isLocalStorageAvailable()) return;
|
|
506
522
|
const { container } = createMockDataTable(25);
|
|
507
523
|
const namespace = 'test-datatable-restore';
|
|
508
524
|
|
|
@@ -552,6 +568,131 @@ describe('KTDataTable - Pagination Reset', () => {
|
|
|
552
568
|
});
|
|
553
569
|
|
|
554
570
|
describe('Edge Cases', () => {
|
|
571
|
+
it('keeps total pages stable when navigating to page 2 in local mode', () => {
|
|
572
|
+
const { container } = createMockDataTable(6);
|
|
573
|
+
datatable = new KTDataTable(container, {
|
|
574
|
+
pageSize: 5,
|
|
575
|
+
stateSave: false,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
expect(datatable.getState().totalPages).toBe(2);
|
|
579
|
+
datatable.goPage(2);
|
|
580
|
+
expect(datatable.getState().page).toBe(2);
|
|
581
|
+
expect(datatable.getState().totalPages).toBe(2);
|
|
582
|
+
|
|
583
|
+
// Trigger another redraw cycle to ensure local mode does not shrink original data.
|
|
584
|
+
datatable.reload();
|
|
585
|
+
expect(datatable.getState().page).toBe(2);
|
|
586
|
+
expect(datatable.getState().totalPages).toBe(2);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('does not shrink originalData when tbody checksum mismatches after pagination', async () => {
|
|
590
|
+
const { container } = createMockDataTable(18);
|
|
591
|
+
datatable = new KTDataTable(container, {
|
|
592
|
+
pageSize: 5,
|
|
593
|
+
stateSave: false,
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
597
|
+
expect(datatable.getState().totalPages).toBe(4);
|
|
598
|
+
|
|
599
|
+
// Simulate a fetch before _contentChecksum was aligned with the paginated tbody.
|
|
600
|
+
datatable.getState()._contentChecksum = 'stale-checksum';
|
|
601
|
+
datatable.reload();
|
|
602
|
+
|
|
603
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
604
|
+
expect(datatable.getState().totalPages).toBe(4);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it('keeps 4 pages when thead has checkbox and actions columns (bulk-actions demo)', async () => {
|
|
608
|
+
const container = document.createElement('div');
|
|
609
|
+
container.id = 'test-bulk-actions-datatable';
|
|
610
|
+
|
|
611
|
+
const table = document.createElement('table');
|
|
612
|
+
table.setAttribute('data-kt-datatable-table', 'true');
|
|
613
|
+
|
|
614
|
+
const thead = document.createElement('thead');
|
|
615
|
+
thead.innerHTML = `
|
|
616
|
+
<tr>
|
|
617
|
+
<th><input type="checkbox" data-kt-datatable-check="true" /></th>
|
|
618
|
+
<th data-kt-datatable-column="label">Label</th>
|
|
619
|
+
<th data-kt-datatable-column="method">Method</th>
|
|
620
|
+
<th data-kt-datatable-column="status">Status</th>
|
|
621
|
+
<th data-kt-datatable-column="lastSession">Last Session</th>
|
|
622
|
+
<th data-kt-datatable-column="actions"></th>
|
|
623
|
+
</tr>
|
|
624
|
+
`;
|
|
625
|
+
|
|
626
|
+
const tbody = document.createElement('tbody');
|
|
627
|
+
for (let i = 1; i <= 18; i++) {
|
|
628
|
+
const row = document.createElement('tr');
|
|
629
|
+
row.innerHTML = `
|
|
630
|
+
<td><input type="checkbox" data-kt-datatable-row-check="true" value="${i - 1}" /></td>
|
|
631
|
+
<td>User ${i}</td>
|
|
632
|
+
<td>Web</td>
|
|
633
|
+
<td>active</td>
|
|
634
|
+
<td>22 Jul 2024</td>
|
|
635
|
+
<td><button type="button">Edit</button></td>
|
|
636
|
+
`;
|
|
637
|
+
tbody.appendChild(row);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
table.appendChild(thead);
|
|
641
|
+
table.appendChild(tbody);
|
|
642
|
+
|
|
643
|
+
const infoElement = document.createElement('div');
|
|
644
|
+
infoElement.setAttribute('data-kt-datatable-info', 'true');
|
|
645
|
+
const sizeElement = document.createElement('select');
|
|
646
|
+
sizeElement.setAttribute('data-kt-datatable-size', 'true');
|
|
647
|
+
const paginationElement = document.createElement('div');
|
|
648
|
+
paginationElement.setAttribute('data-kt-datatable-pagination', 'true');
|
|
649
|
+
|
|
650
|
+
container.appendChild(table);
|
|
651
|
+
container.appendChild(infoElement);
|
|
652
|
+
container.appendChild(sizeElement);
|
|
653
|
+
container.appendChild(paginationElement);
|
|
654
|
+
document.body.appendChild(container);
|
|
655
|
+
|
|
656
|
+
datatable = new KTDataTable(container, {
|
|
657
|
+
pageSize: 5,
|
|
658
|
+
stateSave: false,
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
662
|
+
|
|
663
|
+
expect(datatable.getState().totalPages).toBe(4);
|
|
664
|
+
datatable.goPage(2);
|
|
665
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
666
|
+
expect(datatable.getState().page).toBe(2);
|
|
667
|
+
expect(datatable.getState().totalPages).toBe(4);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('shows page 2 rows with tableLayout fixed and columns config (docs column-widths demo)', async () => {
|
|
671
|
+
const { container } = createMockDataTable(18);
|
|
672
|
+
datatable = new KTDataTable(container, {
|
|
673
|
+
pageSize: 5,
|
|
674
|
+
stateSave: false,
|
|
675
|
+
tableLayout: 'fixed',
|
|
676
|
+
columns: {
|
|
677
|
+
id: { width: '60px' },
|
|
678
|
+
name: { width: '140px' },
|
|
679
|
+
status: { width: '100px' },
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
684
|
+
|
|
685
|
+
datatable.goPage(2);
|
|
686
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
687
|
+
|
|
688
|
+
expect(datatable.getState().page).toBe(2);
|
|
689
|
+
|
|
690
|
+
const rows = container.querySelectorAll('tbody tr');
|
|
691
|
+
expect(rows.length).toBe(5);
|
|
692
|
+
expect(rows[0].cells[0].textContent).toBe('6');
|
|
693
|
+
expect(datatable.getState().totalPages).toBe(4);
|
|
694
|
+
});
|
|
695
|
+
|
|
555
696
|
it('should handle search reset on page 1 (no-op)', () => {
|
|
556
697
|
const { container } = createMockDataTable(25);
|
|
557
698
|
datatable = new KTDataTable(container, {
|
|
@@ -651,9 +792,9 @@ describe('KTDataTable - Pagination Reset', () => {
|
|
|
651
792
|
stateSave: false,
|
|
652
793
|
});
|
|
653
794
|
|
|
654
|
-
const
|
|
655
|
-
// Listen for '
|
|
656
|
-
container.addEventListener('
|
|
795
|
+
const updateSpy = vi.fn();
|
|
796
|
+
// Listen for 'kt.datatable.update' event directly (CustomEvent)
|
|
797
|
+
container.addEventListener('kt.datatable.update', updateSpy);
|
|
657
798
|
|
|
658
799
|
datatable.goPage(2);
|
|
659
800
|
datatable.search('test');
|
|
@@ -661,8 +802,8 @@ describe('KTDataTable - Pagination Reset', () => {
|
|
|
661
802
|
// Wait for async reload to complete
|
|
662
803
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
663
804
|
|
|
664
|
-
//
|
|
665
|
-
expect(
|
|
805
|
+
// update event should still fire
|
|
806
|
+
expect(updateSpy).toHaveBeenCalled();
|
|
666
807
|
});
|
|
667
808
|
});
|
|
668
809
|
});
|
|
@@ -407,13 +407,13 @@ describe('KTDataTable Race Condition Fixes', () => {
|
|
|
407
407
|
|
|
408
408
|
describe('Event Handling During Race Conditions', () => {
|
|
409
409
|
it('should fire fetch event for successful requests', async () => {
|
|
410
|
-
const
|
|
410
|
+
const updateEvents: Event[] = [];
|
|
411
411
|
|
|
412
412
|
const element = container.querySelector(
|
|
413
413
|
'[data-kt-datatable="true"]',
|
|
414
414
|
) as HTMLElement;
|
|
415
|
-
element.addEventListener('
|
|
416
|
-
|
|
415
|
+
element.addEventListener('kt.datatable.update', (e) => {
|
|
416
|
+
updateEvents.push(e);
|
|
417
417
|
});
|
|
418
418
|
|
|
419
419
|
const datatable = new KTDataTable(element, {
|
|
@@ -425,18 +425,18 @@ describe('KTDataTable Race Condition Fixes', () => {
|
|
|
425
425
|
datatable.search('test');
|
|
426
426
|
await waitFor(150); // Complete search
|
|
427
427
|
|
|
428
|
-
// Should fire
|
|
429
|
-
expect(
|
|
428
|
+
// Should fire update for initial and search
|
|
429
|
+
expect(updateEvents.length).toBeGreaterThanOrEqual(2);
|
|
430
430
|
});
|
|
431
431
|
|
|
432
432
|
it('should fire fetched event after successful data load', async () => {
|
|
433
|
-
const
|
|
433
|
+
const updateEvents: Event[] = [];
|
|
434
434
|
|
|
435
435
|
const element = container.querySelector(
|
|
436
436
|
'[data-kt-datatable="true"]',
|
|
437
437
|
) as HTMLElement;
|
|
438
|
-
element.addEventListener('
|
|
439
|
-
|
|
438
|
+
element.addEventListener('kt.datatable.update', (e) => {
|
|
439
|
+
updateEvents.push(e);
|
|
440
440
|
});
|
|
441
441
|
|
|
442
442
|
new KTDataTable(element, {
|
|
@@ -445,8 +445,8 @@ describe('KTDataTable Race Condition Fixes', () => {
|
|
|
445
445
|
|
|
446
446
|
await waitFor(150);
|
|
447
447
|
|
|
448
|
-
// Should fire
|
|
449
|
-
expect(
|
|
448
|
+
// Should fire update for initial request
|
|
449
|
+
expect(updateEvents.length).toBeGreaterThanOrEqual(1);
|
|
450
450
|
});
|
|
451
451
|
|
|
452
452
|
it('should not fire error events for AbortError', async () => {
|
|
@@ -455,7 +455,7 @@ describe('KTDataTable Race Condition Fixes', () => {
|
|
|
455
455
|
const element = container.querySelector(
|
|
456
456
|
'[data-kt-datatable="true"]',
|
|
457
457
|
) as HTMLElement;
|
|
458
|
-
element.addEventListener('
|
|
458
|
+
element.addEventListener('kt.datatable.error', (e) => {
|
|
459
459
|
errorEvents.push(e);
|
|
460
460
|
});
|
|
461
461
|
|
|
@@ -17,8 +17,12 @@ vi.mock('../../../index', () => ({
|
|
|
17
17
|
|
|
18
18
|
// Setup DOM environment before each test
|
|
19
19
|
beforeEach(() => {
|
|
20
|
-
// Clear localStorage
|
|
21
|
-
|
|
20
|
+
// Clear localStorage (may be unavailable in Node.js without --localstorage-file)
|
|
21
|
+
try {
|
|
22
|
+
localStorage.clear();
|
|
23
|
+
} catch {
|
|
24
|
+
// localStorage not available
|
|
25
|
+
}
|
|
22
26
|
|
|
23
27
|
// Reset document body
|
|
24
28
|
document.body.innerHTML = '';
|
|
@@ -29,8 +33,12 @@ beforeEach(() => {
|
|
|
29
33
|
|
|
30
34
|
// Cleanup after each test
|
|
31
35
|
afterEach(() => {
|
|
32
|
-
// Clear localStorage
|
|
33
|
-
|
|
36
|
+
// Clear localStorage (may be unavailable in Node.js without --localstorage-file)
|
|
37
|
+
try {
|
|
38
|
+
localStorage.clear();
|
|
39
|
+
} catch {
|
|
40
|
+
// localStorage not available
|
|
41
|
+
}
|
|
34
42
|
|
|
35
43
|
// Reset document body
|
|
36
44
|
document.body.innerHTML = '';
|