@firecms/core 3.0.0-beta.2-pre.3 → 3.0.0-beta.2-pre.5

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 (35) hide show
  1. package/README.md +2 -3
  2. package/dist/form/EntityForm.d.ts +2 -0
  3. package/dist/hooks/index.d.ts +1 -0
  4. package/dist/{internal → hooks}/useBuildNavigationController.d.ts +11 -4
  5. package/dist/index.es.js +3172 -3162
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +5 -5
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/types/fields.d.ts +0 -1
  10. package/dist/types/firecms.d.ts +6 -25
  11. package/dist/types/navigation.d.ts +2 -2
  12. package/dist/types/plugins.d.ts +0 -8
  13. package/package.json +7 -7
  14. package/src/components/ArrayContainer.tsx +1 -1
  15. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +131 -135
  16. package/src/components/EntityCollectionView/EntityCollectionView.tsx +1 -1
  17. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +0 -1
  18. package/src/components/VirtualTable/VirtualTableHeader.tsx +10 -4
  19. package/src/core/FireCMS.tsx +1 -16
  20. package/src/core/NavigationRoutes.tsx +10 -8
  21. package/src/form/EntityForm.tsx +237 -196
  22. package/src/form/PropertyFieldBinding.tsx +7 -7
  23. package/src/form/components/CustomIdField.tsx +35 -34
  24. package/src/form/components/ErrorFocus.tsx +3 -3
  25. package/src/form/field_bindings/BlockFieldBinding.tsx +13 -12
  26. package/src/hooks/index.tsx +1 -0
  27. package/src/{internal → hooks}/useBuildNavigationController.tsx +23 -17
  28. package/src/internal/useBuildSideEntityController.tsx +1 -1
  29. package/src/types/fields.tsx +1 -1
  30. package/src/types/firecms.tsx +7 -28
  31. package/src/types/navigation.ts +2 -2
  32. package/src/types/plugins.tsx +0 -9
  33. package/src/util/resolutions.ts +1 -1
  34. package/dist/form/components/SubmitListener.d.ts +0 -1
  35. package/src/form/components/SubmitListener.tsx +0 -32
@@ -20,7 +20,6 @@ export interface FieldProps<T extends CMSType = any, CustomProps = any, M extend
20
20
  /**
21
21
  * Initial value of this field
22
22
  */
23
- initialValue: T | undefined;
24
23
  /**
25
24
  * Set value of field directly
26
25
  */
@@ -3,7 +3,7 @@ import { User } from "./user";
3
3
  import { AuthController } from "./auth";
4
4
  import { DataSourceDelegate } from "./datasource";
5
5
  import { EntityCollection, EntityCustomView } from "./collections";
6
- import { CMSView } from "./navigation";
6
+ import { CMSView, NavigationController } from "./navigation";
7
7
  import { FireCMSContext } from "./firecms_context";
8
8
  import { PropertyConfig } from "./property_config";
9
9
  import { Locale } from "./locales";
@@ -62,24 +62,17 @@ export type FireCMSProps<UserType extends User, EC extends EntityCollection> = {
62
62
  */
63
63
  loading: boolean;
64
64
  }) => React.ReactNode;
65
- /**
66
- * List of the mapped collections in the CMS.
67
- * Each entry relates to a collection in the root database.
68
- * Each of the navigation entries in this field
69
- * generates an entry in the main menu.
70
- */
71
- collections?: EC[] | EntityCollectionsBuilder<EC>;
72
- /**
73
- * Custom additional views created by the developer, added to the main
74
- * navigation
75
- */
76
- views?: CMSView[] | CMSViewsBuilder;
77
65
  /**
78
66
  * Record of custom form fields to be used in the CMS.
79
67
  * You can use the key to reference the custom field in
80
68
  * the `propertyConfig` prop of a property in a collection.
81
69
  */
82
70
  propertyConfigs?: Record<string, PropertyConfig>;
71
+ /**
72
+ * This controller is in charge of the navigation of the CMS.
73
+ * It is in charge of resolving the collection and entity paths.
74
+ */
75
+ navigationController: NavigationController;
83
76
  /**
84
77
  * List of additional custom views for entities.
85
78
  * You can use the key to reference the custom view in
@@ -109,18 +102,6 @@ export type FireCMSProps<UserType extends User, EC extends EntityCollection> = {
109
102
  * Delegate for implementing your auth operations.
110
103
  */
111
104
  authController: AuthController<UserType>;
