@firecms/core 3.0.1 → 3.1.0-canary.768c91f

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 (185) 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/EntityBoardCard.d.ts +26 -0
  15. package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
  16. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
  17. package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
  18. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
  19. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
  20. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
  21. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +44 -0
  22. package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
  23. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
  24. package/dist/components/ErrorBoundary.d.ts +1 -1
  25. package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
  26. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
  27. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  28. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +3 -1
  29. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  30. package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
  31. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  32. package/dist/components/VirtualTable/types.d.ts +2 -0
  33. package/dist/components/index.d.ts +3 -0
  34. package/dist/contexts/index.d.ts +10 -0
  35. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  36. package/dist/core/index.d.ts +1 -0
  37. package/dist/form/components/ErrorFocus.d.ts +1 -1
  38. package/dist/form/validation.d.ts +3 -2
  39. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  40. package/dist/hooks/useCollapsedGroups.d.ts +4 -1
  41. package/dist/index.es.js +5266 -1578
  42. package/dist/index.es.js.map +1 -1
  43. package/dist/index.umd.js +5260 -1573
  44. package/dist/index.umd.js.map +1 -1
  45. package/dist/internal/useRestoreScroll.d.ts +1 -1
  46. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  47. package/dist/preview/components/DatePreview.d.ts +13 -3
  48. package/dist/preview/components/ImagePreview.d.ts +5 -1
  49. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  50. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  51. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  52. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  53. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  54. package/dist/types/analytics.d.ts +1 -1
  55. package/dist/types/collections.d.ts +50 -2
  56. package/dist/types/datasource.d.ts +0 -1
  57. package/dist/types/plugins.d.ts +62 -1
  58. package/dist/types/properties.d.ts +259 -4
  59. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  60. package/dist/util/__tests__/objects.test.d.ts +1 -0
  61. package/dist/util/conditions.d.ts +26 -0
  62. package/dist/util/entities.d.ts +2 -3
  63. package/dist/util/index.d.ts +2 -1
  64. package/dist/util/property_utils.d.ts +2 -1
  65. package/dist/util/resolutions.d.ts +3 -3
  66. package/package.json +14 -11
  67. package/src/app/Scaffold.tsx +14 -15
  68. package/src/components/AIIcon.tsx +39 -0
  69. package/src/components/ArrayContainer.tsx +1 -4
  70. package/src/components/ClearFilterSortButton.tsx +19 -16
  71. package/src/components/ConfirmationDialog.tsx +0 -2
  72. package/src/components/DeleteEntityDialog.tsx +2 -4
  73. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
  74. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  75. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  76. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  77. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
  78. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  79. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
  80. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
  81. package/src/components/EntityCollectionView/Board.tsx +324 -0
  82. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  83. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  84. package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
  85. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  86. package/src/components/EntityCollectionView/EntityCard.tsx +235 -0
  87. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +733 -0
  88. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
  89. package/src/components/EntityCollectionView/EntityCollectionView.tsx +519 -203
  90. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
  91. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
  92. package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
  93. package/src/components/EntityCollectionView/ViewModeToggle.tsx +199 -0
  94. package/src/components/EntityCollectionView/board_types.ts +113 -0
  95. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  96. package/src/components/ErrorTooltip.tsx +2 -1
  97. package/src/components/HomePage/DefaultHomePage.tsx +47 -10
  98. package/src/components/HomePage/HomePageDnD.tsx +56 -41
  99. package/src/components/HomePage/NavigationCard.tsx +20 -18
  100. package/src/components/HomePage/NavigationGroup.tsx +17 -16
  101. package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
  102. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  103. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
  104. package/src/components/ReferenceWidget.tsx +2 -4
  105. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  106. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  107. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
  108. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
  109. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
  110. package/src/components/UnsavedChangesDialog.tsx +0 -2
  111. package/src/components/UserDisplay.tsx +4 -4
  112. package/src/components/VirtualTable/VirtualTable.tsx +272 -118
  113. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  114. package/src/components/VirtualTable/VirtualTableHeader.tsx +59 -50
  115. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
  116. package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
  117. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  118. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  119. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +19 -6
  120. package/src/components/VirtualTable/types.tsx +2 -0
  121. package/src/components/common/useColumnsIds.tsx +95 -3
  122. package/src/components/index.tsx +4 -0
  123. package/src/contexts/BreacrumbsContext.tsx +15 -8
  124. package/src/contexts/index.ts +10 -0
  125. package/src/core/DefaultAppBar.tsx +40 -27
  126. package/src/core/DefaultDrawer.tsx +42 -56
  127. package/src/core/DrawerNavigationGroup.tsx +118 -0
  128. package/src/core/DrawerNavigationItem.tsx +4 -3
  129. package/src/core/EntityEditView.tsx +41 -43
  130. package/src/core/EntitySidePanel.tsx +28 -26
  131. package/src/core/SideDialogs.tsx +4 -2
  132. package/src/core/field_configs.tsx +14 -9
  133. package/src/core/index.tsx +1 -0
  134. package/src/form/EntityForm.tsx +69 -60
  135. package/src/form/PropertyFieldBinding.tsx +61 -46
  136. package/src/form/components/ErrorFocus.tsx +3 -3
  137. package/src/form/components/StorageItemPreview.tsx +2 -1
  138. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
  139. package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
  140. package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
  141. package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
  142. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +22 -18
  143. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +83 -83
  144. package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
  145. package/src/form/validation.ts +245 -160
  146. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  147. package/src/hooks/useBuildNavigationController.tsx +46 -23
  148. package/src/hooks/useCollapsedGroups.ts +12 -4
  149. package/src/hooks/useValidateAuthenticator.tsx +1 -1
  150. package/src/internal/useBuildDataSource.ts +68 -34
  151. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  152. package/src/internal/useBuildSideEntityController.tsx +2 -4
  153. package/src/internal/useRestoreScroll.tsx +26 -14
  154. package/src/preview/PropertyPreview.tsx +41 -32
  155. package/src/preview/PropertyPreviewProps.tsx +6 -0
  156. package/src/preview/components/DatePreview.tsx +72 -4
  157. package/src/preview/components/EmptyValue.tsx +1 -1
  158. package/src/preview/components/ImagePreview.tsx +37 -21
  159. package/src/preview/components/StorageThumbnail.tsx +16 -12
  160. package/src/preview/components/UrlComponentPreview.tsx +28 -25
  161. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  162. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  163. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  164. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  165. package/src/routes/CustomCMSRoute.tsx +1 -0
  166. package/src/routes/FireCMSRoute.tsx +26 -13
  167. package/src/types/analytics.ts +10 -0
  168. package/src/types/collections.ts +57 -3
  169. package/src/types/datasource.ts +54 -56
  170. package/src/types/plugins.tsx +69 -1
  171. package/src/types/properties.ts +347 -27
  172. package/src/util/__tests__/conditions.test.ts +506 -0
  173. package/src/util/__tests__/objects.test.ts +196 -0
  174. package/src/util/callbacks.ts +6 -3
  175. package/src/util/collections.ts +51 -6
  176. package/src/util/conditions.ts +339 -0
  177. package/src/util/entities.ts +29 -30
  178. package/src/util/entity_cache.ts +2 -1
  179. package/src/util/index.ts +2 -1
  180. package/src/util/join_collections.ts +10 -8
  181. package/src/util/objects.ts +31 -13
  182. package/src/util/{references.ts → previews.ts} +16 -2
  183. package/src/util/property_utils.tsx +37 -11
  184. package/src/util/resolutions.ts +62 -58
  185. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -122,10 +122,10 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
