@jmruthers/pace-core 0.6.5 → 0.6.7
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 +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -392,9 +392,11 @@ describe('DataTable Accessibility', () => {
|
|
|
392
392
|
);
|
|
393
393
|
|
|
394
394
|
// When loading, the table might not be rendered, so check for loading state
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
expect(
|
|
395
|
+
// The spinner has role="status" which provides the aria-live region
|
|
396
|
+
const spinner = screen.getByRole('status');
|
|
397
|
+
expect(spinner).toBeInTheDocument();
|
|
398
|
+
// Check that the visible loading text is present
|
|
399
|
+
expect(screen.getByText('Loading...', { selector: 'strong' })).toBeInTheDocument();
|
|
398
400
|
});
|
|
399
401
|
|
|
400
402
|
it('should not have aria-busy when not loading', async () => {
|
|
@@ -682,7 +684,11 @@ describe('DataTable Accessibility', () => {
|
|
|
682
684
|
const results = await axe(container, {
|
|
683
685
|
rules: {
|
|
684
686
|
// Column visibility button has icon-only design - acceptable pattern
|
|
685
|
-
'button-name': { enabled: false }
|
|
687
|
+
'button-name': { enabled: false },
|
|
688
|
+
// EmptyState uses Alert which renders as <aside> with role="status" - acceptable pattern for status messages
|
|
689
|
+
'aria-allowed-role': { enabled: false },
|
|
690
|
+
// EmptyState uses h5 for title - acceptable when used in status messages
|
|
691
|
+
'heading-order': { enabled: false }
|
|
686
692
|
}
|
|
687
693
|
});
|
|
688
694
|
expect(results).toHaveNoViolations();
|
|
@@ -14,20 +14,20 @@ import '@testing-library/jest-dom';
|
|
|
14
14
|
import React from 'react';
|
|
15
15
|
|
|
16
16
|
// Mock icon components
|
|
17
|
-
const MockEditIcon = ({ className }: { className?: string }) => <
|
|
18
|
-
const MockDeleteIcon = ({ className }: { className?: string }) => <
|
|
17
|
+
const MockEditIcon = ({ className }: { className?: string }) => <span className={className} data-testid="edit-icon">Edit</span>;
|
|
18
|
+
const MockDeleteIcon = ({ className }: { className?: string }) => <span className={className} data-testid="delete-icon">Delete</span>;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Common mock implementations for DataTable components
|
|
22
22
|
*/
|
|
23
23
|
export const mockComponents = {
|
|
24
|
-
DataTableContext: vi.fn(({ children }: any) => <
|
|
25
|
-
DataTableToolbar: vi.fn(() => <
|
|
26
|
-
|
|
27
|
-
DataTableModals: vi.fn(() => <
|
|
28
|
-
PaginationControls: vi.fn(() => <
|
|
29
|
-
LoadingState: vi.fn(() => <
|
|
30
|
-
DataTableErrorBoundary: vi.fn(({ children }: any) => <
|
|
24
|
+
DataTableContext: vi.fn(({ children }: any) => <section data-testid="data-table-context">{children}</section>),
|
|
25
|
+
DataTableToolbar: vi.fn(() => <nav data-testid="data-table-toolbar">Toolbar</nav>),
|
|
26
|
+
UnifiedTableBody: vi.fn(() => <main data-testid="unified-table-body">Body</main>),
|
|
27
|
+
DataTableModals: vi.fn(() => <section data-testid="data-table-modals">Modals</section>),
|
|
28
|
+
PaginationControls: vi.fn(() => <nav data-testid="pagination-controls">Pagination</nav>),
|
|
29
|
+
LoadingState: vi.fn(() => <section data-testid="loading-state">Loading</section>),
|
|
30
|
+
DataTableErrorBoundary: vi.fn(({ children }: any) => <section data-testid="error-boundary">{children}</section>),
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input } from '../../Input/Input';
|
|
3
3
|
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../Select/Select';
|
|
4
|
-
import { Button } from '../../Button/Button';
|
|
5
|
-
import { X, Filter } from 'lucide-react';
|
|
6
4
|
import type { Column } from '@tanstack/react-table';
|
|
7
5
|
import { getColumnHeaderText } from '../utils/columnUtils';
|
|
6
|
+
import { Filter } from 'lucide-react';
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Props for the ColumnFilter component.
|
|
@@ -50,80 +49,70 @@ export function ColumnFilter({
|
|
|
50
49
|
|
|
51
50
|
const hasFilter = columnFilterValue !== undefined && columnFilterValue !== '';
|
|
52
51
|
|
|
53
|
-
// Get the default placeholder using column header text
|
|
52
|
+
// Get the default placeholder using column header text (for Input components)
|
|
54
53
|
const defaultPlaceholder = `Filter ${getColumnHeaderText(column)}...`;
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
switch (filterType) {
|
|
58
|
-
case 'select':
|
|
59
|
-
return (
|
|
60
|
-
<Select
|
|
61
|
-
value={columnFilterValue as string || ''}
|
|
62
|
-
onValueChange={handleFilterChange}
|
|
63
|
-
>
|
|
64
|
-
<SelectTrigger className="h-8">
|
|
65
|
-
<SelectValue placeholder={placeholder || defaultPlaceholder} />
|
|
66
|
-
</SelectTrigger>
|
|
67
|
-
<SelectContent>
|
|
68
|
-
<SelectItem value="">All</SelectItem>
|
|
69
|
-
{options.map((option) => (
|
|
70
|
-
<SelectItem key={option.value} value={option.value}>
|
|
71
|
-
{option.label}
|
|
72
|
-
</SelectItem>
|
|
73
|
-
))}
|
|
74
|
-
</SelectContent>
|
|
75
|
-
</Select>
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
case 'number':
|
|
79
|
-
// Always hide spinner arrows for number filter inputs (cleaner UX)
|
|
80
|
-
return (
|
|
81
|
-
<Input
|
|
82
|
-
type="number"
|
|
83
|
-
value={columnFilterValue as string || ''}
|
|
84
|
-
onChange={(e) => handleFilterChange(e.target.value ? Number(e.target.value) : undefined)}
|
|
85
|
-
placeholder={placeholder || defaultPlaceholder}
|
|
86
|
-
className="h-8 datatable-number-no-spinners"
|
|
87
|
-
/>
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
case 'date':
|
|
91
|
-
return (
|
|
92
|
-
<Input
|
|
93
|
-
type="date"
|
|
94
|
-
value={columnFilterValue as string || ''}
|
|
95
|
-
onChange={(e) => handleFilterChange(e.target.value || undefined)}
|
|
96
|
-
placeholder={placeholder || defaultPlaceholder}
|
|
97
|
-
className="h-8"
|
|
98
|
-
/>
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
default: // text
|
|
102
|
-
return (
|
|
103
|
-
<Input
|
|
104
|
-
value={columnFilterValue as string || ''}
|
|
105
|
-
onChange={(e) => handleFilterChange(e.target.value || undefined)}
|
|
106
|
-
placeholder={placeholder || defaultPlaceholder}
|
|
107
|
-
className="h-8"
|
|
108
|
-
/>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
54
|
+
// For Select components, use just the column name (icon will replace "Filter" text)
|
|
55
|
+
const selectColumnName = `${getColumnHeaderText(column)}...`;
|
|
112
56
|
|
|
113
57
|
return (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
58
|
+
(() => {
|
|
59
|
+
switch (filterType) {
|
|
60
|
+
case 'select':
|
|
61
|
+
return (
|
|
62
|
+
<Select
|
|
63
|
+
value={columnFilterValue as string || ''}
|
|
64
|
+
onValueChange={handleFilterChange}
|
|
65
|
+
>
|
|
66
|
+
<SelectTrigger className="h-8">
|
|
67
|
+
<SelectValue>
|
|
68
|
+
<Filter className="size-4 inline-block mr-2"/>
|
|
69
|
+
<span className="truncate">{selectColumnName}</span>
|
|
70
|
+
</SelectValue>
|
|
71
|
+
</SelectTrigger>
|
|
72
|
+
<SelectContent>
|
|
73
|
+
<SelectItem value="">All</SelectItem>
|
|
74
|
+
{options.map((option) => (
|
|
75
|
+
<SelectItem key={option.value} value={option.value}>
|
|
76
|
+
{option.label}
|
|
77
|
+
</SelectItem>
|
|
78
|
+
))}
|
|
79
|
+
</SelectContent>
|
|
80
|
+
</Select>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
case 'number':
|
|
84
|
+
// Always hide spinner arrows for number filter inputs (cleaner UX)
|
|
85
|
+
return (
|
|
86
|
+
<Input
|
|
87
|
+
type="number"
|
|
88
|
+
value={columnFilterValue as string || ''}
|
|
89
|
+
onChange={(e) => handleFilterChange(e.target.value ? Number(e.target.value) : undefined)}
|
|
90
|
+
placeholder={placeholder || defaultPlaceholder}
|
|
91
|
+
className="h-8 datatable-number-no-spinners"
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
case 'date':
|
|
96
|
+
return (
|
|
97
|
+
<Input
|
|
98
|
+
type="date"
|
|
99
|
+
value={columnFilterValue as string || ''}
|
|
100
|
+
onChange={(e) => handleFilterChange(e.target.value || undefined)}
|
|
101
|
+
placeholder={placeholder || defaultPlaceholder}
|
|
102
|
+
className="h-8"
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
default: // text
|
|
107
|
+
return (
|
|
108
|
+
<Input
|
|
109
|
+
value={columnFilterValue as string || ''}
|
|
110
|
+
onChange={(e) => handleFilterChange(e.target.value || undefined)}
|
|
111
|
+
placeholder={placeholder || defaultPlaceholder}
|
|
112
|
+
className="h-8"
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
})()
|
|
128
117
|
);
|
|
129
118
|
}
|
|
@@ -5,9 +5,11 @@ import {
|
|
|
5
5
|
Select,
|
|
6
6
|
SelectContent,
|
|
7
7
|
SelectItem,
|
|
8
|
+
SelectGroup,
|
|
8
9
|
SelectSeparator,
|
|
9
10
|
SelectTrigger,
|
|
10
11
|
} from '../../Select/Select';
|
|
12
|
+
import { Label } from '../../Label/Label';
|
|
11
13
|
import { Checkbox } from '../../Checkbox/Checkbox';
|
|
12
14
|
import { Settings2, Eye, EyeOff } from 'lucide-react';
|
|
13
15
|
|
|
@@ -37,7 +39,7 @@ export function ColumnVisibilityDropdown<TData>({
|
|
|
37
39
|
);
|
|
38
40
|
|
|
39
41
|
return (
|
|
40
|
-
<Select className="w-52">
|
|
42
|
+
<Select className="w-52" showCheckmark={false}>
|
|
41
43
|
<SelectTrigger asChild>
|
|
42
44
|
<Button
|
|
43
45
|
variant="outline"
|
|
@@ -47,40 +49,40 @@ export function ColumnVisibilityDropdown<TData>({
|
|
|
47
49
|
</Button>
|
|
48
50
|
</SelectTrigger>
|
|
49
51
|
<SelectContent>
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
52
|
+
<SelectGroup className="flex flex-row gap-1 p-2">
|
|
53
|
+
<Button
|
|
54
|
+
variant="ghost"
|
|
55
|
+
size="sm"
|
|
56
|
+
className="h-7 px-2 text-xs flex-1"
|
|
57
|
+
onClick={() => {
|
|
58
|
+
toggleableColumns.forEach(column => {
|
|
59
|
+
if (!column.getIsVisible()) {
|
|
60
|
+
onColumnVisibilityChange(column.id, true);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<Eye className="size-3 mr-1" />
|
|
66
|
+
Show All
|
|
67
|
+
</Button>
|
|
68
|
+
<Button
|
|
69
|
+
variant="ghost"
|
|
70
|
+
size="sm"
|
|
71
|
+
className="h-7 px-2 text-xs flex-1"
|
|
72
|
+
onClick={() => {
|
|
73
|
+
toggleableColumns.forEach(column => {
|
|
74
|
+
if (column.getIsVisible()) {
|
|
75
|
+
onColumnVisibilityChange(column.id, false);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
<EyeOff className="size-3 mr-1" />
|
|
81
|
+
Hide All
|
|
82
|
+
</Button>
|
|
83
|
+
</SelectGroup>
|
|
84
|
+
<SelectSeparator />
|
|
85
|
+
<SelectGroup>
|
|
84
86
|
{toggleableColumns.map((column) => (
|
|
85
87
|
<SelectItem
|
|
86
88
|
key={column.id}
|
|
@@ -88,24 +90,24 @@ export function ColumnVisibilityDropdown<TData>({
|
|
|
88
90
|
className="flex items-center space-x-2 cursor-pointer px-3 py-2 text-sm hover:bg-sec-50"
|
|
89
91
|
onClick={(e) => e.preventDefault()}
|
|
90
92
|
>
|
|
93
|
+
<Label htmlFor={column.id}
|
|
94
|
+
// className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
95
|
+
>
|
|
91
96
|
<Checkbox
|
|
97
|
+
className="mr-2 align-middle"
|
|
92
98
|
id={column.id}
|
|
93
99
|
checked={column.getIsVisible()}
|
|
94
100
|
onCheckedChange={(checked) =>
|
|
95
101
|
onColumnVisibilityChange(column.id, !!checked)
|
|
96
102
|
}
|
|
97
103
|
/>
|
|
98
|
-
<label
|
|
99
|
-
htmlFor={column.id}
|
|
100
|
-
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
101
|
-
>
|
|
102
104
|
{typeof column.columnDef?.header === 'string'
|
|
103
105
|
? column.columnDef.header
|
|
104
106
|
: column.id}
|
|
105
|
-
</
|
|
107
|
+
</Label>
|
|
106
108
|
</SelectItem>
|
|
107
109
|
))}
|
|
108
|
-
</
|
|
110
|
+
</SelectGroup>
|
|
109
111
|
</SelectContent>
|
|
110
112
|
</Select>
|
|
111
113
|
);
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* This is the main component that consumers will use.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import React, { useMemo, useCallback, useEffect, useRef } from 'react';
|
|
11
|
+
import React, { useMemo, useCallback, useEffect, useLayoutEffect, useRef, useState, startTransition, useDeferredValue } from 'react';
|
|
12
12
|
import { useReactTable } from '@tanstack/react-table';
|
|
13
|
+
import { useLocation } from 'react-router-dom';
|
|
13
14
|
import type {
|
|
14
15
|
SortingState,
|
|
15
16
|
} from '@tanstack/react-table';
|
|
@@ -182,6 +183,81 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
182
183
|
|
|
183
184
|
const logger = createLogger('DataTableCore');
|
|
184
185
|
|
|
186
|
+
// Track deletion state to batch updates and prevent flashing
|
|
187
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
188
|
+
const deletionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
189
|
+
const pendingDeletionsRef = useRef<Set<string>>(new Set());
|
|
190
|
+
const dataSnapshotRef = useRef<TData[]>(data);
|
|
191
|
+
const dataChangeCountRef = useRef(0);
|
|
192
|
+
const previousDataLengthRef = useRef(data.length);
|
|
193
|
+
|
|
194
|
+
// CRITICAL: Use useLayoutEffect to capture previous length synchronously
|
|
195
|
+
// This runs before paint, so we can detect changes before React commits
|
|
196
|
+
useLayoutEffect(() => {
|
|
197
|
+
const currentLength = data.length;
|
|
198
|
+
const previousLength = previousDataLengthRef.current;
|
|
199
|
+
|
|
200
|
+
// Check if length decreased - this is the key detection
|
|
201
|
+
const lengthDecreased = currentLength < previousLength;
|
|
202
|
+
|
|
203
|
+
if (lengthDecreased && !isDeleting) {
|
|
204
|
+
// Data length decreased - start deletion batching
|
|
205
|
+
const snapshotLength = dataSnapshotRef.current.length;
|
|
206
|
+
|
|
207
|
+
// Data decrease detected (logging removed for performance)
|
|
208
|
+
|
|
209
|
+
// If snapshot is larger, it's valid (has pre-deletion state)
|
|
210
|
+
// Otherwise, update it to preserve current state
|
|
211
|
+
if (snapshotLength <= currentLength) {
|
|
212
|
+
dataSnapshotRef.current = [...data];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setIsDeleting(true);
|
|
216
|
+
|
|
217
|
+
// Set timeout to reset after batching
|
|
218
|
+
if (deletionTimeoutRef.current) {
|
|
219
|
+
clearTimeout(deletionTimeoutRef.current);
|
|
220
|
+
}
|
|
221
|
+
deletionTimeoutRef.current = setTimeout(() => {
|
|
222
|
+
setIsDeleting(false);
|
|
223
|
+
dataSnapshotRef.current = data;
|
|
224
|
+
previousDataLengthRef.current = data.length;
|
|
225
|
+
// Batching complete (logging removed for performance)
|
|
226
|
+
}, 150);
|
|
227
|
+
|
|
228
|
+
// Update previous length AFTER setting up batching
|
|
229
|
+
previousDataLengthRef.current = currentLength;
|
|
230
|
+
} else if (!lengthDecreased && !isDeleting) {
|
|
231
|
+
// No deletion - update snapshot and previous length normally
|
|
232
|
+
dataSnapshotRef.current = data;
|
|
233
|
+
previousDataLengthRef.current = currentLength;
|
|
234
|
+
} else if (isDeleting) {
|
|
235
|
+
// Already deleting - just update previous length for next comparison
|
|
236
|
+
previousDataLengthRef.current = currentLength;
|
|
237
|
+
}
|
|
238
|
+
}, [data, isDeleting]);
|
|
239
|
+
|
|
240
|
+
// Keep data snapshot stable during deletions to prevent flashing
|
|
241
|
+
// Logging effect - runs after layout to show what happened
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
dataChangeCountRef.current += 1;
|
|
244
|
+
|
|
245
|
+
const previousLength = previousDataLengthRef.current;
|
|
246
|
+
const currentLength = data.length;
|
|
247
|
+
const snapshotLength = dataSnapshotRef.current.length;
|
|
248
|
+
const lengthDecreased = currentLength < previousLength;
|
|
249
|
+
|
|
250
|
+
// Data prop changed (logging removed for performance)
|
|
251
|
+
}, [data, isDeleting]);
|
|
252
|
+
|
|
253
|
+
// Use snapshot data during deletions to prevent flashing
|
|
254
|
+
// CRITICAL: If we're deleting and data length decreased, use snapshot to prevent flashing
|
|
255
|
+
const dataLengthChanged = data.length !== dataSnapshotRef.current.length;
|
|
256
|
+
const isDataDecreasing = data.length < dataSnapshotRef.current.length;
|
|
257
|
+
const effectiveData = (isDeleting && isDataDecreasing) ? dataSnapshotRef.current : data;
|
|
258
|
+
|
|
259
|
+
// Effective data tracking (logging removed for performance)
|
|
260
|
+
|
|
185
261
|
// ============================================================================
|
|
186
262
|
// ALL HOOKS MUST BE CALLED IN THE SAME ORDER EVERY RENDER
|
|
187
263
|
// ============================================================================
|
|
@@ -241,15 +317,38 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
241
317
|
return {};
|
|
242
318
|
}, [secureFeatures.columnVisibility, savedColumnVisibility]);
|
|
243
319
|
|
|
320
|
+
// Get location for route-based key derivation
|
|
321
|
+
let location: { pathname: string } | null = null;
|
|
322
|
+
try {
|
|
323
|
+
// useLocation may not be available if React Router is not set up
|
|
324
|
+
const routerLocation = useLocation();
|
|
325
|
+
location = routerLocation;
|
|
326
|
+
} catch {
|
|
327
|
+
// Router not available, location will be null
|
|
328
|
+
location = null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Extract column field names for sensitive field filtering
|
|
332
|
+
const columnFieldNames = useMemo(() => {
|
|
333
|
+
return columns
|
|
334
|
+
.map((col) => col.accessorKey || col.id)
|
|
335
|
+
.filter((key): key is string => Boolean(key));
|
|
336
|
+
}, [columns]);
|
|
337
|
+
|
|
244
338
|
// Use the centralized state management hook for ALL table state
|
|
245
339
|
// Note: 'actions' prop parameter is shadowed by destructuring, so we rename to stateActions
|
|
246
|
-
const { state, actions: stateActions } = useDataTableState<TData>({
|
|
340
|
+
const { state, actions: stateActions, clearDraft } = useDataTableState<TData>({
|
|
247
341
|
initialPageSize,
|
|
248
342
|
columnIds: effectiveColumnOrder,
|
|
249
343
|
initialRowSelection: selection || {},
|
|
250
344
|
onRowSelectionChange,
|
|
251
345
|
defaultSorting: defaultSorting || [],
|
|
252
|
-
defaultGrouping: defaultGrouping || []
|
|
346
|
+
defaultGrouping: defaultGrouping || [],
|
|
347
|
+
// Persistence props
|
|
348
|
+
rbacPageId: rbac.pageId,
|
|
349
|
+
title,
|
|
350
|
+
location,
|
|
351
|
+
columnFieldNames,
|
|
253
352
|
});
|
|
254
353
|
|
|
255
354
|
// Apply saved visibility to state if available
|
|
@@ -344,7 +443,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
344
443
|
hierarchicalState,
|
|
345
444
|
hierarchicalValidation,
|
|
346
445
|
} = useDataTableDataPipeline<TData>({
|
|
347
|
-
data,
|
|
446
|
+
data: effectiveData,
|
|
348
447
|
features: secureFeatures,
|
|
349
448
|
hierarchical,
|
|
350
449
|
sorting: state.sorting,
|
|
@@ -357,6 +456,34 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
357
456
|
logger.error('Hierarchical data validation failed:', hierarchicalValidation.errors);
|
|
358
457
|
}
|
|
359
458
|
}, [hierarchicalValidation, logger]);
|
|
459
|
+
|
|
460
|
+
// Final table data tracking (logging removed for performance)
|
|
461
|
+
|
|
462
|
+
// Cleanup deletion timeout on unmount
|
|
463
|
+
useEffect(() => {
|
|
464
|
+
return () => {
|
|
465
|
+
if (deletionTimeoutRef.current) {
|
|
466
|
+
clearTimeout(deletionTimeoutRef.current);
|
|
467
|
+
}
|
|
468
|
+
pendingDeletionsRef.current.clear();
|
|
469
|
+
};
|
|
470
|
+
}, []);
|
|
471
|
+
|
|
472
|
+
// Update data snapshot when data changes and we're not deleting
|
|
473
|
+
useEffect(() => {
|
|
474
|
+
if (!isDeleting && data !== dataSnapshotRef.current) {
|
|
475
|
+
dataSnapshotRef.current = data;
|
|
476
|
+
}
|
|
477
|
+
}, [data, isDeleting]);
|
|
478
|
+
|
|
479
|
+
// Cleanup deletion timeout on unmount
|
|
480
|
+
useEffect(() => {
|
|
481
|
+
return () => {
|
|
482
|
+
if (deletionTimeoutRef.current) {
|
|
483
|
+
clearTimeout(deletionTimeoutRef.current);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}, []);
|
|
360
487
|
|
|
361
488
|
const {
|
|
362
489
|
columnOrder: savedColumnOrder,
|
|
@@ -563,19 +690,24 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
563
690
|
// RBAC VALIDATION AND SECURE CONFIGURATION - ALWAYS call these hooks
|
|
564
691
|
// ============================================================================
|
|
565
692
|
|
|
693
|
+
// Wrap handlers - persistence is handled automatically via useDataTableState
|
|
694
|
+
// The state actions (clearCreationData, clearEditing) will automatically update persisted state
|
|
695
|
+
const wrappedOnCreateRow = onCreateRow;
|
|
696
|
+
const wrappedOnEditRow = onEditRow;
|
|
697
|
+
|
|
566
698
|
// MANDATORY: Handlers are automatically secured
|
|
567
699
|
const secureHandlers = useMemo(() => {
|
|
568
700
|
const handlers = {
|
|
569
|
-
onEditRow: permissions.canUpdate.can ?
|
|
701
|
+
onEditRow: permissions.canUpdate.can ? wrappedOnEditRow : undefined,
|
|
570
702
|
onDeleteRow: permissions.canDelete.can ? onDeleteRow : undefined,
|
|
571
|
-
onCreateRow: permissions.canCreate.can ?
|
|
703
|
+
onCreateRow: permissions.canCreate.can ? wrappedOnCreateRow : undefined,
|
|
572
704
|
onImport: permissions.canImport.can ? onImport : undefined,
|
|
573
705
|
onExport: permissions.canExport.can ? onExport : undefined,
|
|
574
706
|
onDeleteSelected: permissions.canDelete.can ? onDeleteSelected : undefined,
|
|
575
707
|
};
|
|
576
708
|
|
|
577
709
|
return handlers;
|
|
578
|
-
}, [permissions.canUpdate.can, permissions.canDelete.can, permissions.canCreate.can, permissions.canImport.can, permissions.canExport.can,
|
|
710
|
+
}, [permissions.canUpdate.can, permissions.canDelete.can, permissions.canCreate.can, permissions.canImport.can, permissions.canExport.can, wrappedOnEditRow, onDeleteRow, wrappedOnCreateRow, onImport, onExport, onDeleteSelected, secureFeatures.creation]);
|
|
579
711
|
|
|
580
712
|
// MANDATORY: Process actions with RBAC checks
|
|
581
713
|
const effectiveActions = useMemo(() => {
|
|
@@ -617,19 +749,60 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
617
749
|
});
|
|
618
750
|
return;
|
|
619
751
|
}
|
|
752
|
+
|
|
753
|
+
// Get row ID for tracking - use current data, not effectiveData
|
|
754
|
+
const rowIndex = data.findIndex(r => r === row);
|
|
755
|
+
const rowId = resolvedGetRowId(row, rowIndex >= 0 ? rowIndex : 0);
|
|
756
|
+
|
|
757
|
+
// Mark deletion as in progress and track this deletion
|
|
758
|
+
// CRITICAL: Set isDeleting and snapshot BEFORE calling the handler
|
|
759
|
+
// This prevents the parent's data update from causing a flash
|
|
760
|
+
if (!isDeleting) {
|
|
761
|
+
// Snapshot current data BEFORE deletion
|
|
762
|
+
dataSnapshotRef.current = [...data]; // Create a copy to prevent reference issues
|
|
763
|
+
setIsDeleting(true);
|
|
764
|
+
}
|
|
765
|
+
pendingDeletionsRef.current.add(rowId);
|
|
766
|
+
|
|
767
|
+
// Clear any existing timeout
|
|
768
|
+
if (deletionTimeoutRef.current) {
|
|
769
|
+
clearTimeout(deletionTimeoutRef.current);
|
|
770
|
+
}
|
|
771
|
+
|
|
620
772
|
try {
|
|
773
|
+
// Call the delete handler - this may update the parent's data prop
|
|
774
|
+
const deleteStartTime = Date.now();
|
|
621
775
|
const result = secureHandlers.onDeleteRow!(row) as any;
|
|
622
776
|
// Handle async operations
|
|
623
777
|
if (result !== undefined && result !== null && typeof result === 'object' && typeof result.then === 'function') {
|
|
624
778
|
await result;
|
|
625
779
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
780
|
+
// Remove from pending deletions
|
|
781
|
+
pendingDeletionsRef.current.delete(rowId);
|
|
782
|
+
|
|
783
|
+
// Reset deletion state after a delay to allow batching
|
|
784
|
+
// This prevents the table from refreshing after each individual deletion
|
|
785
|
+
deletionTimeoutRef.current = setTimeout(() => {
|
|
786
|
+
// Only reset if all deletions are complete
|
|
787
|
+
if (pendingDeletionsRef.current.size === 0) {
|
|
788
|
+
setIsDeleting(false);
|
|
789
|
+
// Update snapshot to latest data
|
|
790
|
+
dataSnapshotRef.current = data;
|
|
791
|
+
toast({
|
|
792
|
+
title: "Delete Successful",
|
|
793
|
+
description: "Row deleted successfully",
|
|
794
|
+
variant: "default"
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}, 150); // Delay to batch rapid deletions
|
|
798
|
+
|
|
631
799
|
} catch (error) {
|
|
632
800
|
logger.error('Delete error:', error);
|
|
801
|
+
pendingDeletionsRef.current.delete(rowId);
|
|
802
|
+
if (pendingDeletionsRef.current.size === 0) {
|
|
803
|
+
setIsDeleting(false);
|
|
804
|
+
dataSnapshotRef.current = data;
|
|
805
|
+
}
|
|
633
806
|
toast({
|
|
634
807
|
title: "Delete Failed",
|
|
635
808
|
description: error instanceof Error ? error.message : 'Failed to delete row',
|
|
@@ -645,7 +818,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
645
818
|
}
|
|
646
819
|
|
|
647
820
|
return result;
|
|
648
|
-
}, [actions, secureFeatures, permissions, secureHandlers, resolvedGetRowId, stateActions,
|
|
821
|
+
}, [actions, secureFeatures, permissions, secureHandlers, resolvedGetRowId, stateActions, effectiveData, isDeleting, pendingDeletionsRef, deletionTimeoutRef, dataSnapshotRef]);
|
|
649
822
|
|
|
650
823
|
// Normalize columnOrder for useTableColumns: ensure 'select' is always first
|
|
651
824
|
const normalizedColumnOrderForColumns = useMemo(() => {
|