@jmruthers/pace-core 0.5.76 → 0.5.78

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 (447) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
  3. package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
  4. package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
  5. package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
  6. package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
  7. package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
  8. package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
  9. package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
  10. package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
  11. package/dist/chunk-2OGV6IRV.js.map +1 -0
  12. package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
  13. package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
  14. package/dist/chunk-CVMVPYAL.js.map +1 -0
  15. package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
  16. package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
  17. package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
  18. package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
  19. package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
  20. package/dist/chunk-LRQ6RBJC.js.map +1 -0
  21. package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
  22. package/dist/chunk-MNJXXD6C.js.map +1 -0
  23. package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
  24. package/dist/chunk-PTR5PMPE.js.map +1 -0
  25. package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
  26. package/dist/chunk-Q7APDV6H.js.map +1 -0
  27. package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
  28. package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
  29. package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
  30. package/dist/chunk-S63MFSY6.js.map +1 -0
  31. package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
  32. package/dist/chunk-WUXCWRL6.js +20 -0
  33. package/dist/chunk-WUXCWRL6.js.map +1 -0
  34. package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
  35. package/dist/chunk-YVVGHRGI.js.map +1 -0
  36. package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
  37. package/dist/chunk-ZMNXIJP4.js.map +1 -0
  38. package/dist/components.d.ts +6 -6
  39. package/dist/components.js +14 -18
  40. package/dist/components.js.map +1 -1
  41. package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
  42. package/dist/hooks.d.ts +5 -5
  43. package/dist/hooks.js +8 -9
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +19 -27
  46. package/dist/index.js +21 -29
  47. package/dist/index.js.map +1 -1
  48. package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
  49. package/dist/providers.d.ts +7 -21
  50. package/dist/providers.js +3 -10
  51. package/dist/rbac/index.d.ts +71 -221
  52. package/dist/rbac/index.js +15 -16
  53. package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
  54. package/dist/types.d.ts +3 -3
  55. package/dist/types.js +61 -18
  56. package/dist/types.js.map +1 -1
  57. package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
  58. package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
  59. package/dist/utils.d.ts +2 -2
  60. package/dist/utils.js +52 -9
  61. package/dist/utils.js.map +1 -1
  62. package/docs/CONTENT_AUDIT_REPORT.md +253 -0
  63. package/docs/DOCUMENTATION_AUDIT.md +172 -0
  64. package/docs/README.md +142 -147
  65. package/docs/STYLE_GUIDE.md +37 -0
  66. package/docs/api/classes/ColumnFactory.md +17 -17
  67. package/docs/api/classes/ErrorBoundary.md +1 -1
  68. package/docs/api/classes/InvalidScopeError.md +4 -4
  69. package/docs/api/classes/MissingUserContextError.md +4 -4
  70. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  71. package/docs/api/classes/PermissionDeniedError.md +5 -5
  72. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  73. package/docs/api/classes/RBACAuditManager.md +8 -8
  74. package/docs/api/classes/RBACCache.md +35 -5
  75. package/docs/api/classes/RBACEngine.md +49 -20
  76. package/docs/api/classes/RBACError.md +4 -4
  77. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  78. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  79. package/docs/api/classes/StorageUtils.md +1 -1
  80. package/docs/api/enums/FileCategory.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +4 -4
  82. package/docs/api/interfaces/ButtonProps.md +1 -1
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  87. package/docs/api/interfaces/DataRecord.md +11 -0
  88. package/docs/api/interfaces/DataTableAction.md +65 -29
  89. package/docs/api/interfaces/DataTableColumn.md +36 -23
  90. package/docs/api/interfaces/DataTableProps.md +80 -38
  91. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  92. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventLogoProps.md +1 -1
  95. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  96. package/docs/api/interfaces/FileMetadata.md +1 -1
  97. package/docs/api/interfaces/FileReference.md +1 -1
  98. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  99. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  100. package/docs/api/interfaces/FileUploadProps.md +1 -1
  101. package/docs/api/interfaces/FooterProps.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoginFormProps.md +1 -1
  106. package/docs/api/interfaces/NavigationAccessRecord.md +11 -11
  107. package/docs/api/interfaces/NavigationContextType.md +9 -9
  108. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  109. package/docs/api/interfaces/NavigationItem.md +1 -1
  110. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  111. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  112. package/docs/api/interfaces/Organisation.md +1 -1
  113. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  114. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  115. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  116. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  117. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  118. package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
  119. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  120. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  121. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  122. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  123. package/docs/api/interfaces/PaletteData.md +1 -1
  124. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  125. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  126. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  127. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  128. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  131. package/docs/api/interfaces/RBACConfig.md +1 -1
  132. package/docs/api/interfaces/RBACLogger.md +1 -1
  133. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  134. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  135. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  136. package/docs/api/interfaces/RouteConfig.md +2 -2
  137. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  138. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  139. package/docs/api/interfaces/StorageConfig.md +1 -1
  140. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  141. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  142. package/docs/api/interfaces/StorageListOptions.md +1 -1
  143. package/docs/api/interfaces/StorageListResult.md +1 -1
  144. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  145. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  146. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  147. package/docs/api/interfaces/StyleImport.md +1 -1
  148. package/docs/api/interfaces/SwitchProps.md +1 -1
  149. package/docs/api/interfaces/ToastActionElement.md +1 -1
  150. package/docs/api/interfaces/ToastProps.md +1 -1
  151. package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
  152. package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
  153. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  154. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  155. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  156. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  157. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  158. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  159. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  160. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  161. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  162. package/docs/api/interfaces/UserEventAccess.md +11 -11
  163. package/docs/api/interfaces/UserMenuProps.md +1 -1
  164. package/docs/api/interfaces/UserProfile.md +1 -1
  165. package/docs/api/modules.md +251 -269
  166. package/docs/api-reference/components.md +193 -0
  167. package/docs/api-reference/hooks.md +265 -0
  168. package/docs/api-reference/providers.md +6 -0
  169. package/docs/api-reference/types.md +6 -0
  170. package/docs/api-reference/utilities.md +207 -0
  171. package/docs/architecture/README.md +6 -0
  172. package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
  173. package/docs/architecture/rbac-security-architecture.md +258 -0
  174. package/docs/architecture/services.md +9 -1
  175. package/docs/best-practices/README.md +6 -0
  176. package/docs/best-practices/accessibility.md +6 -0
  177. package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
  178. package/docs/best-practices/deployment.md +6 -0
  179. package/docs/best-practices/performance.md +475 -2
  180. package/docs/best-practices/security.md +6 -0
  181. package/docs/best-practices/testing.md +6 -0
  182. package/docs/core-concepts/authentication.md +6 -0
  183. package/docs/core-concepts/events.md +6 -0
  184. package/docs/core-concepts/organisations.md +6 -0
  185. package/docs/core-concepts/permissions.md +6 -0
  186. package/docs/core-concepts/rbac-system.md +8 -0
  187. package/docs/documentation-index.md +121 -182
  188. package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
  189. package/docs/getting-started/documentation-index.md +40 -0
  190. package/docs/getting-started/examples/README.md +878 -35
  191. package/docs/{faq.md → getting-started/faq.md} +7 -1
  192. package/docs/getting-started/installation-guide.md +6 -0
  193. package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
  194. package/docs/implementation-guides/app-layout.md +6 -0
  195. package/docs/implementation-guides/authentication.md +1021 -0
  196. package/docs/implementation-guides/component-styling.md +6 -0
  197. package/docs/implementation-guides/data-tables.md +1264 -2076
  198. package/docs/implementation-guides/dynamic-colors.md +6 -0
  199. package/docs/implementation-guides/event-theming-summary.md +6 -0
  200. package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
  201. package/docs/implementation-guides/file-upload-storage.md +6 -0
  202. package/docs/implementation-guides/forms.md +6 -0
  203. package/docs/implementation-guides/inactivity-tracking.md +6 -0
  204. package/docs/implementation-guides/navigation.md +6 -0
  205. package/docs/implementation-guides/organisation-security.md +6 -0
  206. package/docs/implementation-guides/permission-enforcement.md +6 -0
  207. package/docs/implementation-guides/public-pages-advanced.md +6 -0
  208. package/docs/implementation-guides/public-pages.md +6 -0
  209. package/docs/migration/MIGRATION_GUIDE.md +827 -351
  210. package/docs/migration/README.md +7 -1
  211. package/docs/migration/organisation-context-timing-fix.md +6 -0
  212. package/docs/migration/rbac-migration.md +44 -1
  213. package/docs/migration/service-architecture.md +6 -0
  214. package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
  215. package/docs/migration/v0.4.16-css-first-approach.md +6 -0
  216. package/docs/migration/v0.4.17-source-path-fix.md +6 -0
  217. package/docs/rbac/README-rbac-rls-integration.md +6 -0
  218. package/docs/rbac/README.md +6 -0
  219. package/docs/rbac/advanced-patterns.md +6 -0
  220. package/docs/rbac/api-reference.md +7 -1
  221. package/docs/rbac/breaking-changes-v3.md +222 -0
  222. package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
  223. package/docs/rbac/examples.md +6 -0
  224. package/docs/rbac/getting-started.md +6 -0
  225. package/docs/rbac/migration-guide.md +260 -0
  226. package/docs/rbac/quick-start.md +70 -13
  227. package/docs/rbac/rbac-rls-integration.md +6 -0
  228. package/docs/rbac/super-admin-guide.md +6 -0
  229. package/docs/rbac/troubleshooting.md +6 -0
  230. package/docs/security/README.md +6 -0
  231. package/docs/security/checklist.md +6 -0
  232. package/docs/styles/README.md +7 -1
  233. package/docs/{usage.md → styles/usage.md} +6 -0
  234. package/docs/testing/README.md +6 -0
  235. package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
  236. package/docs/troubleshooting/README.md +387 -5
  237. package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
  238. package/docs/troubleshooting/common-issues.md +6 -0
  239. package/docs/troubleshooting/database-view-compatibility.md +6 -0
  240. package/docs/troubleshooting/organisation-context-setup.md +6 -0
  241. package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
  242. package/docs/troubleshooting/styling-issues.md +6 -0
  243. package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
  244. package/package.json +1 -1
  245. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
  246. package/src/__tests__/helpers/test-providers.tsx +3 -53
  247. package/src/components/DataTable/DataTable.test.tsx +319 -0
  248. package/src/components/DataTable/DataTable.tsx +32 -11
  249. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
  250. package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
  251. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
  252. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
  253. package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
  254. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
  255. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
  256. package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
  257. package/src/components/DataTable/components/ActionButtons.tsx +88 -104
  258. package/src/components/DataTable/components/DataTableCore.tsx +309 -337
  259. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
  260. package/src/components/DataTable/components/DataTableModals.tsx +22 -1
  261. package/src/components/DataTable/components/EditableRow.tsx +69 -84
  262. package/src/components/DataTable/components/EmptyState.tsx +5 -1
  263. package/src/components/DataTable/components/ImportModal.tsx +65 -36
  264. package/src/components/DataTable/components/PaginationControls.tsx +40 -100
  265. package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
  266. package/src/components/DataTable/context/DataTableContext.tsx +1 -1
  267. package/src/components/DataTable/core/ColumnFactory.ts +5 -0
  268. package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
  269. package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
  270. package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
  271. package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
  272. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
  273. package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
  274. package/src/components/DataTable/hooks/index.ts +7 -0
  275. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
  276. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
  277. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
  278. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
  279. package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
  280. package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
  281. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
  282. package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
  283. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
  284. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
  285. package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
  286. package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
  287. package/src/components/DataTable/index.ts +12 -3
  288. package/src/components/DataTable/types.ts +129 -9
  289. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
  290. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
  291. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
  292. package/src/components/DataTable/utils/a11yUtils.ts +244 -0
  293. package/src/components/DataTable/utils/debugTools.ts +609 -0
  294. package/src/components/DataTable/utils/exportUtils.ts +114 -16
  295. package/src/components/DataTable/utils/flexibleImport.ts +202 -32
  296. package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
  297. package/src/components/DataTable/utils/index.ts +2 -0
  298. package/src/components/DataTable/utils/paginationUtils.ts +350 -0
  299. package/src/components/DataTable/utils/rowUtils.ts +6 -5
  300. package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
  301. package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
  302. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
  303. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
  304. package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
  305. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
  306. package/src/components/Select/Select.tsx +1 -0
  307. package/src/components/examples/PermissionExample.tsx +173 -0
  308. package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
  309. package/src/examples/PublicEventPage.tsx +274 -0
  310. package/src/examples/PublicPageApp.tsx +308 -0
  311. package/src/examples/PublicPageUsageExample.tsx +216 -0
  312. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
  313. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
  314. package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
  315. package/src/hooks/useOrganisationPermissions.test.ts +42 -18
  316. package/src/hooks/useOrganisationPermissions.ts +12 -6
  317. package/src/hooks/useOrganisationSecurity.test.ts +138 -85
  318. package/src/hooks/useOrganisationSecurity.ts +41 -10
  319. package/src/index.ts +0 -1
  320. package/src/providers/AuthProvider.simplified.tsx +880 -0
  321. package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
  322. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
  323. package/src/providers/index.ts +0 -1
  324. package/src/providers/services/EventServiceProvider.tsx +19 -15
  325. package/src/providers/services/InactivityServiceProvider.tsx +19 -15
  326. package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
  327. package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
  328. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
  329. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
  330. package/src/rbac/README.md +1 -1
  331. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
  332. package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
  333. package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
  334. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
  335. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
  336. package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
  337. package/src/rbac/adapters.tsx +26 -69
  338. package/src/rbac/api.test.ts +90 -27
  339. package/src/rbac/api.ts +61 -10
  340. package/src/rbac/audit.test.ts +33 -38
  341. package/src/rbac/audit.ts +21 -6
  342. package/src/rbac/cache.ts +33 -1
  343. package/src/rbac/components/NavigationGuard.tsx +11 -11
  344. package/src/rbac/components/NavigationProvider.test.tsx +11 -5
  345. package/src/rbac/components/NavigationProvider.tsx +37 -13
  346. package/src/rbac/components/PagePermissionGuard.tsx +111 -50
  347. package/src/rbac/components/PagePermissionProvider.tsx +5 -5
  348. package/src/rbac/components/PermissionEnforcer.tsx +11 -11
  349. package/src/rbac/components/RoleBasedRouter.tsx +5 -5
  350. package/src/rbac/components/SecureDataProvider.tsx +5 -5
  351. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
  352. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
  353. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
  354. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
  355. package/src/rbac/engine.test.simple.ts +19 -13
  356. package/src/rbac/engine.test.ts +1 -0
  357. package/src/rbac/engine.ts +330 -766
  358. package/src/rbac/errors.ts +156 -0
  359. package/src/rbac/hooks/usePermissions.ts +32 -10
  360. package/src/rbac/hooks/useRBAC.test.ts +126 -512
  361. package/src/rbac/hooks/useRBAC.ts +147 -193
  362. package/src/rbac/hooks/useResolvedScope.ts +12 -0
  363. package/src/rbac/index.ts +7 -4
  364. package/src/rbac/security.ts +109 -18
  365. package/src/rbac/types.ts +12 -1
  366. package/src/services/AuthService.ts +2 -15
  367. package/src/services/EventService.ts +43 -46
  368. package/src/services/OrganisationService.ts +51 -31
  369. package/src/services/__tests__/AuthService.test.ts +1 -1
  370. package/src/services/__tests__/EventService.test.ts +1 -1
  371. package/src/services/__tests__/OrganisationService.test.ts +1 -1
  372. package/src/services/base/BaseService.ts +8 -0
  373. package/src/styles/base.css +208 -0
  374. package/src/styles/semantic.css +24 -0
  375. package/src/types/database.generated.ts +7347 -0
  376. package/src/types/database.ts +20 -0
  377. package/src/utils/logger.ts +179 -0
  378. package/src/utils/organisationContext.ts +11 -4
  379. package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
  380. package/dist/appNameResolver-UURKN7NF.js +0 -22
  381. package/dist/audit-6TOCAMKO.js.map +0 -1
  382. package/dist/chunk-B2WTCLCV.js.map +0 -1
  383. package/dist/chunk-FGMFQSHX.js.map +0 -1
  384. package/dist/chunk-K34IM5CT.js.map +0 -1
  385. package/dist/chunk-KHJS6VIA.js.map +0 -1
  386. package/dist/chunk-KK73ZB4E.js.map +0 -1
  387. package/dist/chunk-M5IWZRBT.js.map +0 -1
  388. package/dist/chunk-ULBI5JGB.js +0 -109
  389. package/dist/chunk-ULBI5JGB.js.map +0 -1
  390. package/dist/chunk-WN6XJWOS.js.map +0 -1
  391. package/dist/chunk-XLZ7U46Z.js.map +0 -1
  392. package/dist/chunk-Y6TXWPJO.js.map +0 -1
  393. package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
  394. package/docs/TERMINOLOGY.md +0 -231
  395. package/docs/api/interfaces/RBACContextType.md +0 -468
  396. package/docs/api/interfaces/RBACProviderProps.md +0 -107
  397. package/docs/best-practices/performance-expansion.md +0 -473
  398. package/docs/breaking-changes.md +0 -179
  399. package/docs/consuming-app-example.md +0 -290
  400. package/docs/documentation-templates.md +0 -539
  401. package/docs/examples/navigation-menu-auth-fix.md +0 -344
  402. package/docs/getting-started/examples/basic-auth-app.md +0 -520
  403. package/docs/getting-started/examples/full-featured-app.md +0 -616
  404. package/docs/getting-started/quick-start.md +0 -376
  405. package/docs/implementation-guides/datatable-filtering.md +0 -313
  406. package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
  407. package/docs/implementation-guides/hierarchical-datatable.md +0 -850
  408. package/docs/implementation-guides/large-datasets.md +0 -281
  409. package/docs/implementation-guides/performance.md +0 -403
  410. package/docs/migration/quick-migration-guide.md +0 -320
  411. package/docs/migration-guide.md +0 -193
  412. package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
  413. package/docs/performance/README.md +0 -551
  414. package/docs/style-guide.md +0 -964
  415. package/docs/troubleshooting/authentication-issues.md +0 -334
  416. package/docs/troubleshooting/debugging.md +0 -1117
  417. package/docs/troubleshooting/migration.md +0 -918
  418. package/src/__tests__/hooks/usePermissions.test.ts +0 -261
  419. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
  420. package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
  421. package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
  422. package/src/hooks/services/usePermissions.ts +0 -70
  423. package/src/hooks/services/useRBACService.ts +0 -30
  424. package/src/hooks/usePermissionCheck.ts +0 -150
  425. package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
  426. package/src/providers/services/RBACServiceProvider.tsx +0 -79
  427. package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
  428. package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
  429. package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
  430. package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
  431. package/src/rbac/providers/RBACProvider.tsx +0 -645
  432. package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
  433. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
  434. package/src/rbac/providers/index.ts +0 -11
  435. package/src/services/RBACService.ts +0 -522
  436. package/src/services/__tests__/RBACService.test.ts +0 -492
  437. package/src/services/interfaces/IRBACService.ts +0 -62
  438. package/src/utils/appNameResolver.test 2.ts +0 -494
  439. /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
  440. /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
  441. /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
  442. /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
  443. /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
  444. /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
  445. /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
  446. /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
  447. /package/docs/{app.css.example → styles/app.css.example} +0 -0
