@firecms/core 3.0.0-canary.21 → 3.0.0-canary.211

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 (382) hide show
  1. package/README.md +3 -3
  2. package/dist/app/AppBar.d.ts +12 -0
  3. package/dist/app/Drawer.d.ts +16 -0
  4. package/dist/app/Scaffold.d.ts +34 -0
  5. package/dist/app/index.d.ts +4 -0
  6. package/dist/app/useApp.d.ts +16 -0
  7. package/dist/components/ArrayContainer.d.ts +23 -6
  8. package/dist/components/CircularProgressCenter.d.ts +1 -1
  9. package/dist/components/ClearFilterSortButton.d.ts +5 -0
  10. package/dist/components/{DeleteConfirmationDialog.d.ts → ConfirmationDialog.d.ts} +1 -1
  11. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +13 -13
  12. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  13. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +22 -6
  14. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +1 -0
  15. package/dist/components/EntityCollectionTable/column_utils.d.ts +1 -2
  16. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +3 -1
  17. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  18. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -4
  19. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +2 -2
  20. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +7 -4
  21. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +16 -2
  22. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +11 -0
  23. package/dist/components/EntityCollectionView/utils.d.ts +3 -0
  24. package/dist/components/EntityJsonPreview.d.ts +3 -0
  25. package/dist/components/EntityPreview.d.ts +5 -4
  26. package/dist/components/ErrorView.d.ts +1 -1
  27. package/dist/components/HomePage/DefaultHomePage.d.ts +1 -1
  28. package/dist/components/HomePage/NavigationCardBinding.d.ts +1 -1
  29. package/dist/components/PropertyConfigBadge.d.ts +2 -1
  30. package/dist/components/PropertyIdCopyTooltip.d.ts +8 -0
  31. package/dist/components/ReferenceWidget.d.ts +3 -1
  32. package/dist/components/SelectableTable/SelectableTable.d.ts +13 -3
  33. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
  34. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  35. package/dist/components/VirtualTable/VirtualTableProps.d.ts +28 -12
  36. package/dist/components/VirtualTable/types.d.ts +3 -3
  37. package/dist/components/{EntityCollectionTable/internal → common}/default_entity_actions.d.ts +1 -3
  38. package/dist/components/common/index.d.ts +2 -1
  39. package/dist/components/common/table_height.d.ts +5 -0
  40. package/dist/components/common/types.d.ts +4 -6
  41. package/dist/components/common/useColumnsIds.d.ts +3 -1
  42. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +13 -2
  43. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  44. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  45. package/dist/components/index.d.ts +4 -2
  46. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  47. package/dist/core/DefaultAppBar.d.ts +29 -0
  48. package/dist/core/DefaultDrawer.d.ts +19 -0
  49. package/dist/core/DrawerNavigationItem.d.ts +10 -0
  50. package/dist/core/EntityEditView.d.ts +35 -11
  51. package/dist/core/EntityEditViewFormActions.d.ts +2 -0
  52. package/dist/core/FireCMS.d.ts +3 -3
  53. package/dist/core/FireCMSRouter.d.ts +4 -0
  54. package/dist/core/NavigationRoutes.d.ts +2 -3
  55. package/dist/core/SideDialogs.d.ts +4 -2
  56. package/dist/core/index.d.ts +4 -4
  57. package/dist/form/EntityForm.d.ts +37 -69
  58. package/dist/form/EntityFormActions.d.ts +17 -0
  59. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  60. package/dist/form/components/ErrorFocus.d.ts +1 -1
  61. package/dist/form/components/FieldHelperText.d.ts +3 -3
  62. package/dist/form/components/FormEntry.d.ts +6 -0
  63. package/dist/form/components/FormLayout.d.ts +5 -0
  64. package/dist/form/components/LabelWithIcon.d.ts +1 -1
  65. package/dist/form/components/LabelWithIconAndTooltip.d.ts +15 -0
  66. package/dist/form/components/StorageItemPreview.d.ts +4 -4
  67. package/dist/form/components/index.d.ts +3 -1
  68. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
  69. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
  70. package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
  71. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +1 -1
  72. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  73. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +11 -0
  74. package/dist/form/field_bindings/{MultiSelectBinding.d.ts → MultiSelectFieldBinding.d.ts} +1 -1
  75. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
  76. package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -2
  77. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
  78. package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
  79. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -5
  80. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -2
  81. package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
  82. package/dist/form/index.d.ts +16 -18
  83. package/dist/form/useClearRestoreValue.d.ts +2 -2
  84. package/dist/hooks/data/delete.d.ts +4 -4
  85. package/dist/hooks/data/save.d.ts +4 -5
  86. package/dist/hooks/data/useCollectionFetch.d.ts +1 -1
  87. package/dist/hooks/data/useEntityFetch.d.ts +4 -3
  88. package/dist/hooks/index.d.ts +1 -0
  89. package/dist/hooks/useAuthController.d.ts +1 -1
  90. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  91. package/dist/hooks/useBuildNavigationController.d.ts +7 -5
  92. package/dist/hooks/useFireCMSContext.d.ts +1 -1
  93. package/dist/hooks/useModeController.d.ts +1 -2
  94. package/dist/hooks/useProjectLog.d.ts +8 -2
  95. package/dist/hooks/useResolvedNavigationFrom.d.ts +3 -3
  96. package/dist/hooks/useValidateAuthenticator.d.ts +4 -8
  97. package/dist/index.d.ts +1 -0
  98. package/dist/index.es.js +21921 -14330
  99. package/dist/index.es.js.map +1 -1
  100. package/dist/index.umd.js +24271 -588
  101. package/dist/index.umd.js.map +1 -1
  102. package/dist/internal/useBuildDataSource.d.ts +1 -16
  103. package/dist/internal/useBuildSideEntityController.d.ts +2 -2
  104. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  105. package/dist/preview/PropertyPreviewProps.d.ts +6 -1
  106. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  107. package/dist/preview/components/ReferencePreview.d.ts +3 -2
  108. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  109. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  110. package/dist/preview/util.d.ts +3 -3
  111. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  112. package/dist/routes/FireCMSRoute.d.ts +1 -0
  113. package/dist/routes/HomePageRoute.d.ts +3 -0
  114. package/dist/types/auth.d.ts +8 -10
  115. package/dist/types/collections.d.ts +77 -22
  116. package/dist/types/datasource.d.ts +52 -36
  117. package/dist/types/dialogs_controller.d.ts +7 -3
  118. package/dist/types/entities.d.ts +7 -2
  119. package/dist/types/entity_actions.d.ts +28 -4
  120. package/dist/types/entity_callbacks.d.ts +16 -16
  121. package/dist/types/entity_overrides.d.ts +2 -2
  122. package/dist/types/export_import.d.ts +4 -4
  123. package/dist/types/fields.d.ts +71 -42
  124. package/dist/types/firecms.d.ts +6 -2
  125. package/dist/types/firecms_context.d.ts +1 -1
  126. package/dist/types/index.d.ts +0 -1
  127. package/dist/types/navigation.d.ts +32 -14
  128. package/dist/types/permissions.d.ts +4 -4
  129. package/dist/types/plugins.d.ts +23 -12
  130. package/dist/types/properties.d.ts +52 -24
  131. package/dist/types/property_config.d.ts +0 -2
  132. package/dist/types/side_dialogs_controller.d.ts +10 -0
  133. package/dist/types/side_entity_controller.d.ts +6 -1
  134. package/dist/types/storage.d.ts +86 -3
  135. package/dist/types/user.d.ts +1 -0
  136. package/dist/util/builders.d.ts +3 -3
  137. package/dist/util/entities.d.ts +3 -3
  138. package/dist/util/entity_actions.d.ts +2 -0
  139. package/dist/util/entity_cache.d.ts +23 -0
  140. package/dist/util/icon_list.d.ts +5 -1
  141. package/dist/util/icon_synonyms.d.ts +1 -98
  142. package/dist/util/icons.d.ts +6 -3
  143. package/dist/util/index.d.ts +2 -0
  144. package/dist/util/navigation_from_path.d.ts +6 -1
  145. package/dist/util/navigation_utils.d.ts +14 -3
  146. package/dist/util/objects.d.ts +2 -1
  147. package/dist/util/permissions.d.ts +4 -4
  148. package/dist/util/plurals.d.ts +0 -2
  149. package/dist/util/property_utils.d.ts +3 -3
  150. package/dist/util/resolutions.d.ts +36 -17
  151. package/dist/util/storage.d.ts +23 -2
  152. package/dist/util/useStorageUploadController.d.ts +3 -3
  153. package/package.json +62 -48
  154. package/src/app/AppBar.tsx +18 -0
  155. package/src/app/Drawer.tsx +24 -0
  156. package/src/app/Scaffold.tsx +253 -0
  157. package/src/app/index.ts +4 -0
  158. package/src/app/useApp.tsx +32 -0
  159. package/src/components/ArrayContainer.tsx +127 -41
  160. package/src/components/CircularProgressCenter.tsx +2 -2
  161. package/src/components/ClearFilterSortButton.tsx +41 -0
  162. package/src/components/{DeleteConfirmationDialog.tsx → ConfirmationDialog.tsx} +3 -3
  163. package/src/components/DeleteEntityDialog.tsx +10 -19
  164. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +54 -37
  165. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +38 -31
  166. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +30 -9
  167. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +47 -39
  168. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  169. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +30 -16
  170. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +19 -17
  171. package/src/components/EntityCollectionTable/index.tsx +1 -1
  172. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +31 -36
  173. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +36 -34
  174. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +20 -8
  175. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +81 -80
  176. package/src/components/EntityCollectionView/EntityCollectionView.tsx +207 -113
  177. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
  178. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +68 -0
  179. package/src/components/EntityCollectionView/useSelectionController.tsx +20 -7
  180. package/src/components/EntityCollectionView/utils.ts +19 -0
  181. package/src/components/EntityJsonPreview.tsx +66 -0
  182. package/src/components/EntityPreview.tsx +62 -52
  183. package/src/components/EntityView.tsx +10 -10
  184. package/src/components/ErrorView.tsx +4 -4
  185. package/src/components/HomePage/DefaultHomePage.tsx +26 -19
  186. package/src/components/HomePage/FavouritesView.tsx +6 -11
  187. package/src/components/HomePage/NavigationCard.tsx +3 -3
  188. package/src/components/HomePage/NavigationCardBinding.tsx +5 -10
  189. package/src/components/HomePage/NavigationGroup.tsx +2 -2
  190. package/src/components/HomePage/SmallNavigationCard.tsx +5 -6
  191. package/src/components/NotFoundPage.tsx +2 -2
  192. package/src/components/PropertyConfigBadge.tsx +9 -3
  193. package/src/components/PropertyIdCopyTooltip.tsx +47 -0
  194. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +19 -13
  195. package/src/components/ReferenceWidget.tsx +21 -11
  196. package/src/components/SearchIconsView.tsx +10 -7
  197. package/src/components/SelectableTable/SelectableTable.tsx +27 -12
  198. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +2 -3
  199. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +25 -8
  200. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +36 -12
  201. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +92 -23
  202. package/src/components/UnsavedChangesDialog.tsx +42 -0
  203. package/src/components/VirtualTable/VirtualTable.tsx +96 -48
  204. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -9
  205. package/src/components/VirtualTable/VirtualTableHeader.tsx +10 -10
  206. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +2 -2
  207. package/src/components/VirtualTable/VirtualTableProps.tsx +33 -14
  208. package/src/components/VirtualTable/VirtualTableRow.tsx +5 -6
  209. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +5 -5
  210. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +3 -2
  211. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +2 -1
  212. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +16 -28
  213. package/src/components/VirtualTable/types.tsx +2 -3
  214. package/src/components/{EntityCollectionTable/internal → common}/default_entity_actions.tsx +33 -40
  215. package/src/components/common/index.ts +2 -1
  216. package/src/components/{VirtualTable/common.tsx → common/table_height.tsx} +5 -2
  217. package/src/components/common/types.tsx +4 -6
  218. package/src/components/common/useColumnsIds.tsx +24 -3
  219. package/src/components/common/useDataSourceTableController.tsx +420 -0
  220. package/src/components/common/useDebouncedCallback.tsx +20 -0
  221. package/src/components/common/useScrollRestoration.tsx +68 -0
  222. package/src/components/common/useTableSearchHelper.ts +53 -12
  223. package/src/components/index.tsx +4 -2
  224. package/src/contexts/BreacrumbsContext.tsx +38 -0
  225. package/src/contexts/DialogsProvider.tsx +5 -4
  226. package/src/contexts/ModeController.tsx +1 -3
  227. package/src/contexts/SnackbarProvider.tsx +2 -0
  228. package/src/core/DefaultAppBar.tsx +219 -0
  229. package/src/core/DefaultDrawer.tsx +185 -0
  230. package/src/core/DrawerNavigationItem.tsx +66 -0
  231. package/src/core/EntityEditView.tsx +345 -480
  232. package/src/core/EntityEditViewFormActions.tsx +199 -0
  233. package/src/core/EntitySidePanel.tsx +86 -17
  234. package/src/core/FireCMS.tsx +56 -49
  235. package/src/core/FireCMSRouter.tsx +17 -0
  236. package/src/core/NavigationRoutes.tsx +28 -38
  237. package/src/core/SideDialogs.tsx +22 -12
  238. package/src/core/field_configs.tsx +11 -12
  239. package/src/core/index.tsx +6 -5
  240. package/src/form/EntityForm.tsx +585 -539
  241. package/src/form/EntityFormActions.tsx +169 -0
  242. package/src/form/PropertyFieldBinding.tsx +85 -44
  243. package/src/form/components/CustomIdField.tsx +6 -2
  244. package/src/form/components/FieldHelperText.tsx +4 -4
  245. package/src/form/components/FormEntry.tsx +22 -0
  246. package/src/form/components/FormLayout.tsx +16 -0
  247. package/src/form/components/LabelWithIcon.tsx +30 -19
  248. package/src/form/components/LabelWithIconAndTooltip.tsx +28 -0
  249. package/src/form/components/StorageItemPreview.tsx +22 -12
  250. package/src/form/components/StorageUploadProgress.tsx +4 -5
  251. package/src/form/components/index.tsx +3 -1
  252. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +31 -19
  253. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +47 -35
  254. package/src/form/field_bindings/BlockFieldBinding.tsx +56 -34
  255. package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -14
  256. package/src/form/field_bindings/KeyValueFieldBinding.tsx +60 -51
  257. package/src/form/field_bindings/MapFieldBinding.tsx +71 -54
  258. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +153 -0
  259. package/src/form/field_bindings/{MultiSelectBinding.tsx → MultiSelectFieldBinding.tsx} +25 -20
  260. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +11 -16
  261. package/src/form/field_bindings/ReferenceFieldBinding.tsx +42 -31
  262. package/src/form/field_bindings/RepeatFieldBinding.tsx +56 -35
  263. package/src/form/field_bindings/SelectFieldBinding.tsx +22 -14
  264. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +42 -50
  265. package/src/form/field_bindings/SwitchFieldBinding.tsx +29 -24
  266. package/src/form/field_bindings/TextFieldBinding.tsx +28 -24
  267. package/src/form/index.tsx +16 -37
  268. package/src/form/useClearRestoreValue.tsx +2 -2
  269. package/src/form/validation.ts +13 -23
  270. package/src/hooks/data/delete.ts +6 -5
  271. package/src/hooks/data/save.ts +13 -9
  272. package/src/hooks/data/useCollectionFetch.tsx +3 -3
  273. package/src/hooks/data/useDataSource.tsx +11 -3
  274. package/src/hooks/data/useEntityFetch.tsx +10 -6
  275. package/src/hooks/index.tsx +1 -0
  276. package/src/hooks/useAuthController.tsx +1 -1
  277. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  278. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +8 -10
  279. package/src/hooks/useBuildModeController.tsx +22 -29
  280. package/src/hooks/useBuildNavigationController.tsx +159 -54
  281. package/src/hooks/useFireCMSContext.tsx +5 -5
  282. package/src/hooks/useLargeLayout.tsx +0 -35
  283. package/src/hooks/useModeController.tsx +1 -2
  284. package/src/hooks/useProjectLog.tsx +32 -10
  285. package/src/hooks/useResolvedNavigationFrom.tsx +10 -12
  286. package/src/hooks/useValidateAuthenticator.tsx +17 -37
  287. package/src/index.ts +1 -0
  288. package/src/internal/useBuildDataSource.ts +72 -82
  289. package/src/internal/useBuildSideDialogsController.tsx +4 -2
  290. package/src/internal/useBuildSideEntityController.tsx +194 -73
  291. package/src/internal/useUnsavedChangesDialog.tsx +127 -91
  292. package/src/preview/PropertyPreview.tsx +17 -24
  293. package/src/preview/PropertyPreviewProps.tsx +7 -1
  294. package/src/preview/components/BooleanPreview.tsx +2 -2
  295. package/src/preview/components/EmptyValue.tsx +1 -1
  296. package/src/preview/components/EnumValuesChip.tsx +2 -2
  297. package/src/preview/components/ImagePreview.tsx +25 -36
  298. package/src/preview/components/ReferencePreview.tsx +22 -33
  299. package/src/preview/components/StorageThumbnail.tsx +5 -1
  300. package/src/preview/components/UrlComponentPreview.tsx +60 -28
  301. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +2 -3
  302. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
  303. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +2 -3
  304. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +1 -2
  305. package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -5
  306. package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -5
  307. package/src/preview/property_previews/MapPropertyPreview.tsx +12 -11
  308. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +12 -12
  309. package/src/preview/property_previews/StringPropertyPreview.tsx +3 -3
  310. package/src/preview/util.ts +10 -10
  311. package/src/routes/CustomCMSRoute.tsx +21 -0
  312. package/src/routes/FireCMSRoute.tsx +235 -0
  313. package/src/routes/HomePageRoute.tsx +17 -0
  314. package/src/types/auth.tsx +9 -13
  315. package/src/types/collections.ts +94 -27
  316. package/src/types/customization_controller.tsx +0 -1
  317. package/src/types/datasource.ts +61 -43
  318. package/src/types/dialogs_controller.tsx +7 -3
  319. package/src/types/entities.ts +12 -2
  320. package/src/types/entity_actions.tsx +32 -7
  321. package/src/types/entity_callbacks.ts +18 -18
  322. package/src/types/entity_overrides.tsx +2 -2
  323. package/src/types/export_import.ts +4 -4
  324. package/src/types/fields.tsx +81 -46
  325. package/src/types/firecms.tsx +7 -2
  326. package/src/types/firecms_context.tsx +1 -1
  327. package/src/types/index.ts +0 -1
  328. package/src/types/navigation.ts +38 -18
  329. package/src/types/permissions.ts +5 -5
  330. package/src/types/plugins.tsx +25 -14
  331. package/src/types/properties.ts +60 -25
  332. package/src/types/property_config.tsx +0 -2
  333. package/src/types/side_dialogs_controller.tsx +15 -0
  334. package/src/types/side_entity_controller.tsx +6 -1
  335. package/src/types/storage.ts +94 -3
  336. package/src/types/user.ts +2 -0
  337. package/src/util/builders.ts +10 -8
  338. package/src/util/entities.ts +9 -6
  339. package/src/util/entity_actions.ts +28 -0
  340. package/src/util/entity_cache.ts +204 -0
  341. package/src/util/enums.ts +1 -1
  342. package/src/util/icon_list.ts +16 -10
  343. package/src/util/icon_synonyms.ts +3 -100
  344. package/src/util/icons.tsx +17 -7
  345. package/src/util/index.ts +2 -0
  346. package/src/util/join_collections.ts +3 -0
  347. package/src/util/make_properties_editable.ts +13 -5
  348. package/src/util/navigation_from_path.ts +13 -6
  349. package/src/util/navigation_utils.ts +59 -7
  350. package/src/util/objects.ts +48 -28
  351. package/src/util/parent_references_from_path.ts +3 -3
  352. package/src/util/permissions.ts +9 -8
  353. package/src/util/plurals.ts +0 -2
  354. package/src/util/property_utils.tsx +11 -4
  355. package/src/util/references.ts +11 -2
  356. package/src/util/resolutions.ts +100 -46
  357. package/src/util/storage.ts +79 -21
  358. package/src/util/strings.ts +2 -2
  359. package/src/util/useStorageUploadController.tsx +71 -27
  360. package/dist/components/FireCMSAppBar.d.ts +0 -26
  361. package/dist/components/PropertyIdCopyTooltipContent.d.ts +0 -3
  362. package/dist/components/VirtualTable/common.d.ts +0 -2
  363. package/dist/core/Drawer.d.ts +0 -23
  364. package/dist/core/Scaffold.d.ts +0 -55
  365. package/dist/core/SideEntityView.d.ts +0 -7
  366. package/dist/form/components/FormikArrayContainer.d.ts +0 -18
  367. package/dist/form/field_bindings/MarkdownFieldBinding.d.ts +0 -9
  368. package/dist/internal/useBuildCustomizationController.d.ts +0 -2
  369. package/dist/internal/useLocaleConfig.d.ts +0 -1
  370. package/dist/types/appcheck.d.ts +0 -26
  371. package/src/components/FireCMSAppBar.tsx +0 -165
  372. package/src/components/PropertyIdCopyTooltipContent.tsx +0 -28
  373. package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -225
  374. package/src/core/Drawer.tsx +0 -191
  375. package/src/core/Scaffold.tsx +0 -281
  376. package/src/core/SideEntityView.tsx +0 -38
  377. package/src/form/components/FormikArrayContainer.tsx +0 -44
  378. package/src/form/field_bindings/MarkdownFieldBinding.tsx +0 -695
  379. package/src/internal/useBuildCustomizationController.tsx +0 -5
  380. package/src/internal/useLocaleConfig.tsx +0 -18
  381. package/src/types/appcheck.ts +0 -29
  382. /package/src/util/{common.tsx → common.ts} +0 -0