112
- /**
113
- * Path under the navigation routes of the CMS will be created. Defaults to `/`.
114
- * Internally FireCMS uses `react-router` to create the routes, the base path is attached to the
115
- * `BrowserRouter` component. If you are using FireCMS in a subpath of your website, you can use
116
- * this prop to specify the base path.
117
- */
118
- basePath?: string;
119
- /**
120
- * Path under the collection routes of the CMS will be created.
121
- * Defaults to `/c`.
122
- */
123
- baseCollectionPath?: string;
124
105
  /**
125
106
  * Use this controller to access the configuration that is stored locally,
126
107
  * and not defined in code
@@ -12,12 +12,12 @@ export type NavigationController = {
12
12
  * Each of the navigation entries in this field
13
13
  * generates an entry in the main menu.
14
14
  */
15
- collections: EntityCollection[];
15
+ collections?: EntityCollection[];
16
16
  /**
17
17
  * Custom additional views created by the developer, added to the main
18
18
  * navigation
19
19
  */
20
- views: CMSView[];
20
+ views?: CMSView[];
21
21
  /**
22
22
  * Configuration for the views that should be displayed at the top
23
23
  * level of the navigation (e.g. in the home page or the navigation
@@ -22,14 +22,6 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
22
22
  */
23
23
  loading?: boolean;
