@maxal_studio/kratosjs-react 1.0.0

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 (529) hide show
  1. package/README.md +44 -0
  2. package/dist/FieldRenderer.d.ts +13 -0
  3. package/dist/FieldRenderer.js +62 -0
  4. package/dist/FormRenderer.d.ts +7 -0
  5. package/dist/FormRenderer.js +78 -0
  6. package/dist/TableRenderer.d.ts +2 -0
  7. package/dist/TableRenderer.js +1 -0
  8. package/dist/api/actionsApi.d.ts +23 -0
  9. package/dist/api/actionsApi.js +46 -0
  10. package/dist/api/authenticatedFetch.d.ts +8 -0
  11. package/dist/api/authenticatedFetch.js +31 -0
  12. package/dist/api/exportApi.d.ts +18 -0
  13. package/dist/api/exportApi.js +50 -0
  14. package/dist/api/http.d.ts +24 -0
  15. package/dist/api/http.js +52 -0
  16. package/dist/api/resourceApi.d.ts +37 -0
  17. package/dist/api/resourceApi.js +52 -0
  18. package/dist/api/tableApi.d.ts +83 -0
  19. package/dist/api/tableApi.js +51 -0
  20. package/dist/api/urls.d.ts +19 -0
  21. package/dist/api/urls.js +46 -0
  22. package/dist/app.d.ts +101 -0
  23. package/dist/app.js +89 -0
  24. package/dist/auth/AuthContext.d.ts +22 -0
  25. package/dist/auth/AuthContext.js +147 -0
  26. package/dist/auth/LoginPage.d.ts +10 -0
  27. package/dist/auth/LoginPage.js +179 -0
  28. package/dist/auth/ProtectedRoute.d.ts +12 -0
  29. package/dist/auth/ProtectedRoute.js +22 -0
  30. package/dist/auth/authApiClient.d.ts +24 -0
  31. package/dist/auth/authApiClient.js +95 -0
  32. package/dist/auth/types.d.ts +103 -0
  33. package/dist/auth/types.js +1 -0
  34. package/dist/components/ActionFormModal.d.ts +22 -0
  35. package/dist/components/ActionFormModal.js +8 -0
  36. package/dist/components/AdminPanel.d.ts +11 -0
  37. package/dist/components/AdminPanel.js +194 -0
  38. package/dist/components/Checkbox.d.ts +10 -0
  39. package/dist/components/Checkbox.js +8 -0
  40. package/dist/components/CheckboxField.d.ts +7 -0
  41. package/dist/components/CheckboxField.js +26 -0
  42. package/dist/components/ColorPickerField.d.ts +7 -0
  43. package/dist/components/ColorPickerField.js +26 -0
  44. package/dist/components/DateTimePickerField.d.ts +7 -0
  45. package/dist/components/DateTimePickerField.js +64 -0
  46. package/dist/components/FileUploadField.d.ts +9 -0
  47. package/dist/components/FileUploadField.js +478 -0
  48. package/dist/components/GlobalSearch.d.ts +22 -0
  49. package/dist/components/GlobalSearch.js +181 -0
  50. package/dist/components/GroupField.d.ts +7 -0
  51. package/dist/components/GroupField.js +23 -0
  52. package/dist/components/HiddenField.d.ts +3 -0
  53. package/dist/components/HiddenField.js +10 -0
  54. package/dist/components/ModalBreadcrumb.d.ts +5 -0
  55. package/dist/components/ModalBreadcrumb.js +33 -0
  56. package/dist/components/ModalDrawer.d.ts +15 -0
  57. package/dist/components/ModalDrawer.js +40 -0
  58. package/dist/components/RadioField.d.ts +7 -0
  59. package/dist/components/RadioField.js +26 -0
  60. package/dist/components/RepeaterField.d.ts +3 -0
  61. package/dist/components/RepeaterField.js +191 -0
  62. package/dist/components/ResourceModalRenderer.d.ts +10 -0
  63. package/dist/components/ResourceModalRenderer.js +80 -0
  64. package/dist/components/RichEditorField.d.ts +3 -0
  65. package/dist/components/RichEditorField.js +655 -0
  66. package/dist/components/SectionField.d.ts +9 -0
  67. package/dist/components/SectionField.js +111 -0
  68. package/dist/components/SelectField.d.ts +8 -0
  69. package/dist/components/SelectField.js +523 -0
  70. package/dist/components/TabsField.d.ts +10 -0
  71. package/dist/components/TabsField.js +214 -0
  72. package/dist/components/TagsInputField.d.ts +7 -0
  73. package/dist/components/TagsInputField.js +172 -0
  74. package/dist/components/TextInputField.d.ts +7 -0
  75. package/dist/components/TextInputField.js +44 -0
  76. package/dist/components/TextareaField.d.ts +7 -0
  77. package/dist/components/TextareaField.js +31 -0
  78. package/dist/components/ToggleField.d.ts +7 -0
  79. package/dist/components/ToggleField.js +57 -0
  80. package/dist/components/ViewModal.d.ts +25 -0
  81. package/dist/components/ViewModal.js +159 -0
  82. package/dist/components/blocks/BlockRenderer.d.ts +7 -0
  83. package/dist/components/blocks/BlockRenderer.js +36 -0
  84. package/dist/components/blocks/FormBlockRenderer.d.ts +6 -0
  85. package/dist/components/blocks/FormBlockRenderer.js +110 -0
  86. package/dist/components/blocks/TableBlockRenderer.d.ts +6 -0
  87. package/dist/components/blocks/TableBlockRenderer.js +12 -0
  88. package/dist/components/blocks/TabsBlockRenderer.d.ts +7 -0
  89. package/dist/components/blocks/TabsBlockRenderer.js +11 -0
  90. package/dist/components/blocks/WidgetBlockRenderer.d.ts +6 -0
  91. package/dist/components/blocks/WidgetBlockRenderer.js +11 -0
  92. package/dist/components/columns/CheckboxColumnComponent.d.ts +6 -0
  93. package/dist/components/columns/CheckboxColumnComponent.js +21 -0
  94. package/dist/components/columns/ColorColumnComponent.d.ts +3 -0
  95. package/dist/components/columns/ColorColumnComponent.js +11 -0
  96. package/dist/components/columns/DeeplinkWrapper.d.ts +15 -0
  97. package/dist/components/columns/DeeplinkWrapper.js +85 -0
  98. package/dist/components/columns/IconColumnComponent.d.ts +3 -0
  99. package/dist/components/columns/IconColumnComponent.js +52 -0
  100. package/dist/components/columns/ImageColumnComponent.d.ts +3 -0
  101. package/dist/components/columns/ImageColumnComponent.js +98 -0
  102. package/dist/components/columns/MediaColumnComponent.d.ts +3 -0
  103. package/dist/components/columns/MediaColumnComponent.js +160 -0
  104. package/dist/components/columns/SelectColumnComponent.d.ts +6 -0
  105. package/dist/components/columns/SelectColumnComponent.js +26 -0
  106. package/dist/components/columns/TagsColumnComponent.d.ts +3 -0
  107. package/dist/components/columns/TagsColumnComponent.js +18 -0
  108. package/dist/components/columns/TextColumnComponent.d.ts +11 -0
  109. package/dist/components/columns/TextColumnComponent.js +107 -0
  110. package/dist/components/columns/TextInputColumnComponent.d.ts +6 -0
  111. package/dist/components/columns/TextInputColumnComponent.js +18 -0
  112. package/dist/components/columns/ToggleColumnComponent.d.ts +6 -0
  113. package/dist/components/columns/ToggleColumnComponent.js +25 -0
  114. package/dist/components/columns/VideoColumnComponent.d.ts +3 -0
  115. package/dist/components/columns/VideoColumnComponent.js +125 -0
  116. package/dist/components/columns/ViewColumnComponent.d.ts +3 -0
  117. package/dist/components/columns/ViewColumnComponent.js +7 -0
  118. package/dist/components/errors/ErrorBoundary.d.ts +23 -0
  119. package/dist/components/errors/ErrorBoundary.js +33 -0
  120. package/dist/components/filters/CustomFilterComponent.d.ts +10 -0
  121. package/dist/components/filters/CustomFilterComponent.js +33 -0
  122. package/dist/components/filters/DateFilterComponent.d.ts +15 -0
  123. package/dist/components/filters/DateFilterComponent.js +132 -0
  124. package/dist/components/filters/QueryBuilderFilterComponent.d.ts +11 -0
  125. package/dist/components/filters/QueryBuilderFilterComponent.js +200 -0
  126. package/dist/components/layout/Header.d.ts +10 -0
  127. package/dist/components/layout/Header.js +70 -0
  128. package/dist/components/layout/PanelBrandMark.d.ts +8 -0
  129. package/dist/components/layout/PanelBrandMark.js +28 -0
  130. package/dist/components/layout/Sidebar.d.ts +35 -0
  131. package/dist/components/layout/Sidebar.js +125 -0
  132. package/dist/components/modals/RelationCreateModal.d.ts +19 -0
  133. package/dist/components/modals/RelationCreateModal.js +57 -0
  134. package/dist/components/modals/ResourceFormModal.d.ts +37 -0
  135. package/dist/components/modals/ResourceFormModal.js +44 -0
  136. package/dist/components/modals/useResourceForm.d.ts +40 -0
  137. package/dist/components/modals/useResourceForm.js +138 -0
  138. package/dist/components/modals/view/RecordActions.d.ts +17 -0
  139. package/dist/components/modals/view/RecordActions.js +16 -0
  140. package/dist/components/modals/view/RecordDetails.d.ts +13 -0
  141. package/dist/components/modals/view/RecordDetails.js +29 -0
  142. package/dist/components/modals/view/RelationPanel.d.ts +18 -0
  143. package/dist/components/modals/view/RelationPanel.js +16 -0
  144. package/dist/components/modals/view/RelationTabs.d.ts +32 -0
  145. package/dist/components/modals/view/RelationTabs.js +42 -0
  146. package/dist/components/modals/view/useRecordView.d.ts +18 -0
  147. package/dist/components/modals/view/useRecordView.js +114 -0
  148. package/dist/components/pages/PageRenderer.d.ts +6 -0
  149. package/dist/components/pages/PageRenderer.js +107 -0
  150. package/dist/components/table/ColumnTogglePopup.d.ts +11 -0
  151. package/dist/components/table/ColumnTogglePopup.js +16 -0
  152. package/dist/components/table/GridCard.d.ts +21 -0
  153. package/dist/components/table/GridCard.js +30 -0
  154. package/dist/components/table/GridView.d.ts +23 -0
  155. package/dist/components/table/GridView.js +49 -0
  156. package/dist/components/table/LayoutToggle.d.ts +7 -0
  157. package/dist/components/table/LayoutToggle.js +9 -0
  158. package/dist/components/table/TableActionsDropdown.d.ts +13 -0
  159. package/dist/components/table/TableActionsDropdown.js +46 -0
  160. package/dist/components/table/TableBulkActions.d.ts +11 -0
  161. package/dist/components/table/TableBulkActions.js +21 -0
  162. package/dist/components/table/TableHeader.d.ts +14 -0
  163. package/dist/components/table/TableHeader.js +23 -0
  164. package/dist/components/table/TablePagination.d.ts +13 -0
  165. package/dist/components/table/TablePagination.js +55 -0
  166. package/dist/components/table/TableRow.d.ts +21 -0
  167. package/dist/components/table/TableRow.js +32 -0
  168. package/dist/components/table/TableSearchBar.d.ts +11 -0
  169. package/dist/components/table/TableSearchBar.js +12 -0
  170. package/dist/components/table/TableTabs.d.ts +14 -0
  171. package/dist/components/table/TableTabs.js +8 -0
  172. package/dist/components/ui/Badge.d.ts +6 -0
  173. package/dist/components/ui/Badge.js +12 -0
  174. package/dist/components/ui/Button.d.ts +22 -0
  175. package/dist/components/ui/Button.js +22 -0
  176. package/dist/components/ui/Card.d.ts +7 -0
  177. package/dist/components/ui/Card.js +5 -0
  178. package/dist/components/ui/ConfirmDialog.d.ts +19 -0
  179. package/dist/components/ui/ConfirmDialog.js +45 -0
  180. package/dist/components/ui/EmptyState.d.ts +9 -0
  181. package/dist/components/ui/EmptyState.js +6 -0
  182. package/dist/components/ui/ErrorAlert.d.ts +7 -0
  183. package/dist/components/ui/ErrorAlert.js +9 -0
  184. package/dist/components/ui/Input.d.ts +11 -0
  185. package/dist/components/ui/Input.js +10 -0
  186. package/dist/components/ui/Label.d.ts +5 -0
  187. package/dist/components/ui/Label.js +5 -0
  188. package/dist/components/ui/PillButton.d.ts +14 -0
  189. package/dist/components/ui/PillButton.js +19 -0
  190. package/dist/components/ui/Select.d.ts +7 -0
  191. package/dist/components/ui/Select.js +7 -0
  192. package/dist/components/ui/Spinner.d.ts +8 -0
  193. package/dist/components/ui/Spinner.js +14 -0
  194. package/dist/components/ui/Toast.d.ts +21 -0
  195. package/dist/components/ui/Toast.js +47 -0
  196. package/dist/components/ui/index.d.ts +24 -0
  197. package/dist/components/ui/index.js +12 -0
  198. package/dist/components/utils/HintDisplay.d.ts +11 -0
  199. package/dist/components/utils/HintDisplay.js +12 -0
  200. package/dist/components/utils/Icon.d.ts +22 -0
  201. package/dist/components/utils/Icon.js +22 -0
  202. package/dist/components/utils/MediaPreviewModal.d.ts +14 -0
  203. package/dist/components/utils/MediaPreviewModal.js +32 -0
  204. package/dist/components/utils/ViewFieldWrapper.d.ts +11 -0
  205. package/dist/components/utils/ViewFieldWrapper.js +9 -0
  206. package/dist/components/utils/layoutHelpers.d.ts +19 -0
  207. package/dist/components/utils/layoutHelpers.js +257 -0
  208. package/dist/components/widgets/ChartWidget.d.ts +16 -0
  209. package/dist/components/widgets/ChartWidget.js +192 -0
  210. package/dist/components/widgets/StatsWidget.d.ts +16 -0
  211. package/dist/components/widgets/StatsWidget.js +39 -0
  212. package/dist/components/widgets/WidgetRenderer.d.ts +10 -0
  213. package/dist/components/widgets/WidgetRenderer.js +50 -0
  214. package/dist/components/widgets/WidgetShell.d.ts +9 -0
  215. package/dist/components/widgets/WidgetShell.js +7 -0
  216. package/dist/contexts/AuthChallengeRegistryContext.d.ts +15 -0
  217. package/dist/contexts/AuthChallengeRegistryContext.js +15 -0
  218. package/dist/contexts/BlockRegistryContext.d.ts +18 -0
  219. package/dist/contexts/BlockRegistryContext.js +8 -0
  220. package/dist/contexts/ColumnRegistryContext.d.ts +8 -0
  221. package/dist/contexts/ColumnRegistryContext.js +30 -0
  222. package/dist/contexts/FieldRegistryContext.d.ts +13 -0
  223. package/dist/contexts/FieldRegistryContext.js +46 -0
  224. package/dist/contexts/PanelMetadataContext.d.ts +26 -0
  225. package/dist/contexts/PanelMetadataContext.js +26 -0
  226. package/dist/contexts/PanelProviders.d.ts +27 -0
  227. package/dist/contexts/PanelProviders.js +24 -0
  228. package/dist/contexts/ResourceModalContext.d.ts +26 -0
  229. package/dist/contexts/ResourceModalContext.js +76 -0
  230. package/dist/contexts/SlotRegistryContext.d.ts +19 -0
  231. package/dist/contexts/SlotRegistryContext.js +24 -0
  232. package/dist/contexts/TableRefreshContext.d.ts +10 -0
  233. package/dist/contexts/TableRefreshContext.js +30 -0
  234. package/dist/contexts/WidgetRegistryContext.d.ts +17 -0
  235. package/dist/contexts/WidgetRegistryContext.js +14 -0
  236. package/dist/contexts/createRegistryContext.d.ts +19 -0
  237. package/dist/contexts/createRegistryContext.js +20 -0
  238. package/dist/hooks/useAfterStateUpdated.d.ts +6 -0
  239. package/dist/hooks/useAfterStateUpdated.js +62 -0
  240. package/dist/hooks/useValidation.d.ts +26 -0
  241. package/dist/hooks/useValidation.js +76 -0
  242. package/dist/i18n/I18nProvider.d.ts +27 -0
  243. package/dist/i18n/I18nProvider.js +101 -0
  244. package/dist/i18n/LocaleSwitcher.d.ts +10 -0
  245. package/dist/i18n/LocaleSwitcher.js +30 -0
  246. package/dist/i18n/activeLocale.d.ts +11 -0
  247. package/dist/i18n/activeLocale.js +34 -0
  248. package/dist/i18n/buildClientI18n.d.ts +28 -0
  249. package/dist/i18n/buildClientI18n.js +67 -0
  250. package/dist/i18n/index.d.ts +11 -0
  251. package/dist/i18n/index.js +9 -0
  252. package/dist/i18n/locales/core/en.d.ts +225 -0
  253. package/dist/i18n/locales/core/en.js +252 -0
  254. package/dist/i18n/locales/core/index.d.ts +2 -0
  255. package/dist/i18n/locales/core/index.js +4 -0
  256. package/dist/i18n/locales/core/sq.d.ts +253 -0
  257. package/dist/i18n/locales/core/sq.js +255 -0
  258. package/dist/i18n/useFormatter.d.ts +18 -0
  259. package/dist/i18n/useFormatter.js +37 -0
  260. package/dist/i18n/useLocale.d.ts +11 -0
  261. package/dist/i18n/useLocale.js +11 -0
  262. package/dist/i18n/useTranslation.d.ts +12 -0
  263. package/dist/i18n/useTranslation.js +12 -0
  264. package/dist/index.d.ts +106 -0
  265. package/dist/index.js +101 -0
  266. package/dist/pages/ResourceListPage.d.ts +8 -0
  267. package/dist/pages/ResourceListPage.js +139 -0
  268. package/dist/plugin.d.ts +79 -0
  269. package/dist/plugin.js +34 -0
  270. package/dist/runtime/conditions.d.ts +35 -0
  271. package/dist/runtime/conditions.js +97 -0
  272. package/dist/runtime/formTraversal.d.ts +25 -0
  273. package/dist/runtime/formTraversal.js +37 -0
  274. package/dist/runtime/serializedFunctions.d.ts +41 -0
  275. package/dist/runtime/serializedFunctions.js +264 -0
  276. package/dist/slots/Slot.d.ts +24 -0
  277. package/dist/slots/Slot.js +29 -0
  278. package/dist/slots/SlotCluster.d.ts +22 -0
  279. package/dist/slots/SlotCluster.js +49 -0
  280. package/dist/slots/index.d.ts +7 -0
  281. package/dist/slots/index.js +4 -0
  282. package/dist/slots/mergeSlots.d.ts +18 -0
  283. package/dist/slots/mergeSlots.js +35 -0
  284. package/dist/slots/types.d.ts +87 -0
  285. package/dist/slots/types.js +30 -0
  286. package/dist/styles.css +1 -0
  287. package/dist/table/TableContext.d.ts +36 -0
  288. package/dist/table/TableContext.js +13 -0
  289. package/dist/table/TableRenderer.d.ts +29 -0
  290. package/dist/table/TableRenderer.js +159 -0
  291. package/dist/table/components/FiltersPanel.d.ts +11 -0
  292. package/dist/table/components/FiltersPanel.js +52 -0
  293. package/dist/table/components/TableToolbar.d.ts +28 -0
  294. package/dist/table/components/TableToolbar.js +27 -0
  295. package/dist/table/components/TableToolbarButton.d.ts +6 -0
  296. package/dist/table/components/TableToolbarButton.js +9 -0
  297. package/dist/table/components/TableView.d.ts +12 -0
  298. package/dist/table/components/TableView.js +21 -0
  299. package/dist/table/defaultRowActions.d.ts +21 -0
  300. package/dist/table/defaultRowActions.js +37 -0
  301. package/dist/table/hooks/useColumnVisibility.d.ts +13 -0
  302. package/dist/table/hooks/useColumnVisibility.js +59 -0
  303. package/dist/table/hooks/useEditableRows.d.ts +22 -0
  304. package/dist/table/hooks/useEditableRows.js +63 -0
  305. package/dist/table/hooks/useTableActions.d.ts +54 -0
  306. package/dist/table/hooks/useTableActions.js +313 -0
  307. package/dist/table/hooks/useTableData.d.ts +28 -0
  308. package/dist/table/hooks/useTableData.js +63 -0
  309. package/dist/table/hooks/useTableLayout.d.ts +12 -0
  310. package/dist/table/hooks/useTableLayout.js +31 -0
  311. package/dist/table/hooks/useTableQuery.d.ts +29 -0
  312. package/dist/table/hooks/useTableQuery.js +135 -0
  313. package/dist/types/index.d.ts +224 -0
  314. package/dist/types/index.js +6 -0
  315. package/dist/utils/classNames.d.ts +7 -0
  316. package/dist/utils/classNames.js +9 -0
  317. package/dist/utils/columnMediaDimensions.d.ts +13 -0
  318. package/dist/utils/columnMediaDimensions.js +29 -0
  319. package/dist/utils/columnVisibilityStorage.d.ts +22 -0
  320. package/dist/utils/columnVisibilityStorage.js +56 -0
  321. package/dist/utils/fieldErrors.d.ts +13 -0
  322. package/dist/utils/fieldErrors.js +25 -0
  323. package/dist/utils/formatValue.d.ts +28 -0
  324. package/dist/utils/formatValue.js +109 -0
  325. package/dist/utils/layoutStorage.d.ts +23 -0
  326. package/dist/utils/layoutStorage.js +53 -0
  327. package/dist/utils/redirectHandler.d.ts +7 -0
  328. package/dist/utils/redirectHandler.js +25 -0
  329. package/dist/utils/tableFormatters.d.ts +14 -0
  330. package/dist/utils/tableFormatters.js +93 -0
  331. package/dist/utils/widgetVisibilityStorage.d.ts +11 -0
  332. package/dist/utils/widgetVisibilityStorage.js +39 -0
  333. package/package.json +101 -0
  334. package/src/FieldRenderer.test.tsx +44 -0
  335. package/src/FieldRenderer.tsx +104 -0
  336. package/src/FormRenderer.containers.test.tsx +121 -0
  337. package/src/FormRenderer.test.tsx +174 -0
  338. package/src/FormRenderer.tsx +140 -0
  339. package/src/TableRenderer.tsx +2 -0
  340. package/src/api/actionsApi.ts +76 -0
  341. package/src/api/authenticatedFetch.ts +40 -0
  342. package/src/api/exportApi.ts +66 -0
  343. package/src/api/http.test.ts +58 -0
  344. package/src/api/http.ts +68 -0
  345. package/src/api/resourceApi.ts +88 -0
  346. package/src/api/tableApi.test.ts +108 -0
  347. package/src/api/tableApi.ts +107 -0
  348. package/src/api/urls.ts +50 -0
  349. package/src/app.test.tsx +67 -0
  350. package/src/app.tsx +181 -0
  351. package/src/auth/AuthContext.tsx +188 -0
  352. package/src/auth/LoginPage.tsx +380 -0
  353. package/src/auth/ProtectedRoute.tsx +39 -0
  354. package/src/auth/authApiClient.ts +109 -0
  355. package/src/auth/authFlow.test.tsx +168 -0
  356. package/src/auth/types.ts +104 -0
  357. package/src/components/ActionFormModal.tsx +45 -0
  358. package/src/components/AdminPanel.tsx +368 -0
  359. package/src/components/Checkbox.tsx +59 -0
  360. package/src/components/CheckboxField.tsx +88 -0
  361. package/src/components/ColorPickerField.tsx +93 -0
  362. package/src/components/DateTimePickerField.tsx +112 -0
  363. package/src/components/FileUploadField.tsx +841 -0
  364. package/src/components/GlobalSearch.tsx +436 -0
  365. package/src/components/GroupField.tsx +85 -0
  366. package/src/components/HiddenField.tsx +14 -0
  367. package/src/components/ModalBreadcrumb.tsx +74 -0
  368. package/src/components/ModalDrawer.tsx +137 -0
  369. package/src/components/RadioField.tsx +80 -0
  370. package/src/components/RepeaterField.tsx +546 -0
  371. package/src/components/ResourceModalRenderer.tsx +144 -0
  372. package/src/components/RichEditorField.tsx +942 -0
  373. package/src/components/SectionField.tsx +242 -0
  374. package/src/components/SelectField.tsx +843 -0
  375. package/src/components/TabsField.test.tsx +151 -0
  376. package/src/components/TabsField.tsx +386 -0
  377. package/src/components/TagsInputField.tsx +411 -0
  378. package/src/components/TextInputField.tsx +91 -0
  379. package/src/components/TextareaField.tsx +110 -0
  380. package/src/components/ToggleField.tsx +126 -0
  381. package/src/components/ViewModal.tsx +353 -0
  382. package/src/components/blocks/BlockRenderer.tsx +56 -0
  383. package/src/components/blocks/FormBlockRenderer.tsx +160 -0
  384. package/src/components/blocks/TableBlockRenderer.tsx +33 -0
  385. package/src/components/blocks/TabsBlockRenderer.tsx +49 -0
  386. package/src/components/blocks/WidgetBlockRenderer.tsx +19 -0
  387. package/src/components/columns/CheckboxColumnComponent.tsx +38 -0
  388. package/src/components/columns/ColorColumnComponent.tsx +23 -0
  389. package/src/components/columns/CustomColumn.test.tsx +55 -0
  390. package/src/components/columns/DeeplinkWrapper.tsx +103 -0
  391. package/src/components/columns/IconColumnComponent.tsx +55 -0
  392. package/src/components/columns/ImageColumnComponent.tsx +220 -0
  393. package/src/components/columns/MediaColumnComponent.tsx +294 -0
  394. package/src/components/columns/SelectColumnComponent.tsx +49 -0
  395. package/src/components/columns/TagsColumnComponent.tsx +46 -0
  396. package/src/components/columns/TextColumnComponent.tsx +191 -0
  397. package/src/components/columns/TextInputColumnComponent.tsx +35 -0
  398. package/src/components/columns/ToggleColumnComponent.tsx +56 -0
  399. package/src/components/columns/VideoColumnComponent.tsx +236 -0
  400. package/src/components/columns/ViewColumnComponent.tsx +9 -0
  401. package/src/components/errors/ErrorBoundary.tsx +58 -0
  402. package/src/components/filters/CustomFilterComponent.tsx +130 -0
  403. package/src/components/filters/DateFilterComponent.tsx +272 -0
  404. package/src/components/filters/QueryBuilderFilterComponent.tsx +502 -0
  405. package/src/components/layout/Header.tsx +212 -0
  406. package/src/components/layout/PanelBrandMark.tsx +61 -0
  407. package/src/components/layout/Sidebar.tsx +283 -0
  408. package/src/components/modals/RelationCreateModal.tsx +107 -0
  409. package/src/components/modals/ResourceFormModal.test.tsx +119 -0
  410. package/src/components/modals/ResourceFormModal.tsx +128 -0
  411. package/src/components/modals/useResourceForm.ts +207 -0
  412. package/src/components/modals/view/RecordActions.tsx +69 -0
  413. package/src/components/modals/view/RecordDetails.tsx +60 -0
  414. package/src/components/modals/view/RelationPanel.tsx +76 -0
  415. package/src/components/modals/view/RelationTabs.tsx +145 -0
  416. package/src/components/modals/view/useRecordView.ts +134 -0
  417. package/src/components/pages/PageRenderer.tsx +173 -0
  418. package/src/components/table/ColumnTogglePopup.tsx +85 -0
  419. package/src/components/table/GridCard.tsx +155 -0
  420. package/src/components/table/GridView.tsx +138 -0
  421. package/src/components/table/LayoutToggle.tsx +24 -0
  422. package/src/components/table/TableActionsDropdown.tsx +114 -0
  423. package/src/components/table/TableBulkActions.tsx +65 -0
  424. package/src/components/table/TableHeader.tsx +96 -0
  425. package/src/components/table/TablePagination.tsx +169 -0
  426. package/src/components/table/TableRow.tsx +155 -0
  427. package/src/components/table/TableSearchBar.tsx +66 -0
  428. package/src/components/table/TableTabs.tsx +49 -0
  429. package/src/components/ui/Badge.tsx +30 -0
  430. package/src/components/ui/Button.test.tsx +78 -0
  431. package/src/components/ui/Button.tsx +102 -0
  432. package/src/components/ui/Card.tsx +23 -0
  433. package/src/components/ui/ConfirmDialog.tsx +112 -0
  434. package/src/components/ui/EmptyState.tsx +24 -0
  435. package/src/components/ui/ErrorAlert.tsx +37 -0
  436. package/src/components/ui/Input.tsx +48 -0
  437. package/src/components/ui/Label.tsx +15 -0
  438. package/src/components/ui/PillButton.tsx +72 -0
  439. package/src/components/ui/Select.tsx +33 -0
  440. package/src/components/ui/Spinner.tsx +39 -0
  441. package/src/components/ui/Toast.tsx +105 -0
  442. package/src/components/ui/index.ts +24 -0
  443. package/src/components/utils/HintDisplay.tsx +26 -0
  444. package/src/components/utils/Icon.tsx +36 -0
  445. package/src/components/utils/MediaPreviewModal.tsx +114 -0
  446. package/src/components/utils/ViewFieldWrapper.tsx +23 -0
  447. package/src/components/utils/layoutHelpers.ts +267 -0
  448. package/src/components/widgets/ChartWidget.tsx +247 -0
  449. package/src/components/widgets/StatsWidget.tsx +72 -0
  450. package/src/components/widgets/WidgetRenderer.tsx +108 -0
  451. package/src/components/widgets/WidgetShell.tsx +37 -0
  452. package/src/contexts/AuthChallengeRegistryContext.tsx +29 -0
  453. package/src/contexts/BlockRegistryContext.tsx +28 -0
  454. package/src/contexts/ColumnRegistryContext.tsx +38 -0
  455. package/src/contexts/FieldRegistryContext.tsx +56 -0
  456. package/src/contexts/PanelMetadataContext.tsx +60 -0
  457. package/src/contexts/PanelProviders.tsx +85 -0
  458. package/src/contexts/ResourceModalContext.tsx +137 -0
  459. package/src/contexts/SlotRegistryContext.tsx +35 -0
  460. package/src/contexts/TableRefreshContext.tsx +44 -0
  461. package/src/contexts/WidgetRegistryContext.tsx +34 -0
  462. package/src/contexts/createRegistryContext.tsx +29 -0
  463. package/src/hooks/useAfterStateUpdated.ts +70 -0
  464. package/src/hooks/useValidation.test.ts +59 -0
  465. package/src/hooks/useValidation.ts +95 -0
  466. package/src/i18n/I18nProvider.tsx +128 -0
  467. package/src/i18n/LocaleSwitcher.tsx +50 -0
  468. package/src/i18n/activeLocale.ts +39 -0
  469. package/src/i18n/buildClientI18n.ts +101 -0
  470. package/src/i18n/i18n.test.tsx +140 -0
  471. package/src/i18n/index.ts +12 -0
  472. package/src/i18n/locales/core/en.ts +274 -0
  473. package/src/i18n/locales/core/index.ts +5 -0
  474. package/src/i18n/locales/core/sq.ts +275 -0
  475. package/src/i18n/useFormatter.ts +42 -0
  476. package/src/i18n/useLocale.ts +16 -0
  477. package/src/i18n/useTranslation.ts +17 -0
  478. package/src/index.ts +244 -0
  479. package/src/pages/ResourceListPage.tsx +205 -0
  480. package/src/plugin.ts +110 -0
  481. package/src/runtime/conditions.test.ts +99 -0
  482. package/src/runtime/conditions.ts +148 -0
  483. package/src/runtime/formTraversal.ts +41 -0
  484. package/src/runtime/serializedFunctions.test.ts +59 -0
  485. package/src/runtime/serializedFunctions.ts +284 -0
  486. package/src/slots/Slot.test.tsx +89 -0
  487. package/src/slots/Slot.tsx +47 -0
  488. package/src/slots/SlotCluster.test.tsx +95 -0
  489. package/src/slots/SlotCluster.tsx +107 -0
  490. package/src/slots/index.ts +15 -0
  491. package/src/slots/mergeSlots.test.ts +71 -0
  492. package/src/slots/mergeSlots.ts +40 -0
  493. package/src/slots/slotNames.test.ts +21 -0
  494. package/src/slots/types.ts +119 -0
  495. package/src/styles.css +437 -0
  496. package/src/table/TableContext.tsx +41 -0
  497. package/src/table/TableRenderer.test.tsx +197 -0
  498. package/src/table/TableRenderer.tsx +390 -0
  499. package/src/table/components/FiltersPanel.tsx +193 -0
  500. package/src/table/components/TableToolbar.tsx +153 -0
  501. package/src/table/components/TableToolbarButton.tsx +14 -0
  502. package/src/table/components/TableView.tsx +106 -0
  503. package/src/table/defaultRowActions.ts +43 -0
  504. package/src/table/hooks/useColumnVisibility.test.ts +51 -0
  505. package/src/table/hooks/useColumnVisibility.ts +71 -0
  506. package/src/table/hooks/useEditableRows.test.ts +69 -0
  507. package/src/table/hooks/useEditableRows.ts +89 -0
  508. package/src/table/hooks/useTableActions.ts +393 -0
  509. package/src/table/hooks/useTableData.ts +89 -0
  510. package/src/table/hooks/useTableLayout.ts +45 -0
  511. package/src/table/hooks/useTableQuery.test.ts +116 -0
  512. package/src/table/hooks/useTableQuery.ts +172 -0
  513. package/src/test/mockFetch.ts +67 -0
  514. package/src/test/setup.ts +25 -0
  515. package/src/types/index.ts +228 -0
  516. package/src/utils/classNames.ts +10 -0
  517. package/src/utils/columnMediaDimensions.ts +45 -0
  518. package/src/utils/columnVisibilityStorage.ts +55 -0
  519. package/src/utils/fieldErrors.test.ts +35 -0
  520. package/src/utils/fieldErrors.ts +27 -0
  521. package/src/utils/formatValue.test.tsx +65 -0
  522. package/src/utils/formatValue.tsx +117 -0
  523. package/src/utils/layoutStorage.ts +52 -0
  524. package/src/utils/redirectHandler.ts +29 -0
  525. package/src/utils/tableFormatters.test.ts +54 -0
  526. package/src/utils/tableFormatters.ts +104 -0
  527. package/src/utils/widgetVisibilityStorage.ts +38 -0
  528. package/tailwind.config.js +9 -0
  529. package/vite.config.ts +17 -0
