jattac.libs.web.responsive-table 0.9.2 → 0.11.0
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 +47 -11
- package/dist/Context/TableContext.d.ts +1 -0
- package/dist/Hooks/useTableDataSource.d.ts +10 -0
- package/dist/Hooks/useTablePlugins.d.ts +2 -0
- package/dist/Plugins/FilterPlugin.d.ts +1 -0
- package/dist/Plugins/IResponsiveTablePlugin.d.ts +2 -0
- package/dist/UI/ResponsiveTable.d.ts +17 -5
- package/dist/index.d.ts +4 -3
- package/dist/index.es.js +137 -23
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +136 -21
- package/dist/index.js.map +1 -1
- package/docs/api.md +63 -4
- package/docs/examples.md +148 -1
- package/docs/features.md +7 -1
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var React = require('react');
|
|
6
|
+
var ZestTextbox = require('jattac.libs.web.zest-textbox');
|
|
7
|
+
var md = require('react-icons/md');
|
|
6
8
|
|
|
7
9
|
function styleInject(css, ref) {
|
|
8
10
|
if ( ref === void 0 ) ref = {};
|
|
@@ -488,18 +490,36 @@ class FilterPlugin {
|
|
|
488
490
|
this.api = api;
|
|
489
491
|
};
|
|
490
492
|
this.renderHeader = () => {
|
|
491
|
-
var _a;
|
|
493
|
+
var _a, _b;
|
|
492
494
|
if (!((_a = this.api.filterProps) === null || _a === void 0 ? void 0 : _a.showFilter)) {
|
|
493
495
|
return null;
|
|
494
496
|
}
|
|
495
|
-
return (React.createElement("div", { style: { marginBottom: '1rem' } },
|
|
496
|
-
React.createElement(
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
497
|
+
return (React.createElement("div", { style: { marginBottom: '1rem', display: 'flex', alignItems: 'center', gap: '0.5rem' } },
|
|
498
|
+
React.createElement(ZestTextbox, { value: this.filterText, placeholder: (_b = this.api.filterProps.filterPlaceholder) !== null && _b !== void 0 ? _b : 'Search...', onChange: this.handleFilterChange, className: this.api.filterProps.className, zest: { stretch: true } }),
|
|
499
|
+
React.createElement("button", { onClick: this.handleClear, "aria-label": "Clear filter", style: {
|
|
500
|
+
display: 'flex',
|
|
501
|
+
alignItems: 'center',
|
|
502
|
+
justifyContent: 'center',
|
|
503
|
+
minWidth: '2.75rem',
|
|
504
|
+
minHeight: '2.75rem',
|
|
505
|
+
padding: 0,
|
|
506
|
+
border: 'none',
|
|
507
|
+
borderRadius: '50%',
|
|
508
|
+
background: 'transparent',
|
|
509
|
+
cursor: 'pointer',
|
|
510
|
+
color: '#666',
|
|
511
|
+
opacity: this.filterText ? 1 : 0,
|
|
512
|
+
pointerEvents: this.filterText ? 'auto' : 'none',
|
|
513
|
+
transition: 'opacity 0.15s ease',
|
|
514
|
+
flexShrink: 0,
|
|
515
|
+
} },
|
|
516
|
+
React.createElement(md.MdClose, { size: 20 }))));
|
|
501
517
|
};
|
|
502
518
|
this.processData = (data) => {
|
|
519
|
+
var _a;
|
|
520
|
+
if (((_a = this.api.filterProps) === null || _a === void 0 ? void 0 : _a.mode) === 'server') {
|
|
521
|
+
return data;
|
|
522
|
+
}
|
|
503
523
|
if (!this.filterText || !this.api.columnDefinitions) {
|
|
504
524
|
return data;
|
|
505
525
|
}
|
|
@@ -526,6 +546,10 @@ class FilterPlugin {
|
|
|
526
546
|
_row,
|
|
527
547
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
528
548
|
_column) => {
|
|
549
|
+
var _a;
|
|
550
|
+
if (((_a = this.api.filterProps) === null || _a === void 0 ? void 0 : _a.mode) === 'server') {
|
|
551
|
+
return content;
|
|
552
|
+
}
|
|
529
553
|
if (!this.filterText || typeof content !== 'string') {
|
|
530
554
|
return content;
|
|
531
555
|
}
|
|
@@ -538,10 +562,21 @@ class FilterPlugin {
|
|
|
538
562
|
clearTimeout(this.debounceTimeout);
|
|
539
563
|
}
|
|
540
564
|
this.debounceTimeout = setTimeout(() => {
|
|
565
|
+
var _a, _b;
|
|
541
566
|
this.filterText = currentFilterText;
|
|
542
567
|
this.api.forceUpdate();
|
|
568
|
+
(_b = (_a = this.api).onFilterChange) === null || _b === void 0 ? void 0 : _b.call(_a, currentFilterText);
|
|
543
569
|
}, 300);
|
|
544
570
|
};
|
|
571
|
+
this.handleClear = () => {
|
|
572
|
+
var _a, _b;
|
|
573
|
+
if (this.debounceTimeout) {
|
|
574
|
+
clearTimeout(this.debounceTimeout);
|
|
575
|
+
}
|
|
576
|
+
this.filterText = '';
|
|
577
|
+
this.api.forceUpdate();
|
|
578
|
+
(_b = (_a = this.api).onFilterChange) === null || _b === void 0 ? void 0 : _b.call(_a, '');
|
|
579
|
+
};
|
|
545
580
|
}
|
|
546
581
|
}
|
|
547
582
|
|
|
@@ -761,7 +796,7 @@ class SortPlugin {
|
|
|
761
796
|
}
|
|
762
797
|
|
|
763
798
|
const useTablePlugins = (props) => {
|
|
764
|
-
const { data, plugins, filterProps, selectionProps, sortProps, columnDefinitions, getScrollableElement, infiniteScrollProps, } = props;
|
|
799
|
+
const { data, plugins, filterProps, selectionProps, sortProps, columnDefinitions, getScrollableElement, infiniteScrollProps, onFilterChange, } = props;
|
|
765
800
|
const [processedData, setProcessedData] = React.useState(data);
|
|
766
801
|
const [activePlugins, setActivePlugins] = React.useState([]);
|
|
767
802
|
// Persist internal plugins using refs to prevent state loss
|
|
@@ -835,6 +870,7 @@ const useTablePlugins = (props) => {
|
|
|
835
870
|
filterProps: filterProps,
|
|
836
871
|
selectionProps: selectionProps,
|
|
837
872
|
columnDefinitions: columnDefinitions,
|
|
873
|
+
onFilterChange: onFilterChange,
|
|
838
874
|
};
|
|
839
875
|
// Initialize/Refresh all active plugins with the current API
|
|
840
876
|
newActivePlugins.forEach((plugin) => {
|
|
@@ -860,6 +896,7 @@ const useTablePlugins = (props) => {
|
|
|
860
896
|
getScrollableElement,
|
|
861
897
|
infiniteScrollProps,
|
|
862
898
|
getRawColumnDefinition,
|
|
899
|
+
onFilterChange,
|
|
863
900
|
]);
|
|
864
901
|
const forceUpdatePlugins = React.useCallback(() => {
|
|
865
902
|
setProcessedData(initializePlugins());
|
|
@@ -1030,7 +1067,10 @@ const useTableDataSource = (props) => {
|
|
|
1030
1067
|
const [totalCount, setTotalCount] = React.useState(undefined);
|
|
1031
1068
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
1032
1069
|
const [isFetchingMore, setIsFetchingMore] = React.useState(false);
|
|
1070
|
+
const [error, setError] = React.useState(undefined);
|
|
1033
1071
|
const isInitialMount = React.useRef(true);
|
|
1072
|
+
const dataLengthRef = React.useRef(0);
|
|
1073
|
+
dataLengthRef.current = data.length;
|
|
1034
1074
|
const fetchData = React.useCallback((page, isAppend) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1035
1075
|
if (!dataSource)
|
|
1036
1076
|
return;
|
|
@@ -1041,6 +1081,7 @@ const useTableDataSource = (props) => {
|
|
|
1041
1081
|
setIsLoading(true);
|
|
1042
1082
|
}
|
|
1043
1083
|
try {
|
|
1084
|
+
setError(undefined);
|
|
1044
1085
|
const params = {
|
|
1045
1086
|
page,
|
|
1046
1087
|
pageSize,
|
|
@@ -1062,7 +1103,7 @@ const useTableDataSource = (props) => {
|
|
|
1062
1103
|
setCurrentPage(page);
|
|
1063
1104
|
// Intelligent hasMore detection
|
|
1064
1105
|
if (newTotalCount !== undefined) {
|
|
1065
|
-
const currentTotalLoaded = (isAppend ?
|
|
1106
|
+
const currentTotalLoaded = (isAppend ? dataLengthRef.current : 0) + newItems.length;
|
|
1066
1107
|
setHasMore(currentTotalLoaded < newTotalCount);
|
|
1067
1108
|
}
|
|
1068
1109
|
else {
|
|
@@ -1070,15 +1111,16 @@ const useTableDataSource = (props) => {
|
|
|
1070
1111
|
setHasMore(newItems.length === pageSize);
|
|
1071
1112
|
}
|
|
1072
1113
|
}
|
|
1073
|
-
catch (
|
|
1074
|
-
|
|
1114
|
+
catch (err) {
|
|
1115
|
+
setError(err);
|
|
1075
1116
|
setHasMore(false);
|
|
1117
|
+
console.error('Error fetching data from dataSource:', err);
|
|
1076
1118
|
}
|
|
1077
1119
|
finally {
|
|
1078
1120
|
setIsLoading(false);
|
|
1079
1121
|
setIsFetchingMore(false);
|
|
1080
1122
|
}
|
|
1081
|
-
}), [dataSource, pageSize, sort, filter
|
|
1123
|
+
}), [dataSource, pageSize, sort, filter]);
|
|
1082
1124
|
const loadNextPage = React.useCallback(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
1083
1125
|
if (isLoading || isFetchingMore || !hasMore || !dataSource)
|
|
1084
1126
|
return;
|
|
@@ -1109,6 +1151,7 @@ const useTableDataSource = (props) => {
|
|
|
1109
1151
|
isFetchingMore,
|
|
1110
1152
|
loadNextPage,
|
|
1111
1153
|
resetAndFetch,
|
|
1154
|
+
error,
|
|
1112
1155
|
};
|
|
1113
1156
|
};
|
|
1114
1157
|
|
|
@@ -1116,8 +1159,9 @@ const useTableDataSource = (props) => {
|
|
|
1116
1159
|
* A highly customizable, mobile-first responsive React table.
|
|
1117
1160
|
* Supports static data or async data sources with built-in infinite scroll.
|
|
1118
1161
|
*/
|
|
1119
|
-
function
|
|
1120
|
-
|
|
1162
|
+
function ResponsiveTableInner(props, ref) {
|
|
1163
|
+
var _a;
|
|
1164
|
+
const { columnDefinitions, data: initialData, dataSource, pageSize, noDataComponent, maxHeight, onRowClick, footerRows, mobileBreakpoint, plugins, enablePageLevelStickyHeader, infiniteScrollProps, filterProps, selectionProps, animationProps, sortProps, mobileCardClassName, onDataSourceStateChange, onPageChange, onDataSourceError, } = props;
|
|
1121
1165
|
const tableContainerRef = React.useRef(null);
|
|
1122
1166
|
const headerRef = React.useRef(null);
|
|
1123
1167
|
const { isMobile, isHeaderSticky } = useResponsiveTable({
|
|
@@ -1130,29 +1174,72 @@ function ResponsiveTable(props) {
|
|
|
1130
1174
|
const getScrollableElement = React.useCallback(() => tableContainerRef.current, []);
|
|
1131
1175
|
// Track active sort state for dataSource
|
|
1132
1176
|
const [activeSort /*, setActiveSort*/] = React.useState((sortProps === null || sortProps === void 0 ? void 0 : sortProps.initialSortColumn) ? { columnId: sortProps.initialSortColumn, direction: sortProps.initialSortDirection || 'asc' } : undefined);
|
|
1133
|
-
|
|
1177
|
+
// Track active filter state for dataSource
|
|
1178
|
+
const [activeFilter, setActiveFilter] = React.useState('');
|
|
1179
|
+
const handleFilterChange = React.useCallback((text) => {
|
|
1180
|
+
setActiveFilter(text);
|
|
1181
|
+
}, []);
|
|
1182
|
+
const isServerFilter = !!dataSource && !!(filterProps === null || filterProps === void 0 ? void 0 : filterProps.showFilter) && (filterProps === null || filterProps === void 0 ? void 0 : filterProps.mode) !== 'client';
|
|
1183
|
+
const resolvedFilterProps = filterProps
|
|
1184
|
+
? Object.assign(Object.assign({}, filterProps), { mode: isServerFilter ? 'server' : ((_a = filterProps.mode) !== null && _a !== void 0 ? _a : 'client') }) : undefined;
|
|
1185
|
+
const { data: sourceData, isLoading: isSourceLoading, isFetchingMore, hasMore, totalCount, currentPage, loadNextPage, error, resetAndFetch, } = useTableDataSource({
|
|
1134
1186
|
dataSource,
|
|
1135
1187
|
pageSize,
|
|
1136
1188
|
initialData,
|
|
1137
1189
|
sort: activeSort,
|
|
1138
|
-
|
|
1190
|
+
filter: isServerFilter ? activeFilter : undefined,
|
|
1139
1191
|
});
|
|
1192
|
+
React.useImperativeHandle(ref, () => ({
|
|
1193
|
+
loadNextPage: () => loadNextPage(),
|
|
1194
|
+
resetAndFetch: () => resetAndFetch(),
|
|
1195
|
+
getState: () => ({
|
|
1196
|
+
data: sourceData,
|
|
1197
|
+
currentPage,
|
|
1198
|
+
hasMore,
|
|
1199
|
+
totalCount,
|
|
1200
|
+
isLoading: isSourceLoading,
|
|
1201
|
+
isFetchingMore,
|
|
1202
|
+
error,
|
|
1203
|
+
}),
|
|
1204
|
+
}), [loadNextPage, resetAndFetch, sourceData, currentPage, hasMore, totalCount, isSourceLoading, isFetchingMore, error]);
|
|
1140
1205
|
const currentDataToProcess = dataSource ? sourceData : initialData;
|
|
1141
1206
|
const { processedData, activePlugins, visibleColumns } = useTablePlugins({
|
|
1142
1207
|
data: currentDataToProcess,
|
|
1143
1208
|
plugins,
|
|
1144
|
-
|
|
1209
|
+
onFilterChange: isServerFilter ? handleFilterChange : undefined,
|
|
1210
|
+
filterProps: resolvedFilterProps,
|
|
1145
1211
|
selectionProps,
|
|
1146
1212
|
sortProps,
|
|
1147
1213
|
columnDefinitions,
|
|
1148
1214
|
getScrollableElement,
|
|
1149
1215
|
infiniteScrollProps,
|
|
1150
1216
|
});
|
|
1151
|
-
//
|
|
1217
|
+
// Fire onDataSourceStateChange when dataSource state changes
|
|
1218
|
+
React.useEffect(() => {
|
|
1219
|
+
if (dataSource && onDataSourceStateChange) {
|
|
1220
|
+
onDataSourceStateChange({
|
|
1221
|
+
data: sourceData,
|
|
1222
|
+
currentPage,
|
|
1223
|
+
hasMore,
|
|
1224
|
+
totalCount,
|
|
1225
|
+
isLoading: isSourceLoading,
|
|
1226
|
+
isFetchingMore,
|
|
1227
|
+
error,
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
}, [dataSource, sourceData, currentPage, hasMore, totalCount, isSourceLoading, isFetchingMore, error, onDataSourceStateChange]);
|
|
1231
|
+
// Fire onPageChange when page changes
|
|
1152
1232
|
React.useEffect(() => {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1233
|
+
if (dataSource && onPageChange) {
|
|
1234
|
+
onPageChange(currentPage);
|
|
1235
|
+
}
|
|
1236
|
+
}, [dataSource, currentPage, onPageChange]);
|
|
1237
|
+
// Fire onDataSourceError when error occurs
|
|
1238
|
+
React.useEffect(() => {
|
|
1239
|
+
if (dataSource && error && onDataSourceError) {
|
|
1240
|
+
onDataSourceError(error);
|
|
1241
|
+
}
|
|
1242
|
+
}, [dataSource, error, onDataSourceError]);
|
|
1156
1243
|
const hasData = React.useMemo(() => processedData.length > 0, [processedData]);
|
|
1157
1244
|
const noDataSvg = (React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "#ccc", height: "40", width: "40", viewBox: "0 0 24 24" },
|
|
1158
1245
|
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" })));
|
|
@@ -1220,6 +1307,31 @@ function ResponsiveTable(props) {
|
|
|
1220
1307
|
if (isLoading && !hasData) {
|
|
1221
1308
|
return React.createElement(SkeletonView, { isMobile: isMobile, columnDefinitions: visibleColumns });
|
|
1222
1309
|
}
|
|
1310
|
+
if (error && !isLoading && !hasData) {
|
|
1311
|
+
return (React.createElement("div", { style: {
|
|
1312
|
+
display: 'flex',
|
|
1313
|
+
flexDirection: 'column',
|
|
1314
|
+
alignItems: 'center',
|
|
1315
|
+
justifyContent: 'center',
|
|
1316
|
+
padding: '4rem 2rem',
|
|
1317
|
+
gap: '1rem',
|
|
1318
|
+
color: '#6c757d',
|
|
1319
|
+
border: '2px dashed #e0e0e0',
|
|
1320
|
+
borderRadius: '12px',
|
|
1321
|
+
backgroundColor: '#f8f9fa',
|
|
1322
|
+
} },
|
|
1323
|
+
React.createElement("div", { style: { fontWeight: 500, fontSize: '1.1rem' } }, "Failed to load data"),
|
|
1324
|
+
React.createElement("div", { style: { fontSize: '0.85rem', textAlign: 'center' } }, error.message),
|
|
1325
|
+
React.createElement("button", { onClick: resetAndFetch, style: {
|
|
1326
|
+
padding: '0.5rem 1.5rem',
|
|
1327
|
+
backgroundColor: '#007bff',
|
|
1328
|
+
color: '#fff',
|
|
1329
|
+
border: 'none',
|
|
1330
|
+
borderRadius: '6px',
|
|
1331
|
+
cursor: 'pointer',
|
|
1332
|
+
fontWeight: 500,
|
|
1333
|
+
} }, "Retry")));
|
|
1334
|
+
}
|
|
1223
1335
|
return (React.createElement(TableProvider, { value: {
|
|
1224
1336
|
data: currentDataToProcess,
|
|
1225
1337
|
processedData,
|
|
@@ -1238,6 +1350,7 @@ function ResponsiveTable(props) {
|
|
|
1238
1350
|
isLoading: isSourceLoading,
|
|
1239
1351
|
isFetchingMore,
|
|
1240
1352
|
loadNextPage,
|
|
1353
|
+
error,
|
|
1241
1354
|
} : undefined,
|
|
1242
1355
|
mobileCardClassName,
|
|
1243
1356
|
} },
|
|
@@ -1247,6 +1360,7 @@ function ResponsiveTable(props) {
|
|
|
1247
1360
|
(hasData || isLoading) && isMobile && (React.createElement(MobileView, { mobileFooter: mobileFooter })),
|
|
1248
1361
|
(hasData || isLoading) && !isMobile && (React.createElement(DesktopView, { maxHeight: maxHeight, isHeaderSticky: isHeaderSticky, tableContainerRef: tableContainerRef, headerRef: headerRef, footerRows: footerRows, renderPluginFooters: renderPluginFooters })))));
|
|
1249
1362
|
}
|
|
1363
|
+
const ResponsiveTable = React.forwardRef(ResponsiveTableInner);
|
|
1250
1364
|
|
|
1251
1365
|
class InfiniteScrollPlugin {
|
|
1252
1366
|
constructor() {
|
|
@@ -1306,4 +1420,5 @@ exports.InfiniteScrollPlugin = InfiniteScrollPlugin;
|
|
|
1306
1420
|
exports.SelectionPlugin = SelectionPlugin;
|
|
1307
1421
|
exports.SortPlugin = SortPlugin;
|
|
1308
1422
|
exports.default = ResponsiveTable;
|
|
1423
|
+
exports.useTableContext = useTableContext;
|
|
1309
1424
|
//# sourceMappingURL=index.js.map
|