@jmruthers/pace-core 0.5.191 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +7 -1
- package/cursor-rules/00-pace-core-compliance.mdc +372 -0
- package/cursor-rules/01-standards-compliance.mdc +275 -0
- package/cursor-rules/02-project-structure.mdc +200 -0
- package/cursor-rules/03-solid-principles.mdc +341 -0
- package/cursor-rules/04-testing-standards.mdc +315 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
- package/cursor-rules/06-code-quality.mdc +392 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
- package/cursor-rules/CHANGELOG.md +101 -0
- package/cursor-rules/README.md +191 -0
- package/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
- package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
- package/dist/{DataTable-WKRZD47S.js → DataTable-DQ7RSOHE.js} +8 -7
- package/dist/{PublicPageProvider-ULXC_u6U.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +37 -156
- package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
- package/dist/{UnifiedAuthProvider-FTSG5XH7.js → UnifiedAuthProvider-ATAP5UTR.js} +3 -3
- package/dist/{api-IHKALJZD.js → api-N774RPUA.js} +2 -2
- package/dist/{chunk-6C4YBBJM.js → chunk-3QRJFVBR.js} +1 -1
- package/dist/chunk-3QRJFVBR.js.map +1 -0
- package/dist/{chunk-OETXORNB.js → chunk-3XTALGJF.js} +211 -136
- package/dist/chunk-3XTALGJF.js.map +1 -0
- package/dist/{chunk-6TQDD426.js → chunk-4N5C5XZU.js} +47 -228
- package/dist/chunk-4N5C5XZU.js.map +1 -0
- package/dist/{chunk-LOMZXPSN.js → chunk-4ZC4GX36.js} +47 -74
- package/dist/chunk-4ZC4GX36.js.map +1 -0
- package/dist/{chunk-6LTQQAT6.js → chunk-BYFSK72L.js} +357 -158
- package/dist/chunk-BYFSK72L.js.map +1 -0
- package/dist/{chunk-XYXSXPUK.js → chunk-EXUD6RNJ.js} +50 -10
- package/dist/chunk-EXUD6RNJ.js.map +1 -0
- package/dist/{chunk-VKB2CO4Z.js → chunk-GLK6VM3F.js} +244 -249
- package/dist/chunk-GLK6VM3F.js.map +1 -0
- package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
- package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
- package/dist/{chunk-XNYQOL3Z.js → chunk-JBKQ3SAO.js} +9 -18
- package/dist/chunk-JBKQ3SAO.js.map +1 -0
- package/dist/{chunk-ROXMHMY2.js → chunk-KNC55RTG.js} +13 -3
- package/dist/{chunk-ROXMHMY2.js.map → chunk-KNC55RTG.js.map} +1 -1
- package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
- package/dist/{chunk-ULHIJK66.js → chunk-T33XF5ZC.js} +255 -140
- package/dist/chunk-T33XF5ZC.js.map +1 -0
- package/dist/{chunk-VRGWKHDB.js → chunk-XM25TVIE.js} +100 -33
- package/dist/chunk-XM25TVIE.js.map +1 -0
- package/dist/components.d.ts +4 -4
- package/dist/components.js +9 -9
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +20 -25
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +18 -21
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +2 -20
- package/dist/rbac/index.js +7 -9
- package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
- package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +3 -3
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +2 -2
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +10 -10
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.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/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +24 -11
- 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/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.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/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- 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 +1 -1
- 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 +1 -1
- 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/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.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/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +2 -2
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +2 -2
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +2 -2
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +2 -2
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.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/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.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 +60 -38
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- 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/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +194 -209
- package/docs/getting-started/cursor-rules.md +262 -0
- package/docs/getting-started/installation-guide.md +6 -1
- package/docs/getting-started/quick-start.md +6 -1
- package/docs/migration/MIGRATION_GUIDE.md +4 -4
- package/docs/migration/REACT_19_MIGRATION.md +227 -0
- package/docs/migration/database-changes-december-2025.md +2 -1
- package/docs/rbac/event-based-apps.md +124 -6
- package/docs/standards/README.md +39 -0
- package/docs/troubleshooting/migration.md +4 -4
- package/examples/PublicPages/PublicEventPage.tsx +1 -1
- package/package.json +11 -6
- package/scripts/audit-consuming-app.cjs +961 -0
- package/scripts/check-pace-core-compliance.cjs +315 -61
- package/scripts/install-cursor-rules.cjs +236 -0
- package/src/__tests__/helpers/test-providers.tsx +1 -1
- package/src/__tests__/helpers/test-utils.tsx +1 -1
- package/src/__tests__/rls-policies.test.ts +3 -1
- package/src/components/Badge/Badge.tsx +2 -4
- package/src/components/Button/Button.tsx +5 -4
- package/src/components/Calendar/Calendar.tsx +1 -1
- package/src/components/DataTable/DataTable.test.tsx +57 -93
- package/src/components/DataTable/DataTable.tsx +2 -2
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
- package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
- package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +88 -16
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
- package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/DataTableCore.tsx +4 -7
- package/src/components/DataTable/components/DataTableModals.tsx +1 -1
- package/src/components/DataTable/components/EditableRow.tsx +1 -1
- package/src/components/DataTable/components/UnifiedTableBody.tsx +86 -17
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
- package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
- package/src/components/Dialog/Dialog.tsx +6 -5
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
- package/src/components/EventSelector/EventSelector.tsx +1 -1
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -3
- package/src/components/FileDisplay/FileDisplay.tsx +16 -4
- package/src/components/Footer/Footer.tsx +1 -1
- package/src/components/Form/Form.test.tsx +36 -15
- package/src/components/Form/Form.tsx +30 -26
- package/src/components/Header/Header.tsx +1 -1
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
- package/src/components/Input/Input.tsx +28 -30
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +42 -42
- package/src/components/LoginForm/LoginForm.tsx +8 -8
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
- package/src/components/NavigationMenu/NavigationMenu.tsx +2 -11
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -1
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +75 -52
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +98 -69
- package/src/components/PaceAppLayout/README.md +1 -1
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -8
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
- package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
- package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
- package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
- package/src/components/Select/Select.tsx +33 -22
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +27 -29
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/components/UserMenu/UserMenu.tsx +1 -1
- package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +14 -7
- package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
- package/src/hooks/public/usePublicEvent.ts +1 -1
- package/src/hooks/public/usePublicEventLogo.ts +1 -1
- package/src/hooks/public/usePublicRouteParams.ts +1 -1
- package/src/hooks/services/useAuthService.ts +21 -3
- package/src/hooks/services/useEventService.ts +21 -3
- package/src/hooks/services/useInactivityService.ts +21 -3
- package/src/hooks/services/useOrganisationService.ts +21 -3
- package/src/hooks/useDataTableState.ts +8 -18
- package/src/hooks/useFileDisplay.ts +10 -17
- package/src/hooks/useFocusManagement.ts +2 -2
- package/src/hooks/useFocusTrap.ts +4 -4
- package/src/hooks/useFormDialog.ts +8 -7
- package/src/hooks/useInactivityTracker.ts +1 -1
- package/src/hooks/usePermissionCache.ts +1 -1
- package/src/hooks/useSecureDataAccess.test.ts +16 -9
- package/src/hooks/useSecureDataAccess.ts +22 -6
- package/src/hooks/useToast.ts +2 -2
- package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
- package/src/providers/services/EventServiceProvider.tsx +0 -8
- package/src/providers/services/UnifiedAuthProvider.tsx +196 -46
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +34 -40
- package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
- package/src/rbac/adapters.tsx +3 -22
- package/src/rbac/api.test.ts +2 -2
- package/src/rbac/api.ts +7 -1
- package/src/rbac/components/EnhancedNavigationMenu.tsx +3 -16
- package/src/rbac/components/NavigationGuard.tsx +2 -11
- package/src/rbac/components/NavigationProvider.tsx +1 -2
- package/src/rbac/components/PagePermissionGuard.tsx +1 -1
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +46 -13
- package/src/rbac/components/RoleBasedRouter.tsx +1 -1
- package/src/rbac/components/SecureDataProvider.tsx +1 -2
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
- package/src/rbac/engine.ts +14 -2
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/usePermissions.ts +51 -11
- package/src/rbac/hooks/useRBAC.ts +3 -13
- package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
- package/src/rbac/hooks/useResolvedScope.ts +58 -33
- package/src/rbac/hooks/useSecureSupabase.ts +4 -9
- package/src/rbac/secureClient.ts +43 -0
- package/src/services/EventService.ts +4 -57
- package/src/services/InactivityService.ts +127 -34
- package/src/services/OrganisationService.ts +68 -10
- package/src/utils/security/secureDataAccess.test.ts +31 -20
- package/src/utils/security/secureDataAccess.ts +4 -3
- package/dist/chunk-6C4YBBJM.js.map +0 -1
- package/dist/chunk-6LTQQAT6.js.map +0 -1
- package/dist/chunk-6TQDD426.js.map +0 -1
- package/dist/chunk-LOMZXPSN.js.map +0 -1
- package/dist/chunk-OETXORNB.js.map +0 -1
- package/dist/chunk-ULHIJK66.js.map +0 -1
- package/dist/chunk-VKB2CO4Z.js.map +0 -1
- package/dist/chunk-VRGWKHDB.js.map +0 -1
- package/dist/chunk-XNYQOL3Z.js.map +0 -1
- package/dist/chunk-XYXSXPUK.js.map +0 -1
- package/scripts/check-pace-core-compliance.js +0 -512
- package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
- package/src/utils/context/superAdminOverride.ts +0 -58
- /package/dist/{DataTable-WKRZD47S.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-FTSG5XH7.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
- /package/dist/{api-IHKALJZD.js.map → api-N774RPUA.js.map} +0 -0
- /package/dist/{chunk-QWWZ5CAQ.js.map → chunk-LXQLPRQ2.js.map} +0 -0
- /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
- /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/index.ts +0 -0
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import React from 'react';
|
|
12
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
12
|
+
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
|
|
13
|
+
import userEvent from '@testing-library/user-event';
|
|
13
14
|
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
14
15
|
import { DataTable } from '../DataTable';
|
|
15
16
|
import { getPaginationBinding, getPageSizeOptions, calculateOptimalPageSize } from '../utils/paginationUtils';
|
|
@@ -76,8 +77,47 @@ vi.mock('../../../providers/services/UnifiedAuthProvider', () => ({
|
|
|
76
77
|
|
|
77
78
|
// Mock RBAC hooks
|
|
78
79
|
vi.mock('../../../rbac/hooks', () => ({
|
|
79
|
-
useCan: () => ({ can: true,
|
|
80
|
-
useResolvedScope: () => ({
|
|
80
|
+
useCan: () => ({ can: true, isLoading: false, error: null }),
|
|
81
|
+
useResolvedScope: () => ({
|
|
82
|
+
resolvedScope: {
|
|
83
|
+
organisationId: 'test-org',
|
|
84
|
+
eventId: 'test-event',
|
|
85
|
+
appId: 'test-app'
|
|
86
|
+
},
|
|
87
|
+
isLoading: false
|
|
88
|
+
}),
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
// Mock useDataTablePermissions to return non-loading permissions
|
|
92
|
+
const mockUseDataTablePermissions = vi.hoisted(() => vi.fn(() => ({
|
|
93
|
+
permissions: {
|
|
94
|
+
canRead: { can: true, isLoading: false, error: null },
|
|
95
|
+
canCreate: { can: true, isLoading: false, error: null },
|
|
96
|
+
canUpdate: { can: true, isLoading: false, error: null },
|
|
97
|
+
canDelete: { can: true, isLoading: false, error: null },
|
|
98
|
+
canExport: { can: true, isLoading: false, error: null },
|
|
99
|
+
canImport: { can: true, isLoading: false, error: null },
|
|
100
|
+
},
|
|
101
|
+
secureFeatures: {
|
|
102
|
+
search: true,
|
|
103
|
+
sorting: true,
|
|
104
|
+
pagination: true,
|
|
105
|
+
selection: true,
|
|
106
|
+
creation: true,
|
|
107
|
+
editing: true,
|
|
108
|
+
deletion: true,
|
|
109
|
+
deleteSelected: true,
|
|
110
|
+
export: true,
|
|
111
|
+
import: true,
|
|
112
|
+
columnVisibility: true,
|
|
113
|
+
filtering: true,
|
|
114
|
+
grouping: true,
|
|
115
|
+
},
|
|
116
|
+
effectivePageId: 'test-pagination',
|
|
117
|
+
})));
|
|
118
|
+
|
|
119
|
+
vi.mock('../hooks/useDataTablePermissions', () => ({
|
|
120
|
+
useDataTablePermissions: mockUseDataTablePermissions
|
|
81
121
|
}));
|
|
82
122
|
|
|
83
123
|
// Mock toast
|
|
@@ -300,7 +340,7 @@ describe('DataTable Pagination Integration', () => {
|
|
|
300
340
|
const largeDataset = generateTestData(100);
|
|
301
341
|
|
|
302
342
|
describe('Client Mode Integration', () => {
|
|
303
|
-
it('should render pagination controls for client mode', () => {
|
|
343
|
+
it('should render pagination controls for client mode', async () => {
|
|
304
344
|
render(
|
|
305
345
|
<DataTable
|
|
306
346
|
data={smallDataset}
|
|
@@ -328,11 +368,17 @@ describe('DataTable Pagination Integration', () => {
|
|
|
328
368
|
/>
|
|
329
369
|
);
|
|
330
370
|
|
|
331
|
-
|
|
371
|
+
// Wait for table to render
|
|
372
|
+
await waitFor(() => {
|
|
373
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
expect(await screen.findByText('Page 1 of 3')).toBeInTheDocument();
|
|
332
377
|
expect(screen.getByText('Rows per page')).toBeInTheDocument();
|
|
333
378
|
});
|
|
334
379
|
|
|
335
380
|
it('should navigate pages correctly in client mode', async () => {
|
|
381
|
+
const user = userEvent.setup();
|
|
336
382
|
render(
|
|
337
383
|
<DataTable
|
|
338
384
|
data={smallDataset}
|
|
@@ -360,16 +406,27 @@ describe('DataTable Pagination Integration', () => {
|
|
|
360
406
|
/>
|
|
361
407
|
);
|
|
362
408
|
|
|
409
|
+
// Wait for table to render
|
|
410
|
+
await waitFor(() => {
|
|
411
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
412
|
+
});
|
|
413
|
+
|
|
363
414
|
// Should start on page 1
|
|
364
|
-
expect(screen.
|
|
415
|
+
expect(await screen.findByText('Page 1 of 3')).toBeInTheDocument();
|
|
365
416
|
|
|
366
|
-
// Click next page
|
|
417
|
+
// Click next page using userEvent
|
|
367
418
|
const nextButton = screen.getByLabelText('Go to next page');
|
|
368
|
-
|
|
369
|
-
|
|
419
|
+
|
|
420
|
+
// Click the button - this triggers table.nextPage() which calls onPaginationChange
|
|
421
|
+
// The onPaginationChange handler updates React state, which should trigger a re-render
|
|
422
|
+
await user.click(nextButton);
|
|
423
|
+
|
|
424
|
+
// Wait for the page text to update - TanStack Table updates state synchronously,
|
|
425
|
+
// but React needs to process the state update and re-render
|
|
426
|
+
// We wait for the text to change, which indicates the component has re-rendered
|
|
370
427
|
await waitFor(() => {
|
|
371
428
|
expect(screen.getByText('Page 2 of 3')).toBeInTheDocument();
|
|
372
|
-
});
|
|
429
|
+
}, { timeout: 10000, interval: 100 });
|
|
373
430
|
});
|
|
374
431
|
|
|
375
432
|
// Note: Page size change test removed due to test environment issues with Select component
|
|
@@ -493,7 +550,7 @@ describe('DataTable Pagination Integration', () => {
|
|
|
493
550
|
});
|
|
494
551
|
|
|
495
552
|
describe('Edge Cases', () => {
|
|
496
|
-
it('should handle empty datasets gracefully', () => {
|
|
553
|
+
it('should handle empty datasets gracefully', async () => {
|
|
497
554
|
render(
|
|
498
555
|
<DataTable
|
|
499
556
|
data={[]}
|
|
@@ -520,10 +577,15 @@ describe('DataTable Pagination Integration', () => {
|
|
|
520
577
|
/>
|
|
521
578
|
);
|
|
522
579
|
|
|
523
|
-
|
|
580
|
+
// Wait for table to render
|
|
581
|
+
await waitFor(() => {
|
|
582
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
expect(await screen.findByText('Page 1 of 1')).toBeInTheDocument();
|
|
524
586
|
});
|
|
525
587
|
|
|
526
|
-
it('should handle single page datasets', () => {
|
|
588
|
+
it('should handle single page datasets', async () => {
|
|
527
589
|
const singlePageData = generateTestData(5);
|
|
528
590
|
|
|
529
591
|
render(
|
|
@@ -553,7 +615,12 @@ describe('DataTable Pagination Integration', () => {
|
|
|
553
615
|
/>
|
|
554
616
|
);
|
|
555
617
|
|
|
556
|
-
|
|
618
|
+
// Wait for table to render
|
|
619
|
+
await waitFor(() => {
|
|
620
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
expect(await screen.findByText('Page 1 of 1')).toBeInTheDocument();
|
|
557
624
|
|
|
558
625
|
// Navigation buttons should be disabled
|
|
559
626
|
expect(screen.getByLabelText('Go to previous page')).toBeDisabled();
|
|
@@ -587,7 +654,7 @@ describe('DataTable Pagination Integration', () => {
|
|
|
587
654
|
// ============================================================================
|
|
588
655
|
|
|
589
656
|
describe('Pagination Performance', () => {
|
|
590
|
-
it('should handle large datasets efficiently in client mode', () => {
|
|
657
|
+
it('should handle large datasets efficiently in client mode', async () => {
|
|
591
658
|
const largeDataset = generateTestData(10000);
|
|
592
659
|
|
|
593
660
|
const startTime = performance.now();
|
|
@@ -624,7 +691,12 @@ describe('Pagination Performance', () => {
|
|
|
624
691
|
|
|
625
692
|
// Should render within reasonable time (adjust threshold as needed)
|
|
626
693
|
expect(renderTime).toBeLessThan(1000); // 1 second
|
|
627
|
-
|
|
694
|
+
// Wait for table to render
|
|
695
|
+
await waitFor(() => {
|
|
696
|
+
expect(screen.getByRole('table')).toBeInTheDocument();
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
expect(await screen.findByText('Page 1 of 200')).toBeInTheDocument();
|
|
628
700
|
});
|
|
629
701
|
|
|
630
702
|
it('should use appropriate page sizes for different modes', () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module Components/DataTable/__tests__
|
|
5
5
|
* @since 2.0.0
|
|
6
6
|
*
|
|
7
|
-
* Tests for SSR safety and React
|
|
7
|
+
* Tests for SSR safety and React 19 strict mode compatibility.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
@@ -39,7 +39,7 @@ const testColumns: DataTableColumn<TestData>[] = [
|
|
|
39
39
|
},
|
|
40
40
|
];
|
|
41
41
|
|
|
42
|
-
const
|
|
42
|
+
const baseProps = {
|
|
43
43
|
data: testData,
|
|
44
44
|
columns: testColumns,
|
|
45
45
|
rbac: { pageId: 'test' },
|
|
@@ -92,7 +92,7 @@ describe('DataTable SSR and Strict Mode Compatibility', () => {
|
|
|
92
92
|
(global as any).window = {} as any;
|
|
93
93
|
|
|
94
94
|
expect(() => {
|
|
95
|
-
React.createElement(SafeDataTable,
|
|
95
|
+
React.createElement(SafeDataTable, baseProps);
|
|
96
96
|
}).not.toThrow();
|
|
97
97
|
|
|
98
98
|
global.window = originalWindow;
|
|
@@ -104,7 +104,7 @@ describe('DataTable SSR and Strict Mode Compatibility', () => {
|
|
|
104
104
|
(global as any).localStorage = undefined as any;
|
|
105
105
|
|
|
106
106
|
expect(() => {
|
|
107
|
-
React.createElement(SafeDataTable,
|
|
107
|
+
React.createElement(SafeDataTable, baseProps);
|
|
108
108
|
}).not.toThrow();
|
|
109
109
|
|
|
110
110
|
global.localStorage = originalLocalStorage;
|
|
@@ -116,20 +116,20 @@ describe('DataTable SSR and Strict Mode Compatibility', () => {
|
|
|
116
116
|
(global as any).document = {} as any;
|
|
117
117
|
|
|
118
118
|
expect(() => {
|
|
119
|
-
React.createElement(SafeDataTable,
|
|
119
|
+
React.createElement(SafeDataTable, baseProps);
|
|
120
120
|
}).not.toThrow();
|
|
121
121
|
|
|
122
122
|
global.document = originalDocument;
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
-
describe('React
|
|
126
|
+
describe('React 19 Strict Mode Compatibility', () => {
|
|
127
127
|
it('should handle strict mode double-invoke without errors', () => {
|
|
128
128
|
let renderCount = 0;
|
|
129
129
|
|
|
130
130
|
const TestComponent = () => {
|
|
131
131
|
renderCount++;
|
|
132
|
-
return <SafeDataTable {...
|
|
132
|
+
return <SafeDataTable {...baseProps} />;
|
|
133
133
|
};
|
|
134
134
|
|
|
135
135
|
expect(() => {
|
|
@@ -153,7 +153,7 @@ describe('DataTable SSR and Strict Mode Compatibility', () => {
|
|
|
153
153
|
};
|
|
154
154
|
}, []);
|
|
155
155
|
|
|
156
|
-
return <SafeDataTable {...
|
|
156
|
+
return <SafeDataTable {...baseProps} />;
|
|
157
157
|
};
|
|
158
158
|
|
|
159
159
|
expect(() => {
|
|
@@ -176,7 +176,7 @@ describe('DataTable SSR and Strict Mode Compatibility', () => {
|
|
|
176
176
|
return (
|
|
177
177
|
<div>
|
|
178
178
|
<span data-testid="count">{count}</span>
|
|
179
|
-
<SafeDataTable {...
|
|
179
|
+
<SafeDataTable {...baseProps} />
|
|
180
180
|
</div>
|
|
181
181
|
);
|
|
182
182
|
};
|
|
@@ -270,7 +270,7 @@ describe('DataTable SSR and Strict Mode Compatibility', () => {
|
|
|
270
270
|
|
|
271
271
|
return (
|
|
272
272
|
<div>
|
|
273
|
-
<SafeDataTable {...
|
|
273
|
+
<SafeDataTable {...baseProps} />
|
|
274
274
|
{mounted && <div data-testid="client-only">Client Only</div>}
|
|
275
275
|
</div>
|
|
276
276
|
);
|
|
@@ -291,7 +291,7 @@ describe('DataTable SSR and Strict Mode Compatibility', () => {
|
|
|
291
291
|
|
|
292
292
|
return (
|
|
293
293
|
<div>
|
|
294
|
-
<SafeDataTable {...
|
|
294
|
+
<SafeDataTable {...baseProps} />
|
|
295
295
|
<div data-testid="client-state">
|
|
296
296
|
{isClient ? 'Client' : 'Server'}
|
|
297
297
|
</div>
|
|
@@ -311,7 +311,7 @@ describe('DataTable SSR and Strict Mode Compatibility', () => {
|
|
|
311
311
|
|
|
312
312
|
render(
|
|
313
313
|
<React.StrictMode>
|
|
314
|
-
<SafeDataTable {...
|
|
314
|
+
<SafeDataTable {...baseProps} />
|
|
315
315
|
</React.StrictMode>
|
|
316
316
|
);
|
|
317
317
|
|
|
@@ -8,12 +8,10 @@
|
|
|
8
8
|
* This is the main component that consumers will use.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import React, { useMemo, useCallback, useEffect,
|
|
12
|
-
import { useReactTable
|
|
11
|
+
import React, { useMemo, useCallback, useEffect, useRef } from 'react';
|
|
12
|
+
import { useReactTable } from '@tanstack/react-table';
|
|
13
13
|
import type {
|
|
14
14
|
SortingState,
|
|
15
|
-
ColumnFiltersState,
|
|
16
|
-
VisibilityState,
|
|
17
15
|
GroupingState,
|
|
18
16
|
ExpandedState,
|
|
19
17
|
PaginationState,
|
|
@@ -159,7 +157,7 @@ export interface DataTableCoreProps<TData extends DataRecord> {
|
|
|
159
157
|
// Utilities
|
|
160
158
|
getRowId?: GetRowId<TData>;
|
|
161
159
|
isLoading?: boolean;
|
|
162
|
-
emptyState?: EmptyStateConfig | React.ReactElement
|
|
160
|
+
emptyState?: EmptyStateConfig | React.ReactElement<any>;
|
|
163
161
|
aggregates?: AggregateConfig[];
|
|
164
162
|
importModalConfig?: ImportModalConfig;
|
|
165
163
|
actions?: DataTableAction<TData>[];
|
|
@@ -220,7 +218,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
220
218
|
onLayoutChange,
|
|
221
219
|
} = props;
|
|
222
220
|
|
|
223
|
-
const logger =
|
|
221
|
+
const logger = createLogger('DataTableCore');
|
|
224
222
|
|
|
225
223
|
// ============================================================================
|
|
226
224
|
// ALL HOOKS MUST BE CALLED IN THE SAME ORDER EVERY RENDER
|
|
@@ -1234,7 +1232,6 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
1234
1232
|
)}
|
|
1235
1233
|
|
|
1236
1234
|
</table>
|
|
1237
|
-
|
|
1238
1235
|
{/* Modal Dialogs */}
|
|
1239
1236
|
<DataTableModals
|
|
1240
1237
|
showImportModal={state.showImportModal}
|
|
@@ -184,7 +184,7 @@ export function DataTableModals<TData extends Record<string, unknown> = Record<s
|
|
|
184
184
|
onStoreFocus,
|
|
185
185
|
onRestoreFocus,
|
|
186
186
|
}: DataTableModalsProps<TData>) {
|
|
187
|
-
const logger =
|
|
187
|
+
const logger = createLogger('DataTableModals');
|
|
188
188
|
// Handle focus management for import modal
|
|
189
189
|
useEffect(() => {
|
|
190
190
|
if (showImportModal) {
|
|
@@ -45,7 +45,7 @@ function SelectEditField<TData extends DataRecord>({
|
|
|
45
45
|
onChange: (value: CellValue) => void;
|
|
46
46
|
className?: string;
|
|
47
47
|
}) {
|
|
48
|
-
const logger =
|
|
48
|
+
const logger = createLogger('SelectEditField');
|
|
49
49
|
// Determine if searchable - explicitly check for true to ensure visible search input appears
|
|
50
50
|
// When selectSearchable is true or undefined, show the visible search input box
|
|
51
51
|
// When selectSearchable is false, hide the search input (type-to-search still works via SelectContent internals)
|
|
@@ -23,6 +23,7 @@ import { getTableCellClasses, getTableHeadClasses, getTableRowClasses } from '..
|
|
|
23
23
|
import { Input } from '../../Input/Input';
|
|
24
24
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectGroup, SelectLabel, SelectSeparator } from '../../Select/Select';
|
|
25
25
|
import { createLogger } from '../../../utils/core/logger';
|
|
26
|
+
import { cn } from '../../../utils/core/cn';
|
|
26
27
|
import type {
|
|
27
28
|
AggregateConfig,
|
|
28
29
|
DataRecord,
|
|
@@ -136,7 +137,7 @@ function SelectEditField<TData extends DataRecord>({
|
|
|
136
137
|
placeholder?: string;
|
|
137
138
|
onChange: (value: CellValue) => void;
|
|
138
139
|
}) {
|
|
139
|
-
const logger =
|
|
140
|
+
const logger = createLogger('SelectEditField');
|
|
140
141
|
// Determine if searchable - explicitly check for true to ensure visible search input appears
|
|
141
142
|
// When selectSearchable is true or undefined, show the visible search input box
|
|
142
143
|
// When selectSearchable is false, hide the search input (type-to-search still works via SelectContent internals)
|
|
@@ -516,7 +517,7 @@ const RowComponent = React.memo(({
|
|
|
516
517
|
}: RowProps) => {
|
|
517
518
|
const rowRef = useRef<HTMLTableRowElement>(null);
|
|
518
519
|
const firstInputRef = useRef<HTMLInputElement>(null);
|
|
519
|
-
const logger =
|
|
520
|
+
const logger = createLogger('RowComponent');
|
|
520
521
|
|
|
521
522
|
const rowId = getRowIdSafe(row.original, row.index, getRowId);
|
|
522
523
|
|
|
@@ -526,6 +527,10 @@ const RowComponent = React.memo(({
|
|
|
526
527
|
const isParent = isHierarchical && hierarchicalRow.isParent;
|
|
527
528
|
const isChild = isHierarchical && !hierarchicalRow.isParent;
|
|
528
529
|
|
|
530
|
+
// React Compiler handles memoization automatically
|
|
531
|
+
const visibleCells = row.getVisibleCells();
|
|
532
|
+
const isSelected = typeof row.getIsSelected === 'function' ? row.getIsSelected() : false;
|
|
533
|
+
|
|
529
534
|
// Auto-focus first editable field when entering edit mode
|
|
530
535
|
useEffect(() => {
|
|
531
536
|
if (isEditing && firstInputRef.current) {
|
|
@@ -565,7 +570,6 @@ const RowComponent = React.memo(({
|
|
|
565
570
|
const groupValue = row.getValue(grouping[0]);
|
|
566
571
|
const subRowsCount = row.subRows?.length || 0;
|
|
567
572
|
const isExpanded = row.getIsExpanded();
|
|
568
|
-
const visibleCells = row.getVisibleCells();
|
|
569
573
|
|
|
570
574
|
// Get child rows for aggregation (convert TanStack Row to original data)
|
|
571
575
|
const childRows: DataRecord[] = row.subRows?.map((subRow: any) => subRow.original) || [];
|
|
@@ -709,20 +713,30 @@ const RowComponent = React.memo(({
|
|
|
709
713
|
}
|
|
710
714
|
|
|
711
715
|
// Regular row (not in edit mode)
|
|
712
|
-
|
|
713
|
-
const allCells = row.getAllCells ? row.getAllCells() : [];
|
|
716
|
+
// visibleCells is already memoized above
|
|
714
717
|
|
|
715
718
|
// Calculate indentation for child rows
|
|
716
719
|
const indentSize = hierarchical?.indentSize || 24;
|
|
717
|
-
const indentation =
|
|
718
|
-
|
|
720
|
+
const indentation = React.useMemo(() => {
|
|
721
|
+
return isChild && hierarchical?.state ?
|
|
722
|
+
calculateIndentation(hierarchicalRow, [], indentSize) : 0;
|
|
723
|
+
}, [isChild, hierarchical?.state, hierarchicalRow, indentSize]);
|
|
719
724
|
|
|
720
|
-
// Apply hierarchical row classes
|
|
721
|
-
const rowClassName =
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
725
|
+
// Apply hierarchical row classes - combine with standard row classes
|
|
726
|
+
const rowClassName = React.useMemo(() => {
|
|
727
|
+
if (isHierarchical) {
|
|
728
|
+
const hierarchicalClass = isParent
|
|
729
|
+
? hierarchical?.parentRowClassName || 'bg-main-50 hover:bg-main-100 font-medium'
|
|
730
|
+
: hierarchical?.childRowClassName || 'bg-sec-25 hover:bg-sec-50';
|
|
731
|
+
// Combine with standard row classes for consistent hover behavior
|
|
732
|
+
return cn(
|
|
733
|
+
getTableRowClasses({ isSelected, isVirtualized: false }),
|
|
734
|
+
hierarchicalClass
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
// Use standard row classes when not hierarchical
|
|
738
|
+
return getTableRowClasses({ isSelected, isVirtualized: false });
|
|
739
|
+
}, [isHierarchical, isParent, isSelected, hierarchical]);
|
|
726
740
|
|
|
727
741
|
return (
|
|
728
742
|
<tr
|
|
@@ -734,7 +748,7 @@ const RowComponent = React.memo(({
|
|
|
734
748
|
...(isChild && indentation > 0 ? { paddingLeft: `${indentation}px` } : {})
|
|
735
749
|
}}
|
|
736
750
|
className={rowClassName}
|
|
737
|
-
aria-selected={
|
|
751
|
+
aria-selected={isSelected ? 'true' : 'false'}
|
|
738
752
|
>
|
|
739
753
|
{visibleCells.map((cell: any, cellIndex: number) => {
|
|
740
754
|
const isFirstCell = cellIndex === 0;
|
|
@@ -797,8 +811,63 @@ const RowComponent = React.memo(({
|
|
|
797
811
|
|
|
798
812
|
RowComponent.displayName = 'RowComponent';
|
|
799
813
|
|
|
800
|
-
//
|
|
801
|
-
|
|
814
|
+
// Custom comparison function for React.memo to prevent unnecessary re-renders
|
|
815
|
+
// This compares the actual row data and props, not just object references
|
|
816
|
+
const areRowPropsEqual = (prevProps: RowProps, nextProps: RowProps): boolean => {
|
|
817
|
+
// Compare row by ID and index (stable identifiers)
|
|
818
|
+
if (prevProps.row.id !== nextProps.row.id || prevProps.row.index !== nextProps.row.index) {
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Compare editing state
|
|
823
|
+
if (prevProps.isEditing !== nextProps.isEditing) {
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (prevProps.editingRowId !== nextProps.editingRowId) {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Compare style object (shallow comparison)
|
|
832
|
+
if (prevProps.style !== nextProps.style) {
|
|
833
|
+
if (!prevProps.style || !nextProps.style) {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
// Convert to records for comparison
|
|
837
|
+
const prevStyle = prevProps.style as Record<string, unknown>;
|
|
838
|
+
const nextStyle = nextProps.style as Record<string, unknown>;
|
|
839
|
+
const styleKeys = new Set([...Object.keys(prevStyle), ...Object.keys(nextStyle)]);
|
|
840
|
+
for (const key of styleKeys) {
|
|
841
|
+
if (prevStyle[key] !== nextStyle[key]) {
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Compare other primitive props
|
|
848
|
+
if (prevProps.grouping.length !== nextProps.grouping.length ||
|
|
849
|
+
prevProps.grouping.some((id, i) => id !== nextProps.grouping[i])) {
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// For hierarchical state, compare the enabled flag and state functions
|
|
854
|
+
if (prevProps.hierarchical?.enabled !== nextProps.hierarchical?.enabled) {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Compare row selection state by checking the function result
|
|
859
|
+
const prevSelected = typeof prevProps.row.getIsSelected === 'function' ? prevProps.row.getIsSelected() : false;
|
|
860
|
+
const nextSelected = typeof nextProps.row.getIsSelected === 'function' ? nextProps.row.getIsSelected() : false;
|
|
861
|
+
if (prevSelected !== nextSelected) {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// If all checks pass, props are equal
|
|
866
|
+
return true;
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
// Use the memoized RowComponent with custom comparison
|
|
870
|
+
const MemoizedRow = React.memo(RowComponent, areRowPropsEqual);
|
|
802
871
|
|
|
803
872
|
/**
|
|
804
873
|
* Unified table body component with intelligent virtualization
|
|
@@ -831,7 +900,7 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
831
900
|
rbac,
|
|
832
901
|
permissions
|
|
833
902
|
}: UnifiedTableBodyProps<TData>) {
|
|
834
|
-
const logger =
|
|
903
|
+
const logger = createLogger('UnifiedTableBody');
|
|
835
904
|
|
|
836
905
|
const headerRef = useRef<HTMLTableSectionElement>(null);
|
|
837
906
|
const bodyRef = useRef<HTMLTableSectionElement>(null);
|