@keenthemes/ktui 1.2.4 → 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 +2551 -2817
- package/dist/ktui.min.js +1 -1
- package/dist/ktui.min.js.map +1 -1
- package/dist/styles.css +136 -40
- package/lib/cjs/components/datatable/datatable-checkbox.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-checkbox.js +34 -15
- package/lib/cjs/components/datatable/datatable-checkbox.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-contracts.d.ts +3 -3
- package/lib/cjs/components/datatable/datatable-contracts.d.ts.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 +18 -10
- 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 +40 -25
- package/lib/cjs/components/datatable/datatable-pagination-renderer.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-remote-provider.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-remote-provider.js +3 -0
- package/lib/cjs/components/datatable/datatable-remote-provider.js.map +1 -1
- package/lib/cjs/components/datatable/datatable-table-renderer.d.ts.map +1 -1
- package/lib/cjs/components/datatable/datatable-table-renderer.js +14 -6
- package/lib/cjs/components/datatable/datatable-table-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 +200 -61
- 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/components/dropdown/dropdown.d.ts +2 -2
- package/lib/cjs/components/dropdown/dropdown.d.ts.map +1 -1
- package/lib/cjs/components/dropdown/dropdown.js +68 -31
- package/lib/cjs/components/dropdown/dropdown.js.map +1 -1
- package/lib/cjs/components/input-number/index.d.ts +7 -0
- package/lib/cjs/components/input-number/index.d.ts.map +1 -0
- package/lib/cjs/components/input-number/index.js +10 -0
- package/lib/cjs/components/input-number/index.js.map +1 -0
- package/lib/cjs/components/input-number/input-number.d.ts +40 -0
- package/lib/cjs/components/input-number/input-number.d.ts.map +1 -0
- package/lib/cjs/components/input-number/input-number.js +248 -0
- package/lib/cjs/components/input-number/input-number.js.map +1 -0
- package/lib/cjs/components/input-number/types.d.ts +30 -0
- package/lib/cjs/components/input-number/types.d.ts.map +1 -0
- package/lib/cjs/components/input-number/types.js +7 -0
- package/lib/cjs/components/input-number/types.js.map +1 -0
- package/lib/cjs/components/select/config.d.ts +1 -0
- package/lib/cjs/components/select/config.d.ts.map +1 -1
- package/lib/cjs/components/select/config.js +2 -1
- package/lib/cjs/components/select/config.js.map +1 -1
- package/lib/cjs/components/select/select.d.ts +8 -1
- package/lib/cjs/components/select/select.d.ts.map +1 -1
- package/lib/cjs/components/select/select.js +14 -1
- package/lib/cjs/components/select/select.js.map +1 -1
- package/lib/cjs/components/select/tags.d.ts.map +1 -1
- package/lib/cjs/components/select/tags.js +10 -0
- package/lib/cjs/components/select/tags.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 +5 -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 +34 -15
- package/lib/esm/components/datatable/datatable-checkbox.js.map +1 -1
- package/lib/esm/components/datatable/datatable-contracts.d.ts +3 -3
- package/lib/esm/components/datatable/datatable-contracts.d.ts.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 +18 -10
- 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 +40 -25
- package/lib/esm/components/datatable/datatable-pagination-renderer.js.map +1 -1
- package/lib/esm/components/datatable/datatable-remote-provider.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-remote-provider.js +3 -0
- package/lib/esm/components/datatable/datatable-remote-provider.js.map +1 -1
- package/lib/esm/components/datatable/datatable-table-renderer.d.ts.map +1 -1
- package/lib/esm/components/datatable/datatable-table-renderer.js +14 -6
- package/lib/esm/components/datatable/datatable-table-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 +200 -61
- 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/components/dropdown/dropdown.d.ts +2 -2
- package/lib/esm/components/dropdown/dropdown.d.ts.map +1 -1
- package/lib/esm/components/dropdown/dropdown.js +68 -31
- package/lib/esm/components/dropdown/dropdown.js.map +1 -1
- package/lib/esm/components/input-number/index.d.ts +7 -0
- package/lib/esm/components/input-number/index.d.ts.map +1 -0
- package/lib/esm/components/input-number/index.js +6 -0
- package/lib/esm/components/input-number/index.js.map +1 -0
- package/lib/esm/components/input-number/input-number.d.ts +40 -0
- package/lib/esm/components/input-number/input-number.d.ts.map +1 -0
- package/lib/esm/components/input-number/input-number.js +245 -0
- package/lib/esm/components/input-number/input-number.js.map +1 -0
- package/lib/esm/components/input-number/types.d.ts +30 -0
- package/lib/esm/components/input-number/types.d.ts.map +1 -0
- package/lib/esm/components/input-number/types.js +6 -0
- package/lib/esm/components/input-number/types.js.map +1 -0
- package/lib/esm/components/select/config.d.ts +1 -0
- package/lib/esm/components/select/config.d.ts.map +1 -1
- package/lib/esm/components/select/config.js +2 -1
- package/lib/esm/components/select/config.js.map +1 -1
- package/lib/esm/components/select/select.d.ts +8 -1
- package/lib/esm/components/select/select.d.ts.map +1 -1
- package/lib/esm/components/select/select.js +14 -1
- package/lib/esm/components/select/select.js.map +1 -1
- package/lib/esm/components/select/tags.d.ts.map +1 -1
- package/lib/esm/components/select/tags.js +11 -1
- package/lib/esm/components/select/tags.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 +3 -0
- package/lib/esm/index.js.map +1 -1
- package/package.json +5 -11
- 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 +35 -27
- package/src/components/datatable/datatable-contracts.ts +3 -3
- package/src/components/datatable/datatable-layout-plugin.ts +449 -0
- package/src/components/datatable/datatable-local-provider.ts +21 -14
- package/src/components/datatable/datatable-pagination-renderer.ts +40 -29
- package/src/components/datatable/datatable-remote-provider.ts +3 -0
- package/src/components/datatable/datatable-table-renderer.ts +40 -32
- package/src/components/datatable/datatable.css +98 -0
- package/src/components/datatable/datatable.ts +223 -86
- package/src/components/datatable/index.ts +5 -0
- package/src/components/datatable/types.ts +33 -0
- package/src/components/dropdown/dropdown.ts +86 -58
- package/src/components/input/input-group.css +14 -1
- package/src/components/input-number/__tests__/input-number.test.ts +278 -0
- package/src/components/input-number/index.ts +11 -0
- package/src/components/input-number/input-number.ts +267 -0
- package/src/components/input-number/types.ts +32 -0
- package/src/components/select/__tests__/ux-behaviors.test.ts +72 -0
- package/src/components/select/config.ts +3 -1
- package/src/components/select/select.css +23 -20
- package/src/components/select/select.ts +15 -1
- package/src/components/select/tags.ts +14 -1
- package/src/index.ts +14 -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();
|
|
@@ -63,20 +66,24 @@ export class KTDataTableLocalDataProvider<
|
|
|
63
66
|
|
|
64
67
|
if (search) {
|
|
65
68
|
const searchTerm = typeof search === 'string' ? search : '';
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
data
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const searchCallback = this.options.config.search?.callback;
|
|
70
|
+
if (searchCallback) {
|
|
71
|
+
filteredData = data = searchCallback.call(
|
|
72
|
+
this,
|
|
73
|
+
data,
|
|
74
|
+
searchTerm,
|
|
75
|
+
) as T[];
|
|
76
|
+
}
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
const sortCallback = this.options.config.sort?.callback;
|
|
73
80
|
if (
|
|
74
81
|
sortField !== undefined &&
|
|
75
82
|
sortOrder !== undefined &&
|
|
76
83
|
sortOrder !== '' &&
|
|
77
|
-
typeof
|
|
84
|
+
typeof sortCallback === 'function'
|
|
78
85
|
) {
|
|
79
|
-
data =
|
|
86
|
+
data = sortCallback.call(
|
|
80
87
|
this,
|
|
81
88
|
data,
|
|
82
89
|
sortField as string,
|
|
@@ -114,7 +121,7 @@ export class KTDataTableLocalDataProvider<
|
|
|
114
121
|
const { _state, ...restConfig } = this.options.config;
|
|
115
122
|
const checksum: string = KTUtils.checksum(JSON.stringify(restConfig));
|
|
116
123
|
|
|
117
|
-
if (_state
|
|
124
|
+
if ((_state?._configChecksum ?? '') !== checksum) {
|
|
118
125
|
this.options.stateStore.patchState({ _configChecksum: checksum });
|
|
119
126
|
return true;
|
|
120
127
|
}
|
|
@@ -13,21 +13,28 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
13
13
|
public render(
|
|
14
14
|
input: KTDataTablePaginationRendererInput,
|
|
15
15
|
): KTDataTableCleanup | void {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
if (input.sizeElement) {
|
|
17
|
+
this.removeChildElements(input.sizeElement);
|
|
18
|
+
this.createPageSizeControls(input);
|
|
19
|
+
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
if (input.paginationElement) {
|
|
22
|
+
this.removeChildElements(input.paginationElement);
|
|
23
|
+
this.createPaginationControls(input);
|
|
24
|
+
}
|
|
21
25
|
|
|
22
26
|
return () => {
|
|
23
27
|
if (input.sizeElement) {
|
|
24
28
|
input.sizeElement.onchange = null;
|
|
29
|
+
this.removeChildElements(input.sizeElement);
|
|
30
|
+
}
|
|
31
|
+
if (input.paginationElement) {
|
|
32
|
+
this.removeChildElements(input.paginationElement);
|
|
25
33
|
}
|
|
26
|
-
this.removeChildElements(input.paginationElement);
|
|
27
34
|
};
|
|
28
35
|
}
|
|
29
36
|
|
|
30
|
-
private removeChildElements(container
|
|
37
|
+
private removeChildElements(container?: HTMLElement | null): void {
|
|
31
38
|
if (!container) {
|
|
32
39
|
return;
|
|
33
40
|
}
|
|
@@ -39,22 +46,21 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
39
46
|
|
|
40
47
|
private createPageSizeControls(
|
|
41
48
|
input: KTDataTablePaginationRendererInput,
|
|
42
|
-
):
|
|
49
|
+
): void {
|
|
43
50
|
if (!input.sizeElement) {
|
|
44
|
-
return
|
|
51
|
+
return;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
+
});
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
}, 100);
|
|
63
|
+
input.sizeElement.append(...options);
|
|
58
64
|
|
|
59
65
|
input.sizeElement.onchange = (event: Event) => {
|
|
60
66
|
input.reloadPageSize(
|
|
@@ -62,18 +68,12 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
62
68
|
1,
|
|
63
69
|
);
|
|
64
70
|
};
|
|
65
|
-
|
|
66
|
-
return input.sizeElement;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
private createPaginationControls(
|
|
70
74
|
input: KTDataTablePaginationRendererInput,
|
|
71
|
-
): HTMLElement {
|
|
72
|
-
if (
|
|
73
|
-
!input.infoElement ||
|
|
74
|
-
!input.paginationElement ||
|
|
75
|
-
input.dataLength === 0
|
|
76
|
-
) {
|
|
75
|
+
): HTMLElement | null {
|
|
76
|
+
if (!input.paginationElement || input.dataLength === 0) {
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -86,7 +86,12 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
86
86
|
private setPaginationInfoText(
|
|
87
87
|
input: KTDataTablePaginationRendererInput,
|
|
88
88
|
): void {
|
|
89
|
-
input.infoElement
|
|
89
|
+
if (!input.infoElement) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const infoTemplate = input.config.info ?? '{start}-{end} of {total}';
|
|
94
|
+
input.infoElement.textContent = infoTemplate
|
|
90
95
|
.replace(
|
|
91
96
|
'{start}',
|
|
92
97
|
(input.state.page - 1) * input.state.pageSize + 1 + '',
|
|
@@ -105,8 +110,14 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
105
110
|
paginationContainer: HTMLElement,
|
|
106
111
|
input: KTDataTablePaginationRendererInput,
|
|
107
112
|
): void {
|
|
113
|
+
const pagination = input.config.pagination;
|
|
114
|
+
if (!pagination) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
108
118
|
const { page: currentPage, totalPages } = input.state;
|
|
109
|
-
const { previous, next, number, more } =
|
|
119
|
+
const { previous, next, number, more } = pagination;
|
|
120
|
+
const pageMoreLimit = input.config.pageMoreLimit ?? 3;
|
|
110
121
|
|
|
111
122
|
const createButton = (
|
|
112
123
|
text: string,
|
|
@@ -135,7 +146,7 @@ export class KTDataTableDomPaginationRenderer implements KTDataTablePaginationRe
|
|
|
135
146
|
const range = this.calculatePageRange(
|
|
136
147
|
currentPage,
|
|
137
148
|
totalPages,
|
|
138
|
-
|
|
149
|
+
pageMoreLimit,
|
|
139
150
|
);
|
|
140
151
|
|
|
141
152
|
if (range.start > 1) {
|
|
@@ -137,6 +137,9 @@ export class KTDataTableRemoteDataProvider<
|
|
|
137
137
|
this.options.config.requestMethod;
|
|
138
138
|
let requestBody: RequestInit['body'] | undefined = undefined;
|
|
139
139
|
let apiEndpoint = this.options.config.apiEndpoint;
|
|
140
|
+
if (!apiEndpoint) {
|
|
141
|
+
throw new Error('KTDataTable: apiEndpoint is required for remote fetch');
|
|
142
|
+
}
|
|
140
143
|
|
|
141
144
|
if (this.abortController) {
|
|
142
145
|
this.abortController.abort();
|