24
24
  collections?: {
25
- /**
26
- * Use this method to inject collections to the CMS.
27
- * You receive the current collections as a parameter, and you can return
28
- * a new list of collections.
29
- * @see {@link joinCollectionLists}
30
- * @param collections
31
- */
32
- injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
33
25
  /**
34
26
  * Use this component to add custom actions to the entity collections
35
27
  * toolbar.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.0.0-beta.2-pre.3",
4
+ "version": "3.0.0-beta.2-pre.5",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -38,15 +38,15 @@
38
38
  "generateIcons": "ts-node --esm src/icons/generateIcons.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@firecms/ui": "^3.0.0-beta.2-pre.3",
41
+ "@firecms/formex": "^3.0.0-beta.2-pre.5",
42
+ "@firecms/ui": "^3.0.0-beta.2-pre.5",
42
43
  "@fontsource/ibm-plex-mono": "^5.0.8",
43
44
  "@fontsource/roboto": "^5.0.8",
44
45
  "@hello-pangea/dnd": "^16.5.0",
45
46
  "date-fns": "^3.3.1",
46
- "formik": "^2.4.5",
47
47
  "history": "^5.3.0",
48
48
  "js-search": "^2.0.1",
49
- "markdown-it": "^13.0.2",
49
+ "markdown-it": "^14.0.0",
50
50
  "notistack": "^3.0.1",
51
51
  "object-hash": "^3.0.0",
52
52
  "react-image-file-resizer": "^0.4.8",
@@ -99,15 +99,15 @@
99
99
  "react-router-dom": "^6.22.0",
100
100
  "ts-jest": "^29.1.2",
101
101
  "ts-node": "^10.9.2",
102
- "tsd": "^0.28.1",
102
+ "tsd": "^0.30.4",
103
103
  "typescript": "^5.3.3",
104
- "vite": "^4.5.2"
104
+ "vite": "^5.1.1"
105
105
  },
106
106
  "files": [
107
107
  "dist",
108
108
  "src"
109
109
  ],
110
- "gitHead": "c78c58643c73ba776dfbcb8315d1a99160bb607a",
110
+ "gitHead": "5159646cfdb35e11cc510080bd3cff17104d0cb4",
111
111
  "publishConfig": {
112
112
  "access": "public"
113
113
  }
@@ -48,7 +48,7 @@ export function ArrayContainer<T>({
48
48
  value,
49
49
  disabled = false,
50
50
  buildEntry,
51
- size,
51
+ size = "medium",
52
52
  onInternalIdAdded,
53
53
  includeAddButton,
54
54
  newDefaultEntry,
@@ -14,7 +14,7 @@ import {
14
14
  ResolvedProperties,
15
15
  ResolvedProperty
16
16
  } from "../../../../types";
17
- import { Form, Formik, FormikProps } from "formik";
17
+ import { Formex, useCreateFormex } from "@firecms/formex";
18
18
  import { useDraggable } from "./useDraggable";
19
19
  import { CustomFieldValidator, getYupEntitySchema } from "../../../../form/validation";
20
20
  import { useWindowSize } from "./useWindowSize";
@@ -219,139 +219,134 @@ export function PopupFormFieldInternal<M extends Record<string, any>>({
219
219
  if (!entity)
220
220
  return <></>;
221
221
 
222
- const form = entity && (
222
+ // enableReinitialize={true}
223
+ // eslint-disable-next-line react-hooks/rules-of-hooks
224
+ const formex = useCreateFormex<M>({
225
+ initialValues: (entity?.values ?? {}) as EntityValues<M>,
226
+ validation: (values) => {
227
+ return validationSchema?.validate(values)
228
+ },
229
+ validateOnInitialRender: true,
230
+ onSubmit: (values, actions) => {
231
+ saveValue(values)
232
+ .then(() => onClose())
233
+ .finally(() => actions.setSubmitting(false));
234
+ }
235
+ });
236
+
237
+ const { values, isSubmitting, setFieldValue, handleSubmit } = formex;
238
+
239
+ // eslint-disable-next-line react-hooks/rules-of-hooks
240
+ useEffect(() => {
241
+ if (!equal(values, internalValue)) {
242
+ setInternalValue(values);
243
+ }
244
+ }, [values]);
245
+
246
+ if (!entity)
247
+ return <ErrorView
248
+ error={"PopupFormField misconfiguration"}/>;
249
+
250
+ if (!collection)
251
+ return <></>;
252
+
253
+ const disabled = isSubmitting;
254
+
255
+ const formContext: FormContext<M> = {
256
+ collection,
257
+ entityId,
258
+ values,
259
+ path,
260
+ setFieldValue,
261
+ save: saveValue
262
+ };
263
+
264
+ const property: ResolvedProperty<any> | undefined = propertyKey && getPropertyInPath(collection.properties, propertyKey as string);
265
+ const fieldProps: PropertyFieldBindingProps<any, M> | undefined = propertyKey && property
266
+ ? {
267
+ propertyKey: propertyKey as string,
268
+ disabled: isSubmitting || isReadOnly(property) || !!property.disabled,
269
+ property,
270
+ includeDescription: false,
271
+ underlyingValueHasChanged: false,
272
+ context: formContext,
273
+ tableMode: true,
274
+ partOfArray: false,
275
+ partOfBlock: false,
276
+ autoFocus: open
277
+ }
278
+ : undefined;
279
+
280
+ let internalForm = <>
223
281
  <div
224
- className={`text-gray-900 dark:text-white overflow-auto rounded rounded-md bg-white dark:bg-gray-950 ${!open ? "hidden" : ""} cursor-grab max-w-[100vw]`}>
225
- <Formik
226
- initialValues={(entity?.values ?? {}) as EntityValues<M>}
227
- enableReinitialize={true}
228
- validationSchema={validationSchema}
229
- validateOnMount={true}
230
- validate={(values) => console.debug("Validating", values)}
231
- onSubmit={(values, actions) => {
232
- saveValue(values)
233
- .then(() => onClose())
234
- .finally(() => actions.setSubmitting(false));
235
- }}
236
- >
237
- {({
238
- values,
239
- setFieldValue,
240
- handleSubmit,
241
- isSubmitting
242
- }: FormikProps<EntityValues<M>>) => {
243
-
244
- // eslint-disable-next-line react-hooks/rules-of-hooks
245
- useEffect(() => {
246
- if (!equal(values, internalValue)) {
247
- setInternalValue(values);
248
- }
249
- }, [values]);
250
-
251
- if (!entity)
252
- return <ErrorView
253
- error={"PopupFormField misconfiguration"}/>;
254
-
255
- if (!collection)
256
- return <></>;
257
-
258
- const disabled = isSubmitting;
259
-
260
- const formContext: FormContext<M> = {
261
- collection,
262
- entityId,
263
- values,
264
- path,
265
- setFieldValue,
266
- save: saveValue
267
- };
268
-
269
- const property: ResolvedProperty<any> | undefined = propertyKey && getPropertyInPath(collection.properties, propertyKey as string);
270
- const fieldProps: PropertyFieldBindingProps<any, M> | undefined = propertyKey && property
271
- ? {
272
- propertyKey: propertyKey as string,
273
- disabled: isSubmitting || isReadOnly(property) || !!property.disabled,
274
- property,
275
- includeDescription: false,
276
- underlyingValueHasChanged: false,
277
- context: formContext,
278
- tableMode: true,
279
- partOfArray: false,
280
- partOfBlock: false,
281
- autoFocus: open
282
- }
283
- : undefined;
284
-
285
- let internalForm = <>
286
- <div
287
- key={`popup_form_${tableKey}_${entityId}_${propertyKey}`}
288
- className="w-[560px] max-w-full max-h-[85vh]">
289
- <Form
290
- onSubmit={handleSubmit}
291
- noValidate>
292
-
293
- <div
294
- className="mb-1 p-4 flex flex-col relative">
295
- <div
296
- ref={innerRef}
297
- className="cursor-auto"
298
- style={{ cursor: "auto !important" }}>
299
- {fieldProps &&
300
- <PropertyFieldBinding {...fieldProps}/>}
301
- </div>
302
- </div>
303
-
304
- <DialogActions>
305
- <Button
306
- variant="filled"
307
- color="primary"
308
- type="submit"
309
- disabled={disabled}
310
- >
311
- Save
312
- </Button>
313
- </DialogActions>
314
-
315
- </Form>
316
-
317
- </div>
318
- </>;
319
-
320
- const plugins = customizationController.plugins;
321
- if (plugins) {
322
- // const formController: FormContext<M> = {
323
- // values,
324
- // setFieldValue
325
- // }
326
- plugins.forEach((plugin: FireCMSPlugin) => {
327
- if (plugin.form?.provider) {
328
- internalForm = (
329
- <plugin.form.provider.Component
330
- status={"existing"}
331
- path={path}
332
- collection={collection}
333
- entity={entity}
334
- context={fireCMSContext}
335
- currentEntityId={entityId}
336
- formContext={formContext}
337
- {...plugin.form.provider.props}>
338
- {internalForm}
339
- </plugin.form.provider.Component>
340
- );
341
- }
342
- });
343
- }
344
- return internalForm;
345
- }}
346
- </Formik>
347
-
348
- {savingError &&
349
- <Typography color={"error"}>
350
- {savingError.message}
351
- </Typography>
352
- }
282
+ key={`popup_form_${tableKey}_${entityId}_${propertyKey}`}
283
+ className="w-[560px] max-w-full max-h-[85vh]">
284
+ <form
285
+ onSubmit={handleSubmit}
286
+ noValidate>
287
+
288
+ <div
289
+ className="mb-1 p-4 flex flex-col relative">
290
+ <div
291
+ ref={innerRef}
292
+ className="cursor-auto"
293
+ style={{ cursor: "auto !important" }}>
294
+ {fieldProps &&
295
+ <PropertyFieldBinding {...fieldProps}/>}
296
+ </div>
297
+ </div>
298
+
299
+ <DialogActions>
300
+ <Button
301
+ variant="filled"
302
+ color="primary"
303
+ type="submit"
304
+ disabled={disabled}
305
+ >
306
+ Save
307
+ </Button>
308
+ </DialogActions>
309
+
310
+ </form>
311
+
353
312
  </div>
354
- );
313
+
314
+ </>;
315
+
316
+ const plugins = customizationController.plugins;
317
+ if (plugins) {
318
+ // const formController: FormContext<M> = {
319
+ // values,
320
+ // setFieldValue
321
+ // }
322
+ plugins.forEach((plugin: FireCMSPlugin) => {
323
+ if (plugin.form?.provider) {
324
+ internalForm = (
325
+ <plugin.form.provider.Component
326
+ status={"existing"}
327
+ path={path}
328
+ collection={collection}
329
+ entity={entity}
330
+ context={fireCMSContext}
331
+ currentEntityId={entityId}
332
+ formContext={formContext}
333
+ {...plugin.form.provider.props}>
334
+ {internalForm}
335
+ </plugin.form.provider.Component>
336
+ );
337
+ }
338
+ });
339
+ }
340
+ const form = <div
341
+ className={`text-gray-900 dark:text-white overflow-auto rounded rounded-md bg-white dark:bg-gray-950 ${!open ? "hidden" : ""} cursor-grab max-w-[100vw]`}>
342
+ {internalForm}
343
+
344
+ {savingError &&
345
+ <Typography color={"error"}>
346
+ {savingError.message}
347
+ </Typography>
348
+ }
349
+ </div>;
355
350
 
356
351
  const draggable = (
357
352
  <div
@@ -394,9 +389,10 @@ export function PopupFormFieldInternal<M extends Record<string, any>>({
394
389
 
395
390
  return (
396
391
  <Portal.Root asChild
397
- container={container}
398
- >
399
- {draggable}
392
+ container={container}>
393
+ <Formex value={formex}>
394
+ {draggable}
395
+ </Formex>
400
396
  </Portal.Root>
401
397
  );
402
398
 
@@ -57,7 +57,7 @@ import {
57
57
  Tooltip,
58
58
  Typography
59
59
  } from "@firecms/ui";
60
- import { setIn } from "formik";
60
+ import { setIn } from "@firecms/formex";
61
61
  import { getSubcollectionColumnId } from "../EntityCollectionTable/internal/common";
62
62
  import {
63
63
  OnCellValueChange,
@@ -112,7 +112,6 @@ export function StringNumberFilterField({
112
112
  updateFilter(operation, val);
113
113
  }}
114
114
  endAdornment={internalValue && <IconButton
115
- className="absolute right-3 top-2"
116
115
  onClick={(e) => updateFilter(operation, undefined)}>
117
116
  <ClearIcon/>
118
117
  </IconButton>}
@@ -198,7 +198,7 @@ function FilterForm<M>({
198
198
  onHover,
199
199
  createFilterField,
200
200
  hidden,
201
- setHidden,
201
+ setHidden
202
202
  }: FilterFormProps<M>) {
203
203
 
204
204
  const id = column.key;
@@ -232,7 +232,13 @@ function FilterForm<M>({
232
232
 
233
233
  if (!filterField) return null;
234
234
  return (
235
- <div className={"text-gray-900 dark:text-white"}>
235
+ <form noValidate={true}
236
+ onSubmit={(e) => {
237
+ e.stopPropagation();
238
+ e.preventDefault();
239
+ submit();
240
+ }}
241
+ className={"text-gray-900 dark:text-white"}>
236
242
  <div
237
243
  className={cn(defaultBorderMixin, "py-4 px-6 text-xs font-semibold uppercase border-b")}>
238
244
  {column.title ?? id}
@@ -252,9 +258,9 @@ function FilterForm<M>({
252
258
  <Button
253
259
  variant="outlined"
254
260
  color="primary"
255
- onClick={submit}>Filter</Button>
261
+ type="submit">Filter</Button>
256
262
  </div>
257
- </div>
263
+ </form>
258
264
  );
259
265
 
260
266
  }
@@ -15,7 +15,6 @@ import { SideDialogsControllerContext } from "../contexts/SideDialogsControllerC
15
15
  import { useLocaleConfig } from "../internal/useLocaleConfig";
16
16
  import { CenteredView } from "@firecms/ui";
17
17
  import { DialogsProvider } from "../contexts/DialogsProvider";
18
- import { useBuildNavigationController } from "../internal/useBuildNavigationController";
19
18
  import { useBuildDataSource } from "../internal/useBuildDataSource";
20
19
  import { useBuildCustomizationController } from "../internal/useBuildCustomizationController";
21
20
  import { CustomizationControllerContext } from "../contexts/CustomizationControllerContext";
@@ -50,25 +49,11 @@ export function FireCMS<UserType extends User, EC extends EntityCollection>(prop
50
49
  propertyConfigs,
51
50
  entityViews,
52
51
  components,
53
- baseCollectionPath,
54
- basePath,
55
- collections,
56
- views
52
+ navigationController,
57
53
  } = props;
58
54
 
59
55
  useLocaleConfig(locale);
60
56
 
61
- const navigationController = useBuildNavigationController({
62
- basePath,
63
- baseCollectionPath,
64
- authController,
65
- collections,
66
- views,
67
- userConfigPersistence,
68
- dataSource: dataSourceDelegate,
69
- plugins
70
- });
71
-
72
57
  /**
73
58
  * Controller in charge of fetching and persisting data
74
59
  */
@@ -2,7 +2,7 @@ import React from "react";
2
2
 
3
3
  import { Route, Routes, useLocation } from "react-router-dom";
4
4
  import { CMSView } from "../types";
5
- import { DefaultHomePage, EntityCollectionView, NotFoundPage } from "../components";
5
+ import { DefaultHomePage, EntityCollectionView, ErrorBoundary, NotFoundPage } from "../components";
6
6
  import { useNavigationController } from "../hooks";
7
7
  import { toArray } from "../util/arrays";
8
8
 
@@ -70,13 +70,15 @@ export const NavigationRoutes = React.memo<NavigationRoutesProps>(
70
70
  return <Route path={urlPath + "/*"}
71
71
  key={`navigation_${collection.id ?? collection.path}`}
72
72
  element={
73
- <EntityCollectionView
74
- key={`collection_view_${collection.id ?? collection.path}`}
75
- isSubCollection={false}
76
- parentCollectionIds={[]}
77
- fullPath={collection.id ?? collection.path}
78
- {...collection}
79
- Actions={toArray(collection.Actions)}/>
73
+ <ErrorBoundary>
74
+ <EntityCollectionView
75
+ key={`collection_view_${collection.id ?? collection.path}`}
76
+ isSubCollection={false}
77
+ parentCollectionIds={[]}
78
+ fullPath={collection.id ?? collection.path}
79
+ {...collection}
80
+ Actions={toArray(collection.Actions)}/>
81
+ </ErrorBoundary>
80
82
  }/>;
81
83
  }
82
84
  );