@ackplus/react-tanstack-data-table 1.0.34 → 1.1.1

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 (272) hide show
  1. package/LICENSE +21 -0
  2. package/{src → dist}/index.d.ts +21 -4
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +63 -0
  5. package/{src → dist}/lib/components/droupdown/menu-dropdown.d.ts +2 -1
  6. package/dist/lib/components/droupdown/menu-dropdown.d.ts.map +1 -0
  7. package/{src → dist}/lib/components/droupdown/menu-dropdown.js +38 -7
  8. package/{src → dist}/lib/components/filters/filter-value-input.d.ts +3 -1
  9. package/dist/lib/components/filters/filter-value-input.d.ts.map +1 -0
  10. package/dist/lib/components/filters/filter-value-input.js +83 -0
  11. package/{src → dist}/lib/components/filters/index.d.ts +1 -0
  12. package/dist/lib/components/filters/index.d.ts.map +1 -0
  13. package/dist/lib/components/filters/index.js +142 -0
  14. package/{src → dist}/lib/components/headers/draggable-header.d.ts +2 -2
  15. package/dist/lib/components/headers/draggable-header.d.ts.map +1 -0
  16. package/{src → dist}/lib/components/headers/draggable-header.js +81 -17
  17. package/dist/lib/components/headers/index.d.ts +6 -0
  18. package/dist/lib/components/headers/index.d.ts.map +1 -0
  19. package/dist/lib/components/headers/index.js +21 -0
  20. package/{src → dist}/lib/components/headers/table-header.d.ts +15 -1
  21. package/dist/lib/components/headers/table-header.d.ts.map +1 -0
  22. package/{src → dist}/lib/components/headers/table-header.js +50 -17
  23. package/{src → dist}/lib/components/index.d.ts +6 -1
  24. package/dist/lib/components/index.d.ts.map +1 -0
  25. package/dist/lib/components/index.js +32 -0
  26. package/{src → dist}/lib/components/pagination/data-table-pagination.d.ts +2 -1
  27. package/dist/lib/components/pagination/data-table-pagination.d.ts.map +1 -0
  28. package/{src → dist}/lib/components/pagination/data-table-pagination.js +20 -6
  29. package/dist/lib/components/pagination/index.d.ts +5 -0
  30. package/dist/lib/components/pagination/index.d.ts.map +1 -0
  31. package/dist/lib/components/pagination/index.js +20 -0
  32. package/{src → dist}/lib/components/rows/data-table-row.d.ts +15 -2
  33. package/dist/lib/components/rows/data-table-row.d.ts.map +1 -0
  34. package/{src → dist}/lib/components/rows/data-table-row.js +58 -25
  35. package/{src → dist}/lib/components/rows/empty-data-row.d.ts +3 -3
  36. package/dist/lib/components/rows/empty-data-row.d.ts.map +1 -0
  37. package/{src → dist}/lib/components/rows/empty-data-row.js +12 -4
  38. package/dist/lib/components/rows/index.d.ts +7 -0
  39. package/dist/lib/components/rows/index.d.ts.map +1 -0
  40. package/dist/lib/components/rows/index.js +22 -0
  41. package/{src → dist}/lib/components/rows/loading-rows.d.ts +3 -1
  42. package/dist/lib/components/rows/loading-rows.d.ts.map +1 -0
  43. package/{src → dist}/lib/components/rows/loading-rows.js +27 -19
  44. package/{src → dist}/lib/components/toolbar/bulk-actions-toolbar.d.ts +4 -3
  45. package/dist/lib/components/toolbar/bulk-actions-toolbar.d.ts.map +1 -0
  46. package/dist/lib/components/toolbar/bulk-actions-toolbar.js +49 -0
  47. package/{src → dist}/lib/components/toolbar/column-filter-control.d.ts +3 -1
  48. package/dist/lib/components/toolbar/column-filter-control.d.ts.map +1 -0
  49. package/{src → dist}/lib/components/toolbar/column-filter-control.js +73 -4
  50. package/{src → dist}/lib/components/toolbar/column-pinning-control.d.ts +2 -1
  51. package/dist/lib/components/toolbar/column-pinning-control.d.ts.map +1 -0
  52. package/{src → dist}/lib/components/toolbar/column-pinning-control.js +71 -7
  53. package/{src → dist}/lib/components/toolbar/column-reset-control.d.ts +3 -1
  54. package/dist/lib/components/toolbar/column-reset-control.d.ts.map +1 -0
  55. package/{src → dist}/lib/components/toolbar/column-reset-control.js +9 -3
  56. package/{src → dist}/lib/components/toolbar/column-visibility-control.d.ts +2 -1
  57. package/dist/lib/components/toolbar/column-visibility-control.d.ts.map +1 -0
  58. package/dist/lib/components/toolbar/column-visibility-control.js +77 -0
  59. package/{src → dist}/lib/components/toolbar/data-table-toolbar.d.ts +3 -2
  60. package/dist/lib/components/toolbar/data-table-toolbar.d.ts.map +1 -0
  61. package/{src → dist}/lib/components/toolbar/data-table-toolbar.js +17 -4
  62. package/{src → dist}/lib/components/toolbar/index.d.ts +4 -0
  63. package/dist/lib/components/toolbar/index.d.ts.map +1 -0
  64. package/{src → dist}/lib/components/toolbar/index.js +6 -0
  65. package/dist/lib/components/toolbar/table-export-control.d.ts +12 -0
  66. package/dist/lib/components/toolbar/table-export-control.d.ts.map +1 -0
  67. package/dist/lib/components/toolbar/table-export-control.js +67 -0
  68. package/{src → dist}/lib/components/toolbar/table-search-control.d.ts +3 -1
  69. package/dist/lib/components/toolbar/table-search-control.d.ts.map +1 -0
  70. package/{src → dist}/lib/components/toolbar/table-search-control.js +45 -2
  71. package/{src → dist}/lib/components/toolbar/table-size-control.d.ts +3 -1
  72. package/dist/lib/components/toolbar/table-size-control.d.ts.map +1 -0
  73. package/{src → dist}/lib/components/toolbar/table-size-control.js +20 -8
  74. package/{src → dist}/lib/contexts/data-table-context.d.ts +6 -2
  75. package/dist/lib/contexts/data-table-context.d.ts.map +1 -0
  76. package/{src → dist}/lib/contexts/data-table-context.js +34 -1
  77. package/dist/lib/data-table.d.ts +5 -0
  78. package/dist/lib/data-table.d.ts.map +1 -0
  79. package/{src/lib/components/table → dist/lib}/data-table.js +449 -151
  80. package/dist/lib/features/column-filter.feature.d.ts +55 -0
  81. package/dist/lib/features/column-filter.feature.d.ts.map +1 -0
  82. package/{src → dist}/lib/features/column-filter.feature.js +116 -18
  83. package/dist/lib/features/index.d.ts +9 -0
  84. package/dist/lib/features/index.d.ts.map +1 -0
  85. package/{src → dist}/lib/features/index.js +7 -0
  86. package/{src → dist}/lib/features/selection.feature.d.ts +8 -1
  87. package/dist/lib/features/selection.feature.d.ts.map +1 -0
  88. package/{src → dist}/lib/features/selection.feature.js +76 -15
  89. package/dist/lib/icons/add-icon.d.ts +4 -0
  90. package/dist/lib/icons/add-icon.d.ts.map +1 -0
  91. package/dist/lib/icons/add-icon.js +12 -0
  92. package/dist/lib/icons/csv-icon.d.ts +4 -0
  93. package/dist/lib/icons/csv-icon.d.ts.map +1 -0
  94. package/dist/lib/icons/csv-icon.js +12 -0
  95. package/dist/lib/icons/delete-icon.d.ts +4 -0
  96. package/dist/lib/icons/delete-icon.d.ts.map +1 -0
  97. package/dist/lib/icons/delete-icon.js +12 -0
  98. package/dist/lib/icons/excel-icon.d.ts +4 -0
  99. package/dist/lib/icons/excel-icon.d.ts.map +1 -0
  100. package/dist/lib/icons/excel-icon.js +12 -0
  101. package/dist/lib/icons/index.d.ts +8 -0
  102. package/dist/lib/icons/index.d.ts.map +1 -0
  103. package/dist/lib/icons/unpin-icon.d.ts +4 -0
  104. package/dist/lib/icons/unpin-icon.d.ts.map +1 -0
  105. package/dist/lib/icons/unpin-icon.js +12 -0
  106. package/{src → dist}/lib/icons/view-comfortable-icon.d.ts +3 -1
  107. package/dist/lib/icons/view-comfortable-icon.d.ts.map +1 -0
  108. package/dist/lib/icons/view-comfortable-icon.js +12 -0
  109. package/dist/lib/icons/view-compact-icon.d.ts +4 -0
  110. package/dist/lib/icons/view-compact-icon.d.ts.map +1 -0
  111. package/dist/lib/icons/view-compact-icon.js +12 -0
  112. package/{src → dist}/lib/types/column.types.d.ts +10 -1
  113. package/dist/lib/types/column.types.d.ts.map +1 -0
  114. package/{src → dist}/lib/types/data-table-api.d.ts +2 -1
  115. package/dist/lib/types/data-table-api.d.ts.map +1 -0
  116. package/{src/lib/components/table → dist/lib/types}/data-table.types.d.ts +10 -10
  117. package/dist/lib/types/data-table.types.d.ts.map +1 -0
  118. package/{src → dist}/lib/types/export.types.d.ts +38 -0
  119. package/dist/lib/types/export.types.d.ts.map +1 -0
  120. package/dist/lib/types/export.types.js +6 -0
  121. package/{src → dist}/lib/types/index.d.ts +5 -0
  122. package/dist/lib/types/index.d.ts.map +1 -0
  123. package/dist/lib/types/index.js +30 -0
  124. package/{src → dist}/lib/types/slots.types.d.ts +50 -3
  125. package/dist/lib/types/slots.types.d.ts.map +1 -0
  126. package/{src → dist}/lib/types/table.types.d.ts +14 -0
  127. package/dist/lib/types/table.types.d.ts.map +1 -0
  128. package/{src → dist}/lib/utils/column-helpers.d.ts +10 -0
  129. package/dist/lib/utils/column-helpers.d.ts.map +1 -0
  130. package/{src → dist}/lib/utils/column-helpers.js +20 -4
  131. package/{src → dist}/lib/utils/debounced-fetch.utils.d.ts +3 -5
  132. package/dist/lib/utils/debounced-fetch.utils.d.ts.map +1 -0
  133. package/{src → dist}/lib/utils/debounced-fetch.utils.js +12 -6
  134. package/{src → dist}/lib/utils/export-utils.d.ts +13 -0
  135. package/dist/lib/utils/export-utils.d.ts.map +1 -0
  136. package/dist/lib/utils/export-utils.js +252 -0
  137. package/{src → dist}/lib/utils/index.d.ts +4 -0
  138. package/dist/lib/utils/index.d.ts.map +1 -0
  139. package/dist/lib/utils/index.js +35 -0
  140. package/{src → dist}/lib/utils/logger.d.ts +43 -0
  141. package/dist/lib/utils/logger.d.ts.map +1 -0
  142. package/{src → dist}/lib/utils/logger.js +22 -2
  143. package/{src → dist}/lib/utils/slot-helpers.d.ts +39 -1
  144. package/dist/lib/utils/slot-helpers.d.ts.map +1 -0
  145. package/{src → dist}/lib/utils/slot-helpers.js +55 -6
  146. package/{src → dist}/lib/utils/special-columns.utils.d.ts +10 -0
  147. package/dist/lib/utils/special-columns.utils.d.ts.map +1 -0
  148. package/{src → dist}/lib/utils/special-columns.utils.js +41 -5
  149. package/{src → dist}/lib/utils/styling-helpers.d.ts +20 -0
  150. package/dist/lib/utils/styling-helpers.d.ts.map +1 -0
  151. package/dist/lib/utils/styling-helpers.js +108 -0
  152. package/{src → dist}/lib/utils/table-helpers.d.ts +25 -0
  153. package/dist/lib/utils/table-helpers.d.ts.map +1 -0
  154. package/{src → dist}/lib/utils/table-helpers.js +24 -0
  155. package/package.json +36 -11
  156. package/src/index.ts +71 -0
  157. package/src/lib/components/droupdown/menu-dropdown.tsx +97 -0
  158. package/src/lib/components/filters/filter-value-input.tsx +225 -0
  159. package/src/lib/components/filters/{index.js → index.ts} +3 -6
  160. package/src/lib/components/headers/draggable-header.tsx +326 -0
  161. package/src/lib/components/headers/{index.d.ts → index.ts} +4 -0
  162. package/src/lib/components/headers/table-header.tsx +173 -0
  163. package/src/lib/components/index.ts +21 -0
  164. package/src/lib/components/pagination/data-table-pagination.tsx +99 -0
  165. package/src/lib/components/pagination/index.ts +5 -0
  166. package/src/lib/components/rows/data-table-row.tsx +208 -0
  167. package/src/lib/components/rows/empty-data-row.tsx +69 -0
  168. package/src/lib/components/rows/{index.d.ts → index.ts} +4 -0
  169. package/src/lib/components/rows/loading-rows.tsx +160 -0
  170. package/src/lib/components/toolbar/bulk-actions-toolbar.tsx +125 -0
  171. package/src/lib/components/toolbar/column-filter-control.tsx +374 -0
  172. package/src/lib/components/toolbar/column-pinning-control.tsx +275 -0
  173. package/src/lib/components/toolbar/column-reset-control.tsx +74 -0
  174. package/src/lib/components/toolbar/column-visibility-control.tsx +105 -0
  175. package/src/lib/components/toolbar/data-table-toolbar.tsx +229 -0
  176. package/src/lib/components/toolbar/index.ts +17 -0
  177. package/src/lib/components/toolbar/table-export-control.tsx +179 -0
  178. package/src/lib/components/toolbar/table-search-control.tsx +155 -0
  179. package/src/lib/components/toolbar/table-size-control.tsx +102 -0
  180. package/src/lib/contexts/data-table-context.tsx +112 -0
  181. package/src/lib/data-table.tsx +1911 -0
  182. package/src/lib/features/README.md +161 -0
  183. package/src/lib/features/column-filter.feature.ts +456 -0
  184. package/src/lib/features/index.ts +23 -0
  185. package/src/lib/features/selection.feature.ts +318 -0
  186. package/src/lib/icons/add-icon.tsx +23 -0
  187. package/src/lib/icons/csv-icon.tsx +15 -0
  188. package/src/lib/icons/delete-icon.tsx +30 -0
  189. package/src/lib/icons/excel-icon.tsx +15 -0
  190. package/src/lib/icons/unpin-icon.tsx +18 -0
  191. package/src/lib/icons/view-comfortable-icon.tsx +45 -0
  192. package/src/lib/icons/view-compact-icon.tsx +55 -0
  193. package/src/lib/types/column.types.ts +44 -0
  194. package/src/lib/types/data-table-api.ts +169 -0
  195. package/src/lib/types/data-table.types.ts +139 -0
  196. package/src/lib/types/export.types.ts +154 -0
  197. package/src/lib/types/index.ts +22 -0
  198. package/src/lib/types/slots.types.ts +332 -0
  199. package/src/lib/types/table.types.ts +90 -0
  200. package/src/lib/utils/column-helpers.ts +72 -0
  201. package/src/lib/utils/debounced-fetch.utils.ts +54 -0
  202. package/src/lib/utils/export-utils.ts +285 -0
  203. package/src/lib/utils/index.ts +27 -0
  204. package/src/lib/utils/logger.ts +203 -0
  205. package/src/lib/utils/slot-helpers.tsx +194 -0
  206. package/src/lib/utils/special-columns.utils.ts +94 -0
  207. package/src/lib/utils/styling-helpers.ts +126 -0
  208. package/src/lib/utils/table-helpers.ts +106 -0
  209. package/src/index.js +0 -27
  210. package/src/lib/components/filters/filter-value-input.js +0 -41
  211. package/src/lib/components/headers/index.js +0 -5
  212. package/src/lib/components/index.js +0 -10
  213. package/src/lib/components/pagination/index.d.ts +0 -1
  214. package/src/lib/components/pagination/index.js +0 -4
  215. package/src/lib/components/rows/index.js +0 -6
  216. package/src/lib/components/table/data-table.d.ts +0 -4
  217. package/src/lib/components/table/index.d.ts +0 -2
  218. package/src/lib/components/table/index.js +0 -5
  219. package/src/lib/components/toolbar/bulk-actions-toolbar.js +0 -30
  220. package/src/lib/components/toolbar/column-visibility-control.js +0 -31
  221. package/src/lib/components/toolbar/table-export-control.d.ts +0 -31
  222. package/src/lib/components/toolbar/table-export-control.js +0 -56
  223. package/src/lib/examples/advanced-features-example.d.ts +0 -1
  224. package/src/lib/examples/advanced-features-example.js +0 -269
  225. package/src/lib/examples/bulk-actions-test.d.ts +0 -1
  226. package/src/lib/examples/bulk-actions-test.js +0 -44
  227. package/src/lib/examples/custom-column-filter-example.d.ts +0 -1
  228. package/src/lib/examples/custom-column-filter-example.js +0 -60
  229. package/src/lib/examples/index.d.ts +0 -8
  230. package/src/lib/examples/index.js +0 -19
  231. package/src/lib/examples/selection-test-example.d.ts +0 -1
  232. package/src/lib/examples/selection-test-example.js +0 -101
  233. package/src/lib/examples/server-side-fetching-example.d.ts +0 -1
  234. package/src/lib/examples/server-side-fetching-example.js +0 -245
  235. package/src/lib/examples/server-side-test.d.ts +0 -1
  236. package/src/lib/examples/server-side-test.js +0 -9
  237. package/src/lib/examples/simple-local-example.d.ts +0 -1
  238. package/src/lib/examples/simple-local-example.js +0 -95
  239. package/src/lib/examples/simple-slots-example.d.ts +0 -1
  240. package/src/lib/examples/simple-slots-example.js +0 -115
  241. package/src/lib/features/column-filter.feature.d.ts +0 -45
  242. package/src/lib/features/index.d.ts +0 -2
  243. package/src/lib/hooks/index.d.ts +0 -1
  244. package/src/lib/hooks/index.js +0 -4
  245. package/src/lib/hooks/use-data-table-api.d.ts +0 -46
  246. package/src/lib/hooks/use-data-table-api.js +0 -690
  247. package/src/lib/icons/add-icon.d.ts +0 -2
  248. package/src/lib/icons/add-icon.js +0 -8
  249. package/src/lib/icons/csv-icon.d.ts +0 -2
  250. package/src/lib/icons/csv-icon.js +0 -8
  251. package/src/lib/icons/delete-icon.d.ts +0 -2
  252. package/src/lib/icons/delete-icon.js +0 -8
  253. package/src/lib/icons/excel-icon.d.ts +0 -2
  254. package/src/lib/icons/excel-icon.js +0 -8
  255. package/src/lib/icons/unpin-icon.d.ts +0 -2
  256. package/src/lib/icons/unpin-icon.js +0 -8
  257. package/src/lib/icons/view-comfortable-icon.js +0 -8
  258. package/src/lib/icons/view-compact-icon.d.ts +0 -2
  259. package/src/lib/icons/view-compact-icon.js +0 -8
  260. package/src/lib/types/export.types.js +0 -2
  261. package/src/lib/types/index.js +0 -8
  262. package/src/lib/utils/export-utils.js +0 -175
  263. package/src/lib/utils/index.js +0 -11
  264. package/src/lib/utils/styling-helpers.js +0 -70
  265. package/tsconfig.tsbuildinfo +0 -1
  266. /package/{src → dist}/lib/icons/index.js +0 -0
  267. /package/{src → dist}/lib/types/column.types.js +0 -0
  268. /package/{src → dist}/lib/types/data-table-api.js +0 -0
  269. /package/{src/lib/components/table → dist/lib/types}/data-table.types.js +0 -0
  270. /package/{src → dist}/lib/types/slots.types.js +0 -0
  271. /package/{src → dist}/lib/types/table.types.js +0 -0
  272. /package/src/lib/icons/{index.d.ts → index.ts} +0 -0
