@firecms/core 3.0.0-beta.12 → 3.0.0-beta.14

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 (120) hide show
  1. package/dist/app/Scaffold.d.ts +4 -0
  2. package/dist/components/ArrayContainer.d.ts +13 -11
  3. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  4. package/dist/components/EntityJsonPreview.d.ts +3 -0
  5. package/dist/components/EntityPreview.d.ts +4 -2
  6. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  7. package/dist/components/VirtualTable/VirtualTableProps.d.ts +0 -4
  8. package/dist/components/index.d.ts +1 -0
  9. package/dist/core/DefaultAppBar.d.ts +8 -2
  10. package/dist/core/EntityEditView.d.ts +8 -1
  11. package/dist/core/FireCMS.d.ts +2 -2
  12. package/dist/form/EntityForm.d.ts +5 -1
  13. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  14. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  15. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -9
  16. package/dist/hooks/data/save.d.ts +1 -1
  17. package/dist/hooks/useBuildNavigationController.d.ts +2 -9
  18. package/dist/index.es.js +9251 -8721
  19. package/dist/index.es.js.map +1 -1
  20. package/dist/index.umd.js +16686 -16160
  21. package/dist/index.umd.js.map +1 -1
  22. package/dist/internal/useBuildDataSource.d.ts +3 -2
  23. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  24. package/dist/types/collections.d.ts +26 -2
  25. package/dist/types/fields.d.ts +4 -1
  26. package/dist/types/firecms.d.ts +3 -2
  27. package/dist/types/navigation.d.ts +9 -0
  28. package/dist/types/plugins.d.ts +13 -0
  29. package/dist/types/properties.d.ts +22 -1
  30. package/dist/types/side_entity_controller.d.ts +4 -0
  31. package/dist/util/callbacks.d.ts +2 -0
  32. package/dist/util/createFormexStub.d.ts +2 -0
  33. package/dist/util/index.d.ts +1 -0
  34. package/dist/util/navigation_utils.d.ts +2 -1
  35. package/dist/util/objects.d.ts +1 -1
  36. package/dist/util/property_utils.d.ts +2 -2
  37. package/dist/util/references.d.ts +2 -2
  38. package/dist/util/resolutions.d.ts +11 -6
  39. package/package.json +9 -6
  40. package/src/app/Scaffold.tsx +13 -3
  41. package/src/components/ArrayContainer.tsx +414 -282
  42. package/src/components/ClearFilterSortButton.tsx +1 -1
  43. package/src/components/ConfirmationDialog.tsx +9 -9
  44. package/src/components/DeleteEntityDialog.tsx +4 -2
  45. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +8 -6
  46. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +1 -1
  47. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
  48. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +13 -2
  49. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +58 -29
  50. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +9 -9
  51. package/src/components/EntityCollectionView/EntityCollectionView.tsx +26 -6
  52. package/src/components/EntityJsonPreview.tsx +66 -0
  53. package/src/components/EntityPreview.tsx +27 -16
  54. package/src/components/EntityView.tsx +4 -1
  55. package/src/components/ErrorView.tsx +1 -1
  56. package/src/components/HomePage/DefaultHomePage.tsx +2 -1
  57. package/src/components/HomePage/NavigationCardBinding.tsx +3 -1
  58. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -1
  59. package/src/components/SelectableTable/SelectableTable.tsx +140 -143
  60. package/src/components/VirtualTable/VirtualTable.tsx +37 -31
  61. package/src/components/VirtualTable/VirtualTableProps.tsx +0 -5
  62. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -1
  63. package/src/components/common/default_entity_actions.tsx +15 -4
  64. package/src/components/index.tsx +2 -0
  65. package/src/core/DefaultAppBar.tsx +17 -5
  66. package/src/core/EntityEditView.tsx +135 -47
  67. package/src/core/EntitySidePanel.tsx +15 -20
  68. package/src/core/FireCMS.tsx +19 -12
  69. package/src/form/EntityForm.tsx +32 -24
  70. package/src/form/PropertyFieldBinding.tsx +8 -6
  71. package/src/form/components/CustomIdField.tsx +3 -1
  72. package/src/form/components/LabelWithIcon.tsx +1 -1
  73. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -1
  74. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -2
  75. package/src/form/field_bindings/BlockFieldBinding.tsx +1 -1
  76. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  77. package/src/form/field_bindings/MapFieldBinding.tsx +5 -4
  78. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +6 -2
  79. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +3 -3
  80. package/src/form/field_bindings/RepeatFieldBinding.tsx +9 -2
  81. package/src/form/field_bindings/SelectFieldBinding.tsx +3 -2
  82. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +227 -156
  83. package/src/hooks/data/save.ts +24 -35
  84. package/src/hooks/useBuildNavigationController.tsx +42 -17
  85. package/src/hooks/useFireCMSContext.tsx +0 -30
  86. package/src/internal/useBuildDataSource.ts +9 -5
  87. package/src/internal/useBuildSideEntityController.tsx +26 -20
  88. package/src/preview/PropertyPreview.tsx +4 -2
  89. package/src/preview/components/ImagePreview.tsx +2 -2
  90. package/src/preview/components/ReferencePreview.tsx +1 -1
  91. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +5 -4
  92. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
  93. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
  94. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
  95. package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
  96. package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
  97. package/src/preview/property_previews/MapPropertyPreview.tsx +1 -1
  98. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +1 -1
  99. package/src/preview/property_previews/StringPropertyPreview.tsx +1 -1
  100. package/src/routes/FireCMSRoute.tsx +15 -4
  101. package/src/types/collections.ts +31 -2
  102. package/src/types/fields.tsx +5 -1
  103. package/src/types/firecms.tsx +3 -3
  104. package/src/types/navigation.ts +11 -0
  105. package/src/types/plugins.tsx +17 -0
  106. package/src/types/properties.ts +27 -1
  107. package/src/types/side_entity_controller.tsx +5 -0
  108. package/src/util/callbacks.ts +119 -0
  109. package/src/util/createFormexStub.tsx +62 -0
  110. package/src/util/index.ts +1 -0
  111. package/src/util/join_collections.ts +3 -1
  112. package/src/util/navigation_from_path.ts +5 -1
  113. package/src/util/navigation_utils.ts +84 -20
  114. package/src/util/objects.ts +54 -17
  115. package/src/util/property_utils.tsx +7 -3
  116. package/src/util/references.ts +8 -6
  117. package/src/util/resolutions.ts +17 -9
  118. package/src/util/useStorageUploadController.tsx +21 -2
  119. package/dist/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.d.ts +0 -5
  120. package/src/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.tsx +0 -59
