@rebasepro/admin 0.0.1-canary.f81da60 → 0.1.2

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 (95) hide show
  1. package/dist/{CollectionEditorDialog-D509-IMx.js → CollectionEditorDialog-ywdxhs1L.js} +18 -18
  2. package/dist/CollectionEditorDialog-ywdxhs1L.js.map +1 -0
  3. package/dist/{CollectionsStudioView-B549BDpU.js → CollectionsStudioView-BDzMFzqH.js} +4 -4
  4. package/dist/{CollectionsStudioView-B549BDpU.js.map → CollectionsStudioView-BDzMFzqH.js.map} +1 -1
  5. package/dist/{ContentHomePage--Bl1FXk7.js → ContentHomePage-0tHuEIm_.js} +26 -26
  6. package/dist/ContentHomePage-0tHuEIm_.js.map +1 -0
  7. package/dist/{ExportCollectionAction-CttNAdM1.js → ExportCollectionAction-BIrq92To.js} +2 -2
  8. package/dist/{ExportCollectionAction-CttNAdM1.js.map → ExportCollectionAction-BIrq92To.js.map} +1 -1
  9. package/dist/{ImportCollectionAction-BB33kxAN.js → ImportCollectionAction-h8yg_To8.js} +2 -2
  10. package/dist/{ImportCollectionAction-BB33kxAN.js.map → ImportCollectionAction-h8yg_To8.js.map} +1 -1
  11. package/dist/{PropertyEditView-UtDO8g0A.js → PropertyEditView-BuZrNnBN.js} +79 -101
  12. package/dist/PropertyEditView-BuZrNnBN.js.map +1 -0
  13. package/dist/{RolesView-B0E7L0hE.js → RolesView-CMPsaIXo.js} +2 -2
  14. package/dist/{RolesView-B0E7L0hE.js.map → RolesView-CMPsaIXo.js.map} +1 -1
  15. package/dist/{UsersView-BM2_7VPV.js → UsersView-BkeblMVT.js} +6 -28
  16. package/dist/UsersView-BkeblMVT.js.map +1 -0
  17. package/dist/collection_editor/ConfigControllerProvider.d.ts +0 -4
  18. package/dist/collection_editor/ui/collection_editor/CollectionDetailsForm.d.ts +1 -3
  19. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +0 -2
  20. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -2
  21. package/dist/collection_editor_ui.js +3 -3
  22. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -1
  23. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -1
  24. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +2 -1
  25. package/dist/components/EntityEditView.d.ts +6 -0
  26. package/dist/components/RebaseCMS.d.ts +1 -1
  27. package/dist/{index-C9YDsMC9.js → index-BuZaHcyc.js} +3 -3
  28. package/dist/index-BuZaHcyc.js.map +1 -0
  29. package/dist/{index-CNDetux9.js → index-CS6uJ7oW.js} +2 -2
  30. package/dist/{index-CNDetux9.js.map → index-CS6uJ7oW.js.map} +1 -1
  31. package/dist/{index-DO7lMeNB.js → index-eRJbMvHi.js} +3 -3
  32. package/dist/index-eRJbMvHi.js.map +1 -0
  33. package/dist/index.js +18 -14
  34. package/dist/index.js.map +1 -1
  35. package/dist/util/navigation_utils.d.ts +10 -1
  36. package/dist/{util-DK1O3uM0.js → util-zfU1zOCX.js} +713 -603
  37. package/dist/util-zfU1zOCX.js.map +1 -0
  38. package/package.json +8 -8
  39. package/src/collection_editor/ConfigControllerProvider.tsx +1 -10
  40. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +3 -47
  41. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +2 -10
  42. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +1 -3
  43. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +3 -3
  44. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +0 -1
  45. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  46. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
  47. package/src/collection_editor/ui/collection_editor/properties/ReferencePropertyField.tsx +15 -49
  48. package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +2 -3
  49. package/src/collection_editor/ui/collection_editor/templates/pages_template.ts +1 -1
  50. package/src/collection_editor/ui/collection_editor/templates/products_template.ts +2 -2
  51. package/src/components/DefaultAppBar.tsx +2 -2
  52. package/src/components/DefaultDrawer.tsx +25 -17
  53. package/src/components/DrawerNavigationGroup.tsx +4 -4
  54. package/src/components/DrawerNavigationItem.tsx +6 -6
  55. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +5 -3
  56. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +1 -1
  57. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +8 -2
  58. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +2 -2
  59. package/src/components/EntityCollectionTable/table_bindings.tsx +37 -27
  60. package/src/components/EntityCollectionView/EntityCard.tsx +2 -2
  61. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +4 -3
  62. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +7 -6
  63. package/src/components/EntityCollectionView/EntityCollectionView.tsx +50 -7
  64. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +17 -8
  65. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +8 -4
  66. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +33 -5
  67. package/src/components/EntityEditView.tsx +80 -81
  68. package/src/components/EntitySidePanel.tsx +11 -7
  69. package/src/components/HomePage/ContentHomePage.tsx +24 -15
  70. package/src/components/HomePage/NavigationCard.tsx +4 -4
  71. package/src/components/HomePage/NavigationGroup.tsx +2 -2
  72. package/src/components/RebaseAuthGate.tsx +2 -0
  73. package/src/components/RebaseCMS.tsx +4 -3
  74. package/src/components/RebaseNavigation.tsx +7 -4
  75. package/src/components/RelationSelector.tsx +30 -2
  76. package/src/components/SelectableTable/SelectableTable.tsx +2 -2
  77. package/src/components/UserSelector.tsx +1 -1
  78. package/src/components/admin/UsersView.tsx +2 -17
  79. package/src/components/app/Scaffold.tsx +3 -3
  80. package/src/components/field_configs.tsx +3 -3
  81. package/src/form/PropertyFieldBinding.tsx +10 -6
  82. package/src/hooks/navigation/useResolvedViews.tsx +1 -3
  83. package/src/hooks/navigation/useTopLevelNavigation.ts +1 -1
  84. package/src/hooks/navigation/utils.ts +1 -1
  85. package/src/preview/PropertyPreview.tsx +17 -13
  86. package/src/routes/RebaseRoute.tsx +27 -2
  87. package/src/util/navigation_utils.ts +16 -2
  88. package/src/util/previews.ts +14 -5
  89. package/dist/CollectionEditorDialog-D509-IMx.js.map +0 -1
  90. package/dist/ContentHomePage--Bl1FXk7.js.map +0 -1
  91. package/dist/PropertyEditView-UtDO8g0A.js.map +0 -1
  92. package/dist/UsersView-BM2_7VPV.js.map +0 -1
  93. package/dist/index-C9YDsMC9.js.map +0 -1
  94. package/dist/index-DO7lMeNB.js.map +0 -1
  95. package/dist/util-DK1O3uM0.js.map +0 -1
