@firecms/core 3.0.1 → 3.1.0-canary.1df3b2c

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 (170) 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/EntityCollectionView/Board.d.ts +2 -0
  10. package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
  11. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
  12. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
  13. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
  14. package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
  15. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
  16. package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
  17. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
  18. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
  19. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
  20. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +49 -0
  21. package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
  22. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
  23. package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
  24. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
  25. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  26. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -0
  27. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  28. package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
  29. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  30. package/dist/components/VirtualTable/types.d.ts +2 -0
  31. package/dist/components/index.d.ts +3 -0
  32. package/dist/contexts/index.d.ts +10 -0
  33. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  34. package/dist/core/index.d.ts +1 -0
  35. package/dist/form/validation.d.ts +3 -2
  36. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  37. package/dist/hooks/useCollapsedGroups.d.ts +4 -1
  38. package/dist/index.es.js +5239 -1590
  39. package/dist/index.es.js.map +1 -1
  40. package/dist/index.umd.js +5233 -1585
  41. package/dist/index.umd.js.map +1 -1
  42. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  43. package/dist/preview/components/DatePreview.d.ts +13 -3
  44. package/dist/preview/components/ImagePreview.d.ts +5 -1
  45. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  46. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  47. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  48. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  49. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  50. package/dist/types/collections.d.ts +42 -2
  51. package/dist/types/datasource.d.ts +0 -1
  52. package/dist/types/plugins.d.ts +46 -1
  53. package/dist/types/properties.d.ts +259 -4
  54. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  55. package/dist/util/__tests__/objects.test.d.ts +1 -0
  56. package/dist/util/conditions.d.ts +26 -0
  57. package/dist/util/entities.d.ts +1 -2
  58. package/dist/util/index.d.ts +2 -1
  59. package/dist/util/property_utils.d.ts +2 -1
  60. package/dist/util/resolutions.d.ts +1 -1
  61. package/package.json +10 -7
  62. package/src/app/Scaffold.tsx +14 -15
  63. package/src/components/AIIcon.tsx +39 -0
  64. package/src/components/ArrayContainer.tsx +1 -4
  65. package/src/components/ClearFilterSortButton.tsx +19 -16
  66. package/src/components/ConfirmationDialog.tsx +0 -2
  67. package/src/components/DeleteEntityDialog.tsx +2 -4
  68. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
  69. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  70. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  71. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  72. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
  73. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  74. package/src/components/EntityCollectionView/Board.tsx +324 -0
  75. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  76. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  77. package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
  78. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  79. package/src/components/EntityCollectionView/EntityCard.tsx +231 -0
  80. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +713 -0
  81. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
  82. package/src/components/EntityCollectionView/EntityCollectionView.tsx +485 -203
  83. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
  84. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
  85. package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
  86. package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
  87. package/src/components/EntityCollectionView/board_types.ts +113 -0
  88. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  89. package/src/components/ErrorTooltip.tsx +2 -1
  90. package/src/components/HomePage/DefaultHomePage.tsx +47 -10
  91. package/src/components/HomePage/HomePageDnD.tsx +56 -41
  92. package/src/components/HomePage/NavigationCard.tsx +20 -18
  93. package/src/components/HomePage/NavigationGroup.tsx +17 -16
  94. package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
  95. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  96. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
  97. package/src/components/ReferenceWidget.tsx +2 -4
  98. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  99. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  100. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
  101. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
  102. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
  103. package/src/components/UnsavedChangesDialog.tsx +0 -2
  104. package/src/components/UserDisplay.tsx +4 -4
  105. package/src/components/VirtualTable/VirtualTable.tsx +170 -19
  106. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  107. package/src/components/VirtualTable/VirtualTableHeader.tsx +20 -11
  108. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
  109. package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
  110. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  111. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  112. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +17 -4
  113. package/src/components/VirtualTable/types.tsx +2 -0
  114. package/src/components/common/useColumnsIds.tsx +95 -3
  115. package/src/components/index.tsx +4 -0
  116. package/src/contexts/BreacrumbsContext.tsx +15 -8
  117. package/src/contexts/index.ts +10 -0
  118. package/src/core/DefaultAppBar.tsx +39 -26
  119. package/src/core/DefaultDrawer.tsx +42 -56
  120. package/src/core/DrawerNavigationGroup.tsx +118 -0
  121. package/src/core/DrawerNavigationItem.tsx +4 -3
  122. package/src/core/EntityEditView.tsx +41 -43
  123. package/src/core/SideDialogs.tsx +4 -2
  124. package/src/core/index.tsx +1 -0
  125. package/src/form/PropertyFieldBinding.tsx +58 -43
  126. package/src/form/components/StorageItemPreview.tsx +2 -1
  127. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
  128. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
  129. package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
  130. package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
  131. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +21 -17
  132. package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
  133. package/src/form/validation.ts +245 -160
  134. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  135. package/src/hooks/useBuildNavigationController.tsx +42 -19
  136. package/src/hooks/useCollapsedGroups.ts +12 -4
  137. package/src/internal/useBuildDataSource.ts +69 -34
  138. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  139. package/src/internal/useBuildSideEntityController.tsx +2 -4
  140. package/src/internal/useRestoreScroll.tsx +26 -14
  141. package/src/preview/PropertyPreview.tsx +40 -32
  142. package/src/preview/PropertyPreviewProps.tsx +6 -0
  143. package/src/preview/components/DatePreview.tsx +72 -4
  144. package/src/preview/components/EmptyValue.tsx +1 -1
  145. package/src/preview/components/ImagePreview.tsx +37 -21
  146. package/src/preview/components/StorageThumbnail.tsx +16 -12
  147. package/src/preview/components/UrlComponentPreview.tsx +28 -25
  148. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  149. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  150. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  151. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  152. package/src/routes/CustomCMSRoute.tsx +1 -0
  153. package/src/routes/FireCMSRoute.tsx +26 -13
  154. package/src/types/collections.ts +48 -3
  155. package/src/types/datasource.ts +54 -56
  156. package/src/types/plugins.tsx +51 -1
  157. package/src/types/properties.ts +347 -27
  158. package/src/util/__tests__/conditions.test.ts +506 -0
  159. package/src/util/__tests__/objects.test.ts +196 -0
  160. package/src/util/callbacks.ts +6 -3
  161. package/src/util/collections.ts +51 -6
  162. package/src/util/conditions.ts +339 -0
  163. package/src/util/entities.ts +28 -29
  164. package/src/util/entity_cache.ts +2 -1
  165. package/src/util/index.ts +2 -1
  166. package/src/util/objects.ts +31 -13
  167. package/src/util/{references.ts → previews.ts} +14 -0
  168. package/src/util/property_utils.tsx +36 -10
  169. package/src/util/resolutions.ts +57 -55
  170. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -140,8 +140,12 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
