@rebasepro/admin 0.4.0 → 0.5.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 (49) hide show
  1. package/README.md +135 -0
  2. package/dist/{CollectionEditorDialog-Bo86h-d-.js → CollectionEditorDialog-C8E440oK.js} +3 -3
  3. package/dist/CollectionEditorDialog-C8E440oK.js.map +1 -0
  4. package/dist/{CollectionsStudioView-bsg4AT_Z.js → CollectionsStudioView-Brr_b06v.js} +4 -4
  5. package/dist/{CollectionsStudioView-bsg4AT_Z.js.map → CollectionsStudioView-Brr_b06v.js.map} +1 -1
  6. package/dist/{ExportCollectionAction-Cf-Id5uO.js → ExportCollectionAction-D3TTYXrq.js} +2 -2
  7. package/dist/{ExportCollectionAction-Cf-Id5uO.js.map → ExportCollectionAction-D3TTYXrq.js.map} +1 -1
  8. package/dist/{ImportCollectionAction-LN5sreK9.js → ImportCollectionAction-Dn7lya6E.js} +2 -2
  9. package/dist/{ImportCollectionAction-LN5sreK9.js.map → ImportCollectionAction-Dn7lya6E.js.map} +1 -1
  10. package/dist/{PropertyEditView-jCWCxmCz.js → PropertyEditView-ChBleIbN.js} +4 -4
  11. package/dist/PropertyEditView-ChBleIbN.js.map +1 -0
  12. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +1 -0
  13. package/dist/collection_editor/ui/collection_editor/templates/blog_template.d.ts +1 -1
  14. package/dist/collection_editor/ui/collection_editor/templates/pages_template.d.ts +1 -1
  15. package/dist/collection_editor/ui/collection_editor/templates/products_template.d.ts +1 -1
  16. package/dist/collection_editor/ui/collection_editor/templates/users_template.d.ts +1 -1
  17. package/dist/collection_editor_ui.js +3 -3
  18. package/dist/{index-z3H8iqsj.js → index-D3S9x_RP.js} +2 -2
  19. package/dist/{index-z3H8iqsj.js.map → index-D3S9x_RP.js.map} +1 -1
  20. package/dist/{index-Cb0bPnzZ.js → index-D9RNzm01.js} +3 -3
  21. package/dist/index-D9RNzm01.js.map +1 -0
  22. package/dist/{index-BrqSvMG0.js → index-DrrT332R.js} +3 -3
  23. package/dist/index-DrrT332R.js.map +1 -0
  24. package/dist/index.js +4 -4
  25. package/dist/{util-CvNp6A-r.js → util-COYYD8zr.js} +79 -110
  26. package/dist/util-COYYD8zr.js.map +1 -0
  27. package/package.json +9 -10
  28. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +2 -2
  29. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +2 -1
  30. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +2 -2
  31. package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +2 -1
  32. package/src/collection_editor/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +1 -1
  33. package/src/collection_editor/ui/collection_editor/templates/blog_template.ts +1 -1
  34. package/src/collection_editor/ui/collection_editor/templates/pages_template.ts +1 -1
  35. package/src/collection_editor/ui/collection_editor/templates/products_template.ts +1 -1
  36. package/src/collection_editor/ui/collection_editor/templates/users_template.ts +1 -1
  37. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +5 -2
  38. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +3 -4
  39. package/src/components/EntityDetailView.tsx +6 -9
  40. package/src/components/EntityEditView.tsx +9 -15
  41. package/src/components/EntitySidePanel.tsx +3 -0
  42. package/src/form/PropertyFieldBinding.tsx +1 -1
  43. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  44. package/src/form/field_bindings/MapFieldBinding.tsx +3 -45
  45. package/dist/CollectionEditorDialog-Bo86h-d-.js.map +0 -1
  46. package/dist/PropertyEditView-jCWCxmCz.js.map +0 -1
  47. package/dist/index-BrqSvMG0.js.map +0 -1
  48. package/dist/index-Cb0bPnzZ.js.map +0 -1
  49. package/dist/util-CvNp6A-r.js.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/admin",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "description": "Rebase CMS — content management views, forms, and routing",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -80,17 +80,16 @@
80
80
  "prosemirror-tables": "^1.8.5",
81
81
  "prosemirror-transform": "^1.12.0",
82
82
  "prosemirror-view": "^1.41.8",
83
- "react-compiler-runtime": "1.0.0",
83
+ "zod": "^3.25.76",
84
84
  "react-dropzone": "^14.4.1",
85
85
  "react-use-measure": "^2.1.7",
