@rebasepro/admin 0.0.1-canary.eae7889 → 0.1.0

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 (197) hide show
  1. package/dist/{CollectionEditorDialog-B2M9lCyL.js → CollectionEditorDialog-MbvXGzEq.js} +42 -31
  2. package/dist/CollectionEditorDialog-MbvXGzEq.js.map +1 -0
  3. package/dist/{CollectionsStudioView-WG6soyfs.js → CollectionsStudioView-D9X6aiAr.js} +12 -12
  4. package/dist/CollectionsStudioView-D9X6aiAr.js.map +1 -0
  5. package/dist/{ContentHomePage-CDF_a6Lp.js → ContentHomePage-CfVB1eUo.js} +26 -26
  6. package/dist/ContentHomePage-CfVB1eUo.js.map +1 -0
  7. package/dist/{ExportCollectionAction-Dc0VOWMN.js → ExportCollectionAction-CUwJg4F9.js} +2 -2
  8. package/dist/{ExportCollectionAction-Dc0VOWMN.js.map → ExportCollectionAction-CUwJg4F9.js.map} +1 -1
  9. package/dist/{ImportCollectionAction-DpCagAOy.js → ImportCollectionAction-DGa_SF_8.js} +2 -2
  10. package/dist/{ImportCollectionAction-DpCagAOy.js.map → ImportCollectionAction-DGa_SF_8.js.map} +1 -1
  11. package/dist/{PropertyEditView-DS67DxoT.js → PropertyEditView-C4nlYmAc.js} +82 -104
  12. package/dist/PropertyEditView-C4nlYmAc.js.map +1 -0
  13. package/dist/{RolesView-CIuYBimF.js → RolesView-CNWxnR8e.js} +7 -5
  14. package/dist/RolesView-CNWxnR8e.js.map +1 -0
  15. package/dist/{UsersView-B5zelXnH.js → UsersView-YiTIcXkA.js} +14 -35
  16. package/dist/UsersView-YiTIcXkA.js.map +1 -0
  17. package/dist/collection_editor/ConfigControllerProvider.d.ts +0 -4
  18. package/dist/collection_editor/types/collection_editor_controller.d.ts +6 -3
  19. package/dist/collection_editor/types/config_controller.d.ts +14 -7
  20. package/dist/collection_editor/ui/AddKanbanColumnAction.d.ts +3 -2
  21. package/dist/collection_editor/ui/CollectionViewHeaderAction.d.ts +3 -2
  22. package/dist/collection_editor/ui/EditorCollectionAction.d.ts +1 -1
  23. package/dist/collection_editor/ui/EditorCollectionActionStart.d.ts +1 -1
  24. package/dist/collection_editor/ui/EditorEntityAction.d.ts +1 -1
  25. package/dist/collection_editor/ui/KanbanSetupAction.d.ts +3 -2
  26. package/dist/collection_editor/ui/PropertyAddColumnComponent.d.ts +3 -2
  27. package/dist/collection_editor/ui/collection_editor/CollectionDetailsForm.d.ts +3 -4
  28. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -3
  29. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -2
  30. package/dist/collection_editor/ui/collection_editor/SubcollectionsEditTab.d.ts +3 -2
  31. package/dist/collection_editor_ui.js +3 -3
  32. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  33. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +1 -1
  34. package/dist/components/EntityCollectionTable/column_utils.d.ts +2 -2
  35. package/dist/components/EntityCollectionTable/fields/TableMultipleRelationField.d.ts +1 -1
  36. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +1 -1
  37. package/dist/components/EntityCollectionTable/fields/TableRelationField.d.ts +1 -1
  38. package/dist/components/EntityCollectionTable/fields/TableRelationSelectorField.d.ts +2 -2
  39. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -1
  40. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +3 -2
  41. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +2 -1
  42. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +4 -2
  43. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +4 -2
  44. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -2
  45. package/dist/components/EntityCollectionView/SplitListView.d.ts +3 -2
  46. package/dist/components/EntityEditView.d.ts +9 -2
  47. package/dist/components/RebaseCMS.d.ts +1 -1
  48. package/dist/components/ReferenceTable/EntitySelectionTable.d.ts +2 -2
  49. package/dist/components/ReferenceWidget.d.ts +2 -2
  50. package/dist/components/RelationSelector.d.ts +1 -1
  51. package/dist/components/SelectableTable/SelectableTable.d.ts +2 -2
  52. package/dist/editor.js +2 -2
  53. package/dist/editor.js.map +1 -1
  54. package/dist/hooks/navigation/useNavigationRegistry.d.ts +2 -1
  55. package/dist/hooks/useEntityHistory.d.ts +1 -1
  56. package/dist/{index-CHxgwt6E.js → index-CtzpHzMQ.js} +11 -4
  57. package/dist/index-CtzpHzMQ.js.map +1 -0
  58. package/dist/{index-Dey5WJpO.js → index-DKlrVD1m.js} +3 -3
  59. package/dist/index-DKlrVD1m.js.map +1 -0
  60. package/dist/{index-CBhrgpR7.js → index-kHJXfLNI.js} +3 -3
  61. package/dist/index-kHJXfLNI.js.map +1 -0
  62. package/dist/index.js +79 -63
  63. package/dist/index.js.map +1 -1
  64. package/dist/{useEntityHistory-Dcj4zhGj.js → useEntityHistory-UVsSclfZ.js} +3 -1
  65. package/dist/useEntityHistory-UVsSclfZ.js.map +1 -0
  66. package/dist/util/navigation_utils.d.ts +10 -1
  67. package/dist/{util-BQ82ySL3.js → util-CwLmSpGp.js} +1653 -1257
  68. package/dist/util-CwLmSpGp.js.map +1 -0
  69. package/package.json +9 -17
  70. package/src/collection_editor/ConfigControllerProvider.tsx +19 -28
  71. package/src/collection_editor/types/collection_editor_controller.tsx +3 -3
  72. package/src/collection_editor/types/config_controller.tsx +7 -7
  73. package/src/collection_editor/ui/AddKanbanColumnAction.tsx +4 -4
  74. package/src/collection_editor/ui/CollectionViewHeaderAction.tsx +3 -3
  75. package/src/collection_editor/ui/EditorCollectionAction.tsx +3 -3
  76. package/src/collection_editor/ui/EditorCollectionActionStart.tsx +7 -7
  77. package/src/collection_editor/ui/EditorEntityAction.tsx +3 -3
  78. package/src/collection_editor/ui/HomePageEditorCollectionAction.tsx +4 -2
  79. package/src/collection_editor/ui/KanbanSetupAction.tsx +4 -3
  80. package/src/collection_editor/ui/MissingReferenceWidget.tsx +3 -2
  81. package/src/collection_editor/ui/NewCollectionButton.tsx +2 -1
  82. package/src/collection_editor/ui/NewCollectionCard.tsx +2 -1
  83. package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +3 -3
  84. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +5 -50
  85. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +12 -20
  86. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +1 -3
  87. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +17 -2
  88. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +3 -3
  89. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +2 -1
  90. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +18 -12
  91. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +1 -2
  92. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +1 -2
  93. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  94. package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +4 -4
  95. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +2 -2
  96. package/src/collection_editor/ui/collection_editor/properties/ReferencePropertyField.tsx +15 -49
  97. package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +4 -5
  98. package/src/collection_editor/ui/collection_editor/templates/pages_template.ts +1 -1
  99. package/src/collection_editor/ui/collection_editor/templates/products_template.ts +2 -2
  100. package/src/components/DefaultAppBar.tsx +2 -2
  101. package/src/components/DefaultDrawer.tsx +25 -17
  102. package/src/components/DrawerNavigationGroup.tsx +4 -4
  103. package/src/components/DrawerNavigationItem.tsx +6 -6
  104. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +4 -4
  105. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +1 -1
  106. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +4 -4
  107. package/src/components/EntityCollectionTable/column_utils.tsx +3 -3
  108. package/src/components/EntityCollectionTable/fields/TableMultipleRelationField.tsx +3 -3
  109. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +3 -3
  110. package/src/components/EntityCollectionTable/fields/TableRelationField.tsx +4 -4
  111. package/src/components/EntityCollectionTable/fields/TableRelationSelectorField.tsx +3 -3
  112. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +8 -2
  113. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +1 -1
  114. package/src/components/EntityCollectionTable/internal/common.tsx +5 -5
  115. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +1 -1
  116. package/src/components/EntityCollectionTable/table_bindings.tsx +45 -35
  117. package/src/components/EntityCollectionView/EntityBoardCard.tsx +18 -19
  118. package/src/components/EntityCollectionView/EntityCard.tsx +2 -2
  119. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +42 -14
  120. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +4 -3
  121. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +157 -54
  122. package/src/components/EntityCollectionView/EntityCollectionView.tsx +169 -75
  123. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +23 -13
  124. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +21 -12
  125. package/src/components/EntityCollectionView/FiltersDialog.tsx +7 -7
  126. package/src/components/EntityCollectionView/SplitListView.tsx +24 -8
  127. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +33 -5
  128. package/src/components/EntityEditView.tsx +85 -85
  129. package/src/components/EntitySidePanel.tsx +18 -10
  130. package/src/components/HomePage/ContentHomePage.tsx +24 -15
  131. package/src/components/HomePage/NavigationCard.tsx +4 -4
  132. package/src/components/HomePage/NavigationGroup.tsx +2 -2
  133. package/src/components/RebaseAuthGate.tsx +2 -0
  134. package/src/components/RebaseCMS.tsx +4 -3
  135. package/src/components/RebaseNavigation.tsx +8 -5
  136. package/src/components/ReferenceTable/EntitySelectionTable.tsx +4 -4
  137. package/src/components/ReferenceWidget.tsx +3 -3
  138. package/src/components/RelationSelector.tsx +33 -5
  139. package/src/components/SelectableTable/SelectableTable.tsx +6 -6
  140. package/src/components/UserSelector.tsx +1 -1
  141. package/src/components/admin/RolesView.tsx +10 -3
  142. package/src/components/admin/UsersView.tsx +13 -25
  143. package/src/components/app/Scaffold.tsx +4 -4
  144. package/src/components/field_configs.tsx +29 -32
  145. package/src/components/history/EntityHistoryView.tsx +12 -1
  146. package/src/editor/editor.tsx +2 -2
  147. package/src/form/EntityForm.tsx +5 -4
  148. package/src/form/PropertyFieldBinding.tsx +14 -10
  149. package/src/form/components/FieldHelperText.tsx +1 -1
  150. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +3 -3
  151. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +5 -5
  152. package/src/form/field_bindings/BlockFieldBinding.tsx +4 -4
  153. package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
  154. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  155. package/src/form/field_bindings/MapFieldBinding.tsx +7 -7
  156. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +1 -1
  157. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +3 -3
  158. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +7 -7
  159. package/src/form/field_bindings/ReferenceFieldBinding.tsx +2 -2
  160. package/src/form/field_bindings/RelationFieldBinding.tsx +4 -4
  161. package/src/form/field_bindings/RepeatFieldBinding.tsx +5 -5
  162. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
  163. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
  164. package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -1
  165. package/src/form/field_bindings/TextFieldBinding.tsx +7 -7
  166. package/src/form/field_bindings/UserSelectFieldBinding.tsx +1 -1
  167. package/src/form/useClearRestoreValue.tsx +1 -1
  168. package/src/form/validation.ts +1 -1
  169. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -1
  170. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -3
  171. package/src/hooks/navigation/useNavigationRegistry.ts +14 -3
  172. package/src/hooks/navigation/useResolvedViews.tsx +1 -3
  173. package/src/hooks/navigation/useTopLevelNavigation.ts +1 -1
  174. package/src/hooks/navigation/utils.ts +1 -1
  175. package/src/hooks/useEntityHistory.ts +7 -2
  176. package/src/preview/PropertyPreview.tsx +27 -23
  177. package/src/preview/components/StorageThumbnail.tsx +4 -1
  178. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -1
  179. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +1 -1
  180. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +1 -1
  181. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +3 -3
  182. package/src/preview/property_previews/StringPropertyPreview.tsx +3 -3
  183. package/src/routes/RebaseRoute.tsx +57 -11
  184. package/src/util/navigation_utils.ts +21 -2
  185. package/src/util/previews.ts +15 -6
  186. package/src/util/property_utils.tsx +3 -3
  187. package/dist/CollectionEditorDialog-B2M9lCyL.js.map +0 -1
  188. package/dist/CollectionsStudioView-WG6soyfs.js.map +0 -1
  189. package/dist/ContentHomePage-CDF_a6Lp.js.map +0 -1
  190. package/dist/PropertyEditView-DS67DxoT.js.map +0 -1
  191. package/dist/RolesView-CIuYBimF.js.map +0 -1
  192. package/dist/UsersView-B5zelXnH.js.map +0 -1
  193. package/dist/index-CBhrgpR7.js.map +0 -1
  194. package/dist/index-CHxgwt6E.js.map +0 -1
  195. package/dist/index-Dey5WJpO.js.map +0 -1
  196. package/dist/useEntityHistory-Dcj4zhGj.js.map +0 -1
  197. package/dist/util-BQ82ySL3.js.map +0 -1
