@keenthemes/ktui 1.2.2 → 1.2.4
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/dist/ktui.js +1738 -986
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +110 -197
- package/lib/cjs/components/context-menu/context-menu.d.ts +66 -0
- package/lib/cjs/components/context-menu/context-menu.d.ts.map +1 -0
- package/lib/cjs/components/context-menu/context-menu.js +423 -0
- package/lib/cjs/components/context-menu/context-menu.js.map +1 -0
- package/lib/cjs/components/context-menu/index.d.ts +7 -0
- package/lib/cjs/components/context-menu/index.d.ts.map +1 -0
- package/lib/cjs/components/context-menu/index.js +10 -0
- package/lib/cjs/components/context-menu/index.js.map +1 -0
- package/lib/cjs/components/context-menu/types.d.ts +30 -0
- package/lib/cjs/components/context-menu/types.d.ts.map +1 -0
- package/lib/cjs/components/context-menu/types.js +7 -0
- package/lib/cjs/components/context-menu/types.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-contracts.d.ts +66 -0
- package/lib/cjs/components/datatable/datatable-contracts.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-contracts.js +7 -0
- package/lib/cjs/components/datatable/datatable-contracts.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-event-adapter.d.ts +7 -0
- package/lib/cjs/components/datatable/datatable-event-adapter.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-event-adapter.js +16 -0
- package/lib/cjs/components/datatable/datatable-event-adapter.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-local-provider.d.ts +25 -0
- package/lib/cjs/components/datatable/datatable-local-provider.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-local-provider.js +184 -0
- package/lib/cjs/components/datatable/datatable-local-provider.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts +15 -0
- package/lib/cjs/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js +128 -0
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.d.ts +25 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.js +188 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-state-store.d.ts +21 -0
- package/lib/cjs/components/datatable/datatable-state-store.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-state-store.js +81 -0
- package/lib/cjs/components/datatable/datatable-state-store.js.map +1 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.d.ts +16 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.js +133 -0
- package/lib/cjs/components/datatable/datatable-table-renderer.js.map +1 -0
- package/lib/cjs/components/datatable/datatable.d.ts +9 -87
- package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +115 -687
- package/lib/cjs/components/datatable/datatable.js.map +1 -1
- package/lib/cjs/components/modal/modal.d.ts.map +1 -1
- package/lib/cjs/components/modal/modal.js +19 -13
- package/lib/cjs/components/modal/modal.js.map +1 -1
- package/lib/cjs/components/select/index.d.ts +1 -1
- package/lib/cjs/components/select/index.d.ts.map +1 -1
- package/lib/cjs/components/theme-switch/theme-switch.d.ts +3 -0
- package/lib/cjs/components/theme-switch/theme-switch.d.ts.map +1 -1
- package/lib/cjs/components/theme-switch/theme-switch.js +17 -4
- package/lib/cjs/components/theme-switch/theme-switch.js.map +1 -1
- package/lib/cjs/components/toggle/toggle.d.ts +2 -0
- package/lib/cjs/components/toggle/toggle.d.ts.map +1 -1
- package/lib/cjs/components/toggle/toggle.js +11 -2
- package/lib/cjs/components/toggle/toggle.js.map +1 -1
- package/lib/cjs/index.d.ts +5 -1
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +7 -7
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/init-all.d.ts +6 -0
- package/lib/cjs/init-all.d.ts.map +1 -0
- package/lib/cjs/init-all.js +17 -0
- package/lib/cjs/init-all.js.map +1 -0
- package/lib/cjs/legacy.d.ts +8 -0
- package/lib/cjs/legacy.d.ts.map +1 -0
- package/lib/cjs/legacy.js +26 -0
- package/lib/cjs/legacy.js.map +1 -0
- package/lib/esm/components/context-menu/context-menu.d.ts +66 -0
- package/lib/esm/components/context-menu/context-menu.d.ts.map +1 -0
- package/lib/esm/components/context-menu/context-menu.js +420 -0
- package/lib/esm/components/context-menu/context-menu.js.map +1 -0
- package/lib/esm/components/context-menu/index.d.ts +7 -0
- package/lib/esm/components/context-menu/index.d.ts.map +1 -0
- package/lib/esm/components/context-menu/index.js +6 -0
- package/lib/esm/components/context-menu/index.js.map +1 -0
- package/lib/esm/components/context-menu/types.d.ts +30 -0
- package/lib/esm/components/context-menu/types.d.ts.map +1 -0
- package/lib/esm/components/context-menu/types.js +6 -0
- package/lib/esm/components/context-menu/types.js.map +1 -0
- package/lib/esm/components/datatable/datatable-contracts.d.ts +66 -0
- package/lib/esm/components/datatable/datatable-contracts.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-contracts.js +6 -0
- package/lib/esm/components/datatable/datatable-contracts.js.map +1 -0
- package/lib/esm/components/datatable/datatable-event-adapter.d.ts +7 -0
- package/lib/esm/components/datatable/datatable-event-adapter.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-event-adapter.js +13 -0
- package/lib/esm/components/datatable/datatable-event-adapter.js.map +1 -0
- package/lib/esm/components/datatable/datatable-local-provider.d.ts +25 -0
- package/lib/esm/components/datatable/datatable-local-provider.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-local-provider.js +181 -0
- package/lib/esm/components/datatable/datatable-local-provider.js.map +1 -0
- package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts +15 -0
- package/lib/esm/components/datatable/datatable-pagination-renderer.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-pagination-renderer.js +125 -0
- package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -0
- package/lib/esm/components/datatable/datatable-remote-provider.d.ts +25 -0
- package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-remote-provider.js +185 -0
- package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -0
- package/lib/esm/components/datatable/datatable-state-store.d.ts +21 -0
- package/lib/esm/components/datatable/datatable-state-store.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-state-store.js +78 -0
- package/lib/esm/components/datatable/datatable-state-store.js.map +1 -0
- package/lib/esm/components/datatable/datatable-table-renderer.d.ts +16 -0
- package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -0
- package/lib/esm/components/datatable/datatable-table-renderer.js +130 -0
- package/lib/esm/components/datatable/datatable-table-renderer.js.map +1 -0
- package/lib/esm/components/datatable/datatable.d.ts +9 -87
- package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable.js +115 -687
- package/lib/esm/components/datatable/datatable.js.map +1 -1
- package/lib/esm/components/modal/modal.d.ts.map +1 -1
- package/lib/esm/components/modal/modal.js +19 -13
- package/lib/esm/components/modal/modal.js.map +1 -1
- package/lib/esm/components/select/index.d.ts +1 -1
- package/lib/esm/components/select/index.d.ts.map +1 -1
- package/lib/esm/components/theme-switch/theme-switch.d.ts +3 -0
- package/lib/esm/components/theme-switch/theme-switch.d.ts.map +1 -1
- package/lib/esm/components/theme-switch/theme-switch.js +17 -4
- package/lib/esm/components/theme-switch/theme-switch.js.map +1 -1
- package/lib/esm/components/toggle/toggle.d.ts +2 -0
- package/lib/esm/components/toggle/toggle.d.ts.map +1 -1
- package/lib/esm/components/toggle/toggle.js +11 -2
- package/lib/esm/components/toggle/toggle.js.map +1 -1
- package/lib/esm/index.d.ts +5 -1
- package/lib/esm/index.d.ts.map +1 -1
- package/lib/esm/index.js +4 -5
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/init-all.d.ts +6 -0
- package/lib/esm/init-all.d.ts.map +1 -0
- package/lib/esm/init-all.js +13 -0
- package/lib/esm/init-all.js.map +1 -0
- package/lib/esm/legacy.d.ts +8 -0
- package/lib/esm/legacy.d.ts.map +1 -0
- package/lib/esm/legacy.js +8 -0
- package/lib/esm/legacy.js.map +1 -0
- package/package.json +34 -4
- package/src/__tests__/entrypoints.test.ts +71 -0
- package/src/components/context-menu/__tests__/context-menu.test.ts +117 -0
- package/src/components/context-menu/context-menu.css +32 -0
- package/src/components/context-menu/context-menu.ts +529 -0
- package/src/components/context-menu/index.ts +10 -0
- package/src/components/context-menu/types.ts +32 -0
- package/src/components/datatable/__tests__/architecture-boundaries.test.ts +259 -0
- package/src/components/datatable/datatable-contracts.ts +96 -0
- package/src/components/datatable/datatable-event-adapter.ts +21 -0
- package/src/components/datatable/datatable-local-provider.ts +194 -0
- package/src/components/datatable/datatable-pagination-renderer.ts +211 -0
- package/src/components/datatable/datatable-remote-provider.ts +175 -0
- package/src/components/datatable/datatable-state-store.ts +94 -0
- package/src/components/datatable/datatable-table-renderer.ts +206 -0
- package/src/components/datatable/datatable.ts +128 -839
- package/src/components/modal/modal.ts +22 -14
- package/src/components/select/index.ts +1 -1
- package/src/components/theme-switch/theme-switch.ts +22 -4
- package/src/components/toggle/toggle.ts +12 -2
- package/src/index.ts +9 -5
- package/src/init-all.ts +15 -0
- package/src/legacy.ts +9 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for datatable architecture boundaries introduced during internal refactor.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
6
|
+
import { KTDataTable } from '../datatable';
|
|
7
|
+
import { createDataTableEventAdapter } from '../datatable-event-adapter';
|
|
8
|
+
import { KTDataTableLocalDataProvider } from '../datatable-local-provider';
|
|
9
|
+
import { KTDataTableDomPaginationRenderer } from '../datatable-pagination-renderer';
|
|
10
|
+
import { KTDataTableRemoteDataProvider } from '../datatable-remote-provider';
|
|
11
|
+
import { KTDataTableConfigStateStore } from '../datatable-state-store';
|
|
12
|
+
import { KTDataTableDomTableRenderer } from '../datatable-table-renderer';
|
|
13
|
+
import { KTDataTableConfigInterface } from '../types';
|
|
14
|
+
import { waitFor } from './setup';
|
|
15
|
+
|
|
16
|
+
function createConfig(
|
|
17
|
+
overrides: Partial<KTDataTableConfigInterface> = {},
|
|
18
|
+
): KTDataTableConfigInterface {
|
|
19
|
+
return {
|
|
20
|
+
pageSize: 10,
|
|
21
|
+
pageSizes: [10, 20],
|
|
22
|
+
pageMore: true,
|
|
23
|
+
pageMoreLimit: 3,
|
|
24
|
+
info: '{start}-{end} of {total}',
|
|
25
|
+
infoEmpty: 'No records found',
|
|
26
|
+
requestMethod: 'GET',
|
|
27
|
+
requestHeaders: {},
|
|
28
|
+
pagination: {
|
|
29
|
+
number: { class: 'number', text: '{page}' },
|
|
30
|
+
previous: { class: 'previous', text: 'Previous' },
|
|
31
|
+
next: { class: 'next', text: 'Next' },
|
|
32
|
+
more: { class: 'more', text: '...' },
|
|
33
|
+
},
|
|
34
|
+
search: {
|
|
35
|
+
delay: 0,
|
|
36
|
+
callback: (data, search) =>
|
|
37
|
+
data.filter((item) => String(item.name).includes(search)),
|
|
38
|
+
},
|
|
39
|
+
sort: {
|
|
40
|
+
callback: (data) => data,
|
|
41
|
+
},
|
|
42
|
+
attributes: {
|
|
43
|
+
table: '[data-kt-datatable-table="true"]',
|
|
44
|
+
info: '[data-kt-datatable-info="true"]',
|
|
45
|
+
size: '[data-kt-datatable-size="true"]',
|
|
46
|
+
pagination: '[data-kt-datatable-pagination="true"]',
|
|
47
|
+
spinner: '[data-kt-datatable-spinner="true"]',
|
|
48
|
+
check: '[data-kt-datatable-check="true"]',
|
|
49
|
+
checkbox: '[data-kt-datatable-row-check="true"]',
|
|
50
|
+
},
|
|
51
|
+
_state: {},
|
|
52
|
+
...overrides,
|
|
53
|
+
} as KTDataTableConfigInterface;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('KTDataTable architecture boundaries', () => {
|
|
57
|
+
it('routes state transitions through the state store', () => {
|
|
58
|
+
const config = createConfig();
|
|
59
|
+
const store = new KTDataTableConfigStateStore(config);
|
|
60
|
+
|
|
61
|
+
store.setPage(3);
|
|
62
|
+
store.setPageSize(20);
|
|
63
|
+
store.setSort('name', 'asc');
|
|
64
|
+
store.setSearch('Ada');
|
|
65
|
+
store.setFilter({ column: 'status', type: 'text', value: 'active' });
|
|
66
|
+
|
|
67
|
+
expect(store.getState()).toMatchObject({
|
|
68
|
+
page: 1,
|
|
69
|
+
pageSize: 20,
|
|
70
|
+
sortField: 'name',
|
|
71
|
+
sortOrder: 'asc',
|
|
72
|
+
search: 'Ada',
|
|
73
|
+
});
|
|
74
|
+
expect(store.getState().filters).toEqual([
|
|
75
|
+
{ column: 'status', type: 'text', value: 'active' },
|
|
76
|
+
]);
|
|
77
|
+
expect(config._state.page).toBe(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('emits through both legacy event channels from one adapter', () => {
|
|
81
|
+
const fireEvent = vi.fn();
|
|
82
|
+
const dispatchEvent = vi.fn();
|
|
83
|
+
const adapter = createDataTableEventAdapter(fireEvent, dispatchEvent);
|
|
84
|
+
|
|
85
|
+
adapter.emit('reload', { page: 1 });
|
|
86
|
+
|
|
87
|
+
expect(fireEvent).toHaveBeenCalledWith('reload', { page: 1 });
|
|
88
|
+
expect(dispatchEvent).toHaveBeenCalledWith('reload', { page: 1 });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('extracts and pages local table data through the local provider', () => {
|
|
92
|
+
const config = createConfig({ pageSize: 1 });
|
|
93
|
+
const stateStore = new KTDataTableConfigStateStore(config);
|
|
94
|
+
const table = document.createElement('table');
|
|
95
|
+
const thead = table.createTHead();
|
|
96
|
+
thead.innerHTML = `
|
|
97
|
+
<tr>
|
|
98
|
+
<th data-kt-datatable-column="id">ID</th>
|
|
99
|
+
<th data-kt-datatable-column="name">Name</th>
|
|
100
|
+
</tr>
|
|
101
|
+
`;
|
|
102
|
+
const tbody = table.createTBody();
|
|
103
|
+
tbody.innerHTML = `
|
|
104
|
+
<tr><td>1</td><td>Ada</td></tr>
|
|
105
|
+
<tr><td>2</td><td>Grace</td></tr>
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const provider = new KTDataTableLocalDataProvider({
|
|
109
|
+
config,
|
|
110
|
+
elements: () => ({
|
|
111
|
+
tableElement: table,
|
|
112
|
+
tbodyElement: tbody,
|
|
113
|
+
theadElement: thead,
|
|
114
|
+
}),
|
|
115
|
+
getLogicalColumnCount: () => 2,
|
|
116
|
+
storeOriginalClasses: vi.fn(),
|
|
117
|
+
stateStore,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const result = provider.fetchSync();
|
|
121
|
+
|
|
122
|
+
expect(result.totalItems).toBe(2);
|
|
123
|
+
expect(result.data).toEqual([{ id: '1', name: 'Ada' }]);
|
|
124
|
+
expect(stateStore.getState().originalData).toHaveLength(2);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('normalizes remote provider fetch results and emits response event', async () => {
|
|
128
|
+
const config = createConfig({ apiEndpoint: '/api/users' });
|
|
129
|
+
const stateStore = new KTDataTableConfigStateStore(config);
|
|
130
|
+
const emit = vi.fn();
|
|
131
|
+
global.fetch = vi.fn().mockResolvedValue(
|
|
132
|
+
new Response(
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
data: [{ id: 1, name: 'Ada' }],
|
|
135
|
+
totalCount: 1,
|
|
136
|
+
}),
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const provider = new KTDataTableRemoteDataProvider({
|
|
141
|
+
config,
|
|
142
|
+
createUrl: (pathOrUrl: string) =>
|
|
143
|
+
new URL(pathOrUrl, window.location.origin),
|
|
144
|
+
eventAdapter: { emit },
|
|
145
|
+
noticeOnTable: vi.fn(),
|
|
146
|
+
stateStore,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const result = await provider.fetch();
|
|
150
|
+
|
|
151
|
+
expect(result.data).toEqual([{ id: 1, name: 'Ada' }]);
|
|
152
|
+
expect(result.totalItems).toBe(1);
|
|
153
|
+
expect(emit).toHaveBeenCalledWith('fetched', {
|
|
154
|
+
response: { data: [{ id: 1, name: 'Ada' }], totalCount: 1 },
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('renders table body output through the table renderer', () => {
|
|
159
|
+
const config = createConfig();
|
|
160
|
+
const stateStore = new KTDataTableConfigStateStore(config);
|
|
161
|
+
stateStore.setOriginalData([{ id: '1', name: 'Ada' }], []);
|
|
162
|
+
const table = document.createElement('table');
|
|
163
|
+
const thead = table.createTHead();
|
|
164
|
+
thead.innerHTML = `
|
|
165
|
+
<tr>
|
|
166
|
+
<th data-kt-datatable-column="id">ID</th>
|
|
167
|
+
<th data-kt-datatable-column="name">Name</th>
|
|
168
|
+
</tr>
|
|
169
|
+
`;
|
|
170
|
+
table.createTBody();
|
|
171
|
+
|
|
172
|
+
new KTDataTableDomTableRenderer().render({
|
|
173
|
+
config,
|
|
174
|
+
context: {} as KTDataTable,
|
|
175
|
+
data: [{ id: '1', name: 'Ada' }],
|
|
176
|
+
getLogicalColumnCount: () => 2,
|
|
177
|
+
getState: () => stateStore.getState(),
|
|
178
|
+
originalTbodyClass: 'body-class',
|
|
179
|
+
originalTrClasses: ['row-class'],
|
|
180
|
+
originalTdClasses: [['id-cell', 'name-cell']],
|
|
181
|
+
tableElement: table,
|
|
182
|
+
theadElement: thead,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
expect(table.tBodies[0].className).toBe('body-class');
|
|
186
|
+
expect(table.tBodies[0].querySelector('tr')?.className).toBe('row-class');
|
|
187
|
+
expect(table.tBodies[0].querySelectorAll('td')[1].textContent).toBe('Ada');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('renders pagination controls through the pagination renderer', async () => {
|
|
191
|
+
const renderer = new KTDataTableDomPaginationRenderer();
|
|
192
|
+
const config = createConfig();
|
|
193
|
+
const sizeElement = document.createElement('select');
|
|
194
|
+
const infoElement = document.createElement('div');
|
|
195
|
+
const paginationElement = document.createElement('div');
|
|
196
|
+
const paginateData = vi.fn();
|
|
197
|
+
|
|
198
|
+
renderer.render({
|
|
199
|
+
config,
|
|
200
|
+
dataLength: 10,
|
|
201
|
+
infoElement,
|
|
202
|
+
paginateData,
|
|
203
|
+
paginationElement,
|
|
204
|
+
reloadPageSize: vi.fn(),
|
|
205
|
+
sizeElement,
|
|
206
|
+
state: {
|
|
207
|
+
page: 2,
|
|
208
|
+
pageSize: 10,
|
|
209
|
+
totalItems: 25,
|
|
210
|
+
totalPages: 3,
|
|
211
|
+
filters: [],
|
|
212
|
+
} as never,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await waitFor(120);
|
|
216
|
+
|
|
217
|
+
expect(infoElement.textContent).toBe('11-20 of 25');
|
|
218
|
+
expect(sizeElement.querySelectorAll('option')).toHaveLength(2);
|
|
219
|
+
expect(paginationElement.querySelectorAll('button')).toHaveLength(5);
|
|
220
|
+
paginationElement.querySelectorAll('button')[1].click();
|
|
221
|
+
expect(paginateData).toHaveBeenCalledWith(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('keeps local and remote provider selection compatible through KTDataTable', async () => {
|
|
225
|
+
const element = document.createElement('div');
|
|
226
|
+
element.innerHTML = `
|
|
227
|
+
<table data-kt-datatable-table="true">
|
|
228
|
+
<thead><tr><th data-kt-datatable-column="name">Name</th></tr></thead>
|
|
229
|
+
<tbody><tr><td>Ada</td></tr></tbody>
|
|
230
|
+
</table>
|
|
231
|
+
<div data-kt-datatable-info="true"></div>
|
|
232
|
+
<select data-kt-datatable-size="true"></select>
|
|
233
|
+
<div data-kt-datatable-pagination="true"></div>
|
|
234
|
+
`;
|
|
235
|
+
document.body.appendChild(element);
|
|
236
|
+
|
|
237
|
+
const fetchSpy = vi
|
|
238
|
+
.fn()
|
|
239
|
+
.mockResolvedValue(
|
|
240
|
+
new Response(
|
|
241
|
+
JSON.stringify({ data: [{ name: 'Ada' }], totalCount: 1 }),
|
|
242
|
+
),
|
|
243
|
+
);
|
|
244
|
+
global.fetch = fetchSpy;
|
|
245
|
+
|
|
246
|
+
new KTDataTable(element, { stateSave: false });
|
|
247
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
248
|
+
|
|
249
|
+
const remoteElement = element.cloneNode(true) as HTMLElement;
|
|
250
|
+
document.body.appendChild(remoteElement);
|
|
251
|
+
new KTDataTable(remoteElement, {
|
|
252
|
+
apiEndpoint: '/api/users',
|
|
253
|
+
stateSave: false,
|
|
254
|
+
});
|
|
255
|
+
await waitFor(0);
|
|
256
|
+
|
|
257
|
+
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
KTDataTableAttributeInterface,
|
|
8
|
+
KTDataTableColumnFilterInterface,
|
|
9
|
+
KTDataTableConfigInterface,
|
|
10
|
+
KTDataTableDataInterface,
|
|
11
|
+
KTDataTableInterface,
|
|
12
|
+
KTDataTableSortOrderInterface,
|
|
13
|
+
KTDataTableStateInterface,
|
|
14
|
+
} from './types';
|
|
15
|
+
|
|
16
|
+
export type KTDataTableEmit = (eventName: string, eventData?: object) => void;
|
|
17
|
+
export type KTDataTableCleanup = () => void;
|
|
18
|
+
|
|
19
|
+
export interface KTDataTableEventAdapter {
|
|
20
|
+
emit(eventName: string, eventData?: object): void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface KTDataTableStateStore {
|
|
24
|
+
getState(): KTDataTableStateInterface;
|
|
25
|
+
replaceState(state: KTDataTableStateInterface): void;
|
|
26
|
+
patchState(state: Partial<KTDataTableStateInterface>): void;
|
|
27
|
+
setPage(page: number): void;
|
|
28
|
+
setPageSize(pageSize: number, page?: number): void;
|
|
29
|
+
setSort(
|
|
30
|
+
field: keyof KTDataTableDataInterface | number,
|
|
31
|
+
order: KTDataTableSortOrderInterface,
|
|
32
|
+
): void;
|
|
33
|
+
setSearch(search: string | object): void;
|
|
34
|
+
setFilter(filter: KTDataTableColumnFilterInterface): void;
|
|
35
|
+
setOriginalData(
|
|
36
|
+
originalData: KTDataTableDataInterface[],
|
|
37
|
+
originalDataAttributes: KTDataTableAttributeInterface[],
|
|
38
|
+
): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface KTDataTableProviderResult<T extends KTDataTableDataInterface> {
|
|
42
|
+
data: T[];
|
|
43
|
+
totalItems: number;
|
|
44
|
+
response?: unknown;
|
|
45
|
+
skipped?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface KTDataTableDataProvider<T extends KTDataTableDataInterface> {
|
|
49
|
+
fetch(): Promise<KTDataTableProviderResult<T>>;
|
|
50
|
+
dispose?(): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface KTDataTableLocalProviderElements {
|
|
54
|
+
tableElement: HTMLTableElement;
|
|
55
|
+
tbodyElement: HTMLTableSectionElement;
|
|
56
|
+
theadElement: HTMLTableSectionElement;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface KTDataTableTableRendererInput<
|
|
60
|
+
T extends KTDataTableDataInterface,
|
|
61
|
+
> {
|
|
62
|
+
config: KTDataTableConfigInterface;
|
|
63
|
+
context: KTDataTableInterface;
|
|
64
|
+
data: T[];
|
|
65
|
+
getLogicalColumnCount: () => number;
|
|
66
|
+
getState: () => KTDataTableStateInterface;
|
|
67
|
+
originalTbodyClass: string;
|
|
68
|
+
originalTrClasses: string[];
|
|
69
|
+
originalTdClasses: string[][];
|
|
70
|
+
tableElement: HTMLTableElement;
|
|
71
|
+
theadElement: HTMLTableSectionElement;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface KTDataTableTableRenderer<T extends KTDataTableDataInterface> {
|
|
75
|
+
render(input: KTDataTableTableRendererInput<T>): HTMLTableSectionElement;
|
|
76
|
+
notice(
|
|
77
|
+
tableElement: HTMLTableElement,
|
|
78
|
+
getLogicalColumnCount: () => number,
|
|
79
|
+
message?: string,
|
|
80
|
+
): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface KTDataTablePaginationRendererInput {
|
|
84
|
+
config: KTDataTableConfigInterface;
|
|
85
|
+
dataLength: number;
|
|
86
|
+
infoElement: HTMLElement;
|
|
87
|
+
paginateData: (page: number) => void;
|
|
88
|
+
paginationElement: HTMLElement;
|
|
89
|
+
reloadPageSize: (pageSize: number, page?: number) => void;
|
|
90
|
+
sizeElement: HTMLSelectElement;
|
|
91
|
+
state: KTDataTableStateInterface;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface KTDataTablePaginationRenderer {
|
|
95
|
+
render(input: KTDataTablePaginationRendererInput): KTDataTableCleanup | void;
|
|
96
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
KTDataTableEmit,
|
|
8
|
+
KTDataTableEventAdapter,
|
|
9
|
+
} from './datatable-contracts';
|
|
10
|
+
|
|
11
|
+
export function createDataTableEventAdapter(
|
|
12
|
+
fireEvent: KTDataTableEmit,
|
|
13
|
+
dispatchEvent: KTDataTableEmit,
|
|
14
|
+
): KTDataTableEventAdapter {
|
|
15
|
+
return {
|
|
16
|
+
emit(eventName: string, eventData?: object): void {
|
|
17
|
+
fireEvent(eventName, eventData);
|
|
18
|
+
dispatchEvent(eventName, eventData);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import KTUtils from '../../helpers/utils';
|
|
7
|
+
import {
|
|
8
|
+
KTDataTableAttributeInterface,
|
|
9
|
+
KTDataTableConfigInterface,
|
|
10
|
+
KTDataTableDataInterface,
|
|
11
|
+
KTDataTableStateInterface,
|
|
12
|
+
} from './types';
|
|
13
|
+
import {
|
|
14
|
+
KTDataTableDataProvider,
|
|
15
|
+
KTDataTableLocalProviderElements,
|
|
16
|
+
KTDataTableProviderResult,
|
|
17
|
+
KTDataTableStateStore,
|
|
18
|
+
} from './datatable-contracts';
|
|
19
|
+
|
|
20
|
+
interface KTDataTableLocalProviderOptions<T extends KTDataTableDataInterface> {
|
|
21
|
+
config: KTDataTableConfigInterface;
|
|
22
|
+
elements: () => KTDataTableLocalProviderElements;
|
|
23
|
+
getLogicalColumnCount: () => number;
|
|
24
|
+
storeOriginalClasses: () => void;
|
|
25
|
+
stateStore: KTDataTableStateStore;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class KTDataTableLocalDataProvider<
|
|
29
|
+
T extends KTDataTableDataInterface,
|
|
30
|
+
> implements KTDataTableDataProvider<T> {
|
|
31
|
+
constructor(private readonly options: KTDataTableLocalProviderOptions<T>) {}
|
|
32
|
+
|
|
33
|
+
public async fetch(): Promise<KTDataTableProviderResult<T>> {
|
|
34
|
+
return this.fetchSync();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public fetchSync(): KTDataTableProviderResult<T> {
|
|
38
|
+
const state = this.options.stateStore.getState();
|
|
39
|
+
let { originalData } = state;
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
!this.options.elements().tableElement ||
|
|
43
|
+
originalData === undefined ||
|
|
44
|
+
this.tableConfigInvalidate() ||
|
|
45
|
+
this.localTableHeaderInvalidate() ||
|
|
46
|
+
this.localTableContentInvalidate()
|
|
47
|
+
) {
|
|
48
|
+
const { originalData, originalDataAttributes } =
|
|
49
|
+
this.localExtractTableContent();
|
|
50
|
+
|
|
51
|
+
this.options.stateStore.setOriginalData(
|
|
52
|
+
originalData,
|
|
53
|
+
originalDataAttributes,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
originalData = this.options.stateStore.getState().originalData;
|
|
58
|
+
let data = [...originalData] as T[];
|
|
59
|
+
let filteredData = data;
|
|
60
|
+
|
|
61
|
+
const { sortField, sortOrder, page, pageSize, search } =
|
|
62
|
+
this.options.stateStore.getState();
|
|
63
|
+
|
|
64
|
+
if (search) {
|
|
65
|
+
const searchTerm = typeof search === 'string' ? search : '';
|
|
66
|
+
filteredData = data = this.options.config.search.callback.call(
|
|
67
|
+
this,
|
|
68
|
+
data,
|
|
69
|
+
searchTerm,
|
|
70
|
+
) as T[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
sortField !== undefined &&
|
|
75
|
+
sortOrder !== undefined &&
|
|
76
|
+
sortOrder !== '' &&
|
|
77
|
+
typeof this.options.config.sort.callback === 'function'
|
|
78
|
+
) {
|
|
79
|
+
data = this.options.config.sort.callback.call(
|
|
80
|
+
this,
|
|
81
|
+
data,
|
|
82
|
+
sortField as string,
|
|
83
|
+
sortOrder,
|
|
84
|
+
) as T[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (data?.length > 0) {
|
|
88
|
+
const startIndex = (page - 1) * pageSize;
|
|
89
|
+
const endIndex = startIndex + pageSize;
|
|
90
|
+
data = data.slice(startIndex, endIndex) as T[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
data,
|
|
95
|
+
totalItems: filteredData.length,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private localTableContentInvalidate(): boolean {
|
|
100
|
+
const { tbodyElement } = this.options.elements();
|
|
101
|
+
const checksum: string = KTUtils.checksum(
|
|
102
|
+
JSON.stringify(tbodyElement.innerHTML),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (this.options.stateStore.getState()._contentChecksum !== checksum) {
|
|
106
|
+
this.options.stateStore.patchState({ _contentChecksum: checksum });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private tableConfigInvalidate(): boolean {
|
|
114
|
+
const { _state, ...restConfig } = this.options.config;
|
|
115
|
+
const checksum: string = KTUtils.checksum(JSON.stringify(restConfig));
|
|
116
|
+
|
|
117
|
+
if (_state._configChecksum !== checksum) {
|
|
118
|
+
this.options.stateStore.patchState({ _configChecksum: checksum });
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private localExtractTableContent(): {
|
|
126
|
+
originalData: T[];
|
|
127
|
+
originalDataAttributes: KTDataTableAttributeInterface[];
|
|
128
|
+
} {
|
|
129
|
+
const originalData: T[] = [];
|
|
130
|
+
const originalDataAttributes: KTDataTableAttributeInterface[] = [];
|
|
131
|
+
const { tbodyElement, theadElement } = this.options.elements();
|
|
132
|
+
|
|
133
|
+
this.options.storeOriginalClasses();
|
|
134
|
+
|
|
135
|
+
const rows = tbodyElement.querySelectorAll<HTMLTableRowElement>('tr');
|
|
136
|
+
const allThs: NodeListOf<HTMLTableCellElement> = theadElement
|
|
137
|
+
? theadElement.querySelectorAll('th')
|
|
138
|
+
: ([] as unknown as NodeListOf<HTMLTableCellElement>);
|
|
139
|
+
|
|
140
|
+
const ths: HTMLTableCellElement[] = Array.from(allThs).filter((th) =>
|
|
141
|
+
th.hasAttribute('data-kt-datatable-column'),
|
|
142
|
+
);
|
|
143
|
+
const columnsByIndex: HTMLTableCellElement[] =
|
|
144
|
+
ths.length > 0 && ths.length !== allThs.length ? Array.from(allThs) : ths;
|
|
145
|
+
|
|
146
|
+
rows.forEach((row: HTMLTableRowElement) => {
|
|
147
|
+
const dataRow: T = {} as T;
|
|
148
|
+
const dataRowAttribute: KTDataTableAttributeInterface =
|
|
149
|
+
{} as KTDataTableAttributeInterface;
|
|
150
|
+
|
|
151
|
+
row.querySelectorAll<HTMLTableCellElement>('td').forEach((td, index) => {
|
|
152
|
+
const colName = columnsByIndex[index]?.getAttribute(
|
|
153
|
+
'data-kt-datatable-column',
|
|
154
|
+
);
|
|
155
|
+
if (colName) {
|
|
156
|
+
dataRow[colName as keyof T] = td.innerHTML?.trim() as T[keyof T];
|
|
157
|
+
} else {
|
|
158
|
+
dataRow[index as keyof T] = td.innerHTML?.trim() as T[keyof T];
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (Object.keys(dataRow).length > 0) {
|
|
163
|
+
originalData.push(dataRow);
|
|
164
|
+
originalDataAttributes.push(dataRowAttribute);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return { originalData, originalDataAttributes };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private localTableHeaderInvalidate(): boolean {
|
|
172
|
+
const { originalData } = this.options.stateStore.getState();
|
|
173
|
+
const { theadElement } = this.options.elements();
|
|
174
|
+
|
|
175
|
+
const totalColumns = originalData.length
|
|
176
|
+
? Object.keys(originalData[0]).length
|
|
177
|
+
: 0;
|
|
178
|
+
|
|
179
|
+
const allThs: NodeListOf<HTMLTableCellElement> = theadElement
|
|
180
|
+
? theadElement.querySelectorAll('th')
|
|
181
|
+
: ([] as unknown as NodeListOf<HTMLTableCellElement>);
|
|
182
|
+
const thsWithColumn = Array.from(allThs).filter((th) =>
|
|
183
|
+
th.hasAttribute('data-kt-datatable-column'),
|
|
184
|
+
);
|
|
185
|
+
const currentTableHeaders =
|
|
186
|
+
thsWithColumn.length > 0
|
|
187
|
+
? thsWithColumn.length !== allThs.length
|
|
188
|
+
? allThs.length
|
|
189
|
+
: thsWithColumn.length
|
|
190
|
+
: this.options.getLogicalColumnCount();
|
|
191
|
+
|
|
192
|
+
return currentTableHeaders !== totalColumns;
|
|
193
|
+
}
|
|
194
|
+
}
|