@keenthemes/ktui 1.2.5 → 1.2.6
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 +1965 -2690
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +60 -0
- package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
- 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 +328 -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 +5 -3
- 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 +11 -12
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
- package/lib/cjs/components/datatable/datatable.d.ts +9 -0
- package/lib/cjs/components/datatable/datatable.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable.js +90 -17
- 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 +27 -0
- 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.map +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
- 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 +324 -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 +5 -3
- 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 +11 -12
- package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
- package/lib/esm/components/datatable/datatable.d.ts +9 -0
- package/lib/esm/components/datatable/datatable.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable.js +90 -17
- 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 +27 -0
- 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.map +1 -1
- package/package.json +1 -1
- package/src/components/datatable/__tests__/locked-layout.test.ts +257 -0
- package/src/components/datatable/__tests__/pagination-reset.test.ts +18 -0
- package/src/components/datatable/datatable-checkbox.ts +5 -8
- package/src/components/datatable/datatable-layout-plugin.ts +449 -0
- package/src/components/datatable/datatable-local-provider.ts +15 -7
- package/src/components/datatable/datatable-pagination-renderer.ts +10 -13
- package/src/components/datatable/datatable.css +98 -0
- package/src/components/datatable/datatable.ts +109 -15
- package/src/components/datatable/index.ts +5 -0
- package/src/components/datatable/types.ts +33 -0
- package/src/index.ts +5 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KTUI - Free & Open-Source Tailwind UI Components by Keenthemes
|
|
3
|
+
* Copyright 2025 by Keenthemes Inc
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
KTDataTableConfigInterface,
|
|
8
|
+
KTDataTableLayoutPluginContextInterface,
|
|
9
|
+
KTDataTableLayoutPluginInterface,
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
type Edge = 'left' | 'right';
|
|
13
|
+
|
|
14
|
+
const LOCKED_CELL_CLASS = 'kt-datatable-locked-cell';
|
|
15
|
+
const LOCKED_HEADER_CLASS = 'kt-datatable-locked-header';
|
|
16
|
+
const LOCKED_TOP_ROW_CLASS = 'kt-datatable-locked-top-row';
|
|
17
|
+
const LOCKED_BOTTOM_ROW_CLASS = 'kt-datatable-locked-bottom-row';
|
|
18
|
+
const LOCKED_LEFT_CLASS = 'kt-datatable-locked-left';
|
|
19
|
+
const LOCKED_RIGHT_CLASS = 'kt-datatable-locked-right';
|
|
20
|
+
const LOCKED_LAYOUT_SEPARATE_CLASS = 'kt-datatable-locked-layout-separate';
|
|
21
|
+
const LOCKED_HEADER_SECTION_CLASS = 'kt-datatable-locked-header-section';
|
|
22
|
+
|
|
23
|
+
const HEADER_Z_INDEX = 40;
|
|
24
|
+
const ROW_Z_INDEX = 30;
|
|
25
|
+
const COLUMN_Z_INDEX = 35;
|
|
26
|
+
const INTERSECTION_Z_INDEX = 45;
|
|
27
|
+
|
|
28
|
+
const toPositiveInteger = (value: number | undefined): number => {
|
|
29
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Math.max(0, Math.floor(value));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const hasStickyColumns = (config: KTDataTableConfigInterface): boolean => {
|
|
37
|
+
const lockedLayout = config.lockedLayout;
|
|
38
|
+
if (!lockedLayout?.stickyColumns) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
(lockedLayout.stickyColumns.left?.length || 0) > 0 ||
|
|
44
|
+
(lockedLayout.stickyColumns.right?.length || 0) > 0
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const hasLockedLayoutConfig = (config: KTDataTableConfigInterface): boolean => {
|
|
49
|
+
const lockedLayout = config.lockedLayout;
|
|
50
|
+
if (!lockedLayout) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
lockedLayout.stickyHeader === true ||
|
|
56
|
+
toPositiveInteger(lockedLayout.stickyRows?.top) > 0 ||
|
|
57
|
+
toPositiveInteger(lockedLayout.stickyRows?.bottom) > 0 ||
|
|
58
|
+
(lockedLayout.stickyColumns?.left?.length || 0) > 0 ||
|
|
59
|
+
(lockedLayout.stickyColumns?.right?.length || 0) > 0
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getScrollContainer = (rootElement: HTMLElement): HTMLElement => {
|
|
64
|
+
return (
|
|
65
|
+
rootElement.closest<HTMLElement>('.kt-table-wrapper') ||
|
|
66
|
+
rootElement.querySelector<HTMLElement>('.kt-table-wrapper') ||
|
|
67
|
+
rootElement
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const clearStickyStyles = (
|
|
72
|
+
tableElement: HTMLTableElement,
|
|
73
|
+
scrollContainer: HTMLElement,
|
|
74
|
+
): void => {
|
|
75
|
+
tableElement.classList.remove(
|
|
76
|
+
'kt-datatable-locked-layout',
|
|
77
|
+
LOCKED_LAYOUT_SEPARATE_CLASS,
|
|
78
|
+
);
|
|
79
|
+
scrollContainer.classList.remove('kt-datatable-locked-layout-host');
|
|
80
|
+
tableElement.style.borderCollapse = '';
|
|
81
|
+
tableElement.style.borderSpacing = '';
|
|
82
|
+
|
|
83
|
+
const theadElement = tableElement.tHead;
|
|
84
|
+
if (theadElement) {
|
|
85
|
+
theadElement.classList.remove(LOCKED_HEADER_SECTION_CLASS);
|
|
86
|
+
theadElement.style.position = '';
|
|
87
|
+
theadElement.style.top = '';
|
|
88
|
+
theadElement.style.zIndex = '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const stickyElements = tableElement.querySelectorAll<HTMLElement>(
|
|
92
|
+
`.${LOCKED_CELL_CLASS}`,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
stickyElements.forEach((element) => {
|
|
96
|
+
element.classList.remove(
|
|
97
|
+
LOCKED_CELL_CLASS,
|
|
98
|
+
LOCKED_HEADER_CLASS,
|
|
99
|
+
LOCKED_TOP_ROW_CLASS,
|
|
100
|
+
LOCKED_BOTTOM_ROW_CLASS,
|
|
101
|
+
LOCKED_LEFT_CLASS,
|
|
102
|
+
LOCKED_RIGHT_CLASS,
|
|
103
|
+
);
|
|
104
|
+
element.style.position = '';
|
|
105
|
+
element.style.top = '';
|
|
106
|
+
element.style.bottom = '';
|
|
107
|
+
element.style.left = '';
|
|
108
|
+
element.style.right = '';
|
|
109
|
+
element.style.zIndex = '';
|
|
110
|
+
element.style.backgroundColor = '';
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const getDirection = (tableElement: HTMLTableElement): 'ltr' | 'rtl' => {
|
|
115
|
+
const scopedDir = tableElement
|
|
116
|
+
.closest<HTMLElement>('[dir]')
|
|
117
|
+
?.getAttribute('dir');
|
|
118
|
+
const globalDir =
|
|
119
|
+
typeof document !== 'undefined'
|
|
120
|
+
? document.documentElement.getAttribute('dir')
|
|
121
|
+
: null;
|
|
122
|
+
return scopedDir === 'rtl' || globalDir === 'rtl' ? 'rtl' : 'ltr';
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const resolveEdgeProperty = (edge: Edge, direction: 'ltr' | 'rtl'): Edge => {
|
|
126
|
+
if (direction === 'rtl') {
|
|
127
|
+
return edge === 'left' ? 'right' : 'left';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return edge;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const setStickyEdge = (
|
|
134
|
+
element: HTMLElement,
|
|
135
|
+
edge: Edge,
|
|
136
|
+
offset: number,
|
|
137
|
+
direction: 'ltr' | 'rtl',
|
|
138
|
+
): void => {
|
|
139
|
+
const resolvedEdge = resolveEdgeProperty(edge, direction);
|
|
140
|
+
if (resolvedEdge === 'left') {
|
|
141
|
+
element.style.left = `${offset}px`;
|
|
142
|
+
element.style.right = '';
|
|
143
|
+
} else {
|
|
144
|
+
element.style.right = `${offset}px`;
|
|
145
|
+
element.style.left = '';
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const ensureStickyCell = (
|
|
150
|
+
element: HTMLElement,
|
|
151
|
+
className: string,
|
|
152
|
+
zIndex: number,
|
|
153
|
+
): void => {
|
|
154
|
+
element.classList.add(LOCKED_CELL_CLASS, className);
|
|
155
|
+
element.style.position = 'sticky';
|
|
156
|
+
element.style.zIndex = String(zIndex);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const measureStickyHeaderHeight = (
|
|
160
|
+
theadElement: HTMLTableSectionElement,
|
|
161
|
+
): number => Math.round(theadElement.offsetHeight);
|
|
162
|
+
|
|
163
|
+
/** Offset for top sticky body rows so they sit flush under a sticky header. */
|
|
164
|
+
const getStickyTopRowOffset = (
|
|
165
|
+
headerHeight: number,
|
|
166
|
+
useCollapsedBorders: boolean,
|
|
167
|
+
): number => {
|
|
168
|
+
if (headerHeight <= 0) {
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Collapsed row borders are shared between thead and the first tbody row.
|
|
173
|
+
return useCollapsedBorders ? headerHeight - 1 : headerHeight;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const markIntersectionZIndex = (element: HTMLElement): void => {
|
|
177
|
+
const isRowLocked =
|
|
178
|
+
element.classList.contains(LOCKED_HEADER_CLASS) ||
|
|
179
|
+
element.classList.contains(LOCKED_TOP_ROW_CLASS) ||
|
|
180
|
+
element.classList.contains(LOCKED_BOTTOM_ROW_CLASS);
|
|
181
|
+
const isColumnLocked =
|
|
182
|
+
element.classList.contains(LOCKED_LEFT_CLASS) ||
|
|
183
|
+
element.classList.contains(LOCKED_RIGHT_CLASS);
|
|
184
|
+
|
|
185
|
+
if (isRowLocked && isColumnLocked) {
|
|
186
|
+
element.style.zIndex = String(INTERSECTION_Z_INDEX);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const applyStickyHeader = (
|
|
191
|
+
theadElement: HTMLTableSectionElement,
|
|
192
|
+
enabled: boolean,
|
|
193
|
+
useSectionSticky: boolean,
|
|
194
|
+
): number => {
|
|
195
|
+
if (!enabled) {
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (useSectionSticky) {
|
|
200
|
+
theadElement.classList.add(LOCKED_HEADER_SECTION_CLASS);
|
|
201
|
+
theadElement.style.position = 'sticky';
|
|
202
|
+
theadElement.style.top = '0';
|
|
203
|
+
theadElement.style.zIndex = String(HEADER_Z_INDEX);
|
|
204
|
+
|
|
205
|
+
Array.from(theadElement.rows).forEach((row) => {
|
|
206
|
+
Array.from(row.cells).forEach((cell) => {
|
|
207
|
+
const headerCell = cell as HTMLTableCellElement;
|
|
208
|
+
headerCell.classList.add(LOCKED_CELL_CLASS, LOCKED_HEADER_CLASS);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return measureStickyHeaderHeight(theadElement);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let cumulativeTop = 0;
|
|
216
|
+
Array.from(theadElement.rows).forEach((row) => {
|
|
217
|
+
const rowTop = cumulativeTop;
|
|
218
|
+
Array.from(row.cells).forEach((cell) => {
|
|
219
|
+
const headerCell = cell as HTMLTableCellElement;
|
|
220
|
+
ensureStickyCell(headerCell, LOCKED_HEADER_CLASS, HEADER_Z_INDEX);
|
|
221
|
+
headerCell.style.top = `${rowTop}px`;
|
|
222
|
+
});
|
|
223
|
+
cumulativeTop += row.offsetHeight;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return cumulativeTop;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const applyStickyRows = (
|
|
230
|
+
tbodyElement: HTMLTableSectionElement,
|
|
231
|
+
headerHeight: number,
|
|
232
|
+
topCount: number,
|
|
233
|
+
bottomCount: number,
|
|
234
|
+
useCollapsedBorders: boolean,
|
|
235
|
+
): void => {
|
|
236
|
+
const rows = Array.from(tbodyElement.rows);
|
|
237
|
+
|
|
238
|
+
let topOffset = getStickyTopRowOffset(headerHeight, useCollapsedBorders);
|
|
239
|
+
rows.slice(0, topCount).forEach((row) => {
|
|
240
|
+
const rowTop = topOffset;
|
|
241
|
+
Array.from(row.cells).forEach((cell) => {
|
|
242
|
+
const td = cell as HTMLTableCellElement;
|
|
243
|
+
ensureStickyCell(td, LOCKED_TOP_ROW_CLASS, ROW_Z_INDEX);
|
|
244
|
+
td.style.top = `${rowTop}px`;
|
|
245
|
+
});
|
|
246
|
+
topOffset += row.offsetHeight;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
let bottomOffset = 0;
|
|
250
|
+
rows
|
|
251
|
+
.slice(Math.max(0, rows.length - bottomCount))
|
|
252
|
+
.reverse()
|
|
253
|
+
.forEach((row) => {
|
|
254
|
+
const rowBottom = bottomOffset;
|
|
255
|
+
Array.from(row.cells).forEach((cell) => {
|
|
256
|
+
const td = cell as HTMLTableCellElement;
|
|
257
|
+
ensureStickyCell(td, LOCKED_BOTTOM_ROW_CLASS, ROW_Z_INDEX);
|
|
258
|
+
td.style.bottom = `${rowBottom}px`;
|
|
259
|
+
});
|
|
260
|
+
bottomOffset += row.offsetHeight;
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const getColumnIndexMap = (
|
|
265
|
+
theadElement: HTMLTableSectionElement,
|
|
266
|
+
config: KTDataTableConfigInterface,
|
|
267
|
+
): Map<string, number> => {
|
|
268
|
+
const map = new Map<string, number>();
|
|
269
|
+
const typedHeaders = Array.from(
|
|
270
|
+
theadElement.querySelectorAll<HTMLTableCellElement>(
|
|
271
|
+
'th[data-kt-datatable-column]',
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
if (typedHeaders.length > 0) {
|
|
276
|
+
typedHeaders.forEach((th, index) => {
|
|
277
|
+
const column = th.getAttribute('data-kt-datatable-column');
|
|
278
|
+
if (column) {
|
|
279
|
+
map.set(column, index);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
return map;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (config.columns) {
|
|
286
|
+
Object.keys(config.columns).forEach((key, index) => {
|
|
287
|
+
map.set(key, index);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return map;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const getColumnCells = (
|
|
295
|
+
tableElement: HTMLTableElement,
|
|
296
|
+
columnIndex: number,
|
|
297
|
+
): HTMLTableCellElement[] => {
|
|
298
|
+
const cells: HTMLTableCellElement[] = [];
|
|
299
|
+
tableElement.querySelectorAll('tr').forEach((row) => {
|
|
300
|
+
const cell = row.children.item(columnIndex);
|
|
301
|
+
if (cell instanceof HTMLTableCellElement) {
|
|
302
|
+
cells.push(cell);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
return cells;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const applyStickyColumns = (
|
|
309
|
+
tableElement: HTMLTableElement,
|
|
310
|
+
theadElement: HTMLTableSectionElement,
|
|
311
|
+
config: KTDataTableConfigInterface,
|
|
312
|
+
): void => {
|
|
313
|
+
const lockedColumns = config.lockedLayout?.stickyColumns;
|
|
314
|
+
if (!lockedColumns) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const direction = getDirection(tableElement);
|
|
319
|
+
const columnMap = getColumnIndexMap(theadElement, config);
|
|
320
|
+
|
|
321
|
+
let leftOffset = 0;
|
|
322
|
+
(lockedColumns.left || []).forEach((key) => {
|
|
323
|
+
const index = columnMap.get(key);
|
|
324
|
+
if (typeof index !== 'number') {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const cells = getColumnCells(tableElement, index);
|
|
329
|
+
if (cells.length === 0) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const width = cells[0].getBoundingClientRect().width;
|
|
334
|
+
cells.forEach((cell) => {
|
|
335
|
+
ensureStickyCell(cell, LOCKED_LEFT_CLASS, COLUMN_Z_INDEX);
|
|
336
|
+
setStickyEdge(cell, 'left', leftOffset, direction);
|
|
337
|
+
});
|
|
338
|
+
leftOffset += width;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
let rightOffset = 0;
|
|
342
|
+
[...(lockedColumns.right || [])].reverse().forEach((key) => {
|
|
343
|
+
const index = columnMap.get(key);
|
|
344
|
+
if (typeof index !== 'number') {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const cells = getColumnCells(tableElement, index);
|
|
349
|
+
if (cells.length === 0) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const width = cells[0].getBoundingClientRect().width;
|
|
354
|
+
cells.forEach((cell) => {
|
|
355
|
+
ensureStickyCell(cell, LOCKED_RIGHT_CLASS, COLUMN_Z_INDEX);
|
|
356
|
+
setStickyEdge(cell, 'right', rightOffset, direction);
|
|
357
|
+
});
|
|
358
|
+
rightOffset += width;
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
tableElement
|
|
362
|
+
.querySelectorAll<HTMLElement>(`.${LOCKED_CELL_CLASS}`)
|
|
363
|
+
.forEach(markIntersectionZIndex);
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export const createStickyLayoutPlugin =
|
|
367
|
+
(): KTDataTableLayoutPluginInterface => {
|
|
368
|
+
let resizeHandler: (() => void) | null = null;
|
|
369
|
+
let scrollContainerTarget: HTMLElement | null = null;
|
|
370
|
+
let isApplying = false;
|
|
371
|
+
|
|
372
|
+
const applyLayout = (
|
|
373
|
+
ctx: KTDataTableLayoutPluginContextInterface,
|
|
374
|
+
): void => {
|
|
375
|
+
if (isApplying || !hasLockedLayoutConfig(ctx.config)) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
isApplying = true;
|
|
380
|
+
try {
|
|
381
|
+
const scrollContainer = getScrollContainer(ctx.rootElement);
|
|
382
|
+
clearStickyStyles(ctx.tableElement, scrollContainer);
|
|
383
|
+
ctx.tableElement.classList.add('kt-datatable-locked-layout');
|
|
384
|
+
scrollContainer.classList.add('kt-datatable-locked-layout-host');
|
|
385
|
+
|
|
386
|
+
if (hasStickyColumns(ctx.config)) {
|
|
387
|
+
ctx.tableElement.classList.add(LOCKED_LAYOUT_SEPARATE_CLASS);
|
|
388
|
+
ctx.tableElement.style.borderCollapse = 'separate';
|
|
389
|
+
ctx.tableElement.style.borderSpacing = '0';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const lockedLayout = ctx.config.lockedLayout || {};
|
|
393
|
+
const useCollapsedBorders = !hasStickyColumns(ctx.config);
|
|
394
|
+
const headerHeight = applyStickyHeader(
|
|
395
|
+
ctx.theadElement,
|
|
396
|
+
lockedLayout.stickyHeader === true,
|
|
397
|
+
useCollapsedBorders,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
applyStickyRows(
|
|
401
|
+
ctx.tbodyElement,
|
|
402
|
+
headerHeight,
|
|
403
|
+
toPositiveInteger(lockedLayout.stickyRows?.top),
|
|
404
|
+
toPositiveInteger(lockedLayout.stickyRows?.bottom),
|
|
405
|
+
useCollapsedBorders,
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
applyStickyColumns(ctx.tableElement, ctx.theadElement, ctx.config);
|
|
409
|
+
} finally {
|
|
410
|
+
isApplying = false;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const detachResizeListener = (): void => {
|
|
415
|
+
if (!resizeHandler) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
window.removeEventListener('resize', resizeHandler);
|
|
420
|
+
if (scrollContainerTarget) {
|
|
421
|
+
scrollContainerTarget.removeEventListener('scroll', resizeHandler);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
resizeHandler = null;
|
|
425
|
+
scrollContainerTarget = null;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
beforeDraw: (ctx) => {
|
|
430
|
+
const scrollContainer = getScrollContainer(ctx.rootElement);
|
|
431
|
+
clearStickyStyles(ctx.tableElement, scrollContainer);
|
|
432
|
+
},
|
|
433
|
+
afterDraw: (ctx) => {
|
|
434
|
+
detachResizeListener();
|
|
435
|
+
applyLayout(ctx);
|
|
436
|
+
|
|
437
|
+
const scrollContainer = getScrollContainer(ctx.rootElement);
|
|
438
|
+
resizeHandler = () => applyLayout(ctx);
|
|
439
|
+
window.addEventListener('resize', resizeHandler);
|
|
440
|
+
scrollContainerTarget = scrollContainer;
|
|
441
|
+
scrollContainer.addEventListener('scroll', resizeHandler);
|
|
442
|
+
},
|
|
443
|
+
dispose: (ctx) => {
|
|
444
|
+
detachResizeListener();
|
|
445
|
+
const scrollContainer = getScrollContainer(ctx.rootElement);
|
|
446
|
+
clearStickyStyles(ctx.tableElement, scrollContainer);
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
};
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
KTDataTableAttributeInterface,
|
|
9
9
|
KTDataTableConfigInterface,
|
|
10
10
|
KTDataTableDataInterface,
|
|
11
|
-
KTDataTableStateInterface,
|
|
12
11
|
} from './types';
|
|
13
12
|
import {
|
|
14
13
|
KTDataTableDataProvider,
|
|
@@ -17,7 +16,7 @@ import {
|
|
|
17
16
|
KTDataTableStateStore,
|
|
18
17
|
} from './datatable-contracts';
|
|
19
18
|
|
|
20
|
-
interface KTDataTableLocalProviderOptions
|
|
19
|
+
interface KTDataTableLocalProviderOptions {
|
|
21
20
|
config: KTDataTableConfigInterface;
|
|
22
21
|
elements: () => KTDataTableLocalProviderElements;
|
|
23
22
|
getLogicalColumnCount: () => number;
|
|
@@ -28,7 +27,7 @@ interface KTDataTableLocalProviderOptions<T extends KTDataTableDataInterface> {
|
|
|
28
27
|
export class KTDataTableLocalDataProvider<
|
|
29
28
|
T extends KTDataTableDataInterface,
|
|
30
29
|
> implements KTDataTableDataProvider<T> {
|
|
31
|
-
constructor(private readonly options: KTDataTableLocalProviderOptions
|
|
30
|
+
constructor(private readonly options: KTDataTableLocalProviderOptions) {}
|
|
32
31
|
|
|
33
32
|
public async fetch(): Promise<KTDataTableProviderResult<T>> {
|
|
34
33
|
return this.fetchSync();
|
|
@@ -37,13 +36,17 @@ export class KTDataTableLocalDataProvider<
|
|
|
37
36
|
public fetchSync(): KTDataTableProviderResult<T> {
|
|
38
37
|
const state = this.options.stateStore.getState();
|
|
39
38
|
let { originalData } = state;
|
|
39
|
+
const skipDomInvalidation = Boolean(
|
|
40
|
+
this.options.config.lockedLayout || this.options.config.layoutPlugin,
|
|
41
|
+
);
|
|
40
42
|
|
|
41
43
|
if (
|
|
42
44
|
!this.options.elements().tableElement ||
|
|
43
45
|
originalData === undefined ||
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
(!skipDomInvalidation &&
|
|
47
|
+
(this.tableConfigInvalidate() ||
|
|
48
|
+
this.localTableHeaderInvalidate() ||
|
|
49
|
+
this.localTableContentInvalidate()))
|
|
47
50
|
) {
|
|
48
51
|
const { originalData, originalDataAttributes } =
|
|
49
52
|
this.localExtractTableContent();
|
|
@@ -80,7 +83,12 @@ export class KTDataTableLocalDataProvider<
|
|
|
80
83
|
sortOrder !== '' &&
|
|
81
84
|
typeof sortCallback === 'function'
|
|
82
85
|
) {
|
|
83
|
-
data = sortCallback.call(
|
|
86
|
+
data = sortCallback.call(
|
|
87
|
+
this,
|
|
88
|
+
data,
|
|
89
|
+
sortField as string,
|
|
90
|
+
sortOrder,
|
|
91
|
+
) as T[];
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
if (data?.length > 0) {
|
|
@@ -26,6 +26,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
26
26
|
return () => {
|
|
27
27
|
if (input.sizeElement) {
|
|
28
28
|
input.sizeElement.onchange = null;
|
|
29
|
+
this.removeChildElements(input.sizeElement);
|
|
29
30
|
}
|
|
30
31
|
if (input.paginationElement) {
|
|
31
32
|
this.removeChildElements(input.paginationElement);
|
|
@@ -51,18 +52,15 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
const pageSizes = input.config.pageSizes ?? [5, 10, 20, 30, 50];
|
|
55
|
+
const options = pageSizes.map((size: number) => {
|
|
56
|
+
const option = document.createElement('option') as HTMLOptionElement;
|
|
57
|
+
option.value = String(size);
|
|
58
|
+
option.text = String(size);
|
|
59
|
+
option.selected = input.state.pageSize === size;
|
|
60
|
+
return option;
|
|
61
|
+
});
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
const options = pageSizes.map((size: number) => {
|
|
57
|
-
const option = document.createElement('option') as HTMLOptionElement;
|
|
58
|
-
option.value = String(size);
|
|
59
|
-
option.text = String(size);
|
|
60
|
-
option.selected = input.state.pageSize === size;
|
|
61
|
-
return option;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
input.sizeElement.append(...options);
|
|
65
|
-
}, 100);
|
|
63
|
+
input.sizeElement.append(...options);
|
|
66
64
|
|
|
67
65
|
input.sizeElement.onchange = (event: Event) => {
|
|
68
66
|
input.reloadPageSize(
|
|
@@ -92,8 +90,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
92
90
|
return;
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
const infoTemplate =
|
|
96
|
-
input.config.info ?? '{start}-{end} of {total}';
|
|
93
|
+
const infoTemplate = input.config.info ?? '{start}-{end} of {total}';
|
|
97
94
|
input.infoElement.textContent = infoTemplate
|
|
98
95
|
.replace(
|
|
99
96
|
'{start}',
|
|
@@ -71,6 +71,104 @@
|
|
|
71
71
|
[data-kt-datatable].loading table {
|
|
72
72
|
opacity: 0.6;
|
|
73
73
|
}
|
|
74
|
+
|
|
75
|
+
/* Locked layout styles */
|
|
76
|
+
[data-kt-datatable] .kt-datatable-locked-layout {
|
|
77
|
+
position: relative;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
[data-kt-datatable]
|
|
81
|
+
.kt-datatable-locked-layout
|
|
82
|
+
[data-kt-datatable-table]
|
|
83
|
+
thead.kt-datatable-locked-header-section {
|
|
84
|
+
/* Sticky thead group — opaque surface, no gap above first body row */
|
|
85
|
+
background-color: color-mix(
|
|
86
|
+
in srgb,
|
|
87
|
+
var(--color-muted, rgb(0, 0, 0)) 40%,
|
|
88
|
+
var(--color-card, var(--color-background, #ffffff))
|
|
89
|
+
) !important;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Sticky columns use border-collapse: separate — per-cell borders replace tr border-b */
|
|
93
|
+
[data-kt-datatable]
|
|
94
|
+
.kt-datatable-locked-layout.kt-datatable-locked-layout-separate
|
|
95
|
+
[data-kt-datatable-table]
|
|
96
|
+
thead
|
|
97
|
+
tr {
|
|
98
|
+
@apply border-b-0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
[data-kt-datatable]
|
|
102
|
+
.kt-datatable-locked-layout.kt-datatable-locked-layout-separate
|
|
103
|
+
[data-kt-datatable-table]
|
|
104
|
+
thead
|
|
105
|
+
th {
|
|
106
|
+
@apply border-b border-border;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
[data-kt-datatable]
|
|
110
|
+
.kt-datatable-locked-layout.kt-datatable-locked-layout-separate
|
|
111
|
+
[data-kt-datatable-table]
|
|
112
|
+
tbody
|
|
113
|
+
tr {
|
|
114
|
+
@apply border-b-0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
[data-kt-datatable]
|
|
118
|
+
.kt-datatable-locked-layout.kt-datatable-locked-layout-separate
|
|
119
|
+
[data-kt-datatable-table]
|
|
120
|
+
tbody
|
|
121
|
+
td {
|
|
122
|
+
@apply border-b border-border;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
[data-kt-datatable]
|
|
126
|
+
.kt-datatable-locked-layout.kt-datatable-locked-layout-separate
|
|
127
|
+
[data-kt-datatable-table]
|
|
128
|
+
tbody
|
|
129
|
+
tr:last-child
|
|
130
|
+
td {
|
|
131
|
+
@apply border-b-0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
[data-kt-datatable] .kt-datatable-locked-cell {
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
isolation: isolate;
|
|
137
|
+
/* Opaque surface so horizontally scrolling cells do not show through */
|
|
138
|
+
background-color: var(--color-card, var(--color-background, #ffffff)) !important;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
[data-kt-datatable] [data-kt-datatable-table] thead th.kt-datatable-locked-cell,
|
|
142
|
+
[data-kt-datatable] .kt-datatable-locked-header {
|
|
143
|
+
/* Match kt-table thead bg-muted/40 without transparency */
|
|
144
|
+
background-color: color-mix(
|
|
145
|
+
in srgb,
|
|
146
|
+
var(--color-muted, rgb(0, 0, 0)) 40%,
|
|
147
|
+
var(--color-card, var(--color-background, #ffffff))
|
|
148
|
+
) !important;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
[data-kt-datatable] .kt-datatable-locked-left {
|
|
152
|
+
box-shadow: inset -1px 0 0 0 var(--color-border, rgba(0, 0, 0, 0.08));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
[data-kt-datatable] .kt-datatable-locked-right {
|
|
156
|
+
box-shadow: inset 1px 0 0 0 var(--color-border, rgba(0, 0, 0, 0.08));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Keep row hover/checked visual states on sticky body cells */
|
|
160
|
+
[data-kt-datatable] [data-kt-datatable-table] tbody tr:hover > .kt-datatable-locked-cell {
|
|
161
|
+
/* Match kt-table row hover bg-muted/50 without transparency */
|
|
162
|
+
background-color: color-mix(
|
|
163
|
+
in srgb,
|
|
164
|
+
var(--color-muted, rgb(0, 0, 0)) 50%,
|
|
165
|
+
var(--color-card, var(--color-background, #ffffff))
|
|
166
|
+
) !important;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
[data-kt-datatable] [data-kt-datatable-table] tbody tr.checked > .kt-datatable-locked-cell {
|
|
170
|
+
background-color: var(--color-muted, rgb(0, 0, 0)) !important;
|
|
171
|
+
}
|
|
74
172
|
}
|
|
75
173
|
|
|
76
174
|
@custom-variant kt-datatable-loading {
|