@firecms/core 3.0.1 → 3.1.0-canary.02232f4

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 (334) hide show
  1. package/README.md +1 -1
  2. package/dist/components/AIIcon.d.ts +16 -0
  3. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
  4. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  5. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
  6. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
  7. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
  8. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
  9. package/dist/components/EntityCollectionTable/internal/popup_field/useDraggable.d.ts +2 -2
  10. package/dist/components/EntityCollectionView/Board.d.ts +2 -0
  11. package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
  12. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
  13. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
  14. package/dist/components/EntityCollectionView/CollectionDataErrorBanner.d.ts +4 -0
  15. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
  16. package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
  17. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
  18. package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
  19. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
  20. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
  21. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
  22. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +44 -0
  23. package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
  24. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
  25. package/dist/components/ErrorBoundary.d.ts +4 -2
  26. package/dist/components/HomePage/DefaultHomePage.d.ts +0 -1
  27. package/dist/components/LanguageToggle.d.ts +1 -0
  28. package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
  29. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
  30. package/dist/components/UnsavedChangesDialog.d.ts +1 -0
  31. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  32. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +4 -1
  33. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  34. package/dist/components/VirtualTable/VirtualTableProps.d.ts +17 -1
  35. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  36. package/dist/components/VirtualTable/types.d.ts +3 -0
  37. package/dist/components/index.d.ts +4 -0
  38. package/dist/contexts/index.d.ts +10 -0
  39. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  40. package/dist/core/index.d.ts +1 -0
  41. package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
  42. package/dist/editor/components/editor-bubble-item.d.ts +8 -0
  43. package/dist/editor/components/editor-bubble.d.ts +8 -0
  44. package/dist/editor/components/image-bubble.d.ts +5 -0
  45. package/dist/editor/components/index.d.ts +16 -0
  46. package/dist/editor/components/table-bubble.d.ts +5 -0
  47. package/dist/editor/editor.d.ts +30 -0
  48. package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
  49. package/dist/editor/extensions/Image/index.d.ts +6 -0
  50. package/dist/editor/extensions/Image.d.ts +6 -0
  51. package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
  52. package/dist/editor/extensions/clipboard.d.ts +7 -0
  53. package/dist/editor/extensions/custom-keymap.d.ts +1 -0
  54. package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
  55. package/dist/editor/hooks/useProseMirror.d.ts +13 -0
  56. package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
  57. package/dist/editor/index.d.ts +2 -0
  58. package/dist/editor/markdown.d.ts +5 -0
  59. package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
  60. package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
  61. package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
  62. package/dist/editor/nodeViews/index.d.ts +6 -0
  63. package/dist/editor/plugins/index.d.ts +2 -0
  64. package/dist/editor/plugins/inputrules.d.ts +6 -0
  65. package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
  66. package/dist/editor/plugins/slashCommandPlugin.d.ts +12 -0
  67. package/dist/editor/schema.d.ts +2 -0
  68. package/dist/editor/selectors/ai-selector.d.ts +0 -0
  69. package/dist/editor/selectors/color-selector.d.ts +10 -0
  70. package/dist/editor/selectors/link-selector.d.ts +8 -0
  71. package/dist/editor/selectors/node-selector.d.ts +15 -0
  72. package/dist/editor/selectors/text-buttons.d.ts +1 -0
  73. package/dist/editor/types.d.ts +5 -0
  74. package/dist/editor/useProseMirror.d.ts +16 -0
  75. package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
  76. package/dist/editor/utils/remove_classes.d.ts +1 -0
  77. package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
  78. package/dist/form/components/ErrorFocus.d.ts +1 -1
  79. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  80. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  81. package/dist/form/validation.d.ts +3 -2
  82. package/dist/hooks/index.d.ts +1 -0
  83. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  84. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  85. package/dist/hooks/useCollapsedGroups.d.ts +6 -3
  86. package/dist/hooks/useTranslation.d.ts +17 -0
  87. package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
  88. package/dist/index.d.ts +5 -0
  89. package/dist/index.es.js +30146 -15178
  90. package/dist/index.es.js.map +1 -1
  91. package/dist/index.umd.js +30032 -15085
  92. package/dist/index.umd.js.map +1 -1
  93. package/dist/internal/useRestoreScroll.d.ts +1 -1
  94. package/dist/locales/de.d.ts +2 -0
  95. package/dist/locales/en.d.ts +10 -0
  96. package/dist/locales/es.d.ts +10 -0
  97. package/dist/locales/fr.d.ts +2 -0
  98. package/dist/locales/hi.d.ts +2 -0
  99. package/dist/locales/it.d.ts +2 -0
  100. package/dist/locales/pt.d.ts +7 -0
  101. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  102. package/dist/preview/components/DatePreview.d.ts +13 -3
  103. package/dist/preview/components/ImagePreview.d.ts +5 -1
  104. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  105. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  106. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  107. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  108. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  109. package/dist/types/analytics.d.ts +1 -1
  110. package/dist/types/collections.d.ts +88 -2
  111. package/dist/types/customization_controller.d.ts +2 -1
  112. package/dist/types/datasource.d.ts +0 -1
  113. package/dist/types/firecms.d.ts +2 -1
  114. package/dist/types/index.d.ts +1 -0
  115. package/dist/types/navigation.d.ts +2 -2
  116. package/dist/types/plugins.d.ts +69 -1
  117. package/dist/types/properties.d.ts +268 -12
  118. package/dist/types/storage.d.ts +1 -0
  119. package/dist/types/translations.d.ts +669 -0
  120. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  121. package/dist/util/__tests__/objects.test.d.ts +1 -0
  122. package/dist/util/conditions.d.ts +26 -0
  123. package/dist/util/entities.d.ts +2 -3
  124. package/dist/util/index.d.ts +3 -1
  125. package/dist/util/lazy_eager.d.ts +7 -0
  126. package/dist/util/objects.d.ts +1 -0
  127. package/dist/util/property_utils.d.ts +2 -1
  128. package/dist/util/resolutions.d.ts +3 -3
  129. package/dist/util/useStorageUploadController.d.ts +10 -1
  130. package/package.json +51 -12
  131. package/src/app/Scaffold.tsx +20 -19
  132. package/src/components/AIIcon.tsx +41 -0
  133. package/src/components/ArrayContainer.tsx +7 -8
  134. package/src/components/ClearFilterSortButton.tsx +25 -19
  135. package/src/components/ConfirmationDialog.tsx +4 -4
  136. package/src/components/DeleteEntityDialog.tsx +12 -11
  137. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +82 -43
  138. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  139. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  140. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  141. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
  142. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +24 -44
  143. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  144. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
  145. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
  146. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
  147. package/src/components/EntityCollectionView/Board.tsx +324 -0
  148. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  149. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  150. package/src/components/EntityCollectionView/BoardSortableList.tsx +174 -0
  151. package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
  152. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  153. package/src/components/EntityCollectionView/EntityCard.tsx +235 -0
  154. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +706 -0
  155. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +236 -0
  156. package/src/components/EntityCollectionView/EntityCollectionView.tsx +531 -209
  157. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +35 -22
  158. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +86 -15
  159. package/src/components/EntityCollectionView/FiltersDialog.tsx +252 -0
  160. package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
  161. package/src/components/EntityCollectionView/board_types.ts +113 -0
  162. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  163. package/src/components/EntityJsonPreview.tsx +2 -1
  164. package/src/components/EntityView.tsx +3 -2
  165. package/src/components/ErrorBoundary.tsx +27 -15
  166. package/src/components/ErrorTooltip.tsx +2 -1
  167. package/src/components/HomePage/DefaultHomePage.tsx +65 -22
  168. package/src/components/HomePage/HomePageDnD.tsx +59 -42
  169. package/src/components/HomePage/NavigationCard.tsx +20 -18
  170. package/src/components/HomePage/NavigationGroup.tsx +20 -17
  171. package/src/components/HomePage/RenameGroupDialog.tsx +15 -15
  172. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  173. package/src/components/LanguageToggle.tsx +66 -0
  174. package/src/components/NotFoundPage.tsx +5 -3
  175. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +12 -17
  176. package/src/components/ReferenceWidget.tsx +5 -6
  177. package/src/components/SearchIconsView.tsx +3 -1
  178. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  179. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  180. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +50 -40
  181. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +53 -40
  182. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +60 -58
  183. package/src/components/UnsavedChangesDialog.tsx +6 -6
  184. package/src/components/UserDisplay.tsx +4 -4
  185. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
  186. package/src/components/VirtualTable/VirtualTable.tsx +275 -119
  187. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  188. package/src/components/VirtualTable/VirtualTableHeader.tsx +76 -64
  189. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +163 -42
  190. package/src/components/VirtualTable/VirtualTableProps.tsx +21 -2
  191. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  192. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  193. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +19 -6
  194. package/src/components/VirtualTable/types.tsx +3 -0
  195. package/src/components/common/default_entity_actions.tsx +4 -0
  196. package/src/components/common/useColumnsIds.tsx +95 -3
  197. package/src/components/common/useDataSourceTableController.tsx +12 -4
  198. package/src/components/index.tsx +5 -0
  199. package/src/contexts/BreacrumbsContext.tsx +15 -8
  200. package/src/contexts/index.ts +10 -0
  201. package/src/core/DefaultAppBar.tsx +49 -32
  202. package/src/core/DefaultDrawer.tsx +49 -57
  203. package/src/core/DrawerNavigationGroup.tsx +120 -0
  204. package/src/core/DrawerNavigationItem.tsx +4 -3
  205. package/src/core/EntityEditView.tsx +94 -50
  206. package/src/core/EntityEditViewFormActions.tsx +24 -17
  207. package/src/core/EntitySidePanel.tsx +34 -30
  208. package/src/core/FireCMS.tsx +33 -6
  209. package/src/core/SideDialogs.tsx +4 -2
  210. package/src/core/field_configs.tsx +18 -11
  211. package/src/core/index.tsx +1 -0
  212. package/src/editor/components/SlashCommandMenu.tsx +516 -0
  213. package/src/editor/components/editor-bubble-item.tsx +32 -0
  214. package/src/editor/components/editor-bubble.tsx +118 -0
  215. package/src/editor/components/image-bubble.tsx +156 -0
  216. package/src/editor/components/index.ts +14 -0
  217. package/src/editor/components/table-bubble.tsx +165 -0
  218. package/src/editor/editor.tsx +455 -0
  219. package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
  220. package/src/editor/extensions/Image/index.ts +133 -0
  221. package/src/editor/extensions/Image.ts +159 -0
  222. package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
  223. package/src/editor/extensions/clipboard.ts +72 -0
  224. package/src/editor/extensions/custom-keymap.ts +24 -0
  225. package/src/editor/extensions/drag-and-drop.tsx +480 -0
  226. package/src/editor/hooks/useProseMirror.ts +124 -0
  227. package/src/editor/hooks/useProseMirrorContext.ts +15 -0
  228. package/src/editor/index.ts +2 -0
  229. package/src/editor/markdown.ts +172 -0
  230. package/src/editor/nodeViews/ImageComponent.tsx +20 -0
  231. package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
  232. package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
  233. package/src/editor/nodeViews/index.ts +35 -0
  234. package/src/editor/plugins/index.ts +58 -0
  235. package/src/editor/plugins/inputrules.ts +82 -0
  236. package/src/editor/plugins/placeholderPlugin.ts +55 -0
  237. package/src/editor/plugins/slashCommandPlugin.ts +61 -0
  238. package/src/editor/schema.ts +240 -0
  239. package/src/editor/selectors/ai-selector.tsx +111 -0
  240. package/src/editor/selectors/color-selector.tsx +200 -0
  241. package/src/editor/selectors/link-selector.tsx +118 -0
  242. package/src/editor/selectors/node-selector.tsx +157 -0
  243. package/src/editor/selectors/text-buttons.tsx +86 -0
  244. package/src/editor/types.ts +6 -0
  245. package/src/editor/useProseMirror.ts +126 -0
  246. package/src/editor/utils/prosemirror-utils.ts +108 -0
  247. package/src/editor/utils/remove_classes.ts +17 -0
  248. package/src/editor/utils/useDebouncedCallback.ts +25 -0
  249. package/src/form/EntityForm.tsx +149 -67
  250. package/src/form/EntityFormActions.tsx +19 -12
  251. package/src/form/PropertyFieldBinding.tsx +68 -51
  252. package/src/form/components/ErrorFocus.tsx +3 -3
  253. package/src/form/components/LocalChangesMenu.tsx +13 -13
  254. package/src/form/components/StorageItemPreview.tsx +5 -3
  255. package/src/form/components/StorageUploadProgress.tsx +18 -3
  256. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +18 -5
  257. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +22 -10
  258. package/src/form/field_bindings/BlockFieldBinding.tsx +26 -9
  259. package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -17
  260. package/src/form/field_bindings/KeyValueFieldBinding.tsx +46 -25
  261. package/src/form/field_bindings/MapFieldBinding.tsx +88 -70
  262. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +93 -52
  263. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +15 -1
  264. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +25 -11
  265. package/src/form/field_bindings/ReferenceFieldBinding.tsx +25 -11
  266. package/src/form/field_bindings/RepeatFieldBinding.tsx +21 -6
  267. package/src/form/field_bindings/SelectFieldBinding.tsx +7 -5
  268. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +110 -92
  269. package/src/form/field_bindings/SwitchFieldBinding.tsx +31 -14
  270. package/src/form/field_bindings/TextFieldBinding.tsx +77 -38
  271. package/src/form/field_bindings/UserSelectFieldBinding.tsx +7 -5
  272. package/src/form/validation.ts +245 -160
  273. package/src/hooks/index.tsx +1 -0
  274. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  275. package/src/hooks/useBuildNavigationController.tsx +91 -41
  276. package/src/hooks/useCollapsedGroups.ts +18 -9
  277. package/src/hooks/useTranslation.ts +31 -0
  278. package/src/hooks/useValidateAuthenticator.tsx +1 -1
  279. package/src/i18n/FireCMSi18nProvider.tsx +160 -0
  280. package/src/index.ts +5 -0
  281. package/src/internal/useBuildDataSource.ts +68 -34
  282. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  283. package/src/internal/useBuildSideEntityController.tsx +24 -24
  284. package/src/internal/useRestoreScroll.tsx +26 -14
  285. package/src/locales/de.ts +718 -0
  286. package/src/locales/en.ts +730 -0
  287. package/src/locales/es.ts +730 -0
  288. package/src/locales/fr.ts +718 -0
  289. package/src/locales/hi.ts +718 -0
  290. package/src/locales/it.ts +718 -0
  291. package/src/locales/pt.ts +727 -0
  292. package/src/preview/PropertyPreview.tsx +43 -33
  293. package/src/preview/PropertyPreviewProps.tsx +6 -0
  294. package/src/preview/components/DatePreview.tsx +72 -4
  295. package/src/preview/components/EmptyValue.tsx +1 -1
  296. package/src/preview/components/ImagePreview.tsx +37 -21
  297. package/src/preview/components/ReferencePreview.tsx +2 -1
  298. package/src/preview/components/StorageThumbnail.tsx +16 -12
  299. package/src/preview/components/UrlComponentPreview.tsx +32 -27
  300. package/src/preview/components/UserPreview.tsx +3 -1
  301. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  302. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  303. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  304. package/src/preview/property_previews/MapPropertyPreview.tsx +49 -27
  305. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  306. package/src/routes/CustomCMSRoute.tsx +1 -0
  307. package/src/routes/FireCMSRoute.tsx +87 -65
  308. package/src/types/analytics.ts +10 -0
  309. package/src/types/collections.ts +97 -3
  310. package/src/types/customization_controller.tsx +2 -1
  311. package/src/types/datasource.ts +54 -56
  312. package/src/types/firecms.tsx +2 -1
  313. package/src/types/index.ts +1 -0
  314. package/src/types/navigation.ts +2 -2
  315. package/src/types/plugins.tsx +77 -1
  316. package/src/types/properties.ts +359 -37
  317. package/src/types/storage.ts +2 -1
  318. package/src/types/translations.ts +752 -0
  319. package/src/util/__tests__/conditions.test.ts +506 -0
  320. package/src/util/__tests__/objects.test.ts +196 -0
  321. package/src/util/callbacks.ts +6 -3
  322. package/src/util/collections.ts +51 -6
  323. package/src/util/conditions.ts +339 -0
  324. package/src/util/entities.ts +29 -30
  325. package/src/util/entity_cache.ts +2 -1
  326. package/src/util/index.ts +3 -1
  327. package/src/util/join_collections.ts +10 -8
  328. package/src/util/lazy_eager.tsx +33 -0
  329. package/src/util/objects.ts +46 -13
  330. package/src/util/{references.ts → previews.ts} +16 -2
  331. package/src/util/property_utils.tsx +37 -11
  332. package/src/util/resolutions.ts +62 -58
  333. package/src/util/useStorageUploadController.tsx +23 -29
  334. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -40,7 +40,10 @@ import {
40
40
  useSnackbarController
41
41
  } from "../hooks";