@@ -13,6 +13,7 @@ import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
13
13
  import { toArray } from "../util/arrays";
14
14
  import { EntityCollectionView, NotFoundPage } from "../components";
15
15
  import { UnsavedChangesDialog } from "../components/UnsavedChangesDialog";
16
+ import { EntityCollection } from "../types";
16
17
 
17
18
  export function FireCMSRoute() {
18
19
 
@@ -68,7 +69,10 @@ export function FireCMSRoute() {
68
69
  }
69
70
 
70
71
  if (navigationEntries.length === 1 && navigationEntries[0].type === "collection") {
71
- const collection = navigation.getCollection(navigationEntries[0].path);
72
+ let collection: EntityCollection<any> | undefined;
73
+ collection = navigation.getCollectionById(navigationEntries[0].id);
74
+ if (!collection)
75
+ collection = navigation.getCollection(navigationEntries[0].path);
72
76
  if (!collection)
73
77
  return null;
74
78
  return <EntityCollectionView
@@ -84,11 +88,16 @@ export function FireCMSRoute() {
84
88
  if (isSidePanel) {
85
89
  const lastCollectionEntry = navigationEntries.findLast((entry) => entry.type === "collection");
86
90
  if (lastCollectionEntry) {
87
- const collection = navigation.getCollection(lastCollectionEntry.path);
91
+ let collection: EntityCollection<any> | undefined;
92
+ const firstEntry = navigationEntries[0] as NavigationViewCollectionInternal<any>;
93
+ collection = navigation.getCollectionById(firstEntry.id);
94
+ if (!collection)
95
+ collection = navigation.getCollection(firstEntry.path);
88
96
  if (!collection)
89
97
  return null;
90
98
  return <EntityCollectionView
91
99
  key={`collection_view_${collection.id ?? collection.path}`}
100
+ fullIdPath={collection.id}
92
101
  isSubCollection={false}
93
102
  parentCollectionIds={[]}
94
103
  fullPath={collection.id}
@@ -175,7 +184,7 @@ function EntityFullScreenRoute({
175
184
  return blocked.current;
176
185
  });
177
186
  } catch (e) {
178
- console.warn("Blocker not available, navigation will not be blocked");
187
+ // console.warn("Blocker not available, navigation will not be blocked");
179
188
  }
180
189
 
181
190
  const lastCollectionEntry = navigationEntries.findLast((entry) => entry.type === "collection");
@@ -189,12 +198,14 @@ function EntityFullScreenRoute({
189
198
  }
190
199
 
191
200
  const collection = isNew ? lastCollectionEntry!.collection : lastEntityEntry!.parentCollection;
192
- const collectionPath = navigation.resolveIdsFrom(isNew ? lastCollectionEntry!.path : lastEntityEntry!.path);
201
+ const fullIdPath = isNew ? lastCollectionEntry!.path : lastEntityEntry!.path;
202
+ const collectionPath = navigation.resolveIdsFrom(fullIdPath);
193
203
 
194
204
  return <>
195
205
  <EntityEditView
196
206
  key={collection.id + "_" + (isNew ? "new" : (isCopy ? entityId + "_copy" : entityId))}
197
207
  entityId={isNew ? undefined : entityId}
208
+ fullIdPath={fullIdPath}
198
209
  collection={collection}
199
210
  layout={"full_screen"}
200
211
  path={collectionPath}
@@ -112,7 +112,7 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
112
112
  * `hidden` in the property definition, will be ignored.
113
113
  * `propertiesOrder` has precedence over `hidden`.
114
114
  * - For properties use the property key.
115
- * - For additional columns use the column id.
115
+ * - For additional fields use the field key.
116
116
  * - If you have subcollections, you get a column for each subcollection,
117
117
  * with the path (or alias) as the subcollection, prefixed with
118
118
  * `subcollection:`. e.g. `subcollection:orders`.
@@ -127,7 +127,8 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
127
127
 
128
128
  /**
129
129
  * If enabled, content is loaded in batches. If `false` all entities in the
130
- * collection are loaded.
130
+ * collection are loaded. This means that when reaching the end of the
131
+ * collection, the CMS will load more entities.
131
132
  * You can specify a number to specify the pagination size (50 by default)
132
133
  * Defaults to `true`
133
134
  */
@@ -215,6 +216,7 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
215
216
  * Force a filter in this view. If applied, the rest of the filters will
216
217
  * be disabled. Filters applied with this prop cannot be changed.
217
218
  * e.g. `forceFilter: { age: [">=", 18] }`
219
+ * e.g. `forceFilter: { related_user: ["==", new EntityReference("sdc43dsw2", "users")] }`
218
220
  */
219
221
  forceFilter?: FilterValues<Extract<keyof M, string>>;
220
222
 
@@ -222,6 +224,7 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
222
224
  * Initial filters applied to the collection this collection is related to.
223
225
  * Defaults to none. Filters applied with this prop can be changed.
224
226
  * e.g. `initialFilter: { age: [">=", 18] }`
227
+ * e.g. `initialFilter: { related_user: ["==", new EntityReference("sdc43dsw2", "users")] }`
225
228
  */
226
229
  initialFilter?: FilterValues<Extract<keyof M, string>>; // setting FilterValues<M> can break defining collections by code
227
230
 
@@ -330,6 +333,17 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
330
333
  * Defaults to false.
331
334
  */
332
335
  alwaysApplyDefaultValues?: boolean;
336
+
337
+ /**
338
+ * If set to true, a tab including the JSON representation of the entity will be included.
339
+ */
340
+ includeJsonView?: boolean;
341
+
342
+ /**
343
+ * If set to true, changes to the entity will be saved in a subcollection.
344
+ * This prop has no effect if the history plugin is not enabled
345
+ */
346
+ history?: boolean;
333
347
  }
334
348
 
335
349
  /**
@@ -512,6 +526,11 @@ export type EntityCustomView<M extends Record<string, any> = any> =
512
526
  */
513
527
  name: string;
514
528
 
529
+ /**
530
+ * Render this custom view in the tab of the entity view, instead of the name
531
+ */
532
+ tabComponent?: React.ReactNode;
533
+
515
534
  /**
516
535
  * If set to true, the actions of the entity (save, discard,delete) will be
517
536
  * included in the view. By default the actions are located in the right or bottom,
@@ -524,6 +543,11 @@ export type EntityCustomView<M extends Record<string, any> = any> =
524
543
  * Builder for rendering the custom view
525
544
  */
526
545
  Builder?: React.ComponentType<EntityCustomViewParams<M>>;
546
+
547
+ /**
548
+ * Position of this tab in the entity view. Defaults to `end`.
549
+ */
550
+ position?: "start" | "end";
527
551
  };
528
552
 
529
553
  /**
@@ -554,6 +578,11 @@ export interface EntityCustomViewParams<M extends Record<string, any> = any> {
554
578
  * Use the form context to access the form state and methods
555
579
  */
556
580
  formContext: FormContext;
581
+
582
+ /**
583
+ * If this is a subcollection, this is the path of the parent collections
584
+ */
585
+ parentCollectionIds?: string[];
557
586
  }
558
587
 
559
588
  export type InferCollectionType<S extends EntityCollection> = S extends EntityCollection<infer M> ? M : never;
@@ -171,9 +171,13 @@ export interface FormContext<M extends Record<string, any> = any> {
171
171
  openEntityMode: "side_panel" | "full_screen";
172
172
 
173
173
  /**
174
- * This is the underlying formex controller that powers the form
174
+ * This is the underlying formex controller that powers the form.
175
+ * If you are in a red only mode, the formex controller is there, but you can't
176
+ * operate with it
175
177
  */
176
178
  formex: FormexController<M>;
179
+
180
+ disabled: boolean;
177
181
  }
178
182
 
179
183
  /**
@@ -47,7 +47,7 @@ export type CMSViewsBuilder = (params: {
47
47
  /**
48
48
  * @group Models
49
49
  */
50
- export type FireCMSProps<USER extends User, EC extends EntityCollection> = {
50
+ export type FireCMSProps<USER extends User> = {
51
51
 
52
52
  /**
53
53
  * Use this function to return the components you want to render under
@@ -92,7 +92,6 @@ export type FireCMSProps<USER extends User, EC extends EntityCollection> = {
92
92
  *
93
93
  * You can also define an entity view from the UI.
94
94
  */
95
-
96
95
  entityViews?: EntityCustomView[];
97
96
 
98
97
  /**
@@ -129,7 +128,8 @@ export type FireCMSProps<USER extends User, EC extends EntityCollection> = {
129
128
 
130
129
  /**
131
130
  * Use plugins to modify the behaviour of the CMS.
132
- * Currently, in ALPHA, and likely subject to change.
131
+ * DEPRECATED: use the `plugins` prop in the `useBuildNavigationController` instead.
132
+ * This prop will work as a fallback for the `plugins` prop in the `useBuildNavigationController`.
133
133
  */
134
134
  plugins?: FireCMSPlugin<any, any, any>[];
135
135
 
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { EntityCollection } from "./collections";
3
3
  import { EntityReference } from "./entities";
4
+ import { FireCMSPlugin } from "./plugins";
4
5
 
5
6
  /**
6
7
  * Controller that includes the resolved navigation and utility methods and
@@ -59,6 +60,11 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
59
60
  */
60
61
  getCollection: (pathOrId: string, includeUserOverride?: boolean) => EC | undefined;
61
62
 
63
+ /**
64
+ * Get the top level collection configuration for a given id
65
+ */
66
+ getCollectionById: (id: string) => EC | undefined;
67
+
62
68
  /**
63
69
  * Get the collection configuration from its parent ids.
64
70
  */
@@ -147,6 +153,11 @@ export type NavigationController<EC extends EntityCollection = EntityCollection<
147
153
  */
148
154
  navigate: (to: string, options?: NavigateOptions) => void;
149
155
 
156
+ /**
157
+ * Plugin system allowing to extend the CMS functionality.
158
+ */
159
+ plugins?: FireCMSPlugin<any, any, any>[];
160
+
150
161
  }
151
162
 
152
163
  export interface NavigateOptions {
@@ -158,6 +158,22 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
158
158
  fieldBuilderEnabled?: <T extends CMSType = CMSType>(props: PluginFieldBuilderParams<T>) => boolean;
159
159
  }
160
160
 
161
+ collection?: {
162
+
163
+ /**
164
+ * Use this method to modify a single collection before it is rendered.
165
+ * @param collection
166
+ */
167
+ modifyCollection?: (collection: EntityCollection) => EntityCollection;
168
+
169
+ /**
170
+ * Use this method to modify, add or remove collections.
171
+ * @param collections
172
+ */
173
+ injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
174
+
175
+ }
176
+
161
177
  }
162
178
 
163
179
  /**
@@ -192,6 +208,7 @@ export interface PluginFormActionProps<USER extends User = User, EC extends Enti
192
208
  path: string;
193
209
  status: EntityStatus;
194
210
  collection: EC;
211
+ disabled: boolean;
195
212
  formContext?: FormContext<any>;
196
213
  context: FireCMSContext<USER>;
197
214
  currentEntityId?: string;
@@ -5,6 +5,7 @@ import { EntityReference, EntityValues, GeoPoint, Vector } from "./entities";
5
5
  import { ResolvedArrayProperty, ResolvedStringProperty } from "./resolved_entities";
6
6
  import { FilterValues } from "./collections";
7
7
  import { ChipColorKey, ChipColorScheme } from "@firecms/ui";
8
+ import { AuthController } from "./auth";
8
9
 
9
10
  /**
10
11
  * @group Entity properties
@@ -270,6 +271,11 @@ export type PropertyBuilderProps<M extends Record<string, any> = any> =
270
271
  * Entity ID
271
272
  */
272
273
  entityId?: string;
274
+
275
+ /**
276
+ * Controller to manage authentication
277
+ */
278
+ authController: AuthController;
273
279
  };
274
280
 
275
281
  /**
@@ -285,7 +291,8 @@ export type PropertyBuilder<T extends CMSType = any, M extends Record<string, an
285
291
  propertyValue,
286
292
  index,
287
293
  path,
288
- entityId
294
+ entityId,
295
+ authController
289
296
  }: PropertyBuilderProps<M>) => Property<T> | null;
290
297
 
291
298
  /**
@@ -481,6 +488,18 @@ export interface ArrayProperty<T extends ArrayT[] = any[], ArrayT extends CMSTyp
481
488
  */
482
489
  minimalistView?: boolean;
483
490
 
491
+ /**
492
+ * Can the elements in this array be reordered. Defaults to `true`.
493
+ * This prop has no effect if `disabled` is set to true.
494
+ */
495
+ sortable?: boolean;
496
+
497
+ /**
498
+ * Can the elements in this array be added. Defaults to `true`
499
+ * This prop has no effect if `disabled` is set to true.
500
+ */
501
+ canAddElements?: boolean;
502
+
484
503
  }