@@ -137,7 +137,7 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
137
137
  {hasAppBar && <DrawerHeader/>}
138
138
 
139
139
  <div
140
- className={cls(defaultBorderMixin, "bg-white dark:bg-surface-800", "grow overflow-auto m-0", {
140
+ className={cls(defaultBorderMixin, "bg-surface-50 dark:bg-surface-800", "grow overflow-auto m-0", {
141
141
  "lg:mt-4": !hasAppBar,
142
142
  "mt-1 lg:m-0 lg:mx-2 lg:mb-2 lg:rounded-lg lg:border-t lg:border-x lg:border-solid": padding,
143
143
  "border-t": hasAppBar && !padding
@@ -158,7 +158,7 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
158
158
 
159
159
  const DrawerHeader = () => {
160
160
  return (
161
- <div className="flex flex-col min-h-16"></div>
161
+ <div className="flex flex-col min-h-14"></div>
162
162
  );
163
163
  };
164
164
 
@@ -185,7 +185,7 @@ function DrawerWrapper(props: {
185
185
 
186
186
  const innerDrawer = <div
187
187
  className={cls("h-full overflow-hidden", defaultBorderMixin,
188
- isFloating ? `absolute top-0 left-0 bottom-0 z-50 bg-surface-50 ${darkBgFloating} shadow-2xl border-r` : `relative bg-surface-50 ${darkBg}`)}
188
+ isFloating ? `absolute top-0 left-0 bottom-0 z-50 bg-surface-50 ${darkBgFloating} shadow-lg border-r` : `relative bg-surface-50 ${darkBg}`)}
189
189
  style={{
190
190
  width: visualWidth,
191
191
  transition: "left 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, opacity 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, width 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms"
@@ -344,10 +344,10 @@ export function getFieldConfig(property: Property, propertyConfigs: Record<strin
344
344
 
345
345
  export function getDefaultFieldId(property: Property) {
346
346
  if (property.type === "string") {
347
- if (property.ui?.multiline) {
348
- return "multiline";
349
- } else if (property.ui?.markdown) {
347
+ if (property.ui?.markdown) {
350
348
  return "markdown";
349
+ } else if (property.ui?.multiline) {
350
+ return "multiline";
351
351
  } else if (property.storage) {
352
352
  return "file_upload";
353
353
  } else if (property.ui?.url) {
@@ -1,9 +1,11 @@
1
1
  import type { EntityCollection } from "@rebasepro/types";
2
2
  import type { FieldProps, PropertyFieldBindingProps } from "../types/fields";
3
3
  import type { RebasePlugin, PluginFieldBuilderParams, Property } from "@rebasepro/types";
4
- import React, { ComponentType, ReactElement, useCallback, useRef } from "react";
4
+ import React, { ComponentType, ReactElement, Suspense, useCallback, useRef } from "react";
5
5
  import { deepEqual as equal } from "fast-equals"
6
6
 
7
+ import { resolveComponentRef } from "@rebasepro/core";
8
+
7
9
  import { Field, FieldProps as FormexFieldProps, getIn } from "@rebasepro/formex";
8
10
 
9
11
  ;
@@ -114,8 +116,9 @@ function PropertyFieldBindingInternal<M extends Record<string, unknown> = Record
114
116
  } else if (readOnly) {
115
117
  Component = ReadOnlyFieldBinding;
116
118
  } else if (resolvedProperty.ui?.Field) {
117
- if (typeof resolvedProperty.ui?.Field === "function") {
118
- Component = resolvedProperty.ui?.Field as ComponentType<FieldProps<any>>;
119
+ const resolved = resolveComponentRef(resolvedProperty.ui.Field);
120
+ if (resolved) {
121
+ Component = resolved as ComponentType<FieldProps<any>>;
119
122
  }
120
123
  } else {
121
124
  const propertyConfig = getFieldConfig(resolvedProperty, customizationController.propertyConfigs);
@@ -139,7 +142,7 @@ function PropertyFieldBindingInternal<M extends Record<string, unknown> = Record
139
142
  index,
140
143
  authController
141
144
  }) as Property | null;
142
- Component = configProperty?.ui?.Field as ComponentType<FieldProps> | undefined;
145
+ Component = resolveComponentRef(configProperty?.ui?.Field) as ComponentType<FieldProps> | undefined;
143
146
  }
144
147
  if (!Component) {
145
148
  console.warn(`No field component found for property ${propertyKey}`);
@@ -265,8 +268,9 @@ function FieldInternal<CustomProps, M extends Record<string, any>>
265
268
 
266
269
  return (
267
270
  <ErrorBoundary>
268
-
269
- <UsedComponent {...cmsFieldProps}/>
271
+ <Suspense fallback={null}>
272
+ <UsedComponent {...cmsFieldProps}/>
273
+ </Suspense>
270
274
 
271
275
  {underlyingValueHasChanged && !isSubmitting &&
272
276
  <Typography variant={"caption"} className={"ml-3.5"}>
@@ -21,7 +21,7 @@ import { AuthController, RebaseData, User } from "@rebasepro/types";
21
21
  import { UserManagementDelegate } from "@rebasepro/types";
22
22
 
23
23
  import { resolveAppViews } from "./useNavigationResolution";
24
- import { NAVIGATION_ADMIN_GROUP_NAME } from "./utils";
24
+
25
25
 
26
26
  // Lazy-load admin views — only rendered when navigation reaches /users or /roles
27
27
  const UsersView = lazy(() => import("../../components/admin/UsersView").then(m => ({ default: m.UsersView })));
@@ -128,7 +128,6 @@ export function useResolvedViews<USER extends User>(
128
128
  views.push({
129
129
  slug: "users",
130
130
  name: "Users",
131
- group: NAVIGATION_ADMIN_GROUP_NAME,
132
131
  icon: "Headset",
133
132
  view: usersViewElement
134
133
  });
@@ -136,7 +135,6 @@ export function useResolvedViews<USER extends User>(
136
135
  views.push({
137
136
  slug: "roles",
138
137
  name: "Roles",
139
- group: NAVIGATION_ADMIN_GROUP_NAME,
140
138
  icon: "Shield",
141
139
  view: rolesViewElement
142
140
  });
@@ -152,7 +152,7 @@ export function useTopLevelNavigation(
152
152
  if (adminView.hideFromNavigation) return acc;
153
153
 
154
154
  const pathKey = adminView.slug;
155
- let groupName = adminView.group?.trim() || NAVIGATION_ADMIN_GROUP_NAME;
155
+ let groupName = NAVIGATION_ADMIN_GROUP_NAME;
156
156
 
157
157
  if (finalNavigationGroupMappings) {
158
158
  for (const pluginGroupDef of finalNavigationGroupMappings) {
@@ -11,7 +11,7 @@ export function getGroup(collectionOrView: EntityCollection<any, any> | AppView)
11
11
  if (!trimmed || trimmed === "") {
12
12
  return NAVIGATION_DEFAULT_GROUP_NAME;
13
13
  }
14
- return trimmed ?? NAVIGATION_DEFAULT_GROUP_NAME;
14
+ return trimmed;
15
15
  }
16
16
 
17
17
  export function computeNavigationGroups({
@@ -1,11 +1,11 @@
1
1
  import type { ArrayProperty, MapProperty, NumberProperty, Property, StringProperty } from "@rebasepro/types";
2
- import React, { createElement } from "react";
2
+ import React, { createElement, Suspense } from "react";
3
3
  import { deepEqual as equal } from "fast-equals"
4
4
 
5
5
  import { EntityReference, EntityRelation } from "@rebasepro/types";
6
6
  import type { PropertyPreviewProps } from "../types/components/PropertyPreviewProps";
7
7
  import { resolveProperty, normalizeToEntityRelation } from "@rebasepro/common";
8
- import { useAuthController, useCustomizationController } from "@rebasepro/core";
8
+ import { useAuthController, useCustomizationController, resolveComponentRef } from "@rebasepro/core";
9
9
  import { EmptyValue } from "./components/EmptyValue";
10
10
  import { UrlComponentPreview } from "./components/UrlComponentPreview";
11
11
  import { StorageThumbnail } from "./components/StorageThumbnail";
@@ -57,17 +57,21 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
57
57
  if (property === null) {
58
58
  content = <EmptyValue/>;
59
59
  } else if (property.ui?.Preview) {
60
- content = createElement(property.ui?.Preview,
61
- {
62
- propertyKey,
63
- value,
64
- property,
65
- size,
66
- height,
67
- width,
68
- // entity,
69
- customProps: property.ui?.customProps
70
- });
60
+ const ResolvedPreview = resolveComponentRef(property.ui.Preview);
61
+ if (ResolvedPreview) {
62
+ content = <Suspense fallback={null}>
63
+ {createElement(ResolvedPreview,
64
+ {
65
+ propertyKey,
66
+ value,
67
+ property,
68
+ size,
69
+ height,
70
+ width,
71
+ customProps: property.ui?.customProps
72
+ })}
73
+ </Suspense>;
74
+ }
71
75
  } else if (value === undefined || value === null) {
72
76
  content = <EmptyValue/>;
73
77
  } else if (property.type === "string") {
@@ -235,6 +235,10 @@ function EntityFullScreenRoute({
235
235
  const navigate = useNavigate();
236
236
  const location = useLocation();
237
237
 
238
+ // defaultValues may be carried via location.state when openNewDocument() is called
239
+ // for full-screen mode. We read it once on mount — after that, the form owns its state.
240
+ const defaultValues = (location.state as { defaultValues?: Record<string, unknown> } | null)?.defaultValues;
241
+
238
242
  // Preserve the current hash (e.g. #full) across tab/save navigations
239
243
  const hash = location.hash;
240
244
 
@@ -289,10 +293,27 @@ function EntityFullScreenRoute({
289
293
  let blocker: Blocker | undefined = undefined;
290
294
  try {
291
295
  blocker = useBlocker(({
296
+ currentLocation,
292
297
  nextLocation
293
298
  }) => {
294
299
  if (nextLocation.pathname.startsWith(entityPath))
295
300
  return false;
301
+
302
+ // Side panel overlay navigations preserve the underlying form via
303
+ // base_location in router state — no data is lost in either direction.
304
+
305
+ // Opening a side panel (e.g. clicking a relation arrow)
306
+ const nextHash = nextLocation.hash;
307
+ if (nextHash === "#side" || nextHash === "#new_side")
308
+ return false;
309
+
310
+ // Closing a side panel (navigate(-1) back to the form's own path)
311
+ const currentHash = currentLocation.hash;
312
+ if ((currentHash === "#side" || currentHash === "#new_side") &&
313
+ (nextLocation.pathname === basePath ||
314
+ nextLocation.pathname.startsWith(entityPath)))
315
+ return false;
316
+
296
317
  return blocked.current;
297
318
  });
298
319
  } catch (e) {
@@ -330,14 +351,18 @@ function EntityFullScreenRoute({
330
351
  path={collectionPath}
331
352
  copy={isCopy}
332
353
  selectedTab={selectedTab ?? undefined}
354
+ defaultValues={isNew ? defaultValues : undefined}
333
355
  onValuesModified={(modified) => blocked.current = modified}
334
356
  onSaved={(params) => {
335
357
  const newSelectedTab = params.selectedTab;
336
358
  const newEntityId = params.entityId;
359
+ // Clear the hash after saving a new entity — preserving #new
360
+ // would cause the route to re-parse as "new" and show "not found".
361
+ const savedHash = isNew ? "" : hash;
337
362
  if (newSelectedTab) {
338
- navigate(`${basePath}/${newEntityId}/${newSelectedTab}${hash}`, { replace: true });
363
+ navigate(`${basePath}/${newEntityId}/${newSelectedTab}${savedHash}`, { replace: true });
339
364
  } else {
340
- navigate(`${basePath}/${newEntityId}${hash}`, { replace: true });
365
+ navigate(`${basePath}/${newEntityId}${savedHash}`, { replace: true });
341
366
  }
342
367
  }}
343
368
  onTabChange={(params) => {
@@ -23,6 +23,7 @@ export function navigateToEntity({
23
23
  copy,
24
24
  path,
25
25
  selectedTab,
26
+ defaultValues,
26
27
  sideEntityController,
27
28
  onClose,
28
29
  navigation
@@ -34,6 +35,15 @@ export function navigateToEntity({
34
35
  entityId?: string | number;
35
36
  selectedTab?: string;
36
37
  copy?: boolean;
38
+ /**
39
+ * Pre-populate the new entity form with these values.
40
+ * Only applied when entityId is not set (i.e. "new" mode).
41
+ *
42
+ * Side panel: passed through EntitySidePanelProps → EntityEditView.
43
+ * Full screen: carried via React Router location.state so the route
44
+ * component can read it on mount without polluting the URL.
45
+ */
46
+ defaultValues?: Record<string, unknown>;
37
47
  path: string;
38
48
  sideEntityController: SideEntityController;
39
49
  onClose?: () => void;
@@ -49,7 +59,8 @@ export function navigateToEntity({
49
59
  selectedTab,
50
60
  collection,
51
61
  updateUrl: true,
52
- onClose
62
+ onClose,
63
+ defaultValues
53
64
  });
54
65
 
55
66
  } else {
@@ -68,7 +79,10 @@ export function navigateToEntity({
68
79
  if (copy) {
69
80
  to += "#copy";
70
81
  }
71
- navigation.navigate(to);
82
+ // Use React Router location.state to carry defaultValues — the correct SPA
83
+ // approach. No URL size limits, no encoding, nothing in the address bar.
84
+ // EntityFullScreenRoute reads location.state.defaultValues on mount.
85
+ navigation.navigate(to, defaultValues ? { state: { defaultValues } } : undefined);
72
86
  }
73
87
 
74
88
  }
@@ -35,16 +35,25 @@ export function getEntityTitlePropertyKey<M extends Record<string, unknown>>(col
35
35
  if (collection.titleProperty) {
36
36
  return collection.titleProperty as string;
37
37
  }
38
- // find first text field property
39
- for (const key in collection.properties) {
38
+
39
+ const orderToSearch = (collection.propertiesOrder as string[]) || Object.keys(collection.properties);
40
+ let firstStringCandidate: string | undefined;
41
+
42
+ for (const key of orderToSearch) {
40
43
  const property = collection.properties[key];
41
- if (!isPropertyBuilder(property)) {
44
+ if (property && !isPropertyBuilder(property)) {
42
45
  const prop = property as Property;
43
46
  if (prop.type === "string" && !prop.ui?.multiline && !prop.ui?.markdown && !prop.storage && !prop.isId) {
44
- return key;
47
+ if (!firstStringCandidate) {
48
+ firstStringCandidate = key;
49
+ }
50
+ const lowerKey = key.toLowerCase();
51
+ if (["name", "title", "label", "displayname", "username"].includes(lowerKey)) {
52
+ return key; // Immediate return if it's a strong title candidate
53
+ }
45
54
  }
46
55
  }
47
56
  }
48
- return undefined;
57
+ return firstStringCandidate;
49
58
  }
50
59