42
42
  import { Alert, CheckIcon, Chip, cls, EditIcon, NotesIcon, paperMixin, Tooltip, Typography } from "@firecms/ui";
43
- import { Formex, FormexController, getIn, setIn, useCreateFormex } from "@firecms/formex";
43
+ import { Formex, FormexController, getIn, setIn, useCreateFormex,
44
+ useFormex
45
+ } from "@firecms/formex";
46
+ import { useTranslation } from "../hooks";
44
47
  import { useAnalyticsController } from "../hooks/useAnalyticsController";
45
48
  import { FormEntry, FormLayout, LabelWithIconAndTooltip, PropertyFieldBinding } from "../form";
46
49
  import { ValidationError } from "yup";
@@ -122,6 +125,35 @@ export function extractTouchedValues(values: any, touched: Record<string, boolea
122
125
  return acc;
123
126
  }
124
127
 
128
+ /**
129
+ * Recursively removes empty plain objects `{}` and empty arrays `[]` from a value tree.
130
+ * This prevents ghost containers created by `setIn` intermediate path construction
131
+ * (e.g. `{ address: {} }` when only `address.city` was touched but value is undefined)
132
+ * from falsely triggering the unsaved local changes indicator.
133
+ */
134
+ function removeEmptyContainers(obj: any): any {
135
+ if (Array.isArray(obj)) {
136
+ const cleaned = obj.map(removeEmptyContainers);
137
+ // Keep arrays even if they contain only nulls/undefined — that's intentional data
138
+ return cleaned;
139
+ }
140
+ if (obj && typeof obj === "object" && Object.getPrototypeOf(obj) === Object.prototype) {
141
+ const result: Record<string, any> = {};
142
+ for (const key of Object.keys(obj)) {
143
+ const cleaned = removeEmptyContainers(obj[key]);
144
+ // Skip empty plain objects
145
+ if (cleaned && typeof cleaned === "object" && !Array.isArray(cleaned)
146
+ && Object.getPrototypeOf(cleaned) === Object.prototype
147
+ && Object.keys(cleaned).length === 0) {
148
+ continue;
149
+ }
150
+ result[key] = cleaned;
151
+ }
152
+ return result;
153
+ }
154
+ return obj;
155
+ }
156
+
125
157
  export function getChanges<T extends object>(source: Partial<T>, comparison: Partial<T>): Partial<T> {
126
158
  const changes: Partial<T> = {};
127
159
 
@@ -181,30 +213,30 @@ export function getChanges<T extends object>(source: Partial<T>, comparison: Par
181
213
  }
182
214
 
183
215
  export function EntityForm<M extends Record<string, any>>({
184
- path,
185
- fullIdPath,
186
- entityId: entityIdProp,
187
- collection,
188
- onValuesModified,
189
- onIdChange,
190
- onSaved,
191
- entity,
192
- initialDirtyValues,
193
- onFormContextReady,
194
- forceActionsAtTheBottom,
195
- initialStatus,
196
- className,
197
- onStatusChange,
198
- onEntityChange,
199
- openEntityMode = "full_screen",
200
- formex: formexProp,
201
- disabled: disabledProp,
202
- Builder,
203
- EntityFormActionsComponent = EntityFormActions,
204
- showDefaultActions = true,
205
- showEntityPath = true,
206
- children
207
- }: EntityFormProps<M>) {
216
+ path,
217
+ fullIdPath,
218
+ entityId: entityIdProp,
219
+ collection,
220
+ onValuesModified,
221
+ onIdChange,
222
+ onSaved,
223
+ entity,
224
+ initialDirtyValues,
225
+ onFormContextReady,
226
+ forceActionsAtTheBottom,
227
+ initialStatus,
228
+ className,
229
+ onStatusChange,
230
+ onEntityChange,
231
+ openEntityMode = "full_screen",
232
+ formex: formexProp,
233
+ disabled: disabledProp,
234
+ Builder,
235
+ EntityFormActionsComponent = EntityFormActions,
236
+ showDefaultActions = true,
237
+ showEntityPath = true,
238
+ children
239
+ }: EntityFormProps<M>) {
208
240
 
209
241
  if (collection.customId && collection.formAutoSave) {
210
242
  console.warn(`The collection ${collection.path} has customId and formAutoSave enabled. This is not supported and formAutoSave will be ignored`);
@@ -212,6 +244,7 @@ export function EntityForm<M extends Record<string, any>>({
212
244
 
213
245
  const sideEntityController = useSideEntityController();
214
246
  const navigationController = useNavigationController();
247
+ const { t } = useTranslation();
215
248
 
216
249
  const navigateBack = useCallback(() => {
217
250
  if (openEntityMode === "side_panel") {
@@ -247,7 +280,7 @@ export function EntityForm<M extends Record<string, any>>({
247
280
  const context = useFireCMSContext();
248
281
  const analyticsController = useAnalyticsController();
249
282
 
250
- const [underlyingChanges] = useState<Partial<EntityValues<M>>>({});
283
+ const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
251
284
 
252
285
  const [customIdLoading, setCustomIdLoading] = useState<boolean>(false);
253
286
 
@@ -328,7 +361,15 @@ export function EntityForm<M extends Record<string, any>>({
328
361
  return [initialValues, initialDirty];
329
362
  }, [autoApplyLocalChanges, localChangesDataRaw, baseInitialValues, initialDirtyValues]);
330
363
 
331
- const hasLocalChanges = !localChangesCleared && localChangesDataRaw && Object.keys(localChangesDataRaw).length > 0;
364
+ const hasLocalChanges = useMemo(() => {
365
+ if (localChangesCleared || !localChangesDataRaw || Object.keys(localChangesDataRaw).length === 0) {
366
+ return false;
367
+ }
368
+ // Compare cached values against entity values to check for real differences
369
+ const entityValues = entity?.values ?? {};
370
+ const realChanges = getChanges(localChangesDataRaw as Partial<M>, entityValues as Partial<M>);
371
+ return Object.keys(realChanges).length > 0;
372
+ }, [localChangesCleared, localChangesDataRaw, entity?.values]);
332
373
 
333
374
  const formex: FormexController<M> = formexProp ?? useCreateFormex<M>({
334
375
  initialValues: initialValues as M,
@@ -348,8 +389,10 @@ export function EntityForm<M extends Record<string, any>>({
348
389
  onValuesChangeDeferred: (values: M, controller: FormexController<M>) => {
349
390
  const key = (status === "new" || status === "copy") ? path + "#new" : path + "/" + entityId;
350
391
  if (controller.dirty) {
351
- const touchedValues = extractTouchedValues(values, controller.touched);
352
- saveEntityToCache(key, touchedValues);
392
+ const touchedValues = removeEmptyContainers(extractTouchedValues(values, controller.touched));
393
+ if (touchedValues && Object.keys(touchedValues).length > 0) {
394
+ saveEntityToCache(key, touchedValues);
395
+ }
353
396
  }
354
397
  },
355
398
  validation: (values) => {
@@ -366,6 +409,15 @@ export function EntityForm<M extends Record<string, any>>({
366
409
  useEffect(() => {
367
410
 
368
411
  const handleKeyDown = (e: KeyboardEvent) => {
412
+ if (e.defaultPrevented) return;
413
+ const activeElement = document.activeElement as HTMLElement;
414
+ const isInput = activeElement && (
415
+ activeElement.tagName === "INPUT" ||
416
+ activeElement.tagName === "TEXTAREA" ||
417
+ activeElement.isContentEditable
418
+ );
419
+ if (isInput) return;
420
+
369
421
  const isUndo = (e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "z";
370
422
  const isRedo =
371
423
  ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "z") ||
@@ -455,12 +507,12 @@ export function EntityForm<M extends Record<string, any>>({
455
507
  }, [entityId, path, snackbarController]);
456
508
 
457
509
  const saveEntity = ({
458
- values,
459
- previousValues,
460
- entityId,
461
- collection,
462
- path
463
- }: {
510
+ values,
511
+ previousValues,
512
+ entityId,
513
+ collection,
514
+ path
515
+ }: {
464
516
  collection: EntityCollection<M>,
465
517
  path: string,
466
518
  entityId: string | undefined,
@@ -493,13 +545,13 @@ export function EntityForm<M extends Record<string, any>>({
493
545
  };
494
546
 
495
547
  const onSaveEntityRequest = async ({
496
- collection,
497
- path,
498
- entityId,
499
- values,
500
- previousValues,
501
- autoSave
502
- }: EntityFormSaveParams<M>): Promise<void> => {
548
+ collection,
549
+ path,
550
+ entityId,
551
+ values,
552
+ previousValues,
553
+ autoSave
554
+ }: EntityFormSaveParams<M>): Promise<void> => {
503
555
  if (!status)
504
556
  return;
505
557
  if (autoSave) {
@@ -567,6 +619,7 @@ export function EntityForm<M extends Record<string, any>>({
567
619
  }, [snackbarController]);
568
620
 
569
621
  const pluginActions: React.ReactNode[] = [];
622
+ const pluginBeforeTitle: React.ReactNode[] = [];
570
623
  const plugins = customizationController.plugins;
571
624
 
572
625
  const actionsDisabled = disabled || formex.isSubmitting || (status === "existing" && !formex.dirty) || Boolean(disabledProp);
@@ -590,6 +643,12 @@ export function EntityForm<M extends Record<string, any>>({
590
643
  key={`actions_${plugin.key}`} {...actionProps} />
591
644
  : null
592
645
  )).filter(Boolean));
646
+ pluginBeforeTitle.push(...plugins.map((plugin) => (
647
+ plugin.form?.BeforeTitle
648
+ ? <plugin.form.BeforeTitle
649
+ key={`before_title_${plugin.key}`} {...actionProps} />
650
+ : null
651
+ )).filter(Boolean));
593
652
  }
594
653
 
595
654
  const titlePropertyKey = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
@@ -631,21 +690,42 @@ export function EntityForm<M extends Record<string, any>>({
631
690
  const modified = formex.dirty;
632
691
 
633
692
  const uniqueFieldValidator: CustomFieldValidator = useCallback(({
634
- name,
635
- value
636
- }) => dataSource.checkUniqueField(path, name, value, entityId, collection),
693
+ name,
694
+ value
695
+ }) => dataSource.checkUniqueField(path, name, value, entityId, collection),
637
696
  [dataSource, path, entityId]);
638
697
 
639
698
  const validationSchema = useMemo(() => entityId
640
- ? getYupEntitySchema(
641
- entityId,
642
- resolvedCollection.properties,
643
- uniqueFieldValidator)
644
- : undefined,
699
+ ? getYupEntitySchema(
700
+ entityId,
701
+ resolvedCollection.properties,
702
+ uniqueFieldValidator)
703
+ : undefined,
645
704
  [entityId, resolvedCollection.properties, uniqueFieldValidator]);
646
705
 
647
706
  useOnAutoSave(autoSave, formex, lastSavedValues, save);
648
707
 
708
+ // Detect external changes to the entity (e.g. from onSnapshot after Admin SDK writes)
709
+ const prevEntityValuesRef = useRef<EntityValues<M> | undefined>(entity?.values);
710
+ useEffect(() => {
711
+ if (!entity?.values || status !== "existing") return;
712
+ const prev = prevEntityValuesRef.current;
713
+ prevEntityValuesRef.current = entity.values;
714
+ if (prev && !equal(prev, entity.values)) {
715
+ // Compute the diff between the old and new entity values
716
+ const changes: Partial<EntityValues<M>> = {};
717
+ const allKeys = new Set([...Object.keys(prev), ...Object.keys(entity.values)]);
718
+ for (const key of allKeys) {
719
+ if (!equal((prev as any)[key], (entity.values as any)[key])) {
720
+ (changes as any)[key] = (entity.values as any)[key];
721
+ }
722
+ }
723
+ if (Object.keys(changes).length > 0) {
724
+ setUnderlyingChanges(changes);
725
+ }
726
+ }
727
+ }, [entity?.values, status]);
728
+
649
729
  useEffect(() => {
650
730
  if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
651
731
  // we update the form fields from the Firestore data
@@ -699,8 +779,8 @@ export function EntityForm<M extends Record<string, any>>({
699
779
 
700
780
  return (
701
781
  <FormEntry propertyKey={key}
702
- widthPercentage={widthPercentage}
703
- key={`field_${key}`}>
782
+ widthPercentage={widthPercentage}
783
+ key={`field_${key}`}>
704
784
  <PropertyFieldBinding {...cmsFormFieldProps} />
705
785
  </FormEntry>
706
786
  );
@@ -713,7 +793,7 @@ export function EntityForm<M extends Record<string, any>>({
713
793
  throw new Error("When using additional fields you need to provide a Builder or a value");
714
794
  }
715
795
  const child = Builder
716
- ? <Builder entity={entity} context={context}/>
796
+ ? <Builder entity={entity} context={context} />
717
797
  : <div className={"w-full"}>
718
798
  {additionalField.value?.({
719
799
  entity,
@@ -725,9 +805,9 @@ export function EntityForm<M extends Record<string, any>>({
725
805
  <div key={`additional_${key}`} className={"w-full"}>
726
806
  <LabelWithIconAndTooltip
727
807
  propertyKey={key}
728
- icon={<NotesIcon size={"small"}/>}
808
+ icon={<NotesIcon size={"small"} />}
729
809
  title={additionalField.name}
730
- className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
810
+ className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"} />
731
811
  <div
732
812
  className={cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar")}>
733
813
  <ErrorBoundary>
@@ -749,6 +829,8 @@ export function EntityForm<M extends Record<string, any>>({
749
829
 
750
830
  const formView = <ErrorBoundary>
751
831
  <>
832
+ {pluginBeforeTitle}
833
+
752
834
  {!Builder && <div className={"w-full py-2 flex flex-col items-start my-4 lg:my-6"}>
753
835
  <Typography
754
836
  className={"my-4 flex-grow line-clamp-1 " + (collection.hideIdFromForm ? "mb-6" : "")}
@@ -758,7 +840,7 @@ export function EntityForm<M extends Record<string, any>>({
758
840
 
759
841
  {!entity?.values && initialStatus === "existing" &&
760
842
  <Alert color={"warning"} size={"small"} outerClassName={"w-full mb-4 text-xs"}>
761
- This entity does not exist in the database
843
+ {t("this_entity_not_exist")}
762
844
  </Alert>}
763
845
 
764
846
  {showEntityPath && <Alert color={"base"} outerClassName={"w-full"} size={"small"}>
@@ -772,27 +854,27 @@ export function EntityForm<M extends Record<string, any>>({
772
854
  {children}
773
855
 
774
856
  {initialEntityId && !entity && initialStatus !== "new" && <Alert color={"info"} size={"small"}>
775
- This entity does not exist in the database
857
+ {t("this_entity_not_exist")}
776
858
  </Alert>}
777
859
 
778
860
  {!Builder && !collection.hideIdFromForm &&
779
861
  <CustomIdField customId={collection.customId}
780
- entityId={entityId}
781
- status={status}
782
- onChange={setEntityId}
783
- error={entityIdError}
784
- loading={customIdLoading}
785
- entity={entity}/>
862
+ entityId={entityId}
863
+ status={status}
864
+ onChange={setEntityId}
865
+ error={entityIdError}
866
+ loading={customIdLoading}
867
+ entity={entity} />
786
868
  }
787
869
 
788
870
  {entityId && formContext && <>
789
871
  <div className="mt-12 flex flex-col gap-8" ref={formRef}>
790
872
  {formFields()}
791
- <ErrorFocus containerRef={formRef}/>
873
+ <ErrorFocus containerRef={formRef} />
792
874
  </div>
793
875
  </>}
794
876
 
795
- {forceActionsAtTheBottom && <div className="h-16"/>}
877
+ {forceActionsAtTheBottom && <div className="h-16" />}
796
878
  </>
797
879
  </ErrorBoundary>;
798
880
 
@@ -852,12 +934,12 @@ export function EntityForm<M extends Record<string, any>>({
852
934
  {formex.dirty
853
935
  ? <Tooltip title={"This form has been modified"}>
854
936
  <Chip size={"small"} className={"py-1"} colorScheme={"orangeDarker"}>
855
- <EditIcon size={"smallest"}/>
937
+ <EditIcon size={"smallest"} />
856
938
  </Chip>
857
939
  </Tooltip>
858
940
  : <Tooltip title={"The current form is in sync with the database"}>
859
941
  <Chip size={"small"} className={"py-1"}>
860
- <CheckIcon size={"smallest"}/>
942
+ <CheckIcon size={"smallest"} />
861
943
  </Chip>
862
944
  </Tooltip>}
863
945
  </div>
@@ -19,6 +19,7 @@ import {
19
19
  } from "@firecms/ui";
20
20
  import { FormexController } from "@firecms/formex";
21
21
  import { useFireCMSContext, useSideEntityController } from "../hooks";
22
+ import { useTranslation } from "../hooks/useTranslation";
22
23
 
23
24
  export interface EntityFormActionsProps {
24
25
  fullPath: string;
@@ -56,6 +57,7 @@ export function EntityFormActions({
56
57
 
57
58
  const context = useFireCMSContext();
58
59
  const sideEntityController = useSideEntityController();
60
+ const { t } = useTranslation();
59
61
 
60
62
  return layout === "bottom"
61
63
  ? buildBottomActions({
@@ -72,7 +74,8 @@ export function EntityFormActions({
72
74
  openEntityMode,
73
75
  navigateBack,
74
76
  formContext,
75
- formex
77
+ formex,
78
+ t
76
79
  })
77
80
  : buildSideActions({
78
81
  fullPath,
@@ -88,7 +91,8 @@ export function EntityFormActions({
88
91
  openEntityMode,
89
92
  navigateBack,
90
93
  formContext,
91
- formex
94
+ formex,
95
+ t
92
96
  });
93
97
  }
94
98
 
@@ -108,6 +112,7 @@ type ActionsViewProps<M extends object> = {
108
112
  navigateBack: () => void;
109
113
  formContext: FormContext,
110
114
  formex: FormexController<any>;
115
+ t: (key: any, vars?: Record<string, string>) => string;
111
116
  };
112
117
 
113
118
  function buildBottomActions<M extends object>({
@@ -125,7 +130,8 @@ function buildBottomActions<M extends object>({
125
130
  openEntityMode,
126
131
  navigateBack,
127
132
  formContext,
128
- formex
133
+ formex,
134
+ t
129
135
  }: ActionsViewProps<M>) {
130
136
 
131
137
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
@@ -165,16 +171,16 @@ function buildBottomActions<M extends object>({
165
171
  <Button variant="text" disabled={disabled || formex.isSubmitting}
166
172
  color={"primary"}
167
173
  type="reset">
168
- {status === "existing" ? "Discard" : "Clear"}
174
+ {status === "existing" ? t("discard") : t("clear")}
169
175
  </Button>
170
176
  <Button variant={"filled"}
171
177
  color="primary"
172
178
  type="submit"
173
179
  disabled={disabled || formex.isSubmitting}
174
180
  startIcon={hasErrors ? <ErrorIcon/> : undefined}>
175
- {status === "existing" && "Save"}
176
- {status === "copy" && "Create copy"}
177
- {status === "new" && "Create"}
181
+ {status === "existing" && t("save")}
182
+ {status === "copy" && t("create_copy")}
183
+ {status === "new" && t("create")}
178
184
  </Button>
179
185
 
180
186
  </DialogActions>;
@@ -193,7 +199,8 @@ function buildSideActions<M extends object>({
193
199
  disabled,
194
200
  status,
195
201
  pluginActions,
196
- formex
202
+ formex,
203
+ t
197
204
  }: ActionsViewProps<M>) {
198
205
 
199
206
  const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
@@ -207,12 +214,12 @@ function buildSideActions<M extends object>({
207
214
  size={"large"}
208
215
  startIcon={hasErrors ? <ErrorIcon/> : undefined}
209
216
  disabled={disabled || formex.isSubmitting}>
210
- {status === "existing" && "Save"}
211
- {status === "copy" && "Create copy"}
212
- {status === "new" && "Create"}
217
+ {status === "existing" && t("save")}
218
+ {status === "copy" && t("create_copy")}
219
+ {status === "new" && t("create")}
213
220
  </LoadingButton>
214
221
  <Button fullWidth={true} variant="text" disabled={disabled || formex.isSubmitting} type="reset">
215
- {status === "existing" ? "Discard" : "Clear"}
222
+ {status === "existing" ? t("discard") : t("clear")}
216
223
  </Button>
217
224
 
218
225
  {pluginActions}
@@ -17,10 +17,10 @@ import {
17
17
  import { ReadOnlyFieldBinding } from "./field_bindings/ReadOnlyFieldBinding";
18
18
 
19
19
  import { isHidden, isPropertyBuilder, isReadOnly, resolveProperty } from "../util";
20
- import { useAuthController, useCustomizationController } from "../hooks";
20
+ import { useAuthController, useCustomizationController, useTranslation } from "../hooks";
21
21
  import { Typography } from "@firecms/ui";
22
22
  import { getFieldConfig, getFieldId } from "../core";
23
- import { ErrorBoundary } from "../components";
23
+ import { ErrorBoundary, CircularProgressCenter } from "../components";
24
24
 
25
25
  /**
26
26
  * This component renders a form field creating the corresponding configuration
@@ -75,21 +75,21 @@ export const PropertyFieldBinding = React.memo(PropertyFieldBindingInternal, (a:
75
75
  }) as typeof PropertyFieldBindingInternal;
76
76
 
77
77
  function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Record<string, any> = any>
78
- ({
79
- propertyKey,
80
- property,
81
- context,
82
- includeDescription,
83
- underlyingValueHasChanged,
84
- disabled: disabledProp,
85
- partOfArray,
86
- partOfBlock,
87
- minimalistView,
88
- autoFocus,
89
- index,
90
- size,
91
- onPropertyChange,
92
- }: PropertyFieldBindingProps<T, M>): ReactElement<PropertyFieldBindingProps<T, M>> {
78
+ ({
79
+ propertyKey,
80
+ property,
81
+ context,
82
+ includeDescription,
83
+ underlyingValueHasChanged,
84
+ disabled: disabledProp,
85
+ partOfArray,
86
+ partOfBlock,
87
+ minimalistView,
88
+ autoFocus,
89
+ index,
90
+ size,
91
+ onPropertyChange,
92
+ }: PropertyFieldBindingProps<T, M>): ReactElement<PropertyFieldBindingProps<T, M>> {
93
93
 
94
94
  const authController = useAuthController();
95
95
  const customizationController = useCustomizationController();
@@ -137,7 +137,7 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Rec
137
137
  }
138
138
  const configProperty = resolveProperty({
139
139
  propertyKey,
140
- propertyOrBuilder: propertyConfig.property,
140
+ propertyOrBuilder: propertyConfig.property as any,
141
141
  values: fieldProps.form.values,
142
142
  path: context.path,
143
143
  entityId: context.entityId,
@@ -145,7 +145,7 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Rec
145
145
  index,
146
146
  authController
147
147
  });
148
- Component = configProperty.Field as ComponentType<FieldProps<T>>;
148
+ Component = configProperty?.Field as ComponentType<FieldProps<T>> | undefined;
149
149
  }
150
150
  if (!Component) {
151
151
  console.warn(`No field component found for property ${propertyKey}`);
@@ -175,7 +175,7 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Rec
175
175
  return <FieldInternal
176
176
  Component={Component as ComponentType<FieldProps>}
177
177
  componentProps={componentProps}
178
- formexFieldProps={fieldProps}/>;
178
+ formexFieldProps={fieldProps} />;
179
179
  }}
180
180
  </Field>
181
181
  );
@@ -185,39 +185,55 @@ function PropertyFieldBindingInternal<T extends CMSType = CMSType, M extends Rec
185
185
  type ResolvedPropertyFieldBindingProps<T extends CMSType = CMSType, M extends Record<string, any> = any> =
186
186
  Omit<PropertyFieldBindingProps<T, M>, "property">
187
187
  & {
188
- property: ResolvedProperty<T>
189
- };
188
+ property: ResolvedProperty<T>
189
+ };
190
190
 
191
191
  function FieldInternal<T extends CMSType, CustomProps, M extends Record<string, any>>
192
- ({
193
- Component,
194
- componentProps: {
195
- propertyKey,
196
- property,
197
- includeDescription,
198
- underlyingValueHasChanged,
199
- partOfArray,
200
- partOfBlock,
201
- minimalistView,
202
- autoFocus,
203
- context,
204
- disabled,
205
- size,
206
- onPropertyChange
207
- },
208
- formexFieldProps
209
- }:
210
- {
211
- Component: ComponentType<FieldProps<T, any, M>>,
212
- componentProps: ResolvedPropertyFieldBindingProps<T, M>,
213
- formexFieldProps: FormexFieldProps<T, any>
214
- }) {
215
-
192
+ ({
193
+ Component,
194
+ componentProps: {
195
+ propertyKey,
196
+ property,
197
+ includeDescription,
198
+ underlyingValueHasChanged,
199
+ partOfArray,
200
+ partOfBlock,
201
+ minimalistView,
202
+ autoFocus,
203
+ context,
204
+ disabled,
205
+ size,
206
+ onPropertyChange
207
+ },
208
+ formexFieldProps
209
+ }:
210
+ {
211
+ Component: ComponentType<FieldProps<T, any, M>>,
212
+ componentProps: ResolvedPropertyFieldBindingProps<T, M>,
213
+ formexFieldProps: FormexFieldProps<T, any>
214
+ }) {
215
+
216
+ const { t } = useTranslation();
216
217
  const { plugins } = useCustomizationController();
217
218
 
218
219
  const customFieldProps: any = property.customProps;
219
220
  const value = formexFieldProps.field.value;
220
- const error = getIn(formexFieldProps.form.errors, propertyKey);
221
+
222
+ // Get error for this field path, but avoid string indexing issues
223
+ // When an array has a string error like "Tags should have unique values",
224
+ // accessing errors["tags"]["0"] returns "T" (string indexing).
225
+ // We traverse the path manually and stop if we hit a string.
226
+ let error: any = formexFieldProps.form.errors;
227
+ for (const part of propertyKey.split(".")) {
228
+ if (error === undefined || error === null) break;
229
+ if (typeof error === "string") {
230
+ // Parent is a string error, children shouldn't inherit individual characters
231
+ error = undefined;
232
+ break;
233
+ }
234
+ error = error[part];
235
+ }
236
+
221
237
  const touched = getIn(formexFieldProps.form.touched, propertyKey);
222
238
 
223
239
  const showError: boolean = error &&
@@ -271,12 +287,13 @@ function FieldInternal<T extends CMSType, CustomProps, M extends Record<string,
271
287
 
272
288
  return (
273
289
  <ErrorBoundary>
274
-
275
- <UsedComponent {...cmsFieldProps}/>
290
+ <React.Suspense fallback={<CircularProgressCenter />}>
291
+ <UsedComponent {...cmsFieldProps} />
292
+ </React.Suspense>
276
293
 
277
294
  {underlyingValueHasChanged && !isSubmitting &&
278
295
  <Typography variant={"caption"} className={"ml-3.5"}>
279
- This value has been updated elsewhere
296
+ {t("value_updated_elsewhere")}
280
297
  </Typography>}
281
298
 
282
299
  </ErrorBoundary>);
@@ -287,7 +304,7 @@ const shouldPropertyReRender = (property: PropertyOrBuilder | ResolvedProperty,
287
304
  if (plugins?.some((plugin) => plugin.form?.fieldBuilder)) {
288
305
  return true;
289
306
  }
290
- if (isPropertyBuilder(property)) {
307
+ if (isPropertyBuilder(property as any)) {
291
308
  return true;
292
309
  }
293
310
  const defAProperty = property as Property | ResolvedProperty;