@@ -6,6 +6,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
6
6
  import { deepEqual as equal } from "fast-equals"
7
7
 
8
8
  const EMPTY_ARRAY: any[] = [];
9
+ const DEFAULT_ENTITY_OPEN_MODE = "split";
9
10
 
10
11
  import { CollectionSize, Entity, EntityReference, EntityTableController, FilterValues, PartialEntityCollection, SaveEntityProps, ViewMode } from "@rebasepro/types";
11
12
  import {
@@ -29,7 +30,8 @@ import {
29
30
  useLargeLayout,
30
31
  usePermissions,
31
32
  useTranslation,
32
- useSlot
33
+ useSlot,
34
+ IconForView
33
35
  } from "@rebasepro/core";
34
36
  import { useUserConfigurationPersistence } from "@rebasepro/core";
35
37
  import { EntityCollectionViewActions } from "./EntityCollectionViewActions";
@@ -62,15 +64,15 @@ import { mergeDeep } from "@rebasepro/utils";
62
64
  import { useCollectionRegistryController, useUrlController, useSideEntityController, useCMSContext } from "../../index";
63
65
  import { useBreadcrumbsController } from "../../index";
64
66
 
65
- const DEFAULT_ENTITY_OPEN_MODE: "side_panel" | "full_screen" | "split" = "split";
66
-
67
67
  function getOpenEntityMode(
68
68
  viewMode: ViewMode,
69
69
  configuredMode?: "side_panel" | "full_screen" | "split"
70
70
  ): "side_panel" | "full_screen" | "split" {
71
71
  if (configuredMode) return configuredMode;
72
72
  if (viewMode === "kanban") return "side_panel";
73
- return DEFAULT_ENTITY_OPEN_MODE;
73
+ if (viewMode === "table" || viewMode === "cards") return "full_screen";
74
+ // "list" view defaults to split
75
+ return "split";
74
76
  }
75
77
 
76
78
  /**
@@ -89,7 +91,7 @@ export type EntityCollectionViewProps<M extends Record<string, unknown>> = {
89
91
  /**
90
92
  * If this is a subcollection, specify the parent collection ids.
91
93
  */
92
- parentCollectionIds?: string[];
94
+ parentCollectionSlugs?: string[], parentEntityIds?: string[];
93
95
  /**
94
96
  * Whether this is a subcollection or not.
95
97
  */
@@ -144,7 +146,7 @@ export const EntityCollectionView = React.memo(
144
146
  function EntityCollectionView<M extends Record<string, unknown>>({
145
147
  path: pathProp,
146
148
 
147
- parentCollectionIds,
149
+ parentCollectionSlugs, parentEntityIds,
148
150
  isSubCollection,
149
151
  className,
150
152
  updateUrl,
@@ -173,9 +175,10 @@ export const EntityCollectionView = React.memo(
173
175
  const scrollRestoration = useScrollRestoration();
174
176
 
175
177
  const collection = useMemo(() => {
178
+ const registryCollection = collectionRegistry.getCollection(path) || collectionProp;
176
179
  const userOverride = userConfigPersistence?.getCollectionConfig<M>(path);
177
- return (userOverride ? mergeDeep(collectionProp, userOverride) : collectionProp) as EntityCollection<M>;
178
- }, [collectionProp, path, userConfigPersistence?.getCollectionConfig]);
180
+ return (userOverride ? mergeDeep(registryCollection, userOverride) : registryCollection) as EntityCollection<M>;
181
+ }, [collectionProp, path, userConfigPersistence, collectionRegistry]);
179
182
 
180
183
  const collectionRef = React.useRef(collection);
181
184
  useEffect(() => {
@@ -371,6 +374,23 @@ export const EntityCollectionView = React.memo(
371
374
  })
372
375
  }, [path, sideEntityController]);
373
376
 
377
+ const openNewDocument = useCallback((defaultValues?: Record<string, unknown>) => {
378
+ const collection = collectionRef.current;
379
+ analyticsController.onAnalyticsEvent?.("new_entity_click", {
380
+ path: path
381
+ });
382
+ navigateToEntity({
383
+ openEntityMode,
384
+ collection,
385
+ entityId: undefined,
386
+ defaultValues,
387
+ path: path,
388
+ sideEntityController,
389
+ navigation: urlController,
390
+ onClose: unselectNavigatedEntity
391
+ });
392
+ }, [path, sideEntityController, openEntityMode, urlController, unselectNavigatedEntity]);
393
+
374
394
  const onMultipleDeleteClick = () => {
375
395
  analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", {
376
396
  path: path
@@ -399,14 +419,14 @@ export const EntityCollectionView = React.memo(
399
419
 
400
420
  const pluginAddColumnComponents = useSlot("collection.add-column", {
401
421
  path,
402
- parentCollectionIds: parentCollectionIds ?? EMPTY_ARRAY,
422
+ parentCollectionSlugs: parentCollectionSlugs ?? EMPTY_ARRAY, parentEntityIds: parentEntityIds ?? EMPTY_ARRAY,
403
423
  collection,
404
424
  tableController
405
425
  });
406
426
 
407
427
  const pluginToolbarWidgets = useSlot("collection.toolbar", {
408
428
  path,
409
- parentCollectionIds: parentCollectionIds ?? EMPTY_ARRAY,
429
+ parentCollectionSlugs: parentCollectionSlugs ?? EMPTY_ARRAY, parentEntityIds: parentEntityIds ?? EMPTY_ARRAY,
410
430
  collection: collection,
411
431
  tableController: tableController,
412
432
  selectionController: usedSelectionController
@@ -414,7 +434,7 @@ export const EntityCollectionView = React.memo(
414
434
 
415
435
  const pluginEmptyStates = useSlot("collection.empty-state", {
416
436
  path,
417
- parentCollectionIds: parentCollectionIds ?? EMPTY_ARRAY,
437
+ parentCollectionSlugs: parentCollectionSlugs ?? EMPTY_ARRAY, parentEntityIds: parentEntityIds ?? EMPTY_ARRAY,
418
438
  collection,
419
439
  canCreate: canCreateEntities,
420
440
  onNewClick
@@ -422,7 +442,7 @@ export const EntityCollectionView = React.memo(
422
442
 
423
443
  const pluginInsights = useSlot("collection.insights", {
424
444
  path,
425
- parentCollectionIds: parentCollectionIds ?? EMPTY_ARRAY,
445
+ parentCollectionSlugs: parentCollectionSlugs ?? EMPTY_ARRAY, parentEntityIds: parentEntityIds ?? EMPTY_ARRAY,
426
446
  collection
427
447
  });
428
448
 
@@ -714,9 +734,11 @@ export const EntityCollectionView = React.memo(
714
734
  }, [updateLastDeleteTimestamp, usedSelectionController]);
715
735
 
716
736
  // Update breadcrumb count when count changes
737
+ const updateCountRef = React.useRef(breadcrumbs.updateCount);
738
+ updateCountRef.current = breadcrumbs.updateCount;
717
739
  useEffect(() => {
718
- breadcrumbs.updateCount(path, docsCount);
719
- }, [docsCount, path, breadcrumbs.updateCount]);
740
+ updateCountRef.current(path, docsCount);
741
+ }, [docsCount, path]);
720
742
 
721
743
  // EntitiesCount fetches count and updates breadcrumb - no visual rendering needed here
722
744
  const countFetcher = <EntitiesCount
@@ -751,14 +773,14 @@ export const EntityCollectionView = React.memo(
751
773
  path,
752
774
  collection: collection as unknown as EntityCollection,
753
775
  tableController: tableController as unknown as EntityTableController,
754
- parentCollectionIds: parentCollectionIds ?? EMPTY_ARRAY
776
+ parentCollectionSlugs: parentCollectionSlugs ?? EMPTY_ARRAY, parentEntityIds: parentEntityIds ?? EMPTY_ARRAY
755
777
  };
756
778
  return <>{headerActionContributions.map((s, i) => (
757
779
  <ErrorBoundary key={`header_action_${propertyKey}_${i}`}>
758
780
  <s.Component {...headerSlotProps} {...(s.props ?? {})}/>
759
781
  </ErrorBoundary>
760
782
  ))}</>;
761
- }, [headerActionContributions, path, parentCollectionIds]);
783
+ }, [headerActionContributions, path, parentCollectionSlugs]);
762
784
 
763
785
  const addColumnComponentInternal = pluginAddColumnComponents.length > 0
764
786
  ? function () {
@@ -794,13 +816,13 @@ export const EntityCollectionView = React.memo(
794
816
  .forEach(plugin => {
795
817
  plugin.hooks!.onColumnsReorder!({
796
818
  fullPath: path,
797
- parentCollectionIds: parentCollectionIds ?? EMPTY_ARRAY,
819
+ parentCollectionSlugs: parentCollectionSlugs ?? EMPTY_ARRAY, parentEntityIds: parentEntityIds ?? EMPTY_ARRAY,
798
820
  collection,
799
821
  newPropertiesOrder
800
822
  });
801
823
  });
802
824
  }
803
- }, [collection, setLocalPropertiesOrder, customizationController, path, parentCollectionIds]);
825
+ }, [collection, setLocalPropertiesOrder, customizationController, path, parentCollectionSlugs]);
804
826
 
