@ackplus/react-tanstack-data-table 1.1.11 → 1.1.13

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.
Files changed (61) hide show
  1. package/README.md +143 -11
  2. package/dist/lib/components/droupdown/menu-dropdown.d.ts.map +1 -1
  3. package/dist/lib/components/droupdown/menu-dropdown.js +8 -1
  4. package/dist/lib/components/filters/filter-value-input.js +2 -2
  5. package/dist/lib/components/pagination/data-table-pagination.d.ts.map +1 -1
  6. package/dist/lib/components/pagination/data-table-pagination.js +10 -1
  7. package/dist/lib/components/toolbar/data-table-toolbar.d.ts.map +1 -1
  8. package/dist/lib/components/toolbar/data-table-toolbar.js +5 -2
  9. package/dist/lib/components/toolbar/table-export-control.d.ts.map +1 -1
  10. package/dist/lib/components/toolbar/table-export-control.js +46 -12
  11. package/dist/lib/components/toolbar/table-refresh-control.d.ts +15 -0
  12. package/dist/lib/components/toolbar/table-refresh-control.d.ts.map +1 -0
  13. package/dist/lib/components/toolbar/table-refresh-control.js +61 -0
  14. package/dist/lib/contexts/data-table-context.d.ts +7 -10
  15. package/dist/lib/contexts/data-table-context.d.ts.map +1 -1
  16. package/dist/lib/contexts/data-table-context.js +5 -1
  17. package/dist/lib/data-table.d.ts.map +1 -1
  18. package/dist/lib/data-table.js +1110 -946
  19. package/dist/lib/features/column-filter.feature.js +38 -21
  20. package/dist/lib/features/selection.feature.d.ts.map +1 -1
  21. package/dist/lib/features/selection.feature.js +11 -3
  22. package/dist/lib/types/column.types.d.ts +19 -0
  23. package/dist/lib/types/column.types.d.ts.map +1 -1
  24. package/dist/lib/types/data-table-api.d.ts +25 -18
  25. package/dist/lib/types/data-table-api.d.ts.map +1 -1
  26. package/dist/lib/types/data-table.types.d.ts +37 -10
  27. package/dist/lib/types/data-table.types.d.ts.map +1 -1
  28. package/dist/lib/types/export.types.d.ts +57 -13
  29. package/dist/lib/types/export.types.d.ts.map +1 -1
  30. package/dist/lib/types/slots.types.d.ts +12 -1
  31. package/dist/lib/types/slots.types.d.ts.map +1 -1
  32. package/dist/lib/types/table.types.d.ts +1 -3
  33. package/dist/lib/types/table.types.d.ts.map +1 -1
  34. package/dist/lib/utils/debounced-fetch.utils.d.ts +8 -4
  35. package/dist/lib/utils/debounced-fetch.utils.d.ts.map +1 -1
  36. package/dist/lib/utils/debounced-fetch.utils.js +63 -14
  37. package/dist/lib/utils/export-utils.d.ts +14 -4
  38. package/dist/lib/utils/export-utils.d.ts.map +1 -1
  39. package/dist/lib/utils/export-utils.js +362 -66
  40. package/dist/lib/utils/slot-helpers.d.ts +1 -1
  41. package/dist/lib/utils/slot-helpers.d.ts.map +1 -1
  42. package/package.json +4 -2
  43. package/src/lib/components/droupdown/menu-dropdown.tsx +9 -3
  44. package/src/lib/components/filters/filter-value-input.tsx +2 -2
  45. package/src/lib/components/pagination/data-table-pagination.tsx +14 -2
  46. package/src/lib/components/toolbar/data-table-toolbar.tsx +15 -1
  47. package/src/lib/components/toolbar/table-export-control.tsx +65 -9
  48. package/src/lib/components/toolbar/table-refresh-control.tsx +58 -0
  49. package/src/lib/contexts/data-table-context.tsx +16 -2
  50. package/src/lib/data-table.tsx +1282 -932
  51. package/src/lib/features/column-filter.feature.ts +40 -19
  52. package/src/lib/features/selection.feature.ts +11 -5
  53. package/src/lib/types/column.types.ts +20 -1
  54. package/src/lib/types/data-table-api.ts +37 -15
  55. package/src/lib/types/data-table.types.ts +59 -3
  56. package/src/lib/types/export.types.ts +79 -10
  57. package/src/lib/types/slots.types.ts +11 -1
  58. package/src/lib/types/table.types.ts +1 -3
  59. package/src/lib/utils/debounced-fetch.utils.ts +90 -18
  60. package/src/lib/utils/export-utils.ts +496 -69
  61. package/src/lib/utils/slot-helpers.tsx +1 -1
