@rjsf/core 6.0.1 → 6.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rjsf/core",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
4
4
  "description": "A simple React component capable of building HTML forms out of a JSON schema.",
5
5
  "scripts": {
6
6
  "compileReplacer": "tsc -p tsconfig.replacer.json && move-file lodashReplacer.js lodashReplacer.cjs",
@@ -88,7 +88,7 @@
88
88
  "eslint": "^8.57.1",
89
89
  "html": "^1.0.0",
90
90
  "jsdom": "^27.0.1",
91
- "mocha": "^10.8.2",
91
+ "mocha": "^11.7.4",
92
92
  "react-portal": "^4.3.0",
93
93
  "sinon": "^9.2.4"
94
94
  },
@@ -51,11 +51,10 @@ import _isEmpty from 'lodash/isEmpty';
51
51
  import _pick from 'lodash/pick';
52
52
  import _set from 'lodash/set';
53
53
  import _toPath from 'lodash/toPath';
54
+ import _unset from 'lodash/unset';
54
55
 
55
56
  import getDefaultRegistry from '../getDefaultRegistry';
56
-
57
- /** Internal only symbol used by the `reset()` function to indicate that a reset operation is happening */
58
- const IS_RESET = Symbol('reset');
57
+ import { ADDITIONAL_PROPERTY_KEY_REMOVE, IS_RESET } from './constants';
59
58
 
60
59
  /** The properties that are passed to the `Form` */