122
122
 
123
123
  const navigate = useNavigate();
124
124
 
125
- const collectionsRef = useRef<EntityCollection[] | undefined>();
126
- const viewsRef = useRef<CMSView[] | undefined>();
127
- const adminViewsRef = useRef<CMSView[] | undefined>();
128
- const navigationEntriesOrderRef = useRef<string[] | undefined>();
125
+ const collectionsRef = useRef<EntityCollection[] | undefined>(undefined);
126
+ const viewsRef = useRef<CMSView[] | undefined>(undefined);
127
+ const adminViewsRef = useRef<CMSView[] | undefined>(undefined);
128
+ const navigationEntriesOrderRef = useRef<string[] | undefined>(undefined);
129
129
 
130
130
  const [initialised, setInitialised] = useState<boolean>(false);
131
131
 
@@ -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
+
@@ -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
 
@@ -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,56 @@ 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
+ if (orderProp) {
242
+ const currentValue = updatedValues[orderProperty as keyof M];
243
+ if (currentValue === undefined || currentValue === null) {
244
+ try {
245
+ const entities = await usedDelegate.fetchCollection({
246
+ path,
247
+ orderBy: orderProperty,
248
+ order: "asc",
249
+ limit: 1,
250
+ collection
251
+ });
252
+ const minOrder = entities.length > 0
253
+ ? entities[0].values?.[orderProperty] ?? null
254
+ : null;
255
+ finalValues = {
256
+ ...updatedValues,
257
+ [orderProperty]: minOrder !== null ? minOrder - 1 : 0
258
+ } as EntityValues<M>;
259
+ } catch (e) {
260
+ console.error("Failed to fetch min order value:", e);
261
+ // Fallback to 0 if query fails
262
+ finalValues = {
263
+ ...updatedValues,
264
+ [orderProperty]: 0
265
+ } as EntityValues<M>;
266
+ }
267
+ }
268
+ }
269
+ }
270
+
237
271
  return usedDelegate.saveEntity({
238
272
  path,
239
273
  collection,
240
274
  entityId,
241
- values: updatedValues,
275
+ values: finalValues,
242
276
  status
243
277
  }).then((res) => {
244
278
  return {
245
279
  id: res.id,
246
280
  path: res.path,
247
- values: usedDelegate.delegateToCMSModel(updatedValues)
281
+ values: usedDelegate.delegateToCMSModel(finalValues)
248
282
  } as Entity<M>;
249
283
  });
250
284
  }, [delegate.saveEntity, navigationController.getCollection]),
@@ -295,12 +329,12 @@ export function useBuildDataSource({
295
329
  }, [delegate.generateEntityId]),
296
330
 
297
331
  countEntities: delegate.countEntities ? async ({
298
- path,
299
- collection,
300
- filter,
301
- order,
302
- orderBy
303
- }: {
332
+ path,
333
+ collection,
334
+ filter,
335
+ order,
336
+ orderBy
337
+ }: {
304
338
  path: string,
305
339
  collection: EntityCollection<any>,
306
340
  filter?: FilterValues<Extract<keyof any, string>>,
@@ -318,11 +352,11 @@ export function useBuildDataSource({
318
352
  } : undefined,
319
353
 
320
354
  isFilterCombinationValid: useCallback(({
321
- path,
322
- databaseId,
323
- filterValues,
324
- sortBy
325
- }: {
355
+ path,
356
+ databaseId,
357
+ filterValues,
358
+ sortBy
359
+ }: {
326
360
  path: string,
327
361
  databaseId?: string,
328
362
  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,