@@ -9,24 +9,14 @@
9
9
  */
10
10
 
11
11
  import React, { useMemo, useCallback, useEffect, useState, useRef } from 'react';
12
- import {
13
- useReactTable,
14
- getCoreRowModel,
15
- getFilteredRowModel,
16
- getSortedRowModel,
17
- getPaginationRowModel,
18
- getGroupedRowModel,
19
- getExpandedRowModel,
20
- flexRender,
21
- type SortingState,
22
- type ColumnDef,
23
- type ColumnFiltersState,
24
- type VisibilityState,
25
- type GroupingState,
26
- type ExpandedState,
27
- type PaginationState,
28
- type HeaderContext,
29
- type CellContext,
12
+ import { useReactTable, flexRender } from '@tanstack/react-table';
13
+ import type {
14
+ SortingState,
15
+ ColumnFiltersState,
16
+ VisibilityState,
17
+ GroupingState,
18
+ ExpandedState,
19
+ PaginationState,
30
20
  } from '@tanstack/react-table';
31
21
  import { Edit, Trash, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react';
32
22
  import { cn } from '../../../utils/cn';
@@ -41,13 +31,14 @@ import { LoadingState } from './LoadingState';
41
31
  import { DataTableModals } from './DataTableModals';
42
32
  import { DataTableErrorBoundary } from './DataTableErrorBoundary';
43
33
  import { useColumnOrderPersistence } from '../hooks/useColumnOrderPersistence';
44
- import { useHierarchicalState } from '../hooks/useHierarchicalState';
34
+ import { useColumnVisibilityPersistence } from '../hooks/useColumnVisibilityPersistence';
45
35
  import { useDataTableState } from '../hooks/useDataTableState';
46
- import { validateHierarchicalData } from '../utils/hierarchicalUtils';
47
- import {
48
- sortHierarchicalDataByStructure,
49
- sortHierarchicalDataWithSorting
50
- } from '../utils/hierarchicalSorting';
36
+ import { useDataTableDataPipeline } from '../hooks/useDataTableDataPipeline';
37
+ import { useServerSideDataEffect } from '../hooks/useServerSideDataEffect';
38
+ import { useEffectiveColumnOrder } from '../hooks/useEffectiveColumnOrder';
39
+ import { useTableHandlers } from '../hooks/useTableHandlers';
40
+ import { useDataTableConfiguration } from '../hooks/useDataTableConfiguration';
41
+ import type { TableStateSnapshot } from '../hooks/useTableHandlers';
51
42
  import { ColumnFactory } from '../core/ColumnFactory';
52
43
  import { AccessDeniedPage } from './AccessDeniedPage';
53
44
  import { useCan, useResolvedScope } from '../../../rbac/hooks';
@@ -57,11 +48,14 @@ import { useUnifiedAuth } from '../../../providers/UnifiedAuthProvider';
57
48
  import { Scope } from '../../../rbac/types';
58
49
  import { useDataTablePermissions } from '../hooks/useDataTablePermissions';
59
50
  import { useTableColumns } from '../hooks/useTableColumns';
51
+ import { initializeLiveRegion, announceSortChange } from '../utils/a11yUtils';
52
+ import { useKeyboardNavigation } from '../hooks/useKeyboardNavigation';
53
+ import { getRowIdSafe } from '../utils/rowUtils';
60
54
 
61
- import type {
55
+ import { normalizeDataTableFeatures } from '../types';
56
+ import type {
62
57
  DataRecord,
63
58
  GetRowId,
64
- ServerSideParams,
65
59
  PerformanceConfig,
66
60
  ServerSideConfig,
67
61
  ChunkingConfig,
@@ -69,16 +63,48 @@ import type {
69
63
  PaginationMode,
70
64
  EmptyStateConfig,
71
65
  DataTableFeatureConfig,
66
+ NormalizedDataTableFeatureConfig,
72
67
  DataTableColumn,
73
68
  SimpleColumn,
74
69
  AggregateConfig,
75
70
  DataTableAction,
76
71
  HierarchicalConfig,
77
- HierarchicalDataRow,
78
- DataTableRBACConfig
72
+ DataTableRBACConfig,
73
+ CellValue
79
74
  } from '../types';
80
75
  import type { ImportModalConfig } from './ImportModal';
81
76
 
77
+ const isCellValue = (value: unknown): value is CellValue => {
78
+ if (value === null || value === undefined) {
79
+ return true;
80
+ }
81
+
82
+ if (value instanceof Date) {
83
+ return true;
84
+ }
85
+
86
+ const valueType = typeof value;
87
+ return valueType === 'string' || valueType === 'number' || valueType === 'boolean';
88
+ };
89
+
90
+ const toCellValueRecord = <TData extends DataRecord>(row: TData): Record<string, CellValue> => {
91
+ if (typeof row !== 'object' || row === null) {
92
+ return {};
93
+ }
94
+
95
+ return Object.entries(row).reduce<Record<string, CellValue>>((accumulator, [key, entryValue]) => {
96
+ if (isCellValue(entryValue)) {
97
+ accumulator[key] = entryValue;
98
+ } else if (entryValue && typeof entryValue === 'object' && 'toString' in entryValue) {
99
+ accumulator[key] = String(entryValue) as CellValue;
100
+ } else {
101
+ accumulator[key] = entryValue as CellValue;
102
+ }
103
+
104
+ return accumulator;
105
+ }, {});
106
+ };
107
+
82
108
  // ============================================================================
83
109
  // CORE COMPONENT PROPS
84
110
  // ============================================================================
@@ -97,9 +123,9 @@ export interface DataTableCoreProps<TData extends DataRecord> {
97
123
  description?: string;
98
124
  variant?: 'default' | 'compact' | 'spacious';
99
125
  className?: string;
100
-
126
+
101
127
  // Feature configuration
102
- features: DataTableFeatureConfig;
128
+ features?: DataTableFeatureConfig;
103
129
 
104
130
  // Hierarchical configuration
105
131
  hierarchical?: HierarchicalConfig;
@@ -138,6 +164,10 @@ export interface DataTableCoreProps<TData extends DataRecord> {
138
164
  // Default state configuration
139
165
  defaultGrouping?: string[];
140
166
  defaultSorting?: SortingState;
167
+
168
+ // Persistence configuration
169
+ storageKey?: string;
170
+ onLayoutChange?: (layout: { columnOrder: string[]; columnVisibility: Record<string, boolean> }) => void;
141
171
  }
142
172
 
143
173
  // ============================================================================
@@ -152,7 +182,7 @@ function DataTableInternal<TData extends DataRecord>({
152
182
  description,
153
183
  variant = 'default',
154
184
  className,
155
- features,
185
+ features: incomingFeatures = {},
156
186
  hierarchical,
157
187
  performance = {},
158
188
  serverSide,
@@ -181,6 +211,8 @@ function DataTableInternal<TData extends DataRecord>({
181
211
  columnOrder: externalColumnOrder,
182
212
  defaultGrouping,
183
213
  defaultSorting,
214
+ storageKey,
215
+ onLayoutChange,
184
216
  }: DataTableCoreProps<TData>) {
185
217
 
186
218
  // ============================================================================
@@ -191,27 +223,45 @@ function DataTableInternal<TData extends DataRecord>({
191
223
  const authResult = useUnifiedAuth();
192
224
  const user = authResult.user;
193
225
 
226
+ const requestedFeatures = useMemo<NormalizedDataTableFeatureConfig>(() =>
227
+ normalizeDataTableFeatures(incomingFeatures),
228
+ [incomingFeatures]);
229
+
194
230
  // MANDATORY: Get permissions and secure features
195
- const { permissions, secureFeatures, effectivePageId } = useDataTablePermissions(rbac, features);
231
+ const { permissions, secureFeatures, effectivePageId } = useDataTablePermissions(rbac, requestedFeatures);
196
232
 
197
233
  // ============================================================================
198
234
  // UNIFIED STATE MANAGEMENT - Use ONLY useDataTableState for all state
199
235
  // ============================================================================
200
236
 
201
- // Calculate initial column order - include selection column if needed
202
- const baseColumnOrder = useMemo(() => {
203
- if (externalColumnOrder && externalColumnOrder.length > 0) {
204
- return externalColumnOrder;
205
- }
206
- return columns.map(col => col.id || col.accessorKey || '').filter(Boolean);
207
- }, [externalColumnOrder, columns]);
237
+ const effectiveColumnOrder = useEffectiveColumnOrder({
238
+ columns,
239
+ externalColumnOrder,
240
+ selectionEnabled: secureFeatures.selection,
241
+ });
242
+
243
+ // ============================================================================
244
+ // COLUMN VISIBILITY PERSISTENCE - ALWAYS call these hooks
245
+ // ============================================================================
246
+
247
+ const {
248
+ columnVisibility: savedColumnVisibility,
249
+ isLoaded: isColumnVisibilityLoaded,
250
+ updateColumnVisibility: updateSavedColumnVisibility,
251
+ } = useColumnVisibilityPersistence({
252
+ tableId: title ? `datatable-${title.toLowerCase().replace(/\s+/g, '-')}` : undefined,
253
+ defaultVisibility: {},
254
+ enablePersistence: secureFeatures.columnVisibility,
255
+ storageKey,
256
+ });
208
257
 
209
- const effectiveColumnOrder = useMemo(() => {
210
- if (features.selection && !baseColumnOrder.includes('select')) {
211
- return ['select', ...baseColumnOrder];
258
+ // Merge saved column visibility into initial state
259
+ const initialColumnVisibility = useMemo(() => {
260
+ if (secureFeatures.columnVisibility && savedColumnVisibility && Object.keys(savedColumnVisibility).length > 0) {
261
+ return savedColumnVisibility;
212
262
  }
213
- return baseColumnOrder;
214
- }, [baseColumnOrder, features.selection]);
263
+ return {};
264
+ }, [secureFeatures.columnVisibility, savedColumnVisibility]);
215
265
 
216
266
  // Use the centralized state management hook for ALL table state
217
267
  // Note: 'actions' prop parameter is shadowed by destructuring, so we rename to stateActions
@@ -224,8 +274,25 @@ function DataTableInternal<TData extends DataRecord>({
224
274
  defaultGrouping: defaultGrouping || []
225
275
  });
226
276
 
277
+ // Apply saved visibility to state if available
278
+ useEffect(() => {
279
+ if (secureFeatures.columnVisibility && isColumnVisibilityLoaded && Object.keys(initialColumnVisibility).length > 0) {
280
+ stateActions.setColumnVisibility(initialColumnVisibility);
281
+ }
282
+ }, [secureFeatures.columnVisibility, isColumnVisibilityLoaded, initialColumnVisibility, stateActions]);
283
+
284
+ // Initialize live region for accessibility announcements
285
+ useEffect(() => {
286
+ initializeLiveRegion();
287
+ }, []);
288
+
227
289
  // Row selection: prefer controlled prop, fall back to state
228
290
  const rowSelection = selection !== undefined ? selection : state.rowSelection;
291
+
292
+ const resolvedGetRowId = useCallback(
293
+ (row: TData, index: number) => getRowIdSafe(row, index, getRowId),
294
+ [getRowId]
295
+ );
229
296
 
230
297
  // ============================================================================
231
298
  // AUTO-EXPAND GROUPS WHEN DEFAULT GROUPING IS PROVIDED
@@ -262,8 +329,6 @@ function DataTableInternal<TData extends DataRecord>({
262
329
  paginationMode: detectedMode,
263
330
  isVirtualized,
264
331
  pageSizeOptions: optimizedPageSizeOptions,
265
- processedData,
266
- totalCount,
267
332
  isLoading: performanceLoading,
268
333
  searchQuery,
269
334
  setSearchQuery,
@@ -272,37 +337,63 @@ function DataTableInternal<TData extends DataRecord>({
272
337
  cleanup,
273
338
  } = performanceHook;
274
339
 
340
+ const finalPaginationMode = paginationMode || detectedMode;
341
+
275
342
  // ============================================================================
276
- // HIERARCHICAL DATA VALIDATION AND PROCESSING - ALWAYS call these hooks
343
+ // KEYBOARD NAVIGATION - ALWAYS call this hook
277
344
  // ============================================================================
278
345
 
279
- const hierarchicalValidation = useMemo(() => {
280
- if (features.hierarchical && hierarchical?.enabled) {
281
- return validateHierarchicalData(data as unknown as HierarchicalDataRow[]);
346
+ const keyboardNavigation = useKeyboardNavigation(
347
+ data.length,
348
+ columns.length,
349
+ {
350
+ enabled: true,
351
+ announceNavigation: true,
352
+ supportsColumnReorder: secureFeatures.columnReordering,
353
+ supportsColumnResize: false, // Column resizing is not currently supported
282
354
  }
283
- return { isValid: true, errors: [] };
284
- }, [features.hierarchical, hierarchical?.enabled, data]);
355
+ );
285
356
 
286
- const processedHierarchicalData = useMemo(() => {
287
- if (features.hierarchical && hierarchical?.enabled && hierarchicalValidation.isValid) {
288
- return sortHierarchicalDataByStructure(data as unknown as HierarchicalDataRow[]);
357
+ // Store focus when modals open, restore when they close
358
+ useEffect(() => {
359
+ if (state.showImportModal) {
360
+ keyboardNavigation.storeFocus();
289
361
  }
290
- return data;
291
- }, [features.hierarchical, hierarchical?.enabled, hierarchicalValidation.isValid, data]);
362
+ }, [state.showImportModal, keyboardNavigation]);
292
363
 
293
- // ============================================================================
294
- // HIERARCHICAL STATE MANAGEMENT - ALWAYS call these hooks
295
- // ============================================================================
296
-
297
- const hierarchicalState = useHierarchicalState(
298
- processedHierarchicalData as unknown as HierarchicalDataRow[],
299
- hierarchical
300
- );
364
+ useEffect(() => {
365
+ if (!state.showImportModal) {
366
+ // Restore focus after modal closes
367
+ setTimeout(() => {
368
+ keyboardNavigation.restoreFocus();
369
+ }, 100); // Small delay to ensure modal is fully closed
370
+ }
371
+ }, [state.showImportModal, keyboardNavigation]);
301
372
 
302
373
  // ============================================================================
303
- // COLUMN ORDER PERSISTENCE - ALWAYS call these hooks
374
+ // HIERARCHICAL DATA VALIDATION AND PROCESSING - ALWAYS call these hooks
304
375
  // ============================================================================
305
376
 
377
+ const {
378
+ finalTableData,
379
+ dataCount: finalDataCount,
380
+ hierarchicalState,
381
+ hierarchicalValidation,
382
+ } = useDataTableDataPipeline<TData>({
383
+ data,
384
+ features: secureFeatures,
385
+ hierarchical,
386
+ sorting: state.sorting,
387
+ finalPaginationMode,
388
+ serverData,
389
+ });
390
+
391
+ useEffect(() => {
392
+ if (!hierarchicalValidation.isValid && import.meta.env?.MODE === 'development') {
393
+ console.error('[DataTable] Hierarchical data validation failed:', hierarchicalValidation.errors);
394
+ }
395
+ }, [hierarchicalValidation]);
396
+
306
397
  const {
307
398
  columnOrder: savedColumnOrder,
308
399
  isLoaded: isColumnOrderLoaded,
@@ -310,18 +401,29 @@ function DataTableInternal<TData extends DataRecord>({
310
401
  } = useColumnOrderPersistence({
311
402
  tableId: title ? `datatable-${title.toLowerCase().replace(/\s+/g, '-')}` : undefined,
312
403
  defaultOrder: columns.map(col => col.id || col.accessorKey || ''),
313
- enablePersistence: features.columnReordering,
404
+ enablePersistence: secureFeatures.columnReordering,
405
+ storageKey,
314
406
  });
315
407
 
408
+ useEffect(() => {
409
+ if (
410
+ secureFeatures.columnReordering &&
411
+ isColumnOrderLoaded &&
412
+ savedColumnOrder &&
413
+ savedColumnOrder.length > 0
414
+ ) {
415
+ stateActions.setColumnOrder(savedColumnOrder);
416
+ }
417
+ }, [secureFeatures.columnReordering, isColumnOrderLoaded, savedColumnOrder, stateActions]);
418
+
316
419
  // ============================================================================
317
420
  // CONFIGURATION RESOLUTION - ALWAYS call these hooks
318
421
  // ============================================================================
319
422
 
320
- const finalPaginationMode = paginationMode || detectedMode;
321
423
  const finalPageSizeOptions = optimizedPageSizeOptions;
322
424
 
323
425
  const validatedInitialPageSize = useMemo(() => {
324
- if (!features.pagination || !finalPageSizeOptions.length) {
426
+ if (!secureFeatures.pagination || !finalPageSizeOptions.length) {
325
427
  return initialPageSize;
326
428
  }
327
429
 
@@ -334,12 +436,14 @@ function DataTableInternal<TData extends DataRecord>({
334
436
  Math.abs(curr - initialPageSize) < Math.abs(prev - initialPageSize) ? curr : prev
335
437
  );
336
438
 
337
- console.warn(
338
- `DataTable: initialPageSize ${initialPageSize} is not available in page size options [${finalPageSizeOptions.join(', ')}]. Using closest option: ${closestOption}`
339
- );
439
+ if (import.meta.env?.MODE === 'development') {
440
+ console.warn(
441
+ `DataTable: initialPageSize ${initialPageSize} is not available in page size options [${finalPageSizeOptions.join(', ')}]. Using closest option: ${closestOption}`
442
+ );
443
+ }
340
444
 
341
445
  return closestOption;
342
- }, [initialPageSize, finalPageSizeOptions, features.pagination]);
446
+ }, [initialPageSize, finalPageSizeOptions, secureFeatures.pagination]);
343
447
 
344
448
  const isLoading = externalIsLoading || performanceLoading;
345
449
 
@@ -347,43 +451,6 @@ function DataTableInternal<TData extends DataRecord>({
347
451
  // DATA PROCESSING - ALWAYS call these hooks
348
452
  // ============================================================================
349
453
 
350
- // Get base data based on pagination mode - use simple memoization
351
- const baseData = useMemo(() => {
352
- return finalPaginationMode === 'server'
353
- ? (serverData?.data || [])
354
- : (processedHierarchicalData as TData[]);
355
- }, [finalPaginationMode, serverData?.data, processedHierarchicalData]);
356
-
357
- const tableData = useMemo(() => {
358
- return features.hierarchical && hierarchical?.enabled
359
- ? hierarchicalState.visibleRows as unknown as TData[]
360
- : baseData;
361
- }, [features.hierarchical, hierarchical?.enabled, hierarchicalState.visibleRows, baseData]);
362
-
363
- const dataCount = useMemo(() => {
364
- return finalPaginationMode === 'server' ? (serverData?.totalCount || 0) : data?.length || 0;
365
- }, [finalPaginationMode, serverData?.totalCount, data?.length]);
366
-
367
- // ============================================================================
368
- // HIERARCHICAL SORTING - ALWAYS call these hooks
369
- // ============================================================================
370
-
371
- const sortedTableData = useMemo(() => {
372
- if (features.hierarchical && hierarchical?.enabled && sorting.length > 0) {
373
- const hierarchicalVisibleRows = hierarchicalState.visibleRows as unknown as HierarchicalDataRow[];
374
- return sortHierarchicalDataWithSorting(hierarchicalVisibleRows, sorting) as unknown as TData[];
375
- }
376
- return tableData;
377
- }, [features.hierarchical, hierarchical?.enabled, hierarchicalState.visibleRows, sorting, tableData]);
378
-
379
- const finalTableData = useMemo(() => {
380
- return features.hierarchical && hierarchical?.enabled && sorting.length > 0
381
- ? sortedTableData
382
- : tableData;
383
- }, [features.hierarchical, hierarchical?.enabled, sorting.length, sortedTableData, tableData]);
384
-
385
- const finalDataCount = finalPaginationMode === 'server' ? (serverData?.totalCount || 0) : finalTableData?.length || 0;
386
-
387
454
  // ============================================================================
388
455
  // ACTIONS PROCESSING - ALWAYS call these hooks
389
456
  // ============================================================================
@@ -413,10 +480,10 @@ function DataTableInternal<TData extends DataRecord>({
413
480
  stateActions.setSearchQuery(value);
414
481
 
415
482
  // Reset to first page when searching to show results from the beginning
416
- if (features.pagination) {
483
+ if (secureFeatures.pagination) {
417
484
  stateActions.setPagination({ ...state.pagination, pageIndex: 0 });
418
485
  }
419
- }, [stateActions, features.pagination, state.pagination]);
486
+ }, [stateActions, secureFeatures.pagination, state.pagination]);
420
487
 
421
488
  // ============================================================================
422
489
  // SERVER-SIDE DATA FETCHING - ALWAYS call these hooks
@@ -439,68 +506,18 @@ function DataTableInternal<TData extends DataRecord>({
439
506
  * - Component is in client-side mode (finalPaginationMode !== 'server')
440
507
  * - No server-side config is provided (!serverSide)
441
508
  */
442
- const handleServerSideChange = useCallback(async () => {
443
- if (finalPaginationMode !== 'server' || !serverSide) return;
444
-
445
- const params: ServerSideParams = {
446
- pageIndex: state.pagination.pageIndex,
447
- pageSize: state.pagination.pageSize,
448
- sorting: state.sorting,
449
- columnFilters: state.columnFilters,
450
- globalFilter: searchQuery,
451
- grouping: state.grouping,
452
- };
453
-
454
- await fetchServerData(params);
455
- }, [
509
+ useServerSideDataEffect<TData>({
456
510
  finalPaginationMode,
457
511
  serverSide,
458
- state.pagination,
459
- state.sorting,
460
- state.columnFilters,
512
+ pagination: state.pagination,
513
+ sorting: state.sorting,
514
+ columnFilters: state.columnFilters,
515
+ grouping: state.grouping,
461
516
  searchQuery,
462
- state.grouping,
517
+ tableDataLength: finalTableData.length,
463
518
  fetchServerData,
464
- ]);
465
-
466
- // ============================================================================
467
- // EFFECTS - ALWAYS call these hooks
468
- // ============================================================================
469
-
470
- /**
471
- * Effect: Trigger server-side data fetch when table state changes.
472
- *
473
- * This effect watches for changes to table state that require a server-side fetch:
474
- * - Sorting changes
475
- * - Filtering changes
476
- * - Pagination changes
477
- * - Grouping changes
478
- *
479
- * The effect includes multiple guards to prevent unnecessary server calls:
480
- * - Skip if not in server-side mode
481
- * - Skip if no server-side config provided
482
- * - Skip if we already have data in client-side mode
483
- *
484
- * This prevents infinite loops and unnecessary network requests.
485
- */
486
- useEffect(() => {
487
- // Only trigger server-side fetch in server mode
488
- if (finalPaginationMode !== 'server') return;
489
- if (!serverSide) return;
490
-
491
- // For client-side mode with data, no server fetch needed
492
- if (tableData && tableData.length > 0 && finalPaginationMode !== 'server') return;
493
-
494
- handleServerSideChange();
495
- }, [handleServerSideChange, tableData, finalPaginationMode, serverSide]);
496
-
497
- useEffect(() => {
498
- return () => {
499
- if (typeof cleanup === 'function') {
500
- cleanup();
501
- }
502
- };
503
- }, [cleanup]);
519
+ cleanup,
520
+ });
504
521
 
505
522
  // ============================================================================
506
523
  // RBAC VALIDATION AND SECURE CONFIGURATION - ALWAYS call these hooks
@@ -548,23 +565,12 @@ function DataTableInternal<TData extends DataRecord>({
548
565
  if (!permissions.canUpdate.can) {
549
566
  throw new Error('Insufficient permissions to edit this resource');
550
567
  }
551
- // CRITICAL: Calculate rowId the same way as in DataTableBody
552
- // Find the index of this row in the data array
553
- let rowId: string;
554
- if (getRowId) {
555
- const rowIndex = data.findIndex(r => r === row);
556
- rowId = getRowId(row, rowIndex);
557
- } else {
558
- rowId = (row as any).id;
559
- }
560
-
561
- if (!rowId) {
562
- console.error('[DataTable] Cannot edit row: no id found. Make sure to provide getRowId or ensure row data has an id property.');
563
- return;
564
- }
565
-
568
+
569
+ const rowIndex = data.findIndex(r => r === row);
570
+ const rowId = resolvedGetRowId(row, rowIndex >= 0 ? rowIndex : 0);
571
+
566
572
  // Set the row into editing mode with the current row data
567
- stateActions.setEditingRow(rowId, { ...row });
573
+ stateActions.setEditingRow(rowId, toCellValueRecord(row));
568
574
  },
569
575
  icon: Edit,
570
576
  testId: 'edit',
@@ -599,7 +605,7 @@ function DataTableInternal<TData extends DataRecord>({
599
605
  }
600
606
 
601
607
  return result;
602
- }, [actions, secureFeatures, permissions, secureHandlers, getRowId, stateActions]);
608
+ }, [actions, secureFeatures, permissions, secureHandlers, resolvedGetRowId, stateActions, data]);
603
609
 
604
610
  // MANDATORY: Process columns with actions
605
611
  const { enhancedColumns } = useTableColumns({
@@ -609,114 +615,17 @@ function DataTableInternal<TData extends DataRecord>({
609
615
  columnOrder: state.columnOrder
610
616
  });
611
617
 
612
- // MANDATORY: Memoize table configuration to prevent unnecessary re-creation
613
- const tableConfig = useMemo(() => ({
614
- data: finalTableData as TData[],
615
- columns: enhancedColumns,
616
- state: {
617
- sorting: state.sorting,
618
- columnFilters: state.columnFilters,
619
- columnVisibility: state.columnVisibility,
620
- rowSelection,
621
- grouping: state.grouping,
622
- expanded: state.expanded,
623
- pagination: state.pagination,
624
- globalFilter: searchQuery,
625
- columnOrder: effectiveColumnOrder,
626
- },
627
- initialState: {
628
- expanded: features.grouping ? {} : undefined,
629
- },
630
- enableRowSelection: features.selection,
631
- enableGrouping: features.grouping,
632
- getRowId: getRowId || ((row: TData) => row?.id || row?.toString() || 'unknown'),
633
- onSortingChange: (updaterOrValue: unknown) => {
634
- const newSorting = typeof updaterOrValue === 'function'
635
- ? (updaterOrValue as (prev: SortingState) => SortingState)(state.sorting)
636
- : updaterOrValue as SortingState;
637
- stateActions.setSorting(newSorting);
638
- },
639
- onColumnFiltersChange: (updaterOrValue: unknown) => {
640
- const newFilters = typeof updaterOrValue === 'function'
641
- ? (updaterOrValue as (prev: ColumnFiltersState) => ColumnFiltersState)(state.columnFilters)
642
- : updaterOrValue as ColumnFiltersState;
643
- stateActions.setColumnFilters(newFilters);
644
- },
645
- onColumnVisibilityChange: (updaterOrValue: unknown) => {
646
- // Handle both function and value updates from TanStack Table
647
- const newVisibility = typeof updaterOrValue === 'function'
648
- ? (updaterOrValue as (prev: VisibilityState) => VisibilityState)(state.columnVisibility)
649
- : updaterOrValue as VisibilityState;
650
- stateActions.setColumnVisibility(newVisibility);
651
- },
652
- onRowSelectionChange: (updaterOrValue: unknown) => {
653
- /**
654
- * Row selection change handler supporting both controlled and uncontrolled modes.
655
- *
656
- * TanStack Table can call with either:
657
- * - A value: `{ rowId: true, rowId2: false }`
658
- * - A function: `(prev) => ({ ...prev, rowId: true })`
659
- *
660
- * We handle both patterns to support the full TanStack API.
661
- */
662
- const newSelection = typeof updaterOrValue === 'function'
663
- ? (updaterOrValue as (prev: Record<string, boolean>) => Record<string, boolean>)(selection !== undefined ? selection : state.rowSelection)
664
- : updaterOrValue as Record<string, boolean>;
665
-
666
- /**
667
- * Controlled vs Uncontrolled Mode:
668
- *
669
- * - Uncontrolled (selection prop is undefined): Update internal state
670
- * - Controlled (selection prop is provided): Don't update internal state,
671
- * let parent manage state, but still notify parent of changes
672
- *
673
- * This pattern allows DataTable to work both as a self-contained component
674
- * and as a fully controlled component in complex forms/parent apps.
675
- */
676
- if (selection === undefined) {
677
- stateActions.setRowSelection(newSelection);
678
- }
679
-
680
- // Always notify parent component so it can react to selection changes
681
- onRowSelectionChange?.(newSelection);
682
- },
683
- onGroupingChange: (updaterOrValue: unknown) => {
684
- const newGrouping = typeof updaterOrValue === 'function'
685
- ? (updaterOrValue as (prev: GroupingState) => GroupingState)(state.grouping)
686
- : updaterOrValue as GroupingState;
687
- stateActions.setGrouping(newGrouping);
688
- },
689
- onExpandedChange: (updaterOrValue: unknown) => {
690
- const newExpanded = typeof updaterOrValue === 'function'
691
- ? (updaterOrValue as (prev: ExpandedState) => ExpandedState)(state.expanded)
692
- : updaterOrValue as ExpandedState;
693
- stateActions.setExpanded(newExpanded);
694
- },
695
- onPaginationChange: (updaterOrValue: unknown) => {
696
- const newPagination = typeof updaterOrValue === 'function'
697
- ? (updaterOrValue as (prev: PaginationState) => PaginationState)(state.pagination)
698
- : updaterOrValue as PaginationState;
699
- stateActions.setPagination(newPagination);
700
- },
701
- onColumnOrderChange: (updaterOrValue: unknown) => {
702
- const newOrder = typeof updaterOrValue === 'function'
703
- ? (updaterOrValue as (prev: string[]) => string[])(state.columnOrder)
704
- : updaterOrValue as string[];
705
- stateActions.setColumnOrder(newOrder);
706
- },
707
- getCoreRowModel: getCoreRowModel(),
708
- getFilteredRowModel: finalPaginationMode === 'client' ? getFilteredRowModel() : undefined,
709
- getSortedRowModel: finalPaginationMode === 'client' ? getSortedRowModel() : undefined,
710
- getPaginationRowModel: features.pagination ? getPaginationRowModel() : undefined,
711
- getGroupedRowModel: features.grouping ? getGroupedRowModel() : undefined,
712
- getExpandedRowModel: features.grouping ? getExpandedRowModel() : undefined,
713
- manualSorting: finalPaginationMode === 'server',
714
- manualFiltering: finalPaginationMode === 'server',
715
- manualPagination: finalPaginationMode === 'server',
716
- pageCount: finalPaginationMode === 'server' ? Math.ceil(dataCount / state.pagination.pageSize) : undefined,
618
+ const tableStateSnapshot = useMemo<TableStateSnapshot<TData>>(() => ({
619
+ sorting: state.sorting,
620
+ columnFilters: state.columnFilters,
621
+ columnVisibility: state.columnVisibility,
622
+ rowSelection,
623
+ grouping: state.grouping,
624
+ expanded: state.expanded,
625
+ pagination: state.pagination,
626
+ globalFilter: searchQuery,
627
+ columnOrder: state.columnOrder,
717
628
  }), [
718
- finalTableData,
719
- enhancedColumns,
720
629
  state.sorting,
721
630
  state.columnFilters,
722
631
  state.columnVisibility,
@@ -725,16 +634,34 @@ function DataTableInternal<TData extends DataRecord>({
725
634
  state.expanded,
726
635
  state.pagination,
727
636
  searchQuery,
728
- effectiveColumnOrder,
729
- features.selection,
730
- features.grouping,
731
- getRowId,
637
+ state.columnOrder,
638
+ ]);
639
+
640
+ const tableHandlers = useTableHandlers({
641
+ state,
642
+ stateSnapshot: tableStateSnapshot,
643
+ actions: stateActions,
644
+ selection,
732
645
  onRowSelectionChange,
646
+ effectiveColumnOrder,
647
+ canPersistVisibility: secureFeatures.columnVisibility && Boolean(storageKey),
648
+ canPersistOrder: secureFeatures.columnReordering && Boolean(storageKey),
649
+ updateSavedColumnVisibility,
650
+ updateColumnOrder,
651
+ onLayoutChange,
652
+ });
653
+
654
+ const tableConfig = useDataTableConfiguration({
655
+ data: finalTableData as TData[],
656
+ columns: enhancedColumns,
657
+ stateSnapshot: tableStateSnapshot,
658
+ handlers: tableHandlers,
659
+ features: secureFeatures,
660
+ getRowId: resolvedGetRowId,
733
661
  finalPaginationMode,
734
- features.pagination,
735
- dataCount,
736
- state.pagination.pageSize
737
- ]);
662
+ finalDataCount,
663
+ pageSize: state.pagination.pageSize,
664
+ });
738
665
 
739
666
  const table = useReactTable(tableConfig);
740
667
 
@@ -776,20 +703,22 @@ function DataTableInternal<TData extends DataRecord>({
776
703
  return (
777
704
  <>
778
705
  {/* Table with semantic HTML structure */}
779
- <table className={getTableClasses({
780
- isFixed: true,
781
- variant,
782
- className: cn('border-collapse relative w-full', className)
783
- })} style={{
784
- tableLayout: 'fixed',
785
- width: '100%'
786
- }}>
706
+ <table
707
+ className={getTableClasses({
708
+ isFixed: true,
709
+ variant,
710
+ className: cn('border-collapse relative w-full', className)
711
+ })}
712
+ aria-label={title}
713
+ aria-describedby={description ? 'table-description' : undefined}
714
+ aria-busy={isLoading ? 'true' : 'false'}
715
+ >
787
716
  {/* Caption with title, description, and toolbar */}
788
717
  <caption className="text-left pb-2">
789
718
  {(title || description) && (
790
719
  <>
791
720
  {title && <h2 >{title}</h2>}
792
- {description && <p>{description}</p>}
721
+ {description && <p id="table-description">{description}</p>}
793
722
  </>
794
723
  )}
795
724
  <>
@@ -871,9 +800,9 @@ function DataTableInternal<TData extends DataRecord>({
871
800
 
872
801
  {/* Column groups */}
873
802
  <colgroup>
874
- {hasSelectColumn && <col span={1} data-col-type="select" />}
803
+ {hasSelectColumn && <col span={1} data-col-type="select" className="w-12" />}
875
804
  <col span={dataColumns} data-col-type="data" />
876
- {hasActionsColumn && <col span={1} data-col-type="actions" />}
805
+ {hasActionsColumn && <col span={1} data-col-type="actions"/>}
877
806
  </colgroup>
878
807
 
879
808
  {/* Table header */}
@@ -895,39 +824,78 @@ function DataTableInternal<TData extends DataRecord>({
895
824
  ? 'descending'
896
825
  : 'none')
897
826
  : undefined;
827
+ const isRightAligned = header.column.columnDef.meta?.align === 'right';
828
+
829
+ // Create custom sort handler with accessibility announcement
830
+ const handleSortClick = (event: React.MouseEvent) => {
831
+ const originalHandler = header.column.getToggleSortingHandler();
832
+ if (originalHandler) {
833
+ originalHandler(event);
834
+ }
835
+
836
+ // Announce the sort change
837
+ const columnName = typeof header.column.columnDef.header === 'string'
838
+ ? header.column.columnDef.header
839
+ : 'column';
840
+ const currentSort = header.column.getIsSorted();
841
+ const newSort = currentSort === 'asc' ? 'desc' : currentSort === 'desc' ? null : 'asc';
842
+ announceSortChange(columnName, newSort);
843
+ };
844
+
845
+ // Get keyboard navigation handlers for this header
846
+ const headerKeyboardHandlers = keyboardNavigation.getHeaderKeyboardHandlers(
847
+ header.index,
848
+ () => {
849
+ // Sort handler for keyboard navigation
850
+ const originalHandler = header.column.getToggleSortingHandler();
851
+ if (originalHandler) {
852
+ originalHandler({} as any);
853
+ }
854
+
855
+ // Announce the sort change
856
+ const columnName = typeof header.column.columnDef.header === 'string'
857
+ ? header.column.columnDef.header
858
+ : 'column';
859
+ const currentSort = header.column.getIsSorted();
860
+ const newSort = currentSort === 'asc' ? 'desc' : currentSort === 'desc' ? null : 'asc';
861
+ announceSortChange(columnName, newSort);
862
+ }
863
+ );
864
+
898
865
  return (
899
866
  <th
900
867
  key={header.id}
868
+ className={`px-3 py-2 ${isRightAligned ? 'text-right' : 'text-left'}`}
869
+ scope="col"
870
+ role="columnheader"
901
871
  {...(isSortable ? { 'aria-sort': ariaSort } : {})}
872
+ {...(isSortable ? headerKeyboardHandlers : {})}
902
873
  >
903
874
  {header.isPlaceholder ? null : (
904
875
  isSortable ? (
905
876
  <Button
906
877
  variant="ghost"
907
- className="h-auto p-0 font-medium hover:bg-transparent"
908
- onClick={header.column.getToggleSortingHandler()}
878
+ className={`h-auto p-0 font-medium hover:bg-transparent ${isRightAligned ? 'justify-end' : 'justify-start'}`}
879
+ onClick={handleSortClick}
880
+ {...headerKeyboardHandlers}
909
881
  aria-label={`Sort by ${typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : 'column'}`}
910
882
  tabIndex={0}
911
883
  >
912
- <div className={`flex items-center gap-1 ${header.column.columnDef.meta?.align === 'right' ? 'justify-end' : ''}`}>
913
- {typeof header.column.columnDef.header === 'function'
914
- ? header.column.columnDef.header(header.getContext())
915
- : header.column.columnDef.header}
916
- {header.column.getIsSorted() === 'asc' ? (
917
- <ChevronUp className="h-4 w-4" />
918
- ) : header.column.getIsSorted() === 'desc' ? (
919
- <ChevronDown className="h-4 w-4" />
920
- ) : (
921
- <ChevronsUpDown className="h-4 w-4" />
922
- )}
923
- </div>
924
- </Button>
925
- ) : (
926
- <div className={header.column.columnDef.meta?.align === 'right' ? 'text-right' : ''}>
927
884
  {typeof header.column.columnDef.header === 'function'
928
885
  ? header.column.columnDef.header(header.getContext())
929
886
  : header.column.columnDef.header}
930
- </div>
887
+ {header.column.getIsSorted() === 'asc' ? (
888
+ <ChevronUp className="h-4 w-4" />
889
+ ) : header.column.getIsSorted() === 'desc' ? (
890
+ <ChevronDown className="h-4 w-4" />
891
+ ) : (
892
+ <ChevronsUpDown className="h-4 w-4" />
893
+ )}
894
+ </Button>
895
+ ) : (
896
+ typeof header.column.columnDef.header === 'function'
897
+ ? header.column.columnDef.header(header.getContext())
898
+ : header.column.columnDef.header
931
899
  )
932
900
  )}
933
901
  </th>
@@ -966,8 +934,12 @@ function DataTableInternal<TData extends DataRecord>({
966
934
  if (onEditRow && editingRowId) {
967
935
  // Find the original row data
968
936
  const originalRow = data.find(row => {
969
- const rowId = getRowId ? getRowId(row, 0) : (row as any).id || String(0);
970
- return rowId === editingRowId;
937
+ try {
938
+ const rowId = resolvedGetRowId(row, 0);
939
+ return rowId === editingRowId;
940
+ } catch {
941
+ return false;
942
+ }
971
943
  });
972
944
  if (originalRow) {
973
945
  onEditRow(originalRow, editingData as Partial<TData>);
@@ -980,19 +952,19 @@ function DataTableInternal<TData extends DataRecord>({
980
952
  }}
981
953
  grouping={state.grouping}
982
954
  aggregates={aggregates}
983
- getRowId={getRowId}
955
+ getRowId={resolvedGetRowId}
984
956
  emptyState={React.isValidElement(emptyState) ? undefined : emptyState as any}
985
957
  isFiltered={searchQuery !== '' || state.columnFilters.length > 0}
986
958
  onClearFilters={() => {
987
959
  stateActions.setSearchQuery('');
988
960
  stateActions.setColumnFilters([]);
989
961
  }}
990
- enableFiltering={features.filtering}
962
+ enableFiltering={secureFeatures.filtering}
991
963
  showFilterRow={showFilterRow}
992
964
  dataLength={finalTableData?.length || 0}
993
965
  virtualHeight={virtualHeight}
994
966
  forceVirtualization={false}
995
- hierarchical={features.hierarchical && hierarchical?.enabled ? {
967
+ hierarchical={secureFeatures.hierarchical && hierarchical?.enabled && hierarchicalState ? {
996
968
  ...hierarchical,
997
969
  state: hierarchicalState,
998
970
  expandAll: hierarchicalState.expandAll,
@@ -1007,7 +979,7 @@ function DataTableInternal<TData extends DataRecord>({
1007
979
  />
1008
980
 
1009
981
  {/* Table footer with pagination */}
1010
- {features.pagination && (
982
+ {secureFeatures.pagination && (
1011
983
  <tfoot>
1012
984
  <tr>
1013
985
  <td colSpan={visibleColumns.length}>