@firecms/core 3.0.0 → 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 (340) 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/VirtualTable.performance.test.d.ts +1 -0
  32. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  33. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +4 -1
  34. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  35. package/dist/components/VirtualTable/VirtualTableProps.d.ts +17 -1
  36. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  37. package/dist/components/VirtualTable/types.d.ts +3 -0
  38. package/dist/components/index.d.ts +4 -0
  39. package/dist/contexts/index.d.ts +10 -0
  40. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  41. package/dist/core/index.d.ts +1 -0
  42. package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
  43. package/dist/editor/components/editor-bubble-item.d.ts +8 -0
  44. package/dist/editor/components/editor-bubble.d.ts +8 -0
  45. package/dist/editor/components/image-bubble.d.ts +5 -0
  46. package/dist/editor/components/index.d.ts +16 -0
  47. package/dist/editor/components/table-bubble.d.ts +5 -0
  48. package/dist/editor/editor.d.ts +30 -0
  49. package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
  50. package/dist/editor/extensions/Image/index.d.ts +6 -0
  51. package/dist/editor/extensions/Image.d.ts +6 -0
  52. package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
  53. package/dist/editor/extensions/clipboard.d.ts +7 -0
  54. package/dist/editor/extensions/custom-keymap.d.ts +1 -0
  55. package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
  56. package/dist/editor/hooks/useProseMirror.d.ts +13 -0
  57. package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
  58. package/dist/editor/index.d.ts +2 -0
  59. package/dist/editor/markdown.d.ts +5 -0
  60. package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
  61. package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
  62. package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
  63. package/dist/editor/nodeViews/index.d.ts +6 -0
  64. package/dist/editor/plugins/index.d.ts +2 -0
  65. package/dist/editor/plugins/inputrules.d.ts +6 -0
  66. package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
  67. package/dist/editor/plugins/slashCommandPlugin.d.ts +12 -0
  68. package/dist/editor/schema.d.ts +2 -0
  69. package/dist/editor/selectors/ai-selector.d.ts +0 -0
  70. package/dist/editor/selectors/color-selector.d.ts +10 -0
  71. package/dist/editor/selectors/link-selector.d.ts +8 -0
  72. package/dist/editor/selectors/node-selector.d.ts +15 -0
  73. package/dist/editor/selectors/text-buttons.d.ts +1 -0
  74. package/dist/editor/types.d.ts +5 -0
  75. package/dist/editor/useProseMirror.d.ts +16 -0
  76. package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
  77. package/dist/editor/utils/remove_classes.d.ts +1 -0
  78. package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
  79. package/dist/form/components/ErrorFocus.d.ts +1 -1
  80. package/dist/form/components/LocalChangesMenu.d.ts +2 -2
  81. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  82. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  83. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  84. package/dist/form/validation.d.ts +3 -2
  85. package/dist/hooks/index.d.ts +1 -0
  86. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  87. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  88. package/dist/hooks/useCollapsedGroups.d.ts +6 -3
  89. package/dist/hooks/useTranslation.d.ts +17 -0
  90. package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
  91. package/dist/index.d.ts +5 -0
  92. package/dist/index.es.js +31028 -16080
  93. package/dist/index.es.js.map +1 -1
  94. package/dist/index.umd.js +29955 -15028
  95. package/dist/index.umd.js.map +1 -1
  96. package/dist/internal/useRestoreScroll.d.ts +1 -1
  97. package/dist/locales/de.d.ts +2 -0
  98. package/dist/locales/en.d.ts +10 -0
  99. package/dist/locales/es.d.ts +10 -0
  100. package/dist/locales/fr.d.ts +2 -0
  101. package/dist/locales/hi.d.ts +2 -0
  102. package/dist/locales/it.d.ts +2 -0
  103. package/dist/locales/pt.d.ts +7 -0
  104. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  105. package/dist/preview/components/DatePreview.d.ts +13 -3
  106. package/dist/preview/components/ImagePreview.d.ts +5 -1
  107. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  108. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  109. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  110. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  111. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  112. package/dist/types/analytics.d.ts +1 -1
  113. package/dist/types/collections.d.ts +88 -2
  114. package/dist/types/customization_controller.d.ts +2 -1
  115. package/dist/types/datasource.d.ts +0 -1
  116. package/dist/types/entities.d.ts +1 -0
  117. package/dist/types/firecms.d.ts +2 -1
  118. package/dist/types/index.d.ts +1 -0
  119. package/dist/types/navigation.d.ts +2 -2
  120. package/dist/types/plugins.d.ts +69 -1
  121. package/dist/types/properties.d.ts +277 -12
  122. package/dist/types/storage.d.ts +9 -0
  123. package/dist/types/translations.d.ts +669 -0
  124. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  125. package/dist/util/__tests__/objects.test.d.ts +1 -0
  126. package/dist/util/conditions.d.ts +26 -0
  127. package/dist/util/entities.d.ts +2 -3
  128. package/dist/util/index.d.ts +3 -1
  129. package/dist/util/lazy_eager.d.ts +7 -0
  130. package/dist/util/objects.d.ts +1 -0
  131. package/dist/util/property_utils.d.ts +2 -1
  132. package/dist/util/resolutions.d.ts +3 -3
  133. package/dist/util/useStorageUploadController.d.ts +11 -2
  134. package/package.json +52 -12
  135. package/src/app/Scaffold.tsx +20 -19
  136. package/src/components/AIIcon.tsx +41 -0
  137. package/src/components/ArrayContainer.tsx +7 -8
  138. package/src/components/ClearFilterSortButton.tsx +25 -19
  139. package/src/components/ConfirmationDialog.tsx +4 -4
  140. package/src/components/DeleteEntityDialog.tsx +12 -11
  141. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +82 -43
  142. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  143. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  144. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  145. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
  146. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +24 -44
  147. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  148. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
  149. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
  150. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
  151. package/src/components/EntityCollectionView/Board.tsx +324 -0
  152. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  153. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  154. package/src/components/EntityCollectionView/BoardSortableList.tsx +174 -0
  155. package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
  156. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  157. package/src/components/EntityCollectionView/EntityCard.tsx +235 -0
  158. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +706 -0
  159. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +236 -0
  160. package/src/components/EntityCollectionView/EntityCollectionView.tsx +531 -209
  161. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +35 -22
  162. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +86 -15
  163. package/src/components/EntityCollectionView/FiltersDialog.tsx +252 -0
  164. package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
  165. package/src/components/EntityCollectionView/board_types.ts +113 -0
  166. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  167. package/src/components/EntityJsonPreview.tsx +2 -1
  168. package/src/components/EntityPreview.tsx +1 -1
  169. package/src/components/EntityView.tsx +3 -2
  170. package/src/components/ErrorBoundary.tsx +27 -15
  171. package/src/components/ErrorTooltip.tsx +2 -1
  172. package/src/components/HomePage/DefaultHomePage.tsx +65 -22
  173. package/src/components/HomePage/HomePageDnD.tsx +59 -42
  174. package/src/components/HomePage/NavigationCard.tsx +20 -18
  175. package/src/components/HomePage/NavigationGroup.tsx +20 -17
  176. package/src/components/HomePage/RenameGroupDialog.tsx +15 -15
  177. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  178. package/src/components/LanguageToggle.tsx +66 -0
  179. package/src/components/NotFoundPage.tsx +5 -3
  180. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +12 -17
  181. package/src/components/ReferenceWidget.tsx +5 -6
  182. package/src/components/SearchIconsView.tsx +3 -1
  183. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  184. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  185. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +50 -40
  186. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +53 -40
  187. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +60 -58
  188. package/src/components/UnsavedChangesDialog.tsx +6 -6
  189. package/src/components/UserDisplay.tsx +4 -4
  190. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +387 -0
  191. package/src/components/VirtualTable/VirtualTable.tsx +277 -121
  192. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  193. package/src/components/VirtualTable/VirtualTableHeader.tsx +76 -64
  194. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +163 -42
  195. package/src/components/VirtualTable/VirtualTableProps.tsx +21 -2
  196. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  197. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  198. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +19 -6
  199. package/src/components/VirtualTable/types.tsx +3 -0
  200. package/src/components/common/default_entity_actions.tsx +4 -0
  201. package/src/components/common/useColumnsIds.tsx +95 -3
  202. package/src/components/common/useDataSourceTableController.tsx +12 -4
  203. package/src/components/index.tsx +5 -0
  204. package/src/contexts/BreacrumbsContext.tsx +15 -8
  205. package/src/contexts/index.ts +10 -0
  206. package/src/core/DefaultAppBar.tsx +49 -32
  207. package/src/core/DefaultDrawer.tsx +49 -57
  208. package/src/core/DrawerNavigationGroup.tsx +120 -0
  209. package/src/core/DrawerNavigationItem.tsx +4 -3
  210. package/src/core/EntityEditView.tsx +94 -50
  211. package/src/core/EntityEditViewFormActions.tsx +24 -17
  212. package/src/core/EntitySidePanel.tsx +34 -30
  213. package/src/core/FireCMS.tsx +33 -6
  214. package/src/core/SideDialogs.tsx +4 -2
  215. package/src/core/field_configs.tsx +18 -11
  216. package/src/core/index.tsx +1 -0
  217. package/src/editor/components/SlashCommandMenu.tsx +516 -0
  218. package/src/editor/components/editor-bubble-item.tsx +32 -0
  219. package/src/editor/components/editor-bubble.tsx +118 -0
  220. package/src/editor/components/image-bubble.tsx +156 -0
  221. package/src/editor/components/index.ts +14 -0
  222. package/src/editor/components/table-bubble.tsx +165 -0
  223. package/src/editor/editor.tsx +455 -0
  224. package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
  225. package/src/editor/extensions/Image/index.ts +133 -0
  226. package/src/editor/extensions/Image.ts +159 -0
  227. package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
  228. package/src/editor/extensions/clipboard.ts +72 -0
  229. package/src/editor/extensions/custom-keymap.ts +24 -0
  230. package/src/editor/extensions/drag-and-drop.tsx +480 -0
  231. package/src/editor/hooks/useProseMirror.ts +124 -0
  232. package/src/editor/hooks/useProseMirrorContext.ts +15 -0
  233. package/src/editor/index.ts +2 -0
  234. package/src/editor/markdown.ts +172 -0
  235. package/src/editor/nodeViews/ImageComponent.tsx +20 -0
  236. package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
  237. package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
  238. package/src/editor/nodeViews/index.ts +35 -0
  239. package/src/editor/plugins/index.ts +58 -0
  240. package/src/editor/plugins/inputrules.ts +82 -0
  241. package/src/editor/plugins/placeholderPlugin.ts +55 -0
  242. package/src/editor/plugins/slashCommandPlugin.ts +61 -0
  243. package/src/editor/schema.ts +240 -0
  244. package/src/editor/selectors/ai-selector.tsx +111 -0
  245. package/src/editor/selectors/color-selector.tsx +200 -0
  246. package/src/editor/selectors/link-selector.tsx +118 -0
  247. package/src/editor/selectors/node-selector.tsx +157 -0
  248. package/src/editor/selectors/text-buttons.tsx +86 -0
  249. package/src/editor/types.ts +6 -0
  250. package/src/editor/useProseMirror.ts +126 -0
  251. package/src/editor/utils/prosemirror-utils.ts +108 -0
  252. package/src/editor/utils/remove_classes.ts +17 -0
  253. package/src/editor/utils/useDebouncedCallback.ts +25 -0
  254. package/src/form/EntityForm.tsx +150 -75
  255. package/src/form/EntityFormActions.tsx +19 -12
  256. package/src/form/PropertyFieldBinding.tsx +68 -51
  257. package/src/form/components/ErrorFocus.tsx +3 -3
  258. package/src/form/components/LocalChangesMenu.tsx +19 -19
  259. package/src/form/components/StorageItemPreview.tsx +5 -3
  260. package/src/form/components/StorageUploadProgress.tsx +22 -6
  261. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +18 -5
  262. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +22 -10
  263. package/src/form/field_bindings/BlockFieldBinding.tsx +26 -9
  264. package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -17
  265. package/src/form/field_bindings/KeyValueFieldBinding.tsx +46 -25
  266. package/src/form/field_bindings/MapFieldBinding.tsx +88 -70
  267. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +93 -52
  268. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +15 -1
  269. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +25 -11
  270. package/src/form/field_bindings/ReferenceFieldBinding.tsx +25 -11
  271. package/src/form/field_bindings/RepeatFieldBinding.tsx +21 -6
  272. package/src/form/field_bindings/SelectFieldBinding.tsx +7 -5
  273. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +110 -92
  274. package/src/form/field_bindings/SwitchFieldBinding.tsx +31 -14
  275. package/src/form/field_bindings/TextFieldBinding.tsx +77 -38
  276. package/src/form/field_bindings/UserSelectFieldBinding.tsx +7 -5
  277. package/src/form/validation.ts +245 -160
  278. package/src/hooks/index.tsx +1 -0
  279. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  280. package/src/hooks/useBuildNavigationController.tsx +91 -41
  281. package/src/hooks/useCollapsedGroups.ts +18 -9
  282. package/src/hooks/useTranslation.ts +31 -0
  283. package/src/hooks/useValidateAuthenticator.tsx +1 -1
  284. package/src/i18n/FireCMSi18nProvider.tsx +160 -0
  285. package/src/index.ts +5 -0
  286. package/src/internal/useBuildDataSource.ts +68 -34
  287. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  288. package/src/internal/useBuildSideEntityController.tsx +24 -24
  289. package/src/internal/useRestoreScroll.tsx +26 -14
  290. package/src/locales/de.ts +718 -0
  291. package/src/locales/en.ts +730 -0
  292. package/src/locales/es.ts +730 -0
  293. package/src/locales/fr.ts +718 -0
  294. package/src/locales/hi.ts +718 -0
  295. package/src/locales/it.ts +718 -0
  296. package/src/locales/pt.ts +727 -0
  297. package/src/preview/PropertyPreview.tsx +43 -33
  298. package/src/preview/PropertyPreviewProps.tsx +6 -0
  299. package/src/preview/components/DatePreview.tsx +72 -4
  300. package/src/preview/components/EmptyValue.tsx +1 -1
  301. package/src/preview/components/ImagePreview.tsx +37 -21
  302. package/src/preview/components/ReferencePreview.tsx +8 -2
  303. package/src/preview/components/StorageThumbnail.tsx +16 -12
  304. package/src/preview/components/UrlComponentPreview.tsx +32 -27
  305. package/src/preview/components/UserPreview.tsx +3 -1
  306. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  307. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  308. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  309. package/src/preview/property_previews/MapPropertyPreview.tsx +49 -27
  310. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  311. package/src/routes/CustomCMSRoute.tsx +1 -0
  312. package/src/routes/FireCMSRoute.tsx +87 -65
  313. package/src/types/analytics.ts +10 -0
  314. package/src/types/collections.ts +97 -3
  315. package/src/types/customization_controller.tsx +2 -1
  316. package/src/types/datasource.ts +54 -56
  317. package/src/types/entities.ts +10 -0
  318. package/src/types/firecms.tsx +2 -1
  319. package/src/types/index.ts +1 -0
  320. package/src/types/navigation.ts +2 -2
  321. package/src/types/plugins.tsx +77 -1
  322. package/src/types/properties.ts +369 -37
  323. package/src/types/storage.ts +11 -1
  324. package/src/types/translations.ts +752 -0
  325. package/src/util/__tests__/conditions.test.ts +506 -0
  326. package/src/util/__tests__/objects.test.ts +196 -0
  327. package/src/util/callbacks.ts +6 -3
  328. package/src/util/collections.ts +51 -6
  329. package/src/util/conditions.ts +339 -0
  330. package/src/util/entities.ts +29 -30
  331. package/src/util/entity_cache.ts +2 -1
  332. package/src/util/index.ts +3 -1
  333. package/src/util/join_collections.ts +10 -8
  334. package/src/util/lazy_eager.tsx +33 -0
  335. package/src/util/objects.ts +46 -13
  336. package/src/util/{references.ts → previews.ts} +16 -2
  337. package/src/util/property_utils.tsx +37 -11
  338. package/src/util/resolutions.ts +62 -58
  339. package/src/util/useStorageUploadController.tsx +34 -30
  340. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -34,7 +34,6 @@ import { getParentReferencesFromPath } from "../util/parent_references_from_path
