@rjsf/core 6.1.2 → 6.2.4

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/dist/core.umd.js +65 -44
  2. package/dist/index.cjs +65 -44
  3. package/dist/index.cjs.map +3 -3
  4. package/dist/index.esm.js +68 -47
  5. package/dist/index.esm.js.map +3 -3
  6. package/lib/components/Form.d.ts +9 -1
  7. package/lib/components/Form.d.ts.map +1 -1
  8. package/lib/components/Form.js +35 -49
  9. package/lib/components/fields/LayoutMultiSchemaField.d.ts.map +1 -1
  10. package/lib/components/fields/LayoutMultiSchemaField.js +1 -1
  11. package/lib/components/fields/SchemaField.d.ts.map +1 -1
  12. package/lib/components/fields/SchemaField.js +1 -0
  13. package/lib/components/templates/BaseInputTemplate.d.ts.map +1 -1
  14. package/lib/components/templates/BaseInputTemplate.js +7 -1
  15. package/lib/components/templates/ButtonTemplates/IconButton.d.ts +1 -0
  16. package/lib/components/templates/ButtonTemplates/IconButton.d.ts.map +1 -1
  17. package/lib/components/templates/ButtonTemplates/IconButton.js +4 -0
  18. package/lib/components/templates/ButtonTemplates/index.d.ts.map +1 -1
  19. package/lib/components/templates/ButtonTemplates/index.js +2 -1
  20. package/lib/components/templates/ObjectFieldTemplate.d.ts +1 -1
  21. package/lib/components/templates/ObjectFieldTemplate.d.ts.map +1 -1
  22. package/lib/components/templates/ObjectFieldTemplate.js +6 -0
  23. package/lib/components/widgets/TextareaWidget.d.ts +0 -6
  24. package/lib/components/widgets/TextareaWidget.d.ts.map +1 -1
  25. package/lib/components/widgets/TextareaWidget.js +0 -4
  26. package/lib/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +7 -10
  28. package/src/components/Form.tsx +44 -58
  29. package/src/components/fields/LayoutMultiSchemaField.tsx +1 -0
  30. package/src/components/fields/SchemaField.tsx +1 -0
  31. package/src/components/templates/BaseInputTemplate.tsx +13 -1
  32. package/src/components/templates/ButtonTemplates/IconButton.tsx +24 -0
  33. package/src/components/templates/ButtonTemplates/index.ts +2 -1
  34. package/src/components/templates/ObjectFieldTemplate.tsx +9 -0
  35. package/src/components/widgets/TextareaWidget.tsx +0 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rjsf/core",
3
- "version": "6.1.2",
3
+ "version": "6.2.4",
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",
@@ -66,7 +66,7 @@
66
66
  "node": ">=20"
67
67
  },
68
68
  "peerDependencies": {
69
- "@rjsf/utils": "^6.x",
69
+ "@rjsf/utils": "^6.2.x",
70
70
  "react": ">=18"
71
71
  },
72
72
  "dependencies": {
@@ -76,21 +76,18 @@
76
76
  "prop-types": "^15.8.1"
77
77
  },
78
78
  "devDependencies": {
79
- "@rjsf/snapshot-tests": "^6.x",
80
- "@rjsf/utils": "^6.x",
81
- "@rjsf/validator-ajv8": "^6.x",
79
+ "@rjsf/snapshot-tests": "^6.2.0",
80
+ "@rjsf/utils": "^6.2.0",
81
+ "@rjsf/validator-ajv8": "^6.2.0",
82
82
  "@testing-library/jest-dom": "^6.9.1",
83
83
  "@testing-library/react": "^16.3.0",
84
84
  "@testing-library/user-event": "^14.6.1",
85
+ "@types/react-portal": "^4.0.7",
85
86
  "ajv": "^8.17.1",
86
87
  "atob": "^2.1.2",
87
- "chai": "^3.5.0",
88
88
  "eslint": "^8.57.1",
89
- "html": "^1.0.0",
90
89
  "jsdom": "^27.0.1",
91
- "mocha": "^11.7.4",
92
- "react-portal": "^4.3.0",
93
- "sinon": "^9.2.4"
90
+ "react-portal": "^4.3.0"
94
91
  },
