@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
@@ -16,7 +16,7 @@ export type EntityCollectionViewStartActionsProps<M extends Record<string, unkno
16
16
  collection: EntityCollection<M>;
17
17
  path: string;
18
18
  relativePath: string;
19
- parentCollectionIds: string[];
19
+ parentCollectionSlugs: string[], parentEntityIds: string[];
20
20
  selectionController: SelectionController<M>;
21
21
  tableController: EntityTableController<M>;
22
22
  collectionEntitiesCount?: number;
@@ -25,18 +25,20 @@ export type EntityCollectionViewStartActionsProps<M extends Record<string, unkno
25
25
  */
26
26
  resolvedProperties?: Properties;
27
27
  compact?: boolean;
28
+ openNewDocument: (defaultValues?: Record<string, unknown>) => void;
28
29
  }
29
30
 
30
31
  export function EntityCollectionViewStartActions<M extends Record<string, unknown>>({
31
32
  collection,
32
33
  relativePath,
33
- parentCollectionIds,
34
+ parentCollectionSlugs, parentEntityIds,
34
35
  path,
35
36
  selectionController,
36
37
  tableController,
37
38
  collectionEntitiesCount,
38
39
  resolvedProperties,
39
- compact
40
+ compact,
41
+ openNewDocument
40
42
  }: EntityCollectionViewStartActionsProps<M>) {
41
43
 
42
44
  const context = useCMSContext();
@@ -51,24 +53,30 @@ export function EntityCollectionViewStartActions<M extends Record<string, unknow
51
53
 
52
54
  // Count active filters (excluding force filters)
53
55
  const filterValues = tableController.filterValues;
54
- const forceFilter = collection.forceFilter;
56
+ const fixedFilter = collection.fixedFilter;
55
57
  const activeFilterCount = filterValues
56
- ? Object.keys(filterValues).filter(key => !forceFilter || !(key in forceFilter)).length
58
+ ? Object.keys(filterValues).filter(key => !fixedFilter || !(key in fixedFilter)).length
57
59
  : 0;
58
60
 
59
61
  const actionProps: CollectionActionsProps<M> = {
60
62
  path,
61
63
  relativePath,
62
- parentCollectionIds,
64
+ parentCollectionSlugs, parentEntityIds,
63
65
  collection,
64
66
  selectionController,
65
67
  context,
66
68
  tableController,
67
- collectionEntitiesCount
69
+ collectionEntitiesCount,
70
+ openNewDocument
68
71
  };
69
72
 
70
73
  const handleBackClick = useCallback(() => {
71
- const collectionUrl = urlController.buildUrlCollectionPath(path);
74
+ let collectionUrl = urlController.buildUrlCollectionPath(path);
75
+ // Preserve the __view query param so the view mode is retained
76
+ const currentViewParam = new URLSearchParams(window.location.search).get("__view");
77
+ if (currentViewParam) {
78
+ collectionUrl += `${collectionUrl.includes("?") ? "&" : "?"}__view=${currentViewParam}`;
79
+ }
72
80
  navigate(collectionUrl);
73
81
  }, [navigate, urlController, path]);
74
82
 
@@ -103,13 +111,14 @@ export function EntityCollectionViewStartActions<M extends Record<string, unknow
103
111
  {t("filters")}{activeFilterCount > 0 ? ` (${activeFilterCount})` : ""}
104
112
  </Button>
105
113
  ) : (
106
- <IconButton
114
+ <Button
115
+ variant="text"
107
116
  size="small"
108
117
  onClick={() => setFiltersDialogOpen(true)}
109
118
  className={cls(activeFilterCount > 0 && "text-primary")}
110
119
  >
111
120
  <FilterIcon size={iconSize.small}/>
112
- </IconButton>
121
+ </Button>
113
122
  )}
114
123
  </Badge>
115
124
  </Tooltip>
@@ -121,7 +130,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, unknow
121
130
  <ClearFilterSortButton
