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

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.
@@ -170,6 +170,7 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
170
170
  enableTableSizeControl = true,
171
171
  enableExport = false,
172
172
  enableReset = true,
173
+ enableRefresh = false,
173
174
 
174
175
  // Loading and empty states
175
176
  loading = false,
@@ -324,12 +325,15 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
324
325
  ...overrides,
325
326
  };
326
327
 
328
+ console.log('Fetching data', filters);
329
+
327
330
  if (logger.isLevelEnabled('info')) {
328
331
  logger.info('Requesting data', { filters });
329
332
  }
330
333
 
331
334
  try {
332
- const result = await debouncedFetch(filters, options === undefined ? 0 : options.delay || 300);
335
+ const delay = options?.delay ?? 300; // respects 0
336
+ const result = await debouncedFetch(filters, delay);
333
337
 
334
338
  if (logger.isLevelEnabled('info')) {
335
339
  logger.info('Fetch resolved', {
@@ -361,75 +365,22 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
361
365
  ]);
362
366
 
363
367
 
364
- const tableStateChange = useCallback((overrides: Partial<TableState> = {}) => {
365
- if (!onDataStateChange) {
366
- if (logger.isLevelEnabled('debug')) {
367
- logger.debug('No onDataStateChange handler registered; skipping state update notification', { overrides });
368
- }
369
- return;
370
- }
371
-
372
- const currentState: Partial<TableState> = {
373
- globalFilter,
374
- columnFilter,
375
- sorting,
376
- pagination,
377
- columnOrder,
378
- columnPinning,
379
- columnVisibility,
380
- columnSizing,
381
- ...overrides,
382
- };
383
-
384
- if (logger.isLevelEnabled('debug')) {
385
- logger.debug('Emitting tableStateChange', currentState);
386
- }
387
-
388
- onDataStateChange?.(currentState);
389
- }, [
390
- onDataStateChange,
391
- globalFilter,
392
- columnFilter,
393
- sorting,
394
- pagination,
395
- columnOrder,
396
- columnPinning,
397
- columnVisibility,
398
- columnSizing,
399
- logger,
400
- ]);
401
-
402
-
403
368
  const handleSelectionStateChange = useCallback((updaterOrValue) => {
404
369
  setSelectionState((prevState) => {
405
- const newSelectionState = typeof updaterOrValue === 'function'
406
- ? updaterOrValue(prevState)
407
- : updaterOrValue;
408
- setTimeout(() => {
409
- if (onSelectionChange) {
410
- onSelectionChange(newSelectionState);
411
- }
412
- if (onDataStateChange) {
413
- tableStateChange({ selectionState: newSelectionState });
414
- }
415
- }, 0);
416
- return newSelectionState;
370
+ const next =
371
+ typeof updaterOrValue === 'function' ? updaterOrValue(prevState) : updaterOrValue;
372
+ onSelectionChange?.(next);
373
+ return next;
417
374
  });
418
- }, [onSelectionChange, onDataStateChange, tableStateChange]);
375
+ }, [onSelectionChange]);
419
376
 
420
377
  const handleColumnFilterStateChange = useCallback((filterState: ColumnFilterState) => {
421
378
  if (!filterState || typeof filterState !== 'object') return;
422
379
 
423
380
  setColumnFilter(filterState);
424
-
425
- if (onColumnFiltersChange) {
426
- setTimeout(() => onColumnFiltersChange(filterState), 0);
427
- }
428
-
429
- if (onDataStateChange) {
430
- setTimeout(() => tableStateChange({ columnFilter: filterState }), 0);
431
- }
432
- }, [onColumnFiltersChange, onDataStateChange, tableStateChange]);
381
+ onColumnFiltersChange?.(filterState);
382
+ return filterState;
383
+ }, [onColumnFiltersChange]);
433
384
 
434
385
 
435
386
  const resetPageToFirst = useCallback(() => {
@@ -445,43 +396,20 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
445
396
  return newPagination;
446
397
  }, [pagination, logger, onPaginationChange]);
447
398
 
448
-
399
+
449
400
  const handleSortingChange = useCallback((updaterOrValue: any) => {
450
- let newSorting = typeof updaterOrValue === 'function'
451
- ? updaterOrValue(sorting)
452
- : updaterOrValue;
453
- newSorting = newSorting.filter((sort: any) => sort.id);
454
- setSorting(newSorting);
455
- onSortingChange?.(newSorting);
456
-
457
- if (logger.isLevelEnabled('debug')) {
458
- logger.debug('Sorting change applied', {
459
- sorting: newSorting,
460
- serverMode: isServerMode,
461
- serverSorting: isServerSorting,
462
- });
463
- }
464
401
 
465
- if (isServerMode || isServerSorting) {
466
- const pagination = resetPageToFirst();
467
- if (logger.isLevelEnabled('debug')) {
468
- logger.debug('Sorting change triggered server fetch', { pagination, sorting: newSorting });
402
+ setSorting((prev) => {
403
+ const next = typeof updaterOrValue === 'function' ? updaterOrValue(prev) : updaterOrValue;
404
+ const cleaned = next.filter((s: any) => s?.id);
405
+ onSortingChange?.(cleaned);
406
+ const nextPagination = resetPageToFirst();
407
+ if (isServerMode || isServerSorting) {
408
+ fetchData({ sorting: cleaned, pagination: nextPagination }, { delay: 0 });
469
409
  }
470
- tableStateChange({ sorting: newSorting, pagination });
471
- fetchData({
472
- sorting: newSorting,
473
- pagination,
474
- });
475
- } else if (onDataStateChange) {
476
- const pagination = resetPageToFirst();
477
- setTimeout(() => {
478
- if (logger.isLevelEnabled('debug')) {
479
- logger.debug('Sorting change notified client state change', { pagination, sorting: newSorting });
480
- }
481
- tableStateChange({ sorting: newSorting, pagination });
482
- }, 0);
483
- }
484
- }, [sorting, onSortingChange, logger, isServerMode, isServerSorting, onDataStateChange, resetPageToFirst, tableStateChange, fetchData]);
410
+ return cleaned;
411
+ });
412
+ }, [onSortingChange, isServerMode, isServerSorting, resetPageToFirst, fetchData]);
485
413
 
486
414
  const handleColumnOrderChange = useCallback((updatedColumnOrder: Updater<ColumnOrderState>) => {
487
415
  const newColumnOrder = typeof updatedColumnOrder === 'function'
@@ -493,144 +421,64 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
493
421
  }
494
422
  }, [onColumnDragEnd, columnOrder]);
495
423
 
496
- const handleColumnPinningChange = useCallback((updatedColumnPinning: Updater<ColumnPinningState>) => {
497
- const newColumnPinning = typeof updatedColumnPinning === 'function'
498
- ? updatedColumnPinning(columnPinning)
499
- : updatedColumnPinning;
500
- setColumnPinning(newColumnPinning);
501
- if (onColumnPinningChange) {
502
- onColumnPinningChange(newColumnPinning);
503
- }
504
- }, [onColumnPinningChange, columnPinning]);
424
+ const handleColumnPinningChange = useCallback(
425
+ (updater: Updater<ColumnPinningState>) => {
426
+ setColumnPinning((prev) => {
427
+ const next = typeof updater === "function" ? updater(prev) : updater;
428
+ // keep direct callback here (optional)
429
+ onColumnPinningChange?.(next);
430
+ return next;
431
+ });
432
+ },
433
+ [onColumnPinningChange]
434
+ );
505
435
 
506
436
  // Column visibility change handler - same pattern as column order
507
437
  const handleColumnVisibilityChange = useCallback((updater: any) => {
508
- const newVisibility = typeof updater === 'function'
509
- ? updater(columnVisibility)
510
- : updater;
511
- setColumnVisibility(newVisibility);
512
-
513
- if (onColumnVisibilityChange) {
514
- setTimeout(() => {
515
- onColumnVisibilityChange(newVisibility);
516
- }, 0);
517
- }
518
-
519
- if (onDataStateChange) {
520
- setTimeout(() => {
521
- tableStateChange({ columnVisibility: newVisibility });
522
- }, 0);
523
- }
524
- }, [onColumnVisibilityChange, onDataStateChange, tableStateChange, columnVisibility]);
438
+ setColumnVisibility((prev) => {
439
+ const next = typeof updater === 'function' ? updater(prev) : updater;
440
+ onColumnVisibilityChange?.(next);
441
+ return next;
442
+ });
443
+ }, [onColumnVisibilityChange]);
525
444
 
526
445
  // Column sizing change handler - same pattern as column order
527
446
  const handleColumnSizingChange = useCallback((updater: any) => {
528
- const newSizing = typeof updater === 'function'
529
- ? updater(columnSizing)
530
- : updater;
531
- setColumnSizing(newSizing);
532
-
533
- if (onColumnSizingChange) {
534
- setTimeout(() => {
535
- onColumnSizingChange(newSizing);
536
- }, 0);
537
- }
538
-
539
- if (onDataStateChange) {
540
- setTimeout(() => {
541
- tableStateChange({ columnSizing: newSizing });
542
- }, 0);
543
- }
544
- }, [onColumnSizingChange, onDataStateChange, tableStateChange, columnSizing]);
447
+ setColumnSizing((prev) => {
448
+ const next = typeof updater === 'function' ? updater(prev) : updater;
449
+ onColumnSizingChange?.(next);
450
+ return next;
451
+ });
452
+ }, [onColumnSizingChange]);
545
453
 
