@ackplus/react-tanstack-data-table 1.1.12 → 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 (51) 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/table-export-control.d.ts.map +1 -1
  8. package/dist/lib/components/toolbar/table-export-control.js +46 -12
  9. package/dist/lib/contexts/data-table-context.d.ts +7 -10
  10. package/dist/lib/contexts/data-table-context.d.ts.map +1 -1
  11. package/dist/lib/contexts/data-table-context.js +5 -1
  12. package/dist/lib/data-table.d.ts.map +1 -1
  13. package/dist/lib/data-table.js +561 -230
  14. package/dist/lib/features/column-filter.feature.js +38 -21
  15. package/dist/lib/features/selection.feature.d.ts.map +1 -1
  16. package/dist/lib/features/selection.feature.js +11 -3
  17. package/dist/lib/types/column.types.d.ts +19 -0
  18. package/dist/lib/types/column.types.d.ts.map +1 -1
  19. package/dist/lib/types/data-table-api.d.ts +24 -18
  20. package/dist/lib/types/data-table-api.d.ts.map +1 -1
  21. package/dist/lib/types/data-table.types.d.ts +36 -10
  22. package/dist/lib/types/data-table.types.d.ts.map +1 -1
  23. package/dist/lib/types/export.types.d.ts +57 -13
  24. package/dist/lib/types/export.types.d.ts.map +1 -1
  25. package/dist/lib/types/slots.types.d.ts +3 -1
  26. package/dist/lib/types/slots.types.d.ts.map +1 -1
  27. package/dist/lib/types/table.types.d.ts +1 -3
  28. package/dist/lib/types/table.types.d.ts.map +1 -1
  29. package/dist/lib/utils/debounced-fetch.utils.d.ts +8 -4
  30. package/dist/lib/utils/debounced-fetch.utils.d.ts.map +1 -1
  31. package/dist/lib/utils/debounced-fetch.utils.js +63 -14
  32. package/dist/lib/utils/export-utils.d.ts +14 -4
  33. package/dist/lib/utils/export-utils.d.ts.map +1 -1
  34. package/dist/lib/utils/export-utils.js +362 -66
  35. package/package.json +4 -2
  36. package/src/lib/components/droupdown/menu-dropdown.tsx +9 -3
  37. package/src/lib/components/filters/filter-value-input.tsx +2 -2
  38. package/src/lib/components/pagination/data-table-pagination.tsx +14 -2
  39. package/src/lib/components/toolbar/table-export-control.tsx +65 -9
  40. package/src/lib/contexts/data-table-context.tsx +16 -2
  41. package/src/lib/data-table.tsx +703 -222
  42. package/src/lib/features/column-filter.feature.ts +40 -19
  43. package/src/lib/features/selection.feature.ts +11 -5
  44. package/src/lib/types/column.types.ts +20 -1
  45. package/src/lib/types/data-table-api.ts +33 -15
  46. package/src/lib/types/data-table.types.ts +58 -3
  47. package/src/lib/types/export.types.ts +79 -10
  48. package/src/lib/types/slots.types.ts +3 -1
  49. package/src/lib/types/table.types.ts +1 -3
  50. package/src/lib/utils/debounced-fetch.utils.ts +90 -18
  51. package/src/lib/utils/export-utils.ts +496 -69