122
131
  key={"clear_filter"}
123
132
  tableController={tableController}
124
- enabled={!collection.forceFilter}/>
133
+ enabled={!collection.fixedFilter}/>
125
134
  ];
126
135
 
127
136
  const pluginActionsStart = useSlot("collection.actions.start", actionProps);
@@ -139,7 +148,7 @@ export function EntityCollectionViewStartActions<M extends Record<string, unknow
139
148
  properties={resolvedProperties}
140
149
  filterValues={tableController.filterValues}
141
150
  setFilterValues={(filterValues) => tableController.setFilterValues?.(filterValues ?? {})}
142
- forceFilter={collection.forceFilter}
151
+ fixedFilter={collection.fixedFilter}
143
152
  />
144
153
  )}
145
154
  </>
@@ -18,7 +18,7 @@ export interface FiltersDialogProps {
18
18
  properties: Record<string, Property>;
19
19
  filterValues: FilterValues<any> | undefined;
20
20
  setFilterValues: (filterValues?: FilterValues<any>) => void;
21
- forceFilter?: FilterValues<any>;
21
+ fixedFilter?: FilterValues<any>;
22
22
  }
23
23
 
24
24
  /**
@@ -31,7 +31,7 @@ export function FiltersDialog({
31
31
  properties,
32
32
  filterValues,
33
33
  setFilterValues,
34
- forceFilter
34
+ fixedFilter
35
35
  }: FiltersDialogProps) {
36
36
  const { t } = useTranslation();
37
37
  // Local state for filters being edited
@@ -52,13 +52,13 @@ export function FiltersDialog({
52
52
  return Object.entries(properties).filter(([key, property]) => {
53
53
  if (!property) return false;
54
54
  // Force filter properties should not be editable
55
- if (forceFilter && key in forceFilter) return false;
55
+ if (fixedFilter && key in fixedFilter) return false;
56
56
  // Check if property type is filterable
57
57
  const baseProperty = property.type === "array" ? property.of : property;
58
58
  if (!baseProperty || Array.isArray(baseProperty)) return false;
59
59
  return ["string", "number", "boolean", "date", "reference"].includes(baseProperty.type);
60
60
  });
61
- }, [properties, forceFilter]);
61
+ }, [properties, fixedFilter]);
62
62
 
63
63
  const handleFilterChange = useCallback((propertyKey: string, value?: [VirtualTableWhereFilterOp, any]) => {
64
64
  setLocalFilters(prev => {
@@ -75,9 +75,9 @@ export function FiltersDialog({
75
75
  const handleApply = useCallback(() => {
76
76
  const hasFilters = Object.keys(localFilters).length > 0;
77
77
  setFilterValues(hasFilters ? { ...localFilters,
78
- ...forceFilter } : (forceFilter || undefined));
78
+ ...fixedFilter } : (fixedFilter || undefined));
79
79
  onOpenChange(false);
80
- }, [localFilters, setFilterValues, forceFilter, onOpenChange]);
80
+ }, [localFilters, setFilterValues, fixedFilter, onOpenChange]);
81
81
 
82
82
  const handleClearAll = useCallback(() => {
83
83
  setLocalFilters({});
@@ -114,7 +114,7 @@ export function FiltersDialog({
114
114
  path={baseProperty.path}
115
115
  title={property.name}
116
116
  includeId={baseProperty.includeId}
117
- previewProperties={baseProperty.previewProperties}
117
+ previewProperties={baseProperty.ui?.previewProperties}
118
118
  hidden={hiddenFields[propertyKey] ?? false}
119
119
  setHidden={(hidden) => setHiddenForField(propertyKey, hidden)}
120
120
  />
@@ -30,7 +30,7 @@ export type SplitListViewProps<M extends Record<string, unknown> = Record<string
30
30
  initialScroll?: number;
31
31
  size?: CollectionSize;
32
32
  path: string;
33
- parentCollectionIds?: string[];
33
+ parentCollectionSlugs?: string[], parentEntityIds?: string[];
34
34
  /**
35
35
  * The entity ID to show in the detail panel.
36
36
  * Comes from the URL path (e.g. /c/authors/14 → selectedEntityId = "14").
@@ -99,7 +99,7 @@ export function SplitListView<M extends Record<string, unknown> = Record<string,
99
99
  initialScroll,
100
100
  size = "m",
101
101
  path,
102
- parentCollectionIds,
102
+ parentCollectionSlugs, parentEntityIds,
103
103
  selectedEntityId,
104
104
  selectedTab,
105
105
  toolbar,
@@ -169,7 +169,12 @@ export function SplitListView<M extends Record<string, unknown> = Record<string,
169
169
 
170
170
  // Close the detail panel: navigate back to the collection path
171
171
  const handleCloseDetail = useCallback(() => {
172
- const collectionUrl = urlController.buildUrlCollectionPath(path);
172
+ let collectionUrl = urlController.buildUrlCollectionPath(path);
173
+ // Preserve the __view query param so the view mode is retained
174
+ const currentViewParam = new URLSearchParams(window.location.search).get("__view");
175
+ if (currentViewParam) {
176
+ collectionUrl += `${collectionUrl.includes("?") ? "&" : "?"}__view=${currentViewParam}`;
177
+ }
173
178
  navigate(collectionUrl);
174
179
  }, [navigate, urlController, path]);
175
180
 
@@ -246,7 +251,8 @@ export function SplitListView<M extends Record<string, unknown> = Record<string,
246
251
  return () => window.removeEventListener("keydown", handleKeyDown);
247
252
  }, [selectedEntityId, handleCloseDetail, externalOnEntityClick, externalOnNewClick, entityIds, tableController.data]);
248
253
 
249
- const usedParentCollectionIds = parentCollectionIds ?? collectionRegistryController.getParentCollectionIds(path);
254
+ const usedParentCollectionIds = parentCollectionSlugs ?? collectionRegistryController.getParentCollectionSlugs(path);
255
+ const usedParentEntityIds = parentEntityIds ?? collectionRegistryController.getParentEntityIds(path);
250
256
 
251
257
  const isDetailVisible = animationPhase !== "idle";
252
258
 
@@ -255,15 +261,19 @@ export function SplitListView<M extends Record<string, unknown> = Record<string,
255
261
  const listPanel = (
256
262
  <div
257
263
  className={cls(
258
- "flex flex-col h-full overflow-y-auto overflow-x-hidden min-w-0 transition-all ease-out w-full",
264
+ "flex flex-col h-full min-w-0 transition-all ease-out w-full",
259
265
  (!largeLayout && isDetailVisible)
260
266
  ? "opacity-0 -translate-x-1/3 pointer-events-none"
261
267
  : "opacity-100 translate-x-0"
262
268
  )}
263
269
  style={{ transitionDuration: `${TRANSITION_DURATION}ms` }}
264
270
  >
271
+ {/* Toolbar stays fixed above the scrollable area */}
265
272
  {toolbar}
