jattac.libs.web.responsive-table 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/dist/Context/TableContext.d.ts +91 -0
- package/dist/Hooks/useTableDataSource.d.ts +23 -0
- package/dist/UI/DesktopView.d.ts +2 -29
- package/dist/UI/InfiniteTable.d.ts +2 -2
- package/dist/UI/MobileView.d.ts +2 -25
- package/dist/UI/ResponsiveTable.d.ts +31 -2
- package/dist/UI/TableBodyCell.d.ts +9 -0
- package/dist/UI/TableBodyRow.d.ts +21 -0
- package/dist/UI/TableHeaderCell.d.ts +8 -0
- package/dist/UI/TableSentinel.d.ts +7 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.es.js +1207 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.js +374 -264
- package/dist/index.js.map +1 -1
- package/docs/configuration.md +23 -0
- package/docs/examples.md +23 -24
- package/package.json +1 -1
- package/dist/Data/IGroupedRowDefinition.d.ts +0 -10
- package/dist/Data/IInfiniteScrollTypes.d.ts +0 -15
- package/dist/Plugins/GroupingPlugin.d.ts +0 -17
- package/dist/Plugins/HighlightPlugin.d.ts +0 -23
- package/dist/UI/ResponsiveTable.stories.d.ts +0 -17
- /package/dist/{Plugins/SelectionPlugin.test.d.ts → UI/SmartDataSource.test.d.ts} +0 -0
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,1207 @@
|
|
|
1
|
+
import React, { createContext, useCallback, useMemo, useContext, useRef, useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
var styles$2 = {"responsiveTable":"ResponsiveTable-module_responsiveTable__4y-Od","tableContainer":"ResponsiveTable-module_tableContainer__VjWjH","cardContainer":"ResponsiveTable-module_cardContainer__Het4h","card":"ResponsiveTable-module_card__b-U2v","card-header":"ResponsiveTable-module_card-header__Ttk51","card-row":"ResponsiveTable-module_card-row__qvIUJ","card-label":"ResponsiveTable-module_card-label__v9L71","card-value":"ResponsiveTable-module_card-value__BO-c-","clickableRow":"ResponsiveTable-module_clickableRow__0kjWm","clickableHeader":"ResponsiveTable-module_clickableHeader__xHQhF","sortable":"ResponsiveTable-module_sortable__yvA60","sorted-asc":"ResponsiveTable-module_sorted-asc__jzOIa","sorted-desc":"ResponsiveTable-module_sorted-desc__7WCFK","headerInnerWrapper":"ResponsiveTable-module_headerInnerWrapper__3VAhD","headerContent":"ResponsiveTable-module_headerContent__ODMzS","sortIcon":"ResponsiveTable-module_sortIcon__A9WtD","footerCell":"ResponsiveTable-module_footerCell__8H-uG","clickableFooterCell":"ResponsiveTable-module_clickableFooterCell__WB9Ss","footerCard":"ResponsiveTable-module_footerCard__-NE2M","footer-card-row":"ResponsiveTable-module_footer-card-row__Vv6Ur","selectedRow":"ResponsiveTable-module_selectedRow__-JyNW","animatedRow":"ResponsiveTable-module_animatedRow__SFjrJ","fadeInUp":"ResponsiveTable-module_fadeInUp__jMCS7","skeleton":"ResponsiveTable-module_skeleton__XxsXW","shimmer":"ResponsiveTable-module_shimmer__H8PhC","noDataWrapper":"ResponsiveTable-module_noDataWrapper__Rj-k3","noData":"ResponsiveTable-module_noData__IpwNq","spinner":"ResponsiveTable-module_spinner__Pn-3D","spin":"ResponsiveTable-module_spin__i3NHn","infoContainer":"ResponsiveTable-module_infoContainer__b9IF5","stickyHeader":"ResponsiveTable-module_stickyHeader__-jjN-","internalStickyHeader":"ResponsiveTable-module_internalStickyHeader__idiJY"};
|
|
4
|
+
|
|
5
|
+
/******************************************************************************
|
|
6
|
+
Copyright (c) Microsoft Corporation.
|
|
7
|
+
|
|
8
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
9
|
+
purpose with or without fee is hereby granted.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
12
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
13
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
14
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
15
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
16
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
17
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
18
|
+
***************************************************************************** */
|
|
19
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
function __rest(s, e) {
|
|
23
|
+
var t = {};
|
|
24
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
25
|
+
t[p] = s[p];
|
|
26
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
27
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
28
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
29
|
+
t[p[i]] = s[p[i]];
|
|
30
|
+
}
|
|
31
|
+
return t;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
35
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
36
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
37
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
38
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
39
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
40
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
45
|
+
var e = new Error(message);
|
|
46
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
const TableContext = createContext(undefined);
|
|
51
|
+
const useTableContext = () => {
|
|
52
|
+
const context = useContext(TableContext);
|
|
53
|
+
if (!context) {
|
|
54
|
+
throw new Error('useTableContext must be used within a TableProvider');
|
|
55
|
+
}
|
|
56
|
+
return context;
|
|
57
|
+
};
|
|
58
|
+
function TableProvider({ children, value }) {
|
|
59
|
+
const { processedData, activePlugins, selectionProps, } = value;
|
|
60
|
+
const getRawColumnDefinition = useCallback((columnDefinition) => {
|
|
61
|
+
if (typeof columnDefinition === 'function') {
|
|
62
|
+
if (processedData.length === 0) {
|
|
63
|
+
return { displayLabel: '', cellRenderer: () => '' };
|
|
64
|
+
}
|
|
65
|
+
return columnDefinition(processedData[0], 0);
|
|
66
|
+
}
|
|
67
|
+
return columnDefinition;
|
|
68
|
+
}, [processedData]);
|
|
69
|
+
const getColumnDefinition = useCallback((columnDefinition, rowIndex) => {
|
|
70
|
+
if (processedData.length === 0) {
|
|
71
|
+
return { displayLabel: '', cellRenderer: () => '' };
|
|
72
|
+
}
|
|
73
|
+
return columnDefinition instanceof Function ? columnDefinition(processedData[0], rowIndex) : columnDefinition;
|
|
74
|
+
}, [processedData]);
|
|
75
|
+
const onHeaderClickCallback = useCallback((colDef) => {
|
|
76
|
+
var _a;
|
|
77
|
+
const rawColumnDefinition = getRawColumnDefinition(colDef);
|
|
78
|
+
return (_a = rawColumnDefinition.interactivity) === null || _a === void 0 ? void 0 : _a.onHeaderClick;
|
|
79
|
+
}, [getRawColumnDefinition]);
|
|
80
|
+
const getClickableHeaderClassName = useCallback((onHeaderClick, colDef) => {
|
|
81
|
+
var _a;
|
|
82
|
+
const rawColumnDefinition = getRawColumnDefinition(colDef);
|
|
83
|
+
return onHeaderClick
|
|
84
|
+
? ((_a = rawColumnDefinition.interactivity) === null || _a === void 0 ? void 0 : _a.className) || styles$2.clickableHeader
|
|
85
|
+
: '';
|
|
86
|
+
}, [getRawColumnDefinition]);
|
|
87
|
+
const getHeaderProps = useCallback((colDef) => {
|
|
88
|
+
const headerProps = {};
|
|
89
|
+
activePlugins.forEach((plugin) => {
|
|
90
|
+
if (plugin.getHeaderProps) {
|
|
91
|
+
Object.assign(headerProps, plugin.getHeaderProps(getRawColumnDefinition(colDef)));
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return headerProps;
|
|
95
|
+
}, [activePlugins, getRawColumnDefinition]);
|
|
96
|
+
const getRowId = useCallback((row, index) => {
|
|
97
|
+
if (selectionProps && selectionProps.rowIdKey) {
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
+
return row[selectionProps.rowIdKey];
|
|
100
|
+
}
|
|
101
|
+
return index;
|
|
102
|
+
}, [selectionProps]);
|
|
103
|
+
const getRowProps = useCallback((row) => {
|
|
104
|
+
const rowProps = {};
|
|
105
|
+
const clickHandlers = [];
|
|
106
|
+
activePlugins.forEach((plugin) => {
|
|
107
|
+
if (plugin.getRowProps) {
|
|
108
|
+
const props = plugin.getRowProps(row);
|
|
109
|
+
if (props.className) {
|
|
110
|
+
rowProps.className = `${rowProps.className || ''} ${props.className}`.trim();
|
|
111
|
+
}
|
|
112
|
+
if (props.onClick) {
|
|
113
|
+
clickHandlers.push(props.onClick);
|
|
114
|
+
}
|
|
115
|
+
const rest = __rest(props, []);
|
|
116
|
+
Object.assign(rowProps, rest);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
if (clickHandlers.length > 0) {
|
|
120
|
+
rowProps.onClick = (e) => {
|
|
121
|
+
clickHandlers.forEach(handler => handler(e));
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return rowProps;
|
|
125
|
+
}, [activePlugins]);
|
|
126
|
+
const renderCell = useCallback((content, row, colDef) => {
|
|
127
|
+
let processedContent = content;
|
|
128
|
+
activePlugins.forEach((plugin) => {
|
|
129
|
+
if (plugin.renderCell) {
|
|
130
|
+
processedContent = plugin.renderCell(processedContent, row, colDef);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return processedContent;
|
|
134
|
+
}, [activePlugins]);
|
|
135
|
+
const contextValue = useMemo(() => (Object.assign(Object.assign({}, value), { currentData: processedData, getRawColumnDefinition,
|
|
136
|
+
getColumnDefinition,
|
|
137
|
+
onHeaderClickCallback,
|
|
138
|
+
getClickableHeaderClassName,
|
|
139
|
+
getHeaderProps,
|
|
140
|
+
getRowProps,
|
|
141
|
+
getRowId,
|
|
142
|
+
renderCell })), [
|
|
143
|
+
value,
|
|
144
|
+
processedData,
|
|
145
|
+
getRawColumnDefinition,
|
|
146
|
+
getColumnDefinition,
|
|
147
|
+
onHeaderClickCallback,
|
|
148
|
+
getClickableHeaderClassName,
|
|
149
|
+
getHeaderProps,
|
|
150
|
+
getRowProps,
|
|
151
|
+
getRowId,
|
|
152
|
+
renderCell,
|
|
153
|
+
]);
|
|
154
|
+
return (React.createElement(TableContext.Provider, { value: contextValue }, children));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function TableHeaderCell(props) {
|
|
158
|
+
const { columnDefinition, colIndex } = props;
|
|
159
|
+
const { onHeaderClickCallback, getClickableHeaderClassName, getHeaderProps, getRawColumnDefinition, getColumnDefinition, } = useTableContext();
|
|
160
|
+
const onHeaderClick = onHeaderClickCallback(columnDefinition);
|
|
161
|
+
const clickableHeaderClassName = getClickableHeaderClassName(onHeaderClick, columnDefinition);
|
|
162
|
+
const headerProps = getHeaderProps(columnDefinition);
|
|
163
|
+
const combinedClassName = `${clickableHeaderClassName} ${headerProps.className ? styles$2[headerProps.className] : ''}`.trim();
|
|
164
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
165
|
+
const { className } = headerProps, restHeaderProps = __rest(headerProps, ["className"]);
|
|
166
|
+
return (React.createElement("th", Object.assign({ key: colIndex, className: combinedClassName }, restHeaderProps, { onClick: onHeaderClick ? () => onHeaderClick(getRawColumnDefinition(columnDefinition).interactivity.id) : restHeaderProps.onClick }),
|
|
167
|
+
React.createElement("div", { className: styles$2.headerInnerWrapper },
|
|
168
|
+
React.createElement("div", { className: styles$2.headerContent }, getColumnDefinition(columnDefinition, 0).displayLabel),
|
|
169
|
+
React.createElement("span", { className: styles$2.sortIcon }))));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function TableBodyCell(props) {
|
|
173
|
+
const { row, rowIndex, columnDefinition } = props;
|
|
174
|
+
const { getColumnDefinition, renderCell } = useTableContext();
|
|
175
|
+
const colDef = getColumnDefinition(columnDefinition, rowIndex);
|
|
176
|
+
const cellContent = colDef.cellRenderer(row);
|
|
177
|
+
return (React.createElement(React.Fragment, null, renderCell(cellContent, row, colDef)));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function TableBodyRow(props) {
|
|
181
|
+
const { row, rowIndex, columnDefinitions, onRowClick, selectionProps, animationProps, } = props;
|
|
182
|
+
const { getRowProps } = useTableContext();
|
|
183
|
+
const rowProps = getRowProps(row);
|
|
184
|
+
const isClickable = onRowClick || selectionProps;
|
|
185
|
+
const pluginOnClick = rowProps.onClick;
|
|
186
|
+
return (React.createElement("tr", { className: `${isClickable ? styles$2.clickableRow : ''} ${(animationProps === null || animationProps === void 0 ? void 0 : animationProps.animateOnLoad) ? styles$2.animatedRow : ''} ${rowProps.className || ''}`.trim(), style: { animationDelay: `${rowIndex * 0.05}s` }, "aria-selected": rowProps['aria-selected'], onClick: (e) => {
|
|
187
|
+
if (pluginOnClick) {
|
|
188
|
+
pluginOnClick(e);
|
|
189
|
+
}
|
|
190
|
+
if (onRowClick) {
|
|
191
|
+
onRowClick(row);
|
|
192
|
+
}
|
|
193
|
+
} }, columnDefinitions.map((columnDefinition, colIndex) => (React.createElement("td", { key: colIndex },
|
|
194
|
+
React.createElement(TableBodyCell, { row: row, rowIndex: rowIndex, columnDefinition: columnDefinition }))))));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const TableSentinel = ({ onIntersect, isLoading }) => {
|
|
198
|
+
const sentinelRef = useRef(null);
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
if (isLoading)
|
|
201
|
+
return;
|
|
202
|
+
const observer = new IntersectionObserver((entries) => {
|
|
203
|
+
if (entries[0].isIntersecting) {
|
|
204
|
+
onIntersect();
|
|
205
|
+
}
|
|
206
|
+
}, {
|
|
207
|
+
root: null, // use the viewport
|
|
208
|
+
rootMargin: '200px', // start loading 200px before reaching the end
|
|
209
|
+
threshold: 0.1,
|
|
210
|
+
});
|
|
211
|
+
const currentSentinel = sentinelRef.current;
|
|
212
|
+
if (currentSentinel) {
|
|
213
|
+
observer.observe(currentSentinel);
|
|
214
|
+
}
|
|
215
|
+
return () => {
|
|
216
|
+
if (currentSentinel) {
|
|
217
|
+
observer.unobserve(currentSentinel);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}, [onIntersect, isLoading]);
|
|
221
|
+
return React.createElement("div", { ref: sentinelRef, style: { height: '1px' }, "aria-hidden": "true" });
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
function DesktopView(props) {
|
|
225
|
+
const { maxHeight, isHeaderSticky, tableContainerRef, headerRef, footerRows, renderPluginFooters, onScroll, } = props;
|
|
226
|
+
const { visibleColumns, originalColumnDefinitions, currentData, getRawColumnDefinition, onRowClick, selectionProps, animationProps, pagination, } = useTableContext();
|
|
227
|
+
const getEffectiveColSpan = useCallback((footerCol, startIndex) => {
|
|
228
|
+
const originalSpan = footerCol.colSpan || 1;
|
|
229
|
+
const endIndex = startIndex + originalSpan;
|
|
230
|
+
let visibleCount = 0;
|
|
231
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
232
|
+
const col = originalColumnDefinitions[i];
|
|
233
|
+
if (col && getRawColumnDefinition(col).visible !== false) {
|
|
234
|
+
visibleCount++;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return visibleCount;
|
|
238
|
+
}, [originalColumnDefinitions, getRawColumnDefinition]);
|
|
239
|
+
const tableFooter = useMemo(() => {
|
|
240
|
+
if (!footerRows || footerRows.length === 0) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
return (React.createElement("tfoot", null, footerRows.map((row, rowIndex) => {
|
|
244
|
+
let currentOriginalIndex = 0;
|
|
245
|
+
return (React.createElement("tr", { key: rowIndex }, row.columns.map((col, colIndex) => {
|
|
246
|
+
const effectiveColSpan = getEffectiveColSpan(col, currentOriginalIndex);
|
|
247
|
+
currentOriginalIndex += (col.colSpan || 1);
|
|
248
|
+
if (effectiveColSpan === 0)
|
|
249
|
+
return null;
|
|
250
|
+
return (React.createElement("td", { key: colIndex, colSpan: effectiveColSpan, className: `${styles$2.footerCell} ${col.className || ''} ${col.onCellClick ? styles$2.clickableFooterCell : ''}`, onClick: col.onCellClick }, col.cellRenderer()));
|
|
251
|
+
})));
|
|
252
|
+
})));
|
|
253
|
+
}, [footerRows, getEffectiveColSpan]);
|
|
254
|
+
const useFixedHeaders = !!maxHeight;
|
|
255
|
+
const fixedHeadersStyle = useFixedHeaders
|
|
256
|
+
? { maxHeight, overflowY: 'auto' }
|
|
257
|
+
: {};
|
|
258
|
+
const headerClassName = useFixedHeaders
|
|
259
|
+
? styles$2.internalStickyHeader
|
|
260
|
+
: (isHeaderSticky ? styles$2.stickyHeader : '');
|
|
261
|
+
return (React.createElement("div", { className: styles$2.tableContainer, style: fixedHeadersStyle, ref: tableContainerRef, onScroll: onScroll },
|
|
262
|
+
React.createElement("table", { className: styles$2['responsiveTable'] },
|
|
263
|
+
React.createElement("thead", { ref: headerRef, className: headerClassName },
|
|
264
|
+
React.createElement("tr", null, visibleColumns.map((columnDefinition, colIndex) => (React.createElement(TableHeaderCell, { key: colIndex, columnDefinition: columnDefinition, colIndex: colIndex }))))),
|
|
265
|
+
React.createElement("tbody", null, currentData.map((row, rowIndex) => (React.createElement(TableBodyRow, { key: rowIndex, row: row, rowIndex: rowIndex, columnDefinitions: visibleColumns, onRowClick: onRowClick, selectionProps: selectionProps, animationProps: animationProps })))),
|
|
266
|
+
tableFooter),
|
|
267
|
+
(pagination === null || pagination === void 0 ? void 0 : pagination.hasMore) && (React.createElement(TableSentinel, { onIntersect: () => pagination.loadNextPage(), isLoading: pagination.isFetchingMore })),
|
|
268
|
+
(pagination === null || pagination === void 0 ? void 0 : pagination.isFetchingMore) && (React.createElement("div", { className: styles$2.infoContainer },
|
|
269
|
+
React.createElement("div", { className: styles$2.spinner }),
|
|
270
|
+
React.createElement("span", null, "Loading more items..."))),
|
|
271
|
+
renderPluginFooters()));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function MobileView(props) {
|
|
275
|
+
const { mobileFooter } = props;
|
|
276
|
+
const { currentData, visibleColumns, onRowClick, selectionProps, animationProps, getRowProps, getRowId, getColumnDefinition, onHeaderClickCallback, getClickableHeaderClassName, pagination, } = useTableContext();
|
|
277
|
+
const isClickable = onRowClick || selectionProps;
|
|
278
|
+
return (React.createElement("div", { className: styles$2.cardContainer },
|
|
279
|
+
currentData.map((row, rowIndex) => {
|
|
280
|
+
const rowProps = getRowProps(row);
|
|
281
|
+
const pluginOnClick = rowProps.onClick;
|
|
282
|
+
return (React.createElement("div", { key: getRowId(row, rowIndex), className: `${styles$2.card} ${isClickable ? styles$2.clickableRow : ''} ${(animationProps === null || animationProps === void 0 ? void 0 : animationProps.animateOnLoad) ? styles$2.animatedRow : ''} ${rowProps.className || ''}`.trim(), style: { animationDelay: `${rowIndex * 0.05}s` }, "aria-selected": rowProps['aria-selected'], onClick: (e) => {
|
|
283
|
+
if (pluginOnClick)
|
|
284
|
+
pluginOnClick(e);
|
|
285
|
+
if (onRowClick)
|
|
286
|
+
onRowClick(row);
|
|
287
|
+
} },
|
|
288
|
+
React.createElement("div", { className: styles$2['card-body'] }, visibleColumns.map((columnDefinition, colIndex) => {
|
|
289
|
+
const colDef = getColumnDefinition(columnDefinition, rowIndex);
|
|
290
|
+
const onHeaderClick = onHeaderClickCallback(columnDefinition);
|
|
291
|
+
const clickableHeaderClassName = getClickableHeaderClassName(onHeaderClick, columnDefinition);
|
|
292
|
+
return (React.createElement("div", { key: colIndex, className: styles$2['card-row'] },
|
|
293
|
+
React.createElement("p", { className: styles$2['card-row-content'] },
|
|
294
|
+
React.createElement("span", { className: `${styles$2['card-label']} ${clickableHeaderClassName}`, onClick: (e) => {
|
|
295
|
+
if (onHeaderClick) {
|
|
296
|
+
e.stopPropagation();
|
|
297
|
+
onHeaderClick(colDef.interactivity.id);
|
|
298
|
+
}
|
|
299
|
+
} }, colDef.displayLabel),
|
|
300
|
+
React.createElement("span", { className: styles$2['card-value'] },
|
|
301
|
+
React.createElement(TableBodyCell, { row: row, rowIndex: rowIndex, columnDefinition: columnDefinition })))));
|
|
302
|
+
}))));
|
|
303
|
+
}),
|
|
304
|
+
(pagination === null || pagination === void 0 ? void 0 : pagination.hasMore) && (React.createElement(TableSentinel, { onIntersect: () => pagination.loadNextPage(), isLoading: pagination.isFetchingMore })),
|
|
305
|
+
(pagination === null || pagination === void 0 ? void 0 : pagination.isFetchingMore) && (React.createElement("div", { className: styles$2.infoContainer },
|
|
306
|
+
React.createElement("div", { className: styles$2.spinner }),
|
|
307
|
+
React.createElement("span", null, "Loading more items..."))),
|
|
308
|
+
mobileFooter));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function SkeletonView(props) {
|
|
312
|
+
const { isMobile, columnDefinitions } = props;
|
|
313
|
+
const skeletonRowCount = 5;
|
|
314
|
+
const columnCount = columnDefinitions.length;
|
|
315
|
+
if (isMobile) {
|
|
316
|
+
return (React.createElement("div", null, [...Array(skeletonRowCount)].map((_, i) => (React.createElement("div", { key: i, className: styles$2.skeletonCard }, [...Array(columnCount)].map((_, j) => (React.createElement("div", { key: j, className: `${styles$2.skeleton} ${styles$2.skeletonText}`, style: { marginBottom: '0.5rem' } }))))))));
|
|
317
|
+
}
|
|
318
|
+
return (React.createElement("table", { className: styles$2.responsiveTable },
|
|
319
|
+
React.createElement("thead", null,
|
|
320
|
+
React.createElement("tr", null, [...Array(columnCount)].map((_, i) => (React.createElement("th", { key: i },
|
|
321
|
+
React.createElement("div", { className: `${styles$2.skeleton} ${styles$2.skeletonText}` })))))),
|
|
322
|
+
React.createElement("tbody", null, [...Array(skeletonRowCount)].map((_, i) => (React.createElement("tr", { key: i }, [...Array(columnCount)].map((_, j) => (React.createElement("td", { key: j },
|
|
323
|
+
React.createElement("div", { className: `${styles$2.skeleton} ${styles$2.skeletonText}` }))))))))));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
var styles$1 = {"spinner":"LoadingSpinner-module_spinner__F9V3x"};
|
|
327
|
+
|
|
328
|
+
var styles = {"infoContainer":"NoMoreDataMessage-module_infoContainer__dk1r5","noMoreData":"NoMoreDataMessage-module_noMoreData__ATuIg"};
|
|
329
|
+
|
|
330
|
+
const LoadingSpinner = () => {
|
|
331
|
+
return (React.createElement("div", { className: styles.infoContainer },
|
|
332
|
+
React.createElement("div", { className: styles$1.spinner }),
|
|
333
|
+
React.createElement("span", null, "Loading more items...")));
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const NoMoreDataMessage = () => {
|
|
337
|
+
return (React.createElement("div", { className: `${styles.infoContainer} ${styles.noMoreData}` },
|
|
338
|
+
React.createElement("p", null, "You've reached the end!")));
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
function debounce(func, delay) {
|
|
342
|
+
let timeout;
|
|
343
|
+
return function (...args) {
|
|
344
|
+
clearTimeout(timeout);
|
|
345
|
+
timeout = setTimeout(() => {
|
|
346
|
+
func.apply(this, args);
|
|
347
|
+
}, delay);
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const useResponsiveTable = (props) => {
|
|
351
|
+
const { mobileBreakpoint = 600, enablePageLevelStickyHeader = true, maxHeight, headerRef, scrollableRef } = props;
|
|
352
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
353
|
+
const [isHeaderSticky, setIsHeaderSticky] = useState(false);
|
|
354
|
+
const handleResize = useCallback(() => {
|
|
355
|
+
setIsMobile(window.innerWidth <= mobileBreakpoint);
|
|
356
|
+
}, [mobileBreakpoint]);
|
|
357
|
+
const handleScroll = useCallback((currentTarget) => {
|
|
358
|
+
// Page-level sticky header logic
|
|
359
|
+
if (enablePageLevelStickyHeader && !maxHeight && (headerRef === null || headerRef === void 0 ? void 0 : headerRef.current)) {
|
|
360
|
+
const { top } = headerRef.current.getBoundingClientRect();
|
|
361
|
+
const sticky = top <= 0;
|
|
362
|
+
if (sticky !== isHeaderSticky) {
|
|
363
|
+
setIsHeaderSticky(sticky);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}, [enablePageLevelStickyHeader, maxHeight, headerRef, isHeaderSticky]);
|
|
367
|
+
const debouncedScrollHandler = useRef(debounce(handleScroll, 200)).current;
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
handleResize(); // Initial check
|
|
370
|
+
window.addEventListener('resize', handleResize);
|
|
371
|
+
const scrollTarget = (scrollableRef === null || scrollableRef === void 0 ? void 0 : scrollableRef.current) || window;
|
|
372
|
+
if (enablePageLevelStickyHeader || maxHeight) { // Only add scroll listener if sticky header or internal scroll is enabled
|
|
373
|
+
scrollTarget.addEventListener('scroll', (e) => debouncedScrollHandler(e.currentTarget));
|
|
374
|
+
}
|
|
375
|
+
return () => {
|
|
376
|
+
window.removeEventListener('resize', handleResize);
|
|
377
|
+
if (enablePageLevelStickyHeader || maxHeight) {
|
|
378
|
+
scrollTarget.removeEventListener('scroll', (e) => debouncedScrollHandler(e.currentTarget));
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}, [handleResize, debouncedScrollHandler, enablePageLevelStickyHeader, maxHeight, scrollableRef]);
|
|
382
|
+
return { isMobile, isHeaderSticky, debouncedScrollHandler: debouncedScrollHandler };
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
class FilterPlugin {
|
|
386
|
+
constructor() {
|
|
387
|
+
this.id = 'filter';
|
|
388
|
+
this.filterText = '';
|
|
389
|
+
this.debounceTimeout = null;
|
|
390
|
+
this.onPluginInit = (api) => {
|
|
391
|
+
this.api = api;
|
|
392
|
+
};
|
|
393
|
+
this.renderHeader = () => {
|
|
394
|
+
var _a;
|
|
395
|
+
if (!((_a = this.api.filterProps) === null || _a === void 0 ? void 0 : _a.showFilter)) {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
return (React.createElement("div", { style: { marginBottom: '1rem' } },
|
|
399
|
+
React.createElement("input", { type: "text", placeholder: this.api.filterProps.filterPlaceholder || "Search...", onChange: this.handleFilterChange, className: this.api.filterProps.className, style: {
|
|
400
|
+
padding: '0.5rem',
|
|
401
|
+
border: '1px solid #ccc',
|
|
402
|
+
borderRadius: '4px',
|
|
403
|
+
} })));
|
|
404
|
+
};
|
|
405
|
+
this.processData = (data) => {
|
|
406
|
+
if (!this.filterText || !this.api.columnDefinitions) {
|
|
407
|
+
return data;
|
|
408
|
+
}
|
|
409
|
+
const lowercasedFilter = this.filterText.toLowerCase();
|
|
410
|
+
return data.filter((row) => {
|
|
411
|
+
return this.api.columnDefinitions.some((colDef) => {
|
|
412
|
+
// If colDef is a function, it won't have getFilterableValue, so skip it.
|
|
413
|
+
if (typeof colDef === 'function') {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
// Now we know colDef is an object (IResponsiveTableColumnDefinition<TData>)
|
|
417
|
+
const typedColDef = colDef;
|
|
418
|
+
// Use a type guard to check if getFilterableValue exists on this branch of the union
|
|
419
|
+
if ('getFilterableValue' in typedColDef && typedColDef.getFilterableValue) {
|
|
420
|
+
const value = typedColDef.getFilterableValue(row);
|
|
421
|
+
return value === null || value === void 0 ? void 0 : value.toString().toLowerCase().includes(lowercasedFilter);
|
|
422
|
+
}
|
|
423
|
+
return false; // If getFilterableValue is not present or not a function
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
};
|
|
427
|
+
this.renderCell = (content,
|
|
428
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
429
|
+
_row,
|
|
430
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
431
|
+
_column) => {
|
|
432
|
+
if (!this.filterText || typeof content !== 'string') {
|
|
433
|
+
return content;
|
|
434
|
+
}
|
|
435
|
+
const parts = content.split(new RegExp(`(${this.filterText})`, 'gi'));
|
|
436
|
+
return (React.createElement("span", null, parts.map((part, i) => part.toLowerCase() === this.filterText.toLowerCase() ? (React.createElement("strong", { key: i }, part)) : (part))));
|
|
437
|
+
};
|
|
438
|
+
this.handleFilterChange = (e) => {
|
|
439
|
+
const currentFilterText = e.target.value;
|
|
440
|
+
if (this.debounceTimeout) {
|
|
441
|
+
clearTimeout(this.debounceTimeout);
|
|
442
|
+
}
|
|
443
|
+
this.debounceTimeout = setTimeout(() => {
|
|
444
|
+
this.filterText = currentFilterText;
|
|
445
|
+
this.api.forceUpdate();
|
|
446
|
+
}, 300);
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
class SelectionPlugin {
|
|
452
|
+
constructor() {
|
|
453
|
+
this.id = 'selection';
|
|
454
|
+
// Internal state for uncontrolled mode
|
|
455
|
+
this.internalSelectedIds = new Set();
|
|
456
|
+
this.onPluginInit = (api) => {
|
|
457
|
+
var _a;
|
|
458
|
+
this.api = api;
|
|
459
|
+
// For uncontrolled mode, initialize with selectedItems if provided on first render
|
|
460
|
+
if (((_a = this.api.selectionProps) === null || _a === void 0 ? void 0 : _a.selectedItems) && this.internalSelectedIds.size === 0) {
|
|
461
|
+
const { selectedItems, rowIdKey } = this.api.selectionProps;
|
|
462
|
+
if (selectedItems && rowIdKey) {
|
|
463
|
+
const initialIds = selectedItems.map(item => item[rowIdKey]);
|
|
464
|
+
this.internalSelectedIds = new Set(initialIds);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
this.getRowId = (row) => {
|
|
469
|
+
const { rowIdKey } = this.api.selectionProps;
|
|
470
|
+
return row[rowIdKey];
|
|
471
|
+
};
|
|
472
|
+
this.isControlled = () => {
|
|
473
|
+
var _a;
|
|
474
|
+
return ((_a = this.api.selectionProps) === null || _a === void 0 ? void 0 : _a.selectedItems) !== undefined;
|
|
475
|
+
};
|
|
476
|
+
this.getSelectedIds = () => {
|
|
477
|
+
if (this.isControlled()) {
|
|
478
|
+
const { selectedItems, rowIdKey } = this.api.selectionProps;
|
|
479
|
+
return new Set((selectedItems === null || selectedItems === void 0 ? void 0 : selectedItems.map(item => item[rowIdKey])) || []);
|
|
480
|
+
}
|
|
481
|
+
return this.internalSelectedIds;
|
|
482
|
+
};
|
|
483
|
+
this.handleRowClick = (row) => {
|
|
484
|
+
const { onSelectionChange, mode = 'multiple', rowIdKey } = this.api.selectionProps;
|
|
485
|
+
const currentSelectedIds = this.getSelectedIds();
|
|
486
|
+
const newSelectedIds = new Set(currentSelectedIds);
|
|
487
|
+
const rowId = this.getRowId(row);
|
|
488
|
+
if (mode === 'single') {
|
|
489
|
+
if (newSelectedIds.has(rowId)) {
|
|
490
|
+
newSelectedIds.clear();
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
newSelectedIds.clear();
|
|
494
|
+
newSelectedIds.add(rowId);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
else { // multiple
|
|
498
|
+
if (newSelectedIds.has(rowId)) {
|
|
499
|
+
newSelectedIds.delete(rowId);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
newSelectedIds.add(rowId);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (!this.isControlled()) {
|
|
506
|
+
this.internalSelectedIds = newSelectedIds;
|
|
507
|
+
}
|
|
508
|
+
const allData = this.api.getData();
|
|
509
|
+
const selectedItems = allData.filter(item => newSelectedIds.has(item[rowIdKey]));
|
|
510
|
+
onSelectionChange(selectedItems);
|
|
511
|
+
// In uncontrolled mode, we need to force a re-render to show the selection change
|
|
512
|
+
if (!this.isControlled()) {
|
|
513
|
+
this.api.forceUpdate();
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
this.getRowProps = (row) => {
|
|
517
|
+
const { selectedRowClassName } = this.api.selectionProps || {};
|
|
518
|
+
const selectedIds = this.getSelectedIds();
|
|
519
|
+
const rowId = this.getRowId(row);
|
|
520
|
+
const isSelected = selectedIds.has(rowId);
|
|
521
|
+
const combinedClassName = [
|
|
522
|
+
isSelected ? styles$2.selectedRow : '',
|
|
523
|
+
isSelected ? selectedRowClassName : ''
|
|
524
|
+
].filter(Boolean).join(' ');
|
|
525
|
+
return {
|
|
526
|
+
className: combinedClassName,
|
|
527
|
+
onClick: () => this.handleRowClick(row),
|
|
528
|
+
'aria-selected': isSelected,
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Type-safe sort comparer helpers
|
|
535
|
+
const createSortComparers = () => ({
|
|
536
|
+
numeric: (key) => (a, b, direction) => {
|
|
537
|
+
const numA = parseFloat(String(a[key]));
|
|
538
|
+
const numB = parseFloat(String(b[key]));
|
|
539
|
+
const aIsNaN = isNaN(numA);
|
|
540
|
+
const bIsNaN = isNaN(numB);
|
|
541
|
+
if (aIsNaN && bIsNaN)
|
|
542
|
+
return 0;
|
|
543
|
+
if (aIsNaN)
|
|
544
|
+
return 1; // Put non-numbers at the end
|
|
545
|
+
if (bIsNaN)
|
|
546
|
+
return -1;
|
|
547
|
+
return direction === 'asc' ? numA - numB : numB - numA;
|
|
548
|
+
},
|
|
549
|
+
caseInsensitiveString: (key) => (a, b, direction) => {
|
|
550
|
+
var _a, _b;
|
|
551
|
+
const valA = String((_a = a[key]) !== null && _a !== void 0 ? _a : '').toLowerCase();
|
|
552
|
+
const valB = String((_b = b[key]) !== null && _b !== void 0 ? _b : '').toLowerCase();
|
|
553
|
+
if (valA < valB)
|
|
554
|
+
return direction === 'asc' ? -1 : 1;
|
|
555
|
+
if (valA > valB)
|
|
556
|
+
return direction === 'asc' ? 1 : -1;
|
|
557
|
+
return 0;
|
|
558
|
+
},
|
|
559
|
+
date: (key) => (a, b, direction) => {
|
|
560
|
+
const dateA = new Date(String(a[key])).getTime();
|
|
561
|
+
const dateB = new Date(String(b[key])).getTime();
|
|
562
|
+
const aIsNaN = isNaN(dateA);
|
|
563
|
+
const bIsNaN = isNaN(dateB);
|
|
564
|
+
if (aIsNaN && bIsNaN)
|
|
565
|
+
return 0;
|
|
566
|
+
if (aIsNaN)
|
|
567
|
+
return 1; // Put invalid dates at the end
|
|
568
|
+
if (bIsNaN)
|
|
569
|
+
return -1;
|
|
570
|
+
return direction === 'asc' ? dateA - dateB : dateB - dateA;
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
class SortPlugin {
|
|
574
|
+
constructor(options) {
|
|
575
|
+
var _a, _b;
|
|
576
|
+
this.id = 'sort';
|
|
577
|
+
this.comparers = createSortComparers();
|
|
578
|
+
this.onPluginInit = (api) => {
|
|
579
|
+
this.api = api;
|
|
580
|
+
};
|
|
581
|
+
this.processData = (data) => {
|
|
582
|
+
if (!this.sortColumn || !this.sortDirection) {
|
|
583
|
+
return data;
|
|
584
|
+
}
|
|
585
|
+
const columnDef = this.api.columnDefinitions.find((col) => (typeof col === 'object' && col.columnId === this.sortColumn));
|
|
586
|
+
if (!columnDef) {
|
|
587
|
+
return data;
|
|
588
|
+
}
|
|
589
|
+
const sortedData = [...data].sort((a, b) => {
|
|
590
|
+
if ('sortComparer' in columnDef && columnDef.sortComparer) {
|
|
591
|
+
if (columnDef.sortComparer.length < 3) {
|
|
592
|
+
console.warn(`The custom sortComparer for column '${this.sortColumn}' should accept all three parameters (a, b, direction) to ensure correct sorting behavior. You provided a function with ${columnDef.sortComparer.length} parameters.`);
|
|
593
|
+
}
|
|
594
|
+
return columnDef.sortComparer(a, b, this.sortDirection);
|
|
595
|
+
}
|
|
596
|
+
if ('getSortableValue' in columnDef && columnDef.getSortableValue) {
|
|
597
|
+
const aValue = columnDef.getSortableValue(a);
|
|
598
|
+
const bValue = columnDef.getSortableValue(b);
|
|
599
|
+
if (aValue < bValue)
|
|
600
|
+
return this.sortDirection === 'asc' ? -1 : 1;
|
|
601
|
+
if (aValue > bValue)
|
|
602
|
+
return this.sortDirection === 'asc' ? 1 : -1;
|
|
603
|
+
return 0;
|
|
604
|
+
}
|
|
605
|
+
// Fallback to dataKey if it exists and no other sorter is provided
|
|
606
|
+
if ('dataKey' in columnDef && columnDef.dataKey) {
|
|
607
|
+
const key = columnDef.dataKey;
|
|
608
|
+
const aValue = a[key];
|
|
609
|
+
const bValue = b[key];
|
|
610
|
+
if (aValue < bValue)
|
|
611
|
+
return this.sortDirection === 'asc' ? -1 : 1;
|
|
612
|
+
if (aValue > bValue)
|
|
613
|
+
return this.sortDirection === 'asc' ? 1 : -1;
|
|
614
|
+
return 0;
|
|
615
|
+
}
|
|
616
|
+
return 0;
|
|
617
|
+
});
|
|
618
|
+
return sortedData;
|
|
619
|
+
};
|
|
620
|
+
this.getHeaderProps = (columnDef) => {
|
|
621
|
+
const { columnId } = columnDef;
|
|
622
|
+
const isSortable = ('sortComparer' in columnDef && !!columnDef.sortComparer) || ('getSortableValue' in columnDef && !!columnDef.getSortableValue);
|
|
623
|
+
// A column must have a columnId and a sort function to be sortable
|
|
624
|
+
if (!isSortable || !columnId) {
|
|
625
|
+
return {};
|
|
626
|
+
}
|
|
627
|
+
const onHeaderClick = (e) => {
|
|
628
|
+
const target = e.target;
|
|
629
|
+
console.log('SortPlugin: Header clicked. Target:', target);
|
|
630
|
+
// If the click is on an interactive element, don't sort
|
|
631
|
+
if (target.closest('input, button, a, [onclick]')) {
|
|
632
|
+
console.log('SortPlugin: Interactive element clicked, ignoring sort.');
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
console.log('SortPlugin: Non-interactive element clicked, proceeding with sort.');
|
|
636
|
+
if (this.sortColumn === columnId) {
|
|
637
|
+
if (this.sortDirection === 'desc') {
|
|
638
|
+
this.sortColumn = null;
|
|
639
|
+
this.sortDirection = null;
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
this.sortDirection = 'desc';
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
this.sortColumn = columnId;
|
|
647
|
+
this.sortDirection = 'asc';
|
|
648
|
+
}
|
|
649
|
+
this.api.forceUpdate();
|
|
650
|
+
};
|
|
651
|
+
let sortClassName = 'sortable';
|
|
652
|
+
if (this.sortColumn === columnId) {
|
|
653
|
+
sortClassName = `sorted-${this.sortDirection}`;
|
|
654
|
+
}
|
|
655
|
+
return {
|
|
656
|
+
onClick: onHeaderClick,
|
|
657
|
+
className: sortClassName,
|
|
658
|
+
'aria-sort': (this.sortColumn === columnId ? (this.sortDirection === 'asc' ? 'ascending' : 'descending') : 'none'),
|
|
659
|
+
};
|
|
660
|
+
};
|
|
661
|
+
this.sortColumn = (_a = options === null || options === void 0 ? void 0 : options.initialSortColumn) !== null && _a !== void 0 ? _a : null;
|
|
662
|
+
this.sortDirection = (_b = options === null || options === void 0 ? void 0 : options.initialSortDirection) !== null && _b !== void 0 ? _b : null;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const useTablePlugins = (props) => {
|
|
667
|
+
const { data, plugins, filterProps, selectionProps, sortProps, columnDefinitions, getScrollableElement, infiniteScrollProps, } = props;
|
|
668
|
+
const [processedData, setProcessedData] = useState(data);
|
|
669
|
+
const [activePlugins, setActivePlugins] = useState([]);
|
|
670
|
+
// Persist internal plugins using refs to prevent state loss
|
|
671
|
+
const filterPluginRef = useRef(null);
|
|
672
|
+
const selectionPluginRef = useRef(null);
|
|
673
|
+
const sortPluginRef = useRef(null);
|
|
674
|
+
const getRawColumnDefinition = useCallback((columnDefinition) => {
|
|
675
|
+
if (typeof columnDefinition === 'function') {
|
|
676
|
+
return columnDefinition(data[0] || {}, 0);
|
|
677
|
+
}
|
|
678
|
+
return columnDefinition;
|
|
679
|
+
}, [data]);
|
|
680
|
+
const visibleColumns = useMemo(() => {
|
|
681
|
+
return columnDefinitions.filter(col => {
|
|
682
|
+
const raw = getRawColumnDefinition(col);
|
|
683
|
+
return raw.visible !== false;
|
|
684
|
+
});
|
|
685
|
+
}, [columnDefinitions, getRawColumnDefinition]);
|
|
686
|
+
const initializePlugins = useCallback(() => {
|
|
687
|
+
const newActivePlugins = [];
|
|
688
|
+
// 1. Add external plugins
|
|
689
|
+
if (plugins) {
|
|
690
|
+
newActivePlugins.push(...plugins);
|
|
691
|
+
}
|
|
692
|
+
// 2. Manage internal FilterPlugin
|
|
693
|
+
if (filterProps === null || filterProps === void 0 ? void 0 : filterProps.showFilter) {
|
|
694
|
+
if (!filterPluginRef.current) {
|
|
695
|
+
filterPluginRef.current = new FilterPlugin();
|
|
696
|
+
}
|
|
697
|
+
if (!newActivePlugins.some(p => p.id === 'filter')) {
|
|
698
|
+
newActivePlugins.push(filterPluginRef.current);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
filterPluginRef.current = null;
|
|
703
|
+
}
|
|
704
|
+
// 3. Manage internal SelectionPlugin
|
|
705
|
+
if (selectionProps === null || selectionProps === void 0 ? void 0 : selectionProps.onSelectionChange) {
|
|
706
|
+
if (!selectionPluginRef.current) {
|
|
707
|
+
selectionPluginRef.current = new SelectionPlugin();
|
|
708
|
+
}
|
|
709
|
+
if (!newActivePlugins.some(p => p.id === 'selection')) {
|
|
710
|
+
newActivePlugins.push(selectionPluginRef.current);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
selectionPluginRef.current = null;
|
|
715
|
+
}
|
|
716
|
+
// 4. Manage internal SortPlugin
|
|
717
|
+
const isAnyColumnSortable = columnDefinitions.some(col => {
|
|
718
|
+
const rawCol = getRawColumnDefinition(col);
|
|
719
|
+
return rawCol.sortComparer || rawCol.getSortableValue;
|
|
720
|
+
});
|
|
721
|
+
if (isAnyColumnSortable) {
|
|
722
|
+
if (!sortPluginRef.current) {
|
|
723
|
+
sortPluginRef.current = new SortPlugin(sortProps);
|
|
724
|
+
}
|
|
725
|
+
if (!newActivePlugins.some(p => p.id === 'sort')) {
|
|
726
|
+
newActivePlugins.push(sortPluginRef.current);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
sortPluginRef.current = null;
|
|
731
|
+
}
|
|
732
|
+
setActivePlugins(newActivePlugins);
|
|
733
|
+
const api = {
|
|
734
|
+
getData: () => data,
|
|
735
|
+
forceUpdate: forceUpdatePlugins,
|
|
736
|
+
getScrollableElement: getScrollableElement,
|
|
737
|
+
infiniteScrollProps: infiniteScrollProps,
|
|
738
|
+
filterProps: filterProps,
|
|
739
|
+
selectionProps: selectionProps,
|
|
740
|
+
columnDefinitions: columnDefinitions,
|
|
741
|
+
};
|
|
742
|
+
// Initialize/Refresh all active plugins with the current API
|
|
743
|
+
newActivePlugins.forEach((plugin) => {
|
|
744
|
+
if (plugin.onPluginInit) {
|
|
745
|
+
plugin.onPluginInit(api);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
// Run the data processing pipeline
|
|
749
|
+
let currentProcessedData = [...data];
|
|
750
|
+
newActivePlugins.forEach((plugin) => {
|
|
751
|
+
if (plugin.processData) {
|
|
752
|
+
currentProcessedData = plugin.processData(currentProcessedData);
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
return currentProcessedData;
|
|
756
|
+
}, [
|
|
757
|
+
data,
|
|
758
|
+
plugins,
|
|
759
|
+
filterProps,
|
|
760
|
+
selectionProps,
|
|
761
|
+
sortProps,
|
|
762
|
+
columnDefinitions,
|
|
763
|
+
getScrollableElement,
|
|
764
|
+
infiniteScrollProps,
|
|
765
|
+
getRawColumnDefinition,
|
|
766
|
+
]);
|
|
767
|
+
const forceUpdatePlugins = useCallback(() => {
|
|
768
|
+
setProcessedData(initializePlugins());
|
|
769
|
+
}, [initializePlugins]);
|
|
770
|
+
// Handle re-initialization when props change
|
|
771
|
+
useEffect(() => {
|
|
772
|
+
setProcessedData(initializePlugins());
|
|
773
|
+
}, [initializePlugins]);
|
|
774
|
+
return { processedData, activePlugins, visibleColumns, forceUpdatePlugins };
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
function InfiniteTable(props) {
|
|
778
|
+
const { columnDefinitions, data, noDataComponent, maxHeight, onRowClick, footerRows, mobileBreakpoint, plugins, enablePageLevelStickyHeader, infiniteScrollProps, filterProps, selectionProps, animationProps, sortProps, } = props;
|
|
779
|
+
const tableContainerRef = useRef(null);
|
|
780
|
+
const headerRef = useRef(null);
|
|
781
|
+
const { isMobile, isHeaderSticky, debouncedScrollHandler } = useResponsiveTable({
|
|
782
|
+
mobileBreakpoint,
|
|
783
|
+
enablePageLevelStickyHeader,
|
|
784
|
+
maxHeight,
|
|
785
|
+
headerRef,
|
|
786
|
+
scrollableRef: tableContainerRef,
|
|
787
|
+
});
|
|
788
|
+
const [internalData, setInternalData] = useState(data || []);
|
|
789
|
+
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
790
|
+
const [internalHasMore, setInternalHasMore] = useState(true);
|
|
791
|
+
const getScrollableElement = useCallback(() => tableContainerRef.current, []);
|
|
792
|
+
const { processedData, activePlugins, visibleColumns } = useTablePlugins({
|
|
793
|
+
data: internalData,
|
|
794
|
+
plugins,
|
|
795
|
+
filterProps,
|
|
796
|
+
selectionProps,
|
|
797
|
+
sortProps,
|
|
798
|
+
columnDefinitions,
|
|
799
|
+
getScrollableElement,
|
|
800
|
+
infiniteScrollProps,
|
|
801
|
+
});
|
|
802
|
+
const currentHasMore = (infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.hasMore) !== undefined
|
|
803
|
+
? infiniteScrollProps.hasMore
|
|
804
|
+
: internalHasMore;
|
|
805
|
+
const hasData = useMemo(() => processedData.length > 0, [processedData]);
|
|
806
|
+
const noDataComponentNode = noDataComponent || React.createElement("div", { className: styles$2.noData }, "No data");
|
|
807
|
+
const defaultLoadingComponent = React.createElement(LoadingSpinner, null);
|
|
808
|
+
const defaultNoMoreDataComponent = React.createElement(NoMoreDataMessage, null);
|
|
809
|
+
const loadMoreData = useCallback(() => __awaiter(this, void 0, void 0, function* () {
|
|
810
|
+
var _a;
|
|
811
|
+
if (!infiniteScrollProps || isLoadingMore)
|
|
812
|
+
return;
|
|
813
|
+
setIsLoadingMore(true);
|
|
814
|
+
try {
|
|
815
|
+
const newItems = yield ((_a = infiniteScrollProps.onLoadMore) === null || _a === void 0 ? void 0 : _a.call(infiniteScrollProps, internalData));
|
|
816
|
+
if (infiniteScrollProps.hasMore === undefined) {
|
|
817
|
+
if (!newItems || newItems.length === 0) {
|
|
818
|
+
setInternalHasMore(false);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
if (newItems && newItems.length > 0) {
|
|
822
|
+
setInternalData(prevData => [...prevData, ...newItems]);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
catch (error) {
|
|
826
|
+
console.error("Failed to load more items for infinite scroll:", error);
|
|
827
|
+
}
|
|
828
|
+
finally {
|
|
829
|
+
setIsLoadingMore(false);
|
|
830
|
+
}
|
|
831
|
+
}), [infiniteScrollProps, isLoadingMore, internalData]);
|
|
832
|
+
useEffect(() => {
|
|
833
|
+
setInternalData(data || []);
|
|
834
|
+
}, [data]);
|
|
835
|
+
useEffect(() => {
|
|
836
|
+
if (data.length === 0) {
|
|
837
|
+
loadMoreData();
|
|
838
|
+
}
|
|
839
|
+
}, [data.length, loadMoreData]);
|
|
840
|
+
const handleScrollForInfinite = useCallback((currentTarget) => {
|
|
841
|
+
if (!currentTarget)
|
|
842
|
+
return;
|
|
843
|
+
const { scrollHeight, scrollTop, clientHeight } = currentTarget;
|
|
844
|
+
if (currentHasMore && !isLoadingMore && scrollHeight - scrollTop - clientHeight < 100) {
|
|
845
|
+
loadMoreData();
|
|
846
|
+
}
|
|
847
|
+
}, [currentHasMore, isLoadingMore, loadMoreData]);
|
|
848
|
+
const mobileFooter = useMemo(() => {
|
|
849
|
+
if (!footerRows || footerRows.length === 0) {
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
// Helper to get raw column definition in this context
|
|
853
|
+
const getRaw = (colDef) => {
|
|
854
|
+
if (typeof colDef === 'function') {
|
|
855
|
+
return processedData.length > 0 ? colDef(processedData[0], 0) : { displayLabel: '', cellRenderer: () => '' };
|
|
856
|
+
}
|
|
857
|
+
return colDef;
|
|
858
|
+
};
|
|
859
|
+
return (React.createElement("div", { className: styles$2.footerCard },
|
|
860
|
+
React.createElement("div", { className: styles$2['footer-card-body'] }, footerRows.map((row, rowIndex) => {
|
|
861
|
+
let currentColumnIndex = 0;
|
|
862
|
+
return (React.createElement("div", { key: rowIndex }, row.columns.map((col, colIndex) => {
|
|
863
|
+
let label = col.displayLabel;
|
|
864
|
+
if (!label && col.colSpan === 1) {
|
|
865
|
+
const header = columnDefinitions[currentColumnIndex];
|
|
866
|
+
if (header) {
|
|
867
|
+
label = getRaw(header).displayLabel;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
currentColumnIndex += col.colSpan;
|
|
871
|
+
return (React.createElement("p", { key: colIndex, className: `${styles$2['footer-card-row']} ${col.className || ''} ${col.onCellClick ? styles$2.clickableFooterCell : ''}`, onClick: col.onCellClick },
|
|
872
|
+
label && React.createElement("span", { className: styles$2['card-label'] }, label),
|
|
873
|
+
React.createElement("span", { className: styles$2['card-value'] }, col.cellRenderer())));
|
|
874
|
+
})));
|
|
875
|
+
}))));
|
|
876
|
+
}, [footerRows, columnDefinitions, processedData]);
|
|
877
|
+
const renderPluginHeaders = useCallback(() => {
|
|
878
|
+
if (!activePlugins) {
|
|
879
|
+
return null;
|
|
880
|
+
}
|
|
881
|
+
return activePlugins.map((plugin) => {
|
|
882
|
+
if (plugin.renderHeader) {
|
|
883
|
+
if (plugin.id === 'sort' && !isMobile) {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
return React.createElement("div", { key: plugin.id }, plugin.renderHeader());
|
|
887
|
+
}
|
|
888
|
+
return null;
|
|
889
|
+
});
|
|
890
|
+
}, [activePlugins, isMobile]);
|
|
891
|
+
const renderPluginFooters = useCallback(() => {
|
|
892
|
+
if (!plugins) {
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
return plugins.map((plugin) => {
|
|
896
|
+
if (plugin.renderFooter) {
|
|
897
|
+
return React.createElement("div", { key: plugin.id + '-footer' }, plugin.renderFooter());
|
|
898
|
+
}
|
|
899
|
+
return null;
|
|
900
|
+
});
|
|
901
|
+
}, [plugins]);
|
|
902
|
+
const infiniteStatusUI = (React.createElement(React.Fragment, null,
|
|
903
|
+
isLoadingMore && ((infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.loadingMoreComponent) || defaultLoadingComponent),
|
|
904
|
+
!isLoadingMore && !currentHasMore && ((infiniteScrollProps === null || infiniteScrollProps === void 0 ? void 0 : infiniteScrollProps.noMoreDataComponent) || defaultNoMoreDataComponent)));
|
|
905
|
+
if ((animationProps === null || animationProps === void 0 ? void 0 : animationProps.isLoading) && !hasData) {
|
|
906
|
+
return React.createElement(SkeletonView, { isMobile: isMobile, columnDefinitions: visibleColumns });
|
|
907
|
+
}
|
|
908
|
+
return (React.createElement(TableProvider, { value: {
|
|
909
|
+
data: internalData,
|
|
910
|
+
processedData,
|
|
911
|
+
visibleColumns,
|
|
912
|
+
originalColumnDefinitions: columnDefinitions,
|
|
913
|
+
activePlugins,
|
|
914
|
+
onRowClick,
|
|
915
|
+
selectionProps,
|
|
916
|
+
animationProps,
|
|
917
|
+
} },
|
|
918
|
+
React.createElement("div", null,
|
|
919
|
+
React.createElement("div", { style: { display: 'flex', justifyContent: 'flex-end' } }, renderPluginHeaders()),
|
|
920
|
+
!hasData && noDataComponentNode,
|
|
921
|
+
hasData && (isMobile ? (React.createElement(MobileView, { mobileFooter: mobileFooter })) : (React.createElement(DesktopView, { maxHeight: maxHeight, isHeaderSticky: isHeaderSticky, tableContainerRef: tableContainerRef, headerRef: headerRef, footerRows: footerRows, renderPluginFooters: renderPluginFooters, onScroll: (e) => {
|
|
922
|
+
debouncedScrollHandler(e.currentTarget); // For sticky header
|
|
923
|
+
handleScrollForInfinite(e.currentTarget); // For infinite scroll
|
|
924
|
+
} }))),
|
|
925
|
+
hasData && infiniteStatusUI)));
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const useTableDataSource = (props) => {
|
|
929
|
+
const { dataSource, pageSize = 20, initialData = [], sort, filter } = props;
|
|
930
|
+
const [data, setData] = useState(initialData);
|
|
931
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
932
|
+
const [hasMore, setHasMore] = useState(true);
|
|
933
|
+
const [totalCount, setTotalCount] = useState(undefined);
|
|
934
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
935
|
+
const [isFetchingMore, setIsFetchingMore] = useState(false);
|
|
936
|
+
const isInitialMount = useRef(true);
|
|
937
|
+
const fetchData = useCallback((page, isAppend) => __awaiter(void 0, void 0, void 0, function* () {
|
|
938
|
+
if (!dataSource)
|
|
939
|
+
return;
|
|
940
|
+
if (isAppend) {
|
|
941
|
+
setIsFetchingMore(true);
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
setIsLoading(true);
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
const params = {
|
|
948
|
+
page,
|
|
949
|
+
pageSize,
|
|
950
|
+
sort,
|
|
951
|
+
filter,
|
|
952
|
+
};
|
|
953
|
+
const result = yield dataSource(params);
|
|
954
|
+
let newItems = [];
|
|
955
|
+
let newTotalCount = undefined;
|
|
956
|
+
if (Array.isArray(result)) {
|
|
957
|
+
newItems = result;
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
newItems = result.items;
|
|
961
|
+
newTotalCount = result.totalCount;
|
|
962
|
+
}
|
|
963
|
+
setData(prev => isAppend ? [...prev, ...newItems] : newItems);
|
|
964
|
+
setTotalCount(newTotalCount);
|
|
965
|
+
setCurrentPage(page);
|
|
966
|
+
// Intelligent hasMore detection
|
|
967
|
+
if (newTotalCount !== undefined) {
|
|
968
|
+
const currentTotalLoaded = (isAppend ? data.length : 0) + newItems.length;
|
|
969
|
+
setHasMore(currentTotalLoaded < newTotalCount);
|
|
970
|
+
}
|
|
971
|
+
else {
|
|
972
|
+
// If we got fewer items than pageSize, we've reached the end
|
|
973
|
+
setHasMore(newItems.length === pageSize);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
catch (error) {
|
|
977
|
+
console.error('Error fetching data from dataSource:', error);
|
|
978
|
+
setHasMore(false);
|
|
979
|
+
}
|
|
980
|
+
finally {
|
|
981
|
+
setIsLoading(false);
|
|
982
|
+
setIsFetchingMore(false);
|
|
983
|
+
}
|
|
984
|
+
}), [dataSource, pageSize, sort, filter, data.length]);
|
|
985
|
+
const loadNextPage = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
986
|
+
if (isLoading || isFetchingMore || !hasMore || !dataSource)
|
|
987
|
+
return;
|
|
988
|
+
yield fetchData(currentPage + 1, true);
|
|
989
|
+
}), [currentPage, hasMore, isLoading, isFetchingMore, dataSource, fetchData]);
|
|
990
|
+
const resetAndFetch = useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
991
|
+
if (!dataSource)
|
|
992
|
+
return;
|
|
993
|
+
yield fetchData(1, false);
|
|
994
|
+
}), [dataSource, fetchData]);
|
|
995
|
+
// Handle changes in sort or filter (reset to page 1)
|
|
996
|
+
useEffect(() => {
|
|
997
|
+
if (isInitialMount.current) {
|
|
998
|
+
isInitialMount.current = false;
|
|
999
|
+
if (dataSource && initialData.length === 0) {
|
|
1000
|
+
resetAndFetch();
|
|
1001
|
+
}
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
resetAndFetch();
|
|
1005
|
+
}, [sort, filter, dataSource]); // initialData and pageSize changes don't trigger reset by default
|
|
1006
|
+
return {
|
|
1007
|
+
data,
|
|
1008
|
+
currentPage,
|
|
1009
|
+
hasMore,
|
|
1010
|
+
totalCount,
|
|
1011
|
+
isLoading,
|
|
1012
|
+
isFetchingMore,
|
|
1013
|
+
loadNextPage,
|
|
1014
|
+
resetAndFetch,
|
|
1015
|
+
};
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* A highly customizable, mobile-first responsive React table.
|
|
1020
|
+
* Supports static data or async data sources with built-in infinite scroll.
|
|
1021
|
+
*/
|
|
1022
|
+
function ResponsiveTable(props) {
|
|
1023
|
+
const { columnDefinitions, data: initialData, dataSource, pageSize, noDataComponent, maxHeight, onRowClick, footerRows, mobileBreakpoint, plugins, enablePageLevelStickyHeader, infiniteScrollProps, filterProps, selectionProps, animationProps, sortProps, } = props;
|
|
1024
|
+
const tableContainerRef = useRef(null);
|
|
1025
|
+
const headerRef = useRef(null);
|
|
1026
|
+
const { isMobile, isHeaderSticky } = useResponsiveTable({
|
|
1027
|
+
mobileBreakpoint,
|
|
1028
|
+
enablePageLevelStickyHeader,
|
|
1029
|
+
maxHeight,
|
|
1030
|
+
headerRef,
|
|
1031
|
+
scrollableRef: tableContainerRef,
|
|
1032
|
+
});
|
|
1033
|
+
const getScrollableElement = useCallback(() => tableContainerRef.current, []);
|
|
1034
|
+
// Track active sort state for dataSource
|
|
1035
|
+
const [activeSort /*, setActiveSort*/] = useState((sortProps === null || sortProps === void 0 ? void 0 : sortProps.initialSortColumn) ? { columnId: sortProps.initialSortColumn, direction: sortProps.initialSortDirection || 'asc' } : undefined);
|
|
1036
|
+
const { data: sourceData, isLoading: isSourceLoading, isFetchingMore, hasMore, totalCount, currentPage, loadNextPage, } = useTableDataSource({
|
|
1037
|
+
dataSource,
|
|
1038
|
+
pageSize,
|
|
1039
|
+
initialData,
|
|
1040
|
+
sort: activeSort,
|
|
1041
|
+
// We'll need to extract filter state if we want to support dataSource filtering
|
|
1042
|
+
});
|
|
1043
|
+
const currentDataToProcess = dataSource ? sourceData : initialData;
|
|
1044
|
+
const { processedData, activePlugins, visibleColumns } = useTablePlugins({
|
|
1045
|
+
data: currentDataToProcess,
|
|
1046
|
+
plugins,
|
|
1047
|
+
filterProps,
|
|
1048
|
+
selectionProps,
|
|
1049
|
+
sortProps,
|
|
1050
|
+
columnDefinitions,
|
|
1051
|
+
getScrollableElement,
|
|
1052
|
+
infiniteScrollProps,
|
|
1053
|
+
});
|
|
1054
|
+
// Sync sort state from SortPlugin back to our local state to trigger dataSource re-fetch
|
|
1055
|
+
useEffect(() => {
|
|
1056
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1057
|
+
activePlugins.find(p => p.id === 'sort');
|
|
1058
|
+
}, [activePlugins, dataSource]);
|
|
1059
|
+
const hasData = useMemo(() => processedData.length > 0, [processedData]);
|
|
1060
|
+
const noDataSvg = (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "#ccc", height: "40", width: "40", viewBox: "0 0 24 24" },
|
|
1061
|
+
React.createElement("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-14h2v6h-2zm0 8h2v2h-2z" })));
|
|
1062
|
+
const noDataComponentNode = noDataComponent || (React.createElement("div", { className: styles$2.noDataWrapper },
|
|
1063
|
+
noDataSvg,
|
|
1064
|
+
React.createElement("div", { className: styles$2.noData }, "No data")));
|
|
1065
|
+
const mobileFooter = useMemo(() => {
|
|
1066
|
+
if (!footerRows || footerRows.length === 0) {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
// Helper to get raw column definition in this context
|
|
1070
|
+
const getRaw = (colDef) => {
|
|
1071
|
+
if (typeof colDef === 'function') {
|
|
1072
|
+
return processedData.length > 0 ? colDef(processedData[0], 0) : { displayLabel: '', cellRenderer: () => '' };
|
|
1073
|
+
}
|
|
1074
|
+
return colDef;
|
|
1075
|
+
};
|
|
1076
|
+
return (React.createElement("div", { className: styles$2.footerCard },
|
|
1077
|
+
React.createElement("div", { className: styles$2['footer-card-body'] }, footerRows.map((row, rowIndex) => {
|
|
1078
|
+
let currentColumnIndex = 0;
|
|
1079
|
+
return (React.createElement("div", { key: rowIndex }, row.columns.map((col, colIndex) => {
|
|
1080
|
+
let label = col.displayLabel;
|
|
1081
|
+
if (!label && col.colSpan === 1) {
|
|
1082
|
+
const header = columnDefinitions[currentColumnIndex];
|
|
1083
|
+
if (header) {
|
|
1084
|
+
label = getRaw(header).displayLabel;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
currentColumnIndex += col.colSpan;
|
|
1088
|
+
return (React.createElement("p", { key: colIndex, className: `${styles$2['footer-card-row']} ${col.className || ''} ${col.onCellClick ? styles$2.clickableFooterCell : ''}`, onClick: col.onCellClick },
|
|
1089
|
+
label && React.createElement("span", { className: styles$2['card-label'] }, label),
|
|
1090
|
+
React.createElement("span", { className: styles$2['card-value'] }, col.cellRenderer())));
|
|
1091
|
+
})));
|
|
1092
|
+
}))));
|
|
1093
|
+
}, [footerRows, columnDefinitions, processedData]);
|
|
1094
|
+
const renderPluginHeaders = useCallback(() => {
|
|
1095
|
+
if (!activePlugins) {
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
return activePlugins.map((plugin) => {
|
|
1099
|
+
if (plugin.renderHeader) {
|
|
1100
|
+
if (plugin.id === 'sort' && !isMobile) {
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
return React.createElement("div", { key: plugin.id }, plugin.renderHeader());
|
|
1104
|
+
}
|
|
1105
|
+
return null;
|
|
1106
|
+
});
|
|
1107
|
+
}, [activePlugins, isMobile]);
|
|
1108
|
+
const renderPluginFooters = useCallback(() => {
|
|
1109
|
+
if (!plugins) {
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
return plugins.map((plugin) => {
|
|
1113
|
+
if (plugin.renderFooter) {
|
|
1114
|
+
return React.createElement("div", { key: plugin.id + '-footer' }, plugin.renderFooter());
|
|
1115
|
+
}
|
|
1116
|
+
return null;
|
|
1117
|
+
});
|
|
1118
|
+
}, [plugins]);
|
|
1119
|
+
if (infiniteScrollProps) {
|
|
1120
|
+
return React.createElement(InfiniteTable, Object.assign({}, props));
|
|
1121
|
+
}
|
|
1122
|
+
const isLoading = (animationProps === null || animationProps === void 0 ? void 0 : animationProps.isLoading) || isSourceLoading;
|
|
1123
|
+
if (isLoading && !hasData) {
|
|
1124
|
+
return React.createElement(SkeletonView, { isMobile: isMobile, columnDefinitions: visibleColumns });
|
|
1125
|
+
}
|
|
1126
|
+
return (React.createElement(TableProvider, { value: {
|
|
1127
|
+
data: currentDataToProcess,
|
|
1128
|
+
processedData,
|
|
1129
|
+
visibleColumns,
|
|
1130
|
+
originalColumnDefinitions: columnDefinitions,
|
|
1131
|
+
activePlugins,
|
|
1132
|
+
onRowClick,
|
|
1133
|
+
selectionProps,
|
|
1134
|
+
animationProps: Object.assign(Object.assign({}, animationProps), { isLoading }),
|
|
1135
|
+
dataSource,
|
|
1136
|
+
pagination: dataSource ? {
|
|
1137
|
+
currentPage,
|
|
1138
|
+
pageSize: pageSize || 20,
|
|
1139
|
+
hasMore,
|
|
1140
|
+
totalCount,
|
|
1141
|
+
isLoading: isSourceLoading,
|
|
1142
|
+
isFetchingMore,
|
|
1143
|
+
loadNextPage,
|
|
1144
|
+
} : undefined,
|
|
1145
|
+
} },
|
|
1146
|
+
React.createElement("div", null,
|
|
1147
|
+
React.createElement("div", { style: { display: 'flex', justifyContent: 'flex-end' } }, renderPluginHeaders()),
|
|
1148
|
+
!hasData && !isLoading && noDataComponentNode,
|
|
1149
|
+
(hasData || isLoading) && isMobile && (React.createElement(MobileView, { mobileFooter: mobileFooter })),
|
|
1150
|
+
(hasData || isLoading) && !isMobile && (React.createElement(DesktopView, { maxHeight: maxHeight, isHeaderSticky: isHeaderSticky, tableContainerRef: tableContainerRef, headerRef: headerRef, footerRows: footerRows, renderPluginFooters: renderPluginFooters })))));
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
class InfiniteScrollPlugin {
|
|
1154
|
+
constructor() {
|
|
1155
|
+
this.id = 'infinite-scroll';
|
|
1156
|
+
this.isLoadingMore = false;
|
|
1157
|
+
this.onPluginInit = (api) => {
|
|
1158
|
+
this.api = api;
|
|
1159
|
+
this.attachScrollListener();
|
|
1160
|
+
};
|
|
1161
|
+
this.attachScrollListener = () => {
|
|
1162
|
+
var _a, _b;
|
|
1163
|
+
const scrollableElement = (_b = (_a = this.api).getScrollableElement) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1164
|
+
if (scrollableElement) {
|
|
1165
|
+
scrollableElement.addEventListener('scroll', this.handleScroll);
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
this.handleScroll = () => __awaiter(this, void 0, void 0, function* () {
|
|
1169
|
+
var _a, _b, _c, _d;
|
|
1170
|
+
const scrollableElement = (_b = (_a = this.api).getScrollableElement) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1171
|
+
if (!scrollableElement || !this.api.infiniteScrollProps) {
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollableElement;
|
|
1175
|
+
const scrollThreshold = 200; // Load more data when 200px from the bottom
|
|
1176
|
+
if (scrollHeight - scrollTop - clientHeight < scrollThreshold &&
|
|
1177
|
+
this.api.infiniteScrollProps.hasMore &&
|
|
1178
|
+
!this.isLoadingMore) {
|
|
1179
|
+
this.isLoadingMore = true;
|
|
1180
|
+
this.api.forceUpdate(); // Trigger re-render to show loading component
|
|
1181
|
+
yield ((_d = (_c = this.api.infiniteScrollProps).onLoadMore) === null || _d === void 0 ? void 0 : _d.call(_c, this.api.getData()));
|
|
1182
|
+
this.isLoadingMore = false;
|
|
1183
|
+
this.api.forceUpdate(); // Trigger re-render to hide loading component
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
this.processData = (data) => {
|
|
1187
|
+
// This plugin doesn't modify the data directly, but rather triggers loading more.
|
|
1188
|
+
// The main component's data prop should be updated by the consumer of the table.
|
|
1189
|
+
return data;
|
|
1190
|
+
};
|
|
1191
|
+
this.renderFooter = () => {
|
|
1192
|
+
if (!this.api.infiniteScrollProps) {
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
if (this.isLoadingMore) {
|
|
1196
|
+
return this.api.infiniteScrollProps.loadingMoreComponent || React.createElement("div", null, "Loading more...");
|
|
1197
|
+
}
|
|
1198
|
+
else if (!this.api.infiniteScrollProps.hasMore) {
|
|
1199
|
+
return this.api.infiniteScrollProps.noMoreDataComponent || React.createElement("div", null, "No more data.");
|
|
1200
|
+
}
|
|
1201
|
+
return null;
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
export { FilterPlugin, InfiniteScrollPlugin, SelectionPlugin, SortPlugin, ResponsiveTable as default };
|
|
1207
|
+
//# sourceMappingURL=index.es.js.map
|