@jmruthers/pace-core 0.5.136 → 0.5.139

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 (292) hide show
  1. package/dist/{DataTable-CYOHOX3O.js → DataTable-JXFCA2BJ.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-NOHEVYVX.js → chunk-5JMOHWDI.js} +417 -319
  8. package/dist/chunk-5JMOHWDI.js.map +1 -0
  9. package/dist/{chunk-FHWWBIHA.js → chunk-6DXZ6V5Q.js} +5 -5
  10. package/dist/{chunk-2TWNJ46Y.js → chunk-6LAAY47Q.js} +2 -2
  11. package/dist/{chunk-444EZN6N.js → chunk-7QCC6MCP.js} +88 -1
  12. package/dist/chunk-7QCC6MCP.js.map +1 -0
  13. package/dist/chunk-BJPBT3CU.js +21 -0
  14. package/dist/chunk-BJPBT3CU.js.map +1 -0
  15. package/dist/{chunk-L6PGMCMD.js → chunk-BOOI7GK2.js} +38 -12
  16. package/dist/chunk-BOOI7GK2.js.map +1 -0
  17. package/dist/{chunk-XARJS7CD.js → chunk-INQLMHPF.js} +2 -2
  18. package/dist/chunk-JISYG63F.js +70 -0
  19. package/dist/chunk-JISYG63F.js.map +1 -0
  20. package/dist/{chunk-SL2YQDR6.js → chunk-MA6EPSGZ.js} +2 -2
  21. package/dist/{chunk-5DPZ5EAT.js → chunk-OWAG3GSU.js} +1 -3
  22. package/dist/{chunk-LTV3XIJJ.js → chunk-T6JN6LH6.js} +4 -4
  23. package/dist/{chunk-HJGGOMQ6.js → chunk-TLT2ZR3L.js} +147 -103
  24. package/dist/chunk-TLT2ZR3L.js.map +1 -0
  25. package/dist/{chunk-4MT5BGGL.js → chunk-YCWDTTUK.js} +4 -6
  26. package/dist/{chunk-4MT5BGGL.js.map → chunk-YCWDTTUK.js.map} +1 -1
  27. package/dist/components.d.ts +1 -1
  28. package/dist/components.js +12 -11
  29. package/dist/components.js.map +1 -1
  30. package/dist/hooks.js +8 -9
  31. package/dist/hooks.js.map +1 -1
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +15 -14
  34. package/dist/index.js.map +1 -1
  35. package/dist/providers.js +3 -4
  36. package/dist/rbac/index.js +8 -9
  37. package/dist/schema-DTDZQe2u.d.ts +28 -0
  38. package/dist/types.d.ts +152 -3
  39. package/dist/types.js +51 -16
  40. package/dist/types.js.map +1 -1
  41. package/dist/utils.d.ts +89 -4
  42. package/dist/utils.js +214 -96
  43. package/dist/utils.js.map +1 -1
  44. package/dist/validation.d.ts +1 -343
  45. package/dist/validation.js +3 -100
  46. package/docs/api/classes/ColumnFactory.md +1 -1
  47. package/docs/api/classes/ErrorBoundary.md +1 -1
  48. package/docs/api/classes/InvalidScopeError.md +1 -1
  49. package/docs/api/classes/MissingUserContextError.md +1 -1
  50. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  51. package/docs/api/classes/PermissionDeniedError.md +1 -1
  52. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  53. package/docs/api/classes/RBACAuditManager.md +1 -1
  54. package/docs/api/classes/RBACCache.md +1 -1
  55. package/docs/api/classes/RBACEngine.md +1 -1
  56. package/docs/api/classes/RBACError.md +1 -1
  57. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  58. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  59. package/docs/api/classes/StorageUtils.md +1 -1
  60. package/docs/api/enums/FileCategory.md +1 -1
  61. package/docs/api/interfaces/AggregateConfig.md +1 -1
  62. package/docs/api/interfaces/BadgeProps.md +27 -0
  63. package/docs/api/interfaces/ButtonProps.md +1 -1
  64. package/docs/api/interfaces/CardProps.md +1 -1
  65. package/docs/api/interfaces/ColorPalette.md +1 -1
  66. package/docs/api/interfaces/ColorShade.md +1 -1
  67. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  68. package/docs/api/interfaces/DataRecord.md +1 -1
  69. package/docs/api/interfaces/DataTableAction.md +1 -1
  70. package/docs/api/interfaces/DataTableColumn.md +1 -1
  71. package/docs/api/interfaces/DataTableProps.md +1 -1
  72. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  73. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  74. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  75. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  76. package/docs/api/interfaces/EventLogoProps.md +1 -1
  77. package/docs/api/interfaces/ExportColumn.md +1 -1
  78. package/docs/api/interfaces/ExportOptions.md +1 -1
  79. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  80. package/docs/api/interfaces/FileMetadata.md +1 -1
  81. package/docs/api/interfaces/FileReference.md +1 -1
  82. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  83. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  84. package/docs/api/interfaces/FileUploadProps.md +1 -1
  85. package/docs/api/interfaces/FooterProps.md +1 -1
  86. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  87. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  88. package/docs/api/interfaces/InputProps.md +1 -1
  89. package/docs/api/interfaces/LabelProps.md +1 -1
  90. package/docs/api/interfaces/LoginFormProps.md +1 -1
  91. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  92. package/docs/api/interfaces/NavigationContextType.md +1 -1
  93. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  94. package/docs/api/interfaces/NavigationItem.md +1 -1
  95. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  96. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  97. package/docs/api/interfaces/Organisation.md +1 -1
  98. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  99. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  100. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  101. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  102. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  103. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  104. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  105. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  106. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  107. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  108. package/docs/api/interfaces/PaletteData.md +1 -1
  109. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  110. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  111. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  112. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  113. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  114. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  115. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  116. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  117. package/docs/api/interfaces/RBACConfig.md +1 -1
  118. package/docs/api/interfaces/RBACLogger.md +1 -1
  119. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  120. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  121. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  122. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  123. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  124. package/docs/api/interfaces/RouteConfig.md +1 -1
  125. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  126. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  127. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  128. package/docs/api/interfaces/StorageConfig.md +1 -1
  129. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  130. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  131. package/docs/api/interfaces/StorageListOptions.md +1 -1
  132. package/docs/api/interfaces/StorageListResult.md +1 -1
  133. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  134. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  135. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  136. package/docs/api/interfaces/StyleImport.md +1 -1
  137. package/docs/api/interfaces/SwitchProps.md +1 -1
  138. package/docs/api/interfaces/ToastActionElement.md +1 -1
  139. package/docs/api/interfaces/ToastProps.md +1 -1
  140. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  141. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  142. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  143. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  144. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  145. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  146. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  147. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  148. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  149. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  150. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  151. package/docs/api/interfaces/UserEventAccess.md +1 -1
  152. package/docs/api/interfaces/UserMenuProps.md +1 -1
  153. package/docs/api/interfaces/UserProfile.md +1 -1
  154. package/docs/api/modules.md +84 -15
  155. package/docs/architecture/README.md +0 -1
  156. package/docs/styles/README.md +0 -2
  157. package/examples/RBAC/CompleteRBACExample.tsx +324 -0
  158. package/examples/RBAC/EventBasedApp.tsx +239 -0
  159. package/examples/RBAC/PermissionExample.tsx +151 -0
  160. package/examples/RBAC/index.ts +13 -0
  161. package/examples/public-pages/CorrectPublicPageImplementation.tsx +301 -0
  162. package/examples/public-pages/PublicEventPage.tsx +274 -0
  163. package/examples/public-pages/PublicPageApp.tsx +308 -0
  164. package/examples/public-pages/PublicPageUsageExample.tsx +216 -0
  165. package/examples/public-pages/index.ts +14 -0
  166. package/package.json +1 -10
  167. package/src/__tests__/TEST_STANDARD.md +92 -0
  168. package/src/components/Badge/Badge.test.tsx +314 -0
  169. package/src/components/Badge/Badge.tsx +304 -0
  170. package/src/components/Badge/index.ts +3 -0
  171. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +217 -0
  172. package/src/components/DataTable/__tests__/styles.test.ts +1 -1
  173. package/src/components/DataTable/components/ColumnFilter.tsx +8 -4
  174. package/src/components/DataTable/components/DataTableBody.tsx +461 -0
  175. package/src/components/DataTable/components/DraggableColumnHeader.tsx +144 -0
  176. package/src/components/DataTable/components/FilterRow.tsx +9 -3
  177. package/src/components/DataTable/components/PaginationControls.tsx +1 -0
  178. package/src/components/DataTable/components/VirtualizedDataTable.tsx +513 -0
  179. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +14 -68
  180. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +62 -0
  181. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +43 -0
  182. package/src/components/DataTable/core/ActionManager.ts +235 -0
  183. package/src/components/DataTable/core/ColumnManager.ts +205 -0
  184. package/src/components/DataTable/core/DataManager.ts +188 -0
  185. package/src/components/DataTable/core/DataTableContext.tsx +181 -0
  186. package/src/components/DataTable/core/LocalDataAdapter.ts +273 -0
  187. package/src/components/DataTable/core/PluginRegistry.ts +229 -0
  188. package/src/components/DataTable/core/StateManager.ts +311 -0
  189. package/src/components/DataTable/core/interfaces.ts +338 -0
  190. package/src/components/DataTable/styles.ts +27 -6
  191. package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +94 -0
  192. package/src/components/DataTable/utils/columnUtils.ts +40 -0
  193. package/src/components/DataTable/utils/debugTools.ts +609 -0
  194. package/src/components/DataTable/utils/index.ts +1 -0
  195. package/src/components/Dialog/README.md +804 -0
  196. package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +611 -0
  197. package/src/components/Dialog/utils/safeHtml.ts +185 -0
  198. package/src/components/Footer/Footer.test.tsx +1 -1
  199. package/src/components/Form/Form.test.tsx +1 -1
  200. package/src/components/Form/FormErrorSummary.tsx +113 -0
  201. package/src/components/Form/FormFieldset.tsx +127 -0
  202. package/src/components/Form/FormLiveRegion.tsx +198 -0
  203. package/src/components/LoginForm/LoginForm.test.tsx +1 -1
  204. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +76 -10
  205. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  206. package/src/components/PasswordReset/PasswordResetForm.test.tsx +597 -0
  207. package/src/components/PasswordReset/PasswordResetForm.tsx +201 -0
  208. package/src/components/PublicLayout/PublicPageDebugger.tsx +104 -0
  209. package/src/components/PublicLayout/PublicPageDiagnostic.tsx +162 -0
  210. package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +1 -1
  211. package/src/components/Select/Select.test.tsx +1 -1
  212. package/src/components/Select/Select.tsx +20 -8
  213. package/src/components/Table/__tests__/Table.test.tsx +1 -1
  214. package/src/components/index.ts +3 -0
  215. package/src/hooks/__tests__/useFileUrl.unit.test.ts +83 -85
  216. package/src/index.ts +4 -0
  217. package/src/rbac/hooks/useCan.test.ts +24 -0
  218. package/src/rbac/hooks/usePermissions.ts +49 -12
  219. package/src/styles/core.css +3 -0
  220. package/src/utils/appConfig.ts +47 -0
  221. package/src/utils/appIdResolver.test.ts +499 -0
  222. package/src/utils/appIdResolver.ts +130 -0
  223. package/src/utils/appNameResolver.simple.test.ts +212 -0
  224. package/src/utils/appNameResolver.test.ts +121 -0
  225. package/src/utils/appNameResolver.ts +191 -0
  226. package/src/utils/audit.ts +127 -0
  227. package/src/utils/auth-utils.ts +96 -0
  228. package/src/utils/bundleAnalysis.ts +129 -0
  229. package/src/utils/cn.ts +7 -0
  230. package/src/utils/debugLogger.ts +67 -0
  231. package/src/utils/deviceFingerprint.ts +215 -0
  232. package/src/utils/dynamicUtils.ts +105 -0
  233. package/src/utils/file-reference.test.ts +788 -0
  234. package/src/utils/file-reference.ts +519 -0
  235. package/src/utils/formatDate.test.ts +237 -0
  236. package/src/utils/formatting.ts +133 -0
  237. package/src/utils/index.ts +7 -0
  238. package/src/utils/lazyLoad.tsx +44 -0
  239. package/src/utils/logger.ts +179 -0
  240. package/src/utils/organisationContext.test.ts +322 -0
  241. package/src/utils/organisationContext.ts +153 -0
  242. package/src/utils/performanceBenchmark.ts +64 -0
  243. package/src/utils/performanceBudgets.ts +110 -0
  244. package/src/utils/permissionTypes.ts +37 -0
  245. package/src/utils/permissionUtils.test.ts +393 -0
  246. package/src/utils/permissionUtils.ts +34 -0
  247. package/src/utils/sanitization.ts +264 -0
  248. package/src/utils/schemaUtils.ts +37 -0
  249. package/src/utils/secureDataAccess.test.ts +711 -0
  250. package/src/utils/secureDataAccess.ts +377 -0
  251. package/src/utils/secureErrors.ts +79 -0
  252. package/src/utils/secureStorage.ts +244 -0
  253. package/src/utils/security.ts +156 -0
  254. package/src/utils/securityMonitor.ts +45 -0
  255. package/src/utils/sessionTracking.ts +126 -0
  256. package/src/utils/validation.ts +111 -0
  257. package/src/utils/validationUtils.ts +120 -0
  258. package/src/validation/index.ts +2 -2
  259. package/dist/chunk-444EZN6N.js.map +0 -1
  260. package/dist/chunk-APIBCTL2.js +0 -670
  261. package/dist/chunk-APIBCTL2.js.map +0 -1
  262. package/dist/chunk-HJGGOMQ6.js.map +0 -1
  263. package/dist/chunk-K2WWTH7O.js +0 -94
  264. package/dist/chunk-K2WWTH7O.js.map +0 -1
  265. package/dist/chunk-L6PGMCMD.js.map +0 -1
  266. package/dist/chunk-LMC26NLJ.js +0 -84
  267. package/dist/chunk-LMC26NLJ.js.map +0 -1
  268. package/dist/chunk-NOHEVYVX.js.map +0 -1
  269. package/dist/chunk-TVYPTYOY.js.map +0 -1
  270. package/dist/validation-8npbysjg.d.ts +0 -177
  271. /package/dist/{DataTable-CYOHOX3O.js.map → DataTable-JXFCA2BJ.js.map} +0 -0
  272. /package/dist/{UnifiedAuthProvider-5E5TUNMS.js.map → UnifiedAuthProvider-XIQQ7LVU.js.map} +0 -0
  273. /package/dist/{chunk-YLKIDTUK.js.map → chunk-22WKWKRX.js.map} +0 -0
  274. /package/dist/{chunk-FHWWBIHA.js.map → chunk-6DXZ6V5Q.js.map} +0 -0
  275. /package/dist/{chunk-2TWNJ46Y.js.map → chunk-6LAAY47Q.js.map} +0 -0
  276. /package/dist/{chunk-XARJS7CD.js.map → chunk-INQLMHPF.js.map} +0 -0
  277. /package/dist/{chunk-SL2YQDR6.js.map → chunk-MA6EPSGZ.js.map} +0 -0
  278. /package/dist/{chunk-5DPZ5EAT.js.map → chunk-OWAG3GSU.js.map} +0 -0
  279. /package/dist/{chunk-LTV3XIJJ.js.map → chunk-T6JN6LH6.js.map} +0 -0
  280. /package/examples/{components → components 2}/DataTable/HierarchicalActionsExample.tsx +0 -0
  281. /package/examples/{components → components 2}/DataTable/HierarchicalExample.tsx +0 -0
  282. /package/examples/{components → components 2}/DataTable/InitialPageSizeExample.tsx +0 -0
  283. /package/examples/{components → components 2}/DataTable/PerformanceExample.tsx +0 -0
  284. /package/examples/{components → components 2}/DataTable/index.ts +0 -0
  285. /package/examples/{components → components 2}/Dialog/BasicHtmlTest.tsx +0 -0
  286. /package/examples/{components → components 2}/Dialog/DebugHtmlExample.tsx +0 -0
  287. /package/examples/{components → components 2}/Dialog/HtmlDialogExample.tsx +0 -0
  288. /package/examples/{components → components 2}/Dialog/ScrollableDialogExample.tsx +0 -0
  289. /package/examples/{components → components 2}/Dialog/SimpleHtmlTest.tsx +0 -0
  290. /package/examples/{components → components 2}/Dialog/SmartDialogExample.tsx +0 -0
  291. /package/examples/{components → components 2}/Dialog/index.ts +0 -0
  292. /package/examples/{components → components 2}/index.ts +0 -0