86
- "zod": "^3.25.76",
87
- "@rebasepro/core": "0.4.0",
88
- "@rebasepro/common": "0.4.0",
89
- "@rebasepro/formex": "0.4.0",
90
- "@rebasepro/schema-inference": "0.4.0",
91
- "@rebasepro/types": "0.4.0",
92
- "@rebasepro/utils": "0.4.0",
93
- "@rebasepro/ui": "0.4.0"
86
+ "@rebasepro/common": "0.5.0",
87
+ "@rebasepro/formex": "0.5.0",
88
+ "@rebasepro/core": "0.5.0",
89
+ "@rebasepro/schema-inference": "0.5.0",
90
+ "@rebasepro/utils": "0.5.0",
91
+ "@rebasepro/types": "0.5.0",
92
+ "@rebasepro/ui": "0.5.0"
94
93
  },
95
94
  "peerDependencies": {
96
95
  "react": ">=19.0.0",
@@ -85,7 +85,7 @@ export function CollectionRelationsTab() {
85
85
  <Button variant="filled" color="neutral" onClick={() => {
86
86
  setEditingRelationIndex(-1);
87
87
  setEditingRelationState({ relationName: "",
88
- target: "" as unknown as Relation["target"],
88
+ target: "",
89
89
  cardinality: "many",
90
90
  direction: "owning" });
91
91
  }}>
@@ -134,7 +134,7 @@ direction: "owning" });
134
134
  <Button variant="text" onClick={() => {
135
135
  setEditingRelationIndex(-1);
136
136
  setEditingRelationState({ relationName: "",
137
- target: "" as unknown as Relation["target"],
137
+ target: "",
138
138
  cardinality: "many",
139
139
  direction: "owning" });
140
140
  }}>Create your first relation</Button>
@@ -50,7 +50,8 @@ import { MarkdownPropertyField } from "./properties/MarkdownPropertyField";
50
50
  import { isValidRegExp, mergeDeep } from "@rebasepro/utils";
51
51
 
52
52
  export type PropertyWithId = Property & {
53
- id?: string
53
+ id?: string;
54
+ conditions?: import('@rebasepro/types').PropertyConditions;
54
55
  };
55
56
 