140
140
 
141
141
  const fullCollectionPath = cleanBasePath ? `/${cleanBasePath}/${cleanBaseCollectionPath}` : `/${cleanBaseCollectionPath}`;
142
142
 
143
- const buildCMSUrlPath = useCallback((path: string): string => cleanBasePath ? `/${cleanBasePath}/${encodePath(path)}` : `/${encodePath(path)}`,
144
- [cleanBasePath]);
143
+
144
+ const buildCMSUrlPath = useCallback((path: string): string => {
145
+ // Strip trailing /* wildcard from paths (used for nested routes in React Router)
146
+ const cleanPath = path.replace(/\/\*$/, "");
147
+ return cleanBasePath ? `/${cleanBasePath}/${encodePath(cleanPath)}` : `/${encodePath(cleanPath)}`;
148
+ }, [cleanBasePath]);
145
149
 
146
150
  const buildUrlCollectionPath = useCallback((path: string): string => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path)}`,
147
151
  [baseCollectionPath]);
@@ -331,10 +335,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
331
335
  try {
332
336
 
333
337
  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
- ]
338
+ resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins),
339
+ resolveCMSViews(viewsProp, authController, dataSourceDelegate, plugins),
340
+ resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)
341
+ ]
338
342
  );
339
343
 
340
344
  const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder, undefined, onNavigationEntriesOrderUpdate);
@@ -597,10 +601,10 @@ function applyPluginModifyCollection(resolvedCollections: EntityCollection[], mo
597
601
  }
598
602
 
599
603
  async function resolveCollections(collections: undefined | EntityCollection[] | EntityCollectionsBuilder<any>,
600
- collectionPermissions: PermissionsBuilder | undefined,
601
- authController: AuthController,
602
- dataSource: DataSourceDelegate,
603
- plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
604
+ collectionPermissions: PermissionsBuilder | undefined,
605
+ authController: AuthController,
606
+ dataSource: DataSourceDelegate,
607
+ plugins: FireCMSPlugin[] | undefined): Promise<EntityCollection[]> {
604
608
  let resolvedCollections: EntityCollection[] = [];
605
609
  if (typeof collections === "function") {
606
610
  resolvedCollections = await collections({
@@ -629,7 +633,12 @@ async function resolveCollections(collections: undefined | EntityCollection[] |
629
633
  return resolvedCollections;
630
634
  }
631
635
 
632
- async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefined, authController: AuthController, dataSource: DataSourceDelegate) {
636
+ async function resolveCMSViews(
637
+ baseViews: CMSView[] | CMSViewsBuilder | undefined,
638
+ authController: AuthController,
639
+ dataSource: DataSourceDelegate,
640
+ plugins?: FireCMSPlugin[]
641
+ ) {
633
642
  let resolvedViews: CMSView[] = [];
634
643
  if (typeof baseViews === "function") {
635
644
  resolvedViews = await baseViews({
@@ -640,6 +649,16 @@ async function resolveCMSViews(baseViews: CMSView[] | CMSViewsBuilder | undefine
640
649
  } else if (Array.isArray(baseViews)) {
641
650
  resolvedViews = baseViews;
642
651
  }
652
+
653
+ // Inject views from plugins
654
+ if (plugins) {
655
+ for (const plugin of plugins) {
656
+ if (plugin.views && plugin.views.length > 0) {
657
+ resolvedViews = [...resolvedViews, ...plugin.views];
658
+ }
659
+ }
660
+ }
661
+
643
662
  return resolvedViews;
644
663
  }
645
664
 
@@ -688,8 +707,8 @@ function useCustomBlocker(): NavigationBlocker {
688
707
  let blocker: any;
689
708
  try {
690
709
  blocker = useBlocker(({
691
- nextLocation
692
- }) => {
710
+ nextLocation
711
+ }) => {
693
712
  const allBasePaths = Object.values(blockListeners).map(b => b.basePath).filter(Boolean) as string[];
694
713
  if (allBasePaths && allBasePaths.some(path => nextLocation.pathname.startsWith(path)))
695
714
  return false;
@@ -729,11 +748,11 @@ function useCustomBlocker(): NavigationBlocker {
729
748
  }
730
749
 
731
750
  function computeNavigationGroups({
732
- navigationGroupMappings,
733
- collections,
734
- views,
735
- plugins
736
- }: {
751
+ navigationGroupMappings,
752
+ collections,
753
+ views,
754
+ plugins
755
+ }: {
737
756
  navigationGroupMappings?: NavigationGroupMapping[],
738
757
  collections?: EntityCollection[],
739
758
  views?: CMSView[],
@@ -743,6 +762,7 @@ function computeNavigationGroups({
743
762
  let result = navigationGroupMappings;
744
763
 
745
764
  // Merge plugin navigation entries
765
+ // IMPORTANT: Deep clone the groups to avoid mutating the original input
746
766
  result = plugins ? plugins?.reduce((acc, plugin) => {
747
767
  if (plugin.homePage?.navigationEntries) {
748
768
  plugin.homePage.navigationEntries.forEach((entry) => {
@@ -763,7 +783,10 @@ function computeNavigationGroups({
763
783
 
764
784
  }
765
785
  return acc;
766
- }, [...(result ?? [])] as NavigationGroupMapping[]) : result;
786
+ }, (result ?? []).map(g => ({
787
+ name: g.name,
788
+ entries: [...g.entries]
789
+ }))) : result;
767
790
 
768
791
  // Track all entries that are already assigned to groups
769
792
  const assignedEntries = new Set<string>();
@@ -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[], 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,11 +26,11 @@ 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(() => {
@@ -62,3 +69,4 @@ export function useCollapsedGroups(groupNames: string[]) {
62
69
  toggleGroupCollapsed
63
70
  };
64
71
  }
72
+
@@ -26,11 +26,11 @@ import { resolveCollection, updateDateAutoValues } from "../util";
26
26
  * @group Firebase
27
27
  */
28
28
  export function useBuildDataSource({
29
- delegate,
30
- propertyConfigs,
31
- navigationController,
32
- authController
33
- }: {
29
+ delegate,
30
+ propertyConfigs,
31
+ navigationController,
32
+ authController
33
+ }: {
34
34
  delegate: DataSourceDelegate,
35
35
  propertyConfigs?: Record<string, PropertyConfig>;
36
36
  navigationController: NavigationController;
@@ -54,15 +54,15 @@ export function useBuildDataSource({
54
54
  * @group Firestore
55
55
  */
56
56
  fetchCollection: useCallback(<M extends Record<string, any>>({
57
- path,
58
- collection,
59
- filter,
60
- limit,
61
- startAfter,
62
- searchString,
63
- orderBy,
64
- order,
65
- }: FetchCollectionProps<M>
57
+ path,
58
+ collection,
59
+ filter,
60
+ limit,
61
+ startAfter,
62
+ searchString,
63
+ orderBy,
64
+ order,
65
+ }: FetchCollectionProps<M>
66
66
  ): Promise<Entity<M>[]> => {
67
67
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
68
68
  return usedDelegate.fetchCollection<M>({
@@ -138,10 +138,10 @@ export function useBuildDataSource({
138
138
  * @group Firestore
139
139
  */
140
140
  fetchEntity: useCallback(<M extends Record<string, any>>({
141
- path,
142
- entityId,
143
- collection
144
- }: FetchEntityProps<M>
141
+ path,
142
+ entityId,
143
+ collection
144
+ }: FetchEntityProps<M>
145
145
  ): Promise<Entity<M> | undefined> => {
146
146
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
147
147
  return usedDelegate.fetchEntity({
@@ -195,7 +195,7 @@ export function useBuildDataSource({
195
195
  * @param status
196
196
  * @group Firestore
197
197
  */
198
- saveEntity: useCallback(<M extends Record<string, any>>(
198
+ saveEntity: useCallback(async <M extends Record<string, any>>(
199
199
  {
200
200
  path,
201
201
  entityId,
@@ -229,22 +229,57 @@ export function useBuildDataSource({
229
229
  inputValues: delegateValues,
230
230
  properties,
231
231
  status,
232
- timestampNowValue: usedDelegate.currentTime?.() ?? new Date(),
233
- setDateToMidnight: usedDelegate.setDateToMidnight
232
+ timestampNowValue: usedDelegate.currentTime?.() ?? new Date()
234
233
  })
235
234
  : delegateValues;
236
235
 
236
+ // Auto-assign order property value for new/copy entities
237
+ let finalValues = updatedValues;
238
+ const orderProperty = collection?.orderProperty;
239
+ if (orderProperty && (status === "new" || status === "copy")) {
240
+ const orderProp = properties?.[orderProperty as keyof M];
241
+ // Only auto-assign if property is disabled (automatic mode)
242
+ if (orderProp?.disabled === true) {
243
+ const currentValue = updatedValues[orderProperty as keyof M];
244
+ if (currentValue === undefined || currentValue === null) {
245
+ try {
246
+ const entities = await usedDelegate.fetchCollection({
247
+ path,
248
+ orderBy: orderProperty,
249
+ order: "asc",
250
+ limit: 1,
251
+ collection
252
+ });
253
+ const minOrder = entities.length > 0
254
+ ? entities[0].values?.[orderProperty] ?? null
255
+ : null;
256
+ finalValues = {
257
+ ...updatedValues,
258
+ [orderProperty]: minOrder !== null ? minOrder - 1 : 0
259
+ } as EntityValues<M>;
260
+ } catch (e) {
261
+ console.error("Failed to fetch min order value:", e);
262
+ // Fallback to 0 if query fails
263
+ finalValues = {
264
+ ...updatedValues,
265
+ [orderProperty]: 0
266
+ } as EntityValues<M>;
267
+ }
268
+ }
269
+ }
270
+ }
271
+
237
272
  return usedDelegate.saveEntity({
238
273
  path,
239
274
  collection,
240
275
  entityId,
241
- values: updatedValues,
276
+ values: finalValues,
242
277
  status
243
278
  }).then((res) => {
244
279
  return {
245
280
  id: res.id,
246
281
  path: res.path,
247
- values: usedDelegate.delegateToCMSModel(updatedValues)
282
+ values: usedDelegate.delegateToCMSModel(finalValues)
248
283
  } as Entity<M>;
249
284
  });
250
285
  }, [delegate.saveEntity, navigationController.getCollection]),
@@ -295,12 +330,12 @@ export function useBuildDataSource({
295
330
  }, [delegate.generateEntityId]),
296
331
 
297
332
  countEntities: delegate.countEntities ? async ({
298
- path,
299
- collection,
300
- filter,
301
- order,
302
- orderBy
303
- }: {
333
+ path,
334
+ collection,
335
+ filter,
336
+ order,
337
+ orderBy
338
+ }: {
304
339
  path: string,
305
340
  collection: EntityCollection<any>,
306
341
  filter?: FilterValues<Extract<keyof any, string>>,
@@ -318,11 +353,11 @@ export function useBuildDataSource({
318
353
  } : undefined,
319
354
 
320
355
  isFilterCombinationValid: useCallback(({
321
- path,
322
- databaseId,
323
- filterValues,
324
- sortBy
325
- }: {
356
+ path,
357
+ databaseId,
358
+ filterValues,
359
+ sortBy
360
+ }: {
326
361
  path: string,
327
362
  databaseId?: string,
328
363
  filterValues: FilterValues<any>,
@@ -32,11 +32,12 @@ export function useBuildSideDialogsController(): SideDialogsController {
32
32
 
33
33
  const close = useCallback(() => {
34
34
 
35
- if (sidePanels.length === 0)
35
+ const currentPanels = sidePanelsRef.current;
36
+ if (currentPanels.length === 0)
36
37
  return;
37
38
 
38
- const lastSidePanel = sidePanels[sidePanels.length - 1];
39
- const updatedPanels = [...sidePanels.slice(0, -1)];
39
+ const lastSidePanel = currentPanels[currentPanels.length - 1];
40
+ const updatedPanels = [...currentPanels.slice(0, -1)];
40
41
  updateSidePanels(updatedPanels);
41
42
 
42
43
  if (routesCount.current > 0) {
@@ -56,7 +57,7 @@ export function useBuildSideDialogsController(): SideDialogsController {
56
57
  }
57
58
  );
58
59
  }
59
- }, [sidePanels, navigate, location]);
60
+ }, [navigate, location]);
60
61
 
61
62
  const open = useCallback((panelProps: SideDialogPanelProps | SideDialogPanelProps[]) => {
62
63
 
@@ -69,7 +70,8 @@ export function useBuildSideDialogsController(): SideDialogsController {
69
70
 
70
71
  const baseLocation = (location.state as any)?.base_location ?? location;
71
72
 
72
- const updatedPanels = [...sidePanels, ...newPanels];
73
+ const currentPanels = sidePanelsRef.current;
74
+ const updatedPanels = [...currentPanels, ...newPanels];
73
75
  updateSidePanels(updatedPanels);
74
76
 
75
77
  newPanels.forEach((panel) => {
@@ -86,7 +88,7 @@ export function useBuildSideDialogsController(): SideDialogsController {
86
88
  }
87
89
  });
88
90
 
89
- }, [location, navigate, sidePanels]);
91
+ }, [location, navigate]);
90
92
 
91
93
  const replace = useCallback((panelProps: SideDialogPanelProps | SideDialogPanelProps[]) => {
92
94
 
@@ -97,7 +99,8 @@ export function useBuildSideDialogsController(): SideDialogsController {
97
99
 
98
100
  const baseLocation = (location.state as any)?.base_location ?? location;
99
101
 
100
- const updatedPanels = [...sidePanels.slice(0, -newPanels.length), ...newPanels];
102
+ const currentPanels = sidePanelsRef.current;
103
+ const updatedPanels = [...currentPanels.slice(0, -newPanels.length), ...newPanels];
101
104
  updateSidePanels(updatedPanels);
102
105
 
103
106
  newPanels.forEach((panel) => {
@@ -115,7 +118,7 @@ export function useBuildSideDialogsController(): SideDialogsController {
115
118
  }
116
119
  });
117
120
 
118
- }, [location, navigate, sidePanels]);
121
+ }, [location, navigate]);
119
122
 
120
123
  return {
121
124
  sidePanels,
@@ -20,7 +20,6 @@ import {
20
20
  } from "../util";
21
21
  import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, FORM_CONTAINER_WIDTH } from "./common";
22
22
  import { useCustomizationController, useLargeLayout } from "../hooks";
23
- import { EntitySidePanel } from "../core/EntitySidePanel";
24
23
  import { JSON_TAB_VALUE } from "../core/EntityEditView";
25
24
 
26
25
  const NEW_URL_HASH = "new_side";
@@ -215,7 +214,6 @@ export const useBuildSideEntityController = (navigation: NavigationController,
215
214
 
216
215
  export function buildSidePanelsFromUrl(path: string, collections: EntityCollection[], newFlag: boolean): EntitySidePanelProps<any>[] {
217
216
 
218
-
219
217
  const navigationViewsForPath: NavigationViewInternal<any>[] = getNavigationEntriesFromPath({
220
218
  path,
221
219
  collections
@@ -288,12 +286,12 @@ const propsToSidePanel = (props: EntitySidePanelProps,
288
286
  const entityViewWidth = getEntityViewWidth(props, smallLayout, customizationController, authController);
289
287
  return {
290
288
  key: `${props.path}/${props.entityId}`,
291
- component: <EntitySidePanel {...resolvedPanelProps}/>,
289
+ component: undefined, // Lazy render in SideDialogs for better performance
292
290
  urlPath: urlPath,
293
291
  parentUrlPath: buildUrlCollectionPath(collectionPath),
294
292
  width: entityViewWidth,
295
293
  onClose: props.onClose,
296
- additional: props
294
+ additional: resolvedPanelProps
297
295
  };
298
296
  }
299
297
 
@@ -1,24 +1,28 @@
1
- import React, { useCallback, useEffect } from "react";
1
+ import React, { useCallback, useEffect, useRef } from "react";
2
2
  import { useLocation } from "react-router-dom";
3
3
 
4
4
  const scrollsMap: Record<string, number> = {};
5
5
 
6
6
  export function useRestoreScroll() {
7
7
 
8
- // const scrollsMap = React.useRef<Record<string, number>>({});
9
-
10
8
  const location = useLocation();
11
9
 
12
- const containerRef = React.useRef<HTMLDivElement>(null);
10
+ const containerRef = useRef<HTMLDivElement>(null);
13
11
  const [scroll, setScroll] = React.useState(0);
14
12
  const [direction, setDirection] = React.useState<"up" | "down">("down");
15
13
 
14
+ // Use ref to track previous scroll for direction calculation
15
+ // This avoids recreating handleScroll on every scroll
16
+ const prevScrollRef = useRef(0);
17
+
16
18
  const handleScroll = useCallback(() => {
17
19
  if (!containerRef.current || !location.key) return;
18
- scrollsMap[location.key] = containerRef.current.scrollTop;
19
- setScroll(containerRef.current.scrollTop);
20
- setDirection(containerRef.current.scrollTop > scroll ? "down" : "up");
21
- }, [containerRef, location.key, scroll]);
20
+ const scrollTop = containerRef.current.scrollTop;
21
+ scrollsMap[location.key] = scrollTop;
22
+ setScroll(scrollTop);
23
+ setDirection(scrollTop > prevScrollRef.current ? "down" : "up");
24
+ prevScrollRef.current = scrollTop;
25
+ }, [location.key]);
22
26
 
23
27
  useEffect(() => {
24
28
  const container = containerRef.current;
@@ -29,16 +33,24 @@ export function useRestoreScroll() {
29
33
  if (container)
30
34
  container.removeEventListener("scroll", handleScroll);
31
35
  };
32
- }, [containerRef, handleScroll, location]);
36
+ }, [handleScroll]);
33
37
 
38
+ // Defer scroll restoration to next tick to allow async content to render
39
+ // This is necessary because DefaultHomePage content loads asynchronously
34
40
  useEffect(() => {
35
- if (!containerRef.current || !scrollsMap[location.key]) return;
36
- containerRef.current.scrollTo(
37
- {
38
- top: scrollsMap[location.key],
41
+ const savedScroll = scrollsMap[location.key];
42
+ if (!containerRef.current || !savedScroll) return;
43
+
44
+ const timeoutId = setTimeout(() => {
45
+ if (!containerRef.current) return;
46
+ containerRef.current.scrollTo({
47
+ top: savedScroll,
39
48
  behavior: "auto"
40
49
  });
41
- }, [location]);
50
+ }, 0);
51
+
52
+ return () => clearTimeout(timeoutId);
53
+ }, [location.key]);
42
54
 
43
55
  return {
44
56
  containerRef,