@jmruthers/pace-core 0.5.184 → 0.5.186

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +60 -1
  3. package/core-usage-manifest.json +312 -0
  4. package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
  5. package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
  6. package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +23 -10
  7. package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
  8. package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
  9. package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
  10. package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
  11. package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
  12. package/dist/{chunk-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
  13. package/dist/chunk-DAGICKHT.js.map +1 -0
  14. package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
  15. package/dist/chunk-FXFJRTKI.js.map +1 -0
  16. package/dist/{chunk-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
  17. package/dist/chunk-GRIQLQ52.js.map +1 -0
  18. package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
  19. package/dist/chunk-HDCUMOOI.js.map +1 -0
  20. package/dist/chunk-HESYZWZW.js +388 -0
  21. package/dist/chunk-HESYZWZW.js.map +1 -0
  22. package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
  23. package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
  24. package/dist/{chunk-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
  25. package/dist/chunk-OALXJH4Y.js.map +1 -0
  26. package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
  27. package/dist/chunk-TC7D3CR3.js.map +1 -0
  28. package/dist/chunk-THRPYOFK.js +215 -0
  29. package/dist/chunk-THRPYOFK.js.map +1 -0
  30. package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
  31. package/dist/chunk-U6WNSFX5.js.map +1 -0
  32. package/dist/{chunk-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
  33. package/dist/chunk-UQWSHFVX.js.map +1 -0
  34. package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
  35. package/dist/components.d.ts +182 -6
  36. package/dist/components.js +157 -11
  37. package/dist/components.js.map +1 -1
  38. package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
  39. package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
  40. package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
  41. package/dist/hooks.d.ts +52 -15
  42. package/dist/hooks.js +12 -22
  43. package/dist/hooks.js.map +1 -1
  44. package/dist/index.d.ts +12 -12
  45. package/dist/index.js +82 -18
  46. package/dist/index.js.map +1 -1
  47. package/dist/providers.d.ts +1 -1
  48. package/dist/providers.js +3 -1
  49. package/dist/rbac/index.d.ts +206 -15
  50. package/dist/rbac/index.js +28 -6
  51. package/dist/timezone-_pgH8qrY.d.ts +530 -0
  52. package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
  53. package/dist/types.d.ts +2 -2
  54. package/dist/types.js +1 -1
  55. package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
  56. package/dist/utils.d.ts +110 -152
  57. package/dist/utils.js +128 -138
  58. package/dist/utils.js.map +1 -1
  59. package/docs/api/README.md +60 -1
  60. package/docs/api/classes/ColumnFactory.md +1 -1
  61. package/docs/api/classes/ErrorBoundary.md +1 -1
  62. package/docs/api/classes/InvalidScopeError.md +1 -1
  63. package/docs/api/classes/Logger.md +178 -0
  64. package/docs/api/classes/MissingUserContextError.md +1 -1
  65. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  66. package/docs/api/classes/PermissionDeniedError.md +1 -1
  67. package/docs/api/classes/RBACAuditManager.md +2 -2
  68. package/docs/api/classes/RBACCache.md +1 -1
  69. package/docs/api/classes/RBACEngine.md +2 -2
  70. package/docs/api/classes/RBACError.md +1 -1
  71. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  72. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  73. package/docs/api/classes/StorageUtils.md +1 -1
  74. package/docs/api/enums/FileCategory.md +1 -1
  75. package/docs/api/enums/LogLevel.md +54 -0
  76. package/docs/api/enums/RBACErrorCode.md +1 -1
  77. package/docs/api/enums/RPCFunction.md +1 -1
  78. package/docs/api/interfaces/AggregateConfig.md +1 -1
  79. package/docs/api/interfaces/BadgeProps.md +1 -1
  80. package/docs/api/interfaces/ButtonProps.md +1 -1
  81. package/docs/api/interfaces/CalendarProps.md +18 -2
  82. package/docs/api/interfaces/CardProps.md +1 -1
  83. package/docs/api/interfaces/ColorPalette.md +1 -1
  84. package/docs/api/interfaces/ColorShade.md +1 -1
  85. package/docs/api/interfaces/ComplianceResult.md +30 -0
  86. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  87. package/docs/api/interfaces/DataRecord.md +1 -1
  88. package/docs/api/interfaces/DataTableAction.md +1 -1
  89. package/docs/api/interfaces/DataTableColumn.md +1 -1
  90. package/docs/api/interfaces/DataTableProps.md +1 -1
  91. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  92. package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
  93. package/docs/api/interfaces/DatabaseIssue.md +41 -0
  94. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  95. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  96. package/docs/api/interfaces/EventAppRoleData.md +6 -6
  97. package/docs/api/interfaces/ExportColumn.md +1 -1
  98. package/docs/api/interfaces/ExportOptions.md +1 -1
  99. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  100. package/docs/api/interfaces/FileMetadata.md +1 -1
  101. package/docs/api/interfaces/FileReference.md +1 -1
  102. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  103. package/docs/api/interfaces/FileUploadOptions.md +48 -8
  104. package/docs/api/interfaces/FileUploadProps.md +46 -13
  105. package/docs/api/interfaces/FooterProps.md +1 -1
  106. package/docs/api/interfaces/FormFieldProps.md +1 -1
  107. package/docs/api/interfaces/FormProps.md +1 -1
  108. package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
  109. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  110. package/docs/api/interfaces/InputProps.md +1 -1
  111. package/docs/api/interfaces/LabelProps.md +1 -1
  112. package/docs/api/interfaces/LoggerConfig.md +62 -0
  113. package/docs/api/interfaces/LoginFormProps.md +1 -1
  114. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  115. package/docs/api/interfaces/NavigationContextType.md +1 -1
  116. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  117. package/docs/api/interfaces/NavigationItem.md +1 -1
  118. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  119. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  120. package/docs/api/interfaces/Organisation.md +1 -1
  121. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  122. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  123. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  124. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  125. package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
  126. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  127. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  128. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  129. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  130. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  131. package/docs/api/interfaces/PaletteData.md +1 -1
  132. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  133. package/docs/api/interfaces/ProgressProps.md +1 -1
  134. package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
  135. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  136. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  137. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  138. package/docs/api/interfaces/QuickFix.md +52 -0
  139. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  140. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  141. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  142. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  143. package/docs/api/interfaces/RBACConfig.md +4 -4
  144. package/docs/api/interfaces/RBACContext.md +1 -1
  145. package/docs/api/interfaces/RBACLogger.md +1 -1
  146. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  148. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  149. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  150. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  151. package/docs/api/interfaces/RBACResult.md +1 -1
  152. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  153. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  154. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  155. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  156. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  157. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  158. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  159. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  160. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  161. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  162. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  163. package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
  164. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  165. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  166. package/docs/api/interfaces/RoleManagementResult.md +5 -5
  167. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  168. package/docs/api/interfaces/RouteConfig.md +1 -1
  169. package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
  170. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  171. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  172. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  173. package/docs/api/interfaces/SetupIssue.md +41 -0
  174. package/docs/api/interfaces/StorageConfig.md +1 -1
  175. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  176. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  177. package/docs/api/interfaces/StorageListOptions.md +1 -1
  178. package/docs/api/interfaces/StorageListResult.md +1 -1
  179. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  180. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  181. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  182. package/docs/api/interfaces/StyleImport.md +1 -1
  183. package/docs/api/interfaces/SwitchProps.md +1 -1
  184. package/docs/api/interfaces/TabsContentProps.md +1 -1
  185. package/docs/api/interfaces/TabsListProps.md +1 -1
  186. package/docs/api/interfaces/TabsProps.md +1 -1
  187. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  188. package/docs/api/interfaces/TextareaProps.md +1 -1
  189. package/docs/api/interfaces/ToastActionElement.md +1 -1
  190. package/docs/api/interfaces/ToastProps.md +1 -1
  191. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  192. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  193. package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
  194. package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
  195. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  196. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  198. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  199. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  200. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  201. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  202. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  203. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  204. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  205. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  206. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  207. package/docs/api/interfaces/UserEventAccess.md +1 -1
  208. package/docs/api/interfaces/UserMenuProps.md +1 -1
  209. package/docs/api/interfaces/UserProfile.md +1 -1
  210. package/docs/api/modules.md +746 -50
  211. package/docs/api-reference/components.md +26 -12
  212. package/docs/api-reference/hooks.md +111 -0
  213. package/docs/api-reference/rpc-functions.md +1 -1
  214. package/docs/api-reference/utilities.md +184 -0
  215. package/docs/getting-started/installation-guide.md +75 -16
  216. package/docs/getting-started/quick-start.md +61 -11
  217. package/docs/implementation-guides/authentication.md +88 -12
  218. package/docs/implementation-guides/file-reference-system.md +26 -3
  219. package/docs/implementation-guides/file-upload-storage.md +30 -1
  220. package/docs/rbac/README.md +1 -0
  221. package/docs/rbac/compliance/compliance-guide.md +544 -0
  222. package/docs/rbac/getting-started.md +158 -33
  223. package/docs/standards/pace-core-compliance.md +432 -0
  224. package/eslint-config-pace-core.cjs +93 -0
  225. package/package.json +15 -3
  226. package/scripts/analyze-bundle.js +232 -0
  227. package/scripts/build-css.js +56 -0
  228. package/scripts/build-docs-incremental.js +1015 -0
  229. package/scripts/check-pace-core-compliance.cjs +2353 -0
  230. package/scripts/check-pace-core-compliance.js +512 -0
  231. package/scripts/generate-docs.js +157 -0
  232. package/scripts/setup-build-cache.js +73 -0
  233. package/scripts/utils/command-runner.js +131 -0
  234. package/scripts/utils/env.js +33 -0
  235. package/scripts/utils/index.js +10 -0
  236. package/scripts/utils/logger.js +88 -0
  237. package/scripts/utils/path-helpers.js +37 -0
  238. package/scripts/validate-formats.js +133 -0
  239. package/scripts/validate-master.js +155 -0
  240. package/scripts/validate-pre-publish.js +140 -0
  241. package/scripts/validate-theme.js +142 -0
  242. package/src/components/Calendar/Calendar.tsx +8 -1
  243. package/src/components/Card/Card.tsx +47 -8
  244. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
  245. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
  246. package/src/components/DatePickerWithTimezone/README.md +135 -0
  247. package/src/components/DatePickerWithTimezone/index.ts +10 -0
  248. package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
  249. package/src/components/DateTimeField/DateTimeField.tsx +232 -0
  250. package/src/components/DateTimeField/README.md +148 -0
  251. package/src/components/DateTimeField/index.ts +10 -0
  252. package/src/components/FileUpload/FileUpload.test.tsx +2 -0
  253. package/src/components/FileUpload/FileUpload.tsx +10 -1
  254. package/src/components/Header/Header.test.tsx +47 -18
  255. package/src/components/Header/Header.tsx +22 -7
  256. package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
  257. package/src/components/PaceAppLayout/README.md +9 -0
  258. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
  259. package/src/components/ProtectedRoute/ProtectedRoute.tsx +146 -5
  260. package/src/components/index.ts +8 -0
  261. package/src/eslint-rules/pace-core-compliance.cjs +406 -0
  262. package/src/eslint-rules/pace-core-compliance.js +640 -0
  263. package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
  264. package/src/hooks/index.ts +5 -0
  265. package/src/hooks/useFileReference.test.ts +2 -0
  266. package/src/hooks/useFormDialog.ts +147 -0
  267. package/src/hooks/usePreventTabReload.ts +106 -0
  268. package/src/hooks/useSecureDataAccess.ts +2 -2
  269. package/src/index.ts +27 -0
  270. package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
  271. package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
  272. package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
  273. package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
  274. package/src/rbac/compliance/database-validator.ts +165 -0
  275. package/src/rbac/compliance/index.ts +38 -0
  276. package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
  277. package/src/rbac/compliance/runtime-compliance.ts +77 -0
  278. package/src/rbac/compliance/setup-validator.ts +131 -0
  279. package/src/rbac/components/PagePermissionGuard.tsx +8 -64
  280. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
  281. package/src/rbac/docs/event-based-apps.md +285 -0
  282. package/src/rbac/errors.ts +11 -0
  283. package/src/rbac/hooks/useRoleManagement.ts +292 -12
  284. package/src/rbac/index.ts +30 -0
  285. package/src/services/OrganisationService.ts +4 -0
  286. package/src/styles/core.css +5 -5
  287. package/src/types/database.generated.ts +63 -9
  288. package/src/types/file-reference.ts +9 -0
  289. package/src/utils/__tests__/timezone.test.ts +345 -0
  290. package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
  291. package/src/utils/file-reference/index.ts +13 -2
  292. package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
  293. package/src/utils/formatting/formatting.ts +179 -0
  294. package/src/utils/index.ts +27 -1
  295. package/src/utils/location/index.ts +16 -0
  296. package/src/utils/location/location.test.ts +286 -0
  297. package/src/utils/location/location.ts +175 -0
  298. package/src/utils/security/secureDataAccess.ts +1 -1
  299. package/src/utils/storage/helpers.ts +68 -0
  300. package/src/utils/timezone/index.ts +17 -0
  301. package/src/utils/timezone/timezone.test.ts +349 -0
  302. package/src/utils/timezone/timezone.ts +281 -0
  303. package/dist/chunk-CSOFYHAG.js.map +0 -1
  304. package/dist/chunk-FUEYYMX5.js.map +0 -1
  305. package/dist/chunk-HKIT6O7W.js +0 -198
  306. package/dist/chunk-HKIT6O7W.js.map +0 -1
  307. package/dist/chunk-KUEN3HFB.js +0 -94
  308. package/dist/chunk-KUEN3HFB.js.map +0 -1
  309. package/dist/chunk-M7W4CP3M.js.map +0 -1
  310. package/dist/chunk-MI7HBHN3.js.map +0 -1
  311. package/dist/chunk-NQPMQGS2.js.map +0 -1
  312. package/dist/chunk-PWAHJW4G.js.map +0 -1
  313. package/dist/chunk-UHNYIBXL.js.map +0 -1
  314. package/dist/chunk-W22JP75J.js.map +0 -1
  315. package/dist/formatting-5wETwiGF.d.ts +0 -162
  316. /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
  317. /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
  318. /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
  319. /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
@@ -0,0 +1,478 @@
1
+ /**
2
+ * @file useFormDialog Hook Unit Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks/__tests__/useFormDialog
5
+ * @since 0.1.0
6
+ *
7
+ * Comprehensive tests for the useFormDialog hook covering all critical functionality.
8
+ */
9
+
10
+ import { renderHook, act } from '@testing-library/react';
11
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
12
+ import { useFormDialog, type UseFormDialogOptions } from '../useFormDialog';
13
+
14
+ interface TestFormData {
15
+ id: string;
16
+ name: string;
17
+ email: string;
18
+ }
19
+
20
+ describe('useFormDialog', () => {
21
+ describe('Initial state', () => {
22
+ it('should initialize with closed state and null formData', () => {
23
+ const { result } = renderHook(() => useFormDialog());
24
+
25
+ expect(result.current.isOpen).toBe(false);
26
+ expect(result.current.formData).toBeNull();
27
+ });
28
+
29
+ it('should initialize with closed state for typed hook', () => {
30
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
31
+
32
+ expect(result.current.isOpen).toBe(false);
33
+ expect(result.current.formData).toBeNull();
34
+ });
35
+ });
36
+
37
+ describe('Opening dialog', () => {
38
+ it('should open dialog without data', () => {
39
+ const { result } = renderHook(() => useFormDialog());
40
+
41
+ act(() => {
42
+ result.current.openDialog();
43
+ });
44
+
45
+ expect(result.current.isOpen).toBe(true);
46
+ expect(result.current.formData).toBeNull();
47
+ });
48
+
49
+ it('should open dialog with data', () => {
50
+ const testData: TestFormData = {
51
+ id: '1',
52
+ name: 'John Doe',
53
+ email: 'john@example.com'
54
+ };
55
+
56
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
57
+
58
+ act(() => {
59
+ result.current.openDialog(testData);
60
+ });
61
+
62
+ expect(result.current.isOpen).toBe(true);
63
+ expect(result.current.formData).toEqual(testData);
64
+ });
65
+
66
+ it('should open dialog with null data explicitly', () => {
67
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
68
+
69
+ act(() => {
70
+ result.current.openDialog(null);
71
+ });
72
+
73
+ expect(result.current.isOpen).toBe(true);
74
+ expect(result.current.formData).toBeNull();
75
+ });
76
+ });
77
+
78
+ describe('Closing dialog', () => {
79
+ it('should close dialog and reset formData by default', () => {
80
+ const testData: TestFormData = {
81
+ id: '1',
82
+ name: 'John Doe',
83
+ email: 'john@example.com'
84
+ };
85
+
86
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
87
+
88
+ act(() => {
89
+ result.current.openDialog(testData);
90
+ });
91
+
92
+ expect(result.current.isOpen).toBe(true);
93
+ expect(result.current.formData).toEqual(testData);
94
+
95
+ act(() => {
96
+ result.current.closeDialog();
97
+ });
98
+
99
+ expect(result.current.isOpen).toBe(false);
100
+ expect(result.current.formData).toBeNull();
101
+ });
102
+
103
+ it('should close dialog without resetting formData when resetOnClose is false', () => {
104
+ const testData: TestFormData = {
105
+ id: '1',
106
+ name: 'John Doe',
107
+ email: 'john@example.com'
108
+ };
109
+
110
+ const { result } = renderHook(() =>
111
+ useFormDialog<TestFormData>({ resetOnClose: false })
112
+ );
113
+
114
+ act(() => {
115
+ result.current.openDialog(testData);
116
+ });
117
+
118
+ act(() => {
119
+ result.current.closeDialog();
120
+ });
121
+
122
+ expect(result.current.isOpen).toBe(false);
123
+ expect(result.current.formData).toEqual(testData);
124
+ });
125
+ });
126
+
127
+ describe('handleOpenChange', () => {
128
+ it('should update state when opening via handleOpenChange', () => {
129
+ const { result } = renderHook(() => useFormDialog());
130
+
131
+ act(() => {
132
+ result.current.handleOpenChange(true);
133
+ });
134
+
135
+ expect(result.current.isOpen).toBe(true);
136
+ });
137
+
138
+ it('should update state when closing via handleOpenChange', () => {
139
+ const { result } = renderHook(() => useFormDialog());
140
+
141
+ act(() => {
142
+ result.current.openDialog();
143
+ });
144
+
145
+ act(() => {
146
+ result.current.handleOpenChange(false);
147
+ });
148
+
149
+ expect(result.current.isOpen).toBe(false);
150
+ });
151
+
152
+ it('should reset formData when closing via handleOpenChange with resetOnClose true', () => {
153
+ const testData: TestFormData = {
154
+ id: '1',
155
+ name: 'John Doe',
156
+ email: 'john@example.com'
157
+ };
158
+
159
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
160
+
161
+ act(() => {
162
+ result.current.openDialog(testData);
163
+ });
164
+
165
+ act(() => {
166
+ result.current.handleOpenChange(false);
167
+ });
168
+
169
+ expect(result.current.isOpen).toBe(false);
170
+ expect(result.current.formData).toBeNull();
171
+ });
172
+
173
+ it('should not reset formData when closing via handleOpenChange with resetOnClose false', () => {
174
+ const testData: TestFormData = {
175
+ id: '1',
176
+ name: 'John Doe',
177
+ email: 'john@example.com'
178
+ };
179
+
180
+ const { result } = renderHook(() =>
181
+ useFormDialog<TestFormData>({ resetOnClose: false })
182
+ );
183
+
184
+ act(() => {
185
+ result.current.openDialog(testData);
186
+ });
187
+
188
+ act(() => {
189
+ result.current.handleOpenChange(false);
190
+ });
191
+
192
+ expect(result.current.isOpen).toBe(false);
193
+ expect(result.current.formData).toEqual(testData);
194
+ });
195
+ });
196
+
197
+ describe('onOpenChange callback', () => {
198
+ it('should call onOpenChange when opening dialog', () => {
199
+ const onOpenChange = vi.fn();
200
+ const { result } = renderHook(() =>
201
+ useFormDialog({ onOpenChange })
202
+ );
203
+
204
+ act(() => {
205
+ result.current.openDialog();
206
+ });
207
+
208
+ expect(onOpenChange).toHaveBeenCalledWith(true);
209
+ expect(onOpenChange).toHaveBeenCalledTimes(1);
210
+ });
211
+
212
+ it('should call onOpenChange when closing dialog', () => {
213
+ const onOpenChange = vi.fn();
214
+ const { result } = renderHook(() =>
215
+ useFormDialog({ onOpenChange })
216
+ );
217
+
218
+ act(() => {
219
+ result.current.openDialog();
220
+ });
221
+
222
+ act(() => {
223
+ result.current.closeDialog();
224
+ });
225
+
226
+ expect(onOpenChange).toHaveBeenCalledWith(false);
227
+ expect(onOpenChange).toHaveBeenCalledTimes(2); // Once for open, once for close
228
+ });
229
+
230
+ it('should call onOpenChange when using handleOpenChange', () => {
231
+ const onOpenChange = vi.fn();
232
+ const { result } = renderHook(() =>
233
+ useFormDialog({ onOpenChange })
234
+ );
235
+
236
+ act(() => {
237
+ result.current.handleOpenChange(true);
238
+ });
239
+
240
+ expect(onOpenChange).toHaveBeenCalledWith(true);
241
+
242
+ act(() => {
243
+ result.current.handleOpenChange(false);
244
+ });
245
+
246
+ expect(onOpenChange).toHaveBeenCalledWith(false);
247
+ expect(onOpenChange).toHaveBeenCalledTimes(2);
248
+ });
249
+
250
+ it('should not call onOpenChange if not provided', () => {
251
+ const { result } = renderHook(() => useFormDialog());
252
+
253
+ act(() => {
254
+ result.current.openDialog();
255
+ });
256
+
257
+ act(() => {
258
+ result.current.closeDialog();
259
+ });
260
+
261
+ // Should not throw
262
+ expect(result.current.isOpen).toBe(false);
263
+ });
264
+ });
265
+
266
+ describe('Controlled vs uncontrolled usage', () => {
267
+ it('should work in uncontrolled mode (using openDialog/closeDialog)', () => {
268
+ const { result } = renderHook(() => useFormDialog());
269
+
270
+ act(() => {
271
+ result.current.openDialog();
272
+ });
273
+ expect(result.current.isOpen).toBe(true);
274
+
275
+ act(() => {
276
+ result.current.closeDialog();
277
+ });
278
+ expect(result.current.isOpen).toBe(false);
279
+ });
280
+
281
+ it('should work in controlled mode (using handleOpenChange)', () => {
282
+ const { result } = renderHook(() => useFormDialog());
283
+
284
+ act(() => {
285
+ result.current.handleOpenChange(true);
286
+ });
287
+ expect(result.current.isOpen).toBe(true);
288
+
289
+ act(() => {
290
+ result.current.handleOpenChange(false);
291
+ });
292
+ expect(result.current.isOpen).toBe(false);
293
+ });
294
+
295
+ it('should work with mixed usage', () => {
296
+ const { result } = renderHook(() => useFormDialog());
297
+
298
+ act(() => {
299
+ result.current.openDialog();
300
+ });
301
+ expect(result.current.isOpen).toBe(true);
302
+
303
+ act(() => {
304
+ result.current.handleOpenChange(false);
305
+ });
306
+ expect(result.current.isOpen).toBe(false);
307
+
308
+ act(() => {
309
+ result.current.handleOpenChange(true);
310
+ });
311
+ expect(result.current.isOpen).toBe(true);
312
+
313
+ act(() => {
314
+ result.current.closeDialog();
315
+ });
316
+ expect(result.current.isOpen).toBe(false);
317
+ });
318
+ });
319
+
320
+ describe('Edge cases', () => {
321
+ it('should handle rapid open/close operations', () => {
322
+ const { result } = renderHook(() => useFormDialog());
323
+
324
+ act(() => {
325
+ result.current.openDialog();
326
+ result.current.closeDialog();
327
+ result.current.openDialog();
328
+ result.current.closeDialog();
329
+ });
330
+
331
+ expect(result.current.isOpen).toBe(false);
332
+ expect(result.current.formData).toBeNull();
333
+ });
334
+
335
+ it('should handle opening with undefined data', () => {
336
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
337
+
338
+ act(() => {
339
+ result.current.openDialog(undefined as unknown as TestFormData);
340
+ });
341
+
342
+ expect(result.current.isOpen).toBe(true);
343
+ // When undefined is passed, it becomes null in the state
344
+ expect(result.current.formData).toBeNull();
345
+ });
346
+
347
+ it('should handle replacing formData when dialog is already open', () => {
348
+ const initialData: TestFormData = {
349
+ id: '1',
350
+ name: 'John Doe',
351
+ email: 'john@example.com'
352
+ };
353
+
354
+ const newData: TestFormData = {
355
+ id: '2',
356
+ name: 'Jane Doe',
357
+ email: 'jane@example.com'
358
+ };
359
+
360
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
361
+
362
+ act(() => {
363
+ result.current.openDialog(initialData);
364
+ });
365
+
366
+ expect(result.current.formData).toEqual(initialData);
367
+
368
+ act(() => {
369
+ result.current.openDialog(newData);
370
+ });
371
+
372
+ expect(result.current.isOpen).toBe(true);
373
+ expect(result.current.formData).toEqual(newData);
374
+ });
375
+
376
+ it('should handle closing when already closed', () => {
377
+ const { result } = renderHook(() => useFormDialog());
378
+
379
+ expect(result.current.isOpen).toBe(false);
380
+
381
+ act(() => {
382
+ result.current.closeDialog();
383
+ });
384
+
385
+ expect(result.current.isOpen).toBe(false);
386
+ expect(result.current.formData).toBeNull();
387
+ });
388
+ });
389
+
390
+ describe('Type safety', () => {
391
+ it('should maintain type safety with generic type parameter', () => {
392
+ const testData: TestFormData = {
393
+ id: '1',
394
+ name: 'John Doe',
395
+ email: 'john@example.com'
396
+ };
397
+
398
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
399
+
400
+ act(() => {
401
+ result.current.openDialog(testData);
402
+ });
403
+
404
+ // TypeScript should infer the correct type
405
+ if (result.current.formData) {
406
+ expect(result.current.formData.id).toBe('1');
407
+ expect(result.current.formData.name).toBe('John Doe');
408
+ expect(result.current.formData.email).toBe('john@example.com');
409
+ }
410
+ });
411
+
412
+ it('should work with primitive types', () => {
413
+ const { result } = renderHook(() => useFormDialog<string>());
414
+
415
+ act(() => {
416
+ result.current.openDialog('test string');
417
+ });
418
+
419
+ expect(result.current.formData).toBe('test string');
420
+ });
421
+
422
+ it('should work with number types', () => {
423
+ const { result } = renderHook(() => useFormDialog<number>());
424
+
425
+ act(() => {
426
+ result.current.openDialog(42);
427
+ });
428
+
429
+ expect(result.current.formData).toBe(42);
430
+ });
431
+ });
432
+
433
+ describe('Options handling', () => {
434
+ it('should use default resetOnClose value of true', () => {
435
+ const testData: TestFormData = {
436
+ id: '1',
437
+ name: 'John Doe',
438
+ email: 'john@example.com'
439
+ };
440
+
441
+ const { result } = renderHook(() => useFormDialog<TestFormData>());
442
+
443
+ act(() => {
444
+ result.current.openDialog(testData);
445
+ });
446
+
447
+ act(() => {
448
+ result.current.closeDialog();
449
+ });
450
+
451
+ // Should reset by default
452
+ expect(result.current.formData).toBeNull();
453
+ });
454
+
455
+ it('should respect resetOnClose option', () => {
456
+ const testData: TestFormData = {
457
+ id: '1',
458
+ name: 'John Doe',
459
+ email: 'john@example.com'
460
+ };
461
+
462
+ const { result } = renderHook(() =>
463
+ useFormDialog<TestFormData>({ resetOnClose: false })
464
+ );
465
+
466
+ act(() => {
467
+ result.current.openDialog(testData);
468
+ });
469
+
470
+ act(() => {
471
+ result.current.closeDialog();
472
+ });
473
+
474
+ // Should not reset when resetOnClose is false
475
+ expect(result.current.formData).toEqual(testData);
476
+ });
477
+ });
478
+ });
@@ -39,6 +39,8 @@ export type { OrganisationSecurityHook } from './useOrganisationSecurity';
39
39
 
40
40
  // === FORM HOOKS ===
41
41
  export { useZodForm } from './useZodForm';
42
+ export { useFormDialog } from './useFormDialog';
43
+ export type { UseFormDialogOptions, UseFormDialogReturn } from './useFormDialog';
42
44
 
43
45
 
44
46
  // === PERFORMANCE HOOKS ===
@@ -62,6 +64,9 @@ export type { UseStorageOptions, UseStorageReturn } from './useStorage';
62
64
  // === PUBLIC DATA ACCESS HOOKS ===
63
65
  export * from './public';
64
66
 
67
+ // === PAGE LIFECYCLE HOOKS ===
68
+ export { usePreventTabReload } from './usePreventTabReload';
69
+ export type { UsePreventTabReloadOptions } from './usePreventTabReload';
65
70
 
66
71
  // RBAC Hooks - Use @jmruthers/pace-core/rbac instead
67
72
  // Note: RBAC functionality has been moved to the dedicated RBAC module
@@ -67,6 +67,8 @@ const mockFileUploadOptions = {
67
67
  organisation_id: 'test-org-123',
68
68
  app_id: 'test-app-123',
69
69
  category: FileCategory.GENERAL_DOCUMENTS,
70
+ folder: 'documents',
71
+ pageContext: 'configuration',
70
72
  is_public: false
71
73
  };
72
74
 
@@ -0,0 +1,147 @@
1
+ /**
2
+ * @file useFormDialog Hook
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks
5
+ * @since 0.1.0
6
+ *
7
+ * Generic React hook for managing form dialog state (open/close, form data, reset behavior).
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * interface ContactFormData {
12
+ * name: string;
13
+ * email: string;
14
+ * }
15
+ *
16
+ * function ContactDialog() {
17
+ * const { isOpen, formData, openDialog, closeDialog, handleOpenChange } =
18
+ * useFormDialog<ContactFormData>();
19
+ *
20
+ * // Use in Dialog component
21
+ * // <Dialog open={isOpen} onOpenChange={handleOpenChange}>...</Dialog>
22
+ * }
23
+ * ```
24
+ */
25
+
26
+ import { useState, useCallback } from 'react';
27
+
28
+ /**
29
+ * Options for configuring the useFormDialog hook behavior
30
+ */
31
+ export interface UseFormDialogOptions<T = unknown> {
32
+ /**
33
+ * Callback invoked when the dialog open state changes
34
+ * @param open - The new open state
35
+ */
36
+ onOpenChange?: (open: boolean) => void;
37
+
38
+ /**
39
+ * Whether to reset form data when the dialog closes
40
+ * @default true
41
+ */
42
+ resetOnClose?: boolean;
43
+ }
44
+
45
+ /**
46
+ * Return type for the useFormDialog hook
47
+ */
48
+ export interface UseFormDialogReturn<T = unknown> {
49
+ /**
50
+ * Current open state of the dialog
51
+ */
52
+ isOpen: boolean;
53
+
54
+ /**
55
+ * Current form data (null when dialog is closed or no data provided)
56
+ */
57
+ formData: T | null;
58
+
59
+ /**
60
+ * Open the dialog with optional form data
61
+ * @param data - Optional form data to populate the dialog
62
+ */
63
+ openDialog: (data?: T | null) => void;
64
+
65
+ /**
66
+ * Close the dialog
67
+ */
68
+ closeDialog: () => void;
69
+
70
+ /**
71
+ * Handler for controlled dialog components (e.g., Radix UI Dialog)
72
+ * @param open - The new open state
73
+ */
74
+ handleOpenChange: (open: boolean) => void;
75
+ }
76
+
77
+ /**
78
+ * Generic React hook for managing form dialog state.
79
+ *
80
+ * Provides state management for form dialogs including:
81
+ * - Open/close state
82
+ * - Form data management
83
+ * - Automatic reset on close (configurable)
84
+ * - Controlled and uncontrolled usage patterns
85
+ *
86
+ * @param options - Configuration options for the hook
87
+ * @returns Object containing dialog state and control functions
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * // Basic usage
92
+ * const { isOpen, openDialog, closeDialog } = useFormDialog();
93
+ *
94
+ * // With form data
95
+ * interface UserData {
96
+ * id: string;
97
+ * name: string;
98
+ * }
99
+ * const { isOpen, formData, openDialog } = useFormDialog<UserData>();
100
+ *
101
+ * // Open with data
102
+ * openDialog({ id: '1', name: 'John' });
103
+ *
104
+ * // Controlled usage with callback
105
+ * const { handleOpenChange, isOpen } = useFormDialog({
106
+ * onOpenChange: (open) => console.log('Dialog state:', open),
107
+ * resetOnClose: false
108
+ * });
109
+ * ```
110
+ */
111
+ export function useFormDialog<T = unknown>({
112
+ onOpenChange,
113
+ resetOnClose = true,
114
+ }: UseFormDialogOptions<T> = {}): UseFormDialogReturn<T> {
115
+ const [isOpen, setIsOpen] = useState(false);
116
+ const [formData, setFormData] = useState<T | null>(null);
117
+
118
+ const openDialog = useCallback((data: T | null = null) => {
119
+ setFormData(data);
120
+ setIsOpen(true);
121
+ onOpenChange?.(true);
122
+ }, [onOpenChange]);
123
+
124
+ const closeDialog = useCallback(() => {
125
+ setIsOpen(false);
126
+ if (resetOnClose) {
127
+ setFormData(null);
128
+ }
129
+ onOpenChange?.(false);
130
+ }, [resetOnClose, onOpenChange]);
131
+
132
+ const handleOpenChange = useCallback((open: boolean) => {
133
+ setIsOpen(open);
134
+ if (!open && resetOnClose) {
135
+ setFormData(null);
136
+ }
137
+ onOpenChange?.(open);
138
+ }, [resetOnClose, onOpenChange]);
139
+
140
+ return {
141
+ isOpen,
142
+ formData,
143
+ openDialog,
144
+ closeDialog,
145
+ handleOpenChange,
146
+ };
147
+ }