@@ -1,6 +1,6 @@
1
1
  // data-table-pagination.tsx
2
2
  import { TablePagination, Box, TablePaginationProps, SxProps } from '@mui/material';
3
- import { memo, ReactNode } from 'react';
3
+ import { memo, ReactNode, useEffect, useMemo } from 'react';
4
4
 
5
5
  import { useDataTableContext } from '../../contexts/data-table-context';
6
6
  import { mergeSlotProps } from '../../utils/slot-helpers';
@@ -33,6 +33,18 @@ export const DataTablePagination = memo((props: DataTablePaginationProps) => {
33
33
  } = props;
34
34
 
35
35
  const { table, tableSize } = useDataTableContext();
36
+ const pageSize = pagination?.pageSize || 1;
37
+ const maxPageIndex = Math.max(0, Math.ceil(totalRow / pageSize) - 1);
38
+ const safePageIndex = useMemo(
39
+ () => Math.min(Math.max(pagination?.pageIndex ?? 0, 0), maxPageIndex),
40
+ [maxPageIndex, pagination?.pageIndex]
41
+ );
42
+
43
+ useEffect(() => {
44
+ if ((pagination?.pageIndex ?? 0) !== safePageIndex) {
45
+ table?.setPageIndex(safePageIndex);
46
+ }
47
+ }, [pagination?.pageIndex, safePageIndex, table]);
36
48
 
37
49
  // Extract slot-specific props with enhanced merging
38
50
  // const paginationSlotProps = extractSlotProps(slotProps, 'pagination');
@@ -59,7 +71,7 @@ export const DataTablePagination = memo((props: DataTablePaginationProps) => {
59
71
  size: tableSize === 'small' ? 'small' : 'medium',
60
72
  count: totalRow,
61
73
  rowsPerPage: pagination?.pageSize,
62
- page: pagination?.pageIndex,
74
+ page: safePageIndex,
63
75
  onPageChange: (_, page: number) => {
64
76
  // Use TanStack Table's native pagination methods
65
77
  table.setPageIndex(page);
@@ -16,6 +16,7 @@ import { TableExportControl } from './table-export-control';
16
16
  import { TableSearchControl } from './table-search-control';
17
17
  import { TableSizeControl } from './table-size-control';
18
18
  import { useDataTableContext } from '../../contexts/data-table-context';
19
+ import { TableRefreshControl } from './table-refresh-control';
19
20
  import { getSlotComponent, mergeSlotProps, extractSlotProps } from '../../utils/slot-helpers';
20
21
 
21
22
  export interface DataTableToolbarProps extends ToolbarProps {
@@ -48,6 +49,7 @@ export function DataTableToolbar(props: DataTableToolbarProps = {}): ReactElemen
48
49
  enableColumnVisibility = true,
49
50
  enableExport = true,
50
51
  enableReset = true,
52
+ enableRefresh = false,
51
53
  enableColumnFilter = true,
52
54
  enableTableSizeControl = true,
53
55
  enableColumnPinning = true,
@@ -70,7 +72,8 @@ export function DataTableToolbar(props: DataTableToolbarProps = {}): ReactElemen
70
72
  const columnVisibilityControlSlotProps = extractSlotProps(slotProps, 'columnVisibilityControl');
71
73
  const resetButtonSlotProps = extractSlotProps(slotProps, 'resetButton');
72
74
  const exportButtonSlotProps = extractSlotProps(slotProps, 'exportButton');
73
-
75
+ const refreshButtonSlotProps = extractSlotProps(slotProps, 'refreshButton');
76
+
74
77
  const ToolbarSlot = getSlotComponent(slots, 'toolbar', Toolbar);
75
78
  const TableSearchControlSlot = getSlotComponent(slots, 'searchInput', TableSearchControl);
76
79
  const TableSizeControlSlot = getSlotComponent(slots, 'tableSizeControl', TableSizeControl);
@@ -79,6 +82,7 @@ export function DataTableToolbar(props: DataTableToolbarProps = {}): ReactElemen
79
82
  const ColumnVisibilityControlSlot = getSlotComponent(slots, 'columnVisibilityControl', ColumnVisibilityControl);
80
83
  const ColumnResetControlSlot = getSlotComponent(slots, 'resetButton', ColumnResetControl);
81
84
  const TableExportControlSlot = getSlotComponent(slots, 'exportButton', TableExportControl);
85
+ const TableRefreshControlSlot = getSlotComponent(slots, 'refreshButton', TableRefreshControl);
82
86
 
83
87
  // Merge all props for maximum flexibility
84
88
  const mergedToolbarProps = mergeSlotProps(
@@ -210,6 +214,16 @@ export function DataTableToolbar(props: DataTableToolbarProps = {}): ReactElemen
210
214
  )}
211
215
  />
212
216
  ) : null}
217
+
218
+ {enableRefresh ? (
219
+ <TableRefreshControlSlot
220
+ {...mergeSlotProps(
221
+ {},
222
+ refreshButtonSlotProps,
223
+ props.refreshButtonProps || {}
224
+ )}
225
+ />
226
+ ) : null}
213
227
  </Stack>
214
228
 
215
229
  {/* Right Section - Extra Filter and More Menu */}
@@ -10,6 +10,8 @@ import {
10
10
  Box,
11
11
  IconButtonProps,
12
12
  SxProps,
13
+ Divider,
14
+ LinearProgress,
13
15
  } from '@mui/material';
14
16
 
15
17
  import { MenuDropdown } from '../droupdown/menu-dropdown';
@@ -46,6 +48,9 @@ export function TableExportControl(props: TableExportControlProps = {}) {
46
48
  slots,
47
49
  slotProps,
48
50
  isExporting,
51
+ exportPhase,
52
+ exportProgress,
53
+ onCancelExport,
49
54
  // Export callbacks from context (DataTable props)
50
55
  exportFilename: contextExportFilename,
51
56
  } = useDataTableContext();
@@ -80,11 +85,18 @@ export function TableExportControl(props: TableExportControlProps = {}) {
80
85
  }
81
86
  };
82
87
 
88
+ const handleCancelExport = () => {
89
+ if (onCancelExport) {
90
+ onCancelExport();
91
+ return;
92
+ }
93
+ apiRef?.current?.export.cancelExport();
94
+ };
95
+
83
96
  // Merge all props for maximum flexibility
84
97
  const mergedIconButtonProps = mergeSlotProps(
85
98
  {
86
99
  size: 'small',
87
- disabled: isExporting,
88
100
  sx: { flexShrink: 0 },
89
101
  },
90
102
  exportIconSlotProps,
@@ -98,6 +110,25 @@ export function TableExportControl(props: TableExportControlProps = {}) {
98
110
  menuItemProps || {}
99
111
  );
100
112
 
113
+ const progressPercentage = typeof exportProgress?.percentage === 'number'
114
+ ? Math.max(0, Math.min(100, exportProgress.percentage))
115
+ : undefined;
116
+
117
+ const getPhaseLabel = () => {
118
+ switch (exportPhase) {
119
+ case 'fetching':
120
+ return 'Fetching rows from server...';
121
+ case 'processing':
122
+ return 'Preparing export file...';
123
+ case 'downloading':
124
+ return 'Downloading file...';
125
+ case 'starting':
126
+ return 'Starting export...';
127
+ default:
128
+ return 'Export in progress...';
129
+ }
130
+ };
131
+
101
132
  return (
102
133
  <MenuDropdown
103
134
  anchor={(
@@ -162,15 +193,40 @@ export function TableExportControl(props: TableExportControlProps = {}) {
162
193
  </MenuItem>
163
194
 
164
195
  {isExporting && (
165
- <Box sx={{ p: 2, pt: 1 }}>
166
- <Typography
167
- variant="caption"
168
- color="text.secondary"
169
- sx={{ display: 'block', textAlign: 'center' }}
196
+ <>
197
+ <Box sx={{ px: 2, pb: 1 }}>
198
+ <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 0.75 }}>
199
+ {getPhaseLabel()}
200
+ </Typography>
201
+ <LinearProgress
202
+ variant={progressPercentage !== undefined ? 'determinate' : 'indeterminate'}
203
+ value={progressPercentage !== undefined ? progressPercentage : 0}
204
+ />
205
+ {(exportProgress?.processedRows !== undefined || exportProgress?.totalRows !== undefined) && (
206
+ <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.75 }}>
207
+ {`${exportProgress?.processedRows ?? 0}${exportProgress?.totalRows !== undefined ? ` / ${exportProgress.totalRows}` : ''}${progressPercentage !== undefined ? ` (${progressPercentage}%)` : ''}`}
208
+ </Typography>
209
+ )}
210
+ </Box>
211
+ <Divider sx={{ my: 1 }} />
212
+ <MenuItem
213
+ onClick={() => {
214
+ handleCancelExport();
215
+ handleClose();
216
+ }}
217
+ {...mergedMenuItemProps}
170
218
  >
171
- Export in progress...
172
- </Typography>
173
- </Box>
219
+ <ListItemText
220
+ primary="Cancel Export"
221
+ secondary="Stop current export job"
222
+ slotProps={{
223
+ primary: {
224
+ color: 'error.main',
225
+ },
226
+ }}
227
+ />
228
+ </MenuItem>
229
+ </>
174
230
  )}
175
231
  </Box>
176
232
  )}
