@jmruthers/pace-core 0.5.136 → 0.5.137

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 (289) hide show
  1. package/dist/{DataTable-CYOHOX3O.js → DataTable-6M4L6BI2.js} +10 -9
  2. package/dist/{EventLogo-801uofbR.d.ts → EventLogo-rFL_kRjk.d.ts} +73 -1
  3. package/dist/{UnifiedAuthProvider-5E5TUNMS.js → UnifiedAuthProvider-XIQQ7LVU.js} +4 -5
  4. package/dist/{chunk-YLKIDTUK.js → chunk-22WKWKRX.js} +4 -4
  5. package/dist/{chunk-TVYPTYOY.js → chunk-4C7EXCAR.js} +60 -24
  6. package/dist/chunk-4C7EXCAR.js.map +1 -0
  7. package/dist/{chunk-2TWNJ46Y.js → chunk-6LAAY47Q.js} +2 -2
  8. package/dist/{chunk-444EZN6N.js → chunk-7QCC6MCP.js} +88 -1
  9. package/dist/chunk-7QCC6MCP.js.map +1 -0
  10. package/dist/{chunk-FHWWBIHA.js → chunk-BCIBECNB.js} +5 -5
  11. package/dist/chunk-BJPBT3CU.js +21 -0
  12. package/dist/chunk-BJPBT3CU.js.map +1 -0
  13. package/dist/{chunk-L6PGMCMD.js → chunk-BLCXZEYF.js} +3 -3
  14. package/dist/{chunk-HJGGOMQ6.js → chunk-HAWZXGR2.js} +147 -103
  15. package/dist/chunk-HAWZXGR2.js.map +1 -0
  16. package/dist/{chunk-XARJS7CD.js → chunk-INQLMHPF.js} +2 -2
  17. package/dist/chunk-JISYG63F.js +70 -0
  18. package/dist/chunk-JISYG63F.js.map +1 -0
  19. package/dist/{chunk-NOHEVYVX.js → chunk-KYRHUBIU.js} +417 -319
  20. package/dist/chunk-KYRHUBIU.js.map +1 -0
  21. package/dist/{chunk-SL2YQDR6.js → chunk-MA6EPSGZ.js} +2 -2
  22. package/dist/{chunk-5DPZ5EAT.js → chunk-OWAG3GSU.js} +1 -3
  23. package/dist/{chunk-LTV3XIJJ.js → chunk-T6JN6LH6.js} +4 -4
  24. package/dist/{chunk-4MT5BGGL.js → chunk-YCWDTTUK.js} +4 -6
  25. package/dist/{chunk-4MT5BGGL.js.map → chunk-YCWDTTUK.js.map} +1 -1
  26. package/dist/components.d.ts +1 -1
  27. package/dist/components.js +12 -11
  28. package/dist/components.js.map +1 -1
  29. package/dist/hooks.js +8 -9
  30. package/dist/hooks.js.map +1 -1
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +15 -14
  33. package/dist/index.js.map +1 -1
  34. package/dist/providers.js +3 -4
  35. package/dist/rbac/index.js +8 -9
  36. package/dist/schema-DTDZQe2u.d.ts +28 -0
  37. package/dist/types.d.ts +152 -3
  38. package/dist/types.js +51 -16
  39. package/dist/types.js.map +1 -1
  40. package/dist/utils.d.ts +89 -4
  41. package/dist/utils.js +214 -96
  42. package/dist/utils.js.map +1 -1
  43. package/dist/validation.d.ts +1 -343
  44. package/dist/validation.js +3 -100
  45. package/docs/api/classes/ColumnFactory.md +1 -1
  46. package/docs/api/classes/ErrorBoundary.md +1 -1
  47. package/docs/api/classes/InvalidScopeError.md +1 -1
  48. package/docs/api/classes/MissingUserContextError.md +1 -1
  49. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  50. package/docs/api/classes/PermissionDeniedError.md +1 -1
  51. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  52. package/docs/api/classes/RBACAuditManager.md +1 -1
  53. package/docs/api/classes/RBACCache.md +1 -1
  54. package/docs/api/classes/RBACEngine.md +1 -1
  55. package/docs/api/classes/RBACError.md +1 -1
  56. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  57. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  58. package/docs/api/classes/StorageUtils.md +1 -1
  59. package/docs/api/enums/FileCategory.md +1 -1
  60. package/docs/api/interfaces/AggregateConfig.md +1 -1
  61. package/docs/api/interfaces/BadgeProps.md +27 -0
  62. package/docs/api/interfaces/ButtonProps.md +1 -1
  63. package/docs/api/interfaces/CardProps.md +1 -1
  64. package/docs/api/interfaces/ColorPalette.md +1 -1
  65. package/docs/api/interfaces/ColorShade.md +1 -1
  66. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  67. package/docs/api/interfaces/DataRecord.md +1 -1
  68. package/docs/api/interfaces/DataTableAction.md +1 -1
  69. package/docs/api/interfaces/DataTableColumn.md +1 -1
  70. package/docs/api/interfaces/DataTableProps.md +1 -1
  71. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  72. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  73. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  74. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  75. package/docs/api/interfaces/EventLogoProps.md +1 -1
  76. package/docs/api/interfaces/ExportColumn.md +1 -1
  77. package/docs/api/interfaces/ExportOptions.md +1 -1
  78. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  79. package/docs/api/interfaces/FileMetadata.md +1 -1
  80. package/docs/api/interfaces/FileReference.md +1 -1
  81. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  82. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  83. package/docs/api/interfaces/FileUploadProps.md +1 -1
  84. package/docs/api/interfaces/FooterProps.md +1 -1
  85. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  86. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  87. package/docs/api/interfaces/InputProps.md +1 -1
  88. package/docs/api/interfaces/LabelProps.md +1 -1
  89. package/docs/api/interfaces/LoginFormProps.md +1 -1
  90. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  91. package/docs/api/interfaces/NavigationContextType.md +1 -1
  92. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  93. package/docs/api/interfaces/NavigationItem.md +1 -1
  94. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  95. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  96. package/docs/api/interfaces/Organisation.md +1 -1
  97. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  98. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  99. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  100. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  101. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  102. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  103. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  104. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  105. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  106. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  107. package/docs/api/interfaces/PaletteData.md +1 -1
  108. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  109. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  110. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  111. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  112. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  113. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  114. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  115. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  116. package/docs/api/interfaces/RBACConfig.md +1 -1
  117. package/docs/api/interfaces/RBACLogger.md +1 -1
  118. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  119. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  120. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  121. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  122. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  123. package/docs/api/interfaces/RouteConfig.md +1 -1
  124. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  125. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  126. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  127. package/docs/api/interfaces/StorageConfig.md +1 -1
  128. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  129. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  130. package/docs/api/interfaces/StorageListOptions.md +1 -1
  131. package/docs/api/interfaces/StorageListResult.md +1 -1
  132. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  133. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  134. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  135. package/docs/api/interfaces/StyleImport.md +1 -1
  136. package/docs/api/interfaces/SwitchProps.md +1 -1
  137. package/docs/api/interfaces/ToastActionElement.md +1 -1
  138. package/docs/api/interfaces/ToastProps.md +1 -1
  139. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  140. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  141. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  142. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  143. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  144. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  145. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  146. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  147. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  148. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  149. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  150. package/docs/api/interfaces/UserEventAccess.md +1 -1
  151. package/docs/api/interfaces/UserMenuProps.md +1 -1
  152. package/docs/api/interfaces/UserProfile.md +1 -1
  153. package/docs/api/modules.md +79 -10
  154. package/docs/architecture/README.md +0 -1
  155. package/docs/styles/README.md +0 -2
  156. package/examples/RBAC/CompleteRBACExample.tsx +324 -0
  157. package/examples/RBAC/EventBasedApp.tsx +239 -0
  158. package/examples/RBAC/PermissionExample.tsx +151 -0
  159. package/examples/RBAC/index.ts +13 -0
  160. package/examples/public-pages/CorrectPublicPageImplementation.tsx +301 -0
  161. package/examples/public-pages/PublicEventPage.tsx +274 -0
  162. package/examples/public-pages/PublicPageApp.tsx +308 -0
  163. package/examples/public-pages/PublicPageUsageExample.tsx +216 -0
  164. package/examples/public-pages/index.ts +14 -0
  165. package/package.json +1 -10
  166. package/src/__tests__/TEST_STANDARD.md +92 -0
  167. package/src/components/Badge/Badge.test.tsx +314 -0
  168. package/src/components/Badge/Badge.tsx +304 -0
  169. package/src/components/Badge/index.ts +3 -0
  170. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +217 -0
  171. package/src/components/DataTable/__tests__/styles.test.ts +1 -1
  172. package/src/components/DataTable/components/ColumnFilter.tsx +8 -4
  173. package/src/components/DataTable/components/DataTableBody.tsx +461 -0
  174. package/src/components/DataTable/components/DraggableColumnHeader.tsx +144 -0
  175. package/src/components/DataTable/components/FilterRow.tsx +9 -3
  176. package/src/components/DataTable/components/PaginationControls.tsx +1 -0
  177. package/src/components/DataTable/components/VirtualizedDataTable.tsx +513 -0
  178. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +14 -68
  179. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +62 -0
  180. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +43 -0
  181. package/src/components/DataTable/core/ActionManager.ts +235 -0
  182. package/src/components/DataTable/core/ColumnManager.ts +205 -0
  183. package/src/components/DataTable/core/DataManager.ts +188 -0
  184. package/src/components/DataTable/core/DataTableContext.tsx +181 -0
  185. package/src/components/DataTable/core/LocalDataAdapter.ts +273 -0
  186. package/src/components/DataTable/core/PluginRegistry.ts +229 -0
  187. package/src/components/DataTable/core/StateManager.ts +311 -0
  188. package/src/components/DataTable/core/interfaces.ts +338 -0
  189. package/src/components/DataTable/styles.ts +27 -6
  190. package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +94 -0
  191. package/src/components/DataTable/utils/columnUtils.ts +40 -0
  192. package/src/components/DataTable/utils/debugTools.ts +609 -0
  193. package/src/components/DataTable/utils/index.ts +1 -0
  194. package/src/components/Dialog/README.md +804 -0
  195. package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +611 -0
  196. package/src/components/Dialog/utils/safeHtml.ts +185 -0
  197. package/src/components/Footer/Footer.test.tsx +1 -1
  198. package/src/components/Form/Form.test.tsx +1 -1
  199. package/src/components/Form/FormErrorSummary.tsx +113 -0
  200. package/src/components/Form/FormFieldset.tsx +127 -0
  201. package/src/components/Form/FormLiveRegion.tsx +198 -0
  202. package/src/components/LoginForm/LoginForm.test.tsx +1 -1
  203. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +76 -10
  204. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  205. package/src/components/PasswordReset/PasswordResetForm.test.tsx +597 -0
  206. package/src/components/PasswordReset/PasswordResetForm.tsx +201 -0
  207. package/src/components/PublicLayout/PublicPageDebugger.tsx +104 -0
  208. package/src/components/PublicLayout/PublicPageDiagnostic.tsx +162 -0
  209. package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +1 -1
  210. package/src/components/Select/Select.test.tsx +1 -1
  211. package/src/components/Select/Select.tsx +20 -8
  212. package/src/components/Table/__tests__/Table.test.tsx +1 -1
  213. package/src/components/index.ts +3 -0
  214. package/src/hooks/__tests__/useFileUrl.unit.test.ts +83 -85
  215. package/src/index.ts +4 -0
  216. package/src/styles/core.css +3 -0
  217. package/src/utils/appConfig.ts +47 -0
  218. package/src/utils/appIdResolver.test.ts +499 -0
  219. package/src/utils/appIdResolver.ts +130 -0
  220. package/src/utils/appNameResolver.simple.test.ts +212 -0
  221. package/src/utils/appNameResolver.test.ts +121 -0
  222. package/src/utils/appNameResolver.ts +191 -0
  223. package/src/utils/audit.ts +127 -0
  224. package/src/utils/auth-utils.ts +96 -0
  225. package/src/utils/bundleAnalysis.ts +129 -0
  226. package/src/utils/cn.ts +7 -0
  227. package/src/utils/debugLogger.ts +67 -0
  228. package/src/utils/deviceFingerprint.ts +215 -0
  229. package/src/utils/dynamicUtils.ts +105 -0
  230. package/src/utils/file-reference.test.ts +788 -0
  231. package/src/utils/file-reference.ts +519 -0
  232. package/src/utils/formatDate.test.ts +237 -0
  233. package/src/utils/formatting.ts +133 -0
  234. package/src/utils/index.ts +7 -0
  235. package/src/utils/lazyLoad.tsx +44 -0
  236. package/src/utils/logger.ts +179 -0
  237. package/src/utils/organisationContext.test.ts +322 -0
  238. package/src/utils/organisationContext.ts +153 -0
  239. package/src/utils/performanceBenchmark.ts +64 -0
  240. package/src/utils/performanceBudgets.ts +110 -0
  241. package/src/utils/permissionTypes.ts +37 -0
  242. package/src/utils/permissionUtils.test.ts +393 -0
  243. package/src/utils/permissionUtils.ts +34 -0
  244. package/src/utils/sanitization.ts +264 -0
  245. package/src/utils/schemaUtils.ts +37 -0
  246. package/src/utils/secureDataAccess.test.ts +711 -0
  247. package/src/utils/secureDataAccess.ts +377 -0
  248. package/src/utils/secureErrors.ts +79 -0
  249. package/src/utils/secureStorage.ts +244 -0
  250. package/src/utils/security.ts +156 -0
  251. package/src/utils/securityMonitor.ts +45 -0
  252. package/src/utils/sessionTracking.ts +126 -0
  253. package/src/utils/validation.ts +111 -0
  254. package/src/utils/validationUtils.ts +120 -0
  255. package/src/validation/index.ts +2 -2
  256. package/dist/chunk-444EZN6N.js.map +0 -1
  257. package/dist/chunk-APIBCTL2.js +0 -670
  258. package/dist/chunk-APIBCTL2.js.map +0 -1
  259. package/dist/chunk-HJGGOMQ6.js.map +0 -1
  260. package/dist/chunk-K2WWTH7O.js +0 -94
  261. package/dist/chunk-K2WWTH7O.js.map +0 -1
  262. package/dist/chunk-LMC26NLJ.js +0 -84
  263. package/dist/chunk-LMC26NLJ.js.map +0 -1
  264. package/dist/chunk-NOHEVYVX.js.map +0 -1
  265. package/dist/chunk-TVYPTYOY.js.map +0 -1
  266. package/dist/validation-8npbysjg.d.ts +0 -177
  267. /package/dist/{DataTable-CYOHOX3O.js.map → DataTable-6M4L6BI2.js.map} +0 -0
  268. /package/dist/{UnifiedAuthProvider-5E5TUNMS.js.map → UnifiedAuthProvider-XIQQ7LVU.js.map} +0 -0
  269. /package/dist/{chunk-YLKIDTUK.js.map → chunk-22WKWKRX.js.map} +0 -0
  270. /package/dist/{chunk-2TWNJ46Y.js.map → chunk-6LAAY47Q.js.map} +0 -0
  271. /package/dist/{chunk-FHWWBIHA.js.map → chunk-BCIBECNB.js.map} +0 -0
  272. /package/dist/{chunk-L6PGMCMD.js.map → chunk-BLCXZEYF.js.map} +0 -0
  273. /package/dist/{chunk-XARJS7CD.js.map → chunk-INQLMHPF.js.map} +0 -0
  274. /package/dist/{chunk-SL2YQDR6.js.map → chunk-MA6EPSGZ.js.map} +0 -0
  275. /package/dist/{chunk-5DPZ5EAT.js.map → chunk-OWAG3GSU.js.map} +0 -0
  276. /package/dist/{chunk-LTV3XIJJ.js.map → chunk-T6JN6LH6.js.map} +0 -0
  277. /package/examples/{components → components 2}/DataTable/HierarchicalActionsExample.tsx +0 -0
  278. /package/examples/{components → components 2}/DataTable/HierarchicalExample.tsx +0 -0
  279. /package/examples/{components → components 2}/DataTable/InitialPageSizeExample.tsx +0 -0
  280. /package/examples/{components → components 2}/DataTable/PerformanceExample.tsx +0 -0
  281. /package/examples/{components → components 2}/DataTable/index.ts +0 -0
  282. /package/examples/{components → components 2}/Dialog/BasicHtmlTest.tsx +0 -0
  283. /package/examples/{components → components 2}/Dialog/DebugHtmlExample.tsx +0 -0
  284. /package/examples/{components → components 2}/Dialog/HtmlDialogExample.tsx +0 -0
  285. /package/examples/{components → components 2}/Dialog/ScrollableDialogExample.tsx +0 -0
  286. /package/examples/{components → components 2}/Dialog/SimpleHtmlTest.tsx +0 -0
  287. /package/examples/{components → components 2}/Dialog/SmartDialogExample.tsx +0 -0
  288. /package/examples/{components → components 2}/Dialog/index.ts +0 -0
  289. /package/examples/{components → components 2}/index.ts +0 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * @file Safe HTML Utilities for Dialog Component
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/Dialog/Utils/safeHtml
5
+ * @since 0.4.36
6
+ *
7
+ * Utilities for safely rendering HTML content in Dialog components.
8
+ * Provides sanitization and validation for basic HTML elements.
9
+ */
10
+
11
+ /**
12
+ * Allowed HTML tags for safe rendering in Dialog components
13
+ */
14
+ const ALLOWED_TAGS = [
15
+ 'p', 'div', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
16
+ 'strong', 'em', 'b', 'i', 'u', 's', 'mark', 'small', 'sub', 'sup',
17
+ 'ul', 'ol', 'li', 'dl', 'dt', 'dd',
18
+ 'blockquote', 'pre', 'code', 'kbd', 'samp',
19
+ 'a', 'br', 'hr',
20
+ 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td',
21
+ 'img', 'figure', 'figcaption',
22
+ 'section', 'article', 'aside', 'header', 'footer', 'main', 'nav',
23
+ 'details', 'summary'
24
+ ] as const;
25
+
26
+ /**
27
+ * Allowed HTML attributes for safe rendering
28
+ */
29
+ const ALLOWED_ATTRIBUTES = [
30
+ 'id', 'class', 'style', 'title', 'lang', 'dir',
31
+ 'href', 'target', 'rel', 'download',
32
+ 'src', 'alt', 'width', 'height', 'loading',
33
+ 'colspan', 'rowspan', 'scope', 'headers',
34
+ 'open', 'datetime', 'cite'
35
+ ] as const;
36
+
37
+ /**
38
+ * Basic HTML sanitization function using regex-based approach
39
+ * Removes potentially dangerous elements and attributes while preserving basic formatting
40
+ * This approach is more reliable in SSR environments and doesn't require DOM manipulation
41
+ *
42
+ * @param html - The HTML string to sanitize
43
+ * @returns Sanitized HTML string safe for rendering
44
+ *
45
+ * @example
46
+ * ```tsx
47
+ * const safeHtml = sanitizeHtml('<p>Hello <strong>world</strong>!</p>');
48
+ * // Returns: '<p>Hello <strong>world</strong>!</p>'
49
+ *
50
+ * const dangerousHtml = sanitizeHtml('<script>alert("xss")</script><p>Safe content</p>');
51
+ * // Returns: '<p>Safe content</p>'
52
+ * ```
53
+ */
54
+ export function sanitizeHtml(html: string): string {
55
+ if (!html || typeof html !== 'string') {
56
+ return '';
57
+ }
58
+
59
+ // For debugging, let's just return the HTML as-is for now
60
+ // This will help us see if the issue is with sanitization or rendering
61
+ console.log('🔍 sanitizeHtml input:', html);
62
+
63
+ // Basic safety: just remove script tags and dangerous attributes
64
+ let sanitized = html
65
+ // Remove script tags (including self-closing and malformed)
66
+ .replace(/<script\b[^>]*>.*?<\/script>/gi, '')
67
+ .replace(/<script\b[^>]*\/>/gi, '')
68
+ // Remove iframe tags (including self-closing and malformed)
69
+ .replace(/<iframe\b[^>]*>.*?<\/iframe>/gi, '')
70
+ .replace(/<iframe\b[^>]*\/>/gi, '')
71
+ // Remove object tags (including self-closing and malformed)
72
+ .replace(/<object\b[^>]*>.*?<\/object>/gi, '')
73
+ .replace(/<object\b[^>]*\/>/gi, '')
74
+ // Remove embed tags (including self-closing)
75
+ .replace(/<embed\b[^>]*\/?>/gi, '')
76
+ // Remove form tags (including self-closing and malformed)
77
+ .replace(/<form\b[^>]*>.*?<\/form>/gi, '')
78
+ .replace(/<form\b[^>]*\/>/gi, '')
79
+ // Remove input tags (including self-closing)
80
+ .replace(/<input\b[^>]*\/?>/gi, '')
81
+ // Remove button tags (including self-closing and malformed)
82
+ .replace(/<button\b[^>]*>.*?<\/button>/gi, '')
83
+ .replace(/<button\b[^>]*\/>/gi, '')
84
+ // Remove event handlers from any remaining tags - correct pattern
85
+ .replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, '')
86
+ // Remove javascript: protocols - correct pattern
87
+ .replace(/javascript:[^"'\s>]*/gi, '')
88
+ // Remove data: protocols - correct pattern
89
+ .replace(/data:[^"'\s>]*/gi, '');
90
+
91
+ console.log('🔍 sanitizeHtml output:', sanitized);
92
+ return sanitized;
93
+ }
94
+
95
+ /**
96
+ * Validates if HTML content is safe for rendering
97
+ *
98
+ * @param html - The HTML string to validate
99
+ * @returns Object with validation result and any warnings
100
+ *
101
+ * @example
102
+ * ```tsx
103
+ * const validation = validateHtml('<p>Safe content</p>');
104
+ * console.log(validation.isValid); // true
105
+ * console.log(validation.warnings); // []
106
+ * ```
107
+ */
108
+ export function validateHtml(html: string): { isValid: boolean; warnings: string[] } {
109
+ const warnings: string[] = [];
110
+
111
+ if (!html || typeof html !== 'string') {
112
+ return { isValid: false, warnings: ['HTML content must be a non-empty string'] };
113
+ }
114
+
115
+ // Check for potentially dangerous patterns
116
+ const dangerousPatterns = [
117
+ { pattern: /<script\b[^>]*>.*?<\/script>/gi, message: 'Script tags are not allowed' },
118
+ { pattern: /<script\b[^>]*\/>/gi, message: 'Script tags are not allowed' },
119
+ { pattern: /<iframe\b[^>]*>.*?<\/iframe>/gi, message: 'Iframe tags are not allowed' },
120
+ { pattern: /<iframe\b[^>]*\/>/gi, message: 'Iframe tags are not allowed' },
121
+ { pattern: /<object\b[^>]*>.*?<\/object>/gi, message: 'Object tags are not allowed' },
122
+ { pattern: /<object\b[^>]*\/>/gi, message: 'Object tags are not allowed' },
123
+ { pattern: /<embed\b[^>]*\/?>/gi, message: 'Embed tags are not allowed' },
124
+ { pattern: /<form\b[^>]*>.*?<\/form>/gi, message: 'Form tags are not allowed' },
125
+ { pattern: /<form\b[^>]*\/>/gi, message: 'Form tags are not allowed' },
126
+ { pattern: /<input\b[^>]*\/?>/gi, message: 'Input tags are not allowed' },
127
+ { pattern: /<button\b[^>]*>.*?<\/button>/gi, message: 'Button tags are not allowed' },
128
+ { pattern: /<button\b[^>]*\/>/gi, message: 'Button tags are not allowed' },
129
+ { pattern: /on\w+\s*=/gi, message: 'Event handlers are not allowed' },
130
+ { pattern: /javascript:/gi, message: 'JavaScript protocols are not allowed' },
131
+ { pattern: /data:/gi, message: 'Data protocols are not allowed' }
132
+ ];
133
+
134
+ dangerousPatterns.forEach(({ pattern, message }) => {
135
+ if (pattern.test(html)) {
136
+ warnings.push(message);
137
+ }
138
+ });
139
+
140
+ return {
141
+ isValid: warnings.length === 0,
142
+ warnings
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Safely renders HTML content with sanitization
148
+ *
149
+ * @param html - The HTML string to render
150
+ * @param options - Rendering options
151
+ * @returns Object with sanitized HTML and validation info
152
+ *
153
+ * @example
154
+ * ```tsx
155
+ * const result = renderSafeHtml('<p>Hello <strong>world</strong>!</p>');
156
+ * console.log(result.html); // Sanitized HTML
157
+ * console.log(result.isValid); // true
158
+ * ```
159
+ */
160
+ export function renderSafeHtml(
161
+ html: string,
162
+ options: {
163
+ strict?: boolean;
164
+ logWarnings?: boolean;
165
+ } = {}
166
+ ): {
167
+ html: string;
168
+ isValid: boolean;
169
+ warnings: string[]
170
+ } {
171
+ const { strict = true, logWarnings = false } = options;
172
+
173
+ const validation = validateHtml(html);
174
+ const sanitizedHtml = sanitizeHtml(html);
175
+
176
+ if (logWarnings && validation.warnings.length > 0) {
177
+ console.warn('Dialog HTML content warnings:', validation.warnings);
178
+ }
179
+
180
+ return {
181
+ html: sanitizedHtml,
182
+ isValid: validation.isValid,
183
+ warnings: validation.warnings
184
+ };
185
+ }
@@ -15,7 +15,7 @@ import { renderWithProviders } from '../../__tests__/helpers/test-utils';
15
15
 
16
16
  // Mock the current year for consistent testing
17
17
  const mockCurrentYear = 2024;
18
- vi.mock('../../utils/cn', () => ({
18
+ vi.mock('../../utils/core/cn', () => ({
19
19
  cn: (...classes: (string | undefined)[]) => classes.filter(Boolean).join(' ')
20
20
  }));
21
21
 
@@ -16,7 +16,7 @@ import { Form, FormField } from './index';
16
16
  import { renderWithProviders } from '../../__tests__/helpers/test-utils';
17
17
 
18
18
  // Mock the cn utility
19
- vi.mock('../../utils/cn', () => ({
19
+ vi.mock('../../utils/core/cn', () => ({
20
20
  cn: (...classes: (string | undefined)[]) => classes.filter(Boolean).join(' ')
21
21
  }));
22
22
 
@@ -0,0 +1,113 @@
1
+
2
+ /**
3
+ * @file FormErrorSummary Component
4
+ * @package @jmruthers/pace-core
5
+ * @module Components/Form
6
+ * @since 0.1.0
7
+ *
8
+ * A form error summary component that displays validation errors in a user-friendly format.
9
+ * Provides a centralized location for users to see all form errors at once.
10
+ *
11
+ * Features:
12
+ * - Displays all form errors in one place
13
+ * - Configurable error display format
14
+ * - Optional field name display
15
+ * - Accessible error presentation
16
+ * - Consistent styling with Alert component
17
+ * - Automatic error filtering
18
+ * - Responsive design
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * // Basic error summary
23
+ * <FormErrorSummary errors={formErrors} />
24
+ *
25
+ * // With custom title
26
+ * <FormErrorSummary
27
+ * errors={formErrors}
28
+ * title="Please correct the following issues:"
29
+ * />
30
+ *
31
+ * // Show field names with errors
32
+ * <FormErrorSummary
33
+ * errors={formErrors}
34
+ * showFieldNames={true}
35
+ * />
36
+ *
37
+ * // In a form with React Hook Form
38
+ * const { formState: { errors } } = useForm();
39
+ *
40
+ * <Form onSubmit={handleSubmit}>
41
+ * <FormField name="email" label="Email" />
42
+ * <FormField name="password" label="Password" />
43
+ * <FormErrorSummary errors={errors} />
44
+ * <Button type="submit">Submit</Button>
45
+ * </Form>
46
+ * ```
47
+ *
48
+ * @accessibility
49
+ * - Uses Alert component for consistent accessibility
50
+ * - Proper heading hierarchy with h3
51
+ * - List structure for error items
52
+ * - Screen reader friendly error announcements
53
+ * - High contrast support via Alert component
54
+ *
55
+ * @dependencies
56
+ * - Alert component for consistent styling
57
+ * - React 18+ - Component framework
58
+ * - Tailwind CSS - Styling
59
+ */
60
+
61
+ import { Alert } from '../Alert';
62
+
63
+ export interface FormErrorSummaryProps {
64
+ /** Form validation errors object */
65
+ errors?: Record<string, any>;
66
+ /** Title displayed above the error list */
67
+ title?: string;
68
+ /** Whether to show field names alongside error messages */
69
+ showFieldNames?: boolean;
70
+ }
71
+
72
+ /**
73
+ * FormErrorSummary component for displaying form validation errors
74
+ *
75
+ * This component provides a user-friendly way to display all form errors
76
+ * in one centralized location, improving the user experience by making
77
+ * it clear what needs to be fixed.
78
+ *
79
+ * @param props - Component configuration
80
+ * @param props.errors - Form validation errors object
81
+ * @param props.title - Title displayed above the error list
82
+ * @param props.showFieldNames - Whether to show field names with errors
83
+ * @returns JSX.Element - The rendered error summary or null if no errors
84
+ */
85
+ export function FormErrorSummary({
86
+ errors = {},
87
+ title = "Please fix the following errors:",
88
+ showFieldNames = false
89
+ }: FormErrorSummaryProps) {
90
+ const errorEntries = Object.entries(errors).filter(([, value]) => Boolean(value));
91
+
92
+ if (errorEntries.length === 0) {
93
+ return null;
94
+ }
95
+
96
+ return (
97
+ <Alert variant="destructive" className="mb-4">
98
+ <div>
99
+ <h3>{title}</h3>
100
+ <ul className="list-disc list-inside space-y-1">
101
+ {errorEntries.map(([field, error], index) => {
102
+ const message = typeof error === 'object' && error.message ? error.message : String(error);
103
+ return (
104
+ <li key={index} className="text-sm">
105
+ {showFieldNames ? `${field}: ${message}` : message}
106
+ </li>
107
+ );
108
+ })}
109
+ </ul>
110
+ </div>
111
+ </Alert>
112
+ );
113
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @file FormFieldset Component
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/Form
5
+ * @since 0.1.0
6
+ *
7
+ * A form fieldset component that groups related form fields together with a legend
8
+ * and optional description. Provides semantic structure and accessibility for form sections.
9
+ *
10
+ * Features:
11
+ * - Semantic fieldset grouping for related form fields
12
+ * - Accessible legend with required indicator
13
+ * - Optional description text with proper ARIA association
14
+ * - Consistent styling with design system
15
+ * - Forwarded ref support
16
+ * - Customizable styling via className
17
+ * - Required field indication
18
+ * - Responsive design
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * // Basic fieldset
23
+ * <FormFieldset legend="Personal Information">
24
+ * <FormField name="firstName" label="First Name" />
25
+ * <FormField name="lastName" label="Last Name" />
26
+ * <FormField name="email" label="Email" />
27
+ * </FormFieldset>
28
+ *
29
+ * // Fieldset with description
30
+ * <FormFieldset
31
+ * legend="Contact Details"
32
+ * description="We'll use this information to contact you about your order"
33
+ * >
34
+ * <FormField name="phone" label="Phone Number" />
35
+ * <FormField name="address" label="Address" />
36
+ * </FormFieldset>
37
+ *
38
+ * // Required fieldset
39
+ * <FormFieldset
40
+ * legend="Account Settings"
41
+ * required={true}
42
+ * description="These settings are required for account creation"
43
+ * >
44
+ * <FormField name="username" label="Username" required />
45
+ * <FormField name="password" label="Password" type="password" required />
46
+ * </FormFieldset>
47
+ *
48
+ * // With custom styling
49
+ * <FormFieldset
50
+ * legend="Preferences"
51
+ * className="bg-sec-50 border-sec-200"
52
+ * >
53
+ * <FormField name="newsletter" label="Subscribe to newsletter" type="checkbox" />
54
+ * <FormField name="notifications" label="Enable notifications" type="checkbox" />
55
+ * </FormFieldset>
56
+ * ```
57
+ *
58
+ * @accessibility
59
+ * - Proper semantic HTML with fieldset and legend
60
+ * - ARIA describedby association for descriptions
61
+ * - Required field indication with asterisk
62
+ * - Screen reader friendly structure
63
+ * - Keyboard navigation support
64
+ * - WCAG 2.1 AA compliant
65
+ *
66
+ * @dependencies
67
+ * - React 18+ - Component framework and hooks
68
+ * - Tailwind CSS - Styling
69
+ * - cn utility - Class name merging
70
+ */
71
+
72
+ import React from 'react';
73
+ import { cn } from '../../utils/cn';
74
+
75
+ export interface FormFieldsetProps extends React.FieldsetHTMLAttributes<HTMLFieldSetElement> {
76
+ /** The legend text for the fieldset */
77
+ legend: string;
78
+ /** Optional description text below the legend */
79
+ description?: string;
80
+ /** Whether the fieldset contains required fields */
81
+ required?: boolean;
82
+ /** Form fields to group within the fieldset */
83
+ children: React.ReactNode;
84
+ }
85
+
86
+ /**
87
+ * FormFieldset component for grouping related form fields
88
+ *
89
+ * This component provides semantic grouping for form fields with proper
90
+ * accessibility support and consistent styling. Use it to organize
91
+ * related form inputs into logical sections.
92
+ *
93
+ * @param props - Component configuration
94
+ * @param props.legend - The legend text for the fieldset
95
+ * @param props.description - Optional description text
96
+ * @param props.required - Whether the fieldset contains required fields
97
+ * @param props.children - Form fields to group
98
+ * @param ref - Forwarded ref to the fieldset element
99
+ * @returns JSX.Element - The rendered fieldset with legend and fields
100
+ */
101
+ export const FormFieldset = React.forwardRef<HTMLFieldSetElement, FormFieldsetProps>(
102
+ ({ legend, description, required, className, children, ...props }, ref) => {
103
+ const descriptionId = React.useId();
104
+
105
+ return (
106
+ <fieldset
107
+ ref={ref}
108
+ className={cn("space-y-4 border border-input rounded-lg p-4", className)}
109
+ aria-describedby={description ? descriptionId : undefined}
110
+ {...props}
111
+ >
112
+ <legend className="px-2">
113
+ {legend}
114
+ {required && <span className="text-destructive ml-1">*</span>}
115
+ </legend>
116
+ {description && (
117
+ <p id={descriptionId} className="text-muted-foreground">
118
+ {description}
119
+ </p>
120
+ )}
121
+ {children}
122
+ </fieldset>
123
+ );
124
+ }
125
+ );
126
+
127
+ FormFieldset.displayName = "FormFieldset";
@@ -0,0 +1,198 @@
1
+ /**
2
+ * @file FormLiveRegion Component
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/Form
5
+ * @since 0.1.0
6
+ *
7
+ * An accessible live region component for form validation announcements.
8
+ * Provides real-time feedback to screen readers about form validation states
9
+ * and error messages, improving accessibility for users with assistive technologies.
10
+ *
11
+ * Features:
12
+ * - Real-time form validation announcements
13
+ * - Configurable politeness levels (polite/assertive)
14
+ * - Customizable delay timing
15
+ * - Field-level and form-level announcements
16
+ * - Success message customization
17
+ * - Automatic error counting and field identification
18
+ * - Accessibility compliant with ARIA live regions
19
+ * - Integration with React Hook Form
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * // Basic live region
24
+ * const { control, formState } = useForm();
25
+ *
26
+ * <Form onSubmit={handleSubmit}>
27
+ * <FormField name="email" label="Email" />
28
+ * <FormField name="password" label="Password" />
29
+ * <FormLiveRegion form={{ control, formState }} />
30
+ * <Button type="submit">Submit</Button>
31
+ * </Form>
32
+ *
33
+ * // With custom configuration
34
+ * <FormLiveRegion
35
+ * form={{ control, formState }}
36
+ * politeness="assertive"
37
+ * delay={1000}
38
+ * successMessage="Your form was submitted successfully!"
39
+ * enableFieldAnnouncements={true}
40
+ * />
41
+ *
42
+ * // In a complex form
43
+ * function ComplexForm() {
44
+ * const methods = useForm({
45
+ * defaultValues: { name: '', email: '', message: '' }
46
+ * });
47
+ *
48
+ * return (
49
+ * <FormProvider {...methods}>
50
+ * <form onSubmit={methods.handleSubmit(onSubmit)}>
51
+ * <FormField name="name" label="Name" />
52
+ * <FormField name="email" label="Email" />
53
+ * <FormField name="message" label="Message" />
54
+ * <FormLiveRegion
55
+ * form={methods}
56
+ * politeness="polite"
57
+ * delay={300}
58
+ * />
59
+ * <Button type="submit">Send Message</Button>
60
+ * </form>
61
+ * </FormProvider>
62
+ * );
63
+ * }
64
+ * ```
65
+ *
66
+ * @accessibility
67
+ * - ARIA live region for screen reader announcements
68
+ * - Configurable politeness levels for different message types
69
+ * - Automatic error counting and field identification
70
+ * - Non-intrusive validation feedback
71
+ * - WCAG 2.1 AA compliant
72
+ * - Screen reader friendly error announcements
73
+ *
74
+ * @dependencies
75
+ * - react-hook-form - Form state management
76
+ * - React 18+ - Hooks and effects
77
+ * - Tailwind CSS - Styling
78
+ */
79
+
80
+ import React, { useEffect, useState } from 'react';
81
+ import { UseFormReturn, FieldValues } from 'react-hook-form';
82
+ import { cn } from '../../utils/cn';
83
+
84
+ /**
85
+ * Props for the FormLiveRegion component
86
+ */
87
+ export interface FormLiveRegionProps<T extends FieldValues> {
88
+ /** React Hook Form instance */
89
+ form: UseFormReturn<T>;
90
+ /** ARIA live region politeness level */
91
+ politeness?: 'polite' | 'assertive';
92
+ /** Delay before announcing messages (in ms) */
93
+ delay?: number;
94
+ /** Custom success message */
95
+ successMessage?: string;
96
+ /** Custom class name */
97
+ className?: string;
98
+ /** Enable field-level validation announcements */
99
+ enableFieldAnnouncements?: boolean;
100
+ }
101
+
102
+ /**
103
+ * FormLiveRegion component for accessible form validation announcements
104
+ */
105
+ export function FormLiveRegion<T extends FieldValues>({
106
+ form,
107
+ politeness = 'polite',
108
+ delay = 500,
109
+ successMessage = 'Form submitted successfully',
110
+ className,
111
+ enableFieldAnnouncements = true
112
+ }: FormLiveRegionProps<T>): React.ReactElement {
113
+ const [message, setMessage] = useState<string>('');
114
+ const { formState } = form;
115
+
116
+ // Handle form-level announcements
117
+ useEffect(() => {
118
+ let timeoutId: NodeJS.Timeout;
119
+
120
+ if (formState.isSubmitting) {
121
+ setMessage('Submitting form...');
122
+ } else if (formState.isSubmitSuccessful) {
123
+ timeoutId = setTimeout(() => {
124
+ setMessage(successMessage);
125
+ }, delay);
126
+ } else if (formState.errors && Object.keys(formState.errors).length > 0) {
127
+ const errorCount = Object.keys(formState.errors).length;
128
+ const errorFields = Object.keys(formState.errors).join(', ');
129
+ setMessage(`Form has ${errorCount} error${errorCount === 1 ? '' : 's'} in: ${errorFields}`);
130
+ } else if (formState.isValid && formState.isDirty) {
131
+ setMessage('Form is valid');
132
+ } else {
133
+ setMessage('');
134
+ }
135
+
136
+ return () => {
137
+ if (timeoutId) {
138
+ clearTimeout(timeoutId);
139
+ }
140
+ };
141
+ }, [formState, delay, successMessage]);
142
+
143
+ // Handle field-level announcements
144
+ useEffect(() => {
145
+ if (!enableFieldAnnouncements) return;
146
+
147
+ let timeoutId: NodeJS.Timeout;
148
+
149
+ const fieldErrors = formState.errors;
150
+ const touchedFields = formState.touchedFields;
151
+
152
+ // Find newly touched fields with errors
153
+ const newErrors = Object.keys(fieldErrors).filter(field => {
154
+ // Type-safe access to touchedFields
155
+ const touchedField = touchedFields as Record<string, boolean>;
156
+ return touchedField[field] && fieldErrors[field as keyof typeof fieldErrors];
157
+ });
158
+
159
+ if (newErrors.length > 0) {
160
+ timeoutId = setTimeout(() => {
161
+ const errorField = newErrors[0];
162
+ const errorData = fieldErrors[errorField as keyof typeof fieldErrors];
163
+ const errorMessage = errorData?.message;
164
+ if (errorMessage) {
165
+ setMessage(`${errorField}: ${errorMessage}`);
166
+ }
167
+ }, delay);
168
+ }
169
+
170
+ return () => {
171
+ if (timeoutId) {
172
+ clearTimeout(timeoutId);
173
+ }
174
+ };
175
+ }, [formState.errors, formState.touchedFields, enableFieldAnnouncements, delay]);
176
+
177
+ if (!message) {
178
+ return <></>;
179
+ }
180
+
181
+ return (
182
+ <div
183
+ role="status"
184
+ aria-live={politeness}
185
+ aria-atomic="true"
186
+ className={cn('sr-only', className)}
187
+ style={{
188
+ position: 'absolute',
189
+ left: '-10000px',
190
+ width: '1px',
191
+ height: '1px',
192
+ overflow: 'hidden'
193
+ }}
194
+ >
195
+ {message}
196
+ </div>
197
+ );
198
+ }
@@ -100,7 +100,7 @@ vi.mock('../Alert/Alert', () => ({
100
100
  }));
101
101
 
102
102
  // Mock the cn utility
103
- vi.mock('../../utils/cn', () => ({
103
+ vi.mock('../../utils/core/cn', () => ({
104
104
  cn: (...classes: any[]) => classes.filter(Boolean).join(' '),
105
105
  }));
106
106