@@ -0,0 +1,1911 @@
1
+ /**
2
+ * Main DataTable Component
3
+ *
4
+ * A comprehensive, highly customizable data table component built with:
5
+ * - Material-UI (MUI) for styling
6
+ * - TanStack Table for table logic
7
+ * - TypeScript for type safety
8
+ */
9
+ import {
10
+ Table,
11
+ TableContainer,
12
+ TableBody,
13
+ Box,
14
+ Paper,
15
+ } from '@mui/material';
16
+ import {
17
+ getCoreRowModel,
18
+ useReactTable,
19
+ SortingState,
20
+ getSortedRowModel,
21
+ ColumnOrderState,
22
+ ColumnPinningState,
23
+ getPaginationRowModel,
24
+ Updater,
25
+ } from '@tanstack/react-table';
26
+
27
+ // Import custom features
28
+ import { ColumnFilterFeature, getCombinedFilteredRowModel } from './features/column-filter.feature';
29
+ import { SelectionFeature, SelectionState } from './features';
30
+ import { useVirtualizer } from '@tanstack/react-virtual';
31
+ import React, { useState, useCallback, useMemo, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
32
+
33
+
34
+ // Import from new organized structure
35
+ import { DataTableProvider } from './contexts/data-table-context';
36
+ import { DataTableSize, exportClientData, exportServerData, generateRowId, createLogger, withIdsDeep } from './utils';
37
+ import { useDebouncedFetch } from './utils/debounced-fetch.utils';
38
+ import { getSlotComponentWithProps, mergeSlotProps } from './utils/slot-helpers';
39
+ import { TableHeader } from './components/headers';
40
+ import { DataTablePagination } from './components/pagination';
41
+ import { DataTableRow, LoadingRows, EmptyDataRow } from './components/rows';
42
+ import { DataTableToolbar, BulkActionsToolbar } from './components/toolbar';
43
+ import { DataTableProps } from './types/data-table.types';
44
+ import { ColumnFilterState, TableFiltersForFetch, TableState } from './types';
45
+ import { DataTableApi } from './types/data-table-api';
46
+ import {
47
+ createExpandingColumn,
48
+ createSelectionColumn,
49
+ } from './utils/special-columns.utils';
50
+
51
+
52
+
53
+ // Static default initial state - defined outside component
54
+ const DEFAULT_INITIAL_STATE = {
55
+ sorting: [],
56
+ pagination: {
57
+ pageIndex: 0,
58
+ pageSize: 10,
59
+ },
60
+ selectionState: { ids: [], type: 'include' } as SelectionState,
61
+ globalFilter: '',
62
+ expanded: {},
63
+ columnOrder: [],
64
+ columnPinning: {
65
+ left: [],
66
+ right: [],
67
+ },
68
+ columnVisibility: {},
69
+ columnSizing: {},
70
+ columnFilter: {
71
+ filters: [],
72
+ logic: 'AND',
73
+ pendingFilters: [],
74
+ pendingLogic: 'AND',
75
+ } as ColumnFilterState,
76
+ };
77
+
78
+ /**
79
+ * Main DataTable component with all features
80
+ */
81
+ export const DataTable = forwardRef<DataTableApi<any>, DataTableProps<any>>(function DataTable<T extends Record<string, any>>({
82
+ initialState,
83
+ columns,
84
+ data = [],
85
+ totalRow = 0,
86
+ idKey = 'id' as keyof T,
87
+ extraFilter = null,
88
+ footerFilter = null,
89
+
90
+ // Data management mode (MUI DataGrid style)
91
+ dataMode = 'client',
92
+ initialLoadData = true,
93
+ onFetchData,
94
+ onDataStateChange,
95
+
96
+ // Selection props
97
+ enableRowSelection = false,
98
+ enableMultiRowSelection = true,
99
+ selectMode = 'page',
100
+ isRowSelectable,
101
+ onSelectionChange,
102
+
103
+ // Row click props
104
+ onRowClick,
105
+ selectOnRowClick = false,
106
+
107
+ // Bulk action props
108
+ enableBulkActions = false,
109
+ bulkActions,
110
+
111
+ // Column resizing props
112
+ enableColumnResizing = false,
113
+ columnResizeMode = 'onChange',
114
+ onColumnSizingChange,
115
+
116
+ // Column ordering props
117
+ enableColumnDragging = false,
118
+ onColumnDragEnd,
119
+
120
+ // Column pinning props
121
+ enableColumnPinning = false,
122
+ onColumnPinningChange,
123
+
124
+ // Column visibility props
125
+ onColumnVisibilityChange,
126
+ enableColumnVisibility = true,
127
+
128
+ // Expandable rows props
129
+ enableExpanding = false,
130
+ getRowCanExpand,
131
+ renderSubComponent,
132
+
133
+ // Pagination props
134
+ enablePagination = false,
135
+ paginationMode = 'client',
136
+
137
+ // Filtering props
138
+ enableGlobalFilter = true,
139
+ enableColumnFilter = false,
140
+ filterMode = 'client',
141
+
142
+ // Sorting props
143
+ enableSorting = true,
144
+ sortingMode = 'client',
145
+ onSortingChange,
146
+ exportFilename = 'export',
147
+ onExportProgress,
148
+ onExportComplete,
149
+ onExportError,
150
+ onServerExport,
151
+ onExportCancel,
152
+
153
+ // Styling props
154
+ enableHover = true,
155
+ enableStripes = false,
156
+ tableProps = {},
157
+ fitToScreen = true,
158
+ tableSize: initialTableSize = 'medium',
159
+
160
+ // Sticky header/footer props
161
+ enableStickyHeaderOrFooter = false,
162
+ maxHeight = '400px',
163
+
164
+ // Virtualization props
165
+ enableVirtualization = false,
166
+ estimateRowHeight = 52,
167
+
168
+ // Toolbar props
169
+ enableTableSizeControl = true,
170
+ enableExport = false,
171
+ enableReset = true,
172
+
173
+ // Loading and empty states
174
+ loading = false,
175
+ emptyMessage = 'No data available',
176
+ skeletonRows = 5,
177
+
178
+ // Column filters props
179
+ onColumnFiltersChange,
180
+ onPaginationChange,
181
+ onGlobalFilterChange,
182
+
183
+ // Slots
184
+ slots = {},
185
+ slotProps = {},
186
+
187
+ // Logging
188
+ logging,
189
+
190
+ }: DataTableProps<T>, ref: React.Ref<DataTableApi<T>>) {
191
+ // Convert mode-based props to boolean flags for internal use
192
+ const isServerMode = dataMode === 'server';
193
+ const isServerPagination = paginationMode === 'server' || isServerMode;
194
+ const isServerFiltering = filterMode === 'server' || isServerMode;
195
+ const isServerSorting = sortingMode === 'server' || isServerMode;
196
+
197
+ const logger = useMemo(() => createLogger('DataTable', logging), [logging]);
198
+
199
+ useEffect(() => {
200
+ if (logger.isLevelEnabled('info')) {
201
+ logger.info('mounted', {
202
+ dataMode,
203
+ paginationMode,
204
+ filterMode,
205
+ sortingMode,
206
+ });
207
+ }
208
+ return () => {
209
+ if (logger.isLevelEnabled('info')) {
210
+ logger.info('unmounted');
211
+ }
212
+ };
213
+ }, [logger, dataMode, paginationMode, filterMode, sortingMode]);
214
+
215
+ // -------------------------------
216
+ // Memoized values (grouped together)
217
+ // -------------------------------
218
+ const initialStateConfig = useMemo(() => {
219
+ const config = {
220
+ ...DEFAULT_INITIAL_STATE,
221
+ ...initialState,
222
+ };
223
+ if (logger.isLevelEnabled('info')) {
224
+ logger.info('initialStateConfig', { config });
225
+ }
226
+ return config;
227
+ }, [initialState, logger]);
228
+
229
+
230
+ const initialSelectionState = useMemo(() => {
231
+ return initialStateConfig.selectionState || DEFAULT_INITIAL_STATE.selectionState;
232
+ }, [initialStateConfig.selectionState]);
233
+
234
+ // -------------------------------
235
+ // State hooks (grouped together)
236
+ // -------------------------------
237
+ // const [fetchLoading, setFetchLoading] = useState(false);
238
+ const [sorting, setSorting] = useState<SortingState>(initialState?.sorting || DEFAULT_INITIAL_STATE.sorting);
239
+ const [pagination, setPagination] = useState(initialState?.pagination || DEFAULT_INITIAL_STATE.pagination);
240
+ const [globalFilter, setGlobalFilter] = useState(initialState?.globalFilter || DEFAULT_INITIAL_STATE.globalFilter);
241
+ const [selectionState, setSelectionState] = useState<SelectionState>(initialState?.selectionState || DEFAULT_INITIAL_STATE.selectionState);
242
+ const [columnFilter, setColumnFilter] = useState<ColumnFilterState>(initialState?.columnFilter || DEFAULT_INITIAL_STATE.columnFilter);
243
+ const [expanded, setExpanded] = useState({});
244
+ const [tableSize, setTableSize] = useState<DataTableSize>(initialTableSize || 'medium');
245
+ const [columnOrder, setColumnOrder] = useState<ColumnOrderState>(initialState?.columnOrder || DEFAULT_INITIAL_STATE.columnOrder);
246
+ const [columnPinning, setColumnPinning] = useState<ColumnPinningState>(initialState?.columnPinning || DEFAULT_INITIAL_STATE.columnPinning);
247
+ const [columnVisibility, setColumnVisibility] = useState<Record<string, boolean>>(initialState?.columnVisibility || DEFAULT_INITIAL_STATE.columnVisibility);
248
+ const [columnSizing, setColumnSizing] = useState<Record<string, number>>(initialState?.columnSizing || DEFAULT_INITIAL_STATE.columnSizing);
249
+ const [serverData, setServerData] = useState<T[] | null>(null);
250
+ const [serverTotal, setServerTotal] = useState(0);
251
+ const [exportController, setExportController] = useState<AbortController | null>(null);
252
+
253
+ // -------------------------------
254
+ // Ref hooks (grouped together)
255
+ // -------------------------------
256
+ const tableContainerRef = useRef<HTMLDivElement>(null);
257
+ const internalApiRef = useRef<DataTableApi<T>>(null);
258
+
259
+ const { debouncedFetch, isLoading: fetchLoading } = useDebouncedFetch(onFetchData);
260
+ const tableData = useMemo(() => serverData ? serverData : data, [serverData, data]);
261
+ const tableTotalRow = useMemo(() => serverData ? serverTotal : totalRow || data.length, [serverData, serverTotal, totalRow, data]);
262
+ const tableLoading = useMemo(() => onFetchData ? (loading || fetchLoading) : loading, [onFetchData, loading, fetchLoading]);
263
+
264
+
265
+ const enhancedColumns = useMemo(
266
+ () => {
267
+ let columnsMap = [...columns];
268
+ if (enableExpanding) {
269
+ const expandingColumnMap = createExpandingColumn<T>({
270
+ ...(slotProps?.expandColumn && typeof slotProps.expandColumn === 'object' ? slotProps.expandColumn : {}),
271
+ });
272
+ columnsMap = [expandingColumnMap, ...columnsMap];
273
+ }
274
+ if (enableRowSelection) {
275
+ const selectionColumnMap = createSelectionColumn<T>({
276
+ ...(slotProps?.selectionColumn && typeof slotProps.selectionColumn === 'object' ? slotProps.selectionColumn : {}),
277
+ multiSelect: enableMultiRowSelection,
278
+ });
279
+ columnsMap = [selectionColumnMap, ...columnsMap];
280
+ }
281
+ const enhancedColumns = withIdsDeep(columnsMap);
282
+ if (logger.isLevelEnabled('info')) {
283
+ logger.info('enhancedColumns', { enhancedColumns });
284
+ }
285
+ return enhancedColumns;
286
+ }, [columns, enableExpanding, enableRowSelection, logger, slotProps.expandColumn, slotProps.selectionColumn, enableMultiRowSelection]);
287
+
288
+
289
+ const isExporting = useMemo(() => exportController !== null, [exportController]);
290
+ const isSomeRowsSelected = useMemo(() => {
291
+ if (!enableBulkActions || !enableRowSelection) return false;
292
+ if (selectionState.type === 'exclude') {
293
+ return selectionState.ids.length < tableTotalRow;
294
+ } else {
295
+ return selectionState.ids.length > 0;
296
+ }
297
+ }, [enableBulkActions, enableRowSelection, selectionState, tableTotalRow]);
298
+ const selectedRowCount = useMemo(() => {
299
+ if (!enableBulkActions || !enableRowSelection) return 0;
300
+ if (selectionState.type === 'exclude') {
301
+ return tableTotalRow - selectionState.ids.length;
302
+ } else {
303
+ return selectionState.ids.length;
304
+ }
305
+ }, [enableBulkActions, enableRowSelection, selectionState, tableTotalRow]);
306
+ // -------------------------------
307
+ // Callback hooks (grouped together)
308
+ // -------------------------------
309
+ const fetchData = useCallback(async (overrides: Partial<TableState> = {}) => {
310
+ if (!onFetchData) {
311
+ if (logger.isLevelEnabled('debug')) {
312
+ logger.debug('onFetchData not provided, skipping fetch', { overrides, columnFilter, sorting, pagination });
313
+ }
314
+ return;
315
+ }
316
+
317
+ const filters: TableFiltersForFetch = {
318
+ globalFilter,
319
+ pagination,
320
+ columnFilter,
321
+ sorting,
322
+ ...overrides,
323
+ };
324
+
325
+ if (logger.isLevelEnabled('info')) {
326
+ logger.info('Requesting data', { filters });
327
+ }
328
+
329
+ try {
330
+ const result = await debouncedFetch(filters);
331
+
332
+ if (logger.isLevelEnabled('info')) {
333
+ logger.info('Fetch resolved', {
334
+ rows: result?.data?.length ?? 0,
335
+ total: result?.total,
336
+ });
337
+ }
338
+
339
+ if (result?.data && result?.total !== undefined) {
340
+ setServerData(result.data);
341
+ setServerTotal(result.total);
342
+ } else if (logger.isLevelEnabled('warn')) {
343
+ logger.warn('Fetch handler returned unexpected shape', result);
344
+ }
345
+
346
+ return result;
347
+ } catch (error) {
348
+ logger.error('Fetch failed', error);
349
+ throw error;
350
+ }
351
+ }, [
352
+ onFetchData,
353
+ globalFilter,
354
+ pagination,
355
+ columnFilter,
356
+ sorting,
357
+ debouncedFetch,
358
+ logger,
359
+ ]);
360
+
361
+
362
+ const tableStateChange = useCallback((overrides: Partial<TableState> = {}) => {
363
+ if (!onDataStateChange) {
364
+ if (logger.isLevelEnabled('debug')) {
365
+ logger.debug('No onDataStateChange handler registered; skipping state update notification', { overrides });
366
+ }
367
+ return;
368
+ }
369
+
370
+ const currentState: Partial<TableState> = {
371
+ globalFilter,
372
+ columnFilter,
373
+ sorting,
374
+ pagination,
375
+ columnOrder,
376
+ columnPinning,
377
+ columnVisibility,
378
+ columnSizing,
379
+ ...overrides,
380
+ };
381
+
382
+ if (logger.isLevelEnabled('debug')) {
383
+ logger.debug('Emitting tableStateChange', currentState);
384
+ }
385
+
386
+ onDataStateChange?.(currentState);
387
+ }, [
388
+ onDataStateChange,
389
+ globalFilter,
390
+ columnFilter,
391
+ sorting,
392
+ pagination,
393
+ columnOrder,
394
+ columnPinning,
395
+ columnVisibility,
396
+ columnSizing,
397
+ logger,
398
+ ]);
399
+
400
+
401
+ const handleSelectionStateChange = useCallback((updaterOrValue) => {
402
+ setSelectionState((prevState) => {
403
+ const newSelectionState = typeof updaterOrValue === 'function'
404
+ ? updaterOrValue(prevState)
405
+ : updaterOrValue;
406
+ setTimeout(() => {
407
+ if (onSelectionChange) {
408
+ onSelectionChange(newSelectionState);
409
+ }
410
+ if (onDataStateChange) {
411
+ tableStateChange({ selectionState: newSelectionState });
412
+ }
413
+ }, 0);
414
+ return newSelectionState;
415
+ });
416
+ }, [onSelectionChange, onDataStateChange, tableStateChange]);
417
+
418
+ const handleColumnFilterStateChange = useCallback((filterState: ColumnFilterState) => {
419
+ if (!filterState || typeof filterState !== 'object') return;
420
+
421
+ setColumnFilter(filterState);
422
+
423
+ if (onColumnFiltersChange) {
424
+ setTimeout(() => onColumnFiltersChange(filterState), 0);
425
+ }
426
+
427
+ if (onDataStateChange) {
428
+ setTimeout(() => tableStateChange({ columnFilter: filterState }), 0);
429
+ }
430
+ }, [onColumnFiltersChange, onDataStateChange, tableStateChange]);
431
+
432
+
433
+ const resetPageToFirst = useCallback(() => {
434
+ if (logger.isLevelEnabled('info')) {
435
+ logger.info('Resetting to first page due to state change', {
436
+ previousPageIndex: pagination.pageIndex,
437
+ pageSize: pagination.pageSize,
438
+ });
439
+ }
440
+ const newPagination = { pageIndex: 0, pageSize: pagination.pageSize };
441
+ setPagination(newPagination);
442
+ onPaginationChange?.(newPagination);
443
+ return newPagination;
444
+ }, [pagination, logger, onPaginationChange]);
445
+
446
+
447
+ const handleSortingChange = useCallback((updaterOrValue: any) => {
448
+ let newSorting = typeof updaterOrValue === 'function'
449
+ ? updaterOrValue(sorting)
450
+ : updaterOrValue;
451
+ newSorting = newSorting.filter((sort: any) => sort.id);
452
+ setSorting(newSorting);
453
+ onSortingChange?.(newSorting);
454
+
455
+ if (logger.isLevelEnabled('debug')) {
456
+ logger.debug('Sorting change applied', {
457
+ sorting: newSorting,
458
+ serverMode: isServerMode,
459
+ serverSorting: isServerSorting,
460
+ });
461
+ }
462
+
463
+ if (isServerMode || isServerSorting) {
464
+ const pagination = resetPageToFirst();
465
+ if (logger.isLevelEnabled('debug')) {
466
+ logger.debug('Sorting change triggered server fetch', { pagination, sorting: newSorting });
467
+ }
468
+ tableStateChange({ sorting: newSorting, pagination });
469
+ fetchData({
470
+ sorting: newSorting,
471
+ pagination,
472
+ });
473
+ } else if (onDataStateChange) {
474
+ const pagination = resetPageToFirst();
475
+ setTimeout(() => {
476
+ if (logger.isLevelEnabled('debug')) {
477
+ logger.debug('Sorting change notified client state change', { pagination, sorting: newSorting });
478
+ }
479
+ tableStateChange({ sorting: newSorting, pagination });
480
+ }, 0);
481
+ }
482
+ }, [sorting, onSortingChange, logger, isServerMode, isServerSorting, onDataStateChange, resetPageToFirst, tableStateChange, fetchData]);
483
+
484
+ const handleColumnOrderChange = useCallback((updatedColumnOrder: Updater<ColumnOrderState>) => {
485
+ const newColumnOrder = typeof updatedColumnOrder === 'function'
486
+ ? updatedColumnOrder(columnOrder)
487
+ : updatedColumnOrder;
488
+ setColumnOrder(newColumnOrder);
489
+ if (onColumnDragEnd) {
490
+ onColumnDragEnd(newColumnOrder);
491
+ }
492
+ }, [onColumnDragEnd, columnOrder]);
493
+
494
+ const handleColumnPinningChange = useCallback((updatedColumnPinning: Updater<ColumnPinningState>) => {
495
+ const newColumnPinning = typeof updatedColumnPinning === 'function'
496
+ ? updatedColumnPinning(columnPinning)
497
+ : updatedColumnPinning;
498
+ setColumnPinning(newColumnPinning);
499
+ if (onColumnPinningChange) {
500
+ onColumnPinningChange(newColumnPinning);
501
+ }
502
+ }, [onColumnPinningChange, columnPinning]);
503
+
504
+ // Column visibility change handler - same pattern as column order
505
+ const handleColumnVisibilityChange = useCallback((updater: any) => {
506
+ const newVisibility = typeof updater === 'function'
507
+ ? updater(columnVisibility)
508
+ : updater;
509
+ setColumnVisibility(newVisibility);
510
+
511
+ if (onColumnVisibilityChange) {
512
+ setTimeout(() => {
513
+ onColumnVisibilityChange(newVisibility);
514
+ }, 0);
515
+ }
516
+
517
+ if (onDataStateChange) {
518
+ setTimeout(() => {
519
+ tableStateChange({ columnVisibility: newVisibility });
520
+ }, 0);
521
+ }
522
+ }, [onColumnVisibilityChange, onDataStateChange, tableStateChange, columnVisibility]);
523
+
524
+ // Column sizing change handler - same pattern as column order
525
+ const handleColumnSizingChange = useCallback((updater: any) => {
526
+ const newSizing = typeof updater === 'function'
527
+ ? updater(columnSizing)
528
+ : updater;
529
+ setColumnSizing(newSizing);
530
+
531
+ if (onColumnSizingChange) {
532
+ setTimeout(() => {
533
+ onColumnSizingChange(newSizing);
534
+ }, 0);
535
+ }
536
+
537
+ if (onDataStateChange) {
538
+ setTimeout(() => {
539
+ tableStateChange({ columnSizing: newSizing });
540
+ }, 0);
541
+ }
542
+ }, [onColumnSizingChange, onDataStateChange, tableStateChange, columnSizing]);
543
+
544
+ const handlePaginationChange = useCallback((updater: any) => {
545
+ const newPagination = typeof updater === 'function' ? updater(pagination) : updater;
546
+ if (logger.isLevelEnabled('debug')) {
547
+ logger.debug('Pagination change requested', {
548
+ previous: pagination,
549
+ next: newPagination,
550
+ serverSide: isServerMode || isServerPagination,
551
+ });
552
+ }
553
+
554
+ // Update pagination state
555
+ setPagination(newPagination);
556
+ onPaginationChange?.(newPagination);
557
+
558
+ if (logger.isLevelEnabled('debug')) {
559
+ logger.debug('Pagination state updated', newPagination);
560
+ }
561
+
562
+ // Notify state change and fetch data if needed
563
+ if (isServerMode || isServerPagination) {
564
+ setTimeout(() => {
565
+ if (logger.isLevelEnabled('debug')) {
566
+ logger.debug('Notifying server-side pagination change', newPagination);
567
+ }
568
+ tableStateChange({ pagination: newPagination });
569
+ fetchData({ pagination: newPagination });
570
+ }, 0);
571
+ } else if (onDataStateChange) {
572
+ setTimeout(() => {
573
+ if (logger.isLevelEnabled('debug')) {
574
+ logger.debug('Notifying client-side pagination change', newPagination);
575
+ }
576
+ tableStateChange({ pagination: newPagination });
577
+ }, 0);
578
+ }
579
+ }, [
580
+ pagination,
581
+ isServerMode,
582
+ isServerPagination,
583
+ onDataStateChange,
584
+ fetchData,
585
+ tableStateChange,
586
+ logger,
587
+ onPaginationChange,
588
+ ]);
589
+
590
+
591
+
592
+ const handleGlobalFilterChange = useCallback((updaterOrValue: any) => {
593
+ const newFilter = typeof updaterOrValue === 'function'
594
+ ? updaterOrValue(globalFilter)
595
+ : updaterOrValue;
596
+ setGlobalFilter(newFilter);
597
+
598
+ if (logger.isLevelEnabled('debug')) {
599
+ logger.debug('Global filter change applied', {
600
+ value: newFilter,
601
+ serverMode: isServerMode,
602
+ serverFiltering: isServerFiltering,
603
+ });
604
+ }
605
+
606
+ if (isServerMode || isServerFiltering) {
607
+ const pagination = resetPageToFirst();
608
+ setTimeout(() => {
609
+ if (logger.isLevelEnabled('debug')) {
610
+ logger.debug('Global filter change triggering server fetch', {
611
+ pagination,
612
+ value: newFilter,
613
+ });
614
+ }
615
+ tableStateChange({ globalFilter: newFilter, pagination });
616
+ fetchData({ globalFilter: newFilter, pagination });
617
+ }, 0);
618
+ } else if (onDataStateChange) {
619
+ const pagination = resetPageToFirst();
620
+ setTimeout(() => {
621
+ if (logger.isLevelEnabled('debug')) {
622
+ logger.debug('Global filter change notifying client listeners', {
623
+ pagination,
624
+ value: newFilter,
625
+ });
626
+ }
627
+ tableStateChange({ globalFilter: newFilter, pagination });
628
+ }, 0);
629
+ }
630
+ onGlobalFilterChange?.(newFilter);
631
+ }, [globalFilter, logger, isServerMode, isServerFiltering, onDataStateChange, onGlobalFilterChange, resetPageToFirst, tableStateChange, fetchData]);
632
+
633
+ const onColumnFilterChangeHandler = useCallback((updater: any) => {
634
+ const currentState = columnFilter;
635
+ const newState = typeof updater === 'function'
636
+ ? updater(currentState)
637
+ : updater;
638
+ const legacyFilterState = {
639
+ filters: newState.filters,
640
+ logic: newState.logic,
641
+ pendingFilters: newState.pendingFilters,
642
+ pendingLogic: newState.pendingLogic
643
+ };
644
+ handleColumnFilterStateChange(legacyFilterState);
645
+ }, [columnFilter, handleColumnFilterStateChange]);
646
+
647
+ const onColumnFilterApplyHandler = useCallback((appliedState: ColumnFilterState) => {
648
+ const pagination = resetPageToFirst();
649
+
650
+ if (isServerFiltering) {
651
+ tableStateChange({
652
+ columnFilter: appliedState,
653
+ pagination,
654
+ });
655
+ fetchData({
656
+ columnFilter: appliedState,
657
+ pagination,
658
+ });
659
+ } else if (onDataStateChange) {
660
+ setTimeout(() => tableStateChange({ columnFilter: appliedState, pagination }), 0);
661
+ }
662
+
663
+ setTimeout(() => {
664
+ onColumnFiltersChange?.(appliedState);
665
+ }, 0);
666
+ }, [resetPageToFirst, isServerFiltering, onDataStateChange, tableStateChange, fetchData, onColumnFiltersChange]);
667
+
668
+ // -------------------------------
669
+ // Table creation (after callbacks/memo)
670
+ // -------------------------------
671
+ const table = useReactTable({
672
+ _features: [ColumnFilterFeature, SelectionFeature],
673
+ data: tableData,
674
+ columns: enhancedColumns,
675
+ // Use merged initial state so built-in reset helpers align with our controlled state defaults
676
+ initialState: initialStateConfig,
677
+ state: {
678
+ ...(enableSorting ? { sorting } : {}),
679
+ ...(enablePagination ? { pagination } : {}),
680
+ ...(enableGlobalFilter ? { globalFilter } : {}),
681
+ ...(enableExpanding ? { expanded } : {}),
682
+ ...(enableColumnDragging ? { columnOrder } : {}),
683
+ ...(enableColumnPinning ? { columnPinning } : {}),
684
+ ...(enableColumnVisibility ? { columnVisibility } : {}),
685
+ ...(enableColumnResizing ? { columnSizing } : {}),
686
+ ...(enableColumnFilter ? { columnFilter } : {}),
687
+ ...(enableRowSelection ? { selectionState } : {}),
688
+ },
689
+ // Selection options (same pattern as column filter)
690
+ // Add custom features
691
+ selectMode: selectMode,
692
+ enableAdvanceSelection: !!enableRowSelection,
693
+ isRowSelectable: isRowSelectable,
694
+ ...(enableRowSelection ? { onSelectionStateChange: handleSelectionStateChange } : {}),
695
+ // Column filter
696
+ enableAdvanceColumnFilter: enableColumnFilter,
697
+ onColumnFilterChange: onColumnFilterChangeHandler, // Handle column filters change
698
+ onColumnFilterApply: onColumnFilterApplyHandler, // Handle when filters are actually applied
699
+
700
+
701
+ ...(enableSorting ? { onSortingChange: handleSortingChange } : {}),
702
+ ...(enablePagination ? { onPaginationChange: handlePaginationChange } : {}),
703
+ ...(enableGlobalFilter ? { onGlobalFilterChange: handleGlobalFilterChange } : {}),
704
+ ...(enableExpanding ? { onExpandedChange: setExpanded } : {}),
705
+ ...(enableColumnDragging ? { onColumnOrderChange: handleColumnOrderChange } : {}),
706
+ ...(enableColumnPinning ? { onColumnPinningChange: handleColumnPinningChange } : {}),
707
+ ...(enableColumnVisibility ? { onColumnVisibilityChange: handleColumnVisibilityChange } : {}),
708
+ ...(enableColumnResizing ? { onColumnSizingChange: handleColumnSizingChange } : {}),
709
+
710
+ // Row model
711
+ getCoreRowModel: getCoreRowModel(),
712
+ ...(enableSorting ? { getSortedRowModel: getSortedRowModel() } : {}),
713
+ ...(enableColumnFilter ? { getFilteredRowModel: getCombinedFilteredRowModel<T>() } : {}),
714
+ ...(enablePagination ? { getPaginationRowModel: getPaginationRowModel() } : {}),
715
+ // Sorting
716
+ enableSorting: enableSorting,
717
+ manualSorting: isServerSorting,
718
+ // Filtering
719
+ manualFiltering: isServerFiltering,
720
+ // Column resizing
721
+ enableColumnResizing: enableColumnResizing,
722
+ columnResizeMode: columnResizeMode,
723
+ // Column pinning
724
+ enableColumnPinning: enableColumnPinning,
725
+ // Expanding
726
+ ...(enableExpanding ? { getRowCanExpand: getRowCanExpand } : {}),
727
+ // Pagination
728
+ manualPagination: isServerPagination,
729
+ autoResetPageIndex: false, // Prevent automatic page reset on state changes
730
+ // pageCount: enablePagination ? Math.ceil(tableTotalRow / pagination.pageSize) : -1,
731
+ rowCount: enablePagination ? (tableTotalRow ?? tableData.length) : tableData.length,
732
+ // Row ID
733
+ getRowId: (row: any, index: number) => generateRowId(row, index, idKey),
734
+ // Debug
735
+ debugAll: false, // Disabled for production
736
+ });
737
+
738
+ // Compute width after table is created so column resizing is safe and reflects changes
739
+ const tableWidth = useMemo(() => {
740
+ if (fitToScreen) {
741
+ return '100%';
742
+ }
743
+ if (enableColumnResizing) {
744
+ return table.getCenterTotalSize();
745
+ }
746
+ return '100%';
747
+ }, [fitToScreen, enableColumnResizing, table]);
748
+
749
+ const tableStyle = useMemo(() => ({
750
+ width: tableWidth,
751
+ minWidth: '100%',
752
+ }), [tableWidth]);
753
+
754
+
755
+ // -------------------------------
756
+ // Virtualization and row memo
757
+ // -------------------------------
758
+ // eslint-disable-next-line react-hooks/exhaustive-deps
759
+ const rows = useMemo(() => table.getRowModel()?.rows || [], [table, tableData]);
760
+ const rowVirtualizer = useVirtualizer({
761
+ count: rows.length,
762
+ getScrollElement: () => tableContainerRef.current,
763
+ estimateSize: () => estimateRowHeight,
764
+ overscan: 10,
765
+ enabled: enableVirtualization && !enablePagination && rows.length > 0,
766
+ });
767
+
768
+
769
+ // -------------------------------
770
+ // Callbacks (after table creation)
771
+ // -------------------------------
772
+ const handleColumnReorder = useCallback((draggedColumnId: string, targetColumnId: string) => {
773
+ const currentOrder = columnOrder.length > 0 ? columnOrder : enhancedColumns.map((col, index) => {
774
+ if (col.id) return col.id;
775
+ const anyCol = col as any;
776
+ if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
777
+ return anyCol.accessorKey;
778
+ }
779
+ return `column_${index}`;
780
+ });
781
+ const draggedIndex = currentOrder.indexOf(draggedColumnId);
782
+ const targetIndex = currentOrder.indexOf(targetColumnId);
783
+ if (draggedIndex === -1 || targetIndex === -1) return;
784
+ const newOrder = [...currentOrder];
785
+ newOrder.splice(draggedIndex, 1);
786
+ newOrder.splice(targetIndex, 0, draggedColumnId);
787
+ handleColumnOrderChange(newOrder);
788
+ }, [columnOrder, enhancedColumns, handleColumnOrderChange]);
789
+
790
+ // -------------------------------
791
+ // Effects (after callbacks)
792
+ // -------------------------------
793
+ useEffect(() => {
794
+ if (initialLoadData && onFetchData) {
795
+ if (logger.isLevelEnabled('info')) {
796
+ logger.info('Initial data load triggered', { initialLoadData });
797
+ }
798
+ fetchData();
799
+ } else if (logger.isLevelEnabled('debug')) {
800
+ logger.debug('Skipping initial data load', {
801
+ initialLoadData,
802
+ hasOnFetchData: !!onFetchData
803
+ });
804
+ }
805
+ // eslint-disable-next-line react-hooks/exhaustive-deps
806
+ }, []);
807
+
808
+ useEffect(() => {
809
+ if (enableColumnDragging && columnOrder.length === 0) {
810
+ const initialOrder = enhancedColumns.map((col, index) => {
811
+ if (col.id) return col.id;
812
+ const anyCol = col as any;
813
+ if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
814
+ return anyCol.accessorKey;
815
+ }
816
+ return `column_${index}`;
817
+ });
818
+ setColumnOrder(initialOrder);
819
+ }
820
+ }, [enableColumnDragging, enhancedColumns, columnOrder.length]);
821
+
822
+
823
+ const dataTableApi = useMemo(() => ({
824
+ table: {
825
+ getTable: () => table,
826
+ },
827
+ // Column Management
828
+ columnVisibility: {
829
+ showColumn: (columnId: string) => {
830
+ table.getColumn(columnId)?.toggleVisibility(true);
831
+ },
832
+ hideColumn: (columnId: string) => {
833
+ table.getColumn(columnId)?.toggleVisibility(false);
834
+ },
835
+ toggleColumn: (columnId: string) => {
836
+ table.getColumn(columnId)?.toggleVisibility();
837
+ },
838
+ showAllColumns: () => {
839
+ table.toggleAllColumnsVisible(true);
840
+ },
841
+ hideAllColumns: () => {
842
+ table.toggleAllColumnsVisible(false);
843
+ },
844
+ resetColumnVisibility: () => {
845
+ const initialVisibility = initialStateConfig.columnVisibility || {};
846
+ table.setColumnVisibility(initialVisibility);
847
+ // Manually trigger handler to ensure callbacks are called
848
+ handleColumnVisibilityChange(initialVisibility);
849
+ },
850
+ },
851
+
852
+ // Column Ordering
853
+ columnOrdering: {
854
+ setColumnOrder: (columnOrder: ColumnOrderState) => {
855
+ table.setColumnOrder(columnOrder);
856
+ },
857
+ moveColumn: (columnId: string, toIndex: number) => {
858
+ const currentOrder = table.getState().columnOrder || [];
859
+ const currentIndex = currentOrder.indexOf(columnId);
860
+ if (currentIndex === -1) return;
861
+
862
+ const newOrder = [...currentOrder];
863
+ newOrder.splice(currentIndex, 1);
864
+ newOrder.splice(toIndex, 0, columnId);
865
+
866
+ table.setColumnOrder(newOrder);
867
+ },
868
+ resetColumnOrder: () => {
869
+ const initialOrder = enhancedColumns.map((col, index) => {
870
+ if (col.id) return col.id;
871
+ const anyCol = col as any;
872
+ if (anyCol.accessorKey && typeof anyCol.accessorKey === 'string') {
873
+ return anyCol.accessorKey;
874
+ }
875
+ return `column_${index}`;
876
+ });
877
+ table.setColumnOrder(initialOrder);
878
+ // Manually trigger handler to ensure callbacks are called
879
+ handleColumnOrderChange(initialOrder);
880
+ },
881
+ },
882
+
883
+ // Column Pinning
884
+ columnPinning: {
885
+ pinColumnLeft: (columnId: string) => {
886
+ const currentPinning = table.getState().columnPinning;
887
+ const newPinning = { ...currentPinning };
888
+
889
+ // Remove from right if exists
890
+ newPinning.right = (newPinning.right || []).filter(id => id !== columnId);
891
+ // Add to left if not exists
892
+ newPinning.left = [...(newPinning.left || []).filter(id => id !== columnId), columnId];
893
+
894
+ table.setColumnPinning(newPinning);
895
+ },
896
+ pinColumnRight: (columnId: string) => {
897
+ const currentPinning = table.getState().columnPinning;
898
+ const newPinning = { ...currentPinning };
899
+
900
+ // Remove from left if exists
901
+ newPinning.left = (newPinning.left || []).filter(id => id !== columnId);
902
+ // Add to right if not exists - prepend to beginning (appears rightmost to leftmost)
903
+ // First column pinned appears rightmost, second appears to its left, etc.
904
+ newPinning.right = [columnId, ...(newPinning.right || []).filter(id => id !== columnId)];
905
+
906
+ table.setColumnPinning(newPinning);
907
+ },
908
+ unpinColumn: (columnId: string) => {
909
+ const currentPinning = table.getState().columnPinning;
910
+ const newPinning = {
911
+ left: (currentPinning.left || []).filter(id => id !== columnId),
912
+ right: (currentPinning.right || []).filter(id => id !== columnId),
913
+ };
914
+
915
+ table.setColumnPinning(newPinning);
916
+ },
917
+ setPinning: (pinning: ColumnPinningState) => {
918
+ table.setColumnPinning(pinning);
919
+ },
920
+ resetColumnPinning: () => {
921
+ const initialPinning = initialStateConfig.columnPinning || { left: [], right: [] };
922
+ table.setColumnPinning(initialPinning);
923
+ // Manually trigger handler to ensure callbacks are called
924
+ handleColumnPinningChange(initialPinning);
925
+ },
926
+ },
927
+
928
+ // Column Resizing
929
+ columnResizing: {
930
+ resizeColumn: (columnId: string, width: number) => {
931
+ // Use table's setColumnSizing method
932
+ const currentSizing = table.getState().columnSizing;
933
+ table.setColumnSizing({
934
+ ...currentSizing,
935
+ [columnId]: width,
936
+ });
937
+ },
938
+ autoSizeColumn: (columnId: string) => {
939
+ // TanStack doesn't have built-in auto-size, so reset to default
940
+ table.getColumn(columnId)?.resetSize();
941
+ },
942
+ autoSizeAllColumns: () => {
943
+ const initialSizing = initialStateConfig.columnSizing || {};
944
+ table.setColumnSizing(initialSizing);
945
+ // Manually trigger handler to ensure callbacks are called
946
+ handleColumnSizingChange(initialSizing);
947
+ },
948
+ resetColumnSizing: () => {
949
+ const initialSizing = initialStateConfig.columnSizing || {};
950
+ table.setColumnSizing(initialSizing);
951
+ // Manually trigger handler to ensure callbacks are called
952
+ handleColumnSizingChange(initialSizing);
953
+ },
954
+ },
955
+
956
+ // Filtering
957
+ filtering: {
958
+ setGlobalFilter: (filter: string) => {
959
+ table.setGlobalFilter(filter);
960
+ },
961
+ clearGlobalFilter: () => {
962
+ table.setGlobalFilter('');
963
+ },
964
+ setColumnFilters: (filters: ColumnFilterState) => {
965
+ handleColumnFilterStateChange(filters);
966
+ },
967
+ addColumnFilter: (columnId: string, operator: string, value: any) => {
968
+ const newFilter = {
969
+ id: `filter_${Date.now()}`,
970
+ columnId,
971
+ operator,
972
+ value,
973
+ };
974
+ const columnFilter = table.getState().columnFilter;
975
+
976
+ const currentFilters = columnFilter.filters || [];
977
+ const newFilters = [...currentFilters, newFilter];
978
+ handleColumnFilterStateChange({
979
+ filters: newFilters,
980
+ logic: columnFilter.logic,
981
+ pendingFilters: columnFilter.pendingFilters || [],
982
+ pendingLogic: columnFilter.pendingLogic || 'AND',
983
+ });
984
+ if (logger.isLevelEnabled('debug')) {
985
+ logger.debug(`Adding column filter ${columnId} ${operator} ${value}`, newFilters);
986
+ }
987
+ },
988
+ removeColumnFilter: (filterId: string) => {
989
+ const columnFilter = table.getState().columnFilter;
990
+ const currentFilters = columnFilter.filters || [];
991
+ const newFilters = currentFilters.filter((f: any) => f.id !== filterId);
992
+ handleColumnFilterStateChange({
993
+ filters: newFilters,
994
+ logic: columnFilter.logic,
995
+ pendingFilters: columnFilter.pendingFilters || [],
996
+ pendingLogic: columnFilter.pendingLogic || 'AND',
997
+ });
998
+ if (logger.isLevelEnabled('debug')) {
999
+ logger.debug(`Removing column filter ${filterId}`, newFilters);
1000
+ }
1001
+ },
1002
+ clearAllFilters: () => {
1003
+ table.setGlobalFilter('');
1004
+ handleColumnFilterStateChange({
1005
+ filters: [],
1006
+ logic: 'AND',
1007
+ pendingFilters: [],
1008
+ pendingLogic: 'AND',
1009
+ });
1010
+ },
1011
+ resetFilters: () => {
1012
+ handleColumnFilterStateChange({
1013
+ filters: [],
1014
+ logic: 'AND',
1015
+ pendingFilters: [],
1016
+ pendingLogic: 'AND',
1017
+ });
1018
+ if (logger.isLevelEnabled('debug')) {
1019
+ logger.debug('Resetting filters');
1020
+ }
1021
+ },
1022
+ },
1023
+
1024
+ // Sorting
1025
+ sorting: {
1026
+ setSorting: (sortingState: SortingState) => {
1027
+ table.setSorting(sortingState);
1028
+ if (logger.isLevelEnabled('debug')) {
1029
+ logger.debug(`Setting sorting`, sortingState);
1030
+ }
1031
+ },
1032
+ sortColumn: (columnId: string, direction: 'asc' | 'desc' | false) => {
1033
+ const column = table.getColumn(columnId);
1034
+ if (!column) return;
1035
+
1036
+ if (direction === false) {
1037
+ column.clearSorting();
1038
+ } else {
1039
+ column.toggleSorting(direction === 'desc');
1040
+ }
1041
+ },
1042
+ clearSorting: () => {
1043
+ table.setSorting([]);
1044
+ // Manually trigger handler to ensure callbacks are called
1045
+ handleSortingChange([]);
1046
+ },
1047
+ resetSorting: () => {
1048
+ const initialSorting = initialStateConfig.sorting || [];
1049
+ table.setSorting(initialSorting);
1050
+ // Manually trigger handler to ensure callbacks are called
1051
+ handleSortingChange(initialSorting);
1052
+ },
1053
+ },
1054
+
1055
+ // Pagination
1056
+ pagination: {
1057
+ goToPage: (pageIndex: number) => {
1058
+ table.setPageIndex(pageIndex);
1059
+ if (logger.isLevelEnabled('debug')) {
1060
+ logger.debug(`Going to page ${pageIndex}`);
1061
+ }
1062
+ },
1063
+ nextPage: () => {
1064
+ table.nextPage();
1065
+ if (logger.isLevelEnabled('debug')) {
1066
+ logger.debug('Next page');
1067
+ }
1068
+ },
1069
+ previousPage: () => {
1070
+ table.previousPage();
1071
+ if (logger.isLevelEnabled('debug')) {
1072
+ logger.debug('Previous page');
1073
+ }
1074
+ },
1075
+ setPageSize: (pageSize: number) => {
1076
+ table.setPageSize(pageSize);
1077
+ if (logger.isLevelEnabled('debug')) {
1078
+ logger.debug(`Setting page size to ${pageSize}`);
1079
+ }
1080
+ },
1081
+ goToFirstPage: () => {
1082
+ table.setPageIndex(0);
1083
+ if (logger.isLevelEnabled('debug')) {
1084
+ logger.debug('Going to first page');
1085
+ }
1086
+ },
1087
+ goToLastPage: () => {
1088
+ const pageCount = table.getPageCount();
1089
+ if (pageCount > 0) {
1090
+ table.setPageIndex(pageCount - 1);
1091
+ if (logger.isLevelEnabled('debug')) {
1092
+ logger.debug(`Going to last page ${pageCount - 1}`);
1093
+ }
1094
+ }
1095
+ },
1096
+ resetPagination: () => {
1097
+ const initialPagination = initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 };
1098
+ table.setPagination(initialPagination);
1099
+ // Manually trigger handler to ensure callbacks are called
1100
+ handlePaginationChange(initialPagination);
1101
+ },
1102
+ },
1103
+
1104
+ // Access via table methods: table.selectRow(), table.getIsRowSelected(), etc.
1105
+ selection: {
1106
+ selectRow: (rowId: string) => table.selectRow?.(rowId),
1107
+ deselectRow: (rowId: string) => table.deselectRow?.(rowId),
1108
+ toggleRowSelection: (rowId: string) => table.toggleRowSelected?.(rowId),
1109
+ selectAll: () => table.selectAll?.(),
1110
+ deselectAll: () => table.deselectAll?.(),
1111
+ toggleSelectAll: () => table.toggleAllRowsSelected?.(),
1112
+ getSelectionState: () => table.getSelectionState?.() || { ids: [], type: 'include' as const },
1113
+ getSelectedRows: () => table.getSelectedRows(),
1114
+ getSelectedCount: () => table.getSelectedCount(),
1115
+ isRowSelected: (rowId) => table.getIsRowSelected(rowId) || false,
1116
+ },
1117
+
1118
+ // Data Management
1119
+ data: {
1120
+ refresh: (resetPagination = false) => {
1121
+ const filters = table.getState();
1122
+ const pagination = {
1123
+ pageIndex: resetPagination ? 0 : initialStateConfig.pagination?.pageIndex || 0,
1124
+ pageSize: filters.pagination?.pageSize || initialStateConfig.pagination?.pageSize || 10,
1125
+ };
1126
+ const allState = table.getState();
1127
+ setPagination(pagination);
1128
+ onDataStateChange?.({ ...allState, pagination });
1129
+ fetchData?.({ pagination });
1130
+ if (logger.isLevelEnabled('debug')) {
1131
+ logger.debug('Refreshing data using Ref', { pagination, allState });
1132
+ }
1133
+ },
1134
+ reload: () => {
1135
+ const allState = table.getState();
1136
+
1137
+ onDataStateChange?.(allState);
1138
+ onFetchData?.({});
1139
+ if (logger.isLevelEnabled('debug')) {
1140
+ logger.info('Reloading data', allState);
1141
+ }
1142
+ },
1143
+ // Data CRUD operations
1144
+ getAllData: () => {
1145
+ return table.getRowModel().rows?.map(row => row.original) || [];
1146
+ },
1147
+ getRowData: (rowId: string) => {
1148
+ return table.getRowModel().rows?.find(row => String(row.original[idKey]) === rowId)?.original;
1149
+ },
1150
+ getRowByIndex: (index: number) => {
1151
+ return table.getRowModel().rows?.[index]?.original;
1152
+ },
1153
+ updateRow: (rowId: string, updates: Partial<T>) => {
1154
+ const newData = table.getRowModel().rows?.map(row => String(row.original[idKey]) === rowId
1155
+ ? {
1156
+ ...row.original,
1157
+ ...updates,
1158
+ }
1159
+ : row.original);
1160
+ setServerData?.(newData || []);
1161
+ if (logger.isLevelEnabled('debug')) {
1162
+ logger.debug(`Updating row ${rowId}`, updates);
1163
+ }
1164
+ },
1165
+ updateRowByIndex: (index: number, updates: Partial<T>) => {
1166
+ const newData = table.getRowModel().rows?.map(row => row.original);
1167
+ if (newData?.[index]) {
1168
+ newData[index] = {
1169
+ ...newData[index]!,
1170
+ ...updates,
1171
+ };
1172
+ setServerData(newData);
1173
+ if (logger.isLevelEnabled('debug')) {
1174
+ logger.debug(`Updating row by index ${index}`, updates);
1175
+ }
1176
+ }
1177
+ },
1178
+ insertRow: (newRow: T, index?: number) => {
1179
+ const newData = table.getRowModel().rows?.map(row => row.original) || [];
1180
+ if (index !== undefined) {
1181
+ newData.splice(index, 0, newRow);
1182
+ } else {
1183
+ newData.push(newRow);
1184
+ }
1185
+ setServerData(newData || []);
1186
+ if (logger.isLevelEnabled('debug')) {
1187
+ logger.debug(`Inserting row`, newRow);
1188
+ }
1189
+ },
1190
+ deleteRow: (rowId: string) => {
1191
+ const newData = (table.getRowModel().rows || [])?.filter(row => String(row.original[idKey]) !== rowId);
1192
+ setServerData?.(newData?.map(row => row.original) || []);
1193
+ if (logger.isLevelEnabled('debug')) {
1194
+ logger.debug(`Deleting row ${rowId}`);
1195
+ }
1196
+ },
1197
+ deleteRowByIndex: (index: number) => {
1198
+ const newData = (table.getRowModel().rows || [])?.map(row => row.original);
1199
+ newData.splice(index, 1);
1200
+ setServerData(newData);
1201
+ if (logger.isLevelEnabled('debug')) {
1202
+ logger.debug(`Deleting row by index ${index}`);
1203
+ }
1204
+ },
1205
+ deleteSelectedRows: () => {
1206
+ const selectedRows = table.getSelectedRows?.() || [];
1207
+ if (selectedRows.length === 0) return;
1208
+
1209
+ const selectedIds = new Set(selectedRows.map(row => String(row.original[idKey])));
1210
+ const newData = (table.getRowModel().rows || [])?.filter(row => !selectedIds.has(String(row.original[idKey])));
1211
+
1212
+ setServerData(newData?.map(row => row.original) || []);
1213
+ table.deselectAll?.();
1214
+
1215
+ if (logger.isLevelEnabled('debug')) {
1216
+ logger.debug('Deleting selected rows');
1217
+ }
1218
+ },
1219
+ replaceAllData: (newData: T[]) => {
1220
+ setServerData?.(newData);
1221
+ },
1222
+
1223
+ // Bulk operations
1224
+ updateMultipleRows: (updates: Array<{ rowId: string; data: Partial<T> }>) => {
1225
+ const updateMap = new Map(updates.map(u => [u.rowId, u.data]));
1226
+ const newData = (table.getRowModel().rows || [])?.map(row => {
1227
+ const rowId = String(row.original[idKey]);
1228
+ const updateData = updateMap.get(rowId);
1229
+ return updateData ? {
1230
+ ...row.original,
1231
+ ...updateData,
1232
+ } : row.original;
1233
+ });
1234
+ setServerData(newData || []);
1235
+ },
1236
+ insertMultipleRows: (newRows: T[], startIndex?: number) => {
1237
+ const newData = (table.getRowModel().rows || [])?.map(row => row.original);
1238
+ if (startIndex !== undefined) {
1239
+ newData.splice(startIndex, 0, ...newRows);
1240
+ } else {
1241
+ newData.push(...newRows);
1242
+ }
1243
+ setServerData?.(newData);
1244
+ },
1245
+ deleteMultipleRows: (rowIds: string[]) => {
1246
+ const idsToDelete = new Set(rowIds);
1247
+ const newData = (table.getRowModel().rows || [])?.filter(row => !idsToDelete.has(String(row.original[idKey])))?.map(row => row.original);
1248
+ setServerData(newData);
1249
+ },
1250
+
1251
+ // Field-specific updates
1252
+ updateField: (rowId: string, fieldName: keyof T, value: any) => {
1253
+ const newData = (table.getRowModel().rows || [])?.map(row => String(row.original[idKey]) === rowId
1254
+ ? {
1255
+ ...row.original,
1256
+ [fieldName]: value,
1257
+ }
1258
+ : row.original);
1259
+ setServerData?.(newData);
1260
+ },
1261
+ updateFieldByIndex: (index: number, fieldName: keyof T, value: any) => {
1262
+ const newData = (table.getRowModel().rows || [])?.map(row => row.original);
1263
+ if (newData[index]) {
1264
+ newData[index] = {
1265
+ ...newData[index],
1266
+ [fieldName]: value,
1267
+ };
1268
+ setServerData?.(newData);
1269
+ }
1270
+ },
1271
+
1272
+ // Data queries
1273
+ findRows: (predicate: (row: T) => boolean) => {
1274
+ return (table.getRowModel().rows || [])?.filter(row => predicate(row.original))?.map(row => row.original);
1275
+ },
1276
+ findRowIndex: (predicate: (row: T) => boolean) => {
1277
+ return (table.getRowModel().rows || [])?.findIndex(row => predicate(row.original));
1278
+ },
1279
+ getDataCount: () => {
1280
+ return (table.getRowModel().rows || [])?.length || 0;
1281
+ },
1282
+ getFilteredDataCount: () => {
1283
+ return table.getFilteredRowModel().rows.length;
1284
+ },
1285
+ },
1286
+
1287
+ // Layout Management
1288
+ layout: {
1289
+ resetLayout: () => {
1290
+ table.resetColumnSizing();
1291
+ table.resetColumnVisibility();
1292
+ table.resetSorting();
1293
+ table.resetGlobalFilter();
1294
+ },
1295
+ resetAll: () => {
1296
+ // Reset everything to initial state
1297
+ table.resetColumnSizing();
1298
+ table.resetColumnVisibility();
1299
+ table.resetSorting();
1300
+ table.resetGlobalFilter();
1301
+ table.resetColumnOrder();
1302
+ table.resetExpanded();
1303
+ handleSelectionStateChange(initialSelectionState);
1304
+ table.resetColumnPinning();
1305
+
1306
+ handleColumnFilterStateChange(initialStateConfig.columnFilter || { filters: [], logic: 'AND', pendingFilters: [], pendingLogic: 'AND' });
1307
+
1308
+ if (enablePagination) {
1309
+ table.setPagination(initialStateConfig.pagination || { pageIndex: 0, pageSize: 10 });
1310
+ }
1311
+
1312
+ if (enableColumnPinning) {
1313
+ table.setColumnPinning(initialStateConfig.columnPinning || { left: [], right: [] });
1314
+ }
1315
+ },
1316
+ saveLayout: () => {
1317
+ return {
1318
+ columnVisibility: table.getState().columnVisibility,
1319
+ columnSizing: table.getState().columnSizing,
1320
+ columnOrder: table.getState().columnOrder,
1321
+ columnPinning: table.getState().columnPinning,
1322
+ sorting: table.getState().sorting,
1323
+ pagination: table.getState().pagination,
1324
+ globalFilter: table.getState().globalFilter,
1325
+ columnFilter: table.getState().columnFilter,
1326
+ };
1327
+ },
1328
+ restoreLayout: (layout: Partial<TableState>) => {
1329
+ if (layout.columnVisibility) {
1330
+ table.setColumnVisibility(layout.columnVisibility);
1331
+ }
1332
+ if (layout.columnSizing) {
1333
+ table.setColumnSizing(layout.columnSizing);
1334
+ }
1335
+ if (layout.columnOrder) {
1336
+ table.setColumnOrder(layout.columnOrder);
1337
+ }
1338
+ if (layout.columnPinning) {
1339
+ table.setColumnPinning(layout.columnPinning);
1340
+ }
1341
+ if (layout.sorting) {
1342
+ table.setSorting(layout.sorting);
1343
+ }
1344
+ if (layout.pagination && enablePagination) {
1345
+ table.setPagination(layout.pagination);
1346
+ }
1347
+ if (layout.globalFilter !== undefined) {
1348
+ table.setGlobalFilter(layout.globalFilter);
1349
+ }
1350
+ if (layout.columnFilter) {
1351
+ handleColumnFilterStateChange(layout.columnFilter);
1352
+ }
1353
+ },
1354
+ },
1355
+
1356
+ // Table State
1357
+ state: {
1358
+ getTableState: () => {
1359
+ return table.getState();
1360
+ },
1361
+ getCurrentFilters: () => {
1362
+ return table.getState().columnFilter;
1363
+ },
1364
+ getCurrentSorting: () => {
1365
+ return table.getState().sorting;
1366
+ },
1367
+ getCurrentPagination: () => {
1368
+ return table.getState().pagination;
1369
+ },
1370
+ // Backward compatibility: expose the raw selection array expected by older consumers
1371
+ getCurrentSelection: () => {
1372
+ return table.getSelectionState?.();
1373
+ },
1374
+ },
1375
+
1376
+ // Simplified Export
1377
+ export: {
1378
+ exportCSV: async (options: any = {}) => {
1379
+ const { filename = exportFilename, } = options;
1380
+
1381
+ try {
1382
+ // Create abort controller for this export
1383
+ const controller = new AbortController();
1384
+ setExportController?.(controller);
1385
+
1386
+ if (dataMode === 'server' && onServerExport) {
1387
+ // Server export with selection data
1388
+ const currentFilters = {
1389
+ globalFilter: table.getState().globalFilter,
1390
+ columnFilter: table.getState().columnFilter,
1391
+ sorting: table.getState().sorting,
1392
+ pagination: table.getState().pagination,
1393
+ };
1394
+ if (logger.isLevelEnabled('debug')) {
1395
+ logger.debug('Server export CSV', { currentFilters });
1396
+ }
1397
+ await exportServerData(table, {
1398
+ format: 'csv',
1399
+ filename,
1400
+ fetchData: (filters, selection) => onServerExport(filters, selection),
1401
+ currentFilters,
1402
+ selection: table.getSelectionState?.(),
1403
+ onProgress: onExportProgress,
1404
+ onComplete: onExportComplete,
1405
+ onError: onExportError,
1406
+ });
1407
+ } else {
1408
+ // Client export - auto-detect selected rows if not specified
1409
+ await exportClientData(table, {
1410
+ format: 'csv',
1411
+ filename,
1412
+ onProgress: onExportProgress,
1413
+ onComplete: onExportComplete,
1414
+ onError: onExportError,
1415
+ });
1416
+ if (logger.isLevelEnabled('debug')) {
1417
+ logger.debug('Client export CSV', filename);
1418
+ }
1419
+ }
1420
+ } catch (error: any) {
1421
+ onExportError?.({
1422
+ message: error.message || 'Export failed',
1423
+ code: 'EXPORT_ERROR',
1424
+ });
1425
+ } finally {
1426
+ setExportController?.(null);
1427
+ }
1428
+ },
1429
+ exportExcel: async (options: any = {}) => {
1430
+ const { filename = exportFilename } = options;
1431
+
1432
+ try {
1433
+ // Create abort controller for this export
1434
+ const controller = new AbortController();
1435
+ setExportController?.(controller);
1436
+
1437
+ if (dataMode === 'server' && onServerExport) {
1438
+ // Server export with selection data
1439
+ const currentFilters = {
1440
+ globalFilter: table.getState().globalFilter,
1441
+ columnFilter: table.getState().columnFilter,
1442
+ sorting: table.getState().sorting,
1443
+ pagination: table.getState().pagination,
1444
+ };
1445
+
1446
+ if (logger.isLevelEnabled('debug')) {
1447
+ logger.debug('Server export Excel', { currentFilters });
1448
+ }
1449
+ await exportServerData(table, {
1450
+ format: 'excel',
1451
+ filename,
1452
+ fetchData: (filters, selection) => onServerExport(filters, selection),
1453
+ currentFilters,
1454
+ selection: table.getSelectionState?.(),
1455
+ onProgress: onExportProgress,
1456
+ onComplete: onExportComplete,
1457
+ onError: onExportError,
1458
+ });
1459
+ } else {
1460
+ // Client export - auto-detect selected rows if not specified
1461
+ await exportClientData(table, {
1462
+ format: 'excel',
1463
+ filename,
1464
+ onProgress: onExportProgress,
1465
+ onComplete: onExportComplete,
1466
+ onError: onExportError,
1467
+ });
1468
+ if (logger.isLevelEnabled('debug')) {
1469
+ logger.debug('Client export Excel', filename);
1470
+ }
1471
+ }
1472
+ } catch (error: any) {
1473
+ onExportError?.({
1474
+ message: error.message || 'Export failed',
1475
+ code: 'EXPORT_ERROR',
1476
+ });
1477
+ if (logger.isLevelEnabled('debug')) {
1478
+ logger.debug('Server export Excel failed', error);
1479
+ }
1480
+ } finally {
1481
+ setExportController?.(null);
1482
+ }
1483
+ },
1484
+ exportServerData: async (options) => {
1485
+ const {
1486
+ format,
1487
+ filename = exportFilename,
1488
+ fetchData = onServerExport,
1489
+ } = options;
1490
+
1491
+ if (!fetchData) {
1492
+ onExportError?.({
1493
+ message: 'No server export function provided',
1494
+ code: 'NO_SERVER_EXPORT',
1495
+ });
1496
+ if (logger.isLevelEnabled('debug')) {
1497
+ logger.debug('Server export data failed', 'No server export function provided');
1498
+ }
1499
+ return;
1500
+ }
1501
+
1502
+ try {
1503
+ // Create abort controller for this export
1504
+ const controller = new AbortController();
1505
+ setExportController?.(controller);
1506
+
1507
+ const currentFilters = {
1508
+ globalFilter: table.getState().globalFilter,
1509
+ columnFilter: table.getState().columnFilter,
1510
+ sorting: table.getState().sorting,
1511
+ pagination: table.getState().pagination,
1512
+ };
1513
+ if (logger.isLevelEnabled('debug')) {
1514
+ logger.debug('Server export data', { currentFilters });
1515
+ }
1516
+ await exportServerData(table, {
1517
+ format,
1518
+ filename,
1519
+ fetchData: (filters, selection) => fetchData(filters, selection),
1520
+ currentFilters,
1521
+ selection: table.getSelectionState?.(),
1522
+ onProgress: onExportProgress,
1523
+ onComplete: onExportComplete,
1524
+ onError: onExportError,
1525
+ });
1526
+ } catch (error: any) {
1527
+ onExportError?.({
1528
+ message: error.message || 'Export failed',
1529
+ code: 'EXPORT_ERROR',
1530
+ });
1531
+ if (logger.isLevelEnabled('debug')) {
1532
+ logger.debug('Server export data failed', error);
1533
+ }
1534
+ } finally {
1535
+ setExportController?.(null);
1536
+ }
1537
+ },
1538
+ // Export state
1539
+ isExporting: () => isExporting || false,
1540
+ cancelExport: () => {
1541
+ exportController?.abort();
1542
+ setExportController?.(null);
1543
+ if (logger.isLevelEnabled('debug')) {
1544
+ logger.debug('Export cancelled');
1545
+ }
1546
+ },
1547
+ },
1548
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1549
+ }), [
1550
+ table,
1551
+ enhancedColumns,
1552
+ handleColumnFilterStateChange,
1553
+ idKey,
1554
+ onDataStateChange,
1555
+ onFetchData,
1556
+ enableColumnPinning,
1557
+ enablePagination,
1558
+ // Export dependencies
1559
+ exportFilename,
1560
+ onExportProgress,
1561
+ onExportComplete,
1562
+ onExportError,
1563
+ onServerExport,
1564
+ exportController,
1565
+ setExportController,
1566
+ isExporting,
1567
+ dataMode,
1568
+ selectMode,
1569
+ onSelectionChange,
1570
+ // Note: custom selection removed from dependency array
1571
+ ]);
1572
+
1573
+ internalApiRef.current = dataTableApi;
1574
+
1575
+ useImperativeHandle(ref, () => dataTableApi, [dataTableApi]);
1576
+
1577
+
1578
+ // -------------------------------
1579
+ // Render table rows with slot support (callback)
1580
+ // -------------------------------
1581
+ const renderTableRows = useCallback(() => {
1582
+ if (tableLoading) {
1583
+ const { component: LoadingRowComponent, props: loadingRowProps } = getSlotComponentWithProps(
1584
+ slots,
1585
+ slotProps || {},
1586
+ 'loadingRow',
1587
+ LoadingRows,
1588
+ {}
1589
+ );
1590
+ return (
1591
+ <LoadingRowComponent
1592
+ rowCount={enablePagination ? Math.min(pagination.pageSize, skeletonRows) : skeletonRows}
1593
+ colSpan={table.getAllColumns().length}
1594
+ slots={slots}
1595
+ slotProps={slotProps}
1596
+ {...loadingRowProps}
1597
+ />
1598
+ );
1599
+ }
1600
+ if (rows.length === 0) {
1601
+ const { component: EmptyRowComponent, props: emptyRowProps } = getSlotComponentWithProps(
1602
+ slots,
1603
+ slotProps || {},
1604
+ 'emptyRow',
1605
+ EmptyDataRow,
1606
+ {}
1607
+ );
1608
+ return (
1609
+ <EmptyRowComponent
1610
+ colSpan={table.getAllColumns().length}
1611
+ message={emptyMessage}
1612
+ slots={slots}
1613
+ slotProps={slotProps}
1614
+ {...emptyRowProps}
1615
+ />
1616
+ );
1617
+ }
1618
+ if (enableVirtualization && !enablePagination && rows.length > 0) {
1619
+ const virtualItems = rowVirtualizer.getVirtualItems();
1620
+ return (
1621
+ <>
1622
+ {virtualItems.length > 0 && (
1623
+ <tr>
1624
+ <td
1625
+ colSpan={table.getAllColumns().length}
1626
+ style={{
1627
+ height: `${virtualItems[0]?.start ?? 0}px`,
1628
+ padding: 0,
1629
+ border: 0,
1630
+ }}
1631
+ />
1632
+ </tr>
1633
+ )}
1634
+ {virtualItems.map((virtualRow) => {
1635
+ const row = rows[virtualRow.index];
1636
+ if (!row) return null;
1637
+ return (
1638
+ <DataTableRow
1639
+ key={row.id}
1640
+ row={row}
1641
+ enableHover={enableHover}
1642
+ enableStripes={enableStripes}
1643
+ isOdd={virtualRow.index % 2 === 1}
1644
+ renderSubComponent={renderSubComponent}
1645
+ disableStickyHeader={enableStickyHeaderOrFooter}
1646
+ onRowClick={onRowClick}
1647
+ selectOnRowClick={selectOnRowClick}
1648
+ slots={slots}
1649
+ slotProps={slotProps}
1650
+ />
1651
+ );
1652
+ })}
1653
+ {virtualItems.length > 0 && (
1654
+ <tr>
1655
+ <td
1656
+ colSpan={table.getAllColumns().length}
1657
+ style={{
1658
+ height: `${rowVirtualizer.getTotalSize() -
1659
+ (virtualItems[virtualItems.length - 1]?.end ?? 0)}px`,
1660
+ padding: 0,
1661
+ border: 0,
1662
+ }}
1663
+ />
1664
+ </tr>
1665
+ )}
1666
+ </>
1667
+ );
1668
+ }
1669
+ return rows.map((row, index) => (
1670
+ <DataTableRow
1671
+ key={row.id}
1672
+ row={row}
1673
+ enableHover={enableHover}
1674
+ enableStripes={enableStripes}
1675
+ isOdd={index % 2 === 1}
1676
+ renderSubComponent={renderSubComponent}
1677
+ disableStickyHeader={enableStickyHeaderOrFooter}
1678
+ onRowClick={onRowClick}
1679
+ selectOnRowClick={selectOnRowClick}
1680
+ slots={slots}
1681
+ slotProps={slotProps}
1682
+ />
1683
+ ));
1684
+ }, [
1685
+ tableLoading,
1686
+ rows,
1687
+ enableVirtualization,
1688
+ enablePagination,
1689
+ pagination.pageSize,
1690
+ skeletonRows,
1691
+ table,
1692
+ slotProps,
1693
+ emptyMessage,
1694
+ rowVirtualizer,
1695
+ enableHover,
1696
+ enableStripes,
1697
+ renderSubComponent,
1698
+ enableStickyHeaderOrFooter,
1699
+ onRowClick,
1700
+ selectOnRowClick,
1701
+ slots,
1702
+ ]);
1703
+
1704
+ // -------------------------------
1705
+ // Export cancel callback
1706
+ // -------------------------------
1707
+ const handleCancelExport = useCallback(() => {
1708
+ if (exportController) {
1709
+ exportController.abort();
1710
+ setExportController(null);
1711
+ if (onExportCancel) {
1712
+ onExportCancel();
1713
+ }
1714
+ }
1715
+ }, [exportController, onExportCancel]);
1716
+
1717
+ // -------------------------------
1718
+ // Slot components
1719
+ // -------------------------------
1720
+ const { component: RootComponent, props: rootSlotProps } = getSlotComponentWithProps(
1721
+ slots,
1722
+ slotProps || {},
1723
+ 'root',
1724
+ Box,
1725
+ {}
1726
+ );
1727
+ const { component: ToolbarComponent, props: toolbarSlotProps } = getSlotComponentWithProps(
1728
+ slots,
1729
+ slotProps || {},
1730
+ 'toolbar',
1731
+ DataTableToolbar,
1732
+ {}
1733
+ );
1734
+ const { component: BulkActionsComponent, props: bulkActionsSlotProps } = getSlotComponentWithProps(
1735
+ slots,
1736
+ slotProps || {},
1737
+ 'bulkActionsToolbar',
1738
+ BulkActionsToolbar,
1739
+ {}
1740
+ );
1741
+ const { component: TableContainerComponent, props: tableContainerSlotProps } = getSlotComponentWithProps(
1742
+ slots,
1743
+ slotProps || {},
1744
+ 'tableContainer',
1745
+ TableContainer,
1746
+ {}
1747
+ );
1748
+ const { component: TableComponent, props: tableComponentSlotProps } = getSlotComponentWithProps(
1749
+ slots,
1750
+ slotProps || {},
1751
+ 'table',
1752
+ Table,
1753
+ {}
1754
+ );
1755
+ const { component: BodyComponent, props: bodySlotProps } = getSlotComponentWithProps(
1756
+ slots,
1757
+ slotProps || {},
1758
+ 'body',
1759
+ TableBody,
1760
+ {}
1761
+ );
1762
+ const { component: FooterComponent, props: footerSlotProps } = getSlotComponentWithProps(
1763
+ slots,
1764
+ slotProps || {},
1765
+ 'footer',
1766
+ Box,
1767
+ {}
1768
+ );
1769
+ const { component: PaginationComponent, props: paginationSlotProps } = getSlotComponentWithProps(
1770
+ slots,
1771
+ slotProps || {},
1772
+ 'pagination',
1773
+ DataTablePagination,
1774
+ {}
1775
+ );
1776
+
1777
+ // -------------------------------
1778
+ // Render
1779
+ // -------------------------------
1780
+ return (
1781
+ <DataTableProvider
1782
+ table={table}
1783
+ apiRef={internalApiRef}
1784
+ dataMode={dataMode}
1785
+ tableSize={tableSize}
1786
+ onTableSizeChange={(size) => {
1787
+ setTableSize(size);
1788
+ }}
1789
+ columnFilter={columnFilter}
1790
+ onChangeColumnFilter={handleColumnFilterStateChange}
1791
+ slots={slots}
1792
+ slotProps={slotProps}
1793
+ isExporting={isExporting}
1794
+ exportController={exportController}
1795
+ onCancelExport={handleCancelExport}
1796
+ exportFilename={exportFilename}
1797
+ onExportProgress={onExportProgress}
1798
+ onExportComplete={onExportComplete}
1799
+ onExportError={onExportError}
1800
+ onServerExport={onServerExport}
1801
+ >
1802
+ <RootComponent
1803
+ {...rootSlotProps}
1804
+ >
1805
+ {/* Toolbar */}
1806
+ {(enableGlobalFilter || extraFilter) ? (
1807
+ <ToolbarComponent
1808
+ extraFilter={extraFilter}
1809
+ enableGlobalFilter={enableGlobalFilter}
1810
+ enableColumnVisibility={enableColumnVisibility}
1811
+ enableColumnFilter={enableColumnFilter}
1812
+ enableExport={enableExport}
1813
+ enableReset={enableReset}
1814
+ enableTableSizeControl={enableTableSizeControl}
1815
+ enableColumnPinning={enableColumnPinning}
1816
+ {...toolbarSlotProps}
1817
+ />
1818
+ ) : null}
1819
+
1820
+ {/* Bulk Actions Toolbar - shown when rows are selected */}
1821
+ {enableBulkActions && enableRowSelection && isSomeRowsSelected ? (
1822
+ <BulkActionsComponent
1823
+ selectionState={selectionState}
1824
+ selectedRowCount={selectedRowCount}
1825
+ bulkActions={bulkActions}
1826
+ sx={{
1827
+ position: 'relative',
1828
+ zIndex: 2,
1829
+ ...bulkActionsSlotProps.sx,
1830
+ }}
1831
+ {...bulkActionsSlotProps}
1832
+ />
1833
+ ) : null}
1834
+
1835
+ {/* Table Container */}
1836
+ <TableContainerComponent
1837
+ component={Paper}
1838
+ ref={tableContainerRef}
1839
+ sx={{
1840
+ width: '100%',
1841
+ overflowX: 'auto',
1842
+ ...(enableStickyHeaderOrFooter && {
1843
+ maxHeight: maxHeight,
1844
+ overflowY: 'auto',
1845
+ }),
1846
+ ...(enableVirtualization && {
1847
+ maxHeight: maxHeight,
1848
+ overflowY: 'auto',
1849
+ }),
1850
+ ...tableContainerSlotProps?.sx,
1851
+ }}
1852
+ {...tableContainerSlotProps}
1853
+ >
1854
+ <TableComponent
1855
+ size={tableSize}
1856
+ stickyHeader={enableStickyHeaderOrFooter}
1857
+ style={{
1858
+ ...tableStyle,
1859
+ tableLayout: fitToScreen ? 'fixed' : 'auto',
1860
+ ...tableProps?.style,
1861
+ }}
1862
+ {...mergeSlotProps(tableProps || {}, tableComponentSlotProps)}
1863
+ >
1864
+ {/* Table Headers */}
1865
+ <TableHeader
1866
+ draggable={enableColumnDragging}
1867
+ enableColumnResizing={enableColumnResizing}
1868
+ enableStickyHeader={enableStickyHeaderOrFooter}
1869
+ fitToScreen={fitToScreen}
1870
+ onColumnReorder={handleColumnReorder}
1871
+ slots={slots}
1872
+ slotProps={slotProps}
1873
+ />
1874
+
1875
+ {/* Table Body */}
1876
+ <BodyComponent
1877
+ {...bodySlotProps}
1878
+ >
1879
+ {renderTableRows()}
1880
+ </BodyComponent>
1881
+ </TableComponent>
1882
+ </TableContainerComponent>
1883
+
1884
+ {/* Pagination */}
1885
+ {enablePagination ? (
1886
+ <FooterComponent
1887
+ sx={{
1888
+ ...(enableStickyHeaderOrFooter && {
1889
+ position: 'sticky',
1890
+ bottom: 0,
1891
+ backgroundColor: 'background.paper',
1892
+ borderTop: '1px solid',
1893
+ borderColor: 'divider',
1894
+ zIndex: 1,
1895
+ }),
1896
+ ...footerSlotProps.sx,
1897
+ }}
1898
+ {...footerSlotProps}
1899
+ >
1900
+ <PaginationComponent
1901
+ footerFilter={footerFilter}
1902
+ pagination={pagination}
1903
+ totalRow={tableTotalRow}
1904
+ {...paginationSlotProps}
1905
+ />
1906
+ </FooterComponent>
1907
+ ) : null}
1908
+ </RootComponent>
1909
+ </DataTableProvider>
1910
+ );
1911
+ });