546
454
  const handlePaginationChange = useCallback((updater: any) => {
547
- const newPagination = typeof updater === 'function' ? updater(pagination) : updater;
548
- if (logger.isLevelEnabled('debug')) {
549
- logger.debug('Pagination change requested', {
550
- previous: pagination,
551
- next: newPagination,
552
- serverSide: isServerMode || isServerPagination,
553
- });
554
- }
555
-
556
- // Update pagination state
557
- setPagination(newPagination);
558
- onPaginationChange?.(newPagination);
455
+ setPagination((prev) => {
456
+ const next = typeof updater === 'function' ? updater(prev) : updater;
457
+ onPaginationChange?.(next);
458
+ if (isServerMode || isServerPagination) {
459
+ fetchData({ pagination: next }, { delay: 0 });
460
+ }
461
+ return next;
462
+ });
463
+ }, [isServerMode, isServerPagination, fetchData, onPaginationChange]);
559
464
 
560
- if (logger.isLevelEnabled('debug')) {
561
- logger.debug('Pagination state updated', newPagination);
562
- }
563
465
 
564
- // Notify state change and fetch data if needed
565
- if (isServerMode || isServerPagination) {
566
- setTimeout(() => {
567
- if (logger.isLevelEnabled('debug')) {
568
- logger.debug('Notifying server-side pagination change', newPagination);
569
- }
570
- tableStateChange({ pagination: newPagination });
571
- fetchData({ pagination: newPagination });
572
- }, 0);
573
- } else if (onDataStateChange) {
574
- setTimeout(() => {
575
- if (logger.isLevelEnabled('debug')) {
576
- logger.debug('Notifying client-side pagination change', newPagination);
577
- }
578
- tableStateChange({ pagination: newPagination });
579
- }, 0);
580
- }
581
- }, [
582
- pagination,
583
- isServerMode,
584
- isServerPagination,
585
- onDataStateChange,
586
- fetchData,
587
- tableStateChange,
588
- logger,
589
- onPaginationChange,
590
- ]);
591
466
 
467
+ const handleGlobalFilterChange = useCallback((updaterOrValue: any) => {
468
+ setGlobalFilter((prev) => {
469
+ const next = typeof updaterOrValue === 'function' ? updaterOrValue(prev) : updaterOrValue;
592
470
 
471
+ onGlobalFilterChange?.(next);
593
472
 
594
- const handleGlobalFilterChange = useCallback((updaterOrValue: any) => {
595
- const newFilter = typeof updaterOrValue === 'function'
596
- ? updaterOrValue(globalFilter)
597
- : updaterOrValue;
598
- setGlobalFilter(newFilter);
599
-
600
- if (logger.isLevelEnabled('debug')) {
601
- logger.debug('Global filter change applied', {
602
- value: newFilter,
603
- serverMode: isServerMode,
604
- serverFiltering: isServerFiltering,
605
- });
606
- }
473
+ if (isServerMode || isServerFiltering) {
474
+ const nextPagination = { pageIndex: 0, pageSize: pagination.pageSize };
475
+ setPagination(nextPagination);
476
+ fetchData({ globalFilter: next, pagination: nextPagination }, { delay: 0 });
477
+ }
607
478
 
608
- if (isServerMode || isServerFiltering) {
609
- const pagination = resetPageToFirst();
610
- setTimeout(() => {
611
- if (logger.isLevelEnabled('debug')) {
612
- logger.debug('Global filter change triggering server fetch', {
613
- pagination,
614
- value: newFilter,
615
- });
616
- }
617
- tableStateChange({ globalFilter: newFilter, pagination });
618
- fetchData({ globalFilter: newFilter, pagination });
619
- }, 0);
620
- } else if (onDataStateChange) {
621
- const pagination = resetPageToFirst();
622
- setTimeout(() => {
623
- if (logger.isLevelEnabled('debug')) {
624
- logger.debug('Global filter change notifying client listeners', {
625
- pagination,
626
- value: newFilter,
627
- });
628
- }
629
- tableStateChange({ globalFilter: newFilter, pagination });
630
- }, 0);
631
- }
632
- onGlobalFilterChange?.(newFilter);
633
- }, [globalFilter, logger, isServerMode, isServerFiltering, onDataStateChange, onGlobalFilterChange, resetPageToFirst, tableStateChange, fetchData]);
479
+ return next;
480
+ });
481
+ }, [isServerMode, isServerFiltering, onGlobalFilterChange, fetchData, pagination.pageSize]);
634
482
 
635
483
  const onColumnFilterChangeHandler = useCallback((updater: any) => {
636
484
  const currentState = columnFilter;
@@ -648,24 +496,15 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
648
496
 
649
497
  const onColumnFilterApplyHandler = useCallback((appliedState: ColumnFilterState) => {
650
498
  const pagination = resetPageToFirst();
651
-
652
499
  if (isServerFiltering) {
653
- tableStateChange({
654
- columnFilter: appliedState,
655
- pagination,
656
- });
657
500
  fetchData({
658
501
  columnFilter: appliedState,
659
502
  pagination,
660
503
  });
661
- } else if (onDataStateChange) {
662
- setTimeout(() => tableStateChange({ columnFilter: appliedState, pagination }), 0);
663
504
  }
664
505
 
665
- setTimeout(() => {
666
- onColumnFiltersChange?.(appliedState);
667
- }, 0);
668
- }, [resetPageToFirst, isServerFiltering, onDataStateChange, tableStateChange, fetchData, onColumnFiltersChange]);
506
+ onColumnFiltersChange?.(appliedState);
507
+ }, [resetPageToFirst, isServerFiltering, fetchData, onColumnFiltersChange]);
669
508
 
670
509
  // -------------------------------
671
510
  // Table creation (after callbacks/memo)
@@ -825,501 +664,573 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
825
664
  }, [enableColumnDragging, enhancedColumns, columnOrder.length]);
826
665
 
827
666
 
