@firecms/collection_editor 3.0.0-beta.2-pre.2 → 3.0.0-beta.2-pre.3

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 (71) hide show
  1. package/dist/form/Field.d.ts +53 -0
  2. package/dist/form/Formex.d.ts +4 -0
  3. package/dist/form/index.d.ts +5 -0
  4. package/dist/form/types.d.ts +25 -0
  5. package/dist/form/useCreateFormex.d.ts +9 -0
  6. package/dist/form/utils.d.ts +44 -0
  7. package/dist/index.es.js +2612 -2328
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/index.umd.js +2 -2
  10. package/dist/index.umd.js.map +1 -1
  11. package/dist/types/collection_editor_controller.d.ts +3 -2
  12. package/dist/types/config_controller.d.ts +3 -3
  13. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -5
  14. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
  15. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -2
  16. package/dist/ui/collection_editor/EnumForm.d.ts +1 -2
  17. package/dist/ui/collection_editor/PropertyEditView.d.ts +5 -5
  18. package/dist/ui/collection_editor/PropertyTree.d.ts +14 -13
  19. package/dist/ui/collection_editor/SwitchControl.d.ts +8 -0
  20. package/dist/ui/collection_editor/properties/CommonPropertyFields.d.ts +0 -1
  21. package/dist/ui/collection_editor/util.d.ts +1 -0
  22. package/package.json +5 -5
  23. package/src/ConfigControllerProvider.tsx +23 -21
  24. package/src/form/Field.tsx +162 -0
  25. package/src/form/Formex.tsx +8 -0
  26. package/src/form/README.md +165 -0
  27. package/src/form/index.ts +5 -0
  28. package/src/form/types.ts +27 -0
  29. package/src/form/useCreateFormex.tsx +137 -0
  30. package/src/form/utils.ts +169 -0
  31. package/src/types/collection_editor_controller.tsx +4 -3
  32. package/src/types/config_controller.tsx +3 -3
  33. package/src/ui/CollectionViewHeaderAction.tsx +1 -1
  34. package/src/ui/EditorCollectionAction.tsx +3 -3
  35. package/src/ui/HomePageEditorCollectionAction.tsx +2 -2
  36. package/src/ui/MissingReferenceWidget.tsx +2 -1
  37. package/src/ui/NewCollectionButton.tsx +3 -3
  38. package/src/ui/NewCollectionCard.tsx +2 -1
  39. package/src/ui/PropertyAddColumnComponent.tsx +1 -1
  40. package/src/ui/RootCollectionSuggestions.tsx +2 -1
  41. package/src/ui/collection_editor/CollectionDetailsForm.tsx +2 -2
  42. package/src/ui/collection_editor/CollectionEditorDialog.tsx +422 -374
  43. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +19 -12
  44. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +26 -18
  45. package/src/ui/collection_editor/EnumForm.tsx +118 -114
  46. package/src/ui/collection_editor/GetCodeDialog.tsx +1 -1
  47. package/src/ui/collection_editor/PropertyEditView.tsx +198 -142
  48. package/src/ui/collection_editor/PropertyFieldPreview.tsx +5 -1
  49. package/src/ui/collection_editor/PropertyTree.tsx +132 -113
  50. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +18 -11
  51. package/src/ui/collection_editor/SwitchControl.tsx +39 -0
  52. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +10 -2
  53. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +2 -2
  54. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +13 -9
  55. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +11 -37
  56. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +2 -2
  57. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -6
  58. package/src/ui/collection_editor/properties/MapPropertyField.tsx +2 -2
  59. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +2 -2
  60. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +11 -14
  61. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +10 -9
  62. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +15 -9
  63. package/src/ui/collection_editor/properties/StringPropertyField.tsx +2 -2
  64. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +2 -2
  65. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +27 -18
  66. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +2 -2
  67. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +27 -16
  68. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +33 -18
  69. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +99 -80
  70. package/src/ui/collection_editor/util.ts +7 -0
  71. package/src/ui/collection_editor/utils/strings.ts +2 -1
@@ -1,53 +1,60 @@
1
1
  import React, { useEffect, useState } from "react";