@@ -0,0 +1,148 @@
1
+ import { compileSerializedFunction } from './serializedFunctions';
2
+
3
+ /**
4
+ * Conditional visibility / disabled-state evaluation.
5
+ *
6
+ * Three condition forms are supported, in priority order:
7
+ * 1. `boolean` — static.
8
+ * 2. Structured condition object (preferred) — a small declarative AST that
9
+ * is interpreted without executing any code, so it works under a strict
10
+ * CSP and cannot do anything besides read form values.
11
+ * 3. Serialized function string — `({ get, operation }) => boolean`,
12
+ * compiled via the serialized-function runtime (see its trust-boundary
13
+ * notes).
14
+ */
15
+
16
+ export type ConditionOperator =
17
+ | 'eq'
18
+ | 'ne'
19
+ | 'in'
20
+ | 'nin'
21
+ | 'truthy'
22
+ | 'falsy'
23
+ | 'gt'
24
+ | 'gte'
25
+ | 'lt'
26
+ | 'lte'
27
+ | 'operation';
28
+
29
+ export type ConditionAst =
30
+ | { all: ConditionAst[] }
31
+ | { any: ConditionAst[] }
32
+ | { not: ConditionAst }
33
+ | { op: ConditionOperator; field?: string; value?: any };
34
+
35
+ export type Condition = boolean | string | ConditionAst | undefined;
36
+
37
+ export type FormOperation = 'create' | 'edit' | 'view';
38
+
39
+ /** Read a (possibly dot-separated) field path from the form state. */
40
+ function getFieldValue(formState: Record<string, any>, fieldName: string): any {
41
+ let value: any = formState;
42
+ for (const key of fieldName.split('.')) {
43
+ if (value === undefined || value === null) {
44
+ return undefined;
45
+ }
46
+ value = value[key];
47
+ }
48
+ return value;
49
+ }
50
+
51
+ function isConditionAst(condition: unknown): condition is ConditionAst {
52
+ if (!condition || typeof condition !== 'object' || Array.isArray(condition)) {
53
+ return false;
54
+ }
55
+ const c = condition as Record<string, unknown>;
56
+ return Array.isArray(c.all) || Array.isArray(c.any) || typeof c.not === 'object' || typeof c.op === 'string';
57
+ }
58
+
59
+ /**
60
+ * Pure interpreter for structured conditions — no code execution.
61
+ */
62
+ export function evaluateConditionAst(
63
+ ast: ConditionAst,
64
+ formState: Record<string, any>,
65
+ operation?: FormOperation,
66
+ ): boolean {
67
+ if ('all' in ast && Array.isArray(ast.all)) {
68
+ return ast.all.every(child => evaluateConditionAst(child, formState, operation));
69
+ }
70
+ if ('any' in ast && Array.isArray(ast.any)) {
71
+ return ast.any.some(child => evaluateConditionAst(child, formState, operation));
72
+ }
73
+ if ('not' in ast && ast.not) {
74
+ return !evaluateConditionAst(ast.not as ConditionAst, formState, operation);
75
+ }
76
+
77
+ const { op, field, value } = ast as { op: ConditionOperator; field?: string; value?: any };
78
+ const actual = op === 'operation' ? operation : field !== undefined ? getFieldValue(formState, field) : undefined;
79
+
80
+ switch (op) {
81
+ case 'eq':
82
+ return actual === value;
83
+ case 'ne':
84
+ return actual !== value;
85
+ case 'in':
86
+ return Array.isArray(value) && value.includes(actual);
87
+ case 'nin':
88
+ return Array.isArray(value) && !value.includes(actual);
89
+ case 'truthy':
90
+ return Boolean(actual);
91
+ case 'falsy':
92
+ return !actual;
93
+ case 'gt':
94
+ return actual > value;
95
+ case 'gte':
96
+ return actual >= value;
97
+ case 'lt':
98
+ return actual < value;
99
+ case 'lte':
100
+ return actual <= value;
101
+ case 'operation':
102
+ return operation === value;
103
+ default:
104
+ console.warn('Unknown condition operator:', op);
105
+ return false;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Evaluate a hidden/disabled condition against the current form state.
111
+ * Returns false (not hidden / not disabled) for undefined or invalid input.
112
+ */
113
+ export function evaluateCondition(
114
+ condition: Condition,
115
+ formState: Record<string, any>,
116
+ operation?: FormOperation,
117
+ ): boolean {
118
+ if (condition === undefined || condition === null) {
119
+ return false;
120
+ }
121
+
122
+ if (typeof condition === 'boolean') {
123
+ return condition;
124
+ }
125
+
126
+ if (isConditionAst(condition)) {
127
+ try {
128
+ return evaluateConditionAst(condition, formState, operation);
129
+ } catch (error) {
130
+ console.error('Error evaluating condition AST:', condition, error);
131
+ return false;
132
+ }
133
+ }
134
+
135
+ if (typeof condition === 'string') {
136
+ try {
137
+ const fn = compileSerializedFunction(condition);
138
+ if (!fn) return false;
139
+ const get = (fieldName: string) => getFieldValue(formState, fieldName);
140
+ return Boolean(fn({ get, operation }));
141
+ } catch (error) {
142
+ console.error('Error evaluating condition:', condition, error);
143
+ return false;
144
+ }
145
+ }
146
+
147
+ return false;
148
+ }
@@ -0,0 +1,41 @@
1
+ import { SerializedComponent } from '@maxal_studio/kratosjs';
2
+
3
+ /**
4
+ * Frontend implementation of the declarative children contract (mirrors
5
+ * `src/utils/formSchemaTraversal`). The core package's
6
+ * barrel pulls Node-only deps, so we share the *contract* (the serialized
7
+ * shape), not the module:
8
+ * - children live under `schema`
9
+ * - `isLayout` marks pure layout containers (no value of their own)
10
+ * - `childScope: 'array'` marks item-template containers (Repeater)
11
+ *
12
+ * Any component (core or plugin) that follows the contract is traversed
13
+ * correctly with no type-specific code.
14
+ */
15
+
16
+ /** Direct child components of a node. */
17
+ export function getChildComponents(node: SerializedComponent): SerializedComponent[] {
18
+ const schema = (node as any).schema;
19
+ return Array.isArray(schema) ? (schema as SerializedComponent[]) : [];
20
+ }
21
+
22
+ /** A pure layout container (inherits parent value scope, holds no value of its own). */
23
+ export function isLayout(node: SerializedComponent): boolean {
24
+ return (node as any).isLayout === true;
25
+ }
26
+
27
+ /** Children form an item template whose value lives under the node's own name (Repeater). */
28
+ export function isArrayScope(node: SerializedComponent): boolean {
29
+ return (node as any).childScope === 'array';
30
+ }
31
+
32
+ /**
33
+ * Depth-first predicate over a node and all of its descendants. Recursion stops
34
+ * descending past array-scope containers (their item template is a separate
35
+ * value scope) — the predicate still sees the container node itself.
36
+ */
37
+ export function someComponent(node: SerializedComponent, predicate: (node: SerializedComponent) => boolean): boolean {
38
+ if (predicate(node)) return true;
39
+ if (isArrayScope(node)) return false;
40
+ return getChildComponents(node).some(child => someComponent(child, predicate));
41
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { executeSerializedFunction } from '../runtime/serializedFunctions';
3
+
4
+ describe('executeSerializedFunction', () => {
5
+ it('returns undefined for empty input', () => {
6
+ expect(executeSerializedFunction(undefined)).toBeUndefined();
7
+ expect(executeSerializedFunction('')).toBeUndefined();
8
+ });
9
+
10
+ it('executes arrow functions with expression bodies', () => {
11
+ expect(executeSerializedFunction('(value) => value * 2', 5)).toBe(10);
12
+ });
13
+
14
+ it('executes arrow functions with block bodies', () => {
15
+ expect(
16
+ executeSerializedFunction('(value, record) => { return value + record.suffix; }', 'a', { suffix: 'b' }),
17
+ ).toBe('ab');
18
+ });
19
+
20
+ it('executes single-param arrow functions without parens', () => {
21
+ expect(executeSerializedFunction('value => value.toUpperCase()', 'hi')).toBe('HI');
22
+ });
23
+
24
+ it('executes function expressions', () => {
25
+ expect(executeSerializedFunction('function (value) { return value * 3; }', 3)).toBe(9);
26
+ });
27
+
28
+ it('returns the compiled function when no args are given', () => {
29
+ const fn = executeSerializedFunction('(value) => value + 1');
30
+ expect(typeof fn).toBe('function');
31
+ expect(fn(1)).toBe(2);
32
+ });
33
+
34
+ it('strips a __name() wrapper added by TS toolchains', () => {
35
+ const wrapped = '__name((value) => value * 2, "double")';
36
+ expect(executeSerializedFunction(wrapped, 4)).toBe(8);
37
+ });
38
+
39
+ it('strips nested __name() wrappers in block bodies', () => {
40
+ const wrapped = '__name((value) => { const inc = __name((x) => x + 1, "inc"); return inc(value); }, "outer")';
41
+ expect(executeSerializedFunction(wrapped, 1)).toBe(2);
42
+ });
43
+
44
+ it('does not mangle string literals containing __name-like text', () => {
45
+ expect(executeSerializedFunction('(value) => "__name is fine" + value', '!')).toBe('__name is fine!');
46
+ });
47
+
48
+ it('returns undefined when the function throws', () => {
49
+ // The function intentionally throws; the runtime catches it, logs, and returns undefined.
50
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
51
+ expect(executeSerializedFunction('(value) => { throw new Error("boom"); }', 1)).toBeUndefined();
52
+ expect(errorSpy).toHaveBeenCalled();
53
+ errorSpy.mockRestore();
54
+ });
55
+
56
+ it('handles default parameter values', () => {
57
+ expect(executeSerializedFunction('(value, factor = 2) => value * factor', 5, 3)).toBe(15);
58
+ });
59
+ });
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Runtime for executing functions serialized by the KratosJs backend.
3
+ *
4
+ * ## Trust boundary
5
+ *
6
+ * The strings executed here are NOT user input. They are functions written by
7
+ * the panel developer in resource/column definitions (kratosjs core
8
+ * serializes them with `.toString()`) and served by the developer's own server
9
+ * over the authenticated metadata API. They carry exactly the same trust as
10
+ * the application JS bundle itself: if an attacker can tamper with the
11
+ * metadata endpoint, they already control the panel.
12
+ *
13
+ * Implementation notes:
14
+ * - Compilation uses `new Function` (never `eval`), so serialized code cannot
15
+ * capture local scope from this module.
16
+ * - Compiled functions are cached by source string — schemas re-render often
17
+ * and re-parsing on every render is wasted work.
18
+ * - Deploying a CSP without `'unsafe-eval'` requires avoiding serialized
19
+ * functions entirely (use structured conditions instead, see conditions.ts).
20
+ */
21
+
22
+ const compileCache = new Map<string, ((...args: any[]) => any) | undefined>();
23
+
24
+ /**
25
+ * Strip TypeScript/esbuild helper wrappers from serialized code.
26
+ * Toolchains wrap named function expressions as `__name(fn, "fnName")`;
27
+ * this extracts the first argument and drops the name, iterating until
28
+ * no `__name(` wrappers remain (handles nesting in block bodies).
29
+ */
30
+ export function stripTypeScriptHelpers(code: string): string {
31
+ let cleaned = code;
32
+ let changed = true;
33
+ let iterations = 0;
34
+ const maxIterations = 100; // Safety limit to prevent infinite loops
35
+
36
+ while (changed && iterations < maxIterations) {
37
+ iterations++;
38
+ const before = cleaned;
39
+ let result = '';
40
+ let i = 0;
41
+
42
+ while (i < cleaned.length) {
43
+ // Check if we're at __name(
44
+ if (cleaned.substring(i, i + 6) === '__name' && (cleaned[i + 6] === '(' || /\s/.test(cleaned[i + 6]))) {
45
+ // Skip '__name' and any whitespace, find the opening parenthesis
46
+ let j = i + 6;
47
+ while (j < cleaned.length && /\s/.test(cleaned[j])) j++;
48
+ if (j >= cleaned.length || cleaned[j] !== '(') {
49
+ // Not a __name( call, add it back and continue
50
+ result += cleaned[i];
51
+ i++;
52
+ continue;
53
+ }
54
+
55
+ // Skip '('
56
+ j++;
57
+
58
+ // Find the first argument (the function expression),
59
+ // matching balanced parens/brackets/braces outside strings
60
+ let depth = 0;
61
+ const start = j;
62
+ let inString = false;
63
+ let stringChar = '';
64
+ let inTemplate = false;
65
+
66
+ while (j < cleaned.length) {
67
+ const char = cleaned[j];
68
+ const prevChar = j > 0 ? cleaned[j - 1] : '';
69
+
70
+ if (!inString && !inTemplate) {
71
+ if (char === '`') {
72
+ inTemplate = true;
73
+ } else if (char === '"' || char === "'") {
74
+ inString = true;
75
+ stringChar = char;
76
+ }
77
+ } else if (inString && char === stringChar && prevChar !== '\\') {
78
+ inString = false;
79
+ } else if (inTemplate && char === '`' && prevChar !== '\\') {
80
+ inTemplate = false;
81
+ }
82
+
83
+ if (!inString && !inTemplate) {
84
+ if (char === '(' || char === '[' || char === '{') {
85
+ depth++;
86
+ } else if (char === ')' || char === ']' || char === '}') {
87
+ depth--;
88
+ }
89
+
90
+ // A comma at depth 0 ends the first argument
91
+ if (char === ',' && depth === 0) {
92
+ break;
93
+ }
94
+ }
95
+
96
+ j++;
97
+ }
98
+
99
+ const functionExpr = cleaned.substring(start, j);
100
+ result += functionExpr;
101
+
102
+ // Skip the comma
103
+ if (cleaned[j] === ',') j++;
104
+
105
+ // Skip whitespace
106
+ while (j < cleaned.length && /\s/.test(cleaned[j])) j++;
107
+
108
+ // Skip the string argument ("functionName")
109
+ if (cleaned[j] === '"' || cleaned[j] === "'") {
110
+ const quote = cleaned[j];
111
+ j++;
112
+ while (j < cleaned.length && cleaned[j] !== quote) {
113
+ if (cleaned[j] === '\\') j++; // Skip escaped characters
114
+ j++;
115
+ }
116
+ if (cleaned[j] === quote) j++; // Skip closing quote
117
+ }
118
+
119
+ // Skip whitespace and closing paren
120
+ while (j < cleaned.length && (/\s/.test(cleaned[j]) || cleaned[j] === ')')) j++;
121
+
122
+ i = j;
123
+ } else {
124
+ result += cleaned[i];
125
+ i++;
126
+ }
127
+ }
128
+
129
+ cleaned = result;
130
+ changed = before !== cleaned;
131
+ }
132
+
133
+ return cleaned;
134
+ }
135
+
136
+ /**
137
+ * Extract parameter names from a function parameter list.
138
+ * Handles: (a, b), (a,b), a, (a = 1, b), etc.
139
+ */
140
+ function extractParameters(paramString: string): string[] {
141
+ if (!paramString) return [];
142
+
143
+ const cleaned = paramString.replace(/^\(|\)$/g, '').trim();
144
+ if (!cleaned) return [];
145
+
146
+ const params: string[] = [];
147
+ let current = '';
148
+ let depth = 0;
149
+
150
+ for (let i = 0; i < cleaned.length; i++) {
151
+ const char = cleaned[i];
152
+
153
+ if (char === '(' || char === '[' || char === '{') {
154
+ depth++;
155
+ current += char;
156
+ } else if (char === ')' || char === ']' || char === '}') {
157
+ depth--;
158
+ current += char;
159
+ } else if (char === ',' && depth === 0) {
160
+ const paramName = current.trim().split('=')[0].trim();
161
+ if (paramName) {
162
+ params.push(paramName);
163
+ }
164
+ current = '';
165
+ } else {
166
+ current += char;
167
+ }
168
+ }
169
+
170
+ if (current.trim()) {
171
+ const paramName = current.trim().split('=')[0].trim();
172
+ if (paramName) {
173
+ params.push(paramName);
174
+ }
175
+ }
176
+
177
+ return params.length > 0 ? params : ['value', 'record']; // Default fallback
178
+ }
179
+
180
+ function compile(functionString: string): ((...args: any[]) => any) | undefined {
181
+ const trimmed = stripTypeScriptHelpers(functionString).trim();
182
+
183
+ if (trimmed.includes('=>')) {
184
+ const arrowIndex = trimmed.indexOf('=>');
185
+ const afterArrow = trimmed.substring(arrowIndex + 2).trim();
186
+ const beforeArrow = trimmed.substring(0, arrowIndex).trim();
187
+ const params = extractParameters(beforeArrow);
188
+
189
+ if (afterArrow.startsWith('{')) {
190
+ // Block body — extract content between balanced braces
191
+ let braceCount = 0;
192
+ let startIndex = 0;
193
+ let endIndex = afterArrow.length;
194
+
195
+ for (let i = 0; i < afterArrow.length; i++) {
196
+ if (afterArrow[i] === '{') {
197
+ if (braceCount === 0) startIndex = i + 1;
198
+ braceCount++;
199
+ } else if (afterArrow[i] === '}') {
200
+ braceCount--;
201
+ if (braceCount === 0) {
202
+ endIndex = i;
203
+ break;
204
+ }
205
+ }
206
+ }
207
+
208
+ const functionBody = afterArrow.substring(startIndex, endIndex).trim();
209
+ return new Function(...params, functionBody) as (...args: any[]) => any;
210
+ }
211
+
212
+ // Expression body — wrap in a return statement
213
+ return new Function(...params, `return ${afterArrow}`) as (...args: any[]) => any;
214
+ }
215
+
216
+ if (trimmed.startsWith('function')) {
217
+ return new Function(`return ${trimmed}`)() as (...args: any[]) => any;
218
+ }
219
+
220
+ // Bare function body, or an unrecognized wrapping
221
+ try {
222
+ return new Function('value', 'record', trimmed) as (...args: any[]) => any;
223
+ } catch {
224
+ // Last resort: treat the string as an expression evaluating to a function
225
+ const fn = new Function(`return (${trimmed})`)();
226
+ if (typeof fn === 'function') {
227
+ return fn as (...args: any[]) => any;
228
+ }
229
+ throw new Error('Serialized string is not a valid function');
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Compile a serialized function string into a callable, with caching.
235
+ * Returns undefined (and logs) when the string cannot be compiled.
236
+ */
237
+ export function compileSerializedFunction(functionString: string | undefined): ((...args: any[]) => any) | undefined {
238
+ if (!functionString) {
239
+ return undefined;
240
+ }
241
+
242
+ if (compileCache.has(functionString)) {
243
+ return compileCache.get(functionString);
244
+ }
245
+
246
+ let compiled: ((...args: any[]) => any) | undefined;
247
+ try {
248
+ compiled = compile(functionString);
249
+ } catch (error: any) {
250
+ console.error('Error compiling serialized function:', error);
251
+ console.error('Function string (first 500 chars):', functionString.substring(0, 500));
252
+ compiled = undefined;
253
+ }
254
+
255
+ compileCache.set(functionString, compiled);
256
+ return compiled;
257
+ }
258
+
259
+ /**
260
+ * Execute a serialized function with the given arguments.
261
+ * Without arguments, returns the compiled function itself.
262
+ * Returns undefined on compile or runtime errors.
263
+ *
264
+ * @example
265
+ * executeSerializedFunction('(value, record) => value.toUpperCase()', 'hello', {});
266
+ */
267
+ export function executeSerializedFunction(functionString: string | undefined, ...args: any[]): any {
268
+ const fn = compileSerializedFunction(functionString);
269
+ if (!fn) {
270
+ return undefined;
271
+ }
272
+
273
+ if (args.length === 0) {
274
+ return fn;
275
+ }
276
+
277
+ try {
278
+ return fn(...args);
279
+ } catch (error: any) {
280
+ console.error('Error executing serialized function:', error);
281
+ console.error('Function string (first 500 chars):', functionString?.substring(0, 500));
282
+ return undefined;
283
+ }
284
+ }
@@ -0,0 +1,89 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Slot } from './Slot';
4
+ import { SlotRegistryProvider } from '../contexts/SlotRegistryContext';
5
+ import type { ResolvedSlots, SlotContext } from './types';
6
+
7
+ function renderSlot(slots: ResolvedSlots, context?: Omit<SlotContext, 'slot'>) {
8
+ return render(
9
+ <SlotRegistryProvider slots={slots}>
10
+ <Slot name="header.right" context={context} />
11
+ </SlotRegistryProvider>,
12
+ );
13
+ }
14
+
15
+ describe('Slot', () => {
16
+ it('renders nothing when the slot has no contributions', () => {
17
+ const { container } = renderSlot({});
18
+ expect(container).toBeEmptyDOMElement();
19
+ });
20
+
21
+ it('renders multiple contributions in their resolved order', () => {
22
+ renderSlot({
23
+ 'header.right': [
24
+ { id: 'one', render: () => <span data-testid="item">one</span> },
25
+ { id: 'two', render: () => <span data-testid="item">two</span> },
26
+ ],
27
+ });
28
+ const items = screen.getAllByTestId('item');
29
+ expect(items.map(el => el.textContent)).toEqual(['one', 'two']);
30
+ });
31
+
32
+ it('passes the slot context to a render function', () => {
33
+ renderSlot(
34
+ {
35
+ 'header.right': [
36
+ { id: 'ctx', render: ctx => <span data-testid="ctx">{String(ctx.resourceSlug)}</span> },
37
+ ],
38
+ },
39
+ { resourceSlug: 'posts' },
40
+ );
41
+ expect(screen.getByTestId('ctx')).toHaveTextContent('posts');
42
+ // slot name is injected automatically
43
+ });
44
+
45
+ it('passes the slot context to a component contribution', () => {
46
+ function Comp(ctx: SlotContext) {
47
+ return <span data-testid="comp">{ctx.slot}</span>;
48
+ }
49
+ renderSlot({ 'header.right': [{ id: 'comp', render: Comp }] });
50
+ expect(screen.getByTestId('comp')).toHaveTextContent('header.right');
51
+ });
52
+
53
+ it('forwards slot-specific extras via context.data', () => {
54
+ renderSlot(
55
+ {
56
+ 'header.right': [
57
+ { id: 'd', render: ctx => <span data-testid="data">{String(ctx.data?.selectedCount)}</span> },
58
+ ],
59
+ },
60
+ { data: { selectedCount: 3 } },
61
+ );
62
+ expect(screen.getByTestId('data')).toHaveTextContent('3');
63
+ });
64
+
65
+ describe('error isolation', () => {
66
+ beforeEach(() => {
67
+ // ErrorBoundary logs via console.error; silence it for these cases.
68
+ vi.spyOn(console, 'error').mockImplementation(() => {});
69
+ });
70
+ afterEach(() => {
71
+ vi.restoreAllMocks();
72
+ });
73
+
74
+ it('isolates a throwing contribution so siblings still render', () => {
75
+ function Boom(): React.ReactElement {
76
+ throw new Error('boom');
77
+ }
78
+ renderSlot({
79
+ 'header.right': [
80
+ { id: 'boom', render: Boom },
81
+ { id: 'ok', render: () => <span data-testid="ok">ok</span> },
82
+ ],
83
+ });
84
+ // Sibling still renders; the boundary shows its fallback for the failed one.
85
+ expect(screen.getByTestId('ok')).toBeInTheDocument();
86
+ expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();
87
+ });
88
+ });
89
+ });
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { ErrorBoundary } from '../components/errors/ErrorBoundary';
3
+ import { useSlot } from '../contexts/SlotRegistryContext';
4
+ import type { SlotContext, SlotContribution, SlotName } from './types';
5
+
6
+ /**
7
+ * Render one contribution. Both a React component and a plain
8
+ * `(ctx) => ReactNode` are function components of the context, so a single
9
+ * `createElement` path covers both forms.
10
+ */
11
+ export function renderSlot(contribution: SlotContribution, context: SlotContext): React.ReactNode {
12
+ return (
13
+ <ErrorBoundary key={contribution.id} label={`slot "${context.slot}" (${contribution.id})`}>
14
+ {React.createElement(contribution.render as React.ComponentType<SlotContext>, context)}
15
+ </ErrorBoundary>
16
+ );
17
+ }
18
+
19
+ export interface SlotProps {
20
+ /** The slot to render. */
21
+ name: SlotName;
22
+ /** Context passed to each contribution. `slot` is filled in automatically. */
23
+ context?: Omit<SlotContext, 'slot'>;
24
+ /** Optional wrapper element type. Defaults to a Fragment (no DOM). */
25
+ as?: React.ElementType;
26
+ /** className for the wrapper (ignored when `as` is omitted). */
27
+ className?: string;
28
+ }
29
+
30
+ /**
31
+ * Renders every contribution registered for `name`, in order, each isolated by
32
+ * an `ErrorBoundary`. Renders nothing (no wrapper DOM) when the slot is empty,
33
+ * so slots stay invisible until something is contributed.
34
+ */
35
+ export function Slot({ name, context, as, className }: SlotProps) {
36
+ const contributions = useSlot(name);
37
+ if (contributions.length === 0) return null;
38
+
39
+ const fullContext: SlotContext = { ...context, slot: name };
40
+ const children = contributions.map(contribution => renderSlot(contribution, fullContext));
41
+
42
+ if (as) {
43
+ const Wrapper = as;
44
+ return <Wrapper className={className}>{children}</Wrapper>;
45
+ }
46
+ return <>{children}</>;
47
+ }