95
92
  "directories": {
96
93
  "test": "test"
@@ -9,13 +9,11 @@ import {
9
9
  FieldPathId,
10
10
  FieldPathList,
11
11
  FormContextType,
12
- GenericObjectType,
13
12
  getChangedFields,
14
13
  getTemplate,
15
14
  getUiOptions,
16
15
  isObject,
17
16
  mergeObjects,
18
- NAME_KEY,
19
17
  PathSchema,
20
18
  StrictRJSFSchema,
21
19
  Registry,
@@ -23,7 +21,6 @@ import {
23
21
  RegistryWidgetsType,
24
22
  RJSFSchema,
25
23
  RJSFValidationError,
26
- RJSF_ADDITIONAL_PROPERTIES_FLAG,
27
24
  SchemaUtilsType,
28
25
  shouldRender,
29
26
  SUBMIT_BTN_OPTIONS_KEY,
@@ -44,6 +41,8 @@ import {
44
41
  ERRORS_KEY,
45
42
  ID_KEY,
46
43
  NameGeneratorFunction,
44
+ getUsedFormData,
45
+ getFieldNames,
47
46
  } from '@rjsf/utils';
48
47
  import _cloneDeep from 'lodash/cloneDeep';
49
48
  import _get from 'lodash/get';
@@ -365,6 +364,11 @@ export default class Form<
365
364
  */
366
365
  pendingChanges: PendingChange<T>[] = [];
367
366
 
367
+ /** Flag to track when we're processing a user-initiated field change.
368
+ * This prevents componentDidUpdate from reverting oneOf/anyOf option switches.
369
+ */
370
+ private _isProcessingUserChange = false;
371
+
368
372
  /** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the
369
373
  * `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the
370
374
  * state construction.
@@ -459,11 +463,19 @@ export default class Form<
459
463
  ) {
460
464
  if (snapshot.shouldUpdate) {
461
465
  const { nextState } = snapshot;
462
- if (
463
- !deepEquals(nextState.formData, this.props.formData) &&
464
- !deepEquals(nextState.formData, prevState.formData) &&
465
- this.props.onChange
466
- ) {
466
+
467
+ // Prevent oneOf/anyOf option switches from reverting when getStateFromProps
468
+ // re-evaluates and produces stale formData.
469
+ const nextStateDiffersFromProps = !deepEquals(nextState.formData, this.props.formData);
470
+ const wasProcessingUserChange = this._isProcessingUserChange;
471
+ this._isProcessingUserChange = false;
472
+
473
+ if (wasProcessingUserChange && nextStateDiffersFromProps) {
474
+ // Skip - the user's option switch is already applied via processPendingChange
475
+ return;
476
+ }
477
+
478
+ if (nextStateDiffersFromProps && !deepEquals(nextState.formData, prevState.formData) && this.props.onChange) {
467
479
  this.props.onChange(toIChangeEvent(nextState));
468
480
  }
469
481
  this.setState(nextState);
@@ -757,73 +769,32 @@ export default class Form<
757
769
  *
758
770
  * @param formData - The data for the `Form`
759
771
  * @param fields - The fields to keep while filtering
772
+ * @deprecated - To be removed as an exported `Form` function in a future release; there isn't a planned replacement
760
773
  */
761
- getUsedFormData = (formData: T | undefined, fields: string[][]): T | undefined => {
762
- // For the case of a single input form
763
- if (fields.length === 0 && typeof formData !== 'object') {
764
- return formData;
765
- }
766
-
767
- // _pick has incorrect type definition, it works with string[][], because lodash/hasIn supports it
768
- const data: GenericObjectType = _pick(formData, fields as unknown as string[]);
769
- if (Array.isArray(formData)) {
770
- return Object.keys(data).map((key: string) => data[key]) as unknown as T;
771
- }
772
-
773
- return data as T;
774
+ getUsedFormData = (formData: T | undefined, fields: string[]): T | undefined => {
775
+ return getUsedFormData(formData, fields);
774
776
  };
775
777
 
776
778
  /** Returns the list of field names from inspecting the `pathSchema` as well as using the `formData`
777
779
  *
778
780
  * @param pathSchema - The `PathSchema` object for the form
779
781
  * @param [formData] - The form data to use while checking for empty objects/arrays
782
+ * @deprecated - To be removed as an exported `Form` function in a future release; there isn't a planned replacement
780
783
  */
781
784
  getFieldNames = (pathSchema: PathSchema<T>, formData?: T): string[][] => {
782
- const formValueHasData = (value: T, isLeaf: boolean) =>
783
- typeof value !== 'object' || _isEmpty(value) || (isLeaf && !_isEmpty(value));
784
- const getAllPaths = (_obj: GenericObjectType, acc: string[][] = [], paths: string[][] = [[]]) => {
785
- const objKeys = Object.keys(_obj);
786
- objKeys.forEach((key: string) => {
787
- const data = _obj[key];
788
- if (typeof data === 'object') {
789
- const newPaths = paths.map((path) => [...path, key]);
790
- // If an object is marked with additionalProperties, all its keys are valid
791
- if (data[RJSF_ADDITIONAL_PROPERTIES_FLAG] && data[NAME_KEY] !== '') {
792
- acc.push(data[NAME_KEY]);
793
- } else {
794
- getAllPaths(data, acc, newPaths);
795
- }
796
- } else if (key === NAME_KEY && data !== '') {
797
- paths.forEach((path) => {
798
- const formValue = _get(formData, path);
799
- const isLeaf = objKeys.length === 1;
800
- // adds path to fieldNames if it points to a value or an empty object/array which is not a leaf
801
- if (
802
- formValueHasData(formValue, isLeaf) ||
803
- (Array.isArray(formValue) && formValue.every((val) => formValueHasData(val, isLeaf)))
804
- ) {
805
- acc.push(path);
806
- }
807
- });
808
- }
809
- });
810
- return acc;
811
- };
812
-
813
- return getAllPaths(pathSchema);
785
+ return getFieldNames(pathSchema, formData);
814
786
  };
815
787
 
816
788
  /** Returns the `formData` after filtering to remove any extra data not in a form field
817
789
  *
818
790
  * @param formData - The data for the `Form`
819
791
  * @returns The `formData` after omitting extra data
792
+ * @deprecated - To be removed as an exported `Form` function in a future release, use `SchemaUtils.omitExtraData`
793
+ * instead.
820
794
  */
821
795
  omitExtraData = (formData?: T): T | undefined => {
822
796
  const { schema, schemaUtils } = this.state;
823
- const retrievedSchema = schemaUtils.retrieveSchema(schema, formData);
824
- const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData);
825
- const fieldNames = this.getFieldNames(pathSchema, formData);
826
- return this.getUsedFormData(formData, fieldNames);
797
+ return schemaUtils.omitExtraData(schema, formData);
827
798
  };
828
799
 
829
800
  /** Allows a user to set a value for the provided `fieldPath`, which must be either a dotted path to the field OR a
@@ -868,6 +839,9 @@ export default class Form<
868
839
  if (this.pendingChanges.length === 0) {
869
840
  return;
870
841
  }
842
+ // Mark that we're processing a user-initiated change.
843
+ // This prevents componentDidUpdate from reverting oneOf/anyOf option switches.
844
+ this._isProcessingUserChange = true;
871
845
  const { newValue, path, id } = this.pendingChanges[0];
872
846
  const { newErrorSchema } = this.pendingChanges[0];
873
847
  const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;
@@ -878,6 +852,18 @@ export default class Form<
878
852
  const isRootPath = !path || path.length === 0 || (path.length === 1 && path[0] === rootPathId);
879
853
  let retrievedSchema = this.state.retrievedSchema;
880
854
  let formData = isRootPath ? newValue : _cloneDeep(oldFormData);
855
+
856
+ // When switching from null to an object option in oneOf, MultiSchemaField sends
857
+ // an object with property names but undefined values (e.g., {types: undefined, content: undefined}).
858
+ // In this case, pass undefined to getStateFromProps to trigger fresh default computation.
859
+ // Only do this when the previous formData was null/undefined (switching FROM null).
860
+ const hasOnlyUndefinedValues =
861
+ isObject(formData) &&
862
+ Object.keys(formData as object).length > 0 &&
863
+ Object.values(formData as object).every((v) => v === undefined);
864
+ const wasPreviouslyNull = oldFormData === null || oldFormData === undefined;
865
+ const inputForDefaults = hasOnlyUndefinedValues && wasPreviouslyNull ? undefined : formData;
866
+
881
867
  if (isObject(formData) || Array.isArray(formData)) {
882
868
  if (newValue === ADDITIONAL_PROPERTY_KEY_REMOVE) {
883
869
  // For additional properties, we were given the special remove this key value, so unset it
@@ -887,7 +873,7 @@ export default class Form<
887
873
  _set(formData, path, newValue);
888
874
  }
889
875
  // Pass true to skip live validation in `getStateFromProps()` since we will do it a bit later
890
- const newState = this.getStateFromProps(this.props, formData, undefined, undefined, undefined, true);
876
+ const newState = this.getStateFromProps(this.props, inputForDefaults, undefined, undefined, undefined, true);
891
877
  formData = newState.formData;
892
878
  retrievedSchema = newState.retrievedSchema;
893
879
  }
@@ -183,6 +183,7 @@ export default function LayoutMultiSchemaField<
183
183
 
184
184
  return (
185
185
  <FieldTemplate
186
+ fieldPathId={fieldPathId}
186
187
  id={id}
187
188
  schema={schema}
188
189
  label={(title || schema.title) ?? ''}
@@ -264,6 +264,7 @@ function SchemaFieldRender<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
264
264
  rawHelp: typeof help === 'string' ? help : undefined,
265
265
  errors: errorsComponent,
266
266
  rawErrors: hideError ? undefined : __errors,
267
+ fieldPathId,
267
268
  id,
268
269
  label,
269
270
  hidden,
@@ -1,4 +1,4 @@
1
- import { ChangeEvent, FocusEvent, useCallback } from 'react';
1
+ import { ChangeEvent, FocusEvent, MouseEvent, useCallback } from 'react';
2
2
  import {
3
3
  ariaDescribedByIds,
4
4
  BaseInputTemplateProps,
@@ -42,6 +42,7 @@ export default function BaseInputTemplate<
42
42
  hideError, // remove this from ...rest
43
43
  ...rest
44
44
  } = props;
45
+ const { ClearButton } = registry.templates.ButtonTemplates;
45
46
 
46
47
  // Note: since React 15.2.0 we can't forward unknown element attributes, so we
47
48
  // exclude the "options" and "schema" ones here.
@@ -73,6 +74,14 @@ export default function BaseInputTemplate<
73
74
  ({ target }: FocusEvent<HTMLInputElement>) => onFocus(id, target && target.value),
74
75
  [onFocus, id],
75
76
  );
77
+ const _onClear = useCallback(
78
+ (e: MouseEvent) => {
79
+ e.preventDefault();
80
+ e.stopPropagation();
81
+ onChange(options.emptyValue ?? '');
82
+ },
83
+ [onChange, options.emptyValue],
84
+ );
76
85
 
77
86
  return (
78
87
  <>
@@ -91,6 +100,9 @@ export default function BaseInputTemplate<
91
100
  onFocus={_onFocus}
92
101
  aria-describedby={ariaDescribedByIds(id, !!schema.examples)}
93
102
  />
103
+ {options.allowClearTextInputs && !readonly && !disabled && inputValue && (
104
+ <ClearButton registry={registry} onClick={_onClear} />
105
+ )}
94
106
  {Array.isArray(schema.examples) && (
95
107
  <datalist key={`datalist_${id}`} id={examplesId(id)}>
96
108
  {(schema.examples as string[])
@@ -48,3 +48,27 @@ export function RemoveButton<T = any, S extends StrictRJSFSchema = RJSFSchema, F
48
48
  <IconButton title={translateString(TranslatableString.RemoveButton)} {...props} iconType='danger' icon='remove' />
49
49
  );
50
50
  }
51
+
52
+ export function ClearButton<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>({
53
+ id,
54
+ className,
55
+ onClick,
56
+ disabled,
57
+ registry,
58
+ ...props
59
+ }: IconButtonProps<T, S, F>) {
60
+ const { translateString } = registry;
61
+ return (
62
+ <IconButton
63
+ id={id}
64
+ iconType='default'
65
+ icon='remove'
66
+ className='btn-clear col-xs-12'
67
+ title={translateString(TranslatableString.ClearButton)}
68
+ onClick={onClick}
69
+ disabled={disabled}
70
+ registry={registry}
71
+ {...props}
72
+ />
73
+ );
74
+ }
@@ -2,7 +2,7 @@ import { FormContextType, RJSFSchema, StrictRJSFSchema, TemplatesType } from '@r
2
2
 
3
3
  import SubmitButton from './SubmitButton';
4
4
  import AddButton from './AddButton';
5
- import { CopyButton, MoveDownButton, MoveUpButton, RemoveButton } from './IconButton';
5
+ import { CopyButton, MoveDownButton, MoveUpButton, RemoveButton, ClearButton } from './IconButton';
6
6
 
7
7
  function buttonTemplates<
8
8
  T = any,
@@ -16,6 +16,7 @@ function buttonTemplates<
16
16
  MoveDownButton,
17
17
  MoveUpButton,
18
18
  RemoveButton,
19
+ ClearButton,
19
20
  };
20
21
  }
21
22
 
@@ -46,6 +46,15 @@ export default function ObjectFieldTemplate<
46
46
  registry,
47
47
  options,
48
48
  );
49
+
50
+ // For "pure union" schemas (oneOf/anyOf without properties), skip rendering the empty fieldset wrapper.
51
+ // The AnyOfField/OneOfField will handle rendering the union selector and selected variant's content directly.
52
+ const isPureUnionSchema = (schema.oneOf || schema.anyOf) && !schema.properties && properties.length === 0;
53
+
54
+ if (isPureUnionSchema) {
55
+ return null;
56
+ }
57
+
49
58
  const showOptionalDataControlInTitle = !readonly && !disabled;
50
59
  // Button templates are not overridden in the uiSchema
51
60
  const {
@@ -54,9 +54,4 @@ function TextareaWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F exte
54
54
  );
55
55
  }
56
56
 
57
- TextareaWidget.defaultProps = {
58
- autofocus: false,
59
- options: {},
60
- };
61
-
62
57
  export default TextareaWidget;