@@ -0,0 +1,58 @@
1
+ import React, { ReactElement, useCallback } from 'react';
2
+ import { Refresh } from '@mui/icons-material';
3
+ import { IconButton, Tooltip, IconButtonProps, CircularProgress } from '@mui/material';
4
+
5
+ import { useDataTableContext } from '../../contexts/data-table-context';
6
+ import { extractSlotProps, getSlotComponent, mergeSlotProps } from '../../utils/slot-helpers';
7
+
8
+ export interface TableRefreshControlProps {
9
+ iconButtonProps?: IconButtonProps;
10
+ tooltipProps?: any;
11
+
12
+ /** optional override */
13
+ onRefresh?: () => void | Promise<void>;
14
+
15
+ /** disable + show spinner if true */
16
+ loading?: boolean;
17
+
18
+ /** use spinner instead of icon while loading */
19
+ showSpinnerWhileLoading?: boolean;
20
+
21
+ [key: string]: any;
22
+ }
23
+
24
+ export function TableRefreshControl(props: TableRefreshControlProps = {}): ReactElement {
25
+ const { apiRef, slots, slotProps } = useDataTableContext();
26
+
27
+ const refreshIconSlotProps = extractSlotProps(slotProps, 'refreshIcon');
28
+ const RefreshIconSlot = getSlotComponent(slots, 'refreshIcon', Refresh);
29
+
30
+ const handleRefresh = useCallback(() => {
31
+ if (props.onRefresh) return props.onRefresh();
32
+ // Default: use internal api
33
+ apiRef?.current?.data?.reload?.();
34
+ }, [props, apiRef]);
35
+
36
+ const mergedIconButtonProps = mergeSlotProps(
37
+ {
38
+ size: 'small',
39
+ onClick: handleRefresh,
40
+ disabled: !!props.loading,
41
+ sx: { flexShrink: 0 },
42
+ },
43
+ refreshIconSlotProps,
44
+ props.iconButtonProps || {}
45
+ );
46
+
47
+ return (
48
+ <Tooltip title="Refresh data" {...props.tooltipProps}>
49
+ <IconButton {...mergedIconButtonProps}>
50
+ {props.loading && props.showSpinnerWhileLoading ? (
51
+ <CircularProgress size={16} />
52
+ ) : (
53
+ <RefreshIconSlot {...refreshIconSlotProps} />
54
+ )}
55
+ </IconButton>
56
+ </Tooltip>
57
+ );
58
+ }
@@ -2,7 +2,9 @@ import { Table } from '@tanstack/react-table';
2
2
  import React, { createContext, useContext, ReactNode, useMemo, RefObject, ReactElement } from 'react';
