@firecms/core 3.0.0-canary.20 → 3.0.0-canary.201

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