828
- const dataTableApi = useMemo(() => ({
829
- table: {
830
- getTable: () => table,
831
- },
832
- // Column Management
833
- columnVisibility: {
834
- showColumn: (columnId: string) => {
835
- table.getColumn(columnId)?.toggleVisibility(true);
836
- },
837
- hideColumn: (columnId: string) => {
838
- table.getColumn(columnId)?.toggleVisibility(false);
839
- },
840
- toggleColumn: (columnId: string) => {
841
- table.getColumn(columnId)?.toggleVisibility();
842
- },
843
- showAllColumns: () => {
844
- table.toggleAllColumnsVisible(true);
845
- },
846
- hideAllColumns: () => {
847
- table.toggleAllColumnsVisible(false);
848
- },
849
- resetColumnVisibility: () => {
850
- const initialVisibility = initialStateConfig.columnVisibility || {};
851
- table.setColumnVisibility(initialVisibility);
852
- // Manually trigger handler to ensure callbacks are called
853
- handleColumnVisibilityChange(initialVisibility);
854
- },
855
- },
667
+ const lastSentRef = useRef<string>("");
856
668
 
857
- // Column Ordering
858
- columnOrdering: {
859
- setColumnOrder: (columnOrder: ColumnOrderState) => {
860
- table.setColumnOrder(columnOrder);
861
- },
862
- moveColumn: (columnId: string, toIndex: number) => {
863
- const currentOrder = table.getState().columnOrder || [];
864
- const currentIndex = currentOrder.indexOf(columnId);
865
- if (currentIndex === -1) return;
669
+ const emitTableState = useCallback(() => {
670
+ if (!onDataStateChange) return;
866
671
 
867
- const newOrder = [...currentOrder];
868
- newOrder.splice(currentIndex, 1);
869
- newOrder.splice(toIndex, 0, columnId);
672
+ const live = table.getState();
870
673
 
871
- table.setColumnOrder(newOrder);
872
- },
873
- resetColumnOrder: () => {
874
- const initialOrder = enhancedColumns.map((col, index) => {
875
- if (col.id) return col.id;
876
- const anyCol = col as any;
877
- if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
878
- return anyCol.accessorKey;
879
- }
880
- return `column_${index}`;
881
- });
882
- table.setColumnOrder(initialOrder);
883
- // Manually trigger handler to ensure callbacks are called
884
- handleColumnOrderChange(initialOrder);
885
- },
886
- },
674
+ // only keep what you persist/store
675
+ const payload = {
676
+ sorting: live.sorting,
677
+ pagination: live.pagination,
678
+ globalFilter: live.globalFilter,
679
+ columnFilter: live.columnFilter,
680
+ columnVisibility: live.columnVisibility,
681
+ columnSizing: live.columnSizing,
682
+ columnOrder: live.columnOrder,
683
+ columnPinning: live.columnPinning,
684
+ };
887
685
 
888
- // Column Pinning
889
- columnPinning: {
890
- pinColumnLeft: (columnId: string) => {
891
- const currentPinning = table.getState().columnPinning;
892
- const newPinning = { ...currentPinning };
686
+ const key = JSON.stringify(payload);
687
+ if (key === lastSentRef.current) return;
893
688
 
894
- // Remove from right if exists
895
- newPinning.right = (newPinning.right || []).filter(id => id !== columnId);
896
- // Add to left if not exists
897
- newPinning.left = [...(newPinning.left || []).filter(id => id !== columnId), columnId];
689
+ lastSentRef.current = key;
690
+ onDataStateChange(payload);
691
+ }, [onDataStateChange, table]);
898
692
 
899
- table.setColumnPinning(newPinning);
900
- },
901
- pinColumnRight: (columnId: string) => {
902
- const currentPinning = table.getState().columnPinning;
903
- const newPinning = { ...currentPinning };
693
+ useEffect(() => {
694
+ emitTableState();
695
+ }, [
696
+ emitTableState,
697
+ sorting,
698
+ pagination,
699
+ globalFilter,
700
+ columnFilter,
701
+ columnVisibility,
702
+ columnSizing,
703
+ columnOrder,
704
+ columnPinning,
705
+ ]);
904
706
 
905
- // Remove from left if exists
906
- newPinning.left = (newPinning.left || []).filter(id => id !== columnId);
907
- // Add to right if not exists - prepend to beginning (appears rightmost to leftmost)
908
- // First column pinned appears rightmost, second appears to its left, etc.
909
- newPinning.right = [columnId, ...(newPinning.right || []).filter(id => id !== columnId)];
910
707
 
911
- table.setColumnPinning(newPinning);
912
- },
913
- unpinColumn: (columnId: string) => {
914
- const currentPinning = table.getState().columnPinning;
915
- const newPinning = {
916
- left: (currentPinning.left || []).filter(id => id !== columnId),
917
- right: (currentPinning.right || []).filter(id => id !== columnId),
918
- };
919
-
920
- table.setColumnPinning(newPinning);
921
- },
922
- setPinning: (pinning: ColumnPinningState) => {
923
- table.setColumnPinning(pinning);
924
- },
925
- resetColumnPinning: () => {
926
- const initialPinning = initialStateConfig.columnPinning || { left: [], right: [] };
927
- table.setColumnPinning(initialPinning);
928
- // Manually trigger handler to ensure callbacks are called
929
- handleColumnPinningChange(initialPinning);
930
- },
931
- },
708
+ const getResetState = useCallback((): Partial<TableState> => {
709
+ const resetSorting = initialStateConfig.sorting || [];
710
+ const resetGlobalFilter = initialStateConfig.globalFilter ?? '';
711
+ const resetColumnFilter =
712
+ initialStateConfig.columnFilter || { filters: [], logic: 'AND', pendingFilters: [], pendingLogic: 'AND' };
932
713
 
933
- // Column Resizing
934
- columnResizing: {
935
- resizeColumn: (columnId: string, width: number) => {
936
- // Use table's setColumnSizing method
937
- const currentSizing = table.getState().columnSizing;
938
- table.setColumnSizing({
939
- ...currentSizing,
940
- [columnId]: width,
941
- });
942
- },
943
- autoSizeColumn: (columnId: string) => {
944
- // TanStack doesn't have built-in auto-size, so reset to default
945
- table.getColumn(columnId)?.resetSize();
946
- },
947
- autoSizeAllColumns: () => {
948
- const initialSizing = initialStateConfig.columnSizing || {};
949
- table.setColumnSizing(initialSizing);
950
- // Manually trigger handler to ensure callbacks are called
951
- handleColumnSizingChange(initialSizing);
952
- },
953
- resetColumnSizing: () => {
954
- const initialSizing = initialStateConfig.columnSizing || {};
955
- table.setColumnSizing(initialSizing);
956
- // Manually trigger handler to ensure callbacks are called
957
- handleColumnSizingChange(initialSizing);
958
- },
959
- },
714
+ const resetPagination = enablePagination
715
+ ? (initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 })
716
+ : undefined;
960
717
 
961
- // Filtering
962
- filtering: {
963
- setGlobalFilter: (filter: string) => {
964
- table.setGlobalFilter(filter);
965
- },
966
- clearGlobalFilter: () => {
967
- table.setGlobalFilter('');
968
- },
969
- setColumnFilters: (filters: ColumnFilterState) => {
970
- handleColumnFilterStateChange(filters);
971
- },
972
- addColumnFilter: (columnId: string, operator: string, value: any) => {
973
- const newFilter = {
974
- id: `filter_${Date.now()}`,
975
- columnId,
976
- operator,
977
- value,
978
- };
979
- const columnFilter = table.getState().columnFilter;
980
-
981
- const currentFilters = columnFilter.filters || [];
982
- const newFilters = [...currentFilters, newFilter];
983
- handleColumnFilterStateChange({
984
- filters: newFilters,
985
- logic: columnFilter.logic,
986
- pendingFilters: columnFilter.pendingFilters || [],
987
- pendingLogic: columnFilter.pendingLogic || 'AND',
988
- });
989
- if (logger.isLevelEnabled('debug')) {
990
- logger.debug(`Adding column filter ${columnId} ${operator} ${value}`, newFilters);
991
- }
992
- },
993
- removeColumnFilter: (filterId: string) => {
994
- const columnFilter = table.getState().columnFilter;
995
- const currentFilters = columnFilter.filters || [];
996
- const newFilters = currentFilters.filter((f: any) => f.id !== filterId);
997
- handleColumnFilterStateChange({
998
- filters: newFilters,
999
- logic: columnFilter.logic,
1000
- pendingFilters: columnFilter.pendingFilters || [],
1001
- pendingLogic: columnFilter.pendingLogic || 'AND',
1002
- });
1003
- if (logger.isLevelEnabled('debug')) {
1004
- logger.debug(`Removing column filter ${filterId}`, newFilters);
1005
- }
1006
- },
1007
- clearAllFilters: () => {
1008
- table.setGlobalFilter('');
1009
- handleColumnFilterStateChange({
1010
- filters: [],
1011
- logic: 'AND',
1012
- pendingFilters: [],
1013
- pendingLogic: 'AND',
1014
- });
1015
- },
1016
- resetFilters: () => {
1017
- handleColumnFilterStateChange({
1018
- filters: [],
1019
- logic: 'AND',
1020
- pendingFilters: [],
1021
- pendingLogic: 'AND',
1022
- });
1023
- if (logger.isLevelEnabled('debug')) {
1024
- logger.debug('Resetting filters');
1025
- }
1026
- },
1027
- },
718
+ return {
719
+ sorting: resetSorting,
720
+ globalFilter: resetGlobalFilter,
721
+ columnFilter: resetColumnFilter,
722
+ ...(resetPagination ? { pagination: resetPagination } : {}),
723
+ };
724
+ }, [initialStateConfig, enablePagination]);
1028
725
 
1029
- // Sorting
1030
- sorting: {
1031
- setSorting: (sortingState: SortingState) => {
1032
- table.setSorting(sortingState);
1033
- if (logger.isLevelEnabled('debug')) {
1034
- logger.debug(`Setting sorting`, sortingState);
1035
- }
1036
- },
1037
- sortColumn: (columnId: string, direction: 'asc' | 'desc' | false) => {
1038
- const column = table.getColumn(columnId);
1039
- if (!column) return;
1040
-
1041
- if (direction === false) {
1042
- column.clearSorting();
1043
- } else {
1044
- column.toggleSorting(direction === 'desc');
1045
- }
1046
- },
1047
- clearSorting: () => {
1048
- table.setSorting([]);
1049
- // Manually trigger handler to ensure callbacks are called
1050
- handleSortingChange([]);
1051
- },
1052
- resetSorting: () => {
1053
- const initialSorting = initialStateConfig.sorting || [];
1054
- table.setSorting(initialSorting);
1055
- // Manually trigger handler to ensure callbacks are called
1056
- handleSortingChange(initialSorting);
1057
- },
1058
- },
726
+ const resetAllAndReload = useCallback(() => {
727
+ const resetState = getResetState();
1059
728
 
1060
- // Pagination
1061
- pagination: {
1062
- goToPage: (pageIndex: number) => {
1063
- table.setPageIndex(pageIndex);
1064
- if (logger.isLevelEnabled('debug')) {
1065
- logger.debug(`Going to page ${pageIndex}`);
1066
- }
1067
- },
1068
- nextPage: () => {
1069
- table.nextPage();
1070
- if (logger.isLevelEnabled('debug')) {
1071
- logger.debug('Next page');
1072
- }
1073
- },
1074
- previousPage: () => {
1075
- table.previousPage();
1076
- if (logger.isLevelEnabled('debug')) {
1077
- logger.debug('Previous page');
1078
- }
1079
- },
1080
- setPageSize: (pageSize: number) => {
1081
- table.setPageSize(pageSize);
1082
- if (logger.isLevelEnabled('debug')) {
1083
- logger.debug(`Setting page size to ${pageSize}`);
1084
- }
1085
- },
1086
- goToFirstPage: () => {
1087
- table.setPageIndex(0);
1088
- if (logger.isLevelEnabled('debug')) {
1089
- logger.debug('Going to first page');
1090
- }
1091
- },
1092
- goToLastPage: () => {
1093
- const pageCount = table.getPageCount();
1094
- if (pageCount > 0) {
1095
- table.setPageIndex(pageCount - 1);
1096
- if (logger.isLevelEnabled('debug')) {
1097
- logger.debug(`Going to last page ${pageCount - 1}`);
729
+ setSorting(resetState.sorting || []);
730
+ setGlobalFilter(resetState.globalFilter ?? '');
731
+ setColumnFilter(resetState.columnFilter as any);
732
+
733
+ if (resetState.pagination) setPagination(resetState.pagination);
734
+
735
+ setSelectionState(initialSelectionState);
736
+ setExpanded({});
737
+
738
+ // layout state
739
+ setColumnVisibility(initialStateConfig.columnVisibility || {});
740
+ setColumnSizing(initialStateConfig.columnSizing || {});
741
+ setColumnOrder(initialStateConfig.columnOrder || []);
742
+ setColumnPinning(initialStateConfig.columnPinning || { left: [], right: [] });
743
+
744
+ if (onFetchData) fetchData(resetState, { delay: 0 });
745
+ }, [getResetState, initialSelectionState, initialStateConfig, onFetchData, fetchData]);
746
+
747
+ const dataTableApi = useMemo(() => {
748
+ // helpers (avoid repeating boilerplate)
749
+ const buildInitialOrder = () =>
750
+ enhancedColumns.map((col, index) => {
751
+ if ((col as any).id) return (col as any).id as string;
752
+ const anyCol = col as any;
753
+ if (anyCol.accessorKey && typeof anyCol.accessorKey === "string") return anyCol.accessorKey;
754
+ return `column_${index}`;
755
+ });
756
+
757
+ const applyColumnOrder = (next: ColumnOrderState) => {
758
+ // handleColumnOrderChange supports both Updater<ColumnOrderState> and array in your impl
759
+ handleColumnOrderChange(next as any);
760
+ };
761
+
762
+ const applyPinning = (next: ColumnPinningState) => {
763
+ handleColumnPinningChange(next as any);
764
+ };
765
+
766
+ const applyVisibility = (next: Record<string, boolean>) => {
767
+ handleColumnVisibilityChange(next as any);
768
+ };
769
+
770
+ const applySizing = (next: Record<string, number>) => {
771
+ handleColumnSizingChange(next as any);
772
+ };
773
+
774
+ const applyPagination = (next: any) => {
775
+ handlePaginationChange(next);
776
+ };
777
+
778
+ const applySorting = (next: any) => {
779
+ handleSortingChange(next);
780
+ };
781
+
782
+ const applyGlobalFilter = (next: any) => {
783
+ handleGlobalFilterChange(next);
784
+ };
785
+
786
+ return {
787
+ table: {
788
+ getTable: () => table,
789
+ },
790
+
791
+ // -------------------------------
792
+ // Column Management
793
+ // -------------------------------
794
+ columnVisibility: {
795
+ showColumn: (columnId: string) => {
796
+ applyVisibility({ ...table.getState().columnVisibility, [columnId]: true });
797
+ },
798
+ hideColumn: (columnId: string) => {
799
+ applyVisibility({ ...table.getState().columnVisibility, [columnId]: false });
800
+ },
801
+ toggleColumn: (columnId: string) => {
802
+ const curr = table.getState().columnVisibility?.[columnId] ?? true;
803
+ applyVisibility({ ...table.getState().columnVisibility, [columnId]: !curr });
804
+ },
805
+ showAllColumns: () => {
806
+ // set all known columns true
807
+ const all: Record<string, boolean> = {};
808
+ table.getAllLeafColumns().forEach((c) => (all[c.id] = true));
809
+ applyVisibility(all);
810
+ },
811
+ hideAllColumns: () => {
812
+ const all: Record<string, boolean> = {};
813
+ table.getAllLeafColumns().forEach((c) => (all[c.id] = false));
814
+ applyVisibility(all);
815
+ },
816
+ resetColumnVisibility: () => {
817
+ const initialVisibility = initialStateConfig.columnVisibility || {};
818
+ applyVisibility(initialVisibility);
819
+ },
820
+ },
821
+
822
+ // -------------------------------
823
+ // Column Ordering
824
+ // -------------------------------
825
+ columnOrdering: {
826
+ setColumnOrder: (nextOrder: ColumnOrderState) => {
827
+ applyColumnOrder(nextOrder);
828
+ },
829
+ moveColumn: (columnId: string, toIndex: number) => {
830
+ const currentOrder =
831
+ (table.getState().columnOrder?.length ? table.getState().columnOrder : buildInitialOrder()) || [];
832
+ const fromIndex = currentOrder.indexOf(columnId);
833
+ if (fromIndex === -1) return;
834
+
835
+ const next = [...currentOrder];
836
+ next.splice(fromIndex, 1);
837
+ next.splice(toIndex, 0, columnId);
838
+
839
+ applyColumnOrder(next);
840
+ },
841
+ resetColumnOrder: () => {
842
+ applyColumnOrder(buildInitialOrder());
843
+ },
844
+ },
845
+
846
+ // -------------------------------
847
+ // Column Pinning
848
+ // -------------------------------
849
+ columnPinning: {
850
+ pinColumnLeft: (columnId: string) => {
851
+ const current = table.getState().columnPinning || { left: [], right: [] };
852
+ const next: ColumnPinningState = {
853
+ left: [...(current.left || []).filter((id) => id !== columnId), columnId],
854
+ right: (current.right || []).filter((id) => id !== columnId),
855
+ };
856
+ applyPinning(next);
857
+ },
858
+ pinColumnRight: (columnId: string) => {
859
+ const current = table.getState().columnPinning || { left: [], right: [] };
860
+ const next: ColumnPinningState = {
861
+ left: (current.left || []).filter((id) => id !== columnId),
862
+ // keep your "prepend" behavior
863
+ right: [columnId, ...(current.right || []).filter((id) => id !== columnId)],
864
+ };
865
+ applyPinning(next);
866
+ },
867
+ unpinColumn: (columnId: string) => {
868
+ const current = table.getState().columnPinning || { left: [], right: [] };
869
+ const next: ColumnPinningState = {
870
+ left: (current.left || []).filter((id) => id !== columnId),
871
+ right: (current.right || []).filter((id) => id !== columnId),
872
+ };
873
+ applyPinning(next);
874
+ },
875
+ setPinning: (pinning: ColumnPinningState) => {
876
+ applyPinning(pinning);
877
+ },
878
+ resetColumnPinning: () => {
879
+ const initialPinning = initialStateConfig.columnPinning || { left: [], right: [] };
880
+ applyPinning(initialPinning);
881
+ },
882
+ },
883
+
884
+ // -------------------------------
885
+ // Column Resizing
886
+ // -------------------------------
887
+ columnResizing: {
888
+ resizeColumn: (columnId: string, width: number) => {
889
+ const currentSizing = table.getState().columnSizing || {};
890
+ applySizing({ ...currentSizing, [columnId]: width });
891
+ },
892
+ autoSizeColumn: (columnId: string) => {
893
+ // safe to call tanstack helper; it will feed into onColumnSizingChange if wired,
894
+ // but since you're controlled, we still prefer to update through handler:
895
+ const col = table.getColumn(columnId);
896
+ if (!col) return;
897
+
898
+ col.resetSize();
899
+ // after resetSize, read state and emit via handler so controlled stays synced
900
+ applySizing({ ...(table.getState().columnSizing || {}) });
901
+ },
902
+ autoSizeAllColumns: () => {
903
+ const initialSizing = initialStateConfig.columnSizing || {};
904
+ applySizing(initialSizing);
905
+ },
906
+ resetColumnSizing: () => {
907
+ const initialSizing = initialStateConfig.columnSizing || {};
908
+ applySizing(initialSizing);
909
+ },
910
+ },
911
+
912
+ // -------------------------------
913
+ // Filtering
914
+ // -------------------------------
915
+ filtering: {
916
+ setGlobalFilter: (filter: string) => {
917
+ applyGlobalFilter(filter);
918
+ },
919
+ clearGlobalFilter: () => {
920
+ applyGlobalFilter("");
921
+ },
922
+ setColumnFilters: (filters: ColumnFilterState) => {
923
+ handleColumnFilterStateChange(filters);
924
+ },
925
+ addColumnFilter: (columnId: string, operator: string, value: any) => {
926
+ const newFilter = {
927
+ id: `filter_${Date.now()}`,
928
+ columnId,
929
+ operator,
930
+ value,
931
+ };
932
+
933
+ const current = table.getState().columnFilter;
934
+ const currentFilters = current?.filters || [];
935
+ const nextFilters = [...currentFilters, newFilter];
936
+
937
+ handleColumnFilterStateChange({
938
+ filters: nextFilters,
939
+ logic: current?.logic,
940
+ pendingFilters: current?.pendingFilters || [],
941
+ pendingLogic: current?.pendingLogic || "AND",
942
+ });
943
+
944
+ if (logger.isLevelEnabled("debug")) {
945
+ logger.debug(`Adding column filter ${columnId} ${operator} ${value}`, nextFilters);
1098
946
  }
1099
- }
1100
- },
1101
- resetPagination: () => {
1102
- const initialPagination = initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 };
1103
- table.setPagination(initialPagination);
1104
- // Manually trigger handler to ensure callbacks are called
1105
- handlePaginationChange(initialPagination);
1106
- },
1107
- },
947
+ },
948
+ removeColumnFilter: (filterId: string) => {
949
+ const current = table.getState().columnFilter;
950
+ const currentFilters = current?.filters || [];
951
+ const nextFilters = currentFilters.filter((f: any) => f.id !== filterId);
952
+
953
+ handleColumnFilterStateChange({
954
+ filters: nextFilters,
955
+ logic: current?.logic,
956
+ pendingFilters: current?.pendingFilters || [],
957
+ pendingLogic: current?.pendingLogic || "AND",
958
+ });
1108
959
 
1109
- // Access via table methods: table.selectRow(), table.getIsRowSelected(), etc.
1110
- selection: {
1111
- selectRow: (rowId: string) => table.selectRow?.(rowId),
1112
- deselectRow: (rowId: string) => table.deselectRow?.(rowId),
1113
- toggleRowSelection: (rowId: string) => table.toggleRowSelected?.(rowId),
1114
- selectAll: () => table.selectAll?.(),
1115
- deselectAll: () => table.deselectAll?.(),
1116
- toggleSelectAll: () => table.toggleAllRowsSelected?.(),
1117
- getSelectionState: () => table.getSelectionState?.() || { ids: [], type: 'include' as const },
1118
- getSelectedRows: () => table.getSelectedRows(),
1119
- getSelectedCount: () => table.getSelectedCount(),
1120
- isRowSelected: (rowId) => table.getIsRowSelected(rowId) || false,
1121
- },
960
+ if (logger.isLevelEnabled("debug")) {
961
+ logger.debug(`Removing column filter ${filterId}`, nextFilters);
962
+ }
963
+ },
964
+ clearAllFilters: () => {
965
+ applyGlobalFilter("");
966
+ handleColumnFilterStateChange({
967
+ filters: [],
968
+ logic: "AND",
969
+ pendingFilters: [],
970
+ pendingLogic: "AND",
971
+ });
972
+ },
973
+ resetFilters: () => {
974
+ handleColumnFilterStateChange({
975
+ filters: [],
976
+ logic: "AND",
977
+ pendingFilters: [],
978
+ pendingLogic: "AND",
979
+ });
1122
980
 
1123
- // Data Management
1124
- data: {
1125
- refresh: (resetPagination = false) => {
1126
- const filters = table.getState();
1127
- const pagination = {
1128
- pageIndex: resetPagination ? 0 : initialStateConfig.pagination?.pageIndex || 0,
1129
- pageSize: filters.pagination?.pageSize || initialStateConfig.pagination?.pageSize || 10,
1130
- };
1131
- const allState = table.getState();
1132
- setPagination(pagination);
1133
- onDataStateChange?.({ ...allState, pagination });
1134
- fetchData?.({ pagination });
1135
- if (logger.isLevelEnabled('debug')) {
1136
- logger.debug('Refreshing data using Ref', { pagination, allState });
1137
- }
1138
- },
1139
- reload: () => {
1140
- const allState = table.getState();
981
+ if (logger.isLevelEnabled("debug")) {
982
+ logger.debug("Resetting filters");
983
+ }
984
+ },
985
+ },
986
+
987
+ // -------------------------------
988
+ // Sorting
989
+ // -------------------------------
990
+ sorting: {
991
+ setSorting: (sortingState: SortingState) => {
992
+ applySorting(sortingState);
993
+ if (logger.isLevelEnabled("debug")) logger.debug("Setting sorting", sortingState);
994
+ },
995
+
996
+ // NOTE: toggleSorting is okay, but can become "one behind" in controlled server mode.
997
+ // So we implement deterministic sorting through handler.
998
+ sortColumn: (columnId: string, direction: "asc" | "desc" | false) => {
999
+ const current = table.getState().sorting || [];
1000
+ const filtered = current.filter((s: any) => s.id !== columnId);
1001
+
1002
+ if (direction === false) {
1003
+ applySorting(filtered);
1004
+ return;
1005
+ }
1141
1006
 
1142
- onDataStateChange?.(allState);
1143
- fetchData?.({});
1144
- if (logger.isLevelEnabled('debug')) {
1145
- logger.info('Reloading data', allState);
1146
- }
1147
- },
1148
- // Data CRUD operations
1149
- getAllData: () => {
1150
- return table.getRowModel().rows?.map(row => row.original) || [];
1151
- },
1152
- getRowData: (rowId: string) => {
1153
- return table.getRowModel().rows?.find(row => String(row.original[idKey]) === rowId)?.original;
1154
- },
1155
- getRowByIndex: (index: number) => {
1156
- return table.getRowModel().rows?.[index]?.original;
1157
- },
1158
- updateRow: (rowId: string, updates: Partial<T>) => {
1159
- const newData = table.getRowModel().rows?.map(row => String(row.original[idKey]) === rowId
1160
- ? {
1161
- ...row.original,
1162
- ...updates,
1007
+ applySorting([{ id: columnId, desc: direction === "desc" }, ...filtered]);
1008
+ },
1009
+
1010
+ clearSorting: () => {
1011
+ applySorting([]);
1012
+ },
1013
+ resetSorting: () => {
1014
+ const initialSorting = initialStateConfig.sorting || [];
1015
+ applySorting(initialSorting);
1016
+ },
1017
+ },
1018
+
1019
+ // -------------------------------
1020
+ // Pagination
1021
+ // -------------------------------
1022
+ pagination: {
1023
+ goToPage: (pageIndex: number) => {
1024
+ applyPagination((prev: any) => ({ ...prev, pageIndex }));
1025
+ if (logger.isLevelEnabled("debug")) logger.debug(`Going to page ${pageIndex}`);
1026
+ },
1027
+ nextPage: () => {
1028
+ applyPagination((prev: any) => ({ ...prev, pageIndex: (prev?.pageIndex ?? 0) + 1 }));
1029
+ if (logger.isLevelEnabled("debug")) logger.debug("Next page");
1030
+ },
1031
+ previousPage: () => {
1032
+ applyPagination((prev: any) => ({ ...prev, pageIndex: Math.max(0, (prev?.pageIndex ?? 0) - 1) }));
1033
+ if (logger.isLevelEnabled("debug")) logger.debug("Previous page");
1034
+ },
1035
+ setPageSize: (pageSize: number) => {
1036
+ // usually want pageIndex reset
1037
+ applyPagination(() => ({ pageIndex: 0, pageSize }));
1038
+ if (logger.isLevelEnabled("debug")) logger.debug(`Setting page size to ${pageSize}`);
1039
+ },
1040
+ goToFirstPage: () => {
1041
+ applyPagination((prev: any) => ({ ...prev, pageIndex: 0 }));
1042
+ if (logger.isLevelEnabled("debug")) logger.debug("Going to first page");
1043
+ },
1044
+ goToLastPage: () => {
1045
+ // pageCount can be derived; keep safe fallback
1046
+ const pageCount = table.getPageCount?.() ?? 0;
1047
+ if (pageCount > 0) {
1048
+ applyPagination((prev: any) => ({ ...prev, pageIndex: pageCount - 1 }));
1049
+ if (logger.isLevelEnabled("debug")) logger.debug(`Going to last page ${pageCount - 1}`);
1163
1050
  }
1164
- : row.original);
1165
- setServerData?.(newData || []);
1166
- if (logger.isLevelEnabled('debug')) {
1167
- logger.debug(`Updating row ${rowId}`, updates);
1168
- }
1169
- },
1170
- updateRowByIndex: (index: number, updates: Partial<T>) => {
1171
- const newData = table.getRowModel().rows?.map(row => row.original);
1172
- if (newData?.[index]) {
1173
- newData[index] = {
1174
- ...newData[index]!,
1175
- ...updates,
1051
+ },
1052
+ resetPagination: () => {
1053
+ const initialPagination = initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 };
1054
+ applyPagination(initialPagination);
1055
+ },
1056
+ },
1057
+
1058
+ // -------------------------------
1059
+ // Selection
1060
+ // -------------------------------
1061
+ selection: {
1062
+ selectRow: (rowId: string) => table.selectRow?.(rowId),
1063
+ deselectRow: (rowId: string) => table.deselectRow?.(rowId),
1064
+ toggleRowSelection: (rowId: string) => table.toggleRowSelected?.(rowId),
1065
+ selectAll: () => table.selectAll?.(),
1066
+ deselectAll: () => table.deselectAll?.(),
1067
+ toggleSelectAll: () => table.toggleAllRowsSelected?.(),
1068
+ getSelectionState: () => table.getSelectionState?.() || ({ ids: [], type: "include" } as const),
1069
+ getSelectedRows: () => table.getSelectedRows(),
1070
+ getSelectedCount: () => table.getSelectedCount(),
1071
+ isRowSelected: (rowId: string) => table.getIsRowSelected(rowId) || false,
1072
+ },
1073
+
1074
+ // -------------------------------
1075
+ // Data Management (kept same, but ensure state changes go through handlers)
1076
+ // -------------------------------
1077
+ data: {
1078
+ refresh: (resetPagination = false) => {
1079
+ const allState = table.getState();
1080
+ const current = allState.pagination;
1081
+
1082
+ const nextPagination = {
1083
+ pageIndex: resetPagination ? 0 : current?.pageIndex ?? 0,
1084
+ pageSize: current?.pageSize ?? initialStateConfig.pagination?.pageSize ?? 10,
1176
1085
  };
1177
- setServerData(newData);
1178
- if (logger.isLevelEnabled('debug')) {
1179
- logger.debug(`Updating row by index ${index}`, updates);
1086
+
1087
+ // must go through handler so server fetch triggers
1088
+ applyPagination(nextPagination);
1089
+
1090
+ // emit persisted state (your emitTableState effect will also do it)
1091
+ onDataStateChange?.({ ...allState, pagination: nextPagination });
1092
+
1093
+ fetchData?.({ pagination: nextPagination });
1094
+
1095
+ if (logger.isLevelEnabled("debug")) {
1096
+ logger.debug("Refreshing data", { nextPagination, allState });
1180
1097
  }
1181
- }
1182
- },
1183
- insertRow: (newRow: T, index?: number) => {
1184
- const newData = table.getRowModel().rows?.map(row => row.original) || [];
1185
- if (index !== undefined) {
1186
- newData.splice(index, 0, newRow);
1187
- } else {
1188
- newData.push(newRow);
1189
- }
1190
- setServerData(newData || []);
1191
- if (logger.isLevelEnabled('debug')) {
1192
- logger.debug(`Inserting row`, newRow);
1193
- }
1194
- },
1195
- deleteRow: (rowId: string) => {
1196
- const newData = (table.getRowModel().rows || [])?.filter(row => String(row.original[idKey]) !== rowId);
1197
- setServerData?.(newData?.map(row => row.original) || []);
1198
- if (logger.isLevelEnabled('debug')) {
1199
- logger.debug(`Deleting row ${rowId}`);
1200
- }
1201
- },
1202
- deleteRowByIndex: (index: number) => {
1203
- const newData = (table.getRowModel().rows || [])?.map(row => row.original);
1204
- newData.splice(index, 1);
1205
- setServerData(newData);
1206
- if (logger.isLevelEnabled('debug')) {
1207
- logger.debug(`Deleting row by index ${index}`);
1208
- }
1209
- },
1210
- deleteSelectedRows: () => {
1211
- const selectedRows = table.getSelectedRows?.() || [];
1212
- if (selectedRows.length === 0) return;
1098
+ },
1099
+
1100
+ reload: () => {
1101
+ const allState = table.getState();
1102
+ onDataStateChange?.(allState);
1103
+ fetchData?.();
1104
+ if (logger.isLevelEnabled("debug")) {
1105
+ logger.info("Reloading data", allState);
1106
+ }
1107
+ },
1108
+
1109
+ resetAll: () => resetAllAndReload({ resetLayout: true }),
1110
+
1111
+ getAllData: () => table.getRowModel().rows?.map((row) => row.original) || [],
1112
+ getRowData: (rowId: string) =>
1113
+ table.getRowModel().rows?.find((row) => String(row.original[idKey]) === rowId)?.original,
1114
+ getRowByIndex: (index: number) => table.getRowModel().rows?.[index]?.original,
1115
+
1116
+ updateRow: (rowId: string, updates: Partial<T>) => {
1117
+ const newData = table.getRowModel().rows?.map((row) =>
1118
+ String(row.original[idKey]) === rowId ? { ...row.original, ...updates } : row.original
1119
+ );
1120
+ setServerData?.(newData || []);
1121
+ if (logger.isLevelEnabled("debug")) logger.debug(`Updating row ${rowId}`, updates);
1122
+ },
1123
+
1124
+ updateRowByIndex: (index: number, updates: Partial<T>) => {
1125
+ const newData = table.getRowModel().rows?.map((row) => row.original) || [];
1126
+ if (newData[index]) {
1127
+ newData[index] = { ...newData[index]!, ...updates };
1128
+ setServerData(newData);
1129
+ if (logger.isLevelEnabled("debug")) logger.debug(`Updating row by index ${index}`, updates);
1130
+ }
1131
+ },
1132
+
1133
+ insertRow: (newRow: T, index?: number) => {
1134
+ const newData = table.getRowModel().rows?.map((row) => row.original) || [];
1135
+ if (index !== undefined) newData.splice(index, 0, newRow);
1136
+ else newData.push(newRow);
1137
+ setServerData(newData || []);
1138
+ if (logger.isLevelEnabled("debug")) logger.debug("Inserting row", newRow);
1139
+ },
1140
+
1141
+ deleteRow: (rowId: string) => {
1142
+ const newData = (table.getRowModel().rows || []).filter((row) => String(row.original[idKey]) !== rowId);
1143
+ setServerData?.(newData.map((r) => r.original) || []);
1144
+ if (logger.isLevelEnabled("debug")) logger.debug(`Deleting row ${rowId}`);
1145
+ },
1146
+
1147
+ deleteRowByIndex: (index: number) => {
1148
+ const newData = (table.getRowModel().rows || []).map((row) => row.original);
1149
+ newData.splice(index, 1);
1150
+ setServerData(newData);
1151
+ if (logger.isLevelEnabled("debug")) logger.debug(`Deleting row by index ${index}`);
1152
+ },
1213
1153
 
1214
- const selectedIds = new Set(selectedRows.map(row => String(row.original[idKey])));
1215
- const newData = (table.getRowModel().rows || [])?.filter(row => !selectedIds.has(String(row.original[idKey])));
1154
+ deleteSelectedRows: () => {
1155
+ const selectedRows = table.getSelectedRows?.() || [];
1156
+ if (selectedRows.length === 0) return;
1216
1157
 
1217
- setServerData(newData?.map(row => row.original) || []);
1218
- table.deselectAll?.();
1158
+ const selectedIds = new Set(selectedRows.map((row) => String(row.original[idKey])));
1159
+ const newData = (table.getRowModel().rows || [])
1160
+ .filter((row) => !selectedIds.has(String(row.original[idKey])))
1161
+ .map((row) => row.original);
1219
1162
 
1220
- if (logger.isLevelEnabled('debug')) {
1221
- logger.debug('Deleting selected rows');
1222
- }
1223
- },
1224
- replaceAllData: (newData: T[]) => {
1225
- setServerData?.(newData);
1226
- },
1163
+ setServerData(newData);
1164
+ table.deselectAll?.();
1227
1165
 
1228
- // Bulk operations
1229
- updateMultipleRows: (updates: Array<{ rowId: string; data: Partial<T> }>) => {
1230
- const updateMap = new Map(updates.map(u => [u.rowId, u.data]));
1231
- const newData = (table.getRowModel().rows || [])?.map(row => {
1232
- const rowId = String(row.original[idKey]);
1233
- const updateData = updateMap.get(rowId);
1234
- return updateData ? {
1235
- ...row.original,
1236
- ...updateData,
1237
- } : row.original;
1238
- });
1239
- setServerData(newData || []);
1240
- },
1241
- insertMultipleRows: (newRows: T[], startIndex?: number) => {
1242
- const newData = (table.getRowModel().rows || [])?.map(row => row.original);
1243
- if (startIndex !== undefined) {
1244
- newData.splice(startIndex, 0, ...newRows);
1245
- } else {
1246
- newData.push(...newRows);
1247
- }
1248
- setServerData?.(newData);
1249
- },
1250
- deleteMultipleRows: (rowIds: string[]) => {
1251
- const idsToDelete = new Set(rowIds);
1252
- const newData = (table.getRowModel().rows || [])?.filter(row => !idsToDelete.has(String(row.original[idKey])))?.map(row => row.original);
1253
- setServerData(newData);
1254
- },
1166
+ if (logger.isLevelEnabled("debug")) logger.debug("Deleting selected rows");
1167
+ },
1255
1168
 
1256
- // Field-specific updates
1257
- updateField: (rowId: string, fieldName: keyof T, value: any) => {
1258
- const newData = (table.getRowModel().rows || [])?.map(row => String(row.original[idKey]) === rowId
1259
- ? {
1260
- ...row.original,
1261
- [fieldName]: value,
1262
- }
1263
- : row.original);
1264
- setServerData?.(newData);
1265
- },
1266
- updateFieldByIndex: (index: number, fieldName: keyof T, value: any) => {
1267
- const newData = (table.getRowModel().rows || [])?.map(row => row.original);
1268
- if (newData[index]) {
1269
- newData[index] = {
1270
- ...newData[index],
1271
- [fieldName]: value,
1272
- };
1169
+ replaceAllData: (newData: T[]) => setServerData?.(newData),
1170
+
1171
+ updateMultipleRows: (updates: Array<{ rowId: string; data: Partial<T> }>) => {
1172
+ const updateMap = new Map(updates.map((u) => [u.rowId, u.data]));
1173
+ const newData = (table.getRowModel().rows || []).map((row) => {
1174
+ const rowId = String(row.original[idKey]);
1175
+ const updateData = updateMap.get(rowId);
1176
+ return updateData ? { ...row.original, ...updateData } : row.original;
1177
+ });
1178
+ setServerData(newData || []);
1179
+ },
1180
+
1181
+ insertMultipleRows: (newRows: T[], startIndex?: number) => {
1182
+ const newData = (table.getRowModel().rows || []).map((row) => row.original);
1183
+ if (startIndex !== undefined) newData.splice(startIndex, 0, ...newRows);
1184
+ else newData.push(...newRows);
1273
1185
  setServerData?.(newData);
1274
- }
1275
- },
1186
+ },
1276
1187
 
1277
- // Data queries
1278
- findRows: (predicate: (row: T) => boolean) => {
1279
- return (table.getRowModel().rows || [])?.filter(row => predicate(row.original))?.map(row => row.original);
1280
- },
1281
- findRowIndex: (predicate: (row: T) => boolean) => {
1282
- return (table.getRowModel().rows || [])?.findIndex(row => predicate(row.original));
1283
- },
1284
- getDataCount: () => {
1285
- return (table.getRowModel().rows || [])?.length || 0;
1286
- },
1287
- getFilteredDataCount: () => {
1288
- return table.getFilteredRowModel().rows.length;
1289
- },
1290
- },
1188
+ deleteMultipleRows: (rowIds: string[]) => {
1189
+ const idsToDelete = new Set(rowIds);
1190
+ const newData = (table.getRowModel().rows || [])
1191
+ .filter((row) => !idsToDelete.has(String(row.original[idKey])))
1192
+ .map((row) => row.original);
1193
+ setServerData(newData);
1194
+ },
1291
1195
 
1292
- // Layout Management
1293
- layout: {
1294
- resetLayout: () => {
1295
- table.resetColumnSizing();
1296
- table.resetColumnVisibility();
1297
- table.resetSorting();
1298
- table.resetGlobalFilter();
1299
- },
1300
- resetAll: () => {
1301
- // Reset everything to initial state
1302
- table.resetColumnSizing();
1303
- table.resetColumnVisibility();
1304
- table.resetSorting();
1305
- table.resetGlobalFilter();
1306
- table.resetColumnOrder();
1307
- table.resetExpanded();
1308
- handleSelectionStateChange(initialSelectionState);
1309
- table.resetColumnPinning();
1310
-
1311
- handleColumnFilterStateChange(initialStateConfig.columnFilter || { filters: [], logic: 'AND', pendingFilters: [], pendingLogic: 'AND' });
1312
-
1313
- if (enablePagination) {
1314
- table.setPagination(initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 });
1315
- }
1196
+ updateField: (rowId: string, fieldName: keyof T, value: any) => {
1197
+ const newData = (table.getRowModel().rows || []).map((row) =>
1198
+ String(row.original[idKey]) === rowId ? { ...row.original, [fieldName]: value } : row.original
1199
+ );
1200
+ setServerData?.(newData);
1201
+ },
1316
1202
 
1317
- if (enableColumnPinning) {
1318
- table.setColumnPinning(initialStateConfig.columnPinning || { left: [], right: [] });
1319
- }
1320
- },
1321
- saveLayout: () => {
1322
- return {
1203
+ updateFieldByIndex: (index: number, fieldName: keyof T, value: any) => {
1204
+ const newData = (table.getRowModel().rows || []).map((row) => row.original);
1205
+ if (newData[index]) {
1206
+ newData[index] = { ...newData[index], [fieldName]: value };
1207
+ setServerData?.(newData);
1208
+ }
1209
+ },
1210
+
1211
+ findRows: (predicate: (row: T) => boolean) =>
1212
+ (table.getRowModel().rows || []).filter((row) => predicate(row.original)).map((row) => row.original),
1213
+
1214
+ findRowIndex: (predicate: (row: T) => boolean) =>
1215
+ (table.getRowModel().rows || []).findIndex((row) => predicate(row.original)),
1216
+
1217
+ getDataCount: () => (table.getRowModel().rows || []).length || 0,
1218
+ getFilteredDataCount: () => table.getFilteredRowModel().rows.length,
1219
+ },
1220
+
1221
+ // -------------------------------
1222
+ // Layout Management
1223
+ // -------------------------------
1224
+ layout: {
1225
+ resetLayout: () => {
1226
+ // go through handlers so controlled state updates + emit works
1227
+ applySizing(initialStateConfig.columnSizing || {});
1228
+ applyVisibility(initialStateConfig.columnVisibility || {});
1229
+ applySorting(initialStateConfig.sorting || []);
1230
+ applyGlobalFilter(initialStateConfig.globalFilter ?? "");
1231
+ },
1232
+ resetAll: () => resetAllAndReload({ resetLayout: true }),
1233
+ saveLayout: () => ({
1323
1234
  columnVisibility: table.getState().columnVisibility,
1324
1235
  columnSizing: table.getState().columnSizing,
1325
1236
  columnOrder: table.getState().columnOrder,
@@ -1328,119 +1239,138 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
1328
1239
  pagination: table.getState().pagination,
1329
1240
  globalFilter: table.getState().globalFilter,
1330
1241
  columnFilter: table.getState().columnFilter,
1331
- };
1332
- },
1333
- restoreLayout: (layout: Partial<TableState>) => {
1334
- if (layout.columnVisibility) {
1335
- table.setColumnVisibility(layout.columnVisibility);
1336
- }
1337
- if (layout.columnSizing) {
1338
- table.setColumnSizing(layout.columnSizing);
1339
- }
1340
- if (layout.columnOrder) {
1341
- table.setColumnOrder(layout.columnOrder);
1342
- }
1343
- if (layout.columnPinning) {
1344
- table.setColumnPinning(layout.columnPinning);
1345
- }
1346
- if (layout.sorting) {
1347
- table.setSorting(layout.sorting);
1348
- }
1349
- if (layout.pagination && enablePagination) {
1350
- table.setPagination(layout.pagination);
1351
- }
1352
- if (layout.globalFilter !== undefined) {
1353
- table.setGlobalFilter(layout.globalFilter);
1354
- }
1355
- if (layout.columnFilter) {
1356
- handleColumnFilterStateChange(layout.columnFilter);
1357
- }
1358
- },
1359
- },
1360
-
1361
- // Table State
1362
- state: {
1363
- getTableState: () => {
1364
- return table.getState();
1365
- },
1366
- getCurrentFilters: () => {
1367
- return table.getState().columnFilter;
1368
- },
1369
- getCurrentSorting: () => {
1370
- return table.getState().sorting;
1371
- },
1372
- getCurrentPagination: () => {
1373
- return table.getState().pagination;
1374
- },
1375
- // Backward compatibility: expose the raw selection array expected by older consumers
1376
- getCurrentSelection: () => {
1377
- return table.getSelectionState?.();
1378
- },
1379
- },
1380
-
1381
- // Simplified Export
1382
- export: {
1383
- exportCSV: async (options: any = {}) => {
1384
- const { filename = exportFilename, } = options;
1385
-
1386
- try {
1387
- // Create abort controller for this export
1388
- const controller = new AbortController();
1389
- setExportController?.(controller);
1390
-
1391
- if (dataMode === 'server' && onServerExport) {
1392
- // Server export with selection data
1393
- const currentFilters = {
1394
- globalFilter: table.getState().globalFilter,
1395
- columnFilter: table.getState().columnFilter,
1396
- sorting: table.getState().sorting,
1397
- pagination: table.getState().pagination,
1398
- };
1399
- if (logger.isLevelEnabled('debug')) {
1400
- logger.debug('Server export CSV', { currentFilters });
1242
+ }),
1243
+ restoreLayout: (layout: Partial<TableState>) => {
1244
+ if (layout.columnVisibility) applyVisibility(layout.columnVisibility as any);
1245
+ if (layout.columnSizing) applySizing(layout.columnSizing as any);
1246
+ if (layout.columnOrder) applyColumnOrder(layout.columnOrder as any);
1247
+ if (layout.columnPinning) applyPinning(layout.columnPinning as any);
1248
+ if (layout.sorting) applySorting(layout.sorting as any);
1249
+ if (layout.pagination && enablePagination) applyPagination(layout.pagination as any);
1250
+ if (layout.globalFilter !== undefined) applyGlobalFilter(layout.globalFilter);
1251
+ if (layout.columnFilter) handleColumnFilterStateChange(layout.columnFilter as any);
1252
+ },
1253
+ },
1254
+
1255
+ // -------------------------------
1256
+ // Table State
1257
+ // -------------------------------
1258
+ state: {
1259
+ getTableState: () => table.getState(),
1260
+ getCurrentFilters: () => table.getState().columnFilter,
1261
+ getCurrentSorting: () => table.getState().sorting,
1262
+ getCurrentPagination: () => table.getState().pagination,
1263
+ getCurrentSelection: () => table.getSelectionState?.(),
1264
+ },
1265
+
1266
+ // -------------------------------
1267
+ // Export (unchanged mostly)
1268
+ // -------------------------------
1269
+ export: {
1270
+ exportCSV: async (options: any = {}) => {
1271
+ const { filename = exportFilename } = options;
1272
+
1273
+ try {
1274
+ const controller = new AbortController();
1275
+ setExportController?.(controller);
1276
+
1277
+ if (dataMode === "server" && onServerExport) {
1278
+ const currentFilters = {
1279
+ globalFilter: table.getState().globalFilter,
1280
+ columnFilter: table.getState().columnFilter,
1281
+ sorting: table.getState().sorting,
1282
+ pagination: table.getState().pagination,
1283
+ };
1284
+
1285
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export CSV", { currentFilters });
1286
+
1287
+ await exportServerData(table, {
1288
+ format: "csv",
1289
+ filename,
1290
+ fetchData: (filters: any, selection: any) => onServerExport(filters, selection),
1291
+ currentFilters,
1292
+ selection: table.getSelectionState?.(),
1293
+ onProgress: onExportProgress,
1294
+ onComplete: onExportComplete,
1295
+ onError: onExportError,
1296
+ });
1297
+ } else {
1298
+ await exportClientData(table, {
1299
+ format: "csv",
1300
+ filename,
1301
+ onProgress: onExportProgress,
1302
+ onComplete: onExportComplete,
1303
+ onError: onExportError,
1304
+ });
1305
+
1306
+ if (logger.isLevelEnabled("debug")) logger.debug("Client export CSV", filename);
1401
1307
  }
1402
- await exportServerData(table, {
1403
- format: 'csv',
1404
- filename,
1405
- fetchData: (filters, selection) => onServerExport(filters, selection),
1406
- currentFilters,
1407
- selection: table.getSelectionState?.(),
1408
- onProgress: onExportProgress,
1409
- onComplete: onExportComplete,
1410
- onError: onExportError,
1411
- });
1412
- } else {
1413
- // Client export - auto-detect selected rows if not specified
1414
- await exportClientData(table, {
1415
- format: 'csv',
1416
- filename,
1417
- onProgress: onExportProgress,
1418
- onComplete: onExportComplete,
1419
- onError: onExportError,
1420
- });
1421
- if (logger.isLevelEnabled('debug')) {
1422
- logger.debug('Client export CSV', filename);
1308
+ } catch (error: any) {
1309
+ onExportError?.({ message: error.message || "Export failed", code: "EXPORT_ERROR" });
1310
+ } finally {
1311
+ setExportController?.(null);
1312
+ }
1313
+ },
1314
+
1315
+ exportExcel: async (options: any = {}) => {
1316
+ const { filename = exportFilename } = options;
1317
+
1318
+ try {
1319
+ const controller = new AbortController();
1320
+ setExportController?.(controller);
1321
+
1322
+ if (dataMode === "server" && onServerExport) {
1323
+ const currentFilters = {
1324
+ globalFilter: table.getState().globalFilter,
1325
+ columnFilter: table.getState().columnFilter,
1326
+ sorting: table.getState().sorting,
1327
+ pagination: table.getState().pagination,
1328
+ };
1329
+
1330
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export Excel", { currentFilters });
1331
+
1332
+ await exportServerData(table, {
1333
+ format: "excel",
1334
+ filename,
1335
+ fetchData: (filters: any, selection: any) => onServerExport(filters, selection),
1336
+ currentFilters,
1337
+ selection: table.getSelectionState?.(),
1338
+ onProgress: onExportProgress,
1339
+ onComplete: onExportComplete,
1340
+ onError: onExportError,
1341
+ });
1342
+ } else {
1343
+ await exportClientData(table, {
1344
+ format: "excel",
1345
+ filename,
1346
+ onProgress: onExportProgress,
1347
+ onComplete: onExportComplete,
1348
+ onError: onExportError,
1349
+ });
1350
+
1351
+ if (logger.isLevelEnabled("debug")) logger.debug("Client export Excel", filename);
1423
1352
  }
1353
+ } catch (error: any) {
1354
+ onExportError?.({ message: error.message || "Export failed", code: "EXPORT_ERROR" });
1355
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export Excel failed", error);
1356
+ } finally {
1357
+ setExportController?.(null);
1424
1358
  }
1425
- } catch (error: any) {
1426
- onExportError?.({
1427
- message: error.message || 'Export failed',
1428
- code: 'EXPORT_ERROR',
1429
- });
1430
- } finally {
1431
- setExportController?.(null);
1432
- }
1433
- },
1434
- exportExcel: async (options: any = {}) => {
1435
- const { filename = exportFilename } = options;
1359
+ },
1360
+
1361
+ exportServerData: async (options: any) => {
1362
+ const { format, filename = exportFilename, fetchData: fetchFn = onServerExport } = options;
1436
1363
 
1437
- try {
1438
- // Create abort controller for this export
1439
- const controller = new AbortController();
1440
- setExportController?.(controller);
1364
+ if (!fetchFn) {
1365
+ onExportError?.({ message: "No server export function provided", code: "NO_SERVER_EXPORT" });
1366
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export data failed", "No server export function provided");
1367
+ return;
1368
+ }
1369
+
1370
+ try {
1371
+ const controller = new AbortController();
1372
+ setExportController?.(controller);
1441
1373
 
1442
- if (dataMode === 'server' && onServerExport) {
1443
- // Server export with selection data
1444
1374
  const currentFilters = {
1445
1375
  globalFilter: table.getState().globalFilter,
1446
1376
  columnFilter: table.getState().columnFilter,
@@ -1448,131 +1378,62 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
1448
1378
  pagination: table.getState().pagination,
1449
1379
  };
1450
1380
 
1451
- if (logger.isLevelEnabled('debug')) {
1452
- logger.debug('Server export Excel', { currentFilters });
1453
- }
1381
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export data", { currentFilters });
1382
+
1454
1383
  await exportServerData(table, {
1455
- format: 'excel',
1384
+ format,
1456
1385
  filename,
1457
- fetchData: (filters, selection) => onServerExport(filters, selection),
1386
+ fetchData: (filters: any, selection: any) => fetchFn(filters, selection),
1458
1387
  currentFilters,
1459
1388
  selection: table.getSelectionState?.(),
1460
1389
  onProgress: onExportProgress,
1461
1390
  onComplete: onExportComplete,
1462
1391
  onError: onExportError,
1463
1392
  });
1464
- } else {
1465
- // Client export - auto-detect selected rows if not specified
1466
- await exportClientData(table, {
1467
- format: 'excel',
1468
- filename,
1469
- onProgress: onExportProgress,
1470
- onComplete: onExportComplete,
1471
- onError: onExportError,
1472
- });
1473
- if (logger.isLevelEnabled('debug')) {
1474
- logger.debug('Client export Excel', filename);
1475
- }
1476
- }
1477
- } catch (error: any) {
1478
- onExportError?.({
1479
- message: error.message || 'Export failed',
1480
- code: 'EXPORT_ERROR',
1481
- });
1482
- if (logger.isLevelEnabled('debug')) {
1483
- logger.debug('Server export Excel failed', error);
1393
+ } catch (error: any) {
1394
+ onExportError?.({ message: error.message || "Export failed", code: "EXPORT_ERROR" });
1395
+ if (logger.isLevelEnabled("debug")) logger.debug("Server export data failed", error);
1396
+ } finally {
1397
+ setExportController?.(null);
1484
1398
  }
1485
- } finally {
1486
- setExportController?.(null);
1487
- }
1488
- },
1489
- exportServerData: async (options) => {
1490
- const {
1491
- format,
1492
- filename = exportFilename,
1493
- fetchData = onServerExport,
1494
- } = options;
1495
-
1496
- if (!fetchData) {
1497
- onExportError?.({
1498
- message: 'No server export function provided',
1499
- code: 'NO_SERVER_EXPORT',
1500
- });
1501
- if (logger.isLevelEnabled('debug')) {
1502
- logger.debug('Server export data failed', 'No server export function provided');
1503
- }
1504
- return;
1505
- }
1506
-
1507
- try {
1508
- // Create abort controller for this export
1509
- const controller = new AbortController();
1510
- setExportController?.(controller);
1399
+ },
1511
1400
 
1512
- const currentFilters = {
1513
- globalFilter: table.getState().globalFilter,
1514
- columnFilter: table.getState().columnFilter,
1515
- sorting: table.getState().sorting,
1516
- pagination: table.getState().pagination,
1517
- };
1518
- if (logger.isLevelEnabled('debug')) {
1519
- logger.debug('Server export data', { currentFilters });
1520
- }
1521
- await exportServerData(table, {
1522
- format,
1523
- filename,
1524
- fetchData: (filters, selection) => fetchData(filters, selection),
1525
- currentFilters,
1526
- selection: table.getSelectionState?.(),
1527
- onProgress: onExportProgress,
1528
- onComplete: onExportComplete,
1529
- onError: onExportError,
1530
- });
1531
- } catch (error: any) {
1532
- onExportError?.({
1533
- message: error.message || 'Export failed',
1534
- code: 'EXPORT_ERROR',
1535
- });
1536
- if (logger.isLevelEnabled('debug')) {
1537
- logger.debug('Server export data failed', error);
1538
- }
1539
- } finally {
1401
+ isExporting: () => isExporting || false,
1402
+ cancelExport: () => {
1403
+ exportController?.abort();
1540
1404
  setExportController?.(null);
1541
- }
1405
+ if (logger.isLevelEnabled("debug")) logger.debug("Export cancelled");
1406
+ },
1542
1407
  },
1543
- // Export state
1544
- isExporting: () => isExporting || false,
1545
- cancelExport: () => {
1546
- exportController?.abort();
1547
- setExportController?.(null);
1548
- if (logger.isLevelEnabled('debug')) {
1549
- logger.debug('Export cancelled');
1550
- }
1551
- },
1552
- },
1408
+ };
1553
1409
  // eslint-disable-next-line react-hooks/exhaustive-deps
1554
- }), [
1410
+ }, [
1555
1411
  table,
1556
1412
  enhancedColumns,
1413
+ handleColumnOrderChange,
1414
+ handleColumnPinningChange,
1415
+ handleColumnVisibilityChange,
1416
+ handleColumnSizingChange,
1417
+ handlePaginationChange,
1418
+ handleSortingChange,
1419
+ handleGlobalFilterChange,
1557
1420
  handleColumnFilterStateChange,
1421
+ initialStateConfig,
1422
+ enablePagination,
1558
1423
  idKey,
1559
1424
  onDataStateChange,
1560
1425
  fetchData,
1561
- enableColumnPinning,
1562
- enablePagination,
1563
- // Export dependencies
1426
+ // export
1564
1427
  exportFilename,
1565
1428
  onExportProgress,
1566
1429
  onExportComplete,
1567
1430
  onExportError,
1568
1431
  onServerExport,
1569
1432
  exportController,
1570
- setExportController,
1571
1433
  isExporting,
1572
1434
  dataMode,
1573
- selectMode,
1574
- onSelectionChange,
1575
- // Note: custom selection removed from dependency array
1435
+ logger,
1436
+ resetAllAndReload,
1576
1437
  ]);
1577
1438
 
1578
1439
  internalApiRef.current = dataTableApi;
@@ -1818,7 +1679,15 @@ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(func
1818
1679
  enableReset={enableReset}
1819
1680
  enableTableSizeControl={enableTableSizeControl}
1820
1681
  enableColumnPinning={enableColumnPinning}
1682
+ enableRefresh={enableRefresh}
1821
1683
  {...toolbarSlotProps}
1684
+ refreshButtonProps={{
1685
+ loading: tableLoading, // disable while fetching
1686
+ showSpinnerWhileLoading: false,
1687
+ onRefresh: () => internalApiRef.current?.data?.refresh?.(true),
1688
+ ...toolbarSlotProps.refreshButtonProps,
1689
+ }}
1690
+
1822
1691
  />
1823
1692
  ) : null}
1824
1693