2
2
  import { EntityCollection, unslugify, } from "@firecms/core";
3
3
  import { Button, Card, Chip, CircularProgress, cn, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
4
- import { useFormikContext } from "formik";
5
4
 
6
5
  import { productsCollectionTemplate } from "./templates/products_template";
7
6
  import { blogCollectionTemplate } from "./templates/blog_template";
8
7
  import { usersCollectionTemplate } from "./templates/users_template";
9
8
  import { ImportFileUpload } from "@firecms/data_import_export";
10
9
  import { pagesCollectionTemplate } from "./templates/pages_template";
10
+ import { useFormex } from "../../form";
11
11
 
12
12
  export function CollectionEditorWelcomeView({
13
13
  path,
14
14
  pathSuggestions,
15
15
  parentCollection,
16
16
  onContinue,
17
- collections
17
+ existingCollectionPaths
18
18
  }: {
19
19
  path: string;
20
20
  pathSuggestions?: (path: string) => Promise<string[]>;
21
21
  parentCollection?: EntityCollection;
22
22
  onContinue: (importData?: object[]) => void;
23
- collections?: EntityCollection[];
23
+ existingCollectionPaths?: string[];
24
24
  }) {
25
25
 
26
26
  const [loadingPathSuggestions, setLoadingPathSuggestions] = useState(false);
27
27
  const [filteredPathSuggestions, setFilteredPathSuggestions] = useState<string[] | undefined>();
28
28
  useEffect(() => {
29
- if (pathSuggestions && collections) {
29
+ if (pathSuggestions && existingCollectionPaths) {
30
30
  setLoadingPathSuggestions(true);
31
31
  pathSuggestions(path)
32
32
  .then(suggestions => {
33
- const filteredSuggestions = suggestions.filter(s => !collections.find(c => c.path.trim().toLowerCase() === s.trim().toLowerCase()));
33
+ const filteredSuggestions = suggestions.filter(s => !(existingCollectionPaths ?? []).find(c => c.trim().toLowerCase() === s.trim().toLowerCase()));
34
34
  setFilteredPathSuggestions(filteredSuggestions);
35
35
  })
36
36
  .finally(() => setLoadingPathSuggestions(false));
37
37
  }
38
- }, [collections, path, pathSuggestions]);
38
+ }, [existingCollectionPaths, path, pathSuggestions]);
39
+
40
+ // const {
41
+ // values,
42
+ // setFieldValue,
43
+ // setValues,
44
+ // handleChange,
45
+ // touched,
46
+ // errors,
47
+ // setFieldTouched,
48
+ // isSubmitting,
49
+ // submitCount
50
+ // } = useFormex<EntityCollection>();
39
51
 
40
52
  const {
41
53
  values,
42
54
  setFieldValue,
43
55
  setValues,
44
- handleChange,
45
- touched,
46
- errors,
47
- setFieldTouched,
48
- isSubmitting,
49
56
  submitCount
50
- } = useFormikContext<EntityCollection>();
57
+ } = useFormex<EntityCollection>();
51
58
 