56
57
  export type OnPropertyChangedParams = {
@@ -76,7 +76,7 @@ export function PropertyFieldPreview({
76
76
 
77
77
  <ErrorBoundary>
78
78
  <Typography variant="caption" component="span" className="ml-auto shrink-0 text-text-disabled dark:text-text-disabled-dark font-mono bg-surface-100 dark:bg-surface-900 px-1.5 py-0.5 rounded mr-8">
79
- {("columnType" in property ? (property as Property & { columnType?: string }).columnType : undefined) || property.type}
79
+ {"columnType" in property ? (property as { columnType?: string }).columnType ?? property.type : property.type}
80
80
  </Typography>
81
81
  </ErrorBoundary>
82
82
 
@@ -146,7 +146,7 @@ export function NonEditablePropertyPreview({
146
146
 
147
147
  {property && !isPropertyBuilder(property) && <ErrorBoundary>
148
148
  <Typography variant="caption" component="span" className="ml-auto shrink-0 text-text-disabled dark:text-text-disabled-dark font-mono bg-surface-100 dark:bg-surface-900 px-1.5 py-0.5 rounded mr-8">
149
- {("columnType" in property ? (property as Property & { columnType?: string }).columnType : undefined) || property.type}
149
+ {"columnType" in property ? (property as { columnType?: string }).columnType ?? property.type : property.type}
150
150
  </Typography>
151
151
  </ErrorBoundary>}
152
152
  </Paper>
@@ -18,6 +18,7 @@ import {
18
18
  } from "@rebasepro/ui";
19
19
  import { getFieldConfig, DEFAULT_FIELD_CONFIGS } from "../../../../_cms_internals";
20
20
  import { Properties, Property, EnumValueConfig } from "@rebasepro/types";
21
+ import type { PropertyConditions } from "@rebasepro/types";
21
22
  import { isPropertyBuilder } from "@rebasepro/common";
22
23
  import { PropertyWithId } from "../../PropertyEditView";
23
24
  import { getPropertyPaths } from "./property_paths";
@@ -817,7 +818,7 @@ export function ConditionsEditor({ disabled, collectionProperties }: ConditionsE
817
818
  : [];
818
819
 
819
820
  // Get current conditions from form values
820
- const conditions: Record<string, unknown> = (values as PropertyWithId & { conditions?: Record<string, unknown> }).conditions ?? {};
821
+ const conditions: PropertyConditions = values.conditions ?? {};
821
822
 
822
823
  const activeConditions: { type: ConditionType; group: ConditionGroup }[] = [];
823
824
 
@@ -528,7 +528,7 @@ export function EnumConditionsEditor({ disabled, collectionProperties }: EnumCon
528
528
  const allEnumIds = propertyEnumValues.map(ev => String(ev.id));
529
529
 
530
530
  // Get conditions from the correct path: values.conditions
531
- const conditions = (values as PropertyWithId & { conditions?: Record<string, unknown> }).conditions ?? {};
531
+ const conditions = values.conditions ?? {};
532
532
 
533
533
  // Parse existing enum conditions
534
534
  const activeConditions: { type: EnumConditionType; config: EnumConditionConfig }[] = [];
@@ -1,4 +1,4 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import type { EntityCollection } from "@rebasepro/types";
2
2
 
3
3
  export const blogCollectionTemplate = {
4
4
  slug: "blog",
@@ -1,4 +1,4 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import type { EntityCollection } from "@rebasepro/types";
2
2
 
3
3
  export const pagesCollectionTemplate = {
4
4
  slug: "pages",
@@ -1,4 +1,4 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import type { EntityCollection } from "@rebasepro/types";
2
2
 
3
3
  export const productsCollectionTemplate = {
4
4
  slug: "products",
@@ -1,4 +1,4 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import type { EntityCollection } from "@rebasepro/types";
2
2
 
3
3
  export const usersCollectionTemplate = {
4
4
  slug: "users",
@@ -1,5 +1,8 @@
1
1
  import React, { useCallback, useEffect } from "react";
2
2
 
3
+ /** Tracks events originating inside the inner (non-draggable) area. */
4
+ const innerClicked = new WeakSet<MouseEvent>();
5
+
3
6
  interface DraggableProps {
4
7
  containerRef: React.RefObject<HTMLDivElement | null>,
5
8
  innerRef: React.RefObject<HTMLDivElement | null>,
@@ -22,7 +25,7 @@ export function useDraggable({
22
25
  const listeningRef = React.useRef(false);
23
26
 
24
27
  const onMouseDown = useCallback((event: MouseEvent) => {
25
- if (event.button !== 0 || !containerRef.current || event.defaultPrevented || (event as MouseEvent & { innerClicked?: boolean }).innerClicked) {
28
+ if (event.button !== 0 || !containerRef.current || event.defaultPrevented || innerClicked.has(event)) {
26
29
  return;
27
30
  }
28
31
 
@@ -41,7 +44,7 @@ export function useDraggable({
41
44
  }, [containerRef, onMove]);
42
45
 
43
46
  const onMouseDownInner = useCallback((event: MouseEvent) => {
44
- (event as MouseEvent & { innerClicked?: boolean }).innerClicked = true;
47
+ innerClicked.add(event);
45
48
  }, [])
46
49
 
47
50
  const onSelect = useCallback((event: Event) => {
@@ -451,9 +451,8 @@ function resolveRelationDisplayName(
451
451
 
452
452
  // Helper to check if a property is hidden/internal
453
453
  const isHiddenProp = (p: Property): boolean => {
454
- const ui = (p as any).ui;
455
- if (ui?.hideFromCollection) return true;
456
- if (ui?.disabled?.hidden) return true;
454
+ if (p.ui?.hideFromCollection) return true;
455
+ if (typeof p.ui?.disabled === "object" && p.ui.disabled.hidden) return true;
457
456
  return false;
458
457
  };
459
458
 
@@ -502,7 +501,7 @@ function resolveRelationDisplayName(
502
501
  // 2. Try the entity cache (sessionStorage) as a fallback
503
502
  if (id !== undefined && targetCollection) {
504
503
  try {
505
- const slug = targetCollection.slug ?? (targetCollection as any).table;
504
+ const slug = targetCollection.slug ?? ("table" in targetCollection ? targetCollection.table : undefined);
506
505
  if (slug) {
507
506
  const cacheKey = `${slug}/${id}`;
508
507
  const cached = getEntityFromCache(cacheKey) as { values?: Record<string, unknown> } | undefined;
@@ -152,27 +152,23 @@ function EntityDetailViewInner<M extends Record<string, unknown>>({
152
152
  { status: "existing", entityId }
153
153
  ), [collection, entityId]);
154
154
 
155
- // Track whether we've applied the defaultSelectedView already.
156
- // See EntityEditView for a full explanation of this pattern.
157
- const hasAppliedDefault = useRef(false);
155
+ // Track whether the user has explicitly clicked a tab in this component
156
+ // instance. See EntityEditViewInner for full explanation.
157
+ const userHasChangedTab = useRef(false);
158
158
 
159
159
  const [selectedTab, setSelectedTab] = useState<string>(() => {
160
160
  const val = selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE;
161
- hasAppliedDefault.current = true;
162
161
  return val === "edit" ? MAIN_TAB_VALUE : val;
163
162
  });
164
163
  useEffect(() => {
165
- const val = hasAppliedDefault.current
164
+ const val = userHasChangedTab.current
166
165
  ? (selectedTabProp ?? MAIN_TAB_VALUE)
167
166
  : (selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
168
167
  const target = val === "edit" ? MAIN_TAB_VALUE : val;
169
- if (!hasAppliedDefault.current) {
170
- hasAppliedDefault.current = true;
171
- }
172
168
  if (target !== selectedTab) {
173
169
  setSelectedTab(target);
174
170
  }
175
- }, [selectedTabProp]);
171
+ }, [selectedTabProp, defaultSelectedView]);
176
172
 
177
173
  const subcollections = getSubcollections(collection).filter(c => !c.hideFromNavigation);
178
174
  const subcollectionsCount = subcollections?.length ?? 0;
@@ -347,6 +343,7 @@ function EntityDetailViewInner<M extends Record<string, unknown>>({
347
343
  }).filter(Boolean);
348
344
 
349
345
  const onSideTabClick = useCallback((value: string) => {
346
+ userHasChangedTab.current = true;
350
347
  setSelectedTab(value);
351
348
  onTabChange?.({
352
349
  path,
@@ -224,33 +224,26 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
224
224
  }
225
225
  ), [collection, status, entityId]);
226
226
 
227
- // Track whether we've applied the defaultSelectedView already.
228
- // On the initial render, selectedTabProp may be undefined because the URL
229
- // has no tab segment in that case we fall back to defaultSelectedView.
230
- // But once the component is mounted, selectedTabProp=undefined means the
231
- // user explicitly selected the form tab (which has no URL segment), so we
232
- // must NOT re-apply defaultSelectedView.
233
- const hasAppliedDefault = useRef(false);
227
+ // Track whether the user has explicitly clicked a tab in this component
228
+ // instance. When false, we fall back to defaultSelectedView (which may
229
+ // resolve asynchronously if the collection loads from the registry after
230
+ // mount). Once true, selectedTabProp=undefined means the user selected
231
+ // the form tab, so we must NOT re-apply defaultSelectedView.
232
+ const userHasChangedTab = useRef(false);
234
233
 
235
234
  const [selectedTab, setSelectedTab] = useState<string>(() => {
236
235
  const val = selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE;
237
- hasAppliedDefault.current = true;
238
236
  return val === "edit" ? MAIN_TAB_VALUE : val;
239
237
  });
240
238
  useEffect(() => {
241
- // After initial mount, only sync from selectedTabProp — don't fall
242
- // back to defaultSelectedView, because undefined now means "form tab".
243
- const val = hasAppliedDefault.current
239
+ const val = userHasChangedTab.current
244
240
  ? (selectedTabProp ?? MAIN_TAB_VALUE)
245
241
  : (selectedTabProp ?? defaultSelectedView ?? MAIN_TAB_VALUE);
246
242
  const target = val === "edit" ? MAIN_TAB_VALUE : val;
247
- if (!hasAppliedDefault.current) {
248
- hasAppliedDefault.current = true;
249
- }
250
243
  if (target !== selectedTab) {
251
244
  setSelectedTab(target);
252
245
  }
253
- }, [selectedTabProp]);
246
+ }, [selectedTabProp, defaultSelectedView]);
254
247
 
255
248
  const subcollections = getSubcollections(collection).filter(c => !c.hideFromNavigation);
256
249
  const subcollectionsCount = subcollections?.length ?? 0;
@@ -420,6 +413,7 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
420
413
  }).filter(Boolean);
421
414
 
422
415
  const onSideTabClick = useCallback((value: string) => {
416
+ userHasChangedTab.current = true;
423
417
  setSelectedTab(value);
424
418
  if (status === "existing") {
425
419
  onTabChange?.({
@@ -102,6 +102,9 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
102
102
  // editor / Studio and is not available on the collection object the
103
103
  // caller passed to sideEntityController.open().
104
104
  const hasCorrectedDefaultView = useRef(false);
105
+ useEffect(() => {
106
+ hasCorrectedDefaultView.current = false;
107
+ }, [entityId]);
105
108
  useEffect(() => {
106
109
  if (hasCorrectedDefaultView.current) return;
107
110
  if (selectedTab) return; // Already has a tab — no correction needed
@@ -219,7 +219,7 @@ function FieldInternal<CustomProps, M extends Record<string, unknown>>
219
219
  collection: context.collection,
220
220
  propertyKey: propertyKey,
221
221
  property: property,
222
- Component: Component as any, // Cast component here since it is a generic component type
222
+ Component: Component as ComponentType<FieldProps<Property, unknown, M>>,
223
223
  plugins: plugins
224
224
  });
225
225
  const UsedComponent: ComponentType<FieldProps<Property, any, any>> = WrappedComponent ?? Component;
@@ -348,7 +348,7 @@ function MapKeyValueRow<T extends Record<string, unknown>>({
348
348
  value={arrayValue[index]}
349
349
  disabled={disabled || !fieldKey}
350
350
  setValue={(newValue) => {
351
- const newArrayValue = [...arrayValue] as any[];
351
+ const newArrayValue = [...arrayValue] as unknown[];
352
352
  newArrayValue[index] = newValue;
353
353
  setValue({
354
354
  ...value,
@@ -1,4 +1,3 @@
1
- import type { Properties } from "@rebasepro/types";
2
1
  import type { FieldProps, PropertyFieldBindingProps } from "../../types/fields";
3
2
  import type { MapProperty } from "@rebasepro/types";
4
3
  import React from "react";
@@ -10,8 +9,7 @@ import { getIconForProperty } from "../../util/property_utils";
10
9
  import { isHidden, isReadOnly } from "@rebasepro/common";
11
10
  import { FieldHelperText, LabelWithIconAndTooltip } from "../components";
12
11
  import { PropertyFieldBinding } from "../PropertyFieldBinding";
13
- import { cls, ExpandablePanel, InputLabel, Select, SelectItem } from "@rebasepro/ui";
14
- import { pick } from "@rebasepro/utils";
12
+ import { cls, ExpandablePanel } from "@rebasepro/ui";
15
13
 
16
14
  /**
17
15
  * Field that renders the children property fields
@@ -35,7 +33,6 @@ export function MapFieldBinding({
35
33
  onPropertyChange
36
34
  }: FieldProps<MapProperty>) {
37
35
 
38
- const pickOnlySomeKeys = property.pickOnlySomeKeys || false;
39
36
  const expanded = property.ui?.expanded === undefined ? true : property.ui?.expanded;
40
37
  const minimalistView = minimalistViewProp || property.ui?.minimalistView;
41
38
  const { t } = useTranslation();
@@ -44,19 +41,7 @@ export function MapFieldBinding({
44
41
  throw Error(`You need to specify a 'properties' prop (or specify a custom field) in your map property ${propertyKey}`);
45
42
  }
46
43
 
47
- let mapProperties: Properties;
48
- if (pickOnlySomeKeys) {
49
- if (value) {
50
- mapProperties = pick(property.properties,
51
- ...Object.keys(value)
52
- .filter(key => key in property.properties!)
53
- ) as Properties;
54
- } else {
55
- mapProperties = {};
56
- }
57
- } else {
58
- mapProperties = property.properties;
59
- }
44
+ const mapProperties = property.properties;
60
45
 
61
46
  const mapFormView = <>
62
47
  <div
@@ -96,7 +81,7 @@ export function MapFieldBinding({
96
81
  }
97
82
  </div>
98
83
 
99
- {/*{pickOnlySomeKeys && buildPickKeysSelect(disabled, property.properties, setValue, value)}*/}
84
+
100
85
 
101
86
  </>
102
87
  ;
@@ -133,30 +118,3 @@ export function MapFieldBinding({
133
118
  );
134
119
  }
135
120
 
136
- const buildPickKeysSelect = (disabled: boolean, properties: Properties, setValue: (value: Record<string, unknown> | null) => void, value: Record<string, unknown> | undefined) => {
137
-
138
- const keys = Object.keys(properties)
139
- .filter((key) => !value || !(key in value));
140
-
141
- const handleAddProperty = (updatedValue: string | string[]) => {
142
- setValue({
143
- ...value,
144
- [updatedValue as string]: null
145
- });
146
- };
147
-
148
- if (!keys.length) return <></>;
149
-
150
- return <div className={"m-4"}>
151
- <InputLabel>Add property</InputLabel>
152
- <Select
153
- value={""}
154
- fullWidth={true}
155
- disabled={disabled}
156
- onValueChange={handleAddProperty}
157
- renderValue={(key) => (properties as Properties)[key].name || key}>
158
- {keys.map((key) => <SelectItem key={key}
159
- value={key}>{(properties as Properties)[key].name || key}</SelectItem>)}
160
- </Select>
161
- </div>;
162
- };