@firecms/core 3.0.0-canary.235 → 3.0.0-canary.239

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 (44) hide show
  1. package/dist/components/EntityPreview.d.ts +4 -2
  2. package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
  3. package/dist/components/index.d.ts +1 -0
  4. package/dist/hooks/useBuildNavigationController.d.ts +2 -9
  5. package/dist/index.es.js +271 -144
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +271 -144
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/types/collections.d.ts +13 -0
  10. package/dist/types/plugins.d.ts +12 -0
  11. package/dist/types/side_entity_controller.d.ts +4 -0
  12. package/dist/util/callbacks.d.ts +2 -0
  13. package/dist/util/index.d.ts +1 -0
  14. package/package.json +5 -5
  15. package/src/components/ConfirmationDialog.tsx +9 -9
  16. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +1 -1
  17. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
  18. package/src/components/EntityCollectionView/EntityCollectionView.tsx +2 -0
  19. package/src/components/EntityPreview.tsx +18 -14
  20. package/src/components/ErrorView.tsx +1 -1
  21. package/src/components/HomePage/DefaultHomePage.tsx +1 -1
  22. package/src/components/SelectableTable/SelectableTable.tsx +140 -143
  23. package/src/components/VirtualTable/VirtualTable.tsx +7 -4
  24. package/src/components/index.tsx +2 -0
  25. package/src/core/EntityEditView.tsx +24 -14
  26. package/src/core/EntitySidePanel.tsx +17 -10
  27. package/src/form/components/LabelWithIcon.tsx +1 -1
  28. package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -2
  29. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +1 -1
  30. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -1
  31. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +1 -1
  32. package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
  33. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
  34. package/src/hooks/useBuildNavigationController.tsx +29 -16
  35. package/src/internal/useBuildSideEntityController.tsx +1 -1
  36. package/src/preview/components/ReferencePreview.tsx +1 -1
  37. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -1
  38. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +1 -1
  39. package/src/types/collections.ts +16 -0
  40. package/src/types/firecms.tsx +0 -1
  41. package/src/types/plugins.tsx +16 -0
  42. package/src/types/side_entity_controller.tsx +5 -5
  43. package/src/util/callbacks.ts +119 -0
  44. package/src/util/index.ts +1 -0