34
34
  const DEFAULT_BASE_PATH = "/";
35
35
  const DEFAULT_COLLECTION_PATH = "/c";
36
36
 
37
- export const NAVIGATION_DEFAULT_GROUP_NAME = "Views";
38
37
  export const NAVIGATION_ADMIN_GROUP_NAME = "Admin";
39
38
 
40
39
  export type BuildNavigationContextProps<EC extends EntityCollection, USER extends User> = {
@@ -122,10 +121,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
122
121
 
123
122
  const navigate = useNavigate();
124
123
 
125
- const collectionsRef = useRef<EntityCollection[] | undefined>();
126
- const viewsRef = useRef<CMSView[] | undefined>();
127
- const adminViewsRef = useRef<CMSView[] | undefined>();
128
- const navigationEntriesOrderRef = useRef<string[] | undefined>();
124
+ const collectionsRef = useRef<EntityCollection[] | undefined>(undefined);
125
+ const viewsRef = useRef<CMSView[] | undefined>(undefined);
126
+ const adminViewsRef = useRef<CMSView[] | undefined>(undefined);
127
+ const navigationEntriesOrderRef = useRef<string[] | undefined>(undefined);
129
128
 
130
129
  const [initialised, setInitialised] = useState<boolean>(false);
131
130
 
@@ -140,8 +139,12 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
140
139
 
141
140
  const fullCollectionPath = cleanBasePath ? `/${cleanBasePath}/${cleanBaseCollectionPath}` : `/${cleanBaseCollectionPath}`;
142
141
 
143
- const buildCMSUrlPath = useCallback((path: string): string => cleanBasePath ? `/${cleanBasePath}/${encodePath(path)}` : `/${encodePath(path)}`,
144
- [cleanBasePath]);
142
+
143
+ const buildCMSUrlPath = useCallback((path: string): string => {
144
+ // Strip trailing /* wildcard from paths (used for nested routes in React Router)
145
+ const cleanPath = path.replace(/\/\*$/, "");
146
+ return cleanBasePath ? `/${cleanBasePath}/${encodePath(cleanPath)}` : `/${encodePath(cleanPath)}`;
147
+ }, [cleanBasePath]);
145
148
 
146
149
  const buildUrlCollectionPath = useCallback((path: string): string => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path)}`,
147
150
  [baseCollectionPath]);
@@ -185,7 +188,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
185
188
  path: pathKey,
186
189
  collection,
187
190
  description: collection.description?.trim(),
188
- group: groupName ?? NAVIGATION_DEFAULT_GROUP_NAME
191
+ group: groupName
189
192
  });
190
193
  return acc;
191
194
  }, [] as NavigationEntry[]),
@@ -213,7 +216,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
213
216
  path: view.path,
214
217
  view,
215
218
  description: view.description?.trim(),
216
- group: groupName ?? NAVIGATION_DEFAULT_GROUP_NAME
219
+ group: groupName
217
220
  });
218
221
  return acc;
219
222
  }, [] as NavigationEntry[]),
@@ -238,7 +241,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
238
241
  }, [] as NavigationEntry[])
239
242
  ];
240
243
 
241
- const groupOrderValue = (groupName?: string): number => {
244
+ const groupOrderValue = (groupName?: string | null): number => {
242
245
  if (groupName === NAVIGATION_ADMIN_GROUP_NAME) return 1;
243
246
  return 0; // Other groups
244
247
  };
@@ -262,7 +265,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
262
265
 
263
266
  const collectedGroupsFromEntries = navigationEntries
264
267
  .map(e => e.group)
265
- .filter(Boolean) as string[];
268
+ .filter((g): g is string => g !== null && Boolean(g));
269
+
270
+ // Check if there are any ungrouped entries
271
+ const hasUngroupedEntries = navigationEntries.some(e => e.group === null && e.type !== "admin");
266
272
 
267
273
  // Preserve order from finalNavigationGroupMappings (persisted order)
268
274
  const groupsFromMappings = finalNavigationGroupMappings.map(g => g.name);
@@ -280,7 +286,12 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
280
286
  const uniqueGroupsArray = [...new Set(allDefinedGroups)];
281
287
  const adminGroups = uniqueGroupsArray.filter(g => g === NAVIGATION_ADMIN_GROUP_NAME);
282
288
  const nonAdminGroups = uniqueGroupsArray.filter(g => g !== NAVIGATION_ADMIN_GROUP_NAME);
283
- const uniqueGroups = [...nonAdminGroups, ...adminGroups];
289
+ // Place null (ungrouped) first if there are ungrouped entries
290
+ const uniqueGroups: (string | null)[] = [
291
+ ...(hasUngroupedEntries ? [null] : []),
292
+ ...nonAdminGroups,
293
+ ...adminGroups
294
+ ];
284
295
 
285
296
  return {
286
297
  allowDragAndDrop: plugins?.some(plugin => plugin.homePage?.allowDragAndDrop) ?? false,
@@ -331,10 +342,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
331
342
  try {
332
343
 
333
344
  const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
334
- resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins),
335
- resolveCMSViews(viewsProp, authController, dataSourceDelegate),
336
- resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
337
- ]
345
+ resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins),
346
+ resolveCMSViews(viewsProp, authController, dataSourceDelegate, plugins),
347
+ resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
348
+ ]
338
349
  );
339
350
 
340
351
  const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder, undefined, onNavigationEntriesOrderUpdate);
@@ -483,8 +494,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
483
494
 
484
495
  const urlPathToDataPath = useCallback((path: string): string => {
485
496
  const decodedPath = decodeURIComponent(path);
486
- if (decodedPath.startsWith(fullCollectionPath))
487
- return decodedPath.replace(fullCollectionPath, "");
497
+ const withoutHash = decodedPath.split("#")[0];
498
+ const cleanPath = withoutHash.split("?")[0];
499
+ if (cleanPath.startsWith(fullCollectionPath))
500
+ return cleanPath.replace(fullCollectionPath, "");
488
501
  throw Error("Expected path starting with " + fullCollectionPath);
489
502
  }, [fullCollectionPath]);
490
503
 
@@ -561,9 +574,27 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
561
574
  }
562
575
 
563
576
  function encodePath(input: string) {
564
- return encodeURIComponent(removeInitialAndTrailingSlashes(input))
565
- .replaceAll("%2F", "/")
566
- .replaceAll("%23", "#");
577
+ const cleanInput = removeInitialAndTrailingSlashes(input);
578
+ const [pathPart, rest] = cleanInput.split("?", 2);
579
+
580
+ let encodedPath = encodeURIComponent(pathPart).replaceAll("%2F", "/");
581
+ let result = encodedPath;
582
+
583
+ if (rest !== undefined) {
584
+ const [searchPart, hashPart] = rest.split("#", 2);
585
+ result += `?${searchPart}`;
586
+ if (hashPart !== undefined) {
587
+ result += `#${hashPart}`;
588
+ }
589
+ } else {
590
+ const [pathOnly, hashOnly] = cleanInput.split("#", 2);
591
+ if (hashOnly !== undefined) {
592
+ encodedPath = encodeURIComponent(pathOnly).replaceAll("%2F", "/");
593
+ result = `${encodedPath}#${hashOnly}`;
594
+ }
595
+ }
596
+
597
+ return result;
567
598
  }