@@ -1,244 +1,170 @@
1
- import React, { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
-
1
+ import React, { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
3
2
  import {
4
3
  CMSAnalyticsEvent,
5
4
  Entity,
6
- EntityAction,
7
5
  EntityCollection,
6
+ EntityCustomViewParams,
8
7
  EntityStatus,
9
8
  EntityValues,
10
9
  FormContext,
11
10
  PluginFormActionProps,
11
+ PropertyConfig,
12
12
  PropertyFieldBindingProps,
13
13
  ResolvedEntityCollection
14
14
  } from "../types";
15
- import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
16
- import { PropertyFieldBinding } from "./PropertyFieldBinding";
17
- import { CustomFieldValidator, getYupEntitySchema } from "./validation";
18
- import equal from "react-fast-compare"
15
+ import equal from "react-fast-compare";
16
+
17
+ import { ErrorBoundary, getFormFieldKeys } from "../components";
19
18
  import {
20
- canCreateEntity,
21
- canDeleteEntity,
22
19
  getDefaultValuesFor,
23
20
  getEntityTitlePropertyKey,
24
21
  getValueInPath,
25
22
  isHidden,
26
23
  isReadOnly,
27
- resolveCollection
24
+ mergeDeep,
25
+ resolveCollection,
26
+ useDebouncedCallback
28
27
  } from "../util";
28
+
29
29
  import {
30
- useAuthController,
30
+ saveEntityWithCallbacks,
31
31
  useCustomizationController,
32
32
  useDataSource,
33
33
  useFireCMSContext,
34
- useSideEntityController
34
+ useSnackbarController
35
35
  } from "../hooks";
36
- import { ErrorFocus } from "./components/ErrorFocus";
37
- import { CustomIdField } from "./components/CustomIdField";
38
- import { Alert, Button, cn, DialogActions, IconButton, Tooltip, Typography } from "@firecms/ui";
39
- import { ErrorBoundary } from "../components";
40
- import {
41
- copyEntityAction,
42
- deleteEntityAction
43
- } from "../components/EntityCollectionTable/internal/default_entity_actions";
36
+ import { Alert, CheckIcon, Chip, cls, EditIcon, NotesIcon, paperMixin, Tooltip, Typography } from "@firecms/ui";
37
+ import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
44
38
  import { useAnalyticsController } from "../hooks/useAnalyticsController";
39
+ import { FormEntry, FormLayout, LabelWithIconAndTooltip, PropertyFieldBinding } from "../form";
45
40
  import { ValidationError } from "yup";
46
- import { PropertyIdCopyTooltipContent } from "../components/PropertyIdCopyTooltipContent";
47
-
48
- /**
49
- * @group Components
50
- */
51
- export interface EntityFormProps<M extends Record<string, any>> {
52
-
53
- /**
54
- * New or existing status
55
- */
56
- status: EntityStatus;
41
+ import { removeEntityFromCache, saveEntityToCache } from "../util/entity_cache";
42
+ import { CustomIdField } from "../form/components/CustomIdField";
43
+ import { ErrorFocus } from "../form/components/ErrorFocus";
44
+ import { CustomFieldValidator, getYupEntitySchema } from "../form/validation";
45
+ import { EntityFormActions, EntityFormActionsProps } from "./EntityFormActions";
46
+
47
+ export type OnUpdateParams = {
48
+ entity: Entity<any>,
49
+ status: EntityStatus,
50
+ path: string,
51
+ entityId?: string;
52
+ selectedTab?: string;
53
+ collection: EntityCollection<any>
54
+ };
57
55
 
58
- /**
59
- * Path of the collection this entity is located
60
- */
56
+ export type EntityFormProps<M extends Record<string, any>> = {
61
57
  path: string;
62
-
63
- /**
64
- * The collection is used to build the fields of the form
65
- */
66
- collection: EntityCollection<M>
67
-
68
- /**
69
- * The updated entity is passed from the parent component when the underlying data
70
- * has changed in the datasource
71
- */
58
+ collection: EntityCollection<M>;
59
+ entityId?: string;
72
60
  entity?: Entity<M>;
73
-
74
- /**
75
- * The callback function called when Save is clicked and validation is correct
76
- */
77
- onEntitySaveRequested: (
78
- props: EntityFormSaveParams<M>
79
- ) => Promise<void>;
80
-
81
- /**
82
- * The callback function called when discard is clicked
83
- */
84
- onDiscard?: () => void;
85
-
86
- /**
87
- * The callback function when the form is dirty, so the values are different
88
- * from the original ones
89
- */
90
- onModified?: (dirty: boolean) => void;
91
-
61
+ databaseId?: string;
62
+ onIdChange?: (id: string) => void;
63
+ onValuesModified?: (modified: boolean) => void;
64
+ onSaved?: (params: OnUpdateParams) => void;
65
+ initialDirtyValues?: Partial<M>; // dirty cached entity in memory
66
+ onFormContextReady?: (formContext: FormContext) => void;
67
+ forceActionsAtTheBottom?: boolean;
68
+ className?: string;
69
+ initialStatus: EntityStatus;
70
+ onStatusChange?: (status: EntityStatus) => void;
71
+ onEntityChange?: (entity: Entity<M>) => void;
72
+ formex?: FormexController<M>;
73
+ openEntityMode?: "side_panel" | "full_screen";
92
74
  /**
93
- * The callback function when the form original values have been modified
75
+ * Include the copy and delete actions in the form
94
76
  */
95
- onValuesChanged?: (values?: EntityValues<M>) => void;
77
+ showDefaultActions?: boolean;
96
78
 
97
79
  /**
98
- *
99
- * @param id
80
+ * Display the entity path in the form
100
81
  */
101
- onIdChange?: (id: string) => void;
102
-
103
- currentEntityId?: string;
104
-
105
- onFormContextChange?: (formContext: FormContext<M>) => void;
82
+ showEntityPath?: boolean;
106
83
 
107
- hideId?: boolean;
84
+ EntityFormActionsComponent?: React.FC<EntityFormActionsProps>;
108
85
 
109
- autoSave?: boolean;
86
+ Builder?: React.ComponentType<EntityCustomViewParams<M>>;
110
87
 
111
- onIdUpdateError?: (error: any) => void;
112
-
113
- }
114
-
115
- export type EntityFormSaveParams<M extends Record<string, any>> = {
116
- collection: ResolvedEntityCollection<M>,
117
- path: string,
118
- entityId: string | undefined,
119
- values: EntityValues<M>,
120
- previousValues?: EntityValues<M>,
121
- closeAfterSave: boolean,
122
- autoSave: boolean
88
+ children?: React.ReactNode;
123
89
  };
124
90
 
125
- /**
126
- * This is the form used internally by the CMS
127
- * @param status
128
- * @param path
129
- * @param collection
130
- * @param entity
131
- * @param onEntitySave
132
- * @param onDiscard
133
- * @param onModified
134
- * @param onValuesChanged
135
- * @constructor
136
- * @group Components
137
- */
138
- export const EntityForm = React.memo<EntityFormProps<any>>(EntityFormInternal,
139
- (a: EntityFormProps<any>, b: EntityFormProps<any>) => {
140
- return a.status === b.status &&
141
- a.path === b.path &&
142
- equal(a.entity?.values, b.entity?.values);
143
- }) as typeof EntityFormInternal;
144
-
145
- function getDataSourceEntityValues<M extends object>(initialResolvedCollection: ResolvedEntityCollection,
146
- status: "new" | "existing" | "copy",
147
- entity: Entity<M> | undefined): Partial<EntityValues<M>> {
148
- const properties = initialResolvedCollection.properties;
149
- if ((status === "existing" || status === "copy") && entity) {
150
- return entity.values ?? getDefaultValuesFor(properties);
151
- } else if (status === "new") {
152
- return getDefaultValuesFor(properties);
153
- } else {
154
- console.error({
155
- status,
156
- entity
157
- });
158
- throw new Error("Form has not been initialised with the correct parameters");
91
+ export function EntityForm<M extends Record<string, any>>({
92
+ path,
93
+ entityId: entityIdProp,
94
+ collection,
95
+ onValuesModified,
96
+ onIdChange,
97
+ onSaved,
98
+ entity,
99
+ initialDirtyValues,
100
+ onFormContextReady,
101
+ forceActionsAtTheBottom,
102
+ initialStatus,
103
+ className,
104
+ onStatusChange,
105
+ onEntityChange,
106
+ openEntityMode = "full_screen",
107
+ formex: formexProp,
108
+ Builder,
109
+ EntityFormActionsComponent = EntityFormActions,
110
+ showDefaultActions = true,
111
+ showEntityPath = true,
112
+ children
113
+ }: EntityFormProps<M>) {
114
+
115
+ if (collection.customId && collection.formAutoSave) {
116
+ console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
159
117
  }
160
- }
161
118
 
162
- function EntityFormInternal<M extends Record<string, any>>({
163
- status,
164
- path,
165
- collection: inputCollection,
166
- entity,
167
- onEntitySaveRequested,
168
- onDiscard,
169
- onModified,
170
- onValuesChanged,
171
- onIdChange,
172
- onFormContextChange,
173
- hideId,
174
- autoSave,
175
- onIdUpdateError,
176
- }: EntityFormProps<M>) {
119
+ const [status, setStatus] = useState<EntityStatus>(initialStatus);
120
+ const [saving, setSaving] = useState(false);
177
121
 
178
- const analyticsController = useAnalyticsController();
122
+ const updateStatus = (status: EntityStatus) => {
123
+ setStatus(status);
124
+ onStatusChange?.(status);
125
+ };
179
126
 
180
- const customizationController = useCustomizationController();
127
+ const [valuesToBeSaved, setValuesToBeSaved] = useState<EntityValues<M> | undefined>(undefined);
128
+ useDebouncedCallback(valuesToBeSaved, () => {
129
+ if (valuesToBeSaved)
130
+ saveEntity({
131
+ entityId: entityIdProp,
132
+ collection,
133
+ path,
134
+ values: valuesToBeSaved
135
+ });
136
+ }, false, 2000);
181
137
 
138
+ const dataSource = useDataSource(collection);
139
+ const snackbarController = useSnackbarController();
140
+ const customizationController = useCustomizationController();
182
141
  const context = useFireCMSContext();
183
- const dataSource = useDataSource(inputCollection);
184
- const plugins = customizationController.plugins;
142
+ const analyticsController = useAnalyticsController();
185
143
 
186
- const initialResolvedCollection = useMemo(() => resolveCollection({
187
- collection: inputCollection,
188
- path,
189
- values: entity?.values,
190
- fields: customizationController.propertyConfigs
191
- }), [entity?.values, path]);
144
+ const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
145
+
146
+ const [customIdLoading, setCustomIdLoading] = useState<boolean>(false);
192
147
 
193
148
  const mustSetCustomId: boolean = (status === "new" || status === "copy") &&
194
- (Boolean(initialResolvedCollection.customId) && initialResolvedCollection.customId !== "optional");
149
+ (Boolean(collection.customId) && collection.customId !== "optional");
195
150
 
196
- const initialEntityId = useMemo(() => {
151
+ const initialEntityId: string | undefined = useMemo((): string | undefined => {
197
152
  if (status === "new" || status === "copy") {
198
153
  if (mustSetCustomId) {
199
154
  return undefined;
200
155
  } else {
201
- return dataSource.generateEntityId(path);
156
+ return dataSource.generateEntityId(path, collection);
202
157
  }
203
158
  } else {
204
- return entity?.id;
159
+ return entityIdProp;
205
160
  }
206
- }, []);
161
+ }, [entityIdProp, status]);
207
162
 
208
- const closeAfterSaveRef = useRef(false);
163
+ const [entityId, setEntityId] = useState<string | undefined>(initialEntityId);
164
+ const [entityIdError, setEntityIdError] = useState<boolean>(false);
165
+ const [savingError, setSavingError] = useState<Error | undefined>();
209
166
 
210
- const baseDataSourceValuesRef = useRef<Partial<EntityValues<M>>>(getDataSourceEntityValues(initialResolvedCollection, status, entity));
211
-
212
- const [entityId, setEntityId] = React.useState<string | undefined>(initialEntityId);
213
- const [entityIdError, setEntityIdError] = React.useState<boolean>(false);
214
- const [savingError, setSavingError] = React.useState<Error | undefined>();
215
-
216
- const [customIdLoading, setCustomIdLoading] = React.useState<boolean>(false);
217
-
218
- // const initialValuesRef = useRef<EntityValues<M>>(entity?.values ?? baseDataSourceValues as EntityValues<M>);
219
- const [internalValues, setInternalValues] = useState<EntityValues<M> | undefined>(entity?.values ?? baseDataSourceValuesRef.current as EntityValues<M>);
220
-
221
- const save = (values: EntityValues<M>): Promise<void> => {
222
- return onEntitySaveRequested({
223
- collection: resolvedCollection,
224
- path,
225
- entityId,
226
- values,
227
- previousValues: entity?.values,
228
- closeAfterSave: closeAfterSaveRef.current,
229
- autoSave: autoSave ?? false
230
- }).then(_ => {
231
- const eventName: CMSAnalyticsEvent = status === "new"
232
- ? "new_entity_saved"
233
- : (status === "copy" ? "entity_copied" : (status === "existing" ? "entity_edited" : "unmapped_event"));
234
- analyticsController.onAnalyticsEvent?.(eventName, { path });
235
- }).catch(e => {
236
- console.error(e);
237
- setSavingError(e);
238
- }).finally(() => {
239
- closeAfterSaveRef.current = false;
240
- });
241
- };
167
+ const autoSave = collection.formAutoSave && !collection.customId;
242
168
 
243
169
  const onSubmit = (values: EntityValues<M>, formexController: FormexController<EntityValues<M>>) => {
244
170
 
@@ -255,8 +181,8 @@ function EntityFormInternal<M extends Record<string, any>>({
255
181
  if (status === "existing") {
256
182
  if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
257
183
  } else if (status === "new" || status === "copy") {
258
- if (inputCollection.customId) {
259
- if (inputCollection.customId !== "optional" && !entityId) {
184
+ if (collection.customId) {
185
+ if (collection.customId !== "optional" && !entityId) {
260
186
  throw Error("Form misconfiguration when saving, entityId should be set");
261
187
  }
262
188
  }
@@ -275,112 +201,295 @@ function EntityFormInternal<M extends Record<string, any>>({
275
201
  .finally(() => {
276
202
  formexController.setSubmitting(false);
277
203
  });
278
-
279
204
  };
280
205
 
281
- const formex: FormexController<M> = useCreateFormex<M>({
282
- initialValues: baseDataSourceValuesRef.current as M,
206
+ const formex: FormexController<M> = formexProp ?? useCreateFormex<M>({
207
+ initialValues: (initialDirtyValues ?? getInitialEntityValues(collection, path, status, entity, customizationController.propertyConfigs)) as M,
208
+ initialDirty: Boolean(initialDirtyValues),
283
209
  onSubmit,
210
+ onReset: () => {
211
+ clearDirtyCache();
212
+ onValuesModified?.(false);
213
+ },
284
214
  validation: (values) => {
285
215
  return validationSchema?.validate(values, { abortEarly: false })
286
216
  .then(() => {
287
217
  return {};
288
218
  })
289
- .catch((e) => {
290
-
291
- const errors: Record<string, string> = {};
292
- e.inner.forEach((error: any) => {
293
- errors[error.path] = error.message;
294
- });
219
+ .catch((e: any) => {
295
220
  return yupToFormErrors(e);
296
221
  });
297
222
  }
298
223
  });
299
224
 
225
+
300
226
  useEffect(() => {
301
- baseDataSourceValuesRef.current = getDataSourceEntityValues(initialResolvedCollection, status, entity);
302
- const initialValues = formex.initialValues;
303
- if (!formex.isSubmitting && initialValues && status === "existing") {
304
- setUnderlyingChanges(
305
- Object.entries(resolvedCollection.properties)
306
- .map(([key, property]) => {
307
- if (isHidden(property)) {
308
- return {};
309
- }
310
- const initialValue = initialValues[key];
311
- const latestValue = baseDataSourceValuesRef.current[key];
312
- if (!equal(initialValue, latestValue)) {
313
- return { [key]: latestValue };
314
- }
315
- return {};
316
- })
317
- .reduce((a, b) => ({ ...a, ...b }), {}) as Partial<EntityValues<M>>
318
- );
227
+
228
+ const handleKeyDown = (e: KeyboardEvent) => {
229
+ const isUndo = (e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "z";
230
+ const isRedo =
231
+ ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "z") ||
232
+ ((e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "y");
233
+
234
+ if (isUndo && formex.canUndo) {
235
+ e.preventDefault();
236
+ formex.undo();
237
+ } else if (isRedo && formex.canRedo) {
238
+ e.preventDefault();
239
+ formex.redo();
240
+ }
241
+ };
242
+
243
+ window.addEventListener("keydown", handleKeyDown);
244
+ return () => window.removeEventListener("keydown", handleKeyDown);
245
+
246
+ }, [formex]);
247
+
248
+ const resolvedCollection = useMemo(() => resolveCollection<M>({
249
+ collection,
250
+ path,
251
+ entityId,
252
+ values: formex.values,
253
+ previousValues: formex.initialValues,
254
+ propertyConfigs: customizationController.propertyConfigs
255
+ }), [collection, path, entityId, formex.values, formex.initialValues, customizationController.propertyConfigs]);
256
+
257
+ const onPreSaveHookError = useCallback((e: Error) => {
258
+ setSaving(false);
259
+ snackbarController.open({
260
+ type: "error",
261
+ message: "Error before saving: " + e?.message
262
+ });
263
+ console.error(e);
264
+ }, [snackbarController]);
265
+
266
+ const onSaveSuccessHookError = useCallback((e: Error) => {
267
+ setSaving(false);
268
+ snackbarController.open({
269
+ type: "error",
270
+ message: "Error after saving (entity is saved): " + e?.message
271
+ });
272
+ console.error(e);
273
+ }, [snackbarController]);
274
+
275
+ function clearDirtyCache() {
276
+ if (status === "new" || status === "copy") {
277
+ removeEntityFromCache(path + "#new");
319
278
  } else {
320
- setUnderlyingChanges({});
279
+ removeEntityFromCache(path + "/" + entityId);
280
+ }
281
+ }
282
+
283
+ const onSaveSuccess = (updatedEntity: Entity<M>) => {
284
+
285
+ clearDirtyCache();
286
+ onValuesModified?.(false);
287
+ setSaving(false);
288
+ if (!autoSave)
289
+ snackbarController.open({
290
+ type: "success",
291
+ message: `${collection.singularName ?? collection.name}: Saved correctly`
292
+ });
293
+ onEntityChange?.(updatedEntity);
294
+ updateStatus("existing");
295
+ setEntityId(updatedEntity.id);
296
+
297
+ if (onSaved) {
298
+ onSaved({
299
+ entity: updatedEntity,
300
+ status,
301
+ path,
302
+ entityId: updatedEntity.id,
303
+ collection
304
+ });
321
305
  }
322
- }, [entity, initialResolvedCollection, status]);
323
-
324
- const doOnValuesChanges = (values?: EntityValues<M>) => {
325
- const initialValues = formex.initialValues;
326
- setInternalValues(values);
327
- if (onValuesChanged)
328
- onValuesChanged(values);
329
- if (autoSave && values && !equal(values, initialValues)) {
330
- save(values);
306
+ };
307
+
308
+ const onSaveFailure = useCallback((e: Error) => {
309
+ setSaving(false);
310
+ snackbarController.open({
311
+ type: "error",
312
+ message: "Error saving: " + e?.message
313
+ });
314
+ console.error("Error saving entity", path, entityId);
315
+ console.error(e);
316
+ }, [entityId, path, snackbarController]);
317
+
318
+ const saveEntity = ({
319
+ values,
320
+ previousValues,
321
+ entityId,
322
+ collection,
323
+ path
324
+ }: {
325
+ collection: EntityCollection<M>,
326
+ path: string,
327
+ entityId: string | undefined,
328
+ values: M,
329
+ previousValues?: M,
330
+ }) => {
331
+ setSaving(true);
332
+ return saveEntityWithCallbacks({
333
+ path,
334
+ entityId,
335
+ values,
336
+ previousValues,
337
+ collection,
338
+ status,
339
+ dataSource,
340
+ context,
341
+ onSaveSuccess: (updatedEntity: Entity<M>) => onSaveSuccess(updatedEntity),
342
+ onSaveFailure,
343
+ onPreSaveHookError,
344
+ onSaveSuccessHookError
345
+ }).then();
346
+ };
347
+
348
+ type EntityFormSaveParams<M extends Record<string, any>> = {
349
+ collection: ResolvedEntityCollection<M>,
350
+ path: string,
351
+ entityId: string | undefined,
352
+ values: EntityValues<M>,
353
+ previousValues?: EntityValues<M>,
354
+ autoSave: boolean
355
+ };
356
+
357
+ const onSaveEntityRequest = async ({
358
+ collection,
359
+ path,
360
+ entityId,
361
+ values,
362
+ previousValues,
363
+ autoSave
364
+ }: EntityFormSaveParams<M>): Promise<void> => {
365
+ if (!status)
366
+ return;
367
+ if (autoSave) {
368
+ setValuesToBeSaved(values);
369
+ } else {
370
+ return saveEntity({
371
+ collection,
372
+ path,
373
+ entityId,
374
+ values,
375
+ previousValues
376
+ });
331
377
  }
332
378
  };
333
379
 
334
- useEffect(() => {
335
- if (entityId && onIdChange)
336
- onIdChange(entityId);
337
- }, [entityId, onIdChange]);
380
+ const lastSavedValues = useRef<EntityValues<M> | undefined>(entity?.values);
381
+ const save = (values: EntityValues<M>): Promise<void> => {
382
+ lastSavedValues.current = values;
383
+ return onSaveEntityRequest({
384
+ collection: resolvedCollection,
385
+ path,
386
+ entityId,
387
+ values,
388
+ previousValues: entity?.values,
389
+ autoSave: autoSave ?? false
390
+ }).then((res) => {
391
+ const eventName: CMSAnalyticsEvent = status === "new"
392
+ ? "new_entity_saved"
393
+ : (status === "copy" ? "entity_copied" : (status === "existing" ? "entity_edited" : "unmapped_event"));
394
+ analyticsController.onAnalyticsEvent?.(eventName, { path });
395
+ }).catch(e => {
396
+ console.error(e);
397
+ setSavingError(e);
398
+ });
399
+ };
338
400
 
339
- const resolvedCollection = resolveCollection<M>({
340
- collection: inputCollection,
401
+ const formContext: FormContext<M> = {
402
+ // @ts-ignore
403
+ setFieldValue: useCallback(formex.setFieldValue, []),
404
+ values: formex.values,
405
+ collection: resolvedCollection,
406
+ entityId: entityId as string,
341
407
  path,
342
- entityId,
343
- values: internalValues,
344
- previousValues: formex.initialValues,
345
- fields: customizationController.propertyConfigs
346
- });
408
+ save,
409
+ formex,
410
+ entity,
411
+ savingError,
412
+ status,
413
+ openEntityMode
414
+ };
347
415
 
348
- const titlePropertyKey = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
349
- const title = internalValues && titlePropertyKey ? getValueInPath(internalValues, titlePropertyKey) : undefined;
416
+ useEffect(() => {
417
+ onFormContextReady?.(formContext);
418
+ }, [formex.version, resolvedCollection, entityId, path]);
419
+
420
+ const onIdUpdateError = useCallback((error: any) => {
421
+ snackbarController.open({
422
+ type: "error",
423
+ message: "Error updating id, check the console"
424
+ });
425
+ }, []);
426
+
427
+ const pluginActions: React.ReactNode[] = [];
428
+ const plugins = customizationController.plugins;
429
+
430
+ if (plugins && collection) {
431
+ const actionProps: PluginFormActionProps = {
432
+ entityId,
433
+ path,
434
+ status,
435
+ collection: collection,
436
+ context,
437
+ currentEntityId: entityId,
438
+ formContext,
439
+ openEntityMode
440
+ };
441
+ pluginActions.push(...plugins.map((plugin) => (
442
+ plugin.form?.Actions
443
+ ? <plugin.form.Actions
444
+ key={`actions_${plugin.key}`} {...actionProps} />
445
+ : null
446
+ )).filter(Boolean));
447
+ }
350
448
 
351
- const onIdUpdate = inputCollection.callbacks?.onIdUpdate;
449
+ const titlePropertyKey = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
450
+ const title = (formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : undefined)
451
+ ?? collection.singularName
452
+ ?? collection.name;
352
453
 
454
+ const onIdUpdate = collection.callbacks?.onIdUpdate;
353
455
  const doOnIdUpdate = useCallback(async () => {
354
- if (onIdUpdate && internalValues && (status === "new" || status === "copy")) {
456
+ if (onIdUpdate && formex.values && (status === "new" || status === "copy")) {
355
457
  setCustomIdLoading(true);
356
458
  try {
357
459
  const updatedId = await onIdUpdate({
358
460
  collection: resolvedCollection,
359
461
  path,
360
462
  entityId,
361
- values: internalValues,
463
+ values: formex.values,
362
464
  context
363
465
  });
364
466
  setEntityId(updatedId);
365
467
  } catch (e) {
366
- onIdUpdateError && onIdUpdateError(e);
468
+ onIdUpdateError?.(e);
367
469
  console.error(e);
368
470
  }
369
471
  setCustomIdLoading(false);
370
472
  }
371
- }, [entityId, internalValues, status]);
473
+ }, [entityId, formex.values, status, onIdUpdate, resolvedCollection, path, context, onIdUpdateError]);
372
474
 
373
475
  useEffect(() => {
374
476
  doOnIdUpdate();
375
477
  }, [doOnIdUpdate]);
376
478
 
377
- const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
479
+ useEffect(() => {
480
+ if (!autoSave) {
481
+ onValuesModified?.(modified);
482
+ }
483
+ }, [formex.dirty]);
484
+
485
+ const deferredValues = useDeferredValue(formex.values);
486
+ const modified = formex.dirty;
378
487
 
379
488
  const uniqueFieldValidator: CustomFieldValidator = useCallback(({
380
489
  name,
381
490
  value,
382
491
  property
383
- }) => dataSource.checkUniqueField(path, name, value, entityId),
492
+ }) => dataSource.checkUniqueField(path, name, value, entityId, collection),
384
493
  [dataSource, path, entityId]);
385
494
 
386
495
  const validationSchema = useMemo(() => entityId
@@ -391,327 +500,253 @@ function EntityFormInternal<M extends Record<string, any>>({
391
500
  : undefined,
392
501
  [entityId, resolvedCollection.properties, uniqueFieldValidator]);
393
502
 
394
- const authController = useAuthController();
395
-
396
- const getActionsForEntity = useCallback(({
397
- entity,
398
- customEntityActions
399
- }: {
400
- entity?: Entity<M>,
401
- customEntityActions?: EntityAction[]
402
- }): EntityAction[] => {
403
- const createEnabled = canCreateEntity(inputCollection, authController, path, null);
404
- const deleteEnabled = entity ? canDeleteEntity(inputCollection, authController, path, entity) : true;
405
- const actions: EntityAction[] = [];
406
- if (createEnabled)
407
- actions.push(copyEntityAction);
408
- if (deleteEnabled)
409
- actions.push(deleteEntityAction);
410
- if (customEntityActions)
411
- actions.push(...customEntityActions);
412
- return actions;
413
- }, [authController, inputCollection, path]);
414
-
415
- const pluginActions: React.ReactNode[] = [];
416
-
417
- const formContext: FormContext<M> = {
418
- setFieldValue: formex.setFieldValue,
419
- values: formex.values,
420
- collection: resolvedCollection,
421
- entityId,
422
- path,
423
- save
424
- };
425
-
426
- // eslint-disable-next-line react-hooks/rules-of-hooks
427
503
  useEffect(() => {
428
- if (onFormContextChange) {
429
- onFormContextChange(formContext);
504
+ const key = (status === "new" || status === "copy") ? path + "#new" : path + "/" + entityId;
505
+ if (modified) {
506
+ saveEntityToCache(key, deferredValues);
430
507
  }
431
- }, [onFormContextChange, formContext]);
432
-
433
- if (plugins && inputCollection) {
434
- const actionProps: PluginFormActionProps = {
435
- entityId,
436
- path,
437
- status,
438
- collection: inputCollection,
439
- context,
440
- currentEntityId: entityId,
441
- formContext
442
- };
443
- pluginActions.push(...plugins.map((plugin, i) => (
444
- plugin.form?.Actions
445
- ? <plugin.form.Actions
446
- key={`actions_${plugin.key}`} {...actionProps}/>
447
- : null
448
- )).filter(Boolean));
449
- }
450
-
451
- return <Formex value={formex}>
452
- <div className="h-full overflow-auto">
453
-
454
- {pluginActions.length > 0 && <div
455
- className={cn("w-full flex justify-end items-center sticky top-0 right-0 left-0 z-10 bg-opacity-60 bg-slate-200 dark:bg-opacity-60 dark:bg-slate-800 backdrop-blur-md")}>
456
- {pluginActions}
457
- </div>}
458
-
459
- <div className="pt-12 pb-16 pl-8 pr-8 md:pl-10 md:pr-10">
460
- <div
461
- className={`w-full py-2 flex flex-col items-start mt-${4 + (pluginActions ? 8 : 0)} lg:mt-${8 + (pluginActions ? 8 : 0)} mb-8`}>
462
-
463
- <Typography
464
- className={"mt-4 flex-grow line-clamp-1 " + inputCollection.hideIdFromForm ? "mb-2" : "mb-0"}
465
- variant={"h4"}>{title ?? inputCollection.singularName ?? inputCollection.name}
466
- </Typography>
467
- <Alert color={"base"} className={"w-full"} size={"small"}>
468
- <code className={"text-xs select-all"}>{path}/{entityId}</code>
469
- </Alert>
470
- </div>
508
+ }, [deferredValues, modified, path, entityId, status]);
471
509
 
472
- {!hideId &&
473
- <CustomIdField customId={inputCollection.customId}
474
- entityId={entityId}
475
- status={status}
476
- onChange={setEntityId}
477
- error={entityIdError}
478
- loading={customIdLoading}
479
- entity={entity}/>}
480
-
481
- {entityId && <InnerForm
482
- {...formex}
483
- initialValues={formex.initialValues}
484
- onModified={onModified}
485
- onDiscard={onDiscard}
486
- onValuesChanged={doOnValuesChanges}
487
- underlyingChanges={underlyingChanges}
488
- entity={entity}
489
- resolvedCollection={resolvedCollection}
490
- formContext={formContext}
491
- status={status}
492
- savingError={savingError}
493
- closeAfterSaveRef={closeAfterSaveRef}
494
- autoSave={autoSave}
495
- entityActions={getActionsForEntity({
496
- entity,
497
- customEntityActions: inputCollection.entityActions
498
- })}/>}
499
-
500
- </div>
501
- </div>
502
- </Formex>
503
- }
504
-
505
- function InnerForm<M extends Record<string, any>>(props: FormexController<M> & {
506
- initialValues: EntityValues<M>,
507
- onModified: ((modified: boolean) => void) | undefined,
508
- onValuesChanged?: (changedValues?: EntityValues<M>) => void,
509
- underlyingChanges: Partial<M>,
510
- entity: Entity<M> | undefined,
511
- resolvedCollection: ResolvedEntityCollection<M>,
512
- formContext: FormContext<M>,
513
- onDiscard?: () => void,
514
- status: "new" | "existing" | "copy",
515
- savingError?: Error,
516
- closeAfterSaveRef: MutableRefObject<boolean>,
517
- autoSave?: boolean,
518
- entityActions: EntityAction[],
519
- }) {
520
-
521
- const {
522
- values,
523
- onDiscard,
524
- onModified,
525
- onValuesChanged,
526
- underlyingChanges,
527
- formContext,
528
- entity,
529
- touched,
530
- setFieldValue,
531
- resolvedCollection,
532
- isSubmitting,
533
- status,
534
- handleSubmit,
535
- resetForm,
536
- savingError,
537
- dirty,
538
- closeAfterSaveRef,
539
- autoSave,
540
- entityActions,
541
- } = props;
542
-
543
- const context = useFireCMSContext();
544
- const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
545
- const sideEntityController = useSideEntityController();
546
-
547
- const modified = dirty;
548
- useEffect(() => {
549
- if (onModified)
550
- onModified(modified);
551
- if (onValuesChanged)
552
- onValuesChanged(values);
553
- }, [modified, values]);
510
+ useOnAutoSave(autoSave, formex, lastSavedValues, save);
554
511
 
555
512
  useEffect(() => {
556
- if (!autoSave && !isSubmitting && underlyingChanges && entity) {
513
+ if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
557
514
  // we update the form fields from the Firestore data
558
515
  // if they were not touched
559
516
  Object.entries(underlyingChanges).forEach(([key, value]) => {
560
- const formValue = values[key];
561
- if (!equal(value, formValue) && !touched[key]) {
517
+ const formValue = formex.values[key];
518
+ if (!equal(value, formValue) && !formex.touched[key]) {
562
519
  console.debug("Updated value from the datasource:", key, value);
563
- setFieldValue(key, value !== undefined ? value : null);
520
+ formex.setFieldValue(key, value !== undefined ? value : null);
564
521
  }
565
522
  });
566
523
  }
567
- }, [isSubmitting, autoSave, underlyingChanges, entity, values, touched, setFieldValue]);
524
+ }, [formex.isSubmitting, autoSave, underlyingChanges, entity, formex.values, formex.touched, formex.setFieldValue]);
568
525
 
569
- const formFields = (
570
- <div className={"flex flex-col gap-8"}>
571
- {(resolvedCollection.propertiesOrder ?? Object.keys(resolvedCollection.properties))
572
- .map((key) => {
526
+ const formFieldKeys = getFormFieldKeys(resolvedCollection);
573
527
 
528
+ const formFields = () => {
529
+
530
+ if (Builder) {
531
+ return <Builder
532
+ collection={collection}
533
+ entity={entity}
534
+ modifiedValues={formex.values}
535
+ formContext={formContext}
536
+ />;
537
+ }
538
+ return (
539
+ <FormLayout>
540
+ {formFieldKeys.map((key) => {
574
541
  const property = resolvedCollection.properties[key];
575
- if (!property) {
576
- console.warn(`Property ${key} not found in collection ${resolvedCollection.name}`);
577
- return null;
542
+ if (property) {
543
+ const underlyingValueHasChanged: boolean =
544
+ !!underlyingChanges &&
545
+ Object.keys(underlyingChanges).includes(key) &&
546
+ formex.touched[key];
547
+ const disabled = (!autoSave && formex.isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
548
+ const hidden = isHidden(property);
549
+ if (hidden) return null;
550
+ const widthPercentage = property.widthPercentage ?? 100;
551
+ const cmsFormFieldProps: PropertyFieldBindingProps<any, M> = {
552
+ propertyKey: key,
553
+ disabled,
554
+ property,
555
+ includeDescription: property.description || property.longDescription,
556
+ underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
557
+ context: formContext,
558
+ partOfArray: false,
559
+ minimalistView: false,
560
+ autoFocus: false
561
+ };
562
+
563
+ return (
564
+ <FormEntry propertyKey={key}
565
+ widthPercentage={widthPercentage}
566
+ key={`field_${key}`}>
567
+ <PropertyFieldBinding {...cmsFormFieldProps} />
568
+ </FormEntry>
569
+ );
578
570
  }
579
571
 
580
- const underlyingValueHasChanged: boolean =
581
- !!underlyingChanges &&
582
- Object.keys(underlyingChanges).includes(key) &&
583
- !!touched[key];
584
-
585
- const disabled = (!autoSave && isSubmitting) || isReadOnly(property) || Boolean(property.disabled);
586
- const hidden = isHidden(property);
587
- if (hidden) return null;
588
- const cmsFormFieldProps: PropertyFieldBindingProps<any, M> = {
589
- propertyKey: key,
590
- disabled,
591
- property,
592
- includeDescription: property.description || property.longDescription,
593
- underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
594
- context: formContext,
595
- tableMode: false,
596
- partOfArray: false,
597
- partOfBlock: false,
598
- autoFocus: false
599
- };
600
-
601
- return (
602
- <div id={`form_field_${key}`}
603
- key={`field_${resolvedCollection.name}_${key}`}>
604
- <ErrorBoundary>
605
- <Tooltip title={<PropertyIdCopyTooltipContent propertyId={key}/>}
606
- delayDuration={800}
607
- side={"left"}
608
- align={"start"}
609
- sideOffset={16}>
610
- <PropertyFieldBinding {...cmsFormFieldProps}/>
611
- </Tooltip>
612
- </ErrorBoundary>
613
- </div>
614
- );
615
- })
616
- .filter(Boolean)}
572
+ const additionalField = resolvedCollection.additionalFields?.find(f => f.key === key);
573
+ if (additionalField && entity) {
574
+ const Builder = additionalField.Builder;
575
+ if (!Builder && !additionalField.value) {
576
+ throw new Error("When using additional fields you need to provide a Builder or a value");
577
+ }
578
+ const child = Builder
579
+ ? <Builder entity={entity} context={context}/>
580
+ : <div className={"w-full"}>
581
+ {additionalField.value?.({
582
+ entity,
583
+ context
584
+ })?.toString()}
585
+ </div>;
586
+
587
+ return (
588
+ <div key={`additional_${key}`} className={"w-full"}>
589
+ <LabelWithIconAndTooltip
590
+ propertyKey={key}
591
+ icon={<NotesIcon size={"small"}/>}
592
+ title={additionalField.name}
593
+ className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
594
+ <div
595
+ className={cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
596
+ <ErrorBoundary>
597
+ {child}
598
+ </ErrorBoundary>
599
+ </div>
600
+ </div>
601
+ );
602
+ }
617
603
 
618
- </div>
619
- );
604
+ console.warn(`Property ${key} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
605
+ return null;
606
+ }).filter(Boolean)}
607
+ </FormLayout>
608
+ );
609
+ };
610
+
611
+ const formRef = useRef<HTMLDivElement>(null);
612
+
613
+ const formView = <ErrorBoundary>
614
+ <>
615
+ {!Builder && <div className={"w-full py-2 flex flex-col items-start my-4 lg:my-6"}>
616
+ <Typography
617
+ className={"py-4 flex-grow line-clamp-1 " + (collection.hideIdFromForm ? "mb-2" : "mb-0")}
618
+ variant={"h4"}>
619
+ {title ?? collection.singularName ?? collection.name}
620
+ </Typography>
621
+ {showEntityPath && <Alert color={"base"} className={"w-full"} size={"small"}>
622
+ <code
623
+ className={"text-xs select-all text-text-secondary dark:text-text-secondary-dark"}>
624
+ {entity?.path ?? path}/{entityId}
625
+ </code>
626
+ </Alert>}
627
+ </div>}
628
+
629
+ {children}
630
+
631
+ {!Builder && !collection.hideIdFromForm &&
632
+ <CustomIdField customId={collection.customId}
633
+ entityId={entityId}
634
+ status={status}
635
+ onChange={setEntityId}
636
+ error={entityIdError}
637
+ loading={customIdLoading}
638
+ entity={entity}/>
639
+ }
640
+
641
+ {entityId && formContext && <>
642
+ <div className="mt-12 flex flex-col gap-8" ref={formRef}>
643
+ {formFields()}
644
+ <ErrorFocus containerRef={formRef}/>
645
+ </div>
646
+ </>}
647
+
648
+ {forceActionsAtTheBottom && <div className="h-16"/>}
649
+ </>
650
+ </ErrorBoundary>;
651
+
652
+ useEffect(() => {
653
+ if (entityId && onIdChange)
654
+ onIdChange(entityId);
655
+ }, [entityId, onIdChange]);
656
+
657
+ const disabled = formex.isSubmitting || (!formex.dirty && status === "existing");
620
658
 
621
- const disabled = isSubmitting || (!modified && status === "existing");
622
- const formRef = React.useRef<HTMLDivElement>(null);
659
+ if (!resolvedCollection || !path) {
660
+ throw Error("INTERNAL: Collection and path must be defined in form context");
661
+ }
662
+
663
+ const dialogActions = <EntityFormActionsComponent
664
+ collection={resolvedCollection}
665
+ path={path}
666
+ entity={entity}
667
+ layout={forceActionsAtTheBottom ? "bottom" : "side"}
668
+ savingError={savingError}
669
+ formex={formex}
670
+ disabled={disabled}
671
+ status={status}
672
+ pluginActions={pluginActions ?? []}
673
+ openEntityMode={openEntityMode}
674
+ showDefaultActions={showDefaultActions}
675
+ />;
623
676
 
624
677
  return (
678
+ <Formex value={formContext.formex}>
679
+ <form
680
+ onSubmit={formContext.formex.handleSubmit}
681
+ onReset={() => formex.resetForm({
682
+ values: getInitialEntityValues(collection, path, status, entity, customizationController.propertyConfigs) as M
683
+ })}
684
+ noValidate
685
+ className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className)}>
686
+ <div
687
+ id={`form_${path}`}
688
+ className={cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit")}>
689
+
690
+ <div className={cls("flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10")}>
691
+
692
+ {formContext.formex.dirty
693
+ ? <Tooltip title={"Unsaved changes"}
694
+ className={"self-end sticky top-4 z-10"}>
695
+ <Chip size={"small"} colorScheme={"orangeDarker"}>
696
+ <EditIcon size={"smallest"}/>
697
+ </Chip>
698
+ </Tooltip>
699
+ : <Tooltip title={"In sync with the database"}
700
+ className={"self-end sticky top-4 z-10"}>
701
+ <Chip size={"small"}>
702
+ <CheckIcon size={"smallest"}/>
703
+ </Chip>
704
+ </Tooltip>}
705
+
706
+ {formView}
707
+
708
+ </div>
709
+
710
+ </div>
711
+ {dialogActions}
712
+ </form>
625
713
 
626
- <form onSubmit={handleSubmit}
627
- onReset={() => {
628
- console.debug("Resetting form")
629
- resetForm();
630
- return onDiscard && onDiscard();
631
- }}
632
- noValidate>
633
- <div className="mt-12"
634
- ref={formRef}>
635
-
636
- {formFields}
637
-
638
- <ErrorFocus containerRef={formRef}/>
639
-
640
- </div>
641
-
642
- <div className="h-14"/>
643
-
644
- {!autoSave && <DialogActions position={"absolute"}>
645
-
646
- {savingError &&
647
- <div className="text-right">
648
- <Typography color={"error"}>
649
- {savingError.message}
650
- </Typography>
651
- </div>}
652
-
653
- {entity && formActions.length > 0 && <div className="flex-grow flex overflow-auto no-scrollbar">
654
- {formActions.map(action => (
655
- <IconButton
656
- key={action.name}
657
- color="primary"
658
- onClick={(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
659
- event.stopPropagation();
660
- if (entity)
661
- action.onClick({
662
- entity,
663
- fullPath: resolvedCollection.path,
664
- collection: resolvedCollection,
665
- context,
666
- sideEntityController,
667
- });
668
- }}>
669
- {action.icon}
670
- </IconButton>
671
- ))}
672
- </div>}
673
-
674
- <Button
675
- variant="text"
676
- disabled={disabled}
677
- type="reset"
678
- >
679
- {status === "existing" ? "Discard" : "Clear"}
680
- </Button>
681
-
682
- <Button
683
- variant="text"
684
- color="primary"
685
- type="submit"
686
- disabled={disabled}
687
- onClick={() => {
688
- closeAfterSaveRef.current = false;
689
- }}
690
- >
691
- {status === "existing" && "Save"}
692
- {status === "copy" && "Create copy"}
693
- {status === "new" && "Create"}
694
- </Button>
695
-
696
- <Button
697
- variant="filled"
698
- color="primary"
699
- type="submit"
700
- disabled={disabled}
701
- onClick={() => {
702
- closeAfterSaveRef.current = true;
703
- }}
704
- >
705
- {status === "existing" && "Save and close"}
706
- {status === "copy" && "Create copy and close"}
707
- {status === "new" && "Create and close"}
708
- </Button>
709
-
710
- </DialogActions>}
711
- </form>
714
+ </Formex>
712
715
  );
713
716
  }
714
717
 
718
+ function getInitialEntityValues<M extends object>(
719
+ collection: EntityCollection,
720
+ path: string,
721
+ status: "new" | "existing" | "copy",
722
+ entity: Entity<M> | undefined,
723
+ propertyConfigs?: Record<string, PropertyConfig>
724
+ ): Partial<EntityValues<M>> {
725
+ const resolvedCollection = resolveCollection({
726
+ collection,
727
+ path,
728
+ values: entity?.values,
729
+ propertyConfigs
730
+ });
731
+ const properties = resolvedCollection.properties;
732
+ if ((status === "existing" || status === "copy") && entity) {
733
+ if (!collection.alwaysApplyDefaultValues) {
734
+ return entity.values ?? getDefaultValuesFor(properties);
735
+ } else {
736
+ const defaultValues = getDefaultValuesFor(properties);
737
+ return mergeDeep(defaultValues, entity.values ?? {});
738
+ }
739
+ } else if (status === "new") {
740
+ return getDefaultValuesFor(properties);
741
+ } else {
742
+ console.error({
743
+ status,
744
+ entity
745
+ });
746
+ throw new Error("Form has not been initialised with the correct parameters");
747
+ }
748
+ }
749
+
715
750
  export function yupToFormErrors(yupError: ValidationError): Record<string, any> {
716
751
  let errors: Record<string, any> = {};
717
752
  if (yupError.inner) {
@@ -726,3 +761,14 @@ export function yupToFormErrors(yupError: ValidationError): Record<string, any>
726
761
  }
727
762
  return errors;
728
763
  }
764
+
765
+ function useOnAutoSave(autoSave: undefined | boolean, formex: FormexController<any>, lastSavedValues: any, save: (values: EntityValues<any>) => Promise<void>) {
766
+ if (!autoSave) return;
767
+ useEffect(() => {
768
+ if (autoSave) {
769
+ if (formex.values && !equal(formex.values, lastSavedValues.current)) {
770
+ save(formex.values);
771
+ }
772
+ }
773
+ }, [formex.values]);
774
+ }