266
- {children}
273
+ {/* Scrollable content: title + insights + list rows */}
274
+ <div className="flex-1 overflow-y-auto overflow-x-hidden min-h-0">
275
+ {children}
276
+ </div>
267
277
  </div>
268
278
  );
269
279
 
@@ -283,16 +293,22 @@ export function SplitListView<M extends Record<string, unknown> = Record<string,
283
293
  path={path}
284
294
  collection={collection as EntityCollection<Record<string, unknown>>}
285
295
  entityId={renderedEntityId}
286
- parentCollectionIds={usedParentCollectionIds}
296
+ parentCollectionSlugs={usedParentCollectionIds}
297
+ parentEntityIds={usedParentEntityIds}
287
298
  selectedTab={selectedTab}
288
299
  layout="split"
289
300
  onTabChange={(params) => {
290
301
  const newSelectedTab = params.selectedTab;
291
- const entityUrl = urlController.buildUrlCollectionPath(
302
+ let entityUrl = urlController.buildUrlCollectionPath(
292
303
  newSelectedTab
293
304
  ? `${path}/${renderedEntityId}/${newSelectedTab}`
294
305
  : `${path}/${renderedEntityId}`
295
306
  );
307
+ // Preserve the __view query param
308
+ const currentViewParam = new URLSearchParams(window.location.search).get("__view");
309
+ if (currentViewParam) {
310
+ entityUrl += `${entityUrl.includes("?") ? "&" : "?"}__view=${currentViewParam}`;
311
+ }
296
312
  navigate(entityUrl);
297
313
  }}
298
314
  />
@@ -110,11 +110,39 @@ export function resolveCollectionSlotKeys(
110
110
 
111
111
  // Status: first string-enum that isn't the title
112
112
  let statusKey: string | undefined;
113
- for (const [key, prop] of Object.entries(collection.properties)) {
114
- const p = prop as Property;
115
- if (p.type === "string" && "enum" in p && p.enum && key !== titleKey) {
116
- statusKey = key;
117
- break;
113
+
114
+ // 1. Explicitly defined in previewProperties
115
+ if (!statusKey && collection.previewProperties) {
116
+ for (const key of collection.previewProperties) {
117
+ const p = collection.properties[key] as Property | undefined;
118
+ if (p?.type === "string" && "enum" in p && p.enum && key !== titleKey) {
119
+ statusKey = key;
120
+ break;
121
+ }
122
+ }
123
+ }
124
+
125
+ // 2. Explicitly defined in propertiesOrder
126
+ if (!statusKey && collection.propertiesOrder) {
127
+ for (const key of collection.propertiesOrder) {
128
+ if (typeof key === "string" && !key.startsWith("subcollection:")) {
129
+ const p = collection.properties[key] as Property | undefined;
130
+ if (p?.type === "string" && "enum" in p && p.enum && key !== titleKey) {
131
+ statusKey = key;
132
+ break;
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ // 3. Default automatic inference
139
+ if (!statusKey) {
140
+ for (const [key, prop] of Object.entries(collection.properties)) {
141
+ const p = prop as Property;
142
+ if (p.type === "string" && "enum" in p && p.enum && key !== titleKey) {
143
+ statusKey = key;
144
+ break;
145
+ }
118
146
  }
119
147
  }
120
148
 
@@ -1,9 +1,9 @@
1
- import type { EntityCollection, EntityCustomViewParams } from "@rebasepro/types";
1
+ import type { ComponentRef, EntityCollection, EntityCustomViewParams } from "@rebasepro/types";
2
2
  import type { FormContext } from "../types/fields";
3
3
  import type { PluginFormActionProps } from "@rebasepro/types";
4
- import React, { lazy, Suspense, useEffect, useMemo, useState } from "react";
4
+ import React, { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { Entity, EntityStatus } from "@rebasepro/types";
6
- import { PluginProviderStack } from "@rebasepro/core";
6
+ import { PluginProviderStack, resolveComponentRef } from "@rebasepro/core";
7
7
 
8
8
  import { EntityCollectionView, EntityView } from "../components";
9
9
  import { CircularProgressCenter, iconSize } from "@rebasepro/ui";
@@ -16,7 +16,6 @@ import {
16
16
  resolveDefaultSelectedView
17
17
  } from "@rebasepro/common";
18
18
  import { resolvedSelectedEntityView } from "../util/resolutions";
19
- import { getEntityTitlePropertyKey } from "../util/previews";
20
19
  import { CenteredView, CircularProgress, cls, defaultBorderMixin, IconButton, Tab, Tabs, Tooltip, Typography, Skeleton } from "@rebasepro/ui";
21
20
  import {
22
21
  useCustomizationController,
@@ -66,13 +65,19 @@ export interface EntityEditViewProps<M extends Record<string, unknown> = Record<
66
65
  databaseId?: string;
67
66
  copy?: boolean;
68
67
  selectedTab?: string;
69
- parentCollectionIds: string[];
68
+ parentCollectionSlugs: string[], parentEntityIds: string[];
70
69
  onValuesModified?: (modified: boolean, values: M) => void;
71
70
  onSaved?: (params: OnUpdateParams) => void;
72
71
  onTabChange?: (props: OnTabChangeParams<M>) => void;
73
72
  layout?: "side_panel" | "full_screen" | "split";
74
73
  barActions?: (params: BarActionsParams) => any;
75
74
  formProps?: Partial<EntityFormProps<M>>,
75
+ /**
76
+ * Pre-populate the form with these values when creating a new entity.
77
+ * Only applied when the form is in "new" mode (no entityId).
78
+ * Sourced from EntitySidePanelProps (side panel) or location.state (full screen).
79
+ */
80
+ defaultValues?: Partial<M>;
76
81
  }
77
82
 
78
83
  /**
@@ -99,7 +104,7 @@ export function EntityEditView<M extends Record<string, unknown>>({
99
104
 
100
105
  const initialDirtyValues = entityId
101
106
  ? getEntityFromMemoryCache(props.path + "/" + entityId)
102
- : getEntityFromMemoryCache(props.path + "#new");
107
+ : (props.defaultValues ?? getEntityFromMemoryCache(props.path + "#new"));
103
108
 
104
109
  const { canEdit: canEditHook } = usePermissions();
105
110
 
@@ -143,7 +148,7 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
143
148
  entityId,
144
149
  selectedTab: selectedTabProp,
145
150
  collection,
146
- parentCollectionIds,
151
+ parentCollectionSlugs, parentEntityIds,
147
152
  onValuesModified,
148
153
  onSaved,
149
154
  onTabChange,
@@ -183,9 +188,9 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
183
188
  const customizationController = useCustomizationController();
184
189
  const plugins = customizationController.plugins;
185
190
 
186
- const formActionTopProps: PluginFormActionProps = {
191
+ const formActionTopProps: PluginFormActionProps = useMemo(() => ({
187
192
  entityId,
188
- parentCollectionIds,
193
+ parentCollectionSlugs, parentEntityIds,
189
194
  path: path,
190
195
  status,
191
196
  collection: collection!,
@@ -193,7 +198,7 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
193
198
  formContext: formContext as FormContext<Record<string, unknown>> | undefined,
194
199
  openEntityMode: layout,
195
200
  disabled: false
196
- };
201
+ }), [entityId, parentCollectionSlugs, parentEntityIds, path, status, collection, context, formContext, layout]);
197
202
  const pluginActionsTop = useSlot("form.actions.top", formActionTopProps);
198
203
 
199
204
  const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(
@@ -230,15 +235,51 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
230
235
 
231
236
  const mainViewVisible = selectedTab === MAIN_TAB_VALUE || Boolean(selectedSecondaryForm);
232
237
 
233
- const customViewsView: any[] | undefined = customViews && resolvedEntityViews
234
- .filter(e => !e.includeActions)
238
+ // Track which custom view tabs have been visited so we keep them mounted
239
+ // (preserving their state) but don't eagerly mount tabs never visited.
240
+ const mountedTabsRef = useRef<Set<string>>(new Set());
241
+ if (selectedTab) {
242
+ mountedTabsRef.current.add(selectedTab);
243
+ }
244
+
245
+ // Memoize the read-only fallback form context to avoid recreating it every render
246
+ const readOnlyFormContext = useMemo<FormContext<M> | undefined>(() => {
247
+ if (formContext) return undefined; // not needed when real formContext exists
248
+ if (!entityId) return undefined;
249
+ const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
250
+ return {
251
+ entityId,
252
+ disabled: false,
253
+ openEntityMode: layout,
254
+ status: status,
255
+ values: usedEntity?.values ?? ({} as M),
256
+ setFieldValue: (key: string, value: any) => {
257
+ throw new Error("You can't update values in read only mode");
258
+ },
259
+ save: () => {
260
+ throw new Error("You can't save in read only mode");
261
+ },
262
+ collection,
263
+ path: path,
264
+ entity: usedEntity,
265
+ savingError: undefined,
266
+ formex: formexStub
267
+ };
268
+ }, [formContext, entityId, layout, status, usedEntity, collection, path]);
269
+
270
+ const nonActionCustomViews = useMemo(() =>
271
+ resolvedEntityViews.filter(e => !e.includeActions),
272
+ [resolvedEntityViews]
273
+ );
274
+
275
+ const customViewsView: any[] | undefined = customViews && nonActionCustomViews
235
276
  .map((customView) => {
236
277
 
237
278
  if (!customView)
238
279
  return null;
239
- const Builder = customView.Builder;
280
+ const Builder = resolveComponentRef<EntityCustomViewParams>(customView.Builder);
240
281
  if (!Builder) {
241
- console.error("INTERNAL: customView.Builder is not defined");
282
+ console.error("INTERNAL: customView.Builder is not defined or could not be resolved");
242
283
  return null;
243
284
  }
244
285
 
@@ -246,48 +287,41 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
246
287
  return null;
247
288
  }
248
289
 
249
- const formexStub = createFormexStub<M>(usedEntity?.values ?? {} as M);
250
- const usedFormContext: FormContext<M> = formContext ?? {
251
- entityId,
252
- disabled: false,
253
- openEntityMode: layout,
254
- status: status,
255
- values: usedEntity?.values ?? ({} as M),
256
- setFieldValue: (key: string, value: any) => {
257
- throw new Error("You can't update values in read only mode");
258
- },
259
- save: () => {
260
- throw new Error("You can't save in read only mode");
261
- },
262
- collection,
263
- path: path,
264
- entity: usedEntity,
265
- savingError: undefined,
266
- formex: formexStub
267
- };
290
+ // Only mount tabs that have been visited at least once
291
+ const isActive = selectedTab === customView.key;
292
+ const hasBeenMounted = mountedTabsRef.current.has(customView.key);
293
+ if (!isActive && !hasBeenMounted) {
294
+ return null;
295
+ }
296
+
297
+ const usedFormContext: FormContext<M> = formContext ?? readOnlyFormContext!;
268
298
 
269
299
  return <div
270
300
  className={cls(defaultBorderMixin,
271
301
  "relative flex-1 w-full h-full overflow-auto",
272
- { "hidden": selectedTab !== customView.key }
302
+ { "hidden": !isActive }
273
303
  )}
274
304
  key={`custom_view_${customView.key}`}
275
305
  role="tabpanel">
276
306
  <ErrorBoundary>
277
- {usedFormContext && <Builder
278
- collection={collection}
279
- parentCollectionIds={parentCollectionIds}
280
- entity={usedEntity}
281
- modifiedValues={usedFormContext?.formex?.values ?? usedEntity?.values}
282
- formContext={usedFormContext as unknown as FormContext<Record<string, unknown>>}
283
- />}
307
+ <Suspense fallback={<CircularProgressCenter />}>
308
+ {usedFormContext && <Builder
309
+ collection={collection}
310
+ parentCollectionSlugs={parentCollectionSlugs} parentEntityIds={parentEntityIds}
311
+ entity={usedEntity}
312
+ modifiedValues={usedFormContext?.formex?.values ?? usedEntity?.values}
313
+ formContext={usedFormContext as unknown as FormContext<Record<string, unknown>>}
314
+ />}
315
+ </Suspense>
284
316
  </ErrorBoundary>
285
317
  </div>;
286
318
  }).filter(Boolean);
287
319
 
288
320
  const globalLoading = (dataLoading && !usedEntity) || (canEdit === undefined && (status === "existing" || status === "copy"));
289
321
 
290
- const jsonView = <div
322
+ // Only mount JSON view when its tab is selected (or was previously selected)
323
+ const jsonTabMounted = mountedTabsRef.current.has(JSON_TAB_VALUE);
324
+ const jsonView = (selectedTab === JSON_TAB_VALUE || jsonTabMounted) ? <div
291
325
  className={cls("relative flex-1 h-full overflow-auto w-full",
292
326
  { "hidden": selectedTab !== JSON_TAB_VALUE })}
293
327
  key={"json_view"}
@@ -296,11 +330,11 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
296
330
  <EntityJsonPreview
297
331
  values={formContext?.values ?? entity?.values ?? {}} />
298
332
  </ErrorBoundary>
299
- </div>;
333
+ </div> : null;
300
334
 
301
- const historyView = includeHistoryView ? <div
302
- className={cls("relative flex-1 h-full overflow-auto w-full",
303
- { "hidden": selectedTab !== HISTORY_TAB_VALUE })}
335
+ // Only mount history view when its tab is actually selected
336
+ const historyView = includeHistoryView && selectedTab === HISTORY_TAB_VALUE ? <div
337
+ className={"relative flex-1 h-full overflow-auto w-full"}
304
338
  key={"history_view"}
305
339
  role="tabpanel">
306
340
  <ErrorBoundary>
@@ -332,7 +366,8 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
332
366
  (usedEntity && newFullPath
333
367
  ? <EntityCollectionView
334
368
  path={newFullPath}
335
- parentCollectionIds={[...parentCollectionIds, collection.slug]}
369
+ parentCollectionSlugs={[...parentCollectionSlugs, collection.slug]}
370
+ parentEntityIds={[...parentEntityIds, String(usedEntity?.id)]}
336
371
  updateUrl={false}
337
372
  {...subcollection}
338
373
  openEntityMode={layout} />
@@ -348,7 +383,7 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
348
383
  );
349
384
  }).filter(Boolean);
350
385
 
351
- const onSideTabClick = (value: string) => {
386
+ const onSideTabClick = useCallback((value: string) => {
352
387
  setSelectedTab(value);
353
388
  if (status === "existing") {
354
389
  onTabChange?.({
@@ -358,7 +393,7 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
358
393
  collection
359
394
  });
360
395
  }
361
- };
396
+ }, [status, onTabChange, path, entityId, collection]);
362
397
 
363
398
  const entityReadOnlyView = !canEdit && entity ? <div
364
399
  className={cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", (canEdit || !mainViewVisible || selectedSecondaryForm) ? "hidden" : "")}>
@@ -410,7 +445,7 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
410
445
  onSaved?.(res);
411
446
  formProps?.onSaved?.(res);
412
447
  }}
413
- Builder={selectedSecondaryForm?.Builder as React.ComponentType<EntityCustomViewParams<M>> | undefined}
448
+ Builder={resolveComponentRef(selectedSecondaryForm?.Builder as ComponentRef<EntityCustomViewParams<M>> | undefined) as React.ComponentType<EntityCustomViewParams<M>> | undefined}
414
449
  />;
415
450
 
416
451
  const subcollectionTabs = subcollections && subcollections.map((subcollection) =>
@@ -457,33 +492,6 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
457
492
  </Tooltip>
458
493
  ) : null;
459
494
 
460
- // Compute contextual title for subcollection tabs, e.g. "Orders of James"
461
- const subcollectionContextTitle = useMemo(() => {
462
- if (selectedTab === MAIN_TAB_VALUE || selectedTab === JSON_TAB_VALUE || selectedTab === HISTORY_TAB_VALUE) {
463
- return null;
464
- }
465
- // Check if the selected tab is a subcollection
466
- const matchedSubcollection = subcollections.find(sc => sc.slug === selectedTab);
467
- if (!matchedSubcollection) {
468
- return null;
469
- }
470
- // Check if the selected tab is a custom view (not a subcollection)
471
- const isCustomView = resolvedEntityViews.some(v => v.key === selectedTab);
472
- if (isCustomView) {
473
- return null;
474
- }
475
- // Resolve the parent entity's title
476
- const titleKey = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
477
- const entityValues = usedEntity?.values;
478
- if (!titleKey || !entityValues) {
479
- return matchedSubcollection.name;
480
- }
481
- const titleValue = entityValues[titleKey as keyof M];
482
- if (!titleValue || typeof titleValue !== "string") {
483
- return matchedSubcollection.name;
484
- }
485
- return `${matchedSubcollection.name} of ${titleValue}`;
486
- }, [selectedTab, subcollections, resolvedEntityViews, collection, customizationController.propertyConfigs, usedEntity?.values]);
487
495
 
488
496
  let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-800">
489
497
 
@@ -499,14 +507,6 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
499
507
  status
500
508
  })}
501
509
 
502
- {subcollectionContextTitle && (
503
- <Typography
504
- variant="label"
505
- className="truncate min-w-0 shrink ml-2 text-surface-600 dark:text-surface-400"
506
- >
507
- {subcollectionContextTitle}
508
- </Typography>
509
- )}
510
510
 
511
511
  {pluginActionsTop}
512
512
 
@@ -9,6 +9,7 @@ import { EntityEditView } from "./EntityEditView";
9
9
  import { useSideDialogContext } from "./SideDialogs";
10
10
  import { IconButton } from "@rebasepro/ui";
11
11
  import { useLocation, useNavigate } from "react-router-dom";
12
+ import { removeInitialAndTrailingSlashes } from "@rebasepro/common";
12
13
  import { saveEntityToMemoryCache } from "@rebasepro/core";
13
14
  import { useCollectionRegistryController, useSideEntityController } from "../index";
14
15
  import { useUrlController } from "../index";
@@ -75,8 +76,12 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
75
76
 
76
77
  }
77
78
 
78
- const parentCollectionIds = useMemo(() => {
79
- return collectionRegistryController.getParentCollectionIds(path);
79
+ const parentCollectionSlugs = useMemo(() => {
80
+ return collectionRegistryController.getParentCollectionSlugs(path);
81
+ }, [collectionRegistryController, path]);
82
+
83
+ const parentEntityIds = useMemo(() => {
84
+ return collectionRegistryController.getParentEntityIds(path);
80
85
  }, [collectionRegistryController, path]);
81
86
 
82
87
  const collection = collectionRegistryController.getCollection(path) ?? props.collection;
@@ -102,7 +107,7 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
102
107
  {...props}
103
108
  layout={"side_panel"}
104
109
  collection={collection as EntityCollection}
105
- parentCollectionIds={parentCollectionIds}
110
+ parentCollectionSlugs={parentCollectionSlugs} parentEntityIds={parentEntityIds}
106
111
  onValuesModified={onValuesModified}
107
112
  onSaved={onUpdate}
108
113
  barActions={({
@@ -143,13 +148,16 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
143
148
  selectedTab,
144
149
  collection
145
150
  }) => {
146
- sideEntityController.replace({
147
- path: path,
148
- entityId,
149
- selectedTab,
150
- updateUrl: true,
151
- collection
152
- });
151
+ // Only update the URL to reflect the new tab — don't call
152
+ // sideEntityController.replace() which would recreate the
153
+ // entire EntitySidePanel component, causing a full
154
+ // unmount/remount and expensive re-render of the form.
155
+ if (entityId) {
156
+ const collectionPath = removeInitialAndTrailingSlashes(path);
157
+ const tabSuffix = selectedTab ? "/" + selectedTab : "";
158
+ const fullUrl = urlController.buildUrlCollectionPath(`${collectionPath}/${entityId}${tabSuffix}#side`);
159
+ navigate(fullUrl, { replace: true, state: location.state });
160
+ }
153
161
  }}
154
162
  formProps={formProps}
155
163
  />