@@ -147,6 +147,21 @@ describe('[component] FilterRow', () => {
147
147
  const filters = screen.getAllByTestId('column-filter');
148
148
  expect(filters[0]).toHaveAttribute('data-placeholder', expect.stringContaining('Filter'));
149
149
  });
150
+
151
+ it('uses column header text in filter placeholder', () => {
152
+ const columns = [
153
+ columnHelper.accessor('name', {
154
+ header: 'Full Name',
155
+ }),
156
+ ];
157
+ const table = createTable(columns);
158
+ const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
159
+
160
+ render(<FilterRow table={table} visibleColumns={visibleColumns} />);
161
+
162
+ const filter = screen.getByTestId('column-filter');
163
+ expect(filter).toHaveAttribute('data-placeholder', 'Filter Full Name...');
164
+ });
150
165
  });
151
166
 
152
167
  describe('Filter Type Detection', () => {
@@ -245,6 +260,34 @@ describe('[component] FilterRow', () => {
245
260
  expect(filter).toHaveAttribute('data-filter-type', 'select');
246
261
  });
247
262
 
263
+ it('respects explicit filterType: text even when unique values ≤ 10', () => {
264
+ // This test reproduces the bug: filterType: 'text' should not be overridden
265
+ // by auto-detection when there are ≤10 unique values
266
+ const limitedData: TestData[] = Array.from({ length: 7 }, (_, i) => ({
267
+ id: String(i),
268
+ name: `Brand ${i % 7}`,
269
+ email: `user${i}@example.com`,
270
+ age: 20 + i,
271
+ status: ['active', 'inactive', 'pending'][i % 3],
272
+ createdAt: new Date(),
273
+ }));
274
+
275
+ const columns = [
276
+ columnHelper.accessor('name', {
277
+ header: 'Brand',
278
+ filterType: 'text', // Explicitly set to text
279
+ }),
280
+ ];
281
+ const table = createTable(columns, limitedData);
282
+ const visibleColumns = table.getHeaderGroups()[0]?.headers || [];
283
+
284
+ render(<FilterRow table={table} visibleColumns={visibleColumns} />);
285
+
286
+ const filter = screen.getByTestId('column-filter');
287
+ // Should remain as 'text' filter, NOT auto-detect to 'select'
288
+ expect(filter).toHaveAttribute('data-filter-type', 'text');
289
+ });
290
+
248
291
  it('defaults to text filter when no auto-detection matches', () => {
249
292
  // Use data with many unique values (>10) to avoid auto-detection as select
250
293
  const manyUniqueData: TestData[] = Array.from({ length: 15 }, (_, i) => ({
@@ -0,0 +1,235 @@
1
+ /**
2
+ * @file DataTable Action Manager
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Architecture/Managers
5
+ * @since 0.3.0
6
+ */
7
+
8
+ import type { ActionManager } from './interfaces';
9
+ import type { DataTableAction, DataRecord } from '../types';
10
+
11
+ /**
12
+ * Action manager implementation following SRP
13
+ * Responsible only for action operations
14
+ */
15
+ export class ActionManagerImpl<TData extends DataRecord> implements ActionManager<TData> {
16
+ private actions: DataTableAction<TData>[] = [];
17
+ private actionMap = new Map<string, DataTableAction<TData>>();
18
+
19
+ constructor(initialActions: DataTableAction<TData>[] = []) {
20
+ this.setActions(initialActions);
21
+ }
22
+
23
+ /**
24
+ * Get all actions
25
+ */
26
+ getActions(): DataTableAction<TData>[] {
27
+ return [...this.actions];
28
+ }
29
+
30
+ /**
31
+ * Add a new action
32
+ */
33
+ addAction(action: DataTableAction<TData>): void {
34
+ const actionId = this.getActionId(action);
35
+
36
+ // Remove existing action with same ID if it exists
37
+ this.removeAction(actionId);
38
+
39
+ // Add new action
40
+ this.actions.push(action);
41
+ this.actionMap.set(actionId, action);
42
+ }
43
+
44
+ /**
45
+ * Remove an action by ID
46
+ */
47
+ removeAction(id: string): void {
48
+ const index = this.actions.findIndex(action => this.getActionId(action) === id);
49
+ if (index !== -1) {
50
+ this.actions.splice(index, 1);
51
+ this.actionMap.delete(id);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Update an existing action
57
+ */
58
+ updateAction(id: string, updates: Partial<DataTableAction<TData>>): void {
59
+ const existingAction = this.actionMap.get(id);
60
+ if (!existingAction) {
61
+ throw new Error(`Action with ID "${id}" not found`);
62
+ }
63
+
64
+ const updatedAction = { ...existingAction, ...updates };
65
+ const index = this.actions.findIndex(action => this.getActionId(action) === id);
66
+
67
+ if (index !== -1) {
68
+ this.actions[index] = updatedAction;
69
+ this.actionMap.set(id, updatedAction);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Get action by ID
75
+ */
76
+ getAction(id: string): DataTableAction<TData> | undefined {
77
+ return this.actionMap.get(id);
78
+ }
79
+
80
+ /**
81
+ * Check if action exists
82
+ */
83
+ hasAction(id: string): boolean {
84
+ return this.actionMap.has(id);
85
+ }
86
+
87
+ /**
88
+ * Get action IDs
89
+ */
90
+ getActionIds(): string[] {
91
+ return Array.from(this.actionMap.keys());
92
+ }
93
+
94
+ /**
95
+ * Get enabled actions
96
+ */
97
+ getEnabledActions(): DataTableAction<TData>[] {
98
+ return this.actions.filter(action => {
99
+ if (!action.disabled) return true;
100
+ if (typeof action.disabled === 'function') {
101
+ // For function-based disabled, we need a row to check against
102
+ // Since we don't have a specific row here, we'll create a mock row
103
+ // This is not ideal but allows the method to work
104
+ const mockRow = {} as TData;
105
+ return !action.disabled(mockRow);
106
+ }
107
+ return !action.disabled;
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Get actions by variant
113
+ */
114
+ getActionsByVariant(variant: DataTableAction<TData>['variant']): DataTableAction<TData>[] {
115
+ return this.actions.filter(action => action.variant === variant);
116
+ }
117
+
118
+ /**
119
+ * Get destructive actions
120
+ */
121
+ getDestructiveActions(): DataTableAction<TData>[] {
122
+ return this.getActionsByVariant('destructive');
123
+ }
124
+
125
+ /**
126
+ * Get primary actions
127
+ */
128
+ getPrimaryActions(): DataTableAction<TData>[] {
129
+ return this.getActionsByVariant('default');
130
+ }
131
+
132
+ /**
133
+ * Get secondary actions
134
+ */
135
+ getSecondaryActions(): DataTableAction<TData>[] {
136
+ return this.getActionsByVariant('secondary');
137
+ }
138
+
139
+ /**
140
+ * Set all actions at once
141
+ */
142
+ setActions(actions: DataTableAction<TData>[]): void {
143
+ this.actions = [...actions];
144
+ this.actionMap.clear();
145
+
146
+ actions.forEach(action => {
147
+ const id = this.getActionId(action);
148
+ this.actionMap.set(id, action);
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Clear all actions
154
+ */
155
+ clearActions(): void {
156
+ this.actions = [];
157
+ this.actionMap.clear();
158
+ }
159
+
160
+ /**
161
+ * Get action count
162
+ */
163
+ getActionCount(): number {
164
+ return this.actions.length;
165
+ }
166
+
167
+ /**
168
+ * Get action by index
169
+ */
170
+ getActionByIndex(index: number): DataTableAction<TData> | undefined {
171
+ return this.actions[index];
172
+ }
173
+
174
+ /**
175
+ * Get action index by ID
176
+ */
177
+ getActionIndex(id: string): number {
178
+ return this.actions.findIndex(action => this.getActionId(action) === id);
179
+ }
180
+
181
+ /**
182
+ * Move action to new position
183
+ */
184
+ moveAction(id: string, newIndex: number): void {
185
+ const currentIndex = this.getActionIndex(id);
186
+ if (currentIndex === -1) {
187
+ throw new Error(`Action with ID "${id}" not found`);
188
+ }
189
+
190
+ if (newIndex < 0 || newIndex >= this.actions.length) {
191
+ throw new Error(`Invalid index: ${newIndex}`);
192
+ }
193
+
194
+ const action = this.actions[currentIndex];
195
+ this.actions.splice(currentIndex, 1);
196
+ this.actions.splice(newIndex, 0, action);
197
+ }
198
+
199
+ /**
200
+ * Execute an action
201
+ */
202
+ executeAction(id: string, row: TData): void {
203
+ const action = this.actionMap.get(id);
204
+ if (!action) {
205
+ throw new Error(`Action with ID "${id}" not found`);
206
+ }
207
+
208
+ if (action.disabled && typeof action.disabled === 'function' && action.disabled(row)) {
209
+ throw new Error(`Action "${id}" is disabled for this row`);
210
+ }
211
+
212
+ action.onClick(row);
213
+ }
214
+
215
+ /**
216
+ * Check if action is disabled for a specific row
217
+ */
218
+ isActionDisabled(id: string, row: TData): boolean {
219
+ const action = this.actionMap.get(id);
220
+ if (!action) return true;
221
+
222
+ if (typeof action.disabled === 'function') {
223
+ return action.disabled(row);
224
+ }
225
+
226
+ return !!action.disabled;
227
+ }
228
+
229
+ /**
230
+ * Get action ID from action definition
231
+ */
232
+ private getActionId(action: DataTableAction<TData>): string {
233
+ return action.testId || action.label.toLowerCase().replace(/\s+/g, '-') || `action-${Math.random().toString(36).substr(2, 9)}`;
234
+ }
235
+ }
@@ -0,0 +1,205 @@
1
+ /**
2
+ * @file DataTable Column Manager
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Architecture/Managers
5
+ * @since 0.3.0
6
+ */
7
+
8
+ import type { ColumnManager } from './interfaces';
9
+ import type { ColumnDef } from '@tanstack/react-table';
10
+ import type { DataRecord } from '../types';
11
+
12
+ /**
13
+ * Column manager implementation following SRP
14
+ * Responsible only for column operations
15
+ */
16
+ export class ColumnManagerImpl<TData extends DataRecord> implements ColumnManager<TData> {
17
+ private columns: ColumnDef<TData>[] = [];
18
+ private columnMap = new Map<string, ColumnDef<TData>>();
19
+
20
+ constructor(initialColumns: ColumnDef<TData>[] = []) {
21
+ this.setColumns(initialColumns);
22
+ }
23
+
24
+ /**
25
+ * Get all columns
26
+ */
27
+ getColumns(): ColumnDef<TData>[] {
28
+ return [...this.columns];
29
+ }
30
+
31
+ /**
32
+ * Add a new column
33
+ */
34
+ addColumn(column: ColumnDef<TData>): void {
35
+ const columnId = this.getColumnId(column);
36
+
37
+ // Remove existing column with same ID if it exists
38
+ this.removeColumn(columnId);
39
+
40
+ // Add new column
41
+ this.columns.push(column);
42
+ this.columnMap.set(columnId, column);
43
+ }
44
+
45
+ /**
46
+ * Remove a column by ID
47
+ */
48
+ removeColumn(id: string): void {
49
+ const index = this.columns.findIndex(col => this.getColumnId(col) === id);
50
+ if (index !== -1) {
51
+ this.columns.splice(index, 1);
52
+ this.columnMap.delete(id);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Update an existing column
58
+ */
59
+ updateColumn(id: string, updates: Partial<ColumnDef<TData>>): void {
60
+ const existingColumn = this.columnMap.get(id);
61
+ if (!existingColumn) {
62
+ throw new Error(`Column with ID "${id}" not found`);
63
+ }
64
+
65
+ const updatedColumn = { ...existingColumn, ...updates } as ColumnDef<TData>;
66
+ const index = this.columns.findIndex(col => this.getColumnId(col) === id);
67
+
68
+ if (index !== -1) {
69
+ this.columns[index] = updatedColumn;
70
+ this.columnMap.set(id, updatedColumn);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Get a specific column by ID
76
+ */
77
+ getColumn(id: string): ColumnDef<TData> | undefined {
78
+ return this.columnMap.get(id);
79
+ }
80
+
81
+ /**
82
+ * Check if a column exists
83
+ */
84
+ hasColumn(id: string): boolean {
85
+ return this.columnMap.has(id);
86
+ }
87
+
88
+ /**
89
+ * Get column IDs
90
+ */
91
+ getColumnIds(): string[] {
92
+ return Array.from(this.columnMap.keys());
93
+ }
94
+
95
+ /**
96
+ * Get visible columns
97
+ */
98
+ getVisibleColumns(): ColumnDef<TData>[] {
99
+ return this.columns.filter(column => {
100
+ const id = this.getColumnId(column);
101
+ return column.enableHiding !== false;
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Get sortable columns
107
+ */
108
+ getSortableColumns(): ColumnDef<TData>[] {
109
+ return this.columns.filter(column => column.enableSorting === true);
110
+ }
111
+
112
+ /**
113
+ * Get filterable columns
114
+ */
115
+ getFilterableColumns(): ColumnDef<TData>[] {
116
+ return this.columns.filter(column => column.enableColumnFilter === true);
117
+ }
118
+
119
+ /**
120
+ * Get groupable columns
121
+ */
122
+ getGroupableColumns(): ColumnDef<TData>[] {
123
+ return this.columns.filter(column => column.enableGrouping === true);
124
+ }
125
+
126
+ /**
127
+ * Set all columns at once
128
+ */
129
+ setColumns(columns: ColumnDef<TData>[]): void {
130
+ this.columns = [...columns];
131
+ this.columnMap.clear();
132
+
133
+ columns.forEach(column => {
134
+ const id = this.getColumnId(column);
135
+ this.columnMap.set(id, column);
136
+ });
137
+ }
138
+
139
+ /**
140
+ * Clear all columns
141
+ */
142
+ clearColumns(): void {
143
+ this.columns = [];
144
+ this.columnMap.clear();
145
+ }
146
+
147
+ /**
148
+ * Get column count
149
+ */
150
+ getColumnCount(): number {
151
+ return this.columns.length;
152
+ }
153
+
154
+ /**
155
+ * Get column by index
156
+ */
157
+ getColumnByIndex(index: number): ColumnDef<TData> | undefined {
158
+ return this.columns[index];
159
+ }
160
+
161
+ /**
162
+ * Get column index by ID
163
+ */
164
+ getColumnIndex(id: string): number {
165
+ return this.columns.findIndex(col => this.getColumnId(col) === id);
166
+ }
167
+
168
+ /**
169
+ * Move column to new position
170
+ */
171
+ moveColumn(id: string, newIndex: number): void {
172
+ const currentIndex = this.getColumnIndex(id);
173
+ if (currentIndex === -1) {
174
+ throw new Error(`Column with ID "${id}" not found`);
175
+ }
176
+
177
+ if (newIndex < 0 || newIndex >= this.columns.length) {
178
+ throw new Error(`Invalid index: ${newIndex}`);
179
+ }
180
+
181
+ const column = this.columns[currentIndex];
182
+ this.columns.splice(currentIndex, 1);
183
+ this.columns.splice(newIndex, 0, column);
184
+ }
185
+
186
+ /**
187
+ * Get column ID from column definition
188
+ */
189
+ private getColumnId(column: ColumnDef<TData>): string {
190
+ // Handle different column types
191
+ if ('id' in column && column.id) {
192
+ return column.id;
193
+ }
194
+
195
+ if ('accessorKey' in column && column.accessorKey) {
196
+ return String(column.accessorKey);
197
+ }
198
+
199
+ if ('accessorFn' in column && typeof column.accessorFn === 'function') {
200
+ return `fn-${Math.random().toString(36).substr(2, 9)}`;
201
+ }
202
+
203
+ return `fn-${Math.random().toString(36).substr(2, 9)}`;
204
+ }
205
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @file DataTable Data Manager
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Architecture/Managers
5
+ * @since 0.3.0
6
+ */
7
+
8
+ import type { DataTableDataManager, DataAdapter, FetchOptions } from './interfaces';
9
+ import type { DataRecord } from '../types';
10
+
11
+ /**
12
+ * Data manager implementation following SRP
13
+ * Responsible only for data operations
14
+ */
15
+ export class DataManager<TData extends DataRecord> implements DataTableDataManager<TData> {
16
+ private data: TData[] = [];
17
+ private _isLoading = false;
18
+ private error: Error | null = null;
19
+ private adapter: DataAdapter<TData>;
20
+ private cache = new Map<string, { data: TData[]; timestamp: number }>();
21
+ private cacheTimeout: number;
22
+
23
+ constructor(adapter: DataAdapter<TData>, cacheTimeout = 5 * 60 * 1000) {
24
+ this.adapter = adapter;
25
+ this.cacheTimeout = cacheTimeout;
26
+ }
27
+
28
+ /**
29
+ * Get current data
30
+ */
31
+ getData(): TData[] {
32
+ return this.data;
33
+ }
34
+
35
+ /**
36
+ * Update a data item
37
+ */
38
+ async updateData(id: string, data: Partial<TData>): Promise<void> {
39
+ try {
40
+ this.setLoading(true);
41
+ this.setError(null);
42
+
43
+ await this.adapter.updateData(id, data);
44
+
45
+ // Update local data
46
+ const index = this.data.findIndex(item => this.getRowId(item) === id);
47
+ if (index !== -1) {
48
+ this.data[index] = { ...this.data[index], ...data };
49
+ }
50
+
51
+ this.clearCache();
52
+ } catch (error) {
53
+ this.setError(error as Error);
54
+ throw error;
55
+ } finally {
56
+ this.setLoading(false);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Delete a data item
62
+ */
63
+ async deleteData(id: string): Promise<void> {
64
+ try {
65
+ this.setLoading(true);
66
+ this.setError(null);
67
+
68
+ await this.adapter.deleteData(id);
69
+
70
+ // Remove from local data
71
+ this.data = this.data.filter(item => this.getRowId(item) !== id);
72
+
73
+ this.clearCache();
74
+ } catch (error) {
75
+ this.setError(error as Error);
76
+ throw error;
77
+ } finally {
78
+ this.setLoading(false);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Create a new data item
84
+ */
85
+ async createData(data: Partial<TData>): Promise<TData> {
86
+ try {
87
+ this.setLoading(true);
88
+ this.setError(null);
89
+
90
+ const newItem = await this.adapter.createData(data);
91
+
92
+ // Add to local data
93
+ this.data.push(newItem);
94
+
95
+ this.clearCache();
96
+ return newItem;
97
+ } catch (error) {
98
+ this.setError(error as Error);
99
+ throw error;
100
+ } finally {
101
+ this.setLoading(false);
102
+ }
103
+ }
104
+
105
+
106
+ /**
107
+ * Get cached data if available
108
+ */
109
+ async getCachedData(cacheKey: string): Promise<TData[] | null> {
110
+ const cached = this.cache.get(cacheKey);
111
+ if (!cached) return null;
112
+
113
+ const isExpired = Date.now() - cached.timestamp > this.cacheTimeout;
114
+ if (isExpired) {
115
+ this.cache.delete(cacheKey);
116
+ return null;
117
+ }
118
+
119
+ return cached.data;
120
+ }
121
+
122
+ /**
123
+ * Set cached data
124
+ */
125
+ setCachedData(cacheKey: string, data: TData[]): void {
126
+ this.cache.set(cacheKey, {
127
+ data,
128
+ timestamp: Date.now(),
129
+ });
130
+ }
131
+
132
+ /**
133
+ * Clear all cached data
134
+ */
135
+ clearCache(): void {
136
+ this.cache.clear();
137
+ }
138
+
139
+ /**
140
+ * Check if currently loading
141
+ */
142
+ isLoading(): boolean {
143
+ return this._isLoading;
144
+ }
145
+
146
+ /**
147
+ * Get current error
148
+ */
149
+ getError(): Error | null {
150
+ return this.error;
151
+ }
152
+
153
+ /**
154
+ * Set loading state
155
+ */
156
+ private setLoading(loading: boolean): void {
157
+ this._isLoading = loading;
158
+ }
159
+
160
+ /**
161
+ * Set error state
162
+ */
163
+ private setError(error: Error | null): void {
164
+ this.error = error;
165
+ }
166
+
167
+ /**
168
+ * Get row ID from data item
169
+ */
170
+ private getRowId(item: TData): string {
171
+ return (item as any).id || (item as any).key || JSON.stringify(item);
172
+ }
173
+
174
+ /**
175
+ * Set adapter
176
+ */
177
+ setAdapter(adapter: DataAdapter<TData>): void {
178
+ this.adapter = adapter;
179
+ this.clearCache();
180
+ }
181
+
182
+ /**
183
+ * Get adapter
184
+ */
185
+ getAdapter(): DataAdapter<TData> {
186
+ return this.adapter;
187
+ }
188
+ }