805
827
  // Popover open state managed at parent level to prevent closing when view changes
806
828
  const [viewModePopoverOpen, setViewModePopoverOpen] = useState(false);
@@ -825,7 +847,7 @@ export const EntityCollectionView = React.memo(
825
847
  const pluginErrorViews = useSlot("collection.error", {
826
848
  path,
827
849
  collection,
828
- parentCollectionIds,
850
+ parentCollectionSlugs, parentEntityIds,
829
851
  error: tableController.dataLoadingError as Error
830
852
  });
831
853
  const pluginErrorView = tableController.dataLoadingError && pluginErrorViews.length > 0
@@ -833,7 +855,8 @@ export const EntityCollectionView = React.memo(
833
855
  : null;
834
856
 
835
857
  // Shared empty state — plugin slot takes priority, then default
836
- const isFilteredOrSorted = tableController.filterValues !== undefined || tableController.sortBy !== undefined;
858
+ const isSearching = !!tableController.searchString;
859
+ const isFilteredOrSorted = tableController.filterValues !== undefined || tableController.sortBy !== undefined || isSearching;
837
860
  const emptyComponent = pluginEmptyStates.length > 0
838
861
  ? <>{pluginEmptyStates}</>
839
862
  : canCreateEntities && !isFilteredOrSorted
@@ -847,16 +870,21 @@ export const EntityCollectionView = React.memo(
847
870
  {t("create_your_first_entry")}
848
871
  </Button>
849
872
  </div>
850
- : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>;
873
+ : <Typography variant={"label"}>
874
+ {isSearching
875
+ ? t("no_results_search", { search: tableController.searchString ?? "" })
876
+ : t("no_results_filter_sort")}
877
+ </Typography>;
851
878
 
852
879
  const toolbarNode = (
853
880
  <CollectionTableToolbar
854
881
  compact={isCompact}
855
882
  loading={tableController.dataLoading}
856
883
  onTextSearch={tableController.setSearchString}
884
+ initialSearchText={tableController.searchString}
857
885
  viewModeToggle={viewModeToggleElement}
858
886
  actionsStart={<EntityCollectionViewStartActions
859
- parentCollectionIds={parentCollectionIds ?? EMPTY_ARRAY}
887
+ parentCollectionSlugs={parentCollectionSlugs ?? EMPTY_ARRAY} parentEntityIds={parentEntityIds ?? EMPTY_ARRAY}
860
888
  collection={collection}
861
889
  tableController={tableController}
862
890
  path={path}
@@ -864,14 +892,16 @@ export const EntityCollectionView = React.memo(
864
892
  selectionController={usedSelectionController}
865
893
  collectionEntitiesCount={docsCount ?? undefined}
866
894
  resolvedProperties={resolvedCollection.properties}
895
+ openNewDocument={openNewDocument}
867
896
  compact={isCompact}/>}
868
897
  actions={
869
898
  <EntityCollectionViewActions
870
- parentCollectionIds={parentCollectionIds ?? EMPTY_ARRAY}
899
+ parentCollectionSlugs={parentCollectionSlugs ?? EMPTY_ARRAY} parentEntityIds={parentEntityIds ?? EMPTY_ARRAY}
871
900
  collection={collection}
872
901
  tableController={tableController}
873
902
  onMultipleDeleteClick={onMultipleDeleteClick}
874
903
  onNewClick={onNewClick}
904
+ openNewDocument={openNewDocument}
875
905
  path={path}
876
906
  relativePath={collection.slug}
877
907
  selectionController={usedSelectionController}
@@ -889,7 +919,7 @@ export const EntityCollectionView = React.memo(
889
919
  collection={collection}
890
920
  tableController={tableController}
891
921
  fullPath={path}
892
- parentCollectionIds={parentCollectionIds}
922
+ parentCollectionSlugs={parentCollectionSlugs} parentEntityIds={parentEntityIds}
893
923
  columnProperty={selectedKanbanProperty}
894
924
  onEntityClick={onEntityClick}
895
925
  selectionController={usedSelectionController}
@@ -987,7 +1017,7 @@ export const EntityCollectionView = React.memo(
987
1017
  size={listSize}
988
1018
  emptyComponent={emptyComponent}
989
1019
  path={path}
990
- parentCollectionIds={parentCollectionIds}
1020
+ parentCollectionSlugs={parentCollectionSlugs} parentEntityIds={parentEntityIds}
991
1021
  selectedEntityId={selectedEntityIdProp}
992
1022
  selectedTab={selectedTabProp}
993
1023
  toolbar={toolbarNode}
@@ -1000,7 +1030,7 @@ export const EntityCollectionView = React.memo(
1000
1030
  className={cls(
1001
1031
  "flex flex-col w-full",
1002
1032
  selectedEntityIdProp === undefined
1003
- ? "max-w-6xl mx-auto px-3 md:px-4 lg-px-6 py-4"
1033
+ ? "max-w-6xl mx-auto px-3 md:px-4 lg:px-6 py-4"
1004
1034
  : ""
1005
1035
  )}
1006
1036
  >
@@ -1020,8 +1050,17 @@ export const EntityCollectionView = React.memo(
1020
1050
  </div>
1021
1051
  </div>
1022
1052
  {pluginInsights.length > 0 && (
1023
- <div className="flex-shrink-0">
1024
- {pluginInsights}
1053
+ <div
1054
+ className={cls(
1055
+ "grid transition-[grid-template-rows] duration-150 ease-out",
1056
+ selectedEntityIdProp === undefined
1057
+ ? "grid-rows-[1fr]"
1058
+ : "grid-rows-[0fr]"
1059
+ )}
1060
+ >
1061
+ <div className="overflow-hidden flex-shrink-0">
1062
+ {pluginInsights}
1063
+ </div>
1025
1064
  </div>
1026
1065
  )}
1027
1066
  <EntityCollectionListView
@@ -1042,15 +1081,44 @@ export const EntityCollectionView = React.memo(
1042
1081
  )}
1043
1082
  </SplitListView>
1044
1083
  ) : (
1045
- <>
1084
+ <div className="flex flex-col w-full h-full">
1046
1085
  {toolbarNode}
1047
- {pluginInsights.length > 0 && (viewMode === "list" || viewMode === "table") && (
1048
- <div className="px-3 md:px-4 lg:px-6 max-w-6xl mx-auto w-full flex-shrink-0">
1049
- {pluginInsights}
1050
- </div>
1051
- )}
1052
- {innerView}
1053
- </>
1086
+ <div className="flex-1 flex flex-col overflow-y-auto">
1087
+ {viewMode === "list" ? (
1088
+ <div
1089
+ className={cls(
1090
+ "flex flex-col w-full",
1091
+ selectedEntityIdProp === undefined
1092
+ ? "max-w-6xl mx-auto px-3 md:px-4 lg:px-6 py-4"
1093
+ : ""
1094
+ )}
1095
+ >
1096
+ <div
1097
+ className={cls(
1098
+ "grid transition-[grid-template-rows,transform,margin] duration-150 ease-out",
1099
+ selectedEntityIdProp === undefined
1100
+ ? "grid-rows-[1fr] translate-y-0 mt-12 mb-6"
1101
+ : "grid-rows-[0fr] -translate-y-2 mt-0 mb-0"
1102
+ )}
1103
+ >
1104
+ <div className="overflow-hidden flex items-center gap-4">
1105
+ <Typography gutterBottom variant="h4" className="grow mb-0" component="h4">
1106
+ {collection.name}
1107
+ </Typography>
1108
+ </div>
1109
+ </div>
1110
+ {pluginInsights.length > 0 && (
1111
+ <div className="flex-shrink-0">
1112
+ {pluginInsights}
1113
+ </div>
1114
+ )}
1115
+ {innerView}
1116
+ </div>
1117
+ ) : (
1118
+ innerView
1119
+ )}
1120
+ </div>
1121
+ </div>
1054
1122
  )}
1055
1123
 
1056
1124
  {popupCell && <PopupFormField
@@ -1082,7 +1150,7 @@ export const EntityCollectionView = React.memo(
1082
1150
  );
1083
1151
  }, (a, b) => {
1084
1152
  return equal(a.path, b.path) &&
1085
- equal(a.parentCollectionIds, b.parentCollectionIds) &&
1153
+ equal(a.parentCollectionSlugs, b.parentCollectionSlugs) && equal(a.parentEntityIds, b.parentEntityIds) &&
1086
1154
  equal(a.isSubCollection, b.isSubCollection) &&
1087
1155
  equal(a.className, b.className) &&
1088
1156
  equal(a.properties, b.properties) &&
@@ -1099,11 +1167,18 @@ export const EntityCollectionView = React.memo(
1099
1167
  equal(a.openEntityMode, b.openEntityMode) &&
1100
1168
  equal(a.exportable, b.exportable) &&
1101
1169
  equal(a.history, b.history) &&
1102
- equal(a.forceFilter, b.forceFilter) &&
1170
+ equal(a.fixedFilter, b.fixedFilter) &&
1103
1171
  equal(a.selectedEntityId, b.selectedEntityId) &&
1104
1172
  equal(a.selectedTab, b.selectedTab);
1105
1173
  }) as React.FunctionComponent<EntityCollectionViewProps<any>>
1106
1174
 
1175
+ /**
1176
+ * Inflight count request deduplication map.
1177
+ * Keyed by `path|filterKey|sortByProperty|sortDir` so that concurrent
1178
+ * callers (e.g. React StrictMode double-mount) share the same promise.
1179
+ */
1180
+ const inflightCountRequests = new Map<string, Promise<number>>();
1181
+
1107
1182
  function EntitiesCount({
1108
1183
  path,
1109
1184
  collection,
@@ -1119,52 +1194,71 @@ function EntitiesCount({
1119
1194
  }) {
1120
1195
 
1121
1196
  const dataClient = useData();
1122
- const navigation = useCollectionRegistryController();
1123
1197
 
1124
1198
  const sortByProperty = sortBy ? sortBy[0] : undefined;
1125
1199
  const currentSort = sortBy ? sortBy[1] : undefined;
1126
- // v4: use path directly instead of resolveIdsFrom
1127
- const resolvedPath = path;
1200
+
1201
+ // Use refs for values that should NOT trigger re-fetches
1202
+ const dataClientRef = React.useRef(dataClient);
1203
+ dataClientRef.current = dataClient;
1204
+ const onCountChangeRef = React.useRef(onCountChange);
1205
+ onCountChangeRef.current = onCountChange;
1206
+
1207
+ // Serialize filter to a stable string to avoid re-fetches on object identity changes
1208
+ const filterKey = React.useMemo(() => filter ? JSON.stringify(filter) : "", [filter]);
1128
1209
 
1129
1210
  useEffect(() => {
1130
- const accessor = dataClient.collection(resolvedPath);
1131
- if (accessor.count) {
1132
- if (onCountChange) onCountChange(null);
1133
-
1134
- // Convert filterValues to PostgREST where clause
1135
- const whereMap: Record<string, string> = {};
1136
- if (filter) {
1137
- Object.entries(filter).forEach(([key, value]) => {
1138
- if (value && Array.isArray(value)) {
1139
- const [op, val] = value;
1140
- const postgrestOp = op === "==" ? "eq" : op === "!=" ? "neq" : op === ">" ? "gt" : op === ">=" ? "gte" : op === "<" ? "lt" : op === "<=" ? "lte" : op === "in" ? "in" : op === "not-in" ? "nin" : op === "array-contains" ? "cs" : op === "array-contains-any" ? "csa" : "eq";
1141
-
1142
- let stringVal: string;
1143
- if (Array.isArray(val)) {
1144
- stringVal = `(${val.join(",")})`;
1145
- } else {
1146
- stringVal = String(val);
1147
- }
1148
- whereMap[key] = `${postgrestOp}.${stringVal}`;
1149
- }
1150
- });
1151
- }
1152
- const whereParams = Object.keys(whereMap).length > 0 ? whereMap : undefined;
1153
- const orderByParams = sortByProperty ? `${String(sortByProperty)}:${currentSort}` : undefined;
1211
+ const accessor = dataClientRef.current.collection(path);
1212
+ if (!accessor.count) {
1213
+ onCountChangeRef.current?.(undefined);
1214
+ return;
1215
+ }
1154
1216
 
1155
- accessor.count({
1217
+ let cancelled = false;
1218
+
1219
+ // Convert filterValues to PostgREST where clause
1220
+ const whereMap: Record<string, string> = {};
1221
+ if (filter) {
1222
+ Object.entries(filter).forEach(([key, value]) => {
1223
+ if (value && Array.isArray(value)) {
1224
+ const [op, val] = value;
1225
+ const postgrestOp = op === "==" ? "eq" : op === "!=" ? "neq" : op === ">" ? "gt" : op === ">=" ? "gte" : op === "<" ? "lt" : op === "<=" ? "lte" : op === "in" ? "in" : op === "not-in" ? "nin" : op === "array-contains" ? "cs" : op === "array-contains-any" ? "csa" : "eq";
1226
+
1227
+ let stringVal: string;
1228
+ if (Array.isArray(val)) {
1229
+ stringVal = `(${val.join(",")})`;
1230
+ } else {
1231
+ stringVal = String(val);
1232
+ }
1233
+ whereMap[key] = `${postgrestOp}.${stringVal}`;
1234
+ }
1235
+ });
1236
+ }
1237
+ const whereParams = Object.keys(whereMap).length > 0 ? whereMap : undefined;
1238
+ const orderByParams = sortByProperty ? `${String(sortByProperty)}:${currentSort}` : undefined;
1239
+
1240
+ // Deduplicate inflight count requests (e.g. React StrictMode double-mount)
1241
+ const cacheKey = `${path}|${filterKey}|${sortByProperty ?? ""}|${currentSort ?? ""}`;
1242
+ let countPromise = inflightCountRequests.get(cacheKey);
1243
+ if (!countPromise) {
1244
+ countPromise = accessor.count({
1156
1245
  where: whereParams,
1157
1246
  orderBy: orderByParams
1158
- }).then((c) => {
1159
- if (onCountChange) onCountChange(c);
1160
- }).catch((e) => {
1161
- console.warn("Error fetching count", e);
1162
- if (onCountChange) onCountChange(undefined);
1163
1247
  });
1164
- } else {
1165
- if (onCountChange) onCountChange(undefined);
1248
+ inflightCountRequests.set(cacheKey, countPromise);
1249
+ // Clean up the inflight entry once resolved/rejected
1250
+ countPromise.finally(() => inflightCountRequests.delete(cacheKey));
1166
1251
  }
1167
- }, [path, resolvedPath, collection, filter, sortByProperty, currentSort, dataClient]);
1252
+
1253
+ countPromise.then((c) => {
1254
+ if (!cancelled) onCountChangeRef.current?.(c);
1255
+ }).catch((e) => {
1256
+ console.warn("Error fetching count", e);
1257
+ if (!cancelled) onCountChangeRef.current?.(undefined);
1258
+ });
1259
+
1260
+ return () => { cancelled = true; };
1261
+ }, [path, filterKey, sortByProperty, currentSort]);
1168
1262
 
1169
1263
  // Count is now displayed in the breadcrumb bar, this component only fetches and reports
1170
1264
  return null;
@@ -1175,7 +1269,7 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
1175
1269
  const [parentKey, ...childKey] = key.split(".");
1176
1270
  return { properties: { [parentKey]: buildPropertyWidthOverwrite(childKey.join("."), width) } } as PartialEntityCollection;
1177
1271
  }
1178
- return { properties: { [key]: { columnWidth: width } } } as PartialEntityCollection;
1272
+ return { properties: { [key]: { ui: { columnWidth: width } } } } as PartialEntityCollection;
1179
1273
  }
1180
1274
 
1181
1275
  function EntityIdHeaderWidget({
@@ -2,7 +2,7 @@
2
2
  import type { EntityCollection } from "@rebasepro/types";
3
3
  import React, { lazy, Suspense } from "react";
4
4
 
5
- import { useLargeLayout, useTranslation, useSlot } from "@rebasepro/core";
5
+ import { useLargeLayout, useTranslation, useSlot, resolveComponentRef } from "@rebasepro/core";
6
6
  import { CollectionActionsProps, EntityTableController, SelectionController } from "@rebasepro/types";
7
7
  import { Button, IconButton, Tooltip, Popover, iconSize } from "@rebasepro/ui";
8
8
  import { PlusIcon, Trash2Icon, MoreVerticalIcon } from "lucide-react";
@@ -20,7 +20,7 @@ export type EntityCollectionViewActionsProps<M extends Record<string, unknown>>
20
20
  collection: EntityCollection<M>;
21
21
  path: string;
22
22
  relativePath: string;
23
- parentCollectionIds: string[];
23
+ parentCollectionSlugs: string[], parentEntityIds: string[];
24
24
  selectionEnabled: boolean;
25
25
  onNewClick: () => void;
26
26
  onMultipleDeleteClick: () => void;
@@ -29,12 +29,13 @@ export type EntityCollectionViewActionsProps<M extends Record<string, unknown>>
29
29
  collectionEntitiesCount?: number;
30
30
  compact?: boolean;
31
31
  children?: React.ReactNode;
32
+ openNewDocument: (defaultValues?: Record<string, unknown>) => void;
32
33
  }
33
34
 
34
35
  export function EntityCollectionViewActions<M extends Record<string, unknown>>({
35
36
  collection,
36
37
  relativePath,
37
- parentCollectionIds,
38
+ parentCollectionSlugs, parentEntityIds,
38
39
  onNewClick,
39
40
  onMultipleDeleteClick,
40
41
  selectionEnabled,
@@ -43,7 +44,8 @@ export function EntityCollectionViewActions<M extends Record<string, unknown>>({
43
44
  tableController,
44
45
  collectionEntitiesCount,
45
46
  compact,
46
- children
47
+ children,
48
+ openNewDocument
47
49
  }: EntityCollectionViewActionsProps<M>) {
48
50
  const context = useCMSContext();
49
51
 
@@ -64,6 +66,7 @@ export function EntityCollectionViewActions<M extends Record<string, unknown>>({
64
66
  id={`add_entity_${path}`}
65
67
  onClick={onNewClick}
66
68
  startIcon={<PlusIcon size={iconSize.small}/>}
69
+ size="small"
67
70
  variant="filled"
68
71
  color="primary">
69
72
  Add {collection.singularName ?? collection.name}
@@ -71,9 +74,9 @@ export function EntityCollectionViewActions<M extends Record<string, unknown>>({
71
74
  : <Button
72
75
  id={`add_entity_${path}`}
73
76
  onClick={onNewClick}
74
- variant={compact ? "filled" : "filled"}
77
+ variant="filled"
75
78
  color={compact ? "neutral" : "primary"}
76
- size={compact ? "small" : "medium"}
79
+ size="small"
77
80
  >
78
81
  <PlusIcon size={iconSize.small}/>
79
82
  </Button>);
@@ -110,20 +113,27 @@ export function EntityCollectionViewActions<M extends Record<string, unknown>>({
110
113
  const actionProps: CollectionActionsProps<M> = {
111
114
  path,
112
115
  relativePath,
113
- parentCollectionIds,
116
+ parentCollectionSlugs, parentEntityIds,
114
117
  collection,
115
118
  selectionController,
116
119
  context,
117
120
  tableController,
118
- collectionEntitiesCount
121
+ collectionEntitiesCount,
122
+ openNewDocument
119
123
  };
120
124
 
121
125
  const actions = toArray(collection.Actions)
122
- .map((Action, i) => (
123
- <ErrorBoundary key={`actions_${i}`}>
124
- <Action {...actionProps}/>
125
- </ErrorBoundary>
126
- ));
126
+ .map((actionRef, i) => {
127
+ const Action = resolveComponentRef(actionRef);
128
+ if (!Action) return null;
129
+ return (
130
+ <ErrorBoundary key={`actions_${i}`}>
131
+ <Suspense fallback={null}>
132
+ <Action {...actionProps}/>
133
+ </Suspense>
134
+ </ErrorBoundary>
135
+ );
136
+ });
127
137
 
128
138
  const pluginActions = useSlot("collection.actions", actionProps as any);
129
139