485
504
 
486
505
  /**
@@ -796,6 +815,13 @@ export type StorageConfig = {
796
815
  */
797
816
  maxSize?: number,
798
817
 
818
+ /**
819
+ * Use this callback to process the file before uploading it to the storage.
820
+ * If nothing is returned, the file is uploaded as it is.
821
+ * @param file
822
+ */
823
+ processFile?: (file: File) => Promise<File> | undefined;
824
+
799
825
  /**
800
826
  * Postprocess the saved value (storage path or URL)
801
827
  * after it has been resolved.
@@ -71,6 +71,11 @@ export interface EntitySidePanelProps<M extends Record<string, any> = any> {
71
71
  * Override some form properties
72
72
  */
73
73
  formProps?: Partial<EntityFormProps<M>>;
74
+
75
+ /**
76
+ * Allow the user to open the entity fullscreen
77
+ */
78
+ allowFullScreen?: boolean;
74
79
  }
75
80
 
76
81
  /**
@@ -0,0 +1,119 @@
1
+ import { EntityCallbacks } from "../types";
2
+
3
+ export const mergeCallbacks = (
4
+ baseCallbacks: EntityCallbacks = {},
5
+ pluginCallbacks: EntityCallbacks = {}
6
+ ): EntityCallbacks | undefined => {
7
+
8
+ if (!baseCallbacks && !pluginCallbacks) {
9
+ return undefined;
10
+ }
11
+
12
+ const mergedCallbacks: EntityCallbacks = {};
13
+
14
+ // Handle onFetch - returns Entity<M> or Promise<Entity<M>>
15
+ if (baseCallbacks.onFetch || pluginCallbacks.onFetch) {
16
+ mergedCallbacks.onFetch = async (props) => {
17
+ let entity = props.entity;
18
+ if (baseCallbacks.onFetch) {
19
+ entity = await Promise.resolve(baseCallbacks.onFetch(props));
20
+ }
21
+ if (pluginCallbacks.onFetch) {
22
+ entity = await Promise.resolve(pluginCallbacks.onFetch({
23
+ ...props,
24
+ entity
25
+ }));
26
+ }
27
+ return entity;
28
+ };
29
+ }
30
+
31
+ // Handle onSaveSuccess - returns void or Promise<void>
32
+ if (baseCallbacks.onSaveSuccess || pluginCallbacks.onSaveSuccess) {
33
+ mergedCallbacks.onSaveSuccess = async (props) => {
34
+ if (baseCallbacks.onSaveSuccess) {
35
+ await Promise.resolve(baseCallbacks.onSaveSuccess(props));
36
+ }
37
+ if (pluginCallbacks.onSaveSuccess) {
38
+ await Promise.resolve(pluginCallbacks.onSaveSuccess(props));
39
+ }
40
+ };
41
+ }
42
+
43
+ // Handle onSaveFailure - returns void or Promise<void>
44
+ if (baseCallbacks.onSaveFailure || pluginCallbacks.onSaveFailure) {
45
+ mergedCallbacks.onSaveFailure = async (props) => {
46
+ if (baseCallbacks.onSaveFailure) {
47
+ await Promise.resolve(baseCallbacks.onSaveFailure(props));
48
+ }
49
+ if (pluginCallbacks.onSaveFailure) {
50
+ await Promise.resolve(pluginCallbacks.onSaveFailure(props));
51
+ }
52
+ };
53
+ }
54
+
55
+ // Handle onPreSave - returns Partial<EntityValues<M>> or Promise<Partial<EntityValues<M>>>
56
+ if (baseCallbacks.onPreSave || pluginCallbacks.onPreSave) {
57
+ mergedCallbacks.onPreSave = async (props) => {
58
+ let values = { ...props.values };
59
+ if (baseCallbacks.onPreSave) {
60
+ const baseValues = await Promise.resolve(baseCallbacks.onPreSave(props));
61
+ values = { ...values, ...baseValues };
62
+ }
63
+ if (pluginCallbacks.onPreSave) {
64
+ const pluginValues = await Promise.resolve(pluginCallbacks.onPreSave({
65
+ ...props,
66
+ values
67
+ }));
68
+ values = { ...values, ...pluginValues };
69
+ }
70
+ return values;
71
+ };
72
+ }
73
+
74
+ // Handle onPreDelete - returns void
75
+ if (baseCallbacks.onPreDelete || pluginCallbacks.onPreDelete) {
76
+ mergedCallbacks.onPreDelete = (props) => {
77
+ if (baseCallbacks.onPreDelete) {
78
+ baseCallbacks.onPreDelete(props);
79
+ }
80
+ if (pluginCallbacks.onPreDelete) {
81
+ pluginCallbacks.onPreDelete(props);
82
+ }
83
+ };
84
+ }
85
+
86
+ // Handle onDelete - returns void
87
+ if (baseCallbacks.onDelete || pluginCallbacks.onDelete) {
88
+ mergedCallbacks.onDelete = (props) => {
89
+ if (baseCallbacks.onDelete) {
90
+ baseCallbacks.onDelete(props);
91
+ }
92
+ if (pluginCallbacks.onDelete) {
93
+ pluginCallbacks.onDelete(props);
94
+ }
95
+ };
96
+ }
97
+
98
+ // Handle onIdUpdate - returns string or Promise<string>
99
+ if (baseCallbacks.onIdUpdate || pluginCallbacks.onIdUpdate) {
100
+ mergedCallbacks.onIdUpdate = async (props) => {
101
+ let id = props.entityId || "";
102
+
103
+ if (baseCallbacks.onIdUpdate) {
104
+ id = await Promise.resolve(baseCallbacks.onIdUpdate(props));
105
+ }
106
+
107
+ if (pluginCallbacks.onIdUpdate) {
108
+ id = await Promise.resolve(pluginCallbacks.onIdUpdate({
109
+ ...props,
110
+ entityId: id
111
+ }));
112
+ }
113
+
114
+ return id;
115
+ };
116
+ }
117
+
118
+ return Object.keys(mergedCallbacks).length > 0 ? mergedCallbacks : undefined;
119
+ };
@@ -0,0 +1,62 @@
1
+ import { FormexController } from "@firecms/formex";
2
+
3
+ export function createFormexStub<T extends object>(values: T): FormexController<T> {
4
+ const errorMessage = "You are in a read-only context. You cannot modify the formex controller.";
5
+
6
+ return {
7
+ values,
8
+ initialValues: values,
9
+ touched: {} as Record<string, boolean>,
10
+ dirty: false,
11
+ errors: {} as Record<string, string>,
12
+ submitCount: 0,
13
+ isSubmitting: false,
14
+ isValidating: false,
15
+ version: 0,
16
+ canUndo: false,
17
+ canRedo: false,
18
+
19
+ setValues: () => {
20
+ throw new Error(errorMessage);
21
+ },
22
+ setFieldValue: () => {
23
+ throw new Error(errorMessage);
24
+ },
25
+ setFieldTouched: () => {
26
+ throw new Error(errorMessage);
27
+ },
28
+ setDirty: () => {
29
+ throw new Error(errorMessage);
30
+ },
31
+ setSubmitCount: () => {
32
+ throw new Error(errorMessage);
33
+ },
34
+ setFieldError: () => {
35
+ throw new Error(errorMessage);
36
+ },
37
+ handleChange: () => {
38
+ throw new Error(errorMessage);
39
+ },
40
+ handleBlur: () => {
41
+ throw new Error(errorMessage);
42
+ },
43
+ handleSubmit: () => {
44
+ throw new Error(errorMessage);
45
+ },
46
+ validate: () => {
47
+ throw new Error(errorMessage);
48
+ },
49
+ resetForm: () => {
50
+ throw new Error(errorMessage);
51
+ },
52
+ setSubmitting: () => {
53
+ throw new Error(errorMessage);
54
+ },
55
+ undo: () => {
56
+ throw new Error(errorMessage);
57
+ },
58
+ redo: () => {
59
+ throw new Error(errorMessage);
60
+ }
61
+ };
62
+ }
package/src/util/index.ts CHANGED
@@ -23,3 +23,4 @@ export * from "./join_collections";
23
23
  export * from "./builders";
24
24
  export * from "./useTraceUpdate";
25
25
  export * from "./storage";
26
+ export * from "./callbacks";
@@ -78,6 +78,7 @@ export function mergeCollection(target: EntityCollection,
78
78
  modifyCollection?: (props: ModifyCollectionProps) => EntityCollection | void
79
79
  ): EntityCollection {
80
80
 
81
+
81
82
  const subcollectionsMerged = joinCollectionLists(
82
83
  target?.subcollections ?? [],
83
84
  source?.subcollections ?? [],
@@ -94,7 +95,7 @@ export function mergeCollection(target: EntityCollection,
94
95
  propertiesMerged[key] = source.properties[key] as PropertyOrBuilder;
95
96
  });
96
97
 
97
- const mergedCollection = mergeDeep(target, source);
98
+ const mergedCollection = mergeDeep(target, source, true);
98
99
  const targetPropertiesOrder = getCollectionKeys(target);
99
100
  const sourcePropertiesOrder = getCollectionKeys(source);
100
101
  const mergedPropertiesOrder = [...new Set([...sourcePropertiesOrder, ...targetPropertiesOrder])];
@@ -118,6 +119,7 @@ export function mergeCollection(target: EntityCollection,
118
119
 
119
120
  // @ts-ignore
120
121
  resultCollection["merged"] = true;
122
+
121
123
  return resultCollection
122
124
  }
123
125
 
@@ -51,7 +51,11 @@ export function getNavigationEntriesFromPath(props: {
51
51
  for (let i = 0; i < subpathCombinations.length; i++) {
52
52
  const subpathCombination = subpathCombinations[i];
53
53
 
54
- const collection: EntityCollection<any> | undefined = collections && collections.find((entry) => entry.id === subpathCombination || entry.path === subpathCombination);
54
+ let collection: EntityCollection<any> | undefined;
55
+ collection = collections && collections.find((entry) => entry.id === subpathCombination);
56
+ if (!collection) {
57
+ collection = collections && collections.find((entry) => entry.path === subpathCombination);
58
+ }
55
59
 
56
60
  if (collection) {
57
61
  const pathOrAlias = collection.id ?? collection.path;