@firecms/core 3.0.0-canary.245 → 3.0.0-canary.247

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.
@@ -111,7 +111,8 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
111
111
  propertiesOrder?: (Extract<keyof M, string> | string | `subcollection:${string}` | "collectionGroupParent")[];
112
112
  /**
113
113
  * If enabled, content is loaded in batches. If `false` all entities in the
114
- * collection are loaded.
114
+ * collection are loaded. This means that when reaching the end of the
115
+ * collection, the CMS will load more entities.
115
116
  * You can specify a number to specify the pagination size (50 by default)
116
117
  * Defaults to `true`
117
118
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.0.0-canary.245",
4
+ "version": "3.0.0-canary.247",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -53,9 +53,9 @@
53
53
  "@dnd-kit/core": "^6.3.1",
54
54
  "@dnd-kit/modifiers": "^9.0.0",
55
55
  "@dnd-kit/sortable": "^10.0.0",
56
- "@firecms/editor": "^3.0.0-canary.245",
57
- "@firecms/formex": "^3.0.0-canary.245",
58
- "@firecms/ui": "^3.0.0-canary.245",
56
+ "@firecms/editor": "^3.0.0-canary.247",
57
+ "@firecms/formex": "^3.0.0-canary.247",
58
+ "@firecms/ui": "^3.0.0-canary.247",
59
59
  "@radix-ui/react-portal": "^1.1.3",
60
60
  "clsx": "^2.1.1",
61
61
  "date-fns": "^3.6.0",
@@ -107,7 +107,7 @@
107
107
  "dist",
108
108
  "src"
109
109
  ],
110
- "gitHead": "5e9c802a571bfa9e087a35d0282df4d6b5656d82",
110
+ "gitHead": "1383c05197c9ef33c04ee394c6455bb18e03bc94",
111
111
  "publishConfig": {
112
112
  "access": "public"
113
113
  },
@@ -488,6 +488,7 @@ export function ArrayContainer<T>({
488
488
  disabled={disabled || (value?.length ?? 0) >= max}
489
489
  startIcon={<AddIcon/>}
490
490
  onClick={insertInEnd}
491
+ className={"ml-3.5"}
491
492
  >
492
493
  {addLabel ?? "Add"}
493
494
  </Button>
@@ -5,6 +5,7 @@ import useMeasure from "react-use-measure";
5
5
  import { cls, DoNotDisturbOnIcon, Tooltip } from "@firecms/ui";
6
6
  import { ErrorBoundary } from "../../../components";
7
7
  import { getRowHeight, TableSize } from "../../common/table_height";
8
+ import { ErrorTooltip } from "../../ErrorTooltip";
8
9
 
9
10
  interface EntityTableCellProps {
10
11
  children: React.ReactNode;
@@ -183,7 +184,7 @@ export const EntityTableCell = React.memo<EntityTableCellProps>(
183
184
  ? "border-primary"
184
185
  : "border-transparent";
185
186
 
186
- return (
187
+ const result = <>
187
188
  <div
188
189
  className={cls(
189
190
  "transition-colors duration-100 ease-in-out",
@@ -241,7 +242,17 @@ export const EntityTableCell = React.memo<EntityTableCellProps>(
241
242
  </div>}
242
243
 
243
244
  </div>
244
- );
245
+ </>;
246
+ if (showError) {
247
+ return (
248
+ <ErrorTooltip
249
+ align={"start"}
250
+ title={error?.message ?? "Error"}>
251
+ {result}
252
+ </ErrorTooltip>
253
+ );
254
+ }
255
+ return result;
245
256
  }, (a, b) => {
246
257
  return a.error === b.error &&
247
258
  a.value === b.value &&
@@ -17,7 +17,6 @@ import { Formex, useCreateFormex } from "@firecms/formex";
17
17
  import { useDraggable } from "./useDraggable";
18
18
  import { CustomFieldValidator, getYupEntitySchema } from "../../../../form/validation";
19
19
  import { useWindowSize } from "./useWindowSize";
20
- import { ElementResizeListener } from "./ElementResizeListener";
21
20
  import { getPropertyInPath, isReadOnly, resolveCollection } from "../../../../util";
22
21
  import { Button, CloseIcon, DialogActions, IconButton, Typography } from "@firecms/ui";
23
22
  import { PropertyFieldBinding, yupToFormErrors } from "../../../../form";
@@ -71,7 +70,7 @@ export function PopupFormFieldLoading<M extends Record<string, any>>({
71
70
  collection: inputCollection
72
71
  }).then(setEntity);
73
72
  }
74
- }, [entityId, inputCollection]);
73
+ }, [entityId, inputCollection, dataSource, path]);
75
74
 
76
75
  if (!entity) return null;
77
76
  return <PopupFormFieldInternal {...{
@@ -147,33 +146,35 @@ export function PopupFormFieldInternal<M extends Record<string, any>>({
147
146
  };
148
147
  }, [cellRect, windowSize.height, windowSize.width]);
149
148
 
150
- const normalizePosition = useCallback(({
151
- x,
152
- y
153
- }: { x: number, y: number }) => {
154
-
155
- const draggableBoundingRect = draggableRef.current?.getBoundingClientRect();
156
- if (!draggableBoundingRect)
157
- throw Error("normalizePosition called before draggableBoundingRect is set");
158
-
149
+ const normalizePosition = useCallback((
150
+ pos: { x: number, y: number },
151
+ draggableBoundingRect: DOMRect,
152
+ currentWindowSize: { width: number, height: number }
153
+ ) => {
154
+ if (!draggableBoundingRect || draggableBoundingRect.width === 0 || draggableBoundingRect.height === 0) {
155
+ return pos;
156
+ }
159
157
  return {
160
- x: Math.max(0, Math.min(x, windowSize.width - draggableBoundingRect.width)),
161
- y: Math.max(0, Math.min(y, windowSize.height - draggableBoundingRect.height))
158
+ x: Math.max(0, Math.min(pos.x, currentWindowSize.width - draggableBoundingRect.width)),
159
+ y: Math.max(0, Math.min(pos.y, currentWindowSize.height - draggableBoundingRect.height))
162
160
  };
163
- }, [windowSize]);
161
+ }, []);
164
162
 
165
- const updatePopupLocation = useCallback((position?: {
163
+ const updatePopupLocation = useCallback((newPositionCandidate?: {
166
164
  x: number,
167
165
  y: number
168
166
  }) => {
169
-
170
167
  const draggableBoundingRect = draggableRef.current?.getBoundingClientRect();
171
- if (!cellRect || !draggableBoundingRect) return;
172
- // const newPosition = normalizePosition(position ?? getInitialLocation());
173
- const newPosition = position ?? normalizePosition(getInitialLocation());
174
- if (!popupLocation || newPosition.x !== popupLocation.x || newPosition.y !== popupLocation.y)
175
- setPopupLocation(newPosition);
176
- }, [cellRect, getInitialLocation, normalizePosition, popupLocation]);
168
+ if (!cellRect || !draggableBoundingRect || draggableBoundingRect.width === 0 || draggableBoundingRect.height === 0) {
169
+ return;
170
+ }
171
+ const basePosition = newPositionCandidate ?? getInitialLocation();
172
+ const newNormalizedPosition = normalizePosition(basePosition, draggableBoundingRect, windowSize);
173
+
174
+ if (!popupLocation || newNormalizedPosition.x !== popupLocation.x || newNormalizedPosition.y !== popupLocation.y) {
175
+ setPopupLocation(newNormalizedPosition);
176
+ }
177
+ }, [cellRect, getInitialLocation, normalizePosition, popupLocation, windowSize]);
177
178
 
178
179
  useDraggable({
179
180
  containerRef: draggableRef,
@@ -192,19 +193,25 @@ export function PopupFormFieldInternal<M extends Record<string, any>>({
192
193
 
193
194
  useLayoutEffect(
194
195
  () => {
196
+ if (!cellRect || initialPositionSet.current) return;
197
+ // Ensure draggableRef is available and has dimensions before initial positioning
195
198
  const draggableBoundingRect = draggableRef.current?.getBoundingClientRect();
196
- if (!cellRect || !draggableBoundingRect || initialPositionSet.current) return;
199
+ if (!draggableBoundingRect || draggableBoundingRect.width === 0 || draggableBoundingRect.height === 0) {
200
+ // If not ready, perhaps wait or log. For now, just return.
201
+ // This might need a retry mechanism or ensure content is rendered first.
202
+ return;
203
+ }
197
204
  updatePopupLocation();
198
205
  initialPositionSet.current = true;
199
206
  },
200
- [cellRect, updatePopupLocation, initialPositionSet.current]
207
+ [cellRect, updatePopupLocation] // Removed initialPositionSet.current from deps as it's a ref
201
208
  );
202
209
 
203
210
  useLayoutEffect(
204
211
  () => {
205
212
  updatePopupLocation(popupLocation);
206
213
  },
207
- [windowSize, cellRect]
214
+ [windowSize, cellRect, updatePopupLocation, popupLocation]
208
215
  );
209
216
 
210
217
  const validationSchema = useMemo(() => {
@@ -218,9 +225,28 @@ export function PopupFormFieldInternal<M extends Record<string, any>>({
218
225
  }, [collection, entityId, propertyKey, customFieldValidator]);
219
226
 
220
227
  const adaptResize = useCallback(() => {
228
+ // When the popup resizes, we want to re-evaluate its position
229
+ // based on its current location and new dimensions.
221
230
  return updatePopupLocation(popupLocation);
222
231
  }, [popupLocation, updatePopupLocation]);
223
232
 
233
+ // Setup ResizeObserver
234
+ useEffect(() => {
235
+ const element = draggableRef.current;
236
+ if (!element) return;
237
+
238
+ const observer = new ResizeObserver(() => {
239
+ adaptResize();
240
+ });
241
+
242
+ observer.observe(element);
243
+
244
+ return () => {
245
+ observer.unobserve(element);
246
+ observer.disconnect();
247
+ };
248
+ }, [adaptResize, draggableRef]);
249
+
224
250
  const saveValue = async (values: M) => {
225
251
  setSavingError(null);
226
252
  if (inputCollection && entity && onCellValueChange && propertyKey) {
@@ -372,7 +398,7 @@ export function PopupFormFieldInternal<M extends Record<string, any>>({
372
398
  } cursor-grab overflow-visible`}
373
399
  ref={draggableRef}>
374
400
 
375
- <ElementResizeListener onResize={adaptResize}/>
401
+ {/* ElementResizeListener removed from here */}
376
402
 