61
60
  export interface FormProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> {
@@ -875,7 +874,10 @@ export default class Form<
875
874
  let retrievedSchema = this.state.retrievedSchema;
876
875
  let formData = isRootPath ? newValue : _cloneDeep(oldFormData);
877
876
  if (isObject(formData) || Array.isArray(formData)) {
878
- if (!isRootPath) {
877
+ if (newValue === ADDITIONAL_PROPERTY_KEY_REMOVE) {
878
+ // For additional properties, we were given the special remove this key value, so unset it
879
+ _unset(formData, path);
880
+ } else if (!isRootPath) {
879
881
  // If the newValue is not on the root path, then set it into the form data
880
882
  _set(formData, path, newValue);
881
883
  }
@@ -0,0 +1,5 @@
1
+ /** Internal only symbol used by `Form` & `ObjectField` to mark an additional property element to be removed */
2
+ export const ADDITIONAL_PROPERTY_KEY_REMOVE = Symbol('remove-this-key');
3
+
4
+ /** Internal only symbol used by the `reset()` function to indicate that a reset operation is happening */
5
+ export const IS_RESET = Symbol('reset');
@@ -153,9 +153,12 @@ function getNewFormDataRow<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
153
153
  registry: Registry<T[], S, F>,
154
154
  schema: S,
155
155
  ): T {
156
- const { schemaUtils } = registry;
156
+ const { schemaUtils, globalFormOptions } = registry;
157
157
  let itemSchema = schema.items as S;
158
- if (isFixedItems(schema) && allowAdditionalItems(schema)) {
158
+ if (globalFormOptions.useFallbackUiForUnsupportedType && !itemSchema) {
159
+ // If we don't have itemSchema and useFallbackUiForUnsupportedType is on, use an empty schema
160
+ itemSchema = {} as S;
161
+ } else if (isFixedItems(schema) && allowAdditionalItems(schema)) {
159
162
  itemSchema = schema.additionalItems as S;
160
163
  }
161
164
  // Cast this as a T to work around schema utils being for T[] caused by the FieldProps<T[], S, F> call on the class
@@ -840,7 +843,7 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
840
843
  props: FieldProps<T[], S, F>,
841
844
  ) {
842
845
  const { schema, uiSchema, errorSchema, fieldPathId, registry, formData, onChange } = props;
843
- const { schemaUtils, translateString } = registry;
846
+ const { globalFormOptions, schemaUtils, translateString } = registry;
844
847
  const { keyedFormData, updateKeyedFormData } = useKeyedFormData<T>(formData);
845
848
  // All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
846
849
  const childFieldPathId = props.childFieldPathId ?? fieldPathId;
@@ -1027,24 +1030,14 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
1027
1030
  [onChange, childFieldPathId],
1028
1031
  );
1029
1032
 
1030
- if (!(ITEMS_KEY in schema)) {
1031
- const uiOptions = getUiOptions<T[], S, F>(uiSchema);
1032
- const UnsupportedFieldTemplate = getTemplate<'UnsupportedFieldTemplate', T[], S, F>(
1033
- 'UnsupportedFieldTemplate',
1034
- registry,
1035
- uiOptions,
1036
- );
1037
-
1038
- return (
1039
- <UnsupportedFieldTemplate
1040
- schema={schema}
1041
- fieldPathId={fieldPathId}
1042
- reason={translateString(TranslatableString.MissingItems)}
1043
- registry={registry}
1044
- />
1045
- );
1046
- }
1047
- const arrayProps = {
1033
+ const arrayAsMultiProps: ArrayAsFieldProps<T[], S, F> = {
1034
+ ...props,
1035
+ formData,
1036
+ fieldPathId: childFieldPathId,
1037
+ onSelectChange: onSelectChange,
1038
+ };
1039
+ const arrayProps: InternalArrayFieldProps<T, S, F> = {
1040
+ ...props,
1048
1041
  handleAddItem,
1049
1042
  handleCopyItem,
1050
1043
  handleRemoveItem,
@@ -1052,18 +1045,41 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
1052
1045
  keyedFormData,
1053
1046
  onChange: handleChange,
1054
1047
  };
1055
- if (schemaUtils.isMultiSelect(schema)) {
1048
+ if (!(ITEMS_KEY in schema)) {
1049
+ if (!globalFormOptions.useFallbackUiForUnsupportedType) {
1050
+ const uiOptions = getUiOptions<T[], S, F>(uiSchema);
1051
+ const UnsupportedFieldTemplate = getTemplate<'UnsupportedFieldTemplate', T[], S, F>(
1052
+ 'UnsupportedFieldTemplate',
1053
+ registry,
1054
+ uiOptions,
1055
+ );
1056
+
1057
+ return (
1058
+ <UnsupportedFieldTemplate
1059
+ schema={schema}
1060
+ fieldPathId={fieldPathId}
1061
+ reason={translateString(TranslatableString.MissingItems)}
1062
+ registry={registry}
1063
+ />
1064
+ );
1065
+ }
1066
+ // Add an items schema with type as undefined so it triggers FallbackField later on
1067
+ const fallbackSchema = { ...schema, [ITEMS_KEY]: { type: undefined } };
1068
+ arrayAsMultiProps.schema = fallbackSchema;
1069
+ arrayProps.schema = fallbackSchema;
1070
+ }
1071
+ if (schemaUtils.isMultiSelect(arrayAsMultiProps.schema)) {
1056
1072
  // If array has enum or uniqueItems set to true, call renderMultiSelect() to render the default multiselect widget or a custom widget, if specified.
1057
- return <ArrayAsMultiSelect<T, S, F> {...props} fieldPathId={childFieldPathId} onSelectChange={onSelectChange} />;
1073
+ return <ArrayAsMultiSelect<T, S, F> {...arrayAsMultiProps} />;
1058
1074
  }
1059
1075
  if (isCustomWidget<T[], S, F>(uiSchema)) {
1060
- return <ArrayAsCustomWidget<T, S, F> {...props} fieldPathId={childFieldPathId} onSelectChange={onSelectChange} />;
1076
+ return <ArrayAsCustomWidget<T, S, F> {...arrayAsMultiProps} />;
1061
1077
  }
1062
- if (isFixedItems(schema)) {
1063
- return <FixedArray<T, S, F> {...props} {...arrayProps} />;
1078
+ if (isFixedItems(arrayAsMultiProps.schema)) {
1079
+ return <FixedArray<T, S, F> {...arrayProps} />;
1064
1080
  }
1065
- if (schemaUtils.isFilesArray(schema, uiSchema)) {
1066
- return <ArrayAsFiles {...props} fieldPathId={childFieldPathId} onSelectChange={onSelectChange} />;
1081
+ if (schemaUtils.isFilesArray(arrayAsMultiProps.schema, uiSchema)) {
1082
+ return <ArrayAsFiles<T, S, F> {...arrayAsMultiProps} />;
1067
1083
  }
1068
- return <NormalArray<T, S, F> {...props} {...arrayProps} />;
1084
+ return <NormalArray<T, S, F> {...arrayProps} />;
1069
1085
  }
@@ -21,7 +21,7 @@ import { JSONSchema7TypeName } from 'json-schema';
21
21
  function getFallbackTypeSelectionSchema(title: string): RJSFSchema {
22
22
  return {
23
23
  type: 'string',
24
- enum: ['string', 'number', 'boolean'],
24
+ enum: ['string', 'number', 'boolean', 'object', 'array'],
25
25
  default: 'string',
26
26
  title: title,
27
27
  };
@@ -36,6 +36,9 @@ function getTypeOfFormData(formData: any): JSONSchema7TypeName {
36
36
  if (dataType === 'string' || dataType === 'number' || dataType === 'boolean') {
37
37
  return dataType;
38
38
  }
39
+ if (dataType === 'object') {
40
+ return Array.isArray(formData) ? 'array' : 'object';
41
+ }
39
42
  // Treat everything else as a string
40
43
  return 'string';
41
44
  }
@@ -106,20 +109,14 @@ export default function FallbackField<
106
109
  };
107
110
 
108
111
  if (!globalFormOptions.useFallbackUiForUnsupportedType) {
112
+ const { reason = translateString(TranslatableString.UnknownFieldType, [String(schema.type)]) } = props;
109
113
  const UnsupportedFieldTemplate = getTemplate<'UnsupportedFieldTemplate', T, S, F>(
110
114
  'UnsupportedFieldTemplate',
111
115
  registry,
112
116
  uiOptions,
113
117
  );
114
118
 
115
- return (
116
- <UnsupportedFieldTemplate
117
- schema={schema}
118
- fieldPathId={fieldPathId}
119
- reason={translateString(TranslatableString.UnknownFieldType, [String(schema.type)])}
120
- registry={registry}
121
- />
122
- );
119
+ return <UnsupportedFieldTemplate schema={schema} fieldPathId={fieldPathId} reason={reason} registry={registry} />;
123
120
  }
124
121
 
125
122
  const FallbackFieldTemplate = getTemplate<'FallbackFieldTemplate', T, S, F>(
@@ -151,7 +148,18 @@ export default function FallbackField<
151
148
  required={required}
152
149
  />
153
150
  }
154
- schemaField={<SchemaField {...props} schema={{ type, title: translateString(TranslatableString.Value) } as S} />}
151
+ schemaField={
152
+ <SchemaField
153
+ {...props}
154
+ schema={
155
+ {
156
+ type,
157
+ title: translateString(TranslatableString.Value),
158
+ ...(type === 'object' && { additionalProperties: true }),
159
+ } as S
160
+ }
161
+ />
162
+ }
155
163
  />
156
164
  );
157
165
  }
@@ -4,7 +4,6 @@ import {
4
4
  ANY_OF_KEY,
5
5
  getTemplate,
6
6
  getUiOptions,
7
- hashObject,
8
7
  isFormDataAvailable,
9
8
  orderProperties,
10
9
  shouldRenderOptionalField,
@@ -29,7 +28,8 @@ import get from 'lodash/get';
29
28
  import has from 'lodash/has';
30
29
  import isObject from 'lodash/isObject';
31
30
  import set from 'lodash/set';
32
- import unset from 'lodash/unset';
31
+
32
+ import { ADDITIONAL_PROPERTY_KEY_REMOVE } from '../constants';
33
33
 
34
34
  /** Returns a flag indicating whether the `name` field is required in the object schema
35
35
  *
@@ -220,7 +220,6 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
220
220
  const schema: S = schemaUtils.retrieveSchema(rawSchema, formData, true);
221
221
  const uiOptions = getUiOptions<T, S, F>(uiSchema, globalUiOptions);
222
222
  const { properties: schemaProperties = {} } = schema;
223
- const formDataHash = hashObject(formData || {});
224
223
  // All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
225
224
  const childFieldPathId = props.childFieldPathId ?? fieldPathId;
226
225
 
@@ -320,15 +319,14 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
320
319
  [formData, onChange, childFieldPathId, getAvailableKey],
321
320
  );
322
321
 
323
- /** Handles the remove click which removes the old `key` data and calls the `onChange` callback with it
322
+ /** Handles the remove click which calls the `onChange` callback with the special ADDITIONAL_PROPERTY_FIELD_REMOVE
323
+ * value for the path plus the key to be removed
324
324
  */
325
325
  const handleRemoveProperty = useCallback(
326
326
  (key: string) => {
327
- const copiedFormData = { ...formData } as T;
328
- unset(copiedFormData, key);
329
- onChange(copiedFormData, childFieldPathId.path);
327
+ onChange(ADDITIONAL_PROPERTY_KEY_REMOVE as T, [...childFieldPathId.path, key]);
330
328
  },
331
- [onChange, childFieldPathId, formData],
329
+ [onChange, childFieldPathId],
332
330
  );
333
331
 
334
332
  if (!renderOptionalField || hasFormData) {
@@ -364,10 +362,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
364
362
  const hidden = getUiOptions<T, S, F>(fieldUiSchema).widget === 'hidden';
365
363
  const content = (
366
364
  <ObjectFieldProperty<T, S, F>
367
- // For regular properties, the key is just the name. For additionalProperties, the key is a combination of the
368
- // name and the hash of the formData so that react rerenders the components with the updated additional
369
- // property related callback which will change due to formData changes
370
- key={addedByAdditionalProperties ? `${name}-${formDataHash}` : name}
365
+ key={name}
371
366
  propertyName={name}
372
367
  required={isRequired<S>(schema, name)}
373
368
  schema={get(schema, [PROPERTIES_KEY, name], {}) as S}
@@ -29,9 +29,10 @@ export default function FieldTemplate<
29
29
  if (hidden) {
30
30
  return <div className='hidden'>{children}</div>;
31
31
  }
32
+ const isCheckbox = uiOptions.widget === 'checkbox';
32
33
  return (
33
34
  <WrapIfAdditionalTemplate {...props}>
34
- {displayLabel && <Label label={label} required={required} id={id} />}
35
+ {displayLabel && !isCheckbox && <Label label={label} required={required} id={id} />}
35
36
  {displayLabel && description ? description : null}
36
37
  {children}
37
38
  {errors}
@@ -9,6 +9,7 @@ import {
9
9
  RJSFSchema,
10
10
  StrictRJSFSchema,
11
11
  WidgetProps,
12
+ getUiOptions,
12
13
  } from '@rjsf/utils';
13
14
 
14
15
  /** The `CheckBoxWidget` is a widget for rendering boolean properties.
@@ -57,8 +58,10 @@ function CheckboxWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F exte
57
58
  (event: FocusEvent<HTMLInputElement>) => onFocus(id, event.target.checked),
58
59
  [onFocus, id],
59
60
  );
60
- const description = options.description ?? schema.description;
61
61
 
62
+ const uiOptions = getUiOptions(uiSchema);
63
+ const isCheckboxWidget = uiOptions.widget === 'checkbox';
64
+ const description = isCheckboxWidget ? undefined : (options.description ?? schema.description);
62
65
  return (
63
66
  <div className={`checkbox ${disabled || readonly ? 'disabled' : ''}`}>
64
67
  {!hideLabel && description && (