@jmruthers/pace-core 0.6.1 → 0.6.2
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 +43 -10
- package/cursor-rules/00-pace-core-compliance.mdc +18 -91
- package/cursor-rules/01-standards-compliance.mdc +16 -47
- package/cursor-rules/02-project-structure.mdc +4 -4
- package/cursor-rules/03-solid-principles.mdc +45 -164
- package/cursor-rules/04-testing-standards.mdc +22 -69
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
- package/cursor-rules/06-code-quality.mdc +42 -125
- package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +18 -0
- package/cursor-rules/README.md +2 -1
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
- package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
- package/dist/{DataTable-DQ7RSOHE.js → DataTable-TPTKCX4D.js} +10 -9
- package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +356 -111
- package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
- package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
- package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
- package/dist/chunk-24UVZUZG.js.map +1 -0
- package/dist/{chunk-4N5C5XZU.js → chunk-2UOI2FG5.js} +4 -4
- package/dist/chunk-2UOI2FG5.js.map +1 -0
- package/dist/{chunk-T33XF5ZC.js → chunk-3XC4CPTD.js} +4317 -3963
- package/dist/chunk-3XC4CPTD.js.map +1 -0
- package/dist/{chunk-4ZC4GX36.js → chunk-6J4GEEJR.js} +172 -45
- package/dist/chunk-6J4GEEJR.js.map +1 -0
- package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-BYFSK72L.js → chunk-EHMR7VYL.js} +4 -4
- package/dist/chunk-EHMR7VYL.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
- package/dist/chunk-F2IMUDXZ.js.map +1 -0
- package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-3XTALGJF.js → chunk-MMZ7JXPU.js} +60 -223
- package/dist/chunk-MMZ7JXPU.js.map +1 -0
- package/dist/{chunk-GLK6VM3F.js → chunk-NECFR5MM.js} +254 -170
- package/dist/chunk-NECFR5MM.js.map +1 -0
- package/dist/{chunk-JBKQ3SAO.js → chunk-SFZUDBL5.js} +40 -4
- package/dist/chunk-SFZUDBL5.js.map +1 -0
- package/dist/{chunk-XM25TVIE.js → chunk-XWQCNGTQ.js} +724 -363
- package/dist/chunk-XWQCNGTQ.js.map +1 -0
- package/dist/components.d.ts +5 -5
- package/dist/components.js +14 -11
- package/dist/components.js.map +1 -1
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +55 -122
- package/dist/hooks.js +8 -12
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +60 -13
- package/dist/index.js +19 -19
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +145 -114
- package/dist/rbac/index.js +8 -11
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +1 -1
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +31 -1
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +14 -14
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +7 -1
- package/docs/api/classes/ColumnFactory.md +8 -8
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +18 -15
- 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 +4 -4
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +9 -2
- package/docs/api/interfaces/ButtonProps.md +7 -4
- package/docs/api/interfaces/CalendarProps.md +8 -5
- package/docs/api/interfaces/CardProps.md +8 -5
- 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 +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +24 -21
- package/docs/api/interfaces/DataTableColumn.md +31 -31
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
- package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +8 -8
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +26 -23
- package/docs/api/interfaces/FooterProps.md +10 -8
- package/docs/api/interfaces/FormFieldProps.md +10 -10
- 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 +7 -4
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +14 -11
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +11 -11
- package/docs/api/interfaces/NavigationMenuProps.md +15 -15
- 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 +30 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
- package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
- package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
- package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
- 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 +3 -3
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +4 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +14 -11
- package/docs/api/interfaces/UserMenuProps.md +8 -6
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +575 -634
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +2 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +2 -24
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/standards/README.md +1 -0
- package/package.json +2 -1
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +714 -687
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +454 -0
- package/scripts/audit/core/checks/documentation.cjs +203 -0
- package/scripts/audit/core/checks/environment.cjs +128 -0
- package/scripts/audit/core/checks/error-handling.cjs +299 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +61 -936
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +14 -0
- package/src/components/Button/Button.tsx +22 -0
- package/src/components/Calendar/Calendar.tsx +8 -2
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/DataTable.tsx +38 -4
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +196 -554
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +8 -0
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +8 -0
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +61 -849
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- package/src/components/DataTable/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/EventSelector/EventSelector.tsx +3 -0
- package/src/components/FileDisplay/FileDisplay.tsx +32 -18
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +14 -11
- package/src/components/Form/Form.tsx +1 -0
- package/src/components/Header/Header.tsx +21 -10
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +8 -4
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoginForm/LoginForm.tsx +4 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +4 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +32 -11
- package/src/components/PaceAppLayout/test-setup.tsx +1 -2
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +80 -434
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Toast/Toast.tsx +4 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -2
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +21 -18
- package/src/components/index.ts +2 -2
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +4 -0
- package/src/hooks/public/usePublicEventLogo.ts +4 -0
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +4 -0
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +4 -0
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +14 -0
- package/src/hooks/useFocusTrap.ts +3 -0
- package/src/hooks/useInactivityTracker.ts +3 -0
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +7 -0
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +1 -1
- package/src/index.ts +2 -1
- package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
- package/src/providers/services/AuthServiceProvider.tsx +18 -0
- package/src/providers/services/EventServiceProvider.tsx +18 -0
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +36 -0
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +2 -2
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +14 -5
- package/src/rbac/api.ts +100 -67
- package/src/rbac/components/NavigationProvider.tsx +4 -1
- package/src/rbac/components/PagePermissionGuard.tsx +157 -17
- package/src/rbac/components/RoleBasedRouter.tsx +5 -1
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +20 -5
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +347 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +71 -64
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +33 -13
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.ts +200 -61
- package/src/rbac/types.ts +8 -0
- package/src/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/dist/chunk-3QRJFVBR.js.map +0 -1
- package/dist/chunk-3XTALGJF.js.map +0 -1
- package/dist/chunk-4N5C5XZU.js.map +0 -1
- package/dist/chunk-4ZC4GX36.js.map +0 -1
- package/dist/chunk-BYFSK72L.js.map +0 -1
- package/dist/chunk-EXUD6RNJ.js +0 -451
- package/dist/chunk-EXUD6RNJ.js.map +0 -1
- package/dist/chunk-GLK6VM3F.js.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-JBKQ3SAO.js.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-LXQLPRQ2.js.map +0 -1
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-T33XF5ZC.js.map +0 -1
- package/dist/chunk-XM25TVIE.js.map +0 -1
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -681
- /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-TPTKCX4D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
- /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
|
@@ -14,429 +14,35 @@
|
|
|
14
14
|
|
|
15
15
|
import * as React from "react";
|
|
16
16
|
import { Search, X, ChevronDown, Check } from "lucide-react";
|
|
17
|
-
import { Button
|
|
17
|
+
import { Button } from "../Button/Button";
|
|
18
18
|
import { cn } from "../../utils/core/cn";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
setOpen: (open: boolean) => void;
|
|
34
|
-
setSelectedText: (text: string) => void;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface SelectEventHandlers {
|
|
38
|
-
onValueChange?: (value: string) => void;
|
|
39
|
-
onOpenChange?: (open: boolean) => void;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface UseSelectStateProps {
|
|
43
|
-
value?: string;
|
|
44
|
-
defaultValue?: string;
|
|
45
|
-
selectedText?: string;
|
|
46
|
-
open?: boolean;
|
|
47
|
-
defaultOpen?: boolean;
|
|
48
|
-
disabled?: boolean;
|
|
49
|
-
onValueChange?: (value: string) => void;
|
|
50
|
-
onOpenChange?: (open: boolean) => void;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface UseSelectEventsProps {
|
|
54
|
-
state: SelectState;
|
|
55
|
-
actions: SelectActions;
|
|
56
|
-
selectRef: React.RefObject<HTMLElement | null>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface UseSelectSearchProps {
|
|
60
|
-
children: React.ReactNode;
|
|
61
|
-
searchable?: boolean;
|
|
62
|
-
searchTerm?: string;
|
|
63
|
-
onSearchChange?: (term: string) => void;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface SelectContextValue extends SelectState {
|
|
67
|
-
actions: SelectActions;
|
|
68
|
-
registerItem?: (value: string, text: string) => void;
|
|
69
|
-
unregisterItem?: (value: string) => void;
|
|
70
|
-
direction?: 'up' | 'down';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface SelectProps extends Omit<React.HTMLAttributes<HTMLFormElement>, 'onChange' | 'onKeyDown' | 'onFocus' | 'onBlur'> {
|
|
74
|
-
children: React.ReactNode;
|
|
75
|
-
className?: string;
|
|
76
|
-
direction?: 'up' | 'down';
|
|
77
|
-
// State props are in UseSelectStateProps (via & intersection)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface SelectTriggerProps extends Omit<ButtonProps, 'onClick' | 'onKeyDown'> {
|
|
81
|
-
children: React.ReactNode;
|
|
82
|
-
asChild?: boolean;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface SelectValueProps {
|
|
86
|
-
placeholder?: string;
|
|
87
|
-
children?: React.ReactNode;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface SelectContentProps {
|
|
91
|
-
children: React.ReactNode;
|
|
92
|
-
className?: string;
|
|
93
|
-
searchable?: boolean;
|
|
94
|
-
searchPlaceholder?: string;
|
|
95
|
-
maxHeight?: string;
|
|
96
|
-
style?: React.CSSProperties;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface SelectItemProps {
|
|
100
|
-
value: string;
|
|
101
|
-
children: React.ReactNode;
|
|
102
|
-
disabled?: boolean;
|
|
103
|
-
className?: string;
|
|
104
|
-
onClick?: (e: React.MouseEvent) => void;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ============================================================================
|
|
108
|
-
// HOOKS
|
|
109
|
-
// ============================================================================
|
|
110
|
-
|
|
111
|
-
// State Management Hook
|
|
112
|
-
export const useSelectState = ({
|
|
113
|
-
value: controlledValue,
|
|
114
|
-
defaultValue = '',
|
|
115
|
-
selectedText: controlledSelectedText,
|
|
116
|
-
open: controlledOpen,
|
|
117
|
-
defaultOpen = false,
|
|
118
|
-
disabled = false,
|
|
119
|
-
onValueChange,
|
|
120
|
-
onOpenChange,
|
|
121
|
-
}: UseSelectStateProps) => {
|
|
122
|
-
// Internal state for uncontrolled mode
|
|
123
|
-
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
124
|
-
const [internalSelectedText, setInternalSelectedText] = React.useState('');
|
|
125
|
-
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
126
|
-
|
|
127
|
-
// Computed values (controlled vs uncontrolled)
|
|
128
|
-
const value = controlledValue !== undefined ? controlledValue : internalValue;
|
|
129
|
-
const selectedText = controlledSelectedText !== undefined ? controlledSelectedText : internalSelectedText;
|
|
130
|
-
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
|
|
131
|
-
|
|
132
|
-
// Actions
|
|
133
|
-
const setValue = React.useCallback((newValue: string, newText: string) => {
|
|
134
|
-
if (controlledValue === undefined) {
|
|
135
|
-
setInternalValue(newValue);
|
|
136
|
-
setInternalSelectedText(newText);
|
|
137
|
-
}
|
|
138
|
-
onValueChange?.(newValue);
|
|
139
|
-
|
|
140
|
-
// Close dropdown after selection (for uncontrolled mode)
|
|
141
|
-
if (controlledOpen === undefined) {
|
|
142
|
-
setInternalOpen(false);
|
|
143
|
-
}
|
|
144
|
-
onOpenChange?.(false);
|
|
145
|
-
}, [controlledValue, onValueChange, controlledOpen, onOpenChange]);
|
|
146
|
-
|
|
147
|
-
const setOpen = React.useCallback((newOpen: boolean) => {
|
|
148
|
-
if (disabled) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Close all other select dropdowns when opening this one
|
|
153
|
-
if (newOpen) {
|
|
154
|
-
const allTriggers = document.querySelectorAll('[data-testid="select-trigger"]');
|
|
155
|
-
allTriggers.forEach(trigger => {
|
|
156
|
-
if (trigger.getAttribute('aria-expanded') === 'true') {
|
|
157
|
-
const selectRoot = trigger.closest('[data-testid="select-root"]');
|
|
158
|
-
if (selectRoot) {
|
|
159
|
-
const closeEvent = new CustomEvent('closeSelect');
|
|
160
|
-
selectRoot.dispatchEvent(closeEvent);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (controlledOpen === undefined) {
|
|
167
|
-
setInternalOpen(newOpen);
|
|
168
|
-
}
|
|
169
|
-
onOpenChange?.(newOpen);
|
|
170
|
-
}, [controlledOpen, onOpenChange, disabled]);
|
|
171
|
-
|
|
172
|
-
const setSelectedText = React.useCallback((newText: string) => {
|
|
173
|
-
if (controlledSelectedText === undefined) {
|
|
174
|
-
setInternalSelectedText(newText);
|
|
175
|
-
}
|
|
176
|
-
}, [controlledSelectedText]);
|
|
177
|
-
|
|
178
|
-
const state: SelectState = {
|
|
179
|
-
value,
|
|
180
|
-
selectedText,
|
|
181
|
-
open,
|
|
182
|
-
disabled,
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const actions: SelectActions = {
|
|
186
|
-
setValue,
|
|
187
|
-
setOpen,
|
|
188
|
-
setSelectedText,
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
return { state, actions };
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// Event Handling Hook
|
|
195
|
-
export const useSelectEvents = ({ state, actions, selectRef }: UseSelectEventsProps) => {
|
|
196
|
-
const [isSelecting, setIsSelecting] = React.useState(false);
|
|
197
|
-
|
|
198
|
-
// Listen for close events from other selects
|
|
199
|
-
React.useEffect(() => {
|
|
200
|
-
const handleCloseSelect = () => {
|
|
201
|
-
actions.setOpen(false);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const selectElement = selectRef.current as HTMLElement;
|
|
205
|
-
if (selectElement) {
|
|
206
|
-
selectElement.addEventListener('closeSelect', handleCloseSelect);
|
|
207
|
-
return () => {
|
|
208
|
-
selectElement.removeEventListener('closeSelect', handleCloseSelect);
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}, [actions, selectRef]);
|
|
212
|
-
|
|
213
|
-
// Handle click outside to close dropdown
|
|
214
|
-
React.useEffect(() => {
|
|
215
|
-
if (!state.open) return;
|
|
216
|
-
|
|
217
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
218
|
-
const selectElement = selectRef.current;
|
|
219
|
-
if (!selectElement) return;
|
|
220
|
-
|
|
221
|
-
const target = event.target as Node;
|
|
222
|
-
|
|
223
|
-
// Close if clicking outside the select element
|
|
224
|
-
if (!selectElement.contains(target) && !isSelecting) {
|
|
225
|
-
actions.setOpen(false);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// Add a small delay to avoid closing immediately when opening
|
|
230
|
-
// The delay ensures the click event that opened the dropdown has fully processed
|
|
231
|
-
const timeoutId = setTimeout(() => {
|
|
232
|
-
document.addEventListener('click', handleClickOutside, true); // Use capture phase
|
|
233
|
-
}, 100); // Small delay to let the opening click complete
|
|
234
|
-
|
|
235
|
-
return () => {
|
|
236
|
-
clearTimeout(timeoutId);
|
|
237
|
-
document.removeEventListener('click', handleClickOutside, true);
|
|
238
|
-
};
|
|
239
|
-
}, [state.open, actions, selectRef, isSelecting]);
|
|
240
|
-
|
|
241
|
-
// Handle SelectItem mousedown events
|
|
242
|
-
React.useEffect(() => {
|
|
243
|
-
let timeoutId: NodeJS.Timeout | null = null;
|
|
244
|
-
let isMounted = true;
|
|
245
|
-
|
|
246
|
-
const handleSelectItemMouseDown = () => {
|
|
247
|
-
if (!isMounted) return;
|
|
248
|
-
|
|
249
|
-
setIsSelecting(true);
|
|
250
|
-
timeoutId = setTimeout(() => {
|
|
251
|
-
if (isMounted) {
|
|
252
|
-
setIsSelecting(false);
|
|
253
|
-
}
|
|
254
|
-
}, 150);
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
document.addEventListener('selectItemMouseDown', handleSelectItemMouseDown as EventListener);
|
|
258
|
-
return () => {
|
|
259
|
-
isMounted = false;
|
|
260
|
-
document.removeEventListener('selectItemMouseDown', handleSelectItemMouseDown as EventListener);
|
|
261
|
-
if (timeoutId) {
|
|
262
|
-
clearTimeout(timeoutId);
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
}, []);
|
|
266
|
-
|
|
267
|
-
return { isSelecting };
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
// Search Functionality Hook
|
|
271
|
-
export const useSelectSearch = ({
|
|
272
|
-
children,
|
|
273
|
-
searchable = false,
|
|
274
|
-
searchTerm: controlledSearchTerm,
|
|
275
|
-
onSearchChange
|
|
276
|
-
}: UseSelectSearchProps) => {
|
|
277
|
-
const [internalSearchTerm, setInternalSearchTerm] = React.useState('');
|
|
278
|
-
const [filteredChildren, setFilteredChildren] = React.useState<React.ReactNode>(children);
|
|
279
|
-
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
|
280
|
-
|
|
281
|
-
const searchTerm = controlledSearchTerm !== undefined ? controlledSearchTerm : internalSearchTerm;
|
|
282
|
-
|
|
283
|
-
const setSearchTerm = React.useCallback((term: string) => {
|
|
284
|
-
if (controlledSearchTerm === undefined) {
|
|
285
|
-
setInternalSearchTerm(term);
|
|
286
|
-
}
|
|
287
|
-
onSearchChange?.(term);
|
|
288
|
-
}, [controlledSearchTerm, onSearchChange]);
|
|
289
|
-
|
|
290
|
-
// Filter children based on search term
|
|
291
|
-
React.useEffect(() => {
|
|
292
|
-
if (!searchable || !searchTerm) {
|
|
293
|
-
setFilteredChildren(children);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const filterChildren = (nodes: React.ReactNode): React.ReactNode => {
|
|
298
|
-
return React.Children.map(nodes, (child) => {
|
|
299
|
-
if (!React.isValidElement(child)) return child;
|
|
300
|
-
|
|
301
|
-
// Check if this is a SelectItem by looking for the data-testid or value prop
|
|
302
|
-
const childProps = child.props as { 'data-testid'?: string; value?: unknown; children?: React.ReactNode; [key: string]: unknown };
|
|
303
|
-
const isSelectItem = childProps &&
|
|
304
|
-
(childProps['data-testid'] === 'select-item' ||
|
|
305
|
-
childProps.value !== undefined);
|
|
306
|
-
|
|
307
|
-
if (isSelectItem) {
|
|
308
|
-
const childText = React.Children.toArray(childProps.children).join(' ').toLowerCase();
|
|
309
|
-
const searchLower = searchTerm.toLowerCase();
|
|
310
|
-
|
|
311
|
-
if (childText.includes(searchLower)) {
|
|
312
|
-
return child;
|
|
313
|
-
}
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (childProps.children) {
|
|
318
|
-
const filteredChildChildren = filterChildren(childProps.children);
|
|
319
|
-
if (React.Children.count(filteredChildChildren) > 0) {
|
|
320
|
-
return React.cloneElement(child, {}, filteredChildChildren);
|
|
321
|
-
}
|
|
322
|
-
return null;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
return child;
|
|
326
|
-
});
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
const filtered = filterChildren(children);
|
|
330
|
-
setFilteredChildren(filtered);
|
|
331
|
-
}, [children, searchTerm, searchable]);
|
|
332
|
-
|
|
333
|
-
return {
|
|
334
|
-
searchTerm,
|
|
335
|
-
setSearchTerm,
|
|
336
|
-
filteredChildren,
|
|
337
|
-
searchInputRef,
|
|
338
|
-
};
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
// ============================================================================
|
|
342
|
-
// CONTEXT
|
|
343
|
-
// ============================================================================
|
|
344
|
-
|
|
345
|
-
const SelectContext = React.createContext<SelectContextValue | null>(null);
|
|
346
|
-
|
|
347
|
-
const useSelectContext = () => {
|
|
348
|
-
const context = React.useContext(SelectContext);
|
|
349
|
-
if (!context) {
|
|
350
|
-
throw new Error('Select components must be used within a Select');
|
|
351
|
-
}
|
|
352
|
-
return context;
|
|
353
|
-
};
|
|
19
|
+
import { SelectContext, useSelectContext } from "./context";
|
|
20
|
+
import { useSelectEvents } from "./hooks/useSelectEvents";
|
|
21
|
+
import { useSelectSearch } from "./hooks/useSelectSearch";
|
|
22
|
+
import { useSelectState } from "./hooks/useSelectState";
|
|
23
|
+
import type {
|
|
24
|
+
SelectContentProps,
|
|
25
|
+
SelectContextValue,
|
|
26
|
+
SelectItemProps,
|
|
27
|
+
SelectProps,
|
|
28
|
+
SelectTriggerProps,
|
|
29
|
+
SelectValueProps,
|
|
30
|
+
UseSelectStateProps,
|
|
31
|
+
} from "./types";
|
|
32
|
+
import { getTextContent } from "./utils/text";
|
|
354
33
|
|
|
355
34
|
// ============================================================================
|
|
356
35
|
// ROOT COMPONENT
|
|
357
36
|
// ============================================================================
|
|
358
37
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (Array.isArray(children)) {
|
|
370
|
-
return children.map(getTextContent).join('');
|
|
371
|
-
}
|
|
372
|
-
return '';
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
// Helper function to find SelectItem by value in React children tree
|
|
376
|
-
const findSelectItemText = (children: React.ReactNode, targetValue: string): string | null => {
|
|
377
|
-
if (!children) return null;
|
|
378
|
-
|
|
379
|
-
// Handle arrays directly (common when using .map())
|
|
380
|
-
if (Array.isArray(children)) {
|
|
381
|
-
for (const child of children) {
|
|
382
|
-
if (!child) continue;
|
|
383
|
-
const found = findSelectItemText(child, targetValue);
|
|
384
|
-
if (found) return found;
|
|
385
|
-
}
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Handle React elements directly if it's a single element
|
|
390
|
-
if (React.isValidElement(children)) {
|
|
391
|
-
const props = (children as React.ReactElement<{ value?: string; children?: React.ReactNode }>).props;
|
|
392
|
-
|
|
393
|
-
// Check if this is a SelectItem
|
|
394
|
-
if (props && props.value !== undefined && typeof props.value === 'string' && props.value === targetValue) {
|
|
395
|
-
const text = getTextContent(props.children);
|
|
396
|
-
if (text && text.trim()) {
|
|
397
|
-
return text.trim();
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Recurse into children
|
|
402
|
-
if (props && props.children !== undefined && props.children !== null) {
|
|
403
|
-
const found = findSelectItemText(props.children, targetValue);
|
|
404
|
-
if (found) return found;
|
|
405
|
-
}
|
|
406
|
-
return null;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Handle multiple children using React.Children utilities
|
|
410
|
-
const childrenArray = React.Children.toArray(children);
|
|
411
|
-
|
|
412
|
-
for (const child of childrenArray) {
|
|
413
|
-
if (!React.isValidElement(child)) continue;
|
|
414
|
-
|
|
415
|
-
const props = (child as React.ReactElement<{ value?: string; children?: React.ReactNode }>).props;
|
|
416
|
-
|
|
417
|
-
// Check if this is a SelectItem by checking for value prop
|
|
418
|
-
// SelectItem components always have a value prop
|
|
419
|
-
if (props && props.value !== undefined && typeof props.value === 'string' && props.value === targetValue) {
|
|
420
|
-
const text = getTextContent(props.children);
|
|
421
|
-
if (text && text.trim()) {
|
|
422
|
-
return text.trim();
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Recursively search in children
|
|
427
|
-
// Need to check both props.children and handle null/undefined
|
|
428
|
-
if (props) {
|
|
429
|
-
const childChildren = props.children;
|
|
430
|
-
if (childChildren !== undefined && childChildren !== null) {
|
|
431
|
-
const found = findSelectItemText(childChildren, targetValue);
|
|
432
|
-
if (found) return found;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return null;
|
|
438
|
-
};
|
|
439
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Select component root.
|
|
40
|
+
* Provides select dropdown functionality with search, keyboard navigation, and accessibility.
|
|
41
|
+
*
|
|
42
|
+
* @param props - Select configuration
|
|
43
|
+
* @param ref - Forwarded ref to the form element
|
|
44
|
+
* @returns The rendered select component
|
|
45
|
+
*/
|
|
440
46
|
export const Select = React.forwardRef<HTMLFormElement, SelectProps & UseSelectStateProps>(
|
|
441
47
|
({
|
|
442
48
|
children,
|
|
@@ -618,6 +224,14 @@ Select.displayName = "Select";
|
|
|
618
224
|
// TRIGGER COMPONENT
|
|
619
225
|
// ============================================================================
|
|
620
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Select trigger button component.
|
|
229
|
+
* Opens/closes the select dropdown and displays the selected value.
|
|
230
|
+
*
|
|
231
|
+
* @param props - Select trigger configuration
|
|
232
|
+
* @param ref - Forwarded ref to the button element
|
|
233
|
+
* @returns The rendered select trigger
|
|
234
|
+
*/
|
|
621
235
|
export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
|
|
622
236
|
({ children, className, variant = "outline", size = "default", asChild = false, ...props }, ref) => {
|
|
623
237
|
const { open, disabled, value, actions, direction = 'down' } = useSelectContext();
|
|
@@ -784,6 +398,14 @@ SelectTrigger.displayName = "SelectTrigger";
|
|
|
784
398
|
// VALUE COMPONENT
|
|
785
399
|
// ============================================================================
|
|
786
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Select value display component.
|
|
403
|
+
* Shows the selected value or placeholder text.
|
|
404
|
+
*
|
|
405
|
+
* @param props - Select value configuration
|
|
406
|
+
* @param ref - Forwarded ref to the span element
|
|
407
|
+
* @returns The rendered select value display
|
|
408
|
+
*/
|
|
787
409
|
export const SelectValue = React.forwardRef<HTMLSpanElement, SelectValueProps>(
|
|
788
410
|
({ placeholder = "Select an option...", children }, ref) => {
|
|
789
411
|
const { selectedText } = useSelectContext();
|
|
@@ -806,6 +428,14 @@ SelectValue.displayName = "SelectValue";
|
|
|
806
428
|
// CONTENT COMPONENT
|
|
807
429
|
// ============================================================================
|
|
808
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Select content/dropdown component.
|
|
433
|
+
* Contains the list of selectable options.
|
|
434
|
+
*
|
|
435
|
+
* @param props - Select content configuration
|
|
436
|
+
* @param ref - Forwarded ref to the list element
|
|
437
|
+
* @returns The rendered select content
|
|
438
|
+
*/
|
|
809
439
|
export const SelectContent = React.forwardRef<HTMLUListElement, SelectContentProps>(
|
|
810
440
|
({
|
|
811
441
|
children,
|
|
@@ -914,27 +544,19 @@ SelectContent.displayName = "SelectContent";
|
|
|
914
544
|
// ITEM COMPONENT
|
|
915
545
|
// ============================================================================
|
|
916
546
|
|
|
547
|
+
/**
|
|
548
|
+
* Select item component.
|
|
549
|
+
* Represents a single selectable option in the dropdown.
|
|
550
|
+
*
|
|
551
|
+
* @param props - Select item configuration
|
|
552
|
+
* @param ref - Forwarded ref to the list item element
|
|
553
|
+
* @returns The rendered select item
|
|
554
|
+
*/
|
|
917
555
|
export const SelectItem = React.forwardRef<HTMLLIElement, SelectItemProps>(
|
|
918
556
|
({ value, children, disabled = false, className, onClick }, ref) => {
|
|
919
557
|
const { value: selectedValue, actions, registerItem, unregisterItem } = useSelectContext();
|
|
920
558
|
const isSelected = selectedValue === value;
|
|
921
|
-
|
|
922
|
-
// Extract text content from children for display
|
|
923
|
-
const getTextContent = (children: React.ReactNode): string => {
|
|
924
|
-
if (typeof children === 'string') return children;
|
|
925
|
-
if (typeof children === 'number') return children.toString();
|
|
926
|
-
if (React.isValidElement(children)) {
|
|
927
|
-
const props = children.props as { children?: React.ReactNode; [key: string]: unknown };
|
|
928
|
-
if (props.children) {
|
|
929
|
-
return getTextContent(props.children);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
if (Array.isArray(children)) {
|
|
933
|
-
return children.map(getTextContent).join('');
|
|
934
|
-
}
|
|
935
|
-
return '';
|
|
936
|
-
};
|
|
937
|
-
|
|
559
|
+
|
|
938
560
|
const itemText = getTextContent(children);
|
|
939
561
|
|
|
940
562
|
// Register this item when it mounts or text changes
|
|
@@ -1025,6 +647,14 @@ SelectItem.displayName = "SelectItem";
|
|
|
1025
647
|
// ADDITIONAL COMPONENTS (for backward compatibility)
|
|
1026
648
|
// ============================================================================
|
|
1027
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Select group component.
|
|
652
|
+
* Groups related select items together.
|
|
653
|
+
*
|
|
654
|
+
* @param props - Select group configuration
|
|
655
|
+
* @param ref - Forwarded ref to the div element
|
|
656
|
+
* @returns The rendered select group
|
|
657
|
+
*/
|
|
1028
658
|
export const SelectGroup = React.forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string }>(
|
|
1029
659
|
({ children, className }, ref) => {
|
|
1030
660
|
return (
|
|
@@ -1036,6 +666,14 @@ export const SelectGroup = React.forwardRef<HTMLDivElement, { children: React.Re
|
|
|
1036
666
|
);
|
|
1037
667
|
SelectGroup.displayName = "SelectGroup";
|
|
1038
668
|
|
|
669
|
+
/**
|
|
670
|
+
* Select label component.
|
|
671
|
+
* Provides a label for a group of select items.
|
|
672
|
+
*
|
|
673
|
+
* @param props - Select label configuration
|
|
674
|
+
* @param ref - Forwarded ref to the div element
|
|
675
|
+
* @returns The rendered select label
|
|
676
|
+
*/
|
|
1039
677
|
export const SelectLabel = React.forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string }>(
|
|
1040
678
|
({ children, className }, ref) => {
|
|
1041
679
|
return (
|
|
@@ -1047,6 +685,14 @@ export const SelectLabel = React.forwardRef<HTMLDivElement, { children: React.Re
|
|
|
1047
685
|
);
|
|
1048
686
|
SelectLabel.displayName = "SelectLabel";
|
|
1049
687
|
|
|
688
|
+
/**
|
|
689
|
+
* Select separator component.
|
|
690
|
+
* Provides visual separation between groups of select items.
|
|
691
|
+
*
|
|
692
|
+
* @param props - Select separator configuration
|
|
693
|
+
* @param ref - Forwarded ref to the div element
|
|
694
|
+
* @returns The rendered select separator
|
|
695
|
+
*/
|
|
1050
696
|
export const SelectSeparator = React.forwardRef<HTMLDivElement, { className?: string }>(
|
|
1051
697
|
({ className }, ref) => {
|
|
1052
698
|
return (
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SelectContextValue } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Select context for sharing state between Select components.
|
|
6
|
+
*/
|
|
7
|
+
export const SelectContext =
|
|
8
|
+
React.createContext<SelectContextValue | null>(null);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hook to access Select context.
|
|
12
|
+
* Must be used within a Select component.
|
|
13
|
+
*
|
|
14
|
+
* @returns Select context value
|
|
15
|
+
* @throws Error if used outside Select component
|
|
16
|
+
*/
|
|
17
|
+
export const useSelectContext = () => {
|
|
18
|
+
const context = React.useContext(SelectContext);
|
|
19
|
+
if (!context) {
|
|
20
|
+
throw new Error("Select components must be used within a Select");
|
|
21
|
+
}
|
|
22
|
+
return context;
|
|
23
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { UseSelectEventsProps } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook for managing Select event handlers.
|
|
6
|
+
* Handles click outside, keyboard navigation, and focus management.
|
|
7
|
+
*
|
|
8
|
+
* @param props - Select events configuration
|
|
9
|
+
*/
|
|
10
|
+
export const useSelectEvents = ({
|
|
11
|
+
state,
|
|
12
|
+
actions,
|
|
13
|
+
selectRef,
|
|
14
|
+
}: UseSelectEventsProps) => {
|
|
15
|
+
const [isSelecting, setIsSelecting] = React.useState(false);
|
|
16
|
+
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
const handleCloseSelect = () => {
|
|
19
|
+
actions.setOpen(false);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const selectElement = selectRef.current as HTMLElement;
|
|
23
|
+
if (selectElement) {
|
|
24
|
+
selectElement.addEventListener("closeSelect", handleCloseSelect);
|
|
25
|
+
return () => {
|
|
26
|
+
selectElement.removeEventListener("closeSelect", handleCloseSelect);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}, [actions, selectRef]);
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (!state.open) return;
|
|
33
|
+
|
|
34
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
35
|
+
const selectElement = selectRef.current;
|
|
36
|
+
if (!selectElement) return;
|
|
37
|
+
|
|
38
|
+
const target = event.target as Node;
|
|
39
|
+
|
|
40
|
+
if (!selectElement.contains(target) && !isSelecting) {
|
|
41
|
+
actions.setOpen(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const timeoutId = setTimeout(() => {
|
|
46
|
+
document.addEventListener("click", handleClickOutside, true);
|
|
47
|
+
}, 100);
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
document.removeEventListener("click", handleClickOutside, true);
|
|
52
|
+
};
|
|
53
|
+
}, [state.open, actions, selectRef, isSelecting]);
|
|
54
|
+
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
let timeoutId: NodeJS.Timeout | null = null;
|
|
57
|
+
let isMounted = true;
|
|
58
|
+
|
|
59
|
+
const handleSelectItemMouseDown = () => {
|
|
60
|
+
if (!isMounted) return;
|
|
61
|
+
|
|
62
|
+
setIsSelecting(true);
|
|
63
|
+
timeoutId = setTimeout(() => {
|
|
64
|
+
if (isMounted) {
|
|
65
|
+
setIsSelecting(false);
|
|
66
|
+
}
|
|
67
|
+
}, 150);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
document.addEventListener(
|
|
71
|
+
"selectItemMouseDown",
|
|
72
|
+
handleSelectItemMouseDown as EventListener
|
|
73
|
+
);
|
|
74
|
+
return () => {
|
|
75
|
+
isMounted = false;
|
|
76
|
+
document.removeEventListener(
|
|
77
|
+
"selectItemMouseDown",
|
|
78
|
+
handleSelectItemMouseDown as EventListener
|
|
79
|
+
);
|
|
80
|
+
if (timeoutId) {
|
|
81
|
+
clearTimeout(timeoutId);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
return { isSelecting };
|
|
87
|
+
};
|