3
3
 
4
4
  import { ColumnFilterState, TableSize } from '../types';
5
+ import { SelectionState } from '../features';
5
6
  import { DataTableApi } from '../types/data-table-api';
7
+ import { ExportPhase, ExportProgressPayload, ServerExportResult } from '../types/export.types';
6
8
 
7
9
 
8
10
  /**
@@ -22,14 +24,20 @@ interface DataTableContextValue<T = any> {
22
24
  // Export state - managed by the DataTable component
23
25
  isExporting?: boolean;
24
26
  exportController?: AbortController | null;
27
+ exportPhase?: ExportPhase | null;
28
+ exportProgress?: ExportProgressPayload;
25
29
  onCancelExport?: () => void;
26
30
 
27
31
  // Export callbacks - passed from DataTable props
28
32
  exportFilename?: string;
29
- onExportProgress?: (progress: { processedRows?: number; totalRows?: number; percentage?: number }) => void;
33
+ onExportProgress?: (progress: ExportProgressPayload) => void;
30
34
  onExportComplete?: (result: { success: boolean; filename: string; totalRows: number }) => void;
31
35
  onExportError?: (error: { message: string; code: string }) => void;
32
- onServerExport?: (filters?: Partial<any>) => Promise<{ data: any[]; total: number }>;
36
+ onServerExport?: (
37
+ filters?: Partial<any>,
38
+ selection?: SelectionState,
39
+ signal?: AbortSignal
40
+ ) => Promise<ServerExportResult<any>>;
33
41
  }
34
42
 
35
43
  const DataTableContext = createContext<DataTableContextValue | null>(null);
@@ -51,6 +59,8 @@ export function DataTableProvider<T = any>({
51
59
  slotProps = {},
52
60
  isExporting,
53
61
  exportController,
62
+ exportPhase,
63
+ exportProgress,
54
64
  onCancelExport,
55
65
  exportFilename,
56
66
  onExportProgress,
@@ -70,6 +80,8 @@ export function DataTableProvider<T = any>({
70
80
  slotProps,
71
81
  isExporting,
72
82
  exportController,
83
+ exportPhase,
84
+ exportProgress,
73
85
  onCancelExport,
74
86
  exportFilename,
75
87
  onExportProgress,
@@ -88,6 +100,8 @@ export function DataTableProvider<T = any>({
88
100
  slotProps,
89
101
  isExporting,
90
102
  exportController,
103
+ exportPhase,
104
+ exportProgress,
91
105
  onCancelExport,
92
106
  exportFilename,
93
107
  onExportProgress,