52
59
  return (
53
60
  <div className={"overflow-auto my-auto"}>
@@ -1,6 +1,6 @@
1
1
  import React, { useCallback, useEffect, useMemo, useState } from "react";
2
2
 
3
- import { Field, FormikErrors, getIn, useFormikContext } from "formik";
3
+ import { Field, getIn } from "../../form";
4
4
  import {
5
5
  EntityCollection,
6
6
  ErrorBoundary,
@@ -34,12 +34,13 @@ import { OnPropertyChangedParams, PropertyForm, PropertyFormDialog } from "./Pro
34
34
  import { PropertyTree } from "./PropertyTree";
35
35
  import { PersistedCollection } from "../../types/persisted_collection";
36
36
  import { GetCodeDialog } from "./GetCodeDialog";
37
+ import { useFormex } from "../../form/Formex";
37
38
 
38
39
  type CollectionEditorFormProps = {
39
40
  showErrors: boolean;
40
41
  isNewCollection: boolean;
41
42
  propertyErrorsRef?: React.MutableRefObject<any>;
42
- onPropertyError: (propertyKey: string, namespace: string | undefined, error?: FormikErrors<any>) => void;
43
+ onPropertyError: (propertyKey: string, namespace: string | undefined, error?: Record<string, any>) => void;
43
44
  setDirty?: (dirty: boolean) => void;
44
45
  reservedGroups?: string[];
45
46
  extraIcon: React.ReactNode;
@@ -72,7 +73,7 @@ export function CollectionPropertiesEditorForm({
72
73
  setFieldTouched,
73
74
  errors,
74
75
  dirty
75
- } = useFormikContext<PersistedCollection>();
76
+ } = useFormex<PersistedCollection>();
76
77
 
77
78
  const snackbarController = useSnackbarController();
78
79
 
@@ -156,20 +157,20 @@ export function CollectionPropertiesEditorForm({
156
157
  }
157
158
  : undefined;
158
159
 
159
- const getCurrentPropertiesOrder = (namespace?: string) => {
160
+ const getCurrentPropertiesOrder = useCallback((namespace?: string) => {
160
161
  if (!namespace) return currentPropertiesOrderRef.current[""];
161
162
  return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
162
- }
163
+ }, [values]);
163
164
 
164
- const updatePropertiesOrder = (newPropertiesOrder: string[], namespace?: string) => {
165
+ const updatePropertiesOrder = useCallback((newPropertiesOrder: string[], namespace?: string) => {
165
166
  const propertiesOrderPath = namespaceToPropertiesOrderPath(namespace);
166
167
 
167
168
  setFieldValue(propertiesOrderPath, newPropertiesOrder, false);
168
169
  currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
169
170
 
170
- };
171
+ }, [setFieldValue]);
171
172
 
172
- const deleteProperty = (propertyKey?: string, namespace?: string) => {
173
+ const deleteProperty = useCallback((propertyKey?: string, namespace?: string) => {
173
174
  const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
174
175
  if (!fullId)
175
176
  throw Error("collection editor miss config");
@@ -185,7 +186,7 @@ export function CollectionPropertiesEditorForm({
185
186
  setSelectedPropertyIndex(undefined);
186
187
  setSelectedPropertyKey(undefined);
187
188
  setSelectedPropertyNamespace(undefined);
188
- };
189
+ }, [getCurrentPropertiesOrder, setFieldValue, updatePropertiesOrder]);
189
190
 
190
191
  const onPropertyMove = (propertiesOrder: string[], namespace?: string) => {
191
192
  setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
@@ -263,9 +264,9 @@ export function CollectionPropertiesEditorForm({
263
264
 
264
265
  };
265
266
 
266
- const onPropertyErrorInternal = useCallback((id: string, namespace?: string, error?: FormikErrors<any>) => {
267
+ const onPropertyErrorInternal = useCallback((id: string, namespace?: string, error?: Record<string, any>) => {
267
268
  const propertyPath = id ? getFullId(id, namespace) : undefined;
268
- console.warn("onPropertyErrorInternal", {
269
+ console.debug("onPropertyErrorInternal", {
269
270
  id,
270
271
  namespace,
271
272
  error,
@@ -292,6 +293,17 @@ export function CollectionPropertiesEditorForm({
292
293
  : Object.keys(values.properties)) as string[];
293
294
 
294
295
  const owner = useMemo(() => getUser(values.ownerId), [getUser, values.ownerId]);
296
+
297
+ const onPropertyClick = useCallback((propertyKey: string, namespace?: string) => {
298
+ console.debug("CollectionEditor: onPropertyClick", {
299
+ propertyKey,
300
+ namespace
301
+ });
302
+ setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
303
+ setSelectedPropertyKey(propertyKey);
304
+ setSelectedPropertyNamespace(namespace);
305
+ }, [usedPropertiesOrder]);
306
+
295
307
  const body = (
296
308
  <div className={"grid grid-cols-12 gap-2 h-full bg-gray-50 dark:bg-gray-900"}>
297
309
  <div className={cn(
@@ -357,20 +369,16 @@ export function CollectionPropertiesEditorForm({
357
369
  <ErrorBoundary>
358
370
  <PropertyTree
359
371
  className={"pl-8"}
360
- onPropertyClick={(propertyKey, namespace) => {
361
- setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
362
- setSelectedPropertyKey(propertyKey);
363
- setSelectedPropertyNamespace(namespace);
364
- }}
365
372
  inferredPropertyKeys={inferredPropertyKeys}
366
373
  selectedPropertyKey={selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined}
367
374
  properties={values.properties}
368
375
  additionalFields={values.additionalFields}
369
376
  propertiesOrder={usedPropertiesOrder}
377
+ onPropertyClick={onPropertyClick}
370
378
  onPropertyMove={onPropertyMove}
371
379
  onPropertyRemove={isNewCollection ? deleteProperty : undefined}
372
380
  collectionEditable={collectionEditable}
373
- errors={showErrors ? errors : {}}/>
381
+ errors={errors}/>
374
382
  </ErrorBoundary>
375
383
 
376
384
  <Button className={"mt-8 w-full"}
@@ -384,7 +392,7 @@ export function CollectionPropertiesEditorForm({
384
392
  </div>
385
393
 
386
394
  {!asDialog &&
387
- <div className={"col-span-12 lg:col-span-7 ml-2 p-4 md:p-8 h-full overflow-auto pb-20 md:pb-20"}>
395
+ <div className={"col-span-12 lg:col-span-7 p-4 md:py-8 md:px-4 h-full overflow-auto pb-20 md:pb-20"}>
388
396
  <Paper
389
397
  className="sticky top-8 p-4 min-h-full border border-transparent w-full flex flex-col justify-center ">
390
398
 
@@ -1,9 +1,10 @@
1
1
  import React, { useEffect } from "react";
2
+ import equal from "react-fast-compare"
2
3
 
3
- import { FastField, Formik, getIn, useFormikContext } from "formik";
4
- import { EnumValueConfig, EnumValues, FormikArrayContainer, } from "@firecms/core";
4
+ import { ArrayContainer, EnumValueConfig, EnumValues, } from "@firecms/core";
5
5
  import {
6
6
  AutoAwesomeIcon,
7
+ Badge,
7
8
  Button,
8
9
  CircularProgress,
9
10
  DebouncedTextField,
@@ -18,6 +19,7 @@ import {
18
19
  } from "@firecms/ui";
19
20
  import { FieldHelperView } from "./properties/FieldHelperView";
20
21
  import { extractEnumFromValues } from "@firecms/schema_inference";
22
+ import { Field, Formex, getIn, useCreateFormex, useFormex } from "../../form";
21
23
 
22
24
  type EnumFormProps = {
23
25
  enumValues: EnumValueConfig[];
@@ -28,64 +30,68 @@ type EnumFormProps = {
28
30
  allowDataInference?: boolean;
29
31
  getData?: () => Promise<string[]>;
30
32
  };
31
- export const EnumForm = React.memo(
32
- function EnumForm({
33
- enumValues,
34
- onValuesChanged,
35
- onError,
36
- updateIds,
37
- disabled,
38
- allowDataInference,
39
- getData
40
- }: EnumFormProps) {
41
33
 
42
- return (
43
- <Formik initialValues={{ enumValues }}
44
- // enableReinitialize={true}
45
- validateOnMount={true}
46
- onSubmit={(data: { enumValues: EnumValueConfig[] }, formikHelpers) => {
47
- }}
48
- >
49
- {
50
- ({
51
- values,
52
- errors
53
- }) => {
54
- // eslint-disable-next-line react-hooks/rules-of-hooks
55
- useEffect(() => {
56
- if (onValuesChanged) {
57
- onValuesChanged(values.enumValues);
58
- }
59
- }, [values.enumValues]);
60
- // eslint-disable-next-line react-hooks/rules-of-hooks
61
- useEffect(() => {
62
- if (onError)
63
- onError(Boolean(errors?.enumValues ?? false));
64
- }, [errors]);
65
-
66
- return <EnumFormFields enumValuesPath={"enumValues"}
67
- values={values}
68
- errors={errors}
69
- shouldUpdateId={updateIds}
70
- disabled={disabled}
71
- allowDataInference={allowDataInference}
72
- getData={getData}/>
34
+ export function EnumForm({
35
+ enumValues,
36
+ onValuesChanged,
37
+ onError,
38
+ updateIds,
39
+ disabled,
40
+ allowDataInference,
41
+ getData
42
+ }: EnumFormProps) {
43
+
44
+ const formex = useCreateFormex<{
45
+ enumValues: EnumValueConfig[]
46
+ }>({
47
+ initialValues: { enumValues },
48
+ validateOnChange: true,
49
+ validation: (values) => {
50
+ const errors: any = {};
51
+ if (values.enumValues) {
52
+ values.enumValues.forEach((enumValue, index) => {
53
+ if (!enumValue?.label) {
54
+ errors.enumValues = errors.enumValues ?? [];
55
+ errors.enumValues[index] = errors.enumValues[index] ?? {};
56
+ errors.enumValues[index].label = "You must specify a label for this enum value entry";
57
+ }
58
+ if (!enumValue?.id) {
59
+ errors.enumValues = errors.enumValues ?? [];
60
+ errors.enumValues[index] = errors.enumValues[index] ?? {};
61
+ errors.enumValues[index].id = "You must specify an ID for this enum value entry";
73
62
  }
74
- }
75
- </Formik>
63
+ });
64
+ }
65
+ const hasError = Boolean(errors?.enumValues && Object.keys(errors?.enumValues).length > 0);
66
+ onError?.(hasError);
67
+ return errors;
68
+ }
69
+ });
70
+
71
+ const { values, errors } = formex;
72
+
73
+ useEffect(() => {
74
+ if (onValuesChanged) {
75
+ onValuesChanged(values.enumValues);
76
+ }
77
+ }, [values.enumValues]);
78
+
79
+ return <Formex value={formex}>
80
+ <EnumFormFields enumValuesPath={"enumValues"}
81
+ values={values}
82
+ errors={errors}
83
+ shouldUpdateId={updateIds}
84
+ disabled={disabled}
85
+ allowDataInference={allowDataInference}
86
+ getData={getData}/>
87
+ </Formex>
76
88
 
77
- );
78
- },
79
- function areEqual(prevProps: EnumFormProps, nextProps: EnumFormProps) {
80
- return prevProps.enumValues.length === nextProps.enumValues.length &&
81
- prevProps.onValuesChanged === nextProps.onValuesChanged &&
82
- prevProps.getData === nextProps.getData
83
- ;
84
- }
85
- );
89
+ }
86
90
 
87
91
  type EnumFormFieldsProps = {
88
- values: { enumValues: EnumValueConfig[] };
92
+ values: {
93
+ enumValues: EnumValueConfig[]
94
+ };
89
95
  errors: any;
90
96
  enumValuesPath: string;
91
97
  shouldUpdateId: boolean;
@@ -107,7 +113,7 @@ function EnumFormFields({
107
113
 
108
114
  const {
109
115
  setFieldValue
110
- } = useFormikContext();
116
+ } = useFormex();
111
117
 
112
118
  const [lastInternalIdAdded, setLastInternalIdAdded] = React.useState<number | undefined>();
113
119
  const [editDialogIndex, setEditDialogIndex] = React.useState<number | undefined>();
@@ -118,10 +124,12 @@ function EnumFormFields({
118
124
 
119
125
  const buildEntry = (index: number, internalId: number) => {
120
126
  const justAdded = lastInternalIdAdded === internalId;
127
+ const entryError = errors?.enumValues && errors?.enumValues[index];
121
128
  return <EnumEntry index={index}
122
129
  disabled={disabled}
123
130
  enumValuesPath={enumValuesPath}
124
131
  autoFocus={justAdded}
132
+ entryError={entryError}
125
133
  shouldUpdateId={shouldUpdateId || justAdded}
126
134
  onDialogOpen={() => setEditDialogIndex(index)}
127
135
  inferredEntry={inferredValues.has(values.enumValues[index]?.id as string)}
@@ -151,7 +159,7 @@ function EnumFormFields({
151
159
  newEnumValues.forEach((enumValue) => {
152
160
  inferredValues.add(enumValue.id);
153
161
  });
154
- setFieldValue(enumValuesPath, [...newEnumValues, ...currentEnumValues]);
162
+ setFieldValue(enumValuesPath, [...newEnumValues, ...currentEnumValues], true);
155
163
  }).catch(e => {
156
164
  console.error(e);
157
165
  })
@@ -179,16 +187,16 @@ function EnumFormFields({
179
187
 
180
188
  <Paper className="p-4 m-1">
181
189
 
182
- <FormikArrayContainer
183
- value={values.enumValues}
184
- addLabel={"Add enum value"}
185
- name={enumValuesPath}
186
- buildEntry={buildEntry}
187
- disabled={disabled}
188
- onInternalIdAdded={setLastInternalIdAdded}
189
- small={true}
190
- setFieldValue={setFieldValue}
191
- includeAddButton={true}/>
190
+ <ArrayContainer droppableId={enumValuesPath}
191
+ addLabel={"Add enum value"}
192
+ value={values.enumValues}
193
+ disabled={disabled}
194
+ size={"small"}
195
+ buildEntry={buildEntry}
196
+ onInternalIdAdded={setLastInternalIdAdded}
197
+ includeAddButton={true}
198
+ onValueChange={(value) => setFieldValue(enumValuesPath, value)}
199
+ newDefaultEntry={{ id: "", label: "" }}/>
192
200
 
193
201
  <EnumEntryDialog index={editDialogIndex}
194
202
  open={editDialogIndex !== undefined}
@@ -207,6 +215,7 @@ type EnumEntryProps = {
207
215
  onDialogOpen: () => void;
208
216
  disabled: boolean;
209
217
  inferredEntry?: boolean;
218
+ entryError?: { label?: string, id?: string }
210
219
  };
211
220
 
212
221
  const EnumEntry = React.memo(
@@ -217,7 +226,8 @@ const EnumEntry = React.memo(
217
226
  autoFocus,
218
227
  onDialogOpen,
219
228
  disabled,
220
- inferredEntry
229
+ inferredEntry,
230
+ entryError
221
231
  }: EnumEntryProps) {
222
232
 
223
233
  const {
@@ -226,7 +236,7 @@ const EnumEntry = React.memo(
226
236
  errors,
227
237
  setFieldValue,
228
238
  touched
229
- } = useFormikContext<EnumValues>();
239
+ } = useFormex<EnumValues>();
230
240
 
231
241
  const shouldUpdateIdRef = React.useRef(!getIn(values, `${enumValuesPath}[${index}].id`));
232
242
  const shouldUpdateId = updateId || shouldUpdateIdRef.current;
@@ -234,8 +244,6 @@ const EnumEntry = React.memo(
234
244
  const idValue = getIn(values, `${enumValuesPath}[${index}].id`);
235
245
  const labelValue = getIn(values, `${enumValuesPath}[${index}].label`);
236
246
 
237
- const labelError = getIn(errors, `${enumValuesPath}[${index}].label`);
238
-
239
247
  const currentLabelRef = React.useRef(labelValue);
240
248
 
241
249
  React.useEffect(() => {
@@ -246,35 +254,50 @@ const EnumEntry = React.memo(
246
254
  }, [labelValue]);
247
255
 
248
256
  return (
249
- <div className={"flex w-full align-center justify-center"}>
250
- <FastField name={`${enumValuesPath}[${index}].label`}
257
+ <>
258
+ <div className={"flex w-full align-center justify-center"}>
259
+ <Field name={`${enumValuesPath}[${index}].label`}
251
260
  as={DebouncedTextField}
252
261
  className={"flex-grow"}
253
262
  required
254
263
  disabled={disabled}
255
264
  size="small"
256
- validate={validateLabel}
257
265
  autoFocus={autoFocus}
258
266
  autoComplete="off"
259
267
  endAdornment={inferredEntry && <AutoAwesomeIcon size={"small"}/>}
260
- error={Boolean(labelError)}/>
261
-
262
- {!disabled &&
263
- <IconButton
264
- size="small"
265
- aria-label="edit"
266
- className={"m-1"}
267
- onClick={() => onDialogOpen()}>
268
- <SettingsIcon size={"small"}/>
269
- </IconButton>}
270
-
271
- </div>);
268
+ error={Boolean(entryError?.label)}/>
269
+
270
+ {!disabled &&
271
+ <Badge color={"error"} invisible={!entryError?.id}>
272
+ <IconButton
273
+ size="small"
274
+ aria-label="edit"
275
+ className={"m-1"}
276
+ onClick={() => onDialogOpen()}>
277
+ <SettingsIcon size={"small"}/>
278
+ </IconButton>
279
+ </Badge>}
280
+
281
+ </div>
282
+
283
+ {entryError?.label && <Typography variant={"caption"}
284
+ className={"ml-3.5 text-red-500 dark:text-red-500"}>
285
+ {entryError?.label}
286
+ </Typography>}
287
+
288
+ {entryError?.id && <Typography variant={"caption"}
289
+ className={"ml-3.5 text-red-500 dark:text-red-500"}>
290
+ {entryError?.id}
291
+ </Typography>}
292
+
293
+ </>);
272
294
  },
273
295
  function areEqual(prevProps: EnumEntryProps, nextProps: EnumEntryProps) {
274
296
  return prevProps.index === nextProps.index &&
275
297
  prevProps.enumValuesPath === nextProps.enumValuesPath &&
276
298
  prevProps.shouldUpdateId === nextProps.shouldUpdateId &&
277
299
  prevProps.inferredEntry === nextProps.inferredEntry &&
300
+ equal(prevProps.entryError, nextProps.entryError) &&
278
301
  prevProps.autoFocus === nextProps.autoFocus;
279
302
  }
280
303
  );
@@ -292,12 +315,8 @@ function EnumEntryDialog({
292
315
  }) {
293
316
 
294
317
  const {
295
- values,
296
- handleChange,
297
318
  errors,
298
- setFieldValue,
299
- touched
300
- } = useFormikContext<EnumValues>();
319
+ } = useFormex<EnumValues>();
301
320
 
302
321
  const idError = index !== undefined ? getIn(errors, `${enumValuesPath}[${index}].id`) : undefined;
303
322
  return <Dialog
@@ -309,14 +328,14 @@ function EnumEntryDialog({
309
328
 
310
329
  <DialogContent>
311
330
  {index !== undefined &&
312
- <div><FastField name={`${enumValuesPath}[${index}]id`}
313
- as={DebouncedTextField}
314
- required
315
- validate={validateId}
316
- label={"ID"}
317
- size="small"
318
- autoComplete="off"
319
- error={Boolean(idError)}/>
331
+ <div>
332
+ <Field name={`${enumValuesPath}[${index}].id`}
333
+ as={DebouncedTextField}
334
+ required
335
+ label={"ID"}
336
+ size="small"
337
+ autoComplete="off"
338
+ error={Boolean(idError)}/>
320
339
 
321
340
  <FieldHelperView error={Boolean(idError)}>
322
341
  {idError ?? "Value saved in the data source"}
@@ -337,18 +356,3 @@ function EnumEntryDialog({
337
356
  </Dialog>
338
357
  }
339
358
 
340
- function validateLabel(value: string) {
341
- let error;
342
- if (!value) {
343
- error = "You must specify a label";
344
- }
345
- return error;
346
- }
347
-
348
- function validateId(value: string) {
349
- let error;
350
- if (!value) {
351
- error = "You must specify an ID";
352
- }
353
- return error;
354
- }
@@ -9,7 +9,7 @@ export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange
9
9
 
10
10
  const snackbarController = useSnackbarController();
11
11
 
12
- const code = "import { EntityCollection } from \"firecms\";\n\nconst " + camelCase(collection.name) + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode(collection), null, "\t");
12
+ const code = "import { EntityCollection } from \"firecms\";\n\nconst " + (collection.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode(collection), null, "\t");
13
13
  return <Dialog open={open}
14
14
  onOpenChange={onOpenChange}
15
15
  maxWidth={"4xl"}>