377
403
  <div
378
404
  className="overflow-hidden">
@@ -1,9 +1,8 @@
1
- import React, { useEffect } from "react";
1
+ import React, { useCallback, useEffect } from "react";
2
2
 
3
3
  interface DraggableProps {
4
4
  containerRef: React.RefObject<HTMLDivElement>,
5
5
  innerRef: React.RefObject<HTMLDivElement>,
6
- // ref: React.RefObject<HTMLDivElement>,
7
6
  x?: number;
8
7
  y?: number;
9
8
  onMove: (params: { x: number, y: number }) => void,
@@ -22,7 +21,7 @@ export function useDraggable({
22
21
 
23
22
  const listeningRef = React.useRef(false);
24
23
 
25
- const onMouseDown = (event: any) => {
24
+ const onMouseDown = useCallback((event: any) => {
26
25
  if (event.button !== 0 || !containerRef.current || event.defaultPrevented || event.innerClicked) {
27
26
  return;
28
27
  }
@@ -39,16 +38,17 @@ export function useDraggable({
39
38
  document.addEventListener("selectstart", onSelect);
40
39
  listeningRef.current = true;
41
40
  // event.stopPropagation();
42
- };
43
- const onMouseDownInner = (event: any) => {
41
+ }, [containerRef, onMove]);
42
+
43
+ const onMouseDownInner = useCallback((event: any) => {
44
44
  // @ts-ignore
45
45
  event.innerClicked = true;
46
- };
46
+ }, [])
47
47
 
48
- const onSelect = (event: any) => {
48
+ const onSelect = useCallback((event: any) => {
49
49
  event.preventDefault()
50
50
  event.stopPropagation();
51
- };
51
+ }, [])
52
52
 
53
53
  const onMouseUp = (event: any) => {
54
54
  document.removeEventListener("mousemove", onMouseMove);
@@ -92,6 +92,6 @@ export function useDraggable({
92
92
  if (innerCurrent)
93
93
  innerCurrent.removeEventListener("mousedown", onMouseDownInner);
94
94
  };
95
- });
95
+ }, [containerRef, innerRef, onMouseDownInner, onMouseDown]);
96
96
 
97
97
  }
@@ -425,6 +425,7 @@ export const EntityCollectionView = React.memo(
425
425
  Builder: ({ entity }) => (
426
426
  <Button color={"primary"}
427
427
  variant={"outlined"}
428
+ className={"max-w-full truncate justify-start"}
428
429
  startIcon={<KeyboardTabIcon size={"small"}/>}
429
430
  onClick={(event: any) => {
430
431
  event.stopPropagation();
@@ -7,6 +7,7 @@ import { useUserConfigurationPersistence } from "../../hooks/useUserConfiguratio
7
7
  import { IconButton, StarIcon } from "@firecms/ui";
8
8
  import { NavigationCard } from "./NavigationCard";
9
9
  import { SmallNavigationCard } from "./SmallNavigationCard";
10
+ import React from "react";
10
11
 
11
12
  /**
12
13
  * This is the component used in the home page to render a card for each
@@ -47,6 +48,7 @@ export function NavigationCardBinding({
47
48
  ? [
48
49
  <IconButton
49
50
  key={"favourite"}
51
+ size={"small"}
50
52
  onClick={(e) => {
51
53
  e.preventDefault();
52
54
  e.stopPropagation();
@@ -61,7 +63,7 @@ export function NavigationCardBinding({
61
63
  }
62
64
  }}>
63
65
  <StarIcon
64
- size={18}
66
+ size={"small"}
65
67
  className={favourite ? "text-secondary" : "text-surface-400 dark:text-surface-500"}/>
66
68
  </IconButton>
67
69
  ]
@@ -337,6 +337,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
337
337
  entity={entity}
338
338
  path={path}
339
339
  collection={collection}/>
340
+ <div className="h-16"/>
340
341
  </div>
341
342
  </div> : null;
342
343
 
@@ -214,7 +214,6 @@ function FieldInternal<T extends CMSType, CustomProps, M extends Record<string,
214
214
 
215
215
  const customFieldProps: any = property.customProps;
216
216
  const value = formexFieldProps.field.value;
217
- // const initialValue = formexFieldProps.meta.initialValue;
218
217
  const error = getIn(formexFieldProps.form.errors, propertyKey);
219
218
  const touched = getIn(formexFieldProps.form.touched, propertyKey);
220
219
 
@@ -6,7 +6,7 @@ import { ArrayContainer, ArrayEntryParams, ErrorView } from "../../components";
6
6
  import { getIconForProperty, getReferenceFrom } from "../../util";
7
7
 
8
8
  import { useNavigationController, useReferenceDialog } from "../../hooks";
9
- import { Button, cls, ExpandablePanel, fieldBackgroundMixin, Typography } from "@firecms/ui";
9
+ import { Button, cls, EditIcon, ExpandablePanel, fieldBackgroundMixin, Typography } from "@firecms/ui";
10
10
  import { useClearRestoreValue } from "../useClearRestoreValue";
11
11
 
12
12
  type ArrayOfReferencesFieldProps = FieldProps<EntityReference[]>;
@@ -127,11 +127,12 @@ export function ArrayOfReferencesFieldBinding({
127
127
  />
128
128
 
129
129
  <Button
130
- className="my-4 justify-center text-left"
130
+ className="ml-3.5 my-4 justify-center text-left"
131
131
  variant="text"
132
132
  color="primary"
133
133
  disabled={isSubmitting}
134
134
  onClick={onEntryClick}>
135
+ <EditIcon size={"small"}/>
135
136
  Edit {property.name}
136
137
  </Button>
137
138
  </div>}
@@ -21,6 +21,7 @@ export function MapFieldBinding({
21
21
  error,
22
22
  disabled,
23
23
  property,
24
+ partOfArray,
24
25
  minimalistView: minimalistViewProp,
25
26
  includeDescription,
26
27
  autoFocus,
@@ -51,7 +52,8 @@ export function MapFieldBinding({
51
52
  }
52
53
 
53
54
  const mapFormView = <>
54
- <div className={cls("py-1 flex flex-col space-y-2", minimalistView && property.widthPercentage !== undefined ? "mt-8" : undefined)}>
55
+ <div
56
+ className={cls("py-1 flex flex-col space-y-2", minimalistView && property.widthPercentage !== undefined ? "mt-8" : undefined)}>
55
57
  {Object.entries(mapProperties)
56
58
  .filter(([_, property]) => !isHidden(property))
57
59
  .map(([entryKey, childProperty], index) => {
@@ -81,8 +83,7 @@ export function MapFieldBinding({
81
83
  {...fieldBindingProps}/>
82
84
  </ErrorBoundary>
83
85
  </div>
84
- )
85
- ;
86
+ ) ;
86
87
  }
87
88
  )
88
89
  }
@@ -117,7 +118,7 @@ export function MapFieldBinding({
117
118
 
118
119
  <FieldHelperText includeDescription={includeDescription}
119
120
  showError={showError ?? false}
120
- error={error ? (typeof error === "string" ? error : "A property of this map has an error") : undefined}
121
+ error={error && !partOfArray ? (typeof error === "string" ? error : "A property of this map has an error") : undefined}
121
122
  disabled={disabled}
122
123
  property={property}/>
123
124
 
@@ -79,6 +79,7 @@ export function RepeatFieldBinding<T extends Array<any>>({
79
79
  minimalistView: false,
80
80
  autoFocus: internalId === lastAddedId,
81
81
  };
82
+ console.debug("Building entry for", index, fieldProps);
82
83
  return <ErrorBoundary>
83
84
  <PropertyFieldBinding {...fieldProps} index={index}/>
84
85
  </ErrorBoundary>;
@@ -127,7 +127,8 @@ export interface EntityCollection<M extends Record<string, any> = any, USER exte
127
127
 
128
128
  /**
129
129
  * If enabled, content is loaded in batches. If `false` all entities in the
130
- * collection are loaded.
130
+ * collection are loaded. This means that when reaching the end of the
131
+ * collection, the CMS will load more entities.
131
132
  * You can specify a number to specify the pagination size (50 by default)
132
133
  * Defaults to `true`
133
134
  */
@@ -1,5 +0,0 @@
1
- interface Props {
2
- onResize: (event: Event) => void;
3
- }
4
- export declare const ElementResizeListener: React.FC<Props>;
5
- export {};
@@ -1,59 +0,0 @@
1
- import { RefObject, useCallback, useEffect, useRef } from "react";
2
-
3
- interface Props {
4
- onResize: (event: Event) => void;
5
- }
6
-
7
- export const ElementResizeListener: React.FC<Props> = ({ onResize }) => {
8
- const rafRef = useRef(0);
9
- const objectRef: RefObject<HTMLObjectElement> = useRef(null);
10
- const onResizeRef = useRef(onResize);
11
-
12
- onResizeRef.current = onResize;
13
-
14
- const _onResize = useCallback((e: Event) => {
15
- if (rafRef.current) {
16
- cancelAnimationFrame(rafRef.current);
17
- }
18
- rafRef.current = requestAnimationFrame(() => {
19
- onResizeRef.current(e);
20
- });
21
- }, []);
22
-
23
- const onLoad = useCallback(() => {
24
- const obj = objectRef.current;
25
- if (obj && obj.contentDocument && obj.contentDocument.defaultView) {
26
- obj.contentDocument.defaultView.addEventListener("resize", _onResize);
27
- }
28
- }, [_onResize]);
29
-
30
- useEffect(() => {
31
- const obj = objectRef.current;
32
- return () => {
33
- if (obj && obj.contentDocument && obj.contentDocument.defaultView) {
34
- obj.contentDocument.defaultView.removeEventListener("resize", _onResize);
35
- }
36
- };
37
- }, [_onResize]);
38
-
39
- return (
40
- <object
41
- onLoad={onLoad}
42
- ref={objectRef}
43
- tabIndex={-1}
44
- type={"text/html"}
45
- data={"about:blank"}
46
- title={""}
47
- style={{
48
- position: "absolute",
49
- top: 0,
50
- left: 0,
51
- height: "100%",
52
- width: "100%",
53
- pointerEvents: "none",
54
- zIndex: -1,
55
- opacity: 0
56
- }}
57
- />
58
- );
59
- };