@@ -89,7 +89,7 @@ const DEFAULT_INITIAL_STATE = {
89
89
  */
90
90
  exports.DataTable = (0, react_1.forwardRef)(function DataTable({ initialState, columns, data = [], totalRow = 0, idKey = 'id', extraFilter = null, footerFilter = null,
91
91
  // Data management mode (MUI DataGrid style)
92
- dataMode = 'client', initialLoadData = true, onFetchData, onDataStateChange,
92
+ dataMode = 'client', initialLoadData = true, onFetchData, onRefreshData, onDataChange, onDataStateChange,
93
93
  // Selection props
94
94
  enableRowSelection = false, enableMultiRowSelection = true, selectMode = 'page', isRowSelectable, onSelectionChange,
95
95
  // Row click props
@@ -111,7 +111,7 @@ enablePagination = false, paginationMode = 'client',
111
111
  // Filtering props
112
112
  enableGlobalFilter = true, enableColumnFilter = false, filterMode = 'client',
113
113
  // Sorting props
114
- enableSorting = true, sortingMode = 'client', onSortingChange, exportFilename = 'export', onExportProgress, onExportComplete, onExportError, onServerExport, onExportCancel,
114
+ enableSorting = true, sortingMode = 'client', onSortingChange, exportFilename = 'export', exportConcurrency = 'cancelAndRestart', exportChunkSize = 1000, exportStrictTotalCheck = false, exportSanitizeCSV = true, onExportProgress, onExportComplete, onExportError, onServerExport, onExportCancel, onExportStateChange,
115
115
  // Styling props
116
116
  enableHover = true, enableStripes = false, tableProps = {}, fitToScreen = true, tableSize: initialTableSize = 'medium',
117
117
  // Sticky header/footer props
@@ -184,14 +184,24 @@ logging, }, ref) {
184
184
  const [serverData, setServerData] = (0, react_1.useState)(null);
185
185
  const [serverTotal, setServerTotal] = (0, react_1.useState)(0);
186
186
  const [exportController, setExportController] = (0, react_1.useState)(null);
187
+ const [exportProgress, setExportProgress] = (0, react_1.useState)({});
188
+ const [exportPhase, setExportPhase] = (0, react_1.useState)(null);
189
+ const [queuedExportCount, setQueuedExportCount] = (0, react_1.useState)(0);
187
190
  // -------------------------------
188
191
  // Ref hooks (grouped together)
189
192
  // -------------------------------
190
193
  const tableContainerRef = (0, react_1.useRef)(null);
191
194
  const internalApiRef = (0, react_1.useRef)(null);
195
+ const exportControllerRef = (0, react_1.useRef)(null);
196
+ const exportQueueRef = (0, react_1.useRef)(Promise.resolve());
197
+ const isExternallyControlledData = (0, react_1.useMemo)(() => !onFetchData && (!!onDataChange || !!onRefreshData), [onFetchData, onDataChange, onRefreshData]);
192
198
  const { debouncedFetch, isLoading: fetchLoading } = (0, debounced_fetch_utils_1.useDebouncedFetch)(onFetchData);
193
- const tableData = (0, react_1.useMemo)(() => serverData ? serverData : data, [serverData, data]);
194
- const tableTotalRow = (0, react_1.useMemo)(() => serverData ? serverTotal : totalRow || data.length, [serverData, serverTotal, totalRow, data]);
199
+ const tableData = (0, react_1.useMemo)(() => {
200
+ if (isExternallyControlledData)
201
+ return data;
202
+ return serverData !== null ? serverData : data;
203
+ }, [isExternallyControlledData, serverData, data]);
204
+ const tableTotalRow = (0, react_1.useMemo)(() => (isExternallyControlledData ? (totalRow || data.length) : (serverData !== null ? serverTotal : totalRow || data.length)), [isExternallyControlledData, serverData, serverTotal, totalRow, data]);
195
205
  const tableLoading = (0, react_1.useMemo)(() => onFetchData ? (loading || fetchLoading) : loading, [onFetchData, loading, fetchLoading]);
196
206
  const enhancedColumns = (0, react_1.useMemo)(() => {
197
207
  let columnsMap = [...columns];
@@ -239,7 +249,7 @@ logging, }, ref) {
239
249
  // Callback hooks (grouped together)
240
250
  // -------------------------------
241
251
  const fetchData = (0, react_1.useCallback)(async (overrides = {}, options) => {
242
- var _a, _b, _c;
252
+ var _a, _b, _c, _d, _e;
243
253
  if (!onFetchData) {
244
254
  if (logger.isLevelEnabled('debug')) {
245
255
  logger.debug('onFetchData not provided, skipping fetch', { overrides, columnFilter, sorting, pagination });
@@ -253,20 +263,26 @@ logging, }, ref) {
253
263
  sorting,
254
264
  ...overrides,
255
265
  };
256
- console.log('Fetching data', filters);
257
266
  if (logger.isLevelEnabled('info')) {
258
- logger.info('Requesting data', { filters });
267
+ logger.info('Requesting data', {
268
+ filters,
269
+ reason: (_a = options === null || options === void 0 ? void 0 : options.meta) === null || _a === void 0 ? void 0 : _a.reason,
270
+ force: (_b = options === null || options === void 0 ? void 0 : options.meta) === null || _b === void 0 ? void 0 : _b.force,
271
+ });
259
272
  }
260
273
  try {
261
- const delay = (_a = options === null || options === void 0 ? void 0 : options.delay) !== null && _a !== void 0 ? _a : 300; // respects 0
262
- const result = await debouncedFetch(filters, delay);
274
+ const delay = (_c = options === null || options === void 0 ? void 0 : options.delay) !== null && _c !== void 0 ? _c : 300; // respects 0
275
+ const result = await debouncedFetch(filters, {
276
+ debounceDelay: delay,
277
+ meta: options === null || options === void 0 ? void 0 : options.meta,
278
+ });
263
279
  if (logger.isLevelEnabled('info')) {
264
280
  logger.info('Fetch resolved', {
265
- rows: (_c = (_b = result === null || result === void 0 ? void 0 : result.data) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0,
281
+ rows: (_e = (_d = result === null || result === void 0 ? void 0 : result.data) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : 0,
266
282
  total: result === null || result === void 0 ? void 0 : result.total,
267
283
  });
268
284
  }
269
- if ((result === null || result === void 0 ? void 0 : result.data) && (result === null || result === void 0 ? void 0 : result.total) !== undefined) {
285
+ if (result && Array.isArray(result.data) && result.total !== undefined) {
270
286
  setServerData(result.data);
271
287
  setServerTotal(result.total);
272
288
  }
@@ -288,6 +304,21 @@ logging, }, ref) {
288
304
  debouncedFetch,
289
305
  logger,
290
306
  ]);
307
+ const normalizeRefreshOptions = (0, react_1.useCallback)((options, fallbackReason = 'refresh') => {
308
+ var _a, _b, _c;
309
+ if (typeof options === 'boolean') {
310
+ return {
311
+ resetPagination: options,
312
+ force: false,
313
+ reason: fallbackReason,
314
+ };
315
+ }
316
+ return {
317
+ resetPagination: (_a = options === null || options === void 0 ? void 0 : options.resetPagination) !== null && _a !== void 0 ? _a : false,
318
+ force: (_b = options === null || options === void 0 ? void 0 : options.force) !== null && _b !== void 0 ? _b : false,
319
+ reason: (_c = options === null || options === void 0 ? void 0 : options.reason) !== null && _c !== void 0 ? _c : fallbackReason,
320
+ };
321
+ }, []);
291
322
  const handleSelectionStateChange = (0, react_1.useCallback)((updaterOrValue) => {
292
323
  setSelectionState((prevState) => {
293
324
  const next = typeof updaterOrValue === 'function' ? updaterOrValue(prevState) : updaterOrValue;
@@ -525,12 +556,21 @@ logging, }, ref) {
525
556
  // -------------------------------
526
557
  // Effects (after callbacks)
527
558
  // -------------------------------
559
+ (0, react_1.useEffect)(() => {
560
+ if (!isExternallyControlledData || serverData === null)
561
+ return;
562
+ setServerData(null);
563
+ setServerTotal(0);
564
+ }, [isExternallyControlledData, serverData]);
528
565
  (0, react_1.useEffect)(() => {
529
566
  if (initialLoadData && onFetchData) {
530
567
  if (logger.isLevelEnabled('info')) {
531
568
  logger.info('Initial data load triggered', { initialLoadData });
532
569
  }
533
- fetchData({});
570
+ fetchData({}, {
571
+ delay: 0,
572
+ meta: { reason: 'initial' },
573
+ });
534
574
  }
535
575
  else if (logger.isLevelEnabled('debug')) {
536
576
  logger.debug('Skipping initial data load', {
@@ -559,12 +599,13 @@ logging, }, ref) {
559
599
  if (!onDataStateChange)
560
600
  return;
561
601
  const live = table.getState();
602
+ const liveColumnFilter = live.columnFilter;
562
603
  // only keep what you persist/store
563
604
  const payload = {
564
605
  sorting: live.sorting,
565
606
  pagination: live.pagination,
566
607
  globalFilter: live.globalFilter,
567
- columnFilter: live.columnFilter,
608
+ columnFilter: liveColumnFilter,
568
609
  columnVisibility: live.columnVisibility,
569
610
  columnSizing: live.columnSizing,
570
611
  columnOrder: live.columnOrder,
@@ -593,7 +634,7 @@ logging, }, ref) {
593
634
  var _a;
594
635
  const resetSorting = initialStateConfig.sorting || [];
595
636
  const resetGlobalFilter = (_a = initialStateConfig.globalFilter) !== null && _a !== void 0 ? _a : '';
596
- const resetColumnFilter = initialStateConfig.columnFilter || { filters: [], logic: 'AND', pendingFilters: [], pendingLogic: 'AND' };
637
+ const resetColumnFilter = initialStateConfig.columnFilter;
597
638
  const resetPagination = enablePagination
598
639
  ? (initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 })
599
640
  : undefined;
@@ -604,14 +645,109 @@ logging, }, ref) {
604
645
  ...(resetPagination ? { pagination: resetPagination } : {}),
605
646
  };
606
647
  }, [initialStateConfig, enablePagination]);
648
+ const applyDataMutation = (0, react_1.useCallback)((action, updater, details = {}) => {
649
+ const previousData = [...tableData];
650
+ const nextData = updater(previousData);
651
+ if (nextData === previousData)
652
+ return nextData;
653
+ const nextTotal = Math.max(0, tableTotalRow + (nextData.length - previousData.length));
654
+ if (!isExternallyControlledData) {
655
+ setServerData(nextData);
656
+ setServerTotal(nextTotal);
657
+ }
658
+ onDataChange === null || onDataChange === void 0 ? void 0 : onDataChange(nextData, {
659
+ action,
660
+ previousData,
661
+ nextData,
662
+ totalRow: nextTotal,
663
+ ...details,
664
+ });
665
+ if (logger.isLevelEnabled('debug')) {
666
+ logger.debug('Applied data mutation', {
667
+ action,
668
+ previousCount: previousData.length,
669
+ nextCount: nextData.length,
670
+ totalRow: nextTotal,
671
+ });
672
+ }
673
+ return nextData;
674
+ }, [isExternallyControlledData, logger, onDataChange, tableData, tableTotalRow]);
675
+ const buildRefreshContext = (0, react_1.useCallback)((options, paginationOverride) => {
676
+ const state = table.getState();
677
+ const nextPagination = paginationOverride || state.pagination || pagination;
678
+ return {
679
+ filters: {
680
+ globalFilter,
681
+ pagination: nextPagination,
682
+ columnFilter,
683
+ sorting,
684
+ },
685
+ state: {
686
+ sorting,
687
+ pagination: nextPagination,
688
+ globalFilter,
689
+ columnFilter,
690
+ columnVisibility: state.columnVisibility,
691
+ columnSizing: state.columnSizing,
692
+ columnOrder: state.columnOrder,
693
+ columnPinning: state.columnPinning,
694
+ },
695
+ options,
696
+ };
697
+ }, [table, pagination, globalFilter, columnFilter, sorting]);
698
+ const triggerRefresh = (0, react_1.useCallback)(async (options, fallbackReason = 'refresh') => {
699
+ const normalizedOptions = normalizeRefreshOptions(options, fallbackReason);
700
+ const nextPagination = enablePagination
701
+ ? {
702
+ pageIndex: normalizedOptions.resetPagination ? 0 : pagination.pageIndex,
703
+ pageSize: pagination.pageSize,
704
+ }
705
+ : undefined;
706
+ const shouldUpdatePagination = !!nextPagination
707
+ && (nextPagination.pageIndex !== pagination.pageIndex || nextPagination.pageSize !== pagination.pageSize);
708
+ if (nextPagination && shouldUpdatePagination) {
709
+ setPagination(nextPagination);
710
+ onPaginationChange === null || onPaginationChange === void 0 ? void 0 : onPaginationChange(nextPagination);
711
+ }
712
+ const refreshContext = buildRefreshContext(normalizedOptions, nextPagination);
713
+ if (onRefreshData) {
714
+ await onRefreshData(refreshContext);
715
+ return;
716
+ }
717
+ if (onFetchData) {
718
+ await fetchData(nextPagination ? { pagination: nextPagination } : {}, {
719
+ delay: 0,
720
+ meta: {
721
+ reason: normalizedOptions.reason,
722
+ force: normalizedOptions.force,
723
+ },
724
+ });
725
+ return;
726
+ }
727
+ if (logger.isLevelEnabled('debug')) {
728
+ logger.debug('Refresh skipped because no refresh handler is configured', refreshContext);
729
+ }
730
+ }, [
731
+ normalizeRefreshOptions,
732
+ enablePagination,
733
+ pagination,
734
+ onPaginationChange,
735
+ buildRefreshContext,
736
+ onRefreshData,
737
+ onFetchData,
738
+ fetchData,
739
+ logger,
740
+ ]);
607
741
  const resetAllAndReload = (0, react_1.useCallback)(() => {
608
742
  var _a;
609
743
  const resetState = getResetState();
610
744
  setSorting(resetState.sorting || []);
611
745
  setGlobalFilter((_a = resetState.globalFilter) !== null && _a !== void 0 ? _a : '');
612
746
  setColumnFilter(resetState.columnFilter);
613
- if (resetState.pagination)
747
+ if (resetState.pagination) {
614
748
  setPagination(resetState.pagination);
749
+ onPaginationChange === null || onPaginationChange === void 0 ? void 0 : onPaginationChange(resetState.pagination);
750
+ }
615
751
  setSelectionState(initialSelectionState);
616
752
  setExpanded({});
617
753
  // layout state
@@ -619,9 +755,114 @@ logging, }, ref) {
619
755
  setColumnSizing(initialStateConfig.columnSizing || {});
620
756
  setColumnOrder(initialStateConfig.columnOrder || []);
621
757
  setColumnPinning(initialStateConfig.columnPinning || { left: [], right: [] });
622
- if (onFetchData)
623
- fetchData(resetState, { delay: 0 });
624
- }, [getResetState, initialSelectionState, initialStateConfig, onFetchData, fetchData]);
758
+ const resetOptions = normalizeRefreshOptions({
759
+ resetPagination: true,
760
+ force: true,
761
+ reason: 'reset',
762
+ }, 'reset');
763
+ const refreshContext = buildRefreshContext(resetOptions, resetState.pagination);
764
+ if (onRefreshData) {
765
+ void onRefreshData(refreshContext);
766
+ return;
767
+ }
768
+ if (onFetchData) {
769
+ void fetchData(resetState, {
770
+ delay: 0,
771
+ meta: {
772
+ reason: resetOptions.reason,
773
+ force: resetOptions.force,
774
+ },
775
+ });
776
+ }
777
+ }, [
778
+ getResetState,
779
+ initialSelectionState,
780
+ initialStateConfig,
781
+ onPaginationChange,
782
+ normalizeRefreshOptions,
783
+ buildRefreshContext,
784
+ onRefreshData,
785
+ onFetchData,
786
+ fetchData,
787
+ ]);
788
+ const setExportControllerSafely = (0, react_1.useCallback)((value) => {
789
+ setExportController((current) => {
790
+ const next = typeof value === 'function' ? value(current) : value;
791
+ exportControllerRef.current = next;
792
+ return next;
793
+ });
794
+ }, []);
795
+ const handleExportProgressInternal = (0, react_1.useCallback)((progress) => {
796
+ setExportProgress(progress || {});
797
+ onExportProgress === null || onExportProgress === void 0 ? void 0 : onExportProgress(progress);
798
+ }, [onExportProgress]);
799
+ const handleExportStateChangeInternal = (0, react_1.useCallback)((state) => {
800
+ setExportPhase(state.phase);
801
+ if (state.processedRows !== undefined
802
+ || state.totalRows !== undefined
803
+ || state.percentage !== undefined) {
804
+ setExportProgress({
805
+ processedRows: state.processedRows,
806
+ totalRows: state.totalRows,
807
+ percentage: state.percentage,
808
+ });
809
+ }
810
+ onExportStateChange === null || onExportStateChange === void 0 ? void 0 : onExportStateChange(state);
811
+ }, [onExportStateChange]);
812
+ const runExportWithPolicy = (0, react_1.useCallback)(async (options) => {
813
+ const { format, filename, mode, execute } = options;
814
+ const startExecution = async () => {
815
+ const controller = new AbortController();
816
+ setExportProgress({});
817
+ setExportControllerSafely(controller);
818
+ try {
819
+ await execute(controller);
820
+ }
821
+ finally {
822
+ setExportControllerSafely((current) => (current === controller ? null : current));
823
+ }
824
+ };
825
+ if (exportConcurrency === 'queue') {
826
+ setQueuedExportCount((prev) => prev + 1);
827
+ const runQueued = async () => {
828
+ setQueuedExportCount((prev) => Math.max(0, prev - 1));
829
+ await startExecution();
830
+ };
831
+ const queuedPromise = exportQueueRef.current
832
+ .catch(() => undefined)
833
+ .then(runQueued);
834
+ exportQueueRef.current = queuedPromise;
835
+ return queuedPromise;
836
+ }
837
+ const activeController = exportControllerRef.current;
838
+ if (activeController) {
839
+ if (exportConcurrency === 'ignoreIfRunning') {
840
+ handleExportStateChangeInternal({
841
+ phase: 'error',
842
+ mode,
843
+ format,
844
+ filename,
845
+ message: 'An export is already running',
846
+ code: 'EXPORT_IN_PROGRESS',
847
+ endedAt: Date.now(),
848
+ });
849
+ onExportError === null || onExportError === void 0 ? void 0 : onExportError({
850
+ message: 'An export is already running',
851
+ code: 'EXPORT_IN_PROGRESS',
852
+ });
853
+ return;
854
+ }
855
+ if (exportConcurrency === 'cancelAndRestart') {
856
+ activeController.abort();
857
+ }
858
+ }
859
+ await startExecution();
860
+ }, [
861
+ exportConcurrency,
862
+ handleExportStateChangeInternal,
863
+ onExportError,
864
+ setExportControllerSafely,
865
+ ]);
625
866
  const dataTableApi = (0, react_1.useMemo)(() => {
626
867
  // helpers (avoid repeating boilerplate)
627
868
  const buildInitialOrder = () => enhancedColumns.map((col, index) => {
@@ -654,6 +895,12 @@ logging, }, ref) {
654
895
  const applyGlobalFilter = (next) => {
655
896
  handleGlobalFilterChange(next);
656
897
  };
898
+ const getRowIndexById = (rowsToSearch, rowId) => rowsToSearch.findIndex((row, index) => String((0, utils_1.generateRowId)(row, index, idKey)) === rowId);
899
+ const clampInsertIndex = (rowsToMutate, insertIndex) => {
900
+ if (insertIndex === undefined)
901
+ return rowsToMutate.length;
902
+ return Math.max(0, Math.min(insertIndex, rowsToMutate.length));
903
+ };
657
904
  return {
658
905
  table: {
659
906
  getTable: () => table,
@@ -935,129 +1182,135 @@ logging, }, ref) {
935
1182
  // Data Management (kept same, but ensure state changes go through handlers)
936
1183
  // -------------------------------
937
1184
  data: {
938
- refresh: (resetPagination = false) => {
939
- var _a, _b, _c, _d;
940
- const allState = table.getState();
941
- const current = allState.pagination;
942
- const nextPagination = {
943
- pageIndex: resetPagination ? 0 : (_a = current === null || current === void 0 ? void 0 : current.pageIndex) !== null && _a !== void 0 ? _a : 0,
944
- pageSize: (_d = (_b = current === null || current === void 0 ? void 0 : current.pageSize) !== null && _b !== void 0 ? _b : (_c = initialStateConfig.pagination) === null || _c === void 0 ? void 0 : _c.pageSize) !== null && _d !== void 0 ? _d : 10,
945
- };
946
- // must go through handler so server fetch triggers
947
- applyPagination(nextPagination);
948
- // emit persisted state (your emitTableState effect will also do it)
949
- onDataStateChange === null || onDataStateChange === void 0 ? void 0 : onDataStateChange({ ...allState, pagination: nextPagination });
950
- fetchData === null || fetchData === void 0 ? void 0 : fetchData({ pagination: nextPagination });
951
- if (logger.isLevelEnabled("debug")) {
952
- logger.debug("Refreshing data", { nextPagination, allState });
953
- }
1185
+ refresh: (options) => {
1186
+ void triggerRefresh(options, 'refresh');
954
1187
  },
955
- reload: () => {
956
- const allState = table.getState();
957
- onDataStateChange === null || onDataStateChange === void 0 ? void 0 : onDataStateChange(allState);
958
- fetchData === null || fetchData === void 0 ? void 0 : fetchData();
959
- if (logger.isLevelEnabled("debug")) {
960
- logger.info("Reloading data", allState);
961
- }
1188
+ reload: (options = {}) => {
1189
+ var _a, _b;
1190
+ void triggerRefresh({
1191
+ ...options,
1192
+ resetPagination: (_a = options.resetPagination) !== null && _a !== void 0 ? _a : false,
1193
+ reason: (_b = options.reason) !== null && _b !== void 0 ? _b : 'reload',
1194
+ }, 'reload');
962
1195
  },
963
- resetAll: () => resetAllAndReload({ resetLayout: true }),
964
- getAllData: () => { var _a; return ((_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.map((row) => row.original)) || []; },
965
- getRowData: (rowId) => { var _a, _b; return (_b = (_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.find((row) => String(row.original[idKey]) === rowId)) === null || _b === void 0 ? void 0 : _b.original; },
966
- getRowByIndex: (index) => { var _a, _b; return (_b = (_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a[index]) === null || _b === void 0 ? void 0 : _b.original; },
1196
+ resetAll: () => resetAllAndReload(),
1197
+ getAllData: () => [...tableData],
1198
+ getRowData: (rowId) => {
1199
+ const rowIndex = getRowIndexById(tableData, rowId);
1200
+ return rowIndex === -1 ? undefined : tableData[rowIndex];
1201
+ },
1202
+ getRowByIndex: (index) => tableData[index],
967
1203
  updateRow: (rowId, updates) => {
968
- var _a;
969
- const newData = (_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.map((row) => String(row.original[idKey]) === rowId ? { ...row.original, ...updates } : row.original);
970
- setServerData === null || setServerData === void 0 ? void 0 : setServerData(newData || []);
971
- if (logger.isLevelEnabled("debug"))
972
- logger.debug(`Updating row ${rowId}`, updates);
1204
+ applyDataMutation('updateRow', (rowsToMutate) => {
1205
+ const rowIndex = getRowIndexById(rowsToMutate, rowId);
1206
+ if (rowIndex === -1)
1207
+ return rowsToMutate;
1208
+ const nextData = [...rowsToMutate];
1209
+ nextData[rowIndex] = { ...nextData[rowIndex], ...updates };
1210
+ return nextData;
1211
+ }, { rowId });
973
1212
  },
974
1213
  updateRowByIndex: (index, updates) => {
975
- var _a;
976
- const newData = ((_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.map((row) => row.original)) || [];
977
- if (newData[index]) {
978
- newData[index] = { ...newData[index], ...updates };
979
- setServerData(newData);
980
- if (logger.isLevelEnabled("debug"))
981
- logger.debug(`Updating row by index ${index}`, updates);
982
- }
1214
+ applyDataMutation('updateRowByIndex', (rowsToMutate) => {
1215
+ if (!rowsToMutate[index])
1216
+ return rowsToMutate;
1217
+ const nextData = [...rowsToMutate];
1218
+ nextData[index] = { ...nextData[index], ...updates };
1219
+ return nextData;
1220
+ }, { index });
983
1221
  },
984
1222
  insertRow: (newRow, index) => {
985
- var _a;
986
- const newData = ((_a = table.getRowModel().rows) === null || _a === void 0 ? void 0 : _a.map((row) => row.original)) || [];
987
- if (index !== undefined)
988
- newData.splice(index, 0, newRow);
989
- else
990
- newData.push(newRow);
991
- setServerData(newData || []);
992
- if (logger.isLevelEnabled("debug"))
993
- logger.debug("Inserting row", newRow);
1223
+ applyDataMutation('insertRow', (rowsToMutate) => {
1224
+ const nextData = [...rowsToMutate];
1225
+ nextData.splice(clampInsertIndex(nextData, index), 0, newRow);
1226
+ return nextData;
1227
+ }, { index });
994
1228
  },
995
1229
  deleteRow: (rowId) => {
996
- const newData = (table.getRowModel().rows || []).filter((row) => String(row.original[idKey]) !== rowId);
997
- setServerData === null || setServerData === void 0 ? void 0 : setServerData(newData.map((r) => r.original) || []);
998
- if (logger.isLevelEnabled("debug"))
999
- logger.debug(`Deleting row ${rowId}`);
1230
+ applyDataMutation('deleteRow', (rowsToMutate) => {
1231
+ const rowIndex = getRowIndexById(rowsToMutate, rowId);
1232
+ if (rowIndex === -1)
1233
+ return rowsToMutate;
1234
+ const nextData = [...rowsToMutate];
1235
+ nextData.splice(rowIndex, 1);
1236
+ return nextData;
1237
+ }, { rowId });
1000
1238
  },
1001
1239
  deleteRowByIndex: (index) => {
1002
- const newData = (table.getRowModel().rows || []).map((row) => row.original);
1003
- newData.splice(index, 1);
1004
- setServerData(newData);
1005
- if (logger.isLevelEnabled("debug"))
1006
- logger.debug(`Deleting row by index ${index}`);
1240
+ applyDataMutation('deleteRowByIndex', (rowsToMutate) => {
1241
+ if (index < 0 || index >= rowsToMutate.length)
1242
+ return rowsToMutate;
1243
+ const nextData = [...rowsToMutate];
1244
+ nextData.splice(index, 1);
1245
+ return nextData;
1246
+ }, { index });
1007
1247
  },
1008
1248
  deleteSelectedRows: () => {
1009
- var _a, _b;
1010
- const selectedRows = ((_a = table.getSelectedRows) === null || _a === void 0 ? void 0 : _a.call(table)) || [];
1011
- if (selectedRows.length === 0)
1249
+ var _a, _b, _c;
1250
+ const currentSelection = ((_a = table.getSelectionState) === null || _a === void 0 ? void 0 : _a.call(table)) || selectionState;
1251
+ const selectedIds = new Set((currentSelection.ids || []).map((id) => String(id)));
1252
+ const loadedRowIds = tableData.map((row, index) => String((0, utils_1.generateRowId)(row, index, idKey)));
1253
+ const deletableRowIds = currentSelection.type === 'exclude'
1254
+ ? loadedRowIds.filter((rowId) => !selectedIds.has(rowId))
1255
+ : loadedRowIds.filter((rowId) => selectedIds.has(rowId));
1256
+ if (deletableRowIds.length === 0)
1012
1257
  return;
1013
- const selectedIds = new Set(selectedRows.map((row) => String(row.original[idKey])));
1014
- const newData = (table.getRowModel().rows || [])
1015
- .filter((row) => !selectedIds.has(String(row.original[idKey])))
1016
- .map((row) => row.original);
1017
- setServerData(newData);
1018
- (_b = table.deselectAll) === null || _b === void 0 ? void 0 : _b.call(table);
1019
- if (logger.isLevelEnabled("debug"))
1020
- logger.debug("Deleting selected rows");
1258
+ if (currentSelection.type === 'exclude'
1259
+ && table.getRowCount() > loadedRowIds.length
1260
+ && logger.isLevelEnabled('info')) {
1261
+ logger.info('deleteSelectedRows in exclude mode removed currently loaded rows only', {
1262
+ removedRows: deletableRowIds.length,
1263
+ totalSelected: (_b = table.getSelectedCount) === null || _b === void 0 ? void 0 : _b.call(table),
1264
+ });
1265
+ }
1266
+ const deletableRowIdSet = new Set(deletableRowIds);
1267
+ applyDataMutation('deleteSelectedRows', (rowsToMutate) => rowsToMutate.filter((row, index) => !deletableRowIdSet.has(String((0, utils_1.generateRowId)(row, index, idKey)))), { rowIds: deletableRowIds });
1268
+ (_c = table.deselectAll) === null || _c === void 0 ? void 0 : _c.call(table);
1269
+ },
1270
+ replaceAllData: (newData) => {
1271
+ applyDataMutation('replaceAllData', () => [...newData]);
1021
1272
  },
1022
- replaceAllData: (newData) => setServerData === null || setServerData === void 0 ? void 0 : setServerData(newData),
1023
1273
  updateMultipleRows: (updates) => {
1024
- const updateMap = new Map(updates.map((u) => [u.rowId, u.data]));
1025
- const newData = (table.getRowModel().rows || []).map((row) => {
1026
- const rowId = String(row.original[idKey]);
1027
- const updateData = updateMap.get(rowId);
1028
- return updateData ? { ...row.original, ...updateData } : row.original;
1029
- });
1030
- setServerData(newData || []);
1274
+ const updateMap = new Map(updates.map((update) => [update.rowId, update.data]));
1275
+ applyDataMutation('updateMultipleRows', (rowsToMutate) => rowsToMutate.map((row, index) => {
1276
+ const currentRowId = String((0, utils_1.generateRowId)(row, index, idKey));
1277
+ const updateData = updateMap.get(currentRowId);
1278
+ return updateData ? { ...row, ...updateData } : row;
1279
+ }));
1031
1280
  },
1032
1281
  insertMultipleRows: (newRows, startIndex) => {
1033
- const newData = (table.getRowModel().rows || []).map((row) => row.original);
1034
- if (startIndex !== undefined)
1035
- newData.splice(startIndex, 0, ...newRows);
1036
- else
1037
- newData.push(...newRows);
1038
- setServerData === null || setServerData === void 0 ? void 0 : setServerData(newData);
1282
+ applyDataMutation('insertMultipleRows', (rowsToMutate) => {
1283
+ const nextData = [...rowsToMutate];
1284
+ nextData.splice(clampInsertIndex(nextData, startIndex), 0, ...newRows);
1285
+ return nextData;
1286
+ }, { index: startIndex });
1039
1287
  },
1040
1288
  deleteMultipleRows: (rowIds) => {
1041
1289
  const idsToDelete = new Set(rowIds);
1042
- const newData = (table.getRowModel().rows || [])
1043
- .filter((row) => !idsToDelete.has(String(row.original[idKey])))
1044
- .map((row) => row.original);
1045
- setServerData(newData);
1290
+ applyDataMutation('deleteMultipleRows', (rowsToMutate) => rowsToMutate.filter((row, index) => !idsToDelete.has(String((0, utils_1.generateRowId)(row, index, idKey)))), { rowIds });
1046
1291
  },
1047
1292
  updateField: (rowId, fieldName, value) => {
1048
- const newData = (table.getRowModel().rows || []).map((row) => String(row.original[idKey]) === rowId ? { ...row.original, [fieldName]: value } : row.original);
1049
- setServerData === null || setServerData === void 0 ? void 0 : setServerData(newData);
1293
+ applyDataMutation('updateField', (rowsToMutate) => {
1294
+ const rowIndex = getRowIndexById(rowsToMutate, rowId);
1295
+ if (rowIndex === -1)
1296
+ return rowsToMutate;
1297
+ const nextData = [...rowsToMutate];
1298
+ nextData[rowIndex] = { ...nextData[rowIndex], [fieldName]: value };
1299
+ return nextData;
1300
+ }, { rowId });
1050
1301
  },
1051
1302
  updateFieldByIndex: (index, fieldName, value) => {
1052
- const newData = (table.getRowModel().rows || []).map((row) => row.original);
1053
- if (newData[index]) {
1054
- newData[index] = { ...newData[index], [fieldName]: value };
1055
- setServerData === null || setServerData === void 0 ? void 0 : setServerData(newData);
1056
- }
1303
+ applyDataMutation('updateFieldByIndex', (rowsToMutate) => {
1304
+ if (!rowsToMutate[index])
1305
+ return rowsToMutate;
1306
+ const nextData = [...rowsToMutate];
1307
+ nextData[index] = { ...nextData[index], [fieldName]: value };
1308
+ return nextData;
1309
+ }, { index });
1057
1310
  },
1058
- findRows: (predicate) => (table.getRowModel().rows || []).filter((row) => predicate(row.original)).map((row) => row.original),
1059
- findRowIndex: (predicate) => (table.getRowModel().rows || []).findIndex((row) => predicate(row.original)),
1060
- getDataCount: () => (table.getRowModel().rows || []).length || 0,
1311
+ findRows: (predicate) => tableData.filter(predicate),
1312
+ findRowIndex: (predicate) => tableData.findIndex(predicate),
1313
+ getDataCount: () => tableData.length,
1061
1314
  getFilteredDataCount: () => table.getFilteredRowModel().rows.length,
1062
1315
  },
1063
1316
  // -------------------------------
@@ -1072,7 +1325,7 @@ logging, }, ref) {
1072
1325
  applySorting(initialStateConfig.sorting || []);
1073
1326
  applyGlobalFilter((_a = initialStateConfig.globalFilter) !== null && _a !== void 0 ? _a : "");
1074
1327
  },
1075
- resetAll: () => resetAllAndReload({ resetLayout: true }),
1328
+ resetAll: () => resetAllAndReload(),
1076
1329
  saveLayout: () => ({
1077
1330
  columnVisibility: table.getState().columnVisibility,
1078
1331
  columnSizing: table.getState().columnSizing,
@@ -1117,141 +1370,212 @@ logging, }, ref) {
1117
1370
  // -------------------------------
1118
1371
  export: {
1119
1372
  exportCSV: async (options = {}) => {
1120
- var _a;
1121
- const { filename = exportFilename } = options;
1122
- try {
1123
- const controller = new AbortController();
1124
- setExportController === null || setExportController === void 0 ? void 0 : setExportController(controller);
1125
- if (dataMode === "server" && onServerExport) {
1126
- const currentFilters = {
1127
- globalFilter: table.getState().globalFilter,
1128
- columnFilter: table.getState().columnFilter,
1129
- sorting: table.getState().sorting,
1130
- pagination: table.getState().pagination,
1373
+ const { filename = exportFilename, chunkSize = exportChunkSize, strictTotalCheck = exportStrictTotalCheck, sanitizeCSV = exportSanitizeCSV, } = options;
1374
+ const mode = dataMode === "server" && !!onServerExport ? 'server' : 'client';
1375
+ await runExportWithPolicy({
1376
+ format: 'csv',
1377
+ filename,
1378
+ mode,
1379
+ execute: async (controller) => {
1380
+ var _a;
1381
+ const toStateChange = (state) => {
1382
+ const isFinalPhase = state.phase === 'completed' || state.phase === 'cancelled' || state.phase === 'error';
1383
+ handleExportStateChangeInternal({
1384
+ phase: state.phase,
1385
+ mode,
1386
+ format: 'csv',
1387
+ filename,
1388
+ processedRows: state.processedRows,
1389
+ totalRows: state.totalRows,
1390
+ percentage: state.percentage,
1391
+ message: state.message,
1392
+ code: state.code,
1393
+ startedAt: state.phase === 'starting' ? Date.now() : undefined,
1394
+ endedAt: isFinalPhase ? Date.now() : undefined,
1395
+ queueLength: queuedExportCount,
1396
+ });
1397
+ if (state.phase === 'cancelled') {
1398
+ onExportCancel === null || onExportCancel === void 0 ? void 0 : onExportCancel();
1399
+ }
1131
1400
  };
1132
- if (logger.isLevelEnabled("debug"))
1133
- logger.debug("Server export CSV", { currentFilters });
1134
- await (0, utils_1.exportServerData)(table, {
1135
- format: "csv",
1136
- filename,
1137
- fetchData: (filters, selection) => onServerExport(filters, selection),
1138
- currentFilters,
1139
- selection: (_a = table.getSelectionState) === null || _a === void 0 ? void 0 : _a.call(table),
1140
- onProgress: onExportProgress,
1141
- onComplete: onExportComplete,
1142
- onError: onExportError,
1143
- });
1144
- }
1145
- else {
1401
+ if (mode === 'server' && onServerExport) {
1402
+ const currentFilters = {
1403
+ globalFilter: table.getState().globalFilter,
1404
+ columnFilter: table.getState().columnFilter,
1405
+ sorting: table.getState().sorting,
1406
+ pagination: table.getState().pagination,
1407
+ };
1408
+ if (logger.isLevelEnabled("debug"))
1409
+ logger.debug("Server export CSV", { currentFilters });
1410
+ await (0, utils_1.exportServerData)(table, {
1411
+ format: "csv",
1412
+ filename,
1413
+ fetchData: (filters, selection, signal) => onServerExport(filters, selection, signal),
1414
+ currentFilters,
1415
+ selection: (_a = table.getSelectionState) === null || _a === void 0 ? void 0 : _a.call(table),
1416
+ onProgress: handleExportProgressInternal,
1417
+ onComplete: onExportComplete,
1418
+ onError: onExportError,
1419
+ onStateChange: toStateChange,
1420
+ signal: controller.signal,
1421
+ chunkSize,
1422
+ strictTotalCheck,
1423
+ sanitizeCSV,
1424
+ });
1425
+ return;
1426
+ }
1146
1427
  await (0, utils_1.exportClientData)(table, {
1147
1428
  format: "csv",
1148
1429
  filename,
1149
- onProgress: onExportProgress,
1430
+ onProgress: handleExportProgressInternal,
1150
1431
  onComplete: onExportComplete,
1151
1432
  onError: onExportError,
1433
+ onStateChange: toStateChange,
1434
+ signal: controller.signal,
1435
+ sanitizeCSV,
1152
1436
  });
1153
1437
  if (logger.isLevelEnabled("debug"))
1154
1438
  logger.debug("Client export CSV", filename);
1155
1439
  }
1156
- }
1157
- catch (error) {
1158
- onExportError === null || onExportError === void 0 ? void 0 : onExportError({ message: error.message || "Export failed", code: "EXPORT_ERROR" });
1159
- }
1160
- finally {
1161
- setExportController === null || setExportController === void 0 ? void 0 : setExportController(null);
1162
- }
1440
+ });
1163
1441
  },
1164
1442
  exportExcel: async (options = {}) => {
1165
- var _a;
1166
- const { filename = exportFilename } = options;
1167
- try {
1168
- const controller = new AbortController();
1169
- setExportController === null || setExportController === void 0 ? void 0 : setExportController(controller);
1170
- if (dataMode === "server" && onServerExport) {
1171
- const currentFilters = {
1172
- globalFilter: table.getState().globalFilter,
1173
- columnFilter: table.getState().columnFilter,
1174
- sorting: table.getState().sorting,
1175
- pagination: table.getState().pagination,
1443
+ const { filename = exportFilename, chunkSize = exportChunkSize, strictTotalCheck = exportStrictTotalCheck, sanitizeCSV = exportSanitizeCSV, } = options;
1444
+ const mode = dataMode === "server" && !!onServerExport ? 'server' : 'client';
1445
+ await runExportWithPolicy({
1446
+ format: 'excel',
1447
+ filename,
1448
+ mode,
1449
+ execute: async (controller) => {
1450
+ var _a;
1451
+ const toStateChange = (state) => {
1452
+ const isFinalPhase = state.phase === 'completed' || state.phase === 'cancelled' || state.phase === 'error';
1453
+ handleExportStateChangeInternal({
1454
+ phase: state.phase,
1455
+ mode,
1456
+ format: 'excel',
1457
+ filename,
1458
+ processedRows: state.processedRows,
1459
+ totalRows: state.totalRows,
1460
+ percentage: state.percentage,
1461
+ message: state.message,
1462
+ code: state.code,
1463
+ startedAt: state.phase === 'starting' ? Date.now() : undefined,
1464
+ endedAt: isFinalPhase ? Date.now() : undefined,
1465
+ queueLength: queuedExportCount,
1466
+ });
1467
+ if (state.phase === 'cancelled') {
1468
+ onExportCancel === null || onExportCancel === void 0 ? void 0 : onExportCancel();
1469
+ }
1176
1470
  };
1177
- if (logger.isLevelEnabled("debug"))
1178
- logger.debug("Server export Excel", { currentFilters });
1179
- await (0, utils_1.exportServerData)(table, {
1180
- format: "excel",
1181
- filename,
1182
- fetchData: (filters, selection) => onServerExport(filters, selection),
1183
- currentFilters,
1184
- selection: (_a = table.getSelectionState) === null || _a === void 0 ? void 0 : _a.call(table),
1185
- onProgress: onExportProgress,
1186
- onComplete: onExportComplete,
1187
- onError: onExportError,
1188
- });
1189
- }
1190
- else {
1471
+ if (mode === 'server' && onServerExport) {
1472
+ const currentFilters = {
1473
+ globalFilter: table.getState().globalFilter,
1474
+ columnFilter: table.getState().columnFilter,
1475
+ sorting: table.getState().sorting,
1476
+ pagination: table.getState().pagination,
1477
+ };
1478
+ if (logger.isLevelEnabled("debug"))
1479
+ logger.debug("Server export Excel", { currentFilters });
1480
+ await (0, utils_1.exportServerData)(table, {
1481
+ format: "excel",
1482
+ filename,
1483
+ fetchData: (filters, selection, signal) => onServerExport(filters, selection, signal),
1484
+ currentFilters,
1485
+ selection: (_a = table.getSelectionState) === null || _a === void 0 ? void 0 : _a.call(table),
1486
+ onProgress: handleExportProgressInternal,
1487
+ onComplete: onExportComplete,
1488
+ onError: onExportError,
1489
+ onStateChange: toStateChange,
1490
+ signal: controller.signal,
1491
+ chunkSize,
1492
+ strictTotalCheck,
1493
+ sanitizeCSV,
1494
+ });
1495
+ return;
1496
+ }
1191
1497
  await (0, utils_1.exportClientData)(table, {
1192
1498
  format: "excel",
1193
1499
  filename,
1194
- onProgress: onExportProgress,
1500
+ onProgress: handleExportProgressInternal,
1195
1501
  onComplete: onExportComplete,
1196
1502
  onError: onExportError,
1503
+ onStateChange: toStateChange,
1504
+ signal: controller.signal,
1505
+ sanitizeCSV,
1197
1506
  });
1198
1507
  if (logger.isLevelEnabled("debug"))
1199
1508
  logger.debug("Client export Excel", filename);
1200
1509
  }
1201
- }
1202
- catch (error) {
1203
- onExportError === null || onExportError === void 0 ? void 0 : onExportError({ message: error.message || "Export failed", code: "EXPORT_ERROR" });
1204
- if (logger.isLevelEnabled("debug"))
1205
- logger.debug("Server export Excel failed", error);
1206
- }
1207
- finally {
1208
- setExportController === null || setExportController === void 0 ? void 0 : setExportController(null);
1209
- }
1510
+ });
1210
1511
  },
1211
1512
  exportServerData: async (options) => {
1212
- var _a;
1213
- const { format, filename = exportFilename, fetchData: fetchFn = onServerExport } = options;
1513
+ const { format, filename = exportFilename, fetchData: fetchFn = onServerExport, chunkSize = exportChunkSize, strictTotalCheck = exportStrictTotalCheck, sanitizeCSV = exportSanitizeCSV, } = options;
1214
1514
  if (!fetchFn) {
1215
1515
  onExportError === null || onExportError === void 0 ? void 0 : onExportError({ message: "No server export function provided", code: "NO_SERVER_EXPORT" });
1216
1516
  if (logger.isLevelEnabled("debug"))
1217
1517
  logger.debug("Server export data failed", "No server export function provided");
1218
1518
  return;
1219
1519
  }
1220
- try {
1221
- const controller = new AbortController();
1222
- setExportController === null || setExportController === void 0 ? void 0 : setExportController(controller);
1223
- const currentFilters = {
1224
- globalFilter: table.getState().globalFilter,
1225
- columnFilter: table.getState().columnFilter,
1226
- sorting: table.getState().sorting,
1227
- pagination: table.getState().pagination,
1228
- };
1229
- if (logger.isLevelEnabled("debug"))
1230
- logger.debug("Server export data", { currentFilters });
1231
- await (0, utils_1.exportServerData)(table, {
1232
- format,
1233
- filename,
1234
- fetchData: (filters, selection) => fetchFn(filters, selection),
1235
- currentFilters,
1236
- selection: (_a = table.getSelectionState) === null || _a === void 0 ? void 0 : _a.call(table),
1237
- onProgress: onExportProgress,
1238
- onComplete: onExportComplete,
1239
- onError: onExportError,
1240
- });
1241
- }
1242
- catch (error) {
1243
- onExportError === null || onExportError === void 0 ? void 0 : onExportError({ message: error.message || "Export failed", code: "EXPORT_ERROR" });
1244
- if (logger.isLevelEnabled("debug"))
1245
- logger.debug("Server export data failed", error);
1246
- }
1247
- finally {
1248
- setExportController === null || setExportController === void 0 ? void 0 : setExportController(null);
1249
- }
1520
+ await runExportWithPolicy({
1521
+ format,
1522
+ filename,
1523
+ mode: 'server',
1524
+ execute: async (controller) => {
1525
+ var _a;
1526
+ const currentFilters = {
1527
+ globalFilter: table.getState().globalFilter,
1528
+ columnFilter: table.getState().columnFilter,
1529
+ sorting: table.getState().sorting,
1530
+ pagination: table.getState().pagination,
1531
+ };
1532
+ if (logger.isLevelEnabled("debug"))
1533
+ logger.debug("Server export data", { currentFilters });
1534
+ await (0, utils_1.exportServerData)(table, {
1535
+ format,
1536
+ filename,
1537
+ fetchData: (filters, selection, signal) => fetchFn(filters, selection, signal),
1538
+ currentFilters,
1539
+ selection: (_a = table.getSelectionState) === null || _a === void 0 ? void 0 : _a.call(table),
1540
+ onProgress: handleExportProgressInternal,
1541
+ onComplete: onExportComplete,
1542
+ onError: onExportError,
1543
+ onStateChange: (state) => {
1544
+ const isFinalPhase = state.phase === 'completed' || state.phase === 'cancelled' || state.phase === 'error';
1545
+ handleExportStateChangeInternal({
1546
+ phase: state.phase,
1547
+ mode: 'server',
1548
+ format,
1549
+ filename,
1550
+ processedRows: state.processedRows,
1551
+ totalRows: state.totalRows,
1552
+ percentage: state.percentage,
1553
+ message: state.message,
1554
+ code: state.code,
1555
+ startedAt: state.phase === 'starting' ? Date.now() : undefined,
1556
+ endedAt: isFinalPhase ? Date.now() : undefined,
1557
+ queueLength: queuedExportCount,
1558
+ });
1559
+ if (state.phase === 'cancelled') {
1560
+ onExportCancel === null || onExportCancel === void 0 ? void 0 : onExportCancel();
1561
+ }
1562
+ },
1563
+ signal: controller.signal,
1564
+ chunkSize,
1565
+ strictTotalCheck,
1566
+ sanitizeCSV,
1567
+ });
1568
+ }
1569
+ });
1250
1570
  },
1251
1571
  isExporting: () => isExporting || false,
1252
1572
  cancelExport: () => {
1253
- exportController === null || exportController === void 0 ? void 0 : exportController.abort();
1254
- setExportController === null || setExportController === void 0 ? void 0 : setExportController(null);
1573
+ const activeController = exportControllerRef.current;
1574
+ if (!activeController) {
1575
+ return;
1576
+ }
1577
+ activeController.abort();
1578
+ setExportControllerSafely((current) => (current === activeController ? null : current));
1255
1579
  if (logger.isLevelEnabled("debug"))
1256
1580
  logger.debug("Export cancelled");
1257
1581
  },
@@ -1272,17 +1596,26 @@ logging, }, ref) {
1272
1596
  initialStateConfig,
1273
1597
  enablePagination,
1274
1598
  idKey,
1275
- onDataStateChange,
1276
- fetchData,
1599
+ triggerRefresh,
1600
+ applyDataMutation,
1601
+ tableData,
1602
+ selectionState,
1277
1603
  // export
1278
1604
  exportFilename,
1279
- onExportProgress,
1605
+ exportChunkSize,
1606
+ exportStrictTotalCheck,
1607
+ exportSanitizeCSV,
1280
1608
  onExportComplete,
1281
1609
  onExportError,
1610
+ onExportCancel,
1282
1611
  onServerExport,
1283
- exportController,
1612
+ queuedExportCount,
1284
1613
  isExporting,
1285
1614
  dataMode,
1615
+ handleExportProgressInternal,
1616
+ handleExportStateChangeInternal,
1617
+ runExportWithPolicy,
1618
+ setExportControllerSafely,
1286
1619
  logger,
1287
1620
  resetAllAndReload,
1288
1621
  ]);
@@ -1343,14 +1676,12 @@ logging, }, ref) {
1343
1676
  // Export cancel callback
1344
1677
  // -------------------------------
1345
1678
  const handleCancelExport = (0, react_1.useCallback)(() => {
1346
- if (exportController) {
1347
- exportController.abort();
1348
- setExportController(null);
1349
- if (onExportCancel) {
1350
- onExportCancel();
1351
- }
1679
+ const activeController = exportControllerRef.current;
1680
+ if (activeController) {
1681
+ activeController.abort();
1682
+ setExportControllerSafely((current) => (current === activeController ? null : current));
1352
1683
  }
1353
- }, [exportController, onExportCancel]);
1684
+ }, [setExportControllerSafely]);
1354
1685
  // -------------------------------
1355
1686
  // Slot components
1356
1687
  // -------------------------------
@@ -1367,7 +1698,7 @@ logging, }, ref) {
1367
1698
  // -------------------------------
1368
1699
  return ((0, jsx_runtime_1.jsx)(data_table_context_1.DataTableProvider, { table: table, apiRef: internalApiRef, dataMode: dataMode, tableSize: tableSize, onTableSizeChange: (size) => {
1369
1700
  setTableSize(size);
1370
- }, columnFilter: columnFilter, onChangeColumnFilter: handleColumnFilterStateChange, slots: slots, slotProps: slotProps, isExporting: isExporting, exportController: exportController, onCancelExport: handleCancelExport, exportFilename: exportFilename, onExportProgress: onExportProgress, onExportComplete: onExportComplete, onExportError: onExportError, onServerExport: onServerExport, children: (0, jsx_runtime_1.jsxs)(RootComponent, { ...rootSlotProps, children: [(enableGlobalFilter || extraFilter) ? ((0, jsx_runtime_1.jsx)(ToolbarComponent, { extraFilter: extraFilter, enableGlobalFilter: enableGlobalFilter, enableColumnVisibility: enableColumnVisibility, enableColumnFilter: enableColumnFilter, enableExport: enableExport, enableReset: enableReset, enableTableSizeControl: enableTableSizeControl, enableColumnPinning: enableColumnPinning, enableRefresh: enableRefresh, ...toolbarSlotProps, refreshButtonProps: {
1701
+ }, columnFilter: columnFilter, onChangeColumnFilter: handleColumnFilterStateChange, slots: slots, slotProps: slotProps, isExporting: isExporting, exportController: exportController, exportPhase: exportPhase, exportProgress: exportProgress, onCancelExport: handleCancelExport, exportFilename: exportFilename, onExportProgress: onExportProgress, onExportComplete: onExportComplete, onExportError: onExportError, onServerExport: onServerExport, children: (0, jsx_runtime_1.jsxs)(RootComponent, { ...rootSlotProps, children: [(enableGlobalFilter || extraFilter) ? ((0, jsx_runtime_1.jsx)(ToolbarComponent, { extraFilter: extraFilter, enableGlobalFilter: enableGlobalFilter, enableColumnVisibility: enableColumnVisibility, enableColumnFilter: enableColumnFilter, enableExport: enableExport, enableReset: enableReset, enableTableSizeControl: enableTableSizeControl, enableColumnPinning: enableColumnPinning, enableRefresh: enableRefresh, ...toolbarSlotProps, refreshButtonProps: {
1371
1702
  loading: tableLoading, // disable while fetching
1372
1703
  showSpinnerWhileLoading: false,
1373
1704
  onRefresh: () => { var _a, _b, _c; return (_c = (_b = (_a = internalApiRef.current) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.refresh) === null || _c === void 0 ? void 0 : _c.call(_b, true); },