@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.
- package/dist/core.umd.js +65 -44
- package/dist/index.cjs +65 -44
- package/dist/index.cjs.map +3 -3
- package/dist/index.esm.js +68 -47
- package/dist/index.esm.js.map +3 -3
- package/lib/components/Form.d.ts +9 -1
- package/lib/components/Form.d.ts.map +1 -1
- package/lib/components/Form.js +35 -49
- package/lib/components/fields/LayoutMultiSchemaField.d.ts.map +1 -1
- package/lib/components/fields/LayoutMultiSchemaField.js +1 -1
- package/lib/components/fields/SchemaField.d.ts.map +1 -1
- package/lib/components/fields/SchemaField.js +1 -0
- package/lib/components/templates/BaseInputTemplate.d.ts.map +1 -1
- package/lib/components/templates/BaseInputTemplate.js +7 -1
- package/lib/components/templates/ButtonTemplates/IconButton.d.ts +1 -0
- package/lib/components/templates/ButtonTemplates/IconButton.d.ts.map +1 -1
- package/lib/components/templates/ButtonTemplates/IconButton.js +4 -0
- package/lib/components/templates/ButtonTemplates/index.d.ts.map +1 -1
- package/lib/components/templates/ButtonTemplates/index.js +2 -1
- package/lib/components/templates/ObjectFieldTemplate.d.ts +1 -1
- package/lib/components/templates/ObjectFieldTemplate.d.ts.map +1 -1
- package/lib/components/templates/ObjectFieldTemplate.js +6 -0
- package/lib/components/widgets/TextareaWidget.d.ts +0 -6
- package/lib/components/widgets/TextareaWidget.d.ts.map +1 -1
- package/lib/components/widgets/TextareaWidget.js +0 -4
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -10
- package/src/components/Form.tsx +44 -58
- package/src/components/fields/LayoutMultiSchemaField.tsx +1 -0
- package/src/components/fields/SchemaField.tsx +1 -0
- package/src/components/templates/BaseInputTemplate.tsx +13 -1
- package/src/components/templates/ButtonTemplates/IconButton.tsx +24 -0
- package/src/components/templates/ButtonTemplates/index.ts +2 -1
- package/src/components/templates/ObjectFieldTemplate.tsx +9 -0
- 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.
|
|
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.
|
|
80
|
-
"@rjsf/utils": "^6.
|
|
81
|
-
"@rjsf/validator-ajv8": "^6.
|
|
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
|
-
"
|
|
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"
|
package/src/components/Form.tsx
CHANGED
|
@@ -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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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[]
|
|
762
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
876
|
+
const newState = this.getStateFromProps(this.props, inputForDefaults, undefined, undefined, undefined, true);
|
|
891
877
|
formData = newState.formData;
|
|
892
878
|
retrievedSchema = newState.retrievedSchema;
|
|
893
879
|
}
|
|
@@ -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 {
|