@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.
- package/CHANGELOG.md +8 -0
- package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
- package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
- package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
- package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
- package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
- package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
- package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
- package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
- package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
- package/dist/chunk-2OGV6IRV.js.map +1 -0
- package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
- package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
- package/dist/chunk-CVMVPYAL.js.map +1 -0
- package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
- package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
- package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
- package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
- package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
- package/dist/chunk-LRQ6RBJC.js.map +1 -0
- package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
- package/dist/chunk-MNJXXD6C.js.map +1 -0
- package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
- package/dist/chunk-PTR5PMPE.js.map +1 -0
- package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
- package/dist/chunk-Q7APDV6H.js.map +1 -0
- package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
- package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
- package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
- package/dist/chunk-S63MFSY6.js.map +1 -0
- package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
- package/dist/chunk-WUXCWRL6.js +20 -0
- package/dist/chunk-WUXCWRL6.js.map +1 -0
- package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
- package/dist/chunk-YVVGHRGI.js.map +1 -0
- package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
- package/dist/chunk-ZMNXIJP4.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +14 -18
- package/dist/components.js.map +1 -1
- package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +8 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +19 -27
- package/dist/index.js +21 -29
- package/dist/index.js.map +1 -1
- package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
- package/dist/providers.d.ts +7 -21
- package/dist/providers.js +3 -10
- package/dist/rbac/index.d.ts +71 -221
- package/dist/rbac/index.js +15 -16
- package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
- package/dist/types.d.ts +3 -3
- package/dist/types.js +61 -18
- package/dist/types.js.map +1 -1
- package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
- package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +52 -9
- package/dist/utils.js.map +1 -1
- package/docs/CONTENT_AUDIT_REPORT.md +253 -0
- package/docs/DOCUMENTATION_AUDIT.md +172 -0
- package/docs/README.md +142 -147
- package/docs/STYLE_GUIDE.md +37 -0
- package/docs/api/classes/ColumnFactory.md +17 -17
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +5 -5
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +8 -8
- package/docs/api/classes/RBACCache.md +35 -5
- package/docs/api/classes/RBACEngine.md +49 -20
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +11 -0
- package/docs/api/interfaces/DataTableAction.md +65 -29
- package/docs/api/interfaces/DataTableColumn.md +36 -23
- package/docs/api/interfaces/DataTableProps.md +80 -38
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +11 -11
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +7 -7
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +251 -269
- package/docs/api-reference/components.md +193 -0
- package/docs/api-reference/hooks.md +265 -0
- package/docs/api-reference/providers.md +6 -0
- package/docs/api-reference/types.md +6 -0
- package/docs/api-reference/utilities.md +207 -0
- package/docs/architecture/README.md +6 -0
- package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
- package/docs/architecture/rbac-security-architecture.md +258 -0
- package/docs/architecture/services.md +9 -1
- package/docs/best-practices/README.md +6 -0
- package/docs/best-practices/accessibility.md +6 -0
- package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
- package/docs/best-practices/deployment.md +6 -0
- package/docs/best-practices/performance.md +475 -2
- package/docs/best-practices/security.md +6 -0
- package/docs/best-practices/testing.md +6 -0
- package/docs/core-concepts/authentication.md +6 -0
- package/docs/core-concepts/events.md +6 -0
- package/docs/core-concepts/organisations.md +6 -0
- package/docs/core-concepts/permissions.md +6 -0
- package/docs/core-concepts/rbac-system.md +8 -0
- package/docs/documentation-index.md +121 -182
- package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
- package/docs/getting-started/documentation-index.md +40 -0
- package/docs/getting-started/examples/README.md +878 -35
- package/docs/{faq.md → getting-started/faq.md} +7 -1
- package/docs/getting-started/installation-guide.md +6 -0
- package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
- package/docs/implementation-guides/app-layout.md +6 -0
- package/docs/implementation-guides/authentication.md +1021 -0
- package/docs/implementation-guides/component-styling.md +6 -0
- package/docs/implementation-guides/data-tables.md +1264 -2076
- package/docs/implementation-guides/dynamic-colors.md +6 -0
- package/docs/implementation-guides/event-theming-summary.md +6 -0
- package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
- package/docs/implementation-guides/file-upload-storage.md +6 -0
- package/docs/implementation-guides/forms.md +6 -0
- package/docs/implementation-guides/inactivity-tracking.md +6 -0
- package/docs/implementation-guides/navigation.md +6 -0
- package/docs/implementation-guides/organisation-security.md +6 -0
- package/docs/implementation-guides/permission-enforcement.md +6 -0
- package/docs/implementation-guides/public-pages-advanced.md +6 -0
- package/docs/implementation-guides/public-pages.md +6 -0
- package/docs/migration/MIGRATION_GUIDE.md +827 -351
- package/docs/migration/README.md +7 -1
- package/docs/migration/organisation-context-timing-fix.md +6 -0
- package/docs/migration/rbac-migration.md +44 -1
- package/docs/migration/service-architecture.md +6 -0
- package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
- package/docs/migration/v0.4.16-css-first-approach.md +6 -0
- package/docs/migration/v0.4.17-source-path-fix.md +6 -0
- package/docs/rbac/README-rbac-rls-integration.md +6 -0
- package/docs/rbac/README.md +6 -0
- package/docs/rbac/advanced-patterns.md +6 -0
- package/docs/rbac/api-reference.md +7 -1
- package/docs/rbac/breaking-changes-v3.md +222 -0
- package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
- package/docs/rbac/examples.md +6 -0
- package/docs/rbac/getting-started.md +6 -0
- package/docs/rbac/migration-guide.md +260 -0
- package/docs/rbac/quick-start.md +70 -13
- package/docs/rbac/rbac-rls-integration.md +6 -0
- package/docs/rbac/super-admin-guide.md +6 -0
- package/docs/rbac/troubleshooting.md +6 -0
- package/docs/security/README.md +6 -0
- package/docs/security/checklist.md +6 -0
- package/docs/styles/README.md +7 -1
- package/docs/{usage.md → styles/usage.md} +6 -0
- package/docs/testing/README.md +6 -0
- package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
- package/docs/troubleshooting/README.md +387 -5
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
- package/docs/troubleshooting/common-issues.md +6 -0
- package/docs/troubleshooting/database-view-compatibility.md +6 -0
- package/docs/troubleshooting/organisation-context-setup.md +6 -0
- package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
- package/docs/troubleshooting/styling-issues.md +6 -0
- package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
- package/src/__tests__/helpers/test-providers.tsx +3 -53
- package/src/components/DataTable/DataTable.test.tsx +319 -0
- package/src/components/DataTable/DataTable.tsx +32 -11
- package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
- package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
- package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +88 -104
- package/src/components/DataTable/components/DataTableCore.tsx +309 -337
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
- package/src/components/DataTable/components/DataTableModals.tsx +22 -1
- package/src/components/DataTable/components/EditableRow.tsx +69 -84
- package/src/components/DataTable/components/EmptyState.tsx +5 -1
- package/src/components/DataTable/components/ImportModal.tsx +65 -36
- package/src/components/DataTable/components/PaginationControls.tsx +40 -100
- package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
- package/src/components/DataTable/context/DataTableContext.tsx +1 -1
- package/src/components/DataTable/core/ColumnFactory.ts +5 -0
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
- package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
- package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
- package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
- package/src/components/DataTable/hooks/index.ts +7 -0
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
- package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
- package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
- package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
- package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
- package/src/components/DataTable/index.ts +12 -3
- package/src/components/DataTable/types.ts +129 -9
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
- package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
- package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
- package/src/components/DataTable/utils/a11yUtils.ts +244 -0
- package/src/components/DataTable/utils/debugTools.ts +609 -0
- package/src/components/DataTable/utils/exportUtils.ts +114 -16
- package/src/components/DataTable/utils/flexibleImport.ts +202 -32
- package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
- package/src/components/DataTable/utils/index.ts +2 -0
- package/src/components/DataTable/utils/paginationUtils.ts +350 -0
- package/src/components/DataTable/utils/rowUtils.ts +6 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
- package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
- package/src/components/Select/Select.tsx +1 -0
- package/src/components/examples/PermissionExample.tsx +173 -0
- package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
- package/src/examples/PublicEventPage.tsx +274 -0
- package/src/examples/PublicPageApp.tsx +308 -0
- package/src/examples/PublicPageUsageExample.tsx +216 -0
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
- package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
- package/src/hooks/useOrganisationPermissions.test.ts +42 -18
- package/src/hooks/useOrganisationPermissions.ts +12 -6
- package/src/hooks/useOrganisationSecurity.test.ts +138 -85
- package/src/hooks/useOrganisationSecurity.ts +41 -10
- package/src/index.ts +0 -1
- package/src/providers/AuthProvider.simplified.tsx +880 -0
- package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
- package/src/providers/index.ts +0 -1
- package/src/providers/services/EventServiceProvider.tsx +19 -15
- package/src/providers/services/InactivityServiceProvider.tsx +19 -15
- package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
- package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
- package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
- package/src/rbac/adapters.tsx +26 -69
- package/src/rbac/api.test.ts +90 -27
- package/src/rbac/api.ts +61 -10
- package/src/rbac/audit.test.ts +33 -38
- package/src/rbac/audit.ts +21 -6
- package/src/rbac/cache.ts +33 -1
- package/src/rbac/components/NavigationGuard.tsx +11 -11
- package/src/rbac/components/NavigationProvider.test.tsx +11 -5
- package/src/rbac/components/NavigationProvider.tsx +37 -13
- package/src/rbac/components/PagePermissionGuard.tsx +111 -50
- package/src/rbac/components/PagePermissionProvider.tsx +5 -5
- package/src/rbac/components/PermissionEnforcer.tsx +11 -11
- package/src/rbac/components/RoleBasedRouter.tsx +5 -5
- package/src/rbac/components/SecureDataProvider.tsx +5 -5
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
- package/src/rbac/engine.test.simple.ts +19 -13
- package/src/rbac/engine.test.ts +1 -0
- package/src/rbac/engine.ts +330 -766
- package/src/rbac/errors.ts +156 -0
- package/src/rbac/hooks/usePermissions.ts +32 -10
- package/src/rbac/hooks/useRBAC.test.ts +126 -512
- package/src/rbac/hooks/useRBAC.ts +147 -193
- package/src/rbac/hooks/useResolvedScope.ts +12 -0
- package/src/rbac/index.ts +7 -4
- package/src/rbac/security.ts +109 -18
- package/src/rbac/types.ts +12 -1
- package/src/services/AuthService.ts +2 -15
- package/src/services/EventService.ts +43 -46
- package/src/services/OrganisationService.ts +51 -31
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +1 -1
- package/src/services/__tests__/OrganisationService.test.ts +1 -1
- package/src/services/base/BaseService.ts +8 -0
- package/src/styles/base.css +208 -0
- package/src/styles/semantic.css +24 -0
- package/src/types/database.generated.ts +7347 -0
- package/src/types/database.ts +20 -0
- package/src/utils/logger.ts +179 -0
- package/src/utils/organisationContext.ts +11 -4
- package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
- package/dist/appNameResolver-UURKN7NF.js +0 -22
- package/dist/audit-6TOCAMKO.js.map +0 -1
- package/dist/chunk-B2WTCLCV.js.map +0 -1
- package/dist/chunk-FGMFQSHX.js.map +0 -1
- package/dist/chunk-K34IM5CT.js.map +0 -1
- package/dist/chunk-KHJS6VIA.js.map +0 -1
- package/dist/chunk-KK73ZB4E.js.map +0 -1
- package/dist/chunk-M5IWZRBT.js.map +0 -1
- package/dist/chunk-ULBI5JGB.js +0 -109
- package/dist/chunk-ULBI5JGB.js.map +0 -1
- package/dist/chunk-WN6XJWOS.js.map +0 -1
- package/dist/chunk-XLZ7U46Z.js.map +0 -1
- package/dist/chunk-Y6TXWPJO.js.map +0 -1
- package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
- package/docs/TERMINOLOGY.md +0 -231
- package/docs/api/interfaces/RBACContextType.md +0 -468
- package/docs/api/interfaces/RBACProviderProps.md +0 -107
- package/docs/best-practices/performance-expansion.md +0 -473
- package/docs/breaking-changes.md +0 -179
- package/docs/consuming-app-example.md +0 -290
- package/docs/documentation-templates.md +0 -539
- package/docs/examples/navigation-menu-auth-fix.md +0 -344
- package/docs/getting-started/examples/basic-auth-app.md +0 -520
- package/docs/getting-started/examples/full-featured-app.md +0 -616
- package/docs/getting-started/quick-start.md +0 -376
- package/docs/implementation-guides/datatable-filtering.md +0 -313
- package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
- package/docs/implementation-guides/hierarchical-datatable.md +0 -850
- package/docs/implementation-guides/large-datasets.md +0 -281
- package/docs/implementation-guides/performance.md +0 -403
- package/docs/migration/quick-migration-guide.md +0 -320
- package/docs/migration-guide.md +0 -193
- package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
- package/docs/performance/README.md +0 -551
- package/docs/style-guide.md +0 -964
- package/docs/troubleshooting/authentication-issues.md +0 -334
- package/docs/troubleshooting/debugging.md +0 -1117
- package/docs/troubleshooting/migration.md +0 -918
- package/src/__tests__/hooks/usePermissions.test.ts +0 -261
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
- package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
- package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
- package/src/hooks/services/usePermissions.ts +0 -70
- package/src/hooks/services/useRBACService.ts +0 -30
- package/src/hooks/usePermissionCheck.ts +0 -150
- package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
- package/src/providers/services/RBACServiceProvider.tsx +0 -79
- package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
- package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
- package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
- package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
- package/src/rbac/providers/RBACProvider.tsx +0 -645
- package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
- package/src/rbac/providers/index.ts +0 -11
- package/src/services/RBACService.ts +0 -522
- package/src/services/__tests__/RBACService.test.ts +0 -492
- package/src/services/interfaces/IRBACService.ts +0 -62
- package/src/utils/appNameResolver.test 2.ts +0 -494
- /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
- /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
- /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
- /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
- /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
- /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
- /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
- /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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 {
|
|
34
|
+
import { useColumnVisibilityPersistence } from '../hooks/useColumnVisibilityPersistence';
|
|
45
35
|
import { useDataTableState } from '../hooks/useDataTableState';
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} from '../
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
214
|
-
}, [
|
|
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
|
-
//
|
|
343
|
+
// KEYBOARD NAVIGATION - ALWAYS call this hook
|
|
277
344
|
// ============================================================================
|
|
278
345
|
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
}, [features.hierarchical, hierarchical?.enabled, data]);
|
|
355
|
+
);
|
|
285
356
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
357
|
+
// Store focus when modals open, restore when they close
|
|
358
|
+
useEffect(() => {
|
|
359
|
+
if (state.showImportModal) {
|
|
360
|
+
keyboardNavigation.storeFocus();
|
|
289
361
|
}
|
|
290
|
-
|
|
291
|
-
}, [features.hierarchical, hierarchical?.enabled, hierarchicalValidation.isValid, data]);
|
|
362
|
+
}, [state.showImportModal, keyboardNavigation]);
|
|
292
363
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
//
|
|
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:
|
|
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 (!
|
|
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
|
-
|
|
338
|
-
|
|
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,
|
|
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 (
|
|
483
|
+
if (secureFeatures.pagination) {
|
|
417
484
|
stateActions.setPagination({ ...state.pagination, pageIndex: 0 });
|
|
418
485
|
}
|
|
419
|
-
}, [stateActions,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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=
|
|
908
|
-
onClick={
|
|
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
|
-
|
|
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
|
-
|
|
970
|
-
|
|
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={
|
|
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={
|
|
962
|
+
enableFiltering={secureFeatures.filtering}
|
|
991
963
|
showFilterRow={showFilterRow}
|
|
992
964
|
dataLength={finalTableData?.length || 0}
|
|
993
965
|
virtualHeight={virtualHeight}
|
|
994
966
|
forceVirtualization={false}
|
|
995
|
-
hierarchical={
|
|
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
|
-
{
|
|
982
|
+
{secureFeatures.pagination && (
|
|
1011
983
|
<tfoot>
|
|
1012
984
|
<tr>
|
|
1013
985
|
<td colSpan={visibleColumns.length}>
|