@@ -182,11 +182,13 @@ export function EntityEditViewInner<M extends Record<string, any>>({
182
182
 
183
183
  const subcollections = (collection.subcollections ?? []).filter(c => !c.hideFromNavigation);
184
184
  const subcollectionsCount = subcollections?.length ?? 0;
185
- const customViews = collection.entityViews;
185
+ const customViews = collection.entityViews ?? [];
186
186
  const customViewsCount = customViews?.length ?? 0;
187
187
  const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
188
188
  const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView;
189
189
 
190
+ const plugins = customizationController.plugins;
191
+
190
192
  const {
191
193
  resolvedEntityViews,
192
194
  selectedEntityView,
@@ -212,7 +214,6 @@ export function EntityEditViewInner<M extends Record<string, any>>({
212
214
  }
213
215
 
214
216
  if (!entityId) {
215
- console.error("INTERNAL: entityId is not defined");
216
217
  return null;
217
218
  }
218
219
 
@@ -384,14 +385,24 @@ export function EntityEditViewInner<M extends Record<string, any>>({
384
385
  </Tab>
385
386
  );
386
387
 
387
- const customViewTabs = resolvedEntityViews.map((view) =>
388
- <Tab
389
- className="text-sm min-w-[120px]"
390
- value={view.key}
391
- key={`entity_detail_collection_tab_${view.name}`}>
392
- {view.name}
393
- </Tab>
394
- );
388
+ const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start")
389
+ .map((view) =>
390
+ <Tab
391
+ className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
392
+ value={view.key}
393
+ key={`entity_detail_collection_tab_${view.name}`}>
394
+ {view.tabComponent ?? view.name}
395
+ </Tab>
396
+ );
397
+ const customViewTabsEnd = resolvedEntityViews.filter(view => !view.position || view.position === "end")
398
+ .map((view) =>
399
+ <Tab
400
+ className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
401
+ value={view.key}
402
+ key={`entity_detail_collection_tab_${view.name}`}>
403
+ {view.tabComponent ?? view.name}
404
+ </Tab>
405
+ );
395
406
 
396
407
  const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
397
408
 
@@ -417,11 +428,12 @@ export function EntityEditViewInner<M extends Record<string, any>>({
417
428
  {includeJsonView && <Tab
418
429
  disabled={!hasAdditionalViews}
419
430
  value={JSON_TAB_VALUE}
420
- innerClassName={"block"}
421
431
  className={"text-sm"}>
422
432
  <CodeIcon size={"small"}/>
423
433
  </Tab>}
424
434
 
435
+ {customViewTabsStart}
436
+
425
437
  <Tab
426
438
  disabled={!hasAdditionalViews}
427
439
  value={MAIN_TAB_VALUE}
@@ -430,7 +442,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
430
442
  </Tab>
431
443
 
432
444
 
433
- {customViewTabs}
445
+ {customViewTabsEnd}
434
446
 
435
447
  {subcollectionTabs}
436
448
  </Tabs>}
@@ -453,8 +465,6 @@ export function EntityEditViewInner<M extends Record<string, any>>({
453
465
 
454
466
  </div>;
455
467
 
456
- const plugins = customizationController.plugins;
457
-
458
468
  if (plugins) {
459
469
  plugins.forEach((plugin: FireCMSPlugin) => {
460
470
  if (plugin.form?.provider) {
@@ -19,11 +19,18 @@ import { useLocation, useNavigate } from "react-router-dom";
19
19
  */
20
20
  export function EntitySidePanel(props: EntitySidePanelProps) {
21
21
 
22
+ const {
23
+ allowFullScreen = true,
24
+ path,
25
+ entityId,
26
+ formProps,
27
+ } = props;
28
+
22
29
  const {
23
30
  blocked,
24
31
  setBlocked,
25
32
  setBlockedNavigationMessage,
26
- close
33
+ close,
27
34
  } = useSideDialogContext();
28
35
 
29
36
  const navigate = useNavigate();
@@ -64,21 +71,21 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
64
71
  }
65
72
 
66
73
  const parentCollectionIds = useMemo(() => {
67
- return navigationController.getParentCollectionIds(props.path);
68
- }, [navigationController, props.path]);
74
+ return navigationController.getParentCollectionIds(path);
75
+ }, [navigationController, path]);
69
76
 
70
77
  const collection = useMemo(() => {
71
78
  if (props.collection) {
72
79
  return props.collection;
73
80
  }
74
81
 
75
- const registryCollection = navigationController.getCollection(props.path);
82
+ const registryCollection = navigationController.getCollection(path);
76
83
  if (registryCollection) {
77
84
  return registryCollection;
78
85
  }
79
86
 
80
- console.error("ERROR: No collection found in path `", props.path, "`. Entity id: ", props.entityId);
81
- throw Error("ERROR: No collection found in path `" + props.path + "`. Make sure you have defined a collection for this path in the root navigation.");
87
+ console.error("ERROR: No collection found in path `", path, "`. Entity id: ", entityId);
88
+ throw Error("ERROR: No collection found in path `" + path + "`. Make sure you have defined a collection for this path in the root navigation.");
82
89
  }, [navigationController, props.collection]);
83
90
 
84
91
  useEffect(() => {
@@ -126,16 +133,16 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
126
133
  onClick={onClose}>
127
134
  <CloseIcon size={"small"}/>
128
135
  </IconButton>
129
- <IconButton
136
+ {allowFullScreen && <IconButton
130
137
  className="self-center"
131
138
  onClick={() => {
132
- if (props.entityId)
139
+ if (entityId)
133
140
  navigate(location.pathname);
134
141
  else
135
142
  navigate(location.pathname + "#new");
136
143
  }}>
137
144
  <OpenInFullIcon size={"small"}/>
138
- </IconButton>
145
+ </IconButton>}
139
146
  </>}
140
147
  onTabChange={({
141
148
  path,
@@ -151,7 +158,7 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
151
158
  collection,
152
159
  });
153
160
  }}
154
- formProps={props.formProps}
161
+ formProps={formProps}
155
162
  />
156
163
  </ErrorBoundary>
157
164
 
@@ -24,7 +24,7 @@ export const LabelWithIcon = forwardRef<HTMLDivElement, LabelWithIconProps>(
24
24
  return (
25
25
  <div
26
26
  ref={ref}
27
- className={cls("inline-flex items-center my-0.5",
27
+ className={cls("align-middle inline-flex items-center my-0.5",
28
28
  small ? "gap-1" : "gap-2",
29
29
  className)}
30
30
  >
@@ -69,8 +69,6 @@ export function KeyValueFieldBinding({
69
69
  title={property.name}
70
70
  className={"text-text-secondary dark:text-text-secondary-dark"}/>;
71
71
 
72
-
73
- console.log("minimalistView", propertyKey, minimalistView)
74
72
  return (
75
73
  <>
76
74
 
@@ -141,7 +141,7 @@ export function MarkdownEditorFieldBinding({
141
141
  icon={getIconForProperty(property, "small")}
142
142
  required={property.validation?.required}
143
143
  title={property.name}
144
- className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
144
+ className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
145
145
  <div
146
146
  className={cls("rounded-md", fieldBackgroundMixin, disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin)}>
147
147
  {editor}
@@ -92,7 +92,7 @@ export function MultiSelectFieldBinding({
92
92
  icon={getIconForProperty(property, "small")}
93
93
  required={property.validation?.required}
94
94
  title={property.name}
95
- className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
95
+ className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
96
96
  onValueChange={(updatedValue: string[]) => {
97
97
  let newValue: EnumType[] | null;
98
98
  if (of && (of as ResolvedProperty)?.dataType === "number") {
@@ -39,7 +39,7 @@ export function ReadOnlyFieldBinding({
39
39
  icon={getIconForProperty(property, "small")}
40
40
  required={property.validation?.required}
41
41
  title={property.name}
42
- className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
42
+ className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
43
43
  }
44
44
 
45
45
  <div
@@ -90,7 +90,7 @@ function ReferenceFieldBindingInternal({
90
90
  icon={getIconForProperty(property, "small")}
91
91
  required={property.validation?.required}
92
92
  title={property.name}
93
- className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
93
+ className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
94
94
 
95
95
  {!collection && <ErrorView
96
96
  error={"The specified collection does not exist. Check console"}/>}
@@ -62,7 +62,7 @@ export function SelectFieldBinding<T extends EnumType>({
62
62
  icon={getIconForProperty(property, "small")}
63
63
  required={property.validation?.required}
64
64
  title={property.name}
65
- className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5 my-0"}
65
+ className={"h-6 text-text-secondary dark:text-text-secondary-dark ml-3.5 my-0"}
66
66
  />
67
67
  </PropertyIdCopyTooltip>}
68
68
  endAdornment={
@@ -9,6 +9,7 @@ import {
9
9
  EntityCollection,
10
10
  EntityCollectionsBuilder,
11
11
  EntityReference,
12
+ FireCMSPlugin,
12
13
  NavigationBlocker,
13
14
  NavigationController,
14
15
  PermissionsBuilder,
@@ -43,15 +44,7 @@ export type BuildNavigationContextProps<EC extends EntityCollection, USER extend
43
44
  viewsOrder?: string[];
44
45
  userConfigPersistence?: UserConfigurationPersistence;
45
46
  dataSourceDelegate: DataSourceDelegate;
46
- /**
47
- * Use this method to inject collections to the CMS.
48
- * You receive the current collections as a parameter, and you can return
49
- * a new list of collections.
50
- * @see {@link joinCollectionLists}
51
- * @param collections
52
- */
53
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
54
-
47
+ plugins?: FireCMSPlugin[];
55
48
  /**
56
49
  * If true, the navigation logic will not be updated until this flag is false
57
50
  */
@@ -68,9 +61,9 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
68
61
  views: viewsProp,
69
62
  adminViews: adminViewsProp,
70
63
  viewsOrder,
64
+ plugins,
71
65
  userConfigPersistence,
72
66
  dataSourceDelegate,
73
- injectCollections,
74
67
  disabled
75
68
  } = props;
76
69
 
@@ -203,7 +196,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
203
196
  try {
204
197
 
205
198
  const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([
206
- resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, injectCollections),
199
+ resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins),
207
200
  resolveCMSViews(viewsProp, authController, dataSourceDelegate),
208
201
  resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
209
202
  ]
@@ -250,8 +243,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
250
243
  disabled,
251
244
  viewsProp,
252
245
  adminViewsProp,
253
- computeTopNavigation,
254
- injectCollections
246
+ computeTopNavigation
255
247
  ]);
256
248
 
257
249
  useEffect(() => {
@@ -447,11 +439,24 @@ function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[],
447
439
  });
448
440
  }
449
441
 
442
+ function applyPluginModifyCollection(resolvedCollections: EntityCollection[], modifyCollection: (collection: EntityCollection) => EntityCollection) {
443
+ return resolvedCollections.map((collection: EntityCollection): EntityCollection => {
444
+ const modifiedCollection = modifyCollection(collection);
445
+ if (modifiedCollection.subcollections) {
446
+ return {
447
+ ...modifiedCollection,
448
+ subcollections: applyPluginModifyCollection(modifiedCollection.subcollections, modifyCollection)
449
+ } satisfies EntityCollection;
450
+ }
451
+ return modifiedCollection;
452
+ });
453
+ }
454
+
450
455
  async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
451
456
  collectionPermissions: PermissionsBuilder | undefined,
452
457
  authController: AuthController,
453
458
  dataSource: DataSourceDelegate,
454
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[]): Promise<EntityCollection[]> {
459
+ plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
455
460
  let resolvedCollections: EntityCollection[] = [];
456
461
  if (typeof collections === "function") {
457
462
  resolvedCollections = await collections({
@@ -463,8 +468,16 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
463
468
  resolvedCollections = collections;
464
469
  }
465
470
 
466
- if (injectCollections) {
467
- resolvedCollections = injectCollections(resolvedCollections ?? []);
471
+ if (plugins) {
472
+ for (const plugin of plugins) {
473
+ if (plugin.collection?.modifyCollection) {
474
+ resolvedCollections = applyPluginModifyCollection(resolvedCollections, plugin.collection.modifyCollection);
475
+ }
476
+
477
+ if (plugin.collection?.injectCollections) {
478
+ resolvedCollections = plugin.collection.injectCollections(resolvedCollections ?? []);
479
+ }
480
+ }
468
481
  }
469
482
 
470
483
  resolvedCollections = applyPermissionsFunctionIfEmpty(resolvedCollections, collectionPermissions);
@@ -33,7 +33,7 @@ export function getEntityViewWidth(props: EntitySidePanelProps<any>, small: bool
33
33
  selectedSecondaryForm
34
34
  } = resolvedSelectedEntityView(props.collection?.entityViews, customizationController, props.selectedTab);
35
35
 
36
- const shouldUseSmallLayout = !props.selectedTab || props.selectedTab === JSON_TAB_VALUE || Boolean(selectedSecondaryForm);
36
+ const shouldUseSmallLayout = !props.selectedTab || props.selectedTab === JSON_TAB_VALUE || props.selectedTab === "__history" || Boolean(selectedSecondaryForm);
37
37
 
38
38
  let resolvedWidth: string | undefined;
39
39
  if (props.width) {
@@ -141,7 +141,7 @@ function ReferencePreviewExisting<M extends Record<string, any> = any>({
141
141
  );
142
142
  }
143
143
  return <EntityPreview size={size}
144
- previewProperties={previewProperties}
144
+ previewKeys={previewProperties}
145
145
  disabled={disabled}
146
146
  entity={usedEntity}
147
147
  collection={collection}
@@ -37,7 +37,7 @@ export function ArrayOfMapsPreview({
37
37
  throw Error(`You need to specify a 'properties' prop (or specify a custom field) in your map property ${propertyKey}`);
38
38
  }
39
39
  const values = value;
40
- const previewProperties: string[] | undefined = mapProperty.previewProperties;
40
+ const previewProperties: string[] | undefined = mapProperty.previewKeys;
41
41
 
42
42
  if (!values) return null;
43
43
 
@@ -46,7 +46,7 @@ export function SkeletonPropertyComponent({
46
46
  content = <>{arrayProperty.of.map((p, i) => renderGenericArrayCell(p, i))} </>;
47
47
  } else {
48
48
  if (arrayProperty.of.dataType === "map" && arrayProperty.of.properties) {
49
- content = renderArrayOfMaps(arrayProperty.of.properties, size, arrayProperty.of.previewProperties);
49
+ content = renderArrayOfMaps(arrayProperty.of.properties, size, arrayProperty.of.previewKeys);
50
50
  } else if (arrayProperty.of.dataType === "string") {
51
51
  if (arrayProperty.of.enumValues) {
52
52
  content = renderArrayEnumTableCell();
@@ -337,6 +337,12 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
337
337
  * If set to true, a tab including the JSON representation of the entity will be included.
338
338
  */
339
339
  includeJsonView?: boolean;
340
+
341
+ /**
342
+ * If set to true, changes to the entity will be saved in a subcollection.
343
+ * This prop has no effect if the history plugin is not enabled
344
+ */
345
+ history?: boolean;
340
346
  }
341
347
 
342
348
  /**
@@ -519,6 +525,11 @@ export type EntityCustomView<M extends Record<string, any> = any> =
519
525
  */
520
526
  name: string;
521
527
 
528
+ /**
529
+ * Render this custom view in the tab of the entity view, instead of the name
530
+ */
531
+ tabComponent?: React.ReactNode;
532
+
522
533
  /**
523
534
  * If set to true, the actions of the entity (save, discard,delete) will be
524
535
  * included in the view. By default the actions are located in the right or bottom,
@@ -531,6 +542,11 @@ export type EntityCustomView<M extends Record<string, any> = any> =
531
542
  * Builder for rendering the custom view
532
543
  */
533
544
  Builder?: React.ComponentType<EntityCustomViewParams<M>>;
545
+
546
+ /**
547
+ * Position of this tab in the entity view. Defaults to `end`.
548
+ */
549
+ position?: "start" | "end";
534
550
  };
535
551
 
536
552
  /**
@@ -92,7 +92,6 @@ export type FireCMSProps<USER extends User> = {
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
  /**
@@ -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
  /**
@@ -20,11 +20,6 @@ export interface EntitySidePanelProps<M extends Record<string, any> = any> {
20
20
  */
21
21
  entityId?: string;
22
22
 
23
- // /**
24
- // * Navigation path with ids of the entity
25
- // */
26
- // fullIdPath: string;
27
-
28
23
  /**
29
24
  * Set this flag to true if you want to make a copy of an existing entity
30
25
  */
@@ -76,6 +71,11 @@ export interface EntitySidePanelProps<M extends Record<string, any> = any> {
76
71
  * Override some form properties
77
72
  */
78
73
  formProps?: Partial<EntityFormProps<M>>;
74
+
75
+ /**
76
+ * Allow the user to open the entity fullscreen
77
+ */
78
+ allowFullScreen?: boolean;
79
79
  }
80
80
 
81
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
+ };
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";