568
599
 
569
600
  function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[], authController: AuthController<User>): EntityCollection[] {
@@ -597,10 +628,10 @@ function applyPluginModifyCollection(resolvedCollections: EntityCollection[], mo
597
628
  }
598
629
 
599
630
  async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
600
- collectionPermissions: PermissionsBuilder | undefined,
601
- authController: AuthController,
602
- dataSource: DataSourceDelegate,
603
- plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
631
+ collectionPermissions: PermissionsBuilder | undefined,
632
+ authController: AuthController,
633
+ dataSource: DataSourceDelegate,
634
+ plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
604
635
  let resolvedCollections: EntityCollection[] = [];
605
636
  if (typeof collections === "function") {
606
637
  resolvedCollections = await collections({
@@ -629,7 +660,12 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
629
660
  return resolvedCollections;
630
661
  }
631
662
 
632
- async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefined, authController: AuthController, dataSource: DataSourceDelegate) {
663
+ async function resolveCMSViews(
664
+ baseViews: CMSView[] | CMSViewsBuilder | undefined,
665
+ authController: AuthController,
666
+ dataSource: DataSourceDelegate,
667
+ plugins?: FireCMSPlugin[]
668
+ ) {
633
669
  let resolvedViews: CMSView[] = [];
634
670
  if (typeof baseViews === "function") {
635
671
  resolvedViews = await baseViews({
@@ -640,15 +676,25 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
640
676
  } else if (Array.isArray(baseViews)) {
641
677
  resolvedViews = baseViews;
642
678
  }
679
+
680
+ // Inject views from plugins
681
+ if (plugins) {
682
+ for (const plugin of plugins) {
683
+ if (plugin.views && plugin.views.length > 0) {
684
+ resolvedViews = [...resolvedViews, ...plugin.views];
685
+ }
686
+ }
687
+ }
688
+
643
689
  return resolvedViews;
644
690
  }
645
691
 
646
- function getGroup(collectionOrView: EntityCollection<any, any> | CMSView) {
692
+ function getGroup(collectionOrView: EntityCollection<any, any> | CMSView): string | null {
647
693
  const trimmed = collectionOrView.group?.trim();
648
694
  if (!trimmed || trimmed === "") {
649
- return NAVIGATION_DEFAULT_GROUP_NAME;
695
+ return null;
650
696
  }
651
- return trimmed ?? NAVIGATION_DEFAULT_GROUP_NAME;
697
+ return trimmed;
652
698
  }
653
699
 
654
700
  function areCollectionListsEqual(a: EntityCollection[], b: EntityCollection[]) {
@@ -688,8 +734,8 @@ function useCustomBlocker(): NavigationBlocker {
688
734
  let blocker: any;
689
735
  try {
690
736
  blocker = useBlocker(({
691
- nextLocation
692
- }) => {
737
+ nextLocation
738
+ }) => {
693
739
  const allBasePaths = Object.values(blockListeners).map(b => b.basePath).filter(Boolean) as string[];
694
740
  if (allBasePaths && allBasePaths.some(path => nextLocation.pathname.startsWith(path)))
695
741
  return false;
@@ -729,11 +775,11 @@ function useCustomBlocker(): NavigationBlocker {
729
775
  }
730
776
 
731
777
  function computeNavigationGroups({
732
- navigationGroupMappings,
733
- collections,
734
- views,
735
- plugins
736
- }: {
778
+ navigationGroupMappings,
779
+ collections,
780
+ views,
781
+ plugins
782
+ }: {
737
783
  navigationGroupMappings?: NavigationGroupMapping[],
738
784
  collections?: EntityCollection[],
739
785
  views?: CMSView[],
@@ -743,6 +789,7 @@ function computeNavigationGroups({
743
789
  let result = navigationGroupMappings;
744
790
 
745
791
  // Merge plugin navigation entries
792
+ // IMPORTANT: Deep clone the groups to avoid mutating the original input
746
793
  result = plugins ? plugins?.reduce((acc, plugin) => {
747
794
  if (plugin.homePage?.navigationEntries) {
748
795
  plugin.homePage.navigationEntries.forEach((entry) => {
@@ -763,7 +810,10 @@ function computeNavigationGroups({
763
810
 
764
811
  }
765
812
  return acc;
766
- }, [...(result ?? [])] as NavigationGroupMapping[]) : result;
813
+ }, (result ?? []).map(g => ({
814
+ name: g.name,
815
+ entries: [...g.entries]
816
+ }))) : result;
767
817
 
768
818
  // Track all entries that are already assigned to groups
769
819
  const assignedEntries = new Set<string>();
@@ -780,7 +830,7 @@ function computeNavigationGroups({
780
830
  (collections ?? []).forEach(collection => {
781
831
  const entry = collection.id ?? collection.path;
782
832
  if (!assignedEntries.has(entry)) {
783
- const groupName = getGroup(collection);
833
+ const groupName = getGroup(collection) ?? "__default__";
784
834
  if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
785
835
  unassignedGroupMap[groupName].push(entry);
786
836
  }
@@ -790,7 +840,7 @@ function computeNavigationGroups({
790
840
  (views ?? []).forEach(view => {
791
841
  const entry = view.path;
792
842
  if (!assignedEntries.has(entry)) {
793
- const groupName = getGroup(view);
843
+ const groupName = getGroup(view) ?? "__default__";
794
844
  if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
795
845
  unassignedGroupMap[groupName].push(entry);
796
846
  }
@@ -818,7 +868,7 @@ function computeNavigationGroups({
818
868
 
819
869
  // Add collections
820
870
  (collections ?? []).forEach(collection => {
821
- const name = getGroup(collection);
871
+ const name = getGroup(collection) ?? "__default__";
822
872
  const entry = collection.id ?? collection.path;
823
873
  if (!groupMap[name]) groupMap[name] = [];
824
874
  groupMap[name].push(entry);
@@ -826,7 +876,7 @@ function computeNavigationGroups({
826
876
 
827
877
  // Add views
828
878
  (views ?? []).forEach(view => {
829
- const name = getGroup(view);
879
+ const name = getGroup(view) ?? "__default__";
830
880
  const entry = view.path;
831
881
  if (!groupMap[name]) groupMap[name] = [];
832
882
  groupMap[name].push(entry);
@@ -1,15 +1,22 @@
1
1
  import { useCallback, useEffect, useState } from "react";
2
2
 
3
+ const STORAGE_KEY_PREFIX = "firecms-collapsed-groups";
4
+
3
5
  /**
4
6
  * Custom hook for managing collapsed/expanded state of navigation groups
5
7
  * with localStorage persistence. Automatically cleans up stale group entries
6
8
  * when groups are removed from the navigation.
9
+ *
10
+ * @param groupNames - Array of group names to track
11
+ * @param namespace - Namespace for localStorage key (e.g., "home", "drawer") to allow independent state
7
12
  */
8
- export function useCollapsedGroups(groupNames: string[]) {
13
+ export function useCollapsedGroups(groupNames: (string | null)[], namespace: string = "default") {
14
+ const storageKey = `${STORAGE_KEY_PREFIX}-${namespace}`;
15
+
9
16
  // Load collapsed groups from localStorage on mount
10
17
  const [collapsedGroups, setCollapsedGroups] = useState<Record<string, boolean>>(() => {
11
18
  try {
12
- const stored = localStorage.getItem('firecms-collapsed-groups');
19
+ const stored = localStorage.getItem(storageKey);
13
20
  return stored ? JSON.parse(stored) : {};
14
21
  } catch {
15
22
  return {};
@@ -19,18 +26,18 @@ export function useCollapsedGroups(groupNames: string[]) {
19
26
  // Save to localStorage whenever collapsedGroups changes
20
27
  useEffect(() => {
21
28
  try {
22
- localStorage.setItem('firecms-collapsed-groups', JSON.stringify(collapsedGroups));
29
+ localStorage.setItem(storageKey, JSON.stringify(collapsedGroups));
23
30
  } catch {
24
31
  // Silently fail if localStorage is not available
25
32
  }
26
- }, [collapsedGroups]);
33
+ }, [collapsedGroups, storageKey]);
27
34
 
28
35
  // Clean up collapsed groups state when groups change - remove entries for groups that no longer exist
29
36
  useEffect(() => {
30
37
  // Only clean up if we have actual groups loaded (avoid cleaning up during initial load)
31
38
  if (groupNames.length === 0) return;
32
39
 
33
- const currentGroupNames = new Set(groupNames);
40
+ const currentGroupNames = new Set(groupNames.map(g => g ?? "__default__"));
34
41
 
35
42
  setCollapsedGroups(prev => {
36
43
  const cleaned = Object.fromEntries(
@@ -49,12 +56,13 @@ export function useCollapsedGroups(groupNames: string[]) {
49
56
  });
50
57
  }, [groupNames]);
51
58
 
52
- const isGroupCollapsed = useCallback((name: string) => {
53
- return !!collapsedGroups[name];
59
+ const isGroupCollapsed = useCallback((name?: string | null) => {
60
+ return !!collapsedGroups[name ?? "__default__"];
54
61
  }, [collapsedGroups]);
55
62
 
56
- const toggleGroupCollapsed = useCallback((name: string) => {
57
- setCollapsedGroups(prev => ({ ...prev, [name]: !prev[name] }));
63
+ const toggleGroupCollapsed = useCallback((name?: string | null) => {
64
+ const key = name ?? "__default__";
65
+ setCollapsedGroups(prev => ({ ...prev, [key]: !prev[key] }));
58
66
  }, []);
59
67
 
60
68
  return {
@@ -62,3 +70,4 @@ export function useCollapsedGroups(groupNames: string[]) {
62
70
  toggleGroupCollapsed
63
71
  };
64
72
  }
73
+
@@ -0,0 +1,31 @@
1
+ import { useTranslation as useI18nTranslation } from "react-i18next";
2
+
3
+ const FIRECMS_NS = "firecms_core";
4
+
5
+ /**
6
+ * Internal hook for translating FireCMS UI strings.
7
+ *
8
+ * Uses the `firecms_core` i18next namespace that is initialised by
9
+ * `FireCMSi18nProvider`. Do NOT use `react-i18next` directly in internal
10
+ * components — always go through this hook so the namespace is consistent.
11
+ *
12
+ * @example
13
+ * const { t } = useTranslation();
14
+ * <Button>{t("save")}</Button>
15
+ *
16
+ * @internal
17
+ */
18
+ export function useTranslation() {
19
+ const { t, i18n } = useI18nTranslation(FIRECMS_NS);
20
+
21
+ /**
22
+ * Typed translation function scoped to FirecmsTranslations keys.
23
+ * Also supports i18next interpolation variables, e.g.
24
+ * t("add_to_field", { fieldName: "Tags" })
25
+ * t("error_deleting", { message: err.message })
26
+ */
27
+ const typedT = (key: string, vars?: Record<string, string>): string =>
28
+ t(key, vars) as string;
29
+
30
+ return { t: typedT, i18n };
31
+ }
@@ -51,7 +51,7 @@ export function useValidateAuthenticator<USER extends User = any>
51
51
  * We use this ref to check the authentication only if the user has
52
52
  * changed.
53
53
  */
54
- const checkedUserRef = useRef<User | undefined>();
54
+ const checkedUserRef = useRef<User | undefined>(undefined);
55
55
 
56
56
  const checkAuthentication = useCallback(async () => {
57
57
 
@@ -0,0 +1,160 @@
1
+ import React, { PropsWithChildren, useEffect, useRef } from "react";
2
+ import i18next, { i18n } from "i18next";
3
+ import { I18nextProvider, initReactI18next } from "react-i18next";
4
+ import { en } from "../locales/en";
5
+ import { es } from "../locales/es";
6
+ import { de } from "../locales/de";
7
+ import { fr } from "../locales/fr";
8
+ import { it } from "../locales/it";
9
+ import { hi } from "../locales/hi";
10
+ import { pt } from "../locales/pt";
11
+ import { FireCMSTranslations } from "../types/translations";
12
+
13
+ const FIRECMS_NS = "firecms_core";
14
+
15
+ export const FIRECMS_LOCALE_STORAGE_KEY = "firecms_locale";
16
+
17
+ /** DeepPartial helper — allows partial overrides at any nesting level */
18
+ type DeepPartial<T> = T extends object
19
+ ? { [K in keyof T]?: DeepPartial<T[K]> }
20
+ : T;
21
+
22
+ export interface FireCMSi18nProviderProps {
23
+ /** BCP-47 locale tag, e.g. "en", "es", "fr". Defaults to "en". */
24
+ locale?: string;
25
+ /**
26
+ * Override or extend any FireCMS UI string, keyed by locale.
27
+ *
28
+ * @example
29
+ * translations={{
30
+ * en: { save: "Publish" },
31
+ * es: { save: "Publicar", discard: "Descartar" }
32
+ * }}
33
+ */
34
+ translations?: {
35
+ [locale: string]: DeepPartial<FireCMSTranslations>;
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Initialises a dedicated i18next instance for FireCMS's internal UI strings.
41
+ *
42
+ * This instance is isolated from any app-level i18next configuration the
43
+ * consumer may have. Mount this at the top of the FireCMS component tree.
44
+ *
45
+ * @internal
46
+ */
47
+ export function FireCMSi18nProvider({
48
+ locale = "en",
49
+ translations,
50
+ children
51
+ }: PropsWithChildren<FireCMSi18nProviderProps>) {
52
+ const i18nRef = useRef<i18n | null>(null);
53
+ const [ready, setReady] = React.useState(false);
54
+
55
+ if (!i18nRef.current) {
56
+ const instance = i18next.createInstance();
57
+
58
+ // Build the initial resources: English baseline + any consumer overrides
59
+ const resources = buildResources(translations);
60
+
61
+ let initialLocale = locale;
62
+ if (typeof window !== "undefined") {
63
+ const stored = localStorage.getItem(FIRECMS_LOCALE_STORAGE_KEY);
64
+ if (stored) initialLocale = stored;
65
+ }
66
+
67
+ instance
68
+ .use(initReactI18next)
69
+ .init({
70
+ lng: initialLocale,
71
+ fallbackLng: "en",
72
+ ns: [FIRECMS_NS],
73
+ defaultNS: FIRECMS_NS,
74
+ resources,
75
+ interpolation: {
76
+ // React already escapes — don't double-escape
77
+ escapeValue: false,
78
+ },
79
+ }, () => {
80
+ setReady(true);
81
+ });
82
+
83
+ instance.on("languageChanged", (lng) => {
84
+ if (typeof window !== "undefined") {
85
+ localStorage.setItem(FIRECMS_LOCALE_STORAGE_KEY, lng);
86
+ }
87
+ });
88
+
89
+ i18nRef.current = instance;
90
+ }
91
+
92
+ // When `locale` prop changes, switch language on the existing instance
93
+ // ONLY if the user hasn't explicitly set a preference
94
+ useEffect(() => {
95
+ if (i18nRef.current && i18nRef.current.language !== locale) {
96
+ const hasUserPreference = typeof window !== "undefined" && Boolean(localStorage.getItem(FIRECMS_LOCALE_STORAGE_KEY));
97
+ if (!hasUserPreference) {
98
+ i18nRef.current.changeLanguage(locale);
99
+ }
100
+ }
101
+ }, [locale]);
102
+
103
+ // When consumer translations prop changes, update the resource bundles
104
+ useEffect(() => {
105
+ if (!i18nRef.current) return;
106
+ const resources = buildResources(translations);
107
+ for (const [lang, bundle] of Object.entries(resources)) {
108
+ i18nRef.current.addResourceBundle(
109
+ lang,
110
+ FIRECMS_NS,
111
+ bundle[FIRECMS_NS],
112
+ true, // deep merge
113
+ true // overwrite existing keys
114
+ );
115
+ }
116
+ }, [translations]);
117
+
118
+ if (!ready || !i18nRef.current) return null;
119
+
120
+ return (
121
+ <I18nextProvider i18n={i18nRef.current}>
122
+ {children}
123
+ </I18nextProvider>
124
+ );
125
+ }
126
+
127
+ /**
128
+ * Build an i18next resources object from the English baseline plus any
129
+ * consumer-provided overrides.
130
+ */
131
+ function buildResources(
132
+ translations?: { [locale: string]: DeepPartial<FireCMSTranslations> }
133
+ ): Record<string, Record<string, object>> {
134
+ const resources: Record<string, Record<string, object>> = {
135
+ en: { [FIRECMS_NS]: { ...en } },
136
+ es: { [FIRECMS_NS]: { ...es } },
137
+ de: { [FIRECMS_NS]: { ...de } },
138
+ fr: { [FIRECMS_NS]: { ...fr } },
139
+ it: { [FIRECMS_NS]: { ...it } },
140
+ hi: { [FIRECMS_NS]: { ...hi } },
141
+ pt: { [FIRECMS_NS]: { ...pt } },
142
+ };
143
+
144
+ if (!translations) return resources;
145
+
146
+ for (const [lang, overrides] of Object.entries(translations)) {
147
+ if (!resources[lang]) {
148
+ // For non-English/Spanish locales, start from English as the fallback base
149
+ resources[lang] = { [FIRECMS_NS]: { ...en } };
150
+ }
151
+ // Merge consumer overrides (shallow merge is enough since translations
152
+ // is a flat record — deepMerge option in addResourceBundle handles deeper)
153
+ resources[lang][FIRECMS_NS] = {
154
+ ...resources[lang][FIRECMS_NS],
155
+ ...overrides,
156
+ };
157
+ }
158
+
159
+ return resources;
160
+ }
package/src/index.ts CHANGED
@@ -7,3 +7,8 @@ export * from "./hooks";
7
7
  export * from "./components";
8
8
  export * from "./util";
9
9
  export * from "./contexts";
10
+ export * from "./i18n/FireCMSi18nProvider";
11
+ export * from "./locales/en";
12
+ export * from "./locales/es";
13
+ export * from "./editor";
14
+ export * from "./util/objects";