@rjsf/core 6.0.0-beta.9 → 6.0.0
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/README.md +2 -0
- package/dist/core.umd.js +2042 -1987
- package/dist/index.cjs +4909 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.esm.js +2509 -2389
- package/dist/index.esm.js.map +4 -4
- package/lib/components/Form.d.ts +137 -34
- package/lib/components/Form.d.ts.map +1 -1
- package/lib/components/Form.js +318 -173
- package/lib/components/fields/ArrayField.d.ts +2 -187
- package/lib/components/fields/ArrayField.d.ts.map +1 -1
- package/lib/components/fields/ArrayField.js +526 -492
- package/lib/components/fields/BooleanField.d.ts.map +1 -1
- package/lib/components/fields/BooleanField.js +8 -3
- package/lib/components/fields/FallbackField.d.ts +7 -0
- package/lib/components/fields/FallbackField.d.ts.map +1 -0
- package/lib/components/fields/FallbackField.js +72 -0
- package/lib/components/fields/LayoutGridField.d.ts +109 -186
- package/lib/components/fields/LayoutGridField.d.ts.map +1 -1
- package/lib/components/fields/LayoutGridField.js +426 -426
- package/lib/components/fields/LayoutHeaderField.d.ts +1 -1
- package/lib/components/fields/LayoutHeaderField.js +3 -3
- package/lib/components/fields/LayoutMultiSchemaField.d.ts.map +1 -1
- package/lib/components/fields/LayoutMultiSchemaField.js +6 -6
- package/lib/components/fields/MultiSchemaField.d.ts.map +1 -1
- package/lib/components/fields/MultiSchemaField.js +16 -10
- package/lib/components/fields/NullField.js +3 -3
- package/lib/components/fields/NumberField.d.ts.map +1 -1
- package/lib/components/fields/NumberField.js +3 -3
- package/lib/components/fields/ObjectField.d.ts +2 -68
- package/lib/components/fields/ObjectField.d.ts.map +1 -1
- package/lib/components/fields/ObjectField.js +163 -163
- package/lib/components/fields/OptionalDataControlsField.d.ts +8 -0
- package/lib/components/fields/OptionalDataControlsField.d.ts.map +1 -0
- package/lib/components/fields/OptionalDataControlsField.js +43 -0
- package/lib/components/fields/SchemaField.d.ts.map +1 -1
- package/lib/components/fields/SchemaField.js +52 -30
- package/lib/components/fields/StringField.d.ts.map +1 -1
- package/lib/components/fields/StringField.js +8 -3
- package/lib/components/fields/index.d.ts.map +1 -1
- package/lib/components/fields/index.js +4 -0
- package/lib/components/templates/ArrayFieldDescriptionTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldDescriptionTemplate.js +3 -3
- package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts +3 -3
- package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts.map +1 -1
- package/lib/components/templates/ArrayFieldItemButtonsTemplate.js +3 -8
- package/lib/components/templates/ArrayFieldItemTemplate.d.ts +3 -3
- package/lib/components/templates/ArrayFieldItemTemplate.d.ts.map +1 -1
- package/lib/components/templates/ArrayFieldItemTemplate.js +1 -1
- package/lib/components/templates/ArrayFieldTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldTemplate.d.ts.map +1 -1
- package/lib/components/templates/ArrayFieldTemplate.js +4 -5
- package/lib/components/templates/ArrayFieldTitleTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldTitleTemplate.d.ts.map +1 -1
- package/lib/components/templates/ArrayFieldTitleTemplate.js +3 -3
- package/lib/components/templates/BaseInputTemplate.js +2 -2
- package/lib/components/templates/ButtonTemplates/AddButton.d.ts +1 -1
- package/lib/components/templates/ButtonTemplates/AddButton.d.ts.map +1 -1
- package/lib/components/templates/ButtonTemplates/AddButton.js +2 -2
- package/lib/components/templates/FallbackFieldTemplate.d.ts +7 -0
- package/lib/components/templates/FallbackFieldTemplate.d.ts.map +1 -0
- package/lib/components/templates/FallbackFieldTemplate.js +12 -0
- package/lib/components/templates/FieldErrorTemplate.js +2 -2
- package/lib/components/templates/FieldHelpTemplate.js +2 -2
- package/lib/components/templates/MultiSchemaFieldTemplate.d.ts +8 -0
- package/lib/components/templates/MultiSchemaFieldTemplate.d.ts.map +1 -0
- package/lib/components/templates/MultiSchemaFieldTemplate.js +10 -0
- package/lib/components/templates/ObjectFieldTemplate.d.ts.map +1 -1
- package/lib/components/templates/ObjectFieldTemplate.js +3 -2
- package/lib/components/templates/OptionalDataControlsTemplate.d.ts +11 -0
- package/lib/components/templates/OptionalDataControlsTemplate.d.ts.map +1 -0
- package/lib/components/templates/OptionalDataControlsTemplate.js +20 -0
- package/lib/components/templates/TitleField.d.ts.map +1 -1
- package/lib/components/templates/TitleField.js +2 -2
- package/lib/components/templates/UnsupportedField.js +3 -3
- package/lib/components/templates/WrapIfAdditionalTemplate.js +2 -2
- package/lib/components/templates/index.d.ts.map +1 -1
- package/lib/components/templates/index.js +6 -0
- package/lib/components/widgets/AltDateWidget.d.ts +1 -1
- package/lib/components/widgets/AltDateWidget.d.ts.map +1 -1
- package/lib/components/widgets/AltDateWidget.js +5 -46
- package/lib/components/widgets/CheckboxWidget.d.ts +1 -1
- package/lib/components/widgets/CheckboxWidget.d.ts.map +1 -1
- package/lib/components/widgets/CheckboxWidget.js +2 -2
- package/lib/components/widgets/CheckboxesWidget.d.ts +1 -1
- package/lib/components/widgets/CheckboxesWidget.d.ts.map +1 -1
- package/lib/components/widgets/CheckboxesWidget.js +4 -4
- package/lib/components/widgets/FileWidget.d.ts.map +1 -1
- package/lib/components/widgets/FileWidget.js +7 -87
- package/lib/components/widgets/HiddenWidget.d.ts +1 -1
- package/lib/components/widgets/HiddenWidget.d.ts.map +1 -1
- package/lib/components/widgets/HiddenWidget.js +2 -2
- package/lib/components/widgets/RadioWidget.d.ts +1 -1
- package/lib/components/widgets/RadioWidget.d.ts.map +1 -1
- package/lib/components/widgets/RadioWidget.js +2 -2
- package/lib/components/widgets/RatingWidget.d.ts +1 -1
- package/lib/components/widgets/RatingWidget.d.ts.map +1 -1
- package/lib/components/widgets/RatingWidget.js +2 -2
- package/lib/components/widgets/SelectWidget.d.ts +1 -1
- package/lib/components/widgets/SelectWidget.d.ts.map +1 -1
- package/lib/components/widgets/SelectWidget.js +2 -2
- package/lib/components/widgets/TextareaWidget.d.ts +1 -1
- package/lib/components/widgets/TextareaWidget.d.ts.map +1 -1
- package/lib/components/widgets/TextareaWidget.js +2 -2
- package/lib/getDefaultRegistry.d.ts.map +1 -1
- package/lib/getDefaultRegistry.js +6 -1
- package/lib/getTestRegistry.d.ts +5 -0
- package/lib/getTestRegistry.d.ts.map +1 -0
- package/lib/getTestRegistry.js +23 -0
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -20
- package/src/components/Form.tsx +468 -206
- package/src/components/fields/ArrayField.tsx +871 -723
- package/src/components/fields/BooleanField.tsx +14 -5
- package/src/components/fields/FallbackField.tsx +157 -0
- package/src/components/fields/LayoutGridField.tsx +626 -603
- package/src/components/fields/LayoutHeaderField.tsx +3 -3
- package/src/components/fields/LayoutMultiSchemaField.tsx +9 -10
- package/src/components/fields/MultiSchemaField.tsx +57 -36
- package/src/components/fields/NullField.tsx +3 -3
- package/src/components/fields/NumberField.tsx +11 -3
- package/src/components/fields/ObjectField.tsx +308 -239
- package/src/components/fields/OptionalDataControlsField.tsx +84 -0
- package/src/components/fields/SchemaField.tsx +75 -94
- package/src/components/fields/StringField.tsx +14 -5
- package/src/components/fields/index.ts +4 -0
- package/src/components/templates/ArrayFieldDescriptionTemplate.tsx +3 -3
- package/src/components/templates/ArrayFieldItemButtonsTemplate.tsx +16 -21
- package/src/components/templates/ArrayFieldItemTemplate.tsx +3 -3
- package/src/components/templates/ArrayFieldTemplate.tsx +11 -18
- package/src/components/templates/ArrayFieldTitleTemplate.tsx +4 -3
- package/src/components/templates/BaseInputTemplate.tsx +5 -5
- package/src/components/templates/ButtonTemplates/AddButton.tsx +2 -0
- package/src/components/templates/FallbackFieldTemplate.tsx +28 -0
- package/src/components/templates/FieldErrorTemplate.tsx +2 -2
- package/src/components/templates/FieldHelpTemplate.tsx +2 -2
- package/src/components/templates/MultiSchemaFieldTemplate.tsx +20 -0
- package/src/components/templates/ObjectFieldTemplate.tsx +12 -7
- package/src/components/templates/OptionalDataControlsTemplate.tsx +43 -0
- package/src/components/templates/TitleField.tsx +6 -1
- package/src/components/templates/UnsupportedField.tsx +3 -3
- package/src/components/templates/WrapIfAdditionalTemplate.tsx +5 -5
- package/src/components/templates/index.ts +6 -0
- package/src/components/widgets/AltDateWidget.tsx +8 -126
- package/src/components/widgets/CheckboxWidget.tsx +4 -3
- package/src/components/widgets/CheckboxesWidget.tsx +5 -4
- package/src/components/widgets/FileWidget.tsx +11 -102
- package/src/components/widgets/HiddenWidget.tsx +2 -1
- package/src/components/widgets/RadioWidget.tsx +3 -2
- package/src/components/widgets/RatingWidget.tsx +2 -1
- package/src/components/widgets/SelectWidget.tsx +3 -2
- package/src/components/widgets/TextareaWidget.tsx +3 -2
- package/src/getDefaultRegistry.ts +14 -1
- package/src/getTestRegistry.tsx +38 -0
- package/src/index.ts +2 -1
- package/dist/index.js +0 -4834
- package/dist/index.js.map +0 -7
package/lib/components/Form.js
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Component, createRef } from 'react';
|
|
3
|
-
import { createSchemaUtils, deepEquals, getChangedFields, getTemplate, getUiOptions, isObject, mergeObjects, NAME_KEY, RJSF_ADDITIONAL_PROPERTIES_FLAG, shouldRender, SUBMIT_BTN_OPTIONS_KEY, toErrorList, UI_GLOBAL_OPTIONS_KEY, UI_OPTIONS_KEY, validationDataMerge,
|
|
4
|
-
import
|
|
3
|
+
import { createSchemaUtils, deepEquals, ErrorSchemaBuilder, getChangedFields, getTemplate, getUiOptions, isObject, mergeObjects, NAME_KEY, RJSF_ADDITIONAL_PROPERTIES_FLAG, shouldRender, SUBMIT_BTN_OPTIONS_KEY, toErrorList, toFieldPathId, UI_GLOBAL_OPTIONS_KEY, UI_OPTIONS_KEY, validationDataMerge, DEFAULT_ID_SEPARATOR, DEFAULT_ID_PREFIX, ERRORS_KEY, ID_KEY, } from '@rjsf/utils';
|
|
4
|
+
import _cloneDeep from 'lodash-es/cloneDeep.js';
|
|
5
5
|
import _get from 'lodash-es/get.js';
|
|
6
6
|
import _isEmpty from 'lodash-es/isEmpty.js';
|
|
7
|
-
import _isNil from 'lodash-es/isNil.js';
|
|
8
7
|
import _pick from 'lodash-es/pick.js';
|
|
8
|
+
import _set from 'lodash-es/set.js';
|
|
9
9
|
import _toPath from 'lodash-es/toPath.js';
|
|
10
10
|
import getDefaultRegistry from '../getDefaultRegistry.js';
|
|
11
|
+
/** Internal only symbol used by the `reset()` function to indicate that a reset operation is happening */
|
|
12
|
+
const IS_RESET = Symbol('reset');
|
|
13
|
+
/** Converts the full `FormState` into the `IChangeEvent` version by picking out the public values
|
|
14
|
+
*
|
|
15
|
+
* @param state - The state of the form
|
|
16
|
+
* @param status - The status provided by the onSubmit
|
|
17
|
+
* @returns - The `IChangeEvent` for the state
|
|
18
|
+
*/
|
|
19
|
+
function toIChangeEvent(state, status) {
|
|
20
|
+
return {
|
|
21
|
+
..._pick(state, ['schema', 'uiSchema', 'fieldPathId', 'schemaUtils', 'formData', 'edit', 'errors', 'errorSchema']),
|
|
22
|
+
...(status !== undefined && { status }),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
11
25
|
/** The `Form` component renders the outer form and all the fields defined in the `schema` */
|
|
12
26
|
export default class Form extends Component {
|
|
13
27
|
/** The ref used to hold the `form` element, this needs to be `any` because `tagName` or `_internalFormWrapper` can
|
|
14
28
|
* provide any possible type here
|
|
15
29
|
*/
|
|
16
30
|
formElement;
|
|
31
|
+
/** The list of pending changes
|
|
32
|
+
*/
|
|
33
|
+
pendingChanges = [];
|
|
17
34
|
/** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the
|
|
18
35
|
* `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the
|
|
19
36
|
* state construction.
|
|
@@ -25,9 +42,11 @@ export default class Form extends Component {
|
|
|
25
42
|
if (!props.validator) {
|
|
26
43
|
throw new Error('A validator is required for Form functionality to work');
|
|
27
44
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
const { formData: propsFormData, initialFormData, onChange } = props;
|
|
46
|
+
const formData = propsFormData ?? initialFormData;
|
|
47
|
+
this.state = this.getStateFromProps(props, formData, undefined, undefined, undefined, true);
|
|
48
|
+
if (onChange && !deepEquals(this.state.formData, formData)) {
|
|
49
|
+
onChange(toIChangeEvent(this.state));
|
|
31
50
|
}
|
|
32
51
|
this.formElement = createRef();
|
|
33
52
|
}
|
|
@@ -51,16 +70,23 @@ export default class Form extends Component {
|
|
|
51
70
|
*/
|
|
52
71
|
getSnapshotBeforeUpdate(prevProps, prevState) {
|
|
53
72
|
if (!deepEquals(this.props, prevProps)) {
|
|
73
|
+
// Compare the previous props formData against the current props formData
|
|
54
74
|
const formDataChangedFields = getChangedFields(this.props.formData, prevProps.formData);
|
|
75
|
+
// Compare the current props formData against the current state's formData to determine if the new props were the
|
|
76
|
+
// result of the onChange from the existing state formData
|
|
77
|
+
const stateDataChangedFields = getChangedFields(this.props.formData, this.state.formData);
|
|
55
78
|
const isSchemaChanged = !deepEquals(prevProps.schema, this.props.schema);
|
|
56
79
|
// When formData is not an object, getChangedFields returns an empty array.
|
|
57
80
|
// In this case, deepEquals is most needed to check again.
|
|
58
81
|
const isFormDataChanged = formDataChangedFields.length > 0 || !deepEquals(prevProps.formData, this.props.formData);
|
|
82
|
+
const isStateDataChanged = stateDataChangedFields.length > 0 || !deepEquals(this.state.formData, this.props.formData);
|
|
59
83
|
const nextState = this.getStateFromProps(this.props, this.props.formData,
|
|
60
84
|
// If the `schema` has changed, we need to update the retrieved schema.
|
|
61
85
|
// Or if the `formData` changes, for example in the case of a schema with dependencies that need to
|
|
62
86
|
// match one of the subSchemas, the retrieved schema must be updated.
|
|
63
|
-
isSchemaChanged || isFormDataChanged ? undefined : this.state.retrievedSchema, isSchemaChanged, formDataChangedFields
|
|
87
|
+
isSchemaChanged || isFormDataChanged ? undefined : this.state.retrievedSchema, isSchemaChanged, formDataChangedFields,
|
|
88
|
+
// Skip live validation for this request if no form data has changed from the last state
|
|
89
|
+
!isStateDataChanged);
|
|
64
90
|
const shouldUpdate = !deepEquals(nextState, prevState);
|
|
65
91
|
return { nextState, shouldUpdate };
|
|
66
92
|
}
|
|
@@ -84,7 +110,7 @@ export default class Form extends Component {
|
|
|
84
110
|
if (!deepEquals(nextState.formData, this.props.formData) &&
|
|
85
111
|
!deepEquals(nextState.formData, prevState.formData) &&
|
|
86
112
|
this.props.onChange) {
|
|
87
|
-
this.props.onChange(nextState);
|
|
113
|
+
this.props.onChange(toIChangeEvent(nextState));
|
|
88
114
|
}
|
|
89
115
|
this.setState(nextState);
|
|
90
116
|
}
|
|
@@ -98,16 +124,18 @@ export default class Form extends Component {
|
|
|
98
124
|
* @param retrievedSchema - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`.
|
|
99
125
|
* @param isSchemaChanged - A flag indicating whether the schema has changed.
|
|
100
126
|
* @param formDataChangedFields - The changed fields of `formData`
|
|
127
|
+
* @param skipLiveValidate - Optional flag, if true, means that we are not running live validation
|
|
101
128
|
* @returns - The new state for the `Form`
|
|
102
129
|
*/
|
|
103
|
-
getStateFromProps(props, inputFormData, retrievedSchema, isSchemaChanged = false, formDataChangedFields = []) {
|
|
130
|
+
getStateFromProps(props, inputFormData, retrievedSchema, isSchemaChanged = false, formDataChangedFields = [], skipLiveValidate = false) {
|
|
104
131
|
const state = this.state || {};
|
|
105
132
|
const schema = 'schema' in props ? props.schema : this.props.schema;
|
|
133
|
+
const validator = 'validator' in props ? props.validator : this.props.validator;
|
|
106
134
|
const uiSchema = ('uiSchema' in props ? props.uiSchema : this.props.uiSchema) || {};
|
|
135
|
+
const isUncontrolled = props.formData === undefined && this.props.formData === undefined;
|
|
107
136
|
const edit = typeof inputFormData !== 'undefined';
|
|
108
137
|
const liveValidate = 'liveValidate' in props ? props.liveValidate : this.props.liveValidate;
|
|
109
138
|
const mustValidate = edit && !props.noValidate && liveValidate;
|
|
110
|
-
const rootSchema = schema;
|
|
111
139
|
const experimental_defaultFormStateBehavior = 'experimental_defaultFormStateBehavior' in props
|
|
112
140
|
? props.experimental_defaultFormStateBehavior
|
|
113
141
|
: this.props.experimental_defaultFormStateBehavior;
|
|
@@ -116,11 +144,20 @@ export default class Form extends Component {
|
|
|
116
144
|
: this.props.experimental_customMergeAllOf;
|
|
117
145
|
let schemaUtils = state.schemaUtils;
|
|
118
146
|
if (!schemaUtils ||
|
|
119
|
-
schemaUtils.doesSchemaUtilsDiffer(
|
|
120
|
-
schemaUtils = createSchemaUtils(
|
|
147
|
+
schemaUtils.doesSchemaUtilsDiffer(validator, schema, experimental_defaultFormStateBehavior, experimental_customMergeAllOf)) {
|
|
148
|
+
schemaUtils = createSchemaUtils(validator, schema, experimental_defaultFormStateBehavior, experimental_customMergeAllOf);
|
|
121
149
|
}
|
|
122
|
-
const
|
|
123
|
-
|
|
150
|
+
const rootSchema = schemaUtils.getRootSchema();
|
|
151
|
+
// Compute the formData for getDefaultFormState() function based on the inputFormData, isUncontrolled and state
|
|
152
|
+
let defaultsFormData = inputFormData;
|
|
153
|
+
if (inputFormData === IS_RESET) {
|
|
154
|
+
defaultsFormData = undefined;
|
|
155
|
+
}
|
|
156
|
+
else if (inputFormData === undefined && isUncontrolled) {
|
|
157
|
+
defaultsFormData = state.formData;
|
|
158
|
+
}
|
|
159
|
+
const formData = schemaUtils.getDefaultFormState(rootSchema, defaultsFormData, false, state.initialDefaultsGenerated);
|
|
160
|
+
const _retrievedSchema = this.updateRetrievedSchema(retrievedSchema ?? schemaUtils.retrieveSchema(rootSchema, formData));
|
|
124
161
|
const getCurrentErrors = () => {
|
|
125
162
|
// If the `props.noValidate` option is set or the schema has changed, we reset the error state.
|
|
126
163
|
if (props.noValidate || isSchemaChanged) {
|
|
@@ -141,43 +178,45 @@ export default class Form extends Component {
|
|
|
141
178
|
let errorSchema;
|
|
142
179
|
let schemaValidationErrors = state.schemaValidationErrors;
|
|
143
180
|
let schemaValidationErrorSchema = state.schemaValidationErrorSchema;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
181
|
+
// If we are skipping live validate, it means that the state has already been updated with live validation errors
|
|
182
|
+
if (mustValidate && !skipLiveValidate) {
|
|
183
|
+
const liveValidation = this.liveValidate(rootSchema, schemaUtils, state.errorSchema, formData, undefined, state.customErrors, retrievedSchema,
|
|
147
184
|
// If retrievedSchema is undefined which means the schema or formData has changed, we do not merge state.
|
|
148
|
-
// Else in the case where it hasn't changed,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
schemaValidationErrors = errors;
|
|
156
|
-
schemaValidationErrorSchema = errorSchema;
|
|
185
|
+
// Else in the case where it hasn't changed,
|
|
186
|
+
retrievedSchema !== undefined);
|
|
187
|
+
errors = liveValidation.errors;
|
|
188
|
+
errorSchema = liveValidation.errorSchema;
|
|
189
|
+
schemaValidationErrors = liveValidation.schemaValidationErrors;
|
|
190
|
+
schemaValidationErrorSchema = liveValidation.schemaValidationErrorSchema;
|
|
157
191
|
}
|
|
158
192
|
else {
|
|
159
193
|
const currentErrors = getCurrentErrors();
|
|
160
194
|
errors = currentErrors.errors;
|
|
161
195
|
errorSchema = currentErrors.errorSchema;
|
|
162
|
-
if
|
|
196
|
+
// We only update the error schema for changed fields if mustValidate is false
|
|
197
|
+
if (formDataChangedFields.length > 0 && !mustValidate) {
|
|
163
198
|
const newErrorSchema = formDataChangedFields.reduce((acc, key) => {
|
|
164
199
|
acc[key] = undefined;
|
|
165
200
|
return acc;
|
|
166
201
|
}, {});
|
|
167
202
|
errorSchema = schemaValidationErrorSchema = mergeObjects(currentErrors.errorSchema, newErrorSchema, 'preventDuplicates');
|
|
168
203
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
204
|
+
const mergedErrors = this.mergeErrors({ errorSchema, errors }, props.extraErrors, state.customErrors);
|
|
205
|
+
errors = mergedErrors.errors;
|
|
206
|
+
errorSchema = mergedErrors.errorSchema;
|
|
207
|
+
}
|
|
208
|
+
// Only store a new registry when the props cause a different one to be created
|
|
209
|
+
const newRegistry = this.getRegistry(props, rootSchema, schemaUtils);
|
|
210
|
+
const registry = deepEquals(state.registry, newRegistry) ? state.registry : newRegistry;
|
|
211
|
+
// Only compute a new `fieldPathId` when the `idPrefix` is different than the existing fieldPathId's ID_KEY
|
|
212
|
+
const fieldPathId = state.fieldPathId && state.fieldPathId?.[ID_KEY] === registry.globalFormOptions.idPrefix
|
|
213
|
+
? state.fieldPathId
|
|
214
|
+
: toFieldPathId('', registry.globalFormOptions);
|
|
176
215
|
const nextState = {
|
|
177
216
|
schemaUtils,
|
|
178
|
-
schema,
|
|
217
|
+
schema: rootSchema,
|
|
179
218
|
uiSchema,
|
|
180
|
-
|
|
219
|
+
fieldPathId,
|
|
181
220
|
formData,
|
|
182
221
|
edit,
|
|
183
222
|
errors,
|
|
@@ -185,6 +224,8 @@ export default class Form extends Component {
|
|
|
185
224
|
schemaValidationErrors,
|
|
186
225
|
schemaValidationErrorSchema,
|
|
187
226
|
retrievedSchema: _retrievedSchema,
|
|
227
|
+
initialDefaultsGenerated: true,
|
|
228
|
+
registry,
|
|
188
229
|
};
|
|
189
230
|
return nextState;
|
|
190
231
|
}
|
|
@@ -195,31 +236,18 @@ export default class Form extends Component {
|
|
|
195
236
|
* @returns - True if the component should be updated, false otherwise
|
|
196
237
|
*/
|
|
197
238
|
shouldComponentUpdate(nextProps, nextState) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
/** Gets the previously raised customValidate errors.
|
|
201
|
-
*
|
|
202
|
-
* @returns the previous customValidate errors
|
|
203
|
-
*/
|
|
204
|
-
getPreviousCustomValidateErrors() {
|
|
205
|
-
const { customValidate, uiSchema } = this.props;
|
|
206
|
-
const prevFormData = this.state.formData;
|
|
207
|
-
let customValidateErrors = {};
|
|
208
|
-
if (typeof customValidate === 'function') {
|
|
209
|
-
const errorHandler = customValidate(prevFormData, createErrorHandler(prevFormData), uiSchema);
|
|
210
|
-
const userErrorSchema = unwrapErrorHandler(errorHandler);
|
|
211
|
-
customValidateErrors = userErrorSchema;
|
|
212
|
-
}
|
|
213
|
-
return customValidateErrors;
|
|
239
|
+
const { experimental_componentUpdateStrategy = 'customDeep' } = this.props;
|
|
240
|
+
return shouldRender(this, nextProps, nextState, experimental_componentUpdateStrategy);
|
|
214
241
|
}
|
|
215
242
|
/** Validates the `formData` against the `schema` using the `altSchemaUtils` (if provided otherwise it uses the
|
|
216
243
|
* `schemaUtils` in the state), returning the results.
|
|
217
244
|
*
|
|
218
245
|
* @param formData - The new form data to validate
|
|
219
246
|
* @param schema - The schema used to validate against
|
|
220
|
-
* @param altSchemaUtils - The alternate schemaUtils to use for validation
|
|
247
|
+
* @param [altSchemaUtils] - The alternate schemaUtils to use for validation
|
|
248
|
+
* @param [retrievedSchema] - An optionally retrieved schema for per
|
|
221
249
|
*/
|
|
222
|
-
validate(formData, schema = this.
|
|
250
|
+
validate(formData, schema = this.state.schema, altSchemaUtils, retrievedSchema) {
|
|
223
251
|
const schemaUtils = altSchemaUtils ? altSchemaUtils : this.state.schemaUtils;
|
|
224
252
|
const { customValidate, transformErrors, uiSchema } = this.props;
|
|
225
253
|
const resolvedSchema = retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData);
|
|
@@ -230,14 +258,63 @@ export default class Form extends Component {
|
|
|
230
258
|
/** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */
|
|
231
259
|
renderErrors(registry) {
|
|
232
260
|
const { errors, errorSchema, schema, uiSchema } = this.state;
|
|
233
|
-
const { formContext } = this.props;
|
|
234
261
|
const options = getUiOptions(uiSchema);
|
|
235
262
|
const ErrorListTemplate = getTemplate('ErrorListTemplate', registry, options);
|
|
236
263
|
if (errors && errors.length) {
|
|
237
|
-
return (_jsx(ErrorListTemplate, { errors: errors, errorSchema: errorSchema || {}, schema: schema, uiSchema: uiSchema,
|
|
264
|
+
return (_jsx(ErrorListTemplate, { errors: errors, errorSchema: errorSchema || {}, schema: schema, uiSchema: uiSchema, registry: registry }));
|
|
238
265
|
}
|
|
239
266
|
return null;
|
|
240
267
|
}
|
|
268
|
+
/** Merges any `extraErrors` or `customErrors` into the given `schemaValidation` object, returning the result
|
|
269
|
+
*
|
|
270
|
+
* @param schemaValidation - The `ValidationData` object into which additional errors are merged
|
|
271
|
+
* @param [extraErrors] - The extra errors from the props
|
|
272
|
+
* @param [customErrors] - The customErrors from custom components
|
|
273
|
+
* @return - The `extraErrors` and `customErrors` merged into the `schemaValidation`
|
|
274
|
+
* @private
|
|
275
|
+
*/
|
|
276
|
+
mergeErrors(schemaValidation, extraErrors, customErrors) {
|
|
277
|
+
let errorSchema = schemaValidation.errorSchema;
|
|
278
|
+
let errors = schemaValidation.errors;
|
|
279
|
+
if (extraErrors) {
|
|
280
|
+
const merged = validationDataMerge(schemaValidation, extraErrors);
|
|
281
|
+
errorSchema = merged.errorSchema;
|
|
282
|
+
errors = merged.errors;
|
|
283
|
+
}
|
|
284
|
+
if (customErrors) {
|
|
285
|
+
const merged = validationDataMerge(schemaValidation, customErrors.ErrorSchema, true);
|
|
286
|
+
errorSchema = merged.errorSchema;
|
|
287
|
+
errors = merged.errors;
|
|
288
|
+
}
|
|
289
|
+
return { errors, errorSchema };
|
|
290
|
+
}
|
|
291
|
+
/** Performs live validation and then updates and returns the errors and error schemas by potentially merging in
|
|
292
|
+
* `extraErrors` and `customErrors`.
|
|
293
|
+
*
|
|
294
|
+
* @param rootSchema - The `rootSchema` from the state
|
|
295
|
+
* @param schemaUtils - The `SchemaUtilsType` from the state
|
|
296
|
+
* @param originalErrorSchema - The original `ErrorSchema` from the state
|
|
297
|
+
* @param [formData] - The new form data to validate
|
|
298
|
+
* @param [extraErrors] - The extra errors from the props
|
|
299
|
+
* @param [customErrors] - The customErrors from custom components
|
|
300
|
+
* @param [retrievedSchema] - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`
|
|
301
|
+
* @param [mergeIntoOriginalErrorSchema=false] - Optional flag indicating whether we merge into original schema
|
|
302
|
+
* @returns - An object containing `errorSchema`, `errors`, `schemaValidationErrors` and `schemaValidationErrorSchema`
|
|
303
|
+
* @private
|
|
304
|
+
*/
|
|
305
|
+
liveValidate(rootSchema, schemaUtils, originalErrorSchema, formData, extraErrors, customErrors, retrievedSchema, mergeIntoOriginalErrorSchema = false) {
|
|
306
|
+
const schemaValidation = this.validate(formData, rootSchema, schemaUtils, retrievedSchema);
|
|
307
|
+
const errors = schemaValidation.errors;
|
|
308
|
+
let errorSchema = schemaValidation.errorSchema;
|
|
309
|
+
// We merge 'originalErrorSchema' with 'schemaValidation.errorSchema.'; This done to display the raised field error.
|
|
310
|
+
if (mergeIntoOriginalErrorSchema) {
|
|
311
|
+
errorSchema = mergeObjects(originalErrorSchema, schemaValidation.errorSchema, 'preventDuplicates');
|
|
312
|
+
}
|
|
313
|
+
const schemaValidationErrors = errors;
|
|
314
|
+
const schemaValidationErrorSchema = errorSchema;
|
|
315
|
+
const mergedErrors = this.mergeErrors({ errorSchema, errors }, extraErrors, customErrors);
|
|
316
|
+
return { ...mergedErrors, schemaValidationErrors, schemaValidationErrorSchema };
|
|
317
|
+
}
|
|
241
318
|
/** Returns the `formData` with only the elements specified in the `fields` list
|
|
242
319
|
*
|
|
243
320
|
* @param formData - The data for the `Form`
|
|
@@ -261,26 +338,28 @@ export default class Form extends Component {
|
|
|
261
338
|
* @param [formData] - The form data to use while checking for empty objects/arrays
|
|
262
339
|
*/
|
|
263
340
|
getFieldNames = (pathSchema, formData) => {
|
|
341
|
+
const formValueHasData = (value, isLeaf) => typeof value !== 'object' || _isEmpty(value) || (isLeaf && !_isEmpty(value));
|
|
264
342
|
const getAllPaths = (_obj, acc = [], paths = [[]]) => {
|
|
265
|
-
Object.keys(_obj)
|
|
266
|
-
|
|
343
|
+
const objKeys = Object.keys(_obj);
|
|
344
|
+
objKeys.forEach((key) => {
|
|
345
|
+
const data = _obj[key];
|
|
346
|
+
if (typeof data === 'object') {
|
|
267
347
|
const newPaths = paths.map((path) => [...path, key]);
|
|
268
348
|
// If an object is marked with additionalProperties, all its keys are valid
|
|
269
|
-
if (
|
|
270
|
-
acc.push(
|
|
349
|
+
if (data[RJSF_ADDITIONAL_PROPERTIES_FLAG] && data[NAME_KEY] !== '') {
|
|
350
|
+
acc.push(data[NAME_KEY]);
|
|
271
351
|
}
|
|
272
352
|
else {
|
|
273
|
-
getAllPaths(
|
|
353
|
+
getAllPaths(data, acc, newPaths);
|
|
274
354
|
}
|
|
275
355
|
}
|
|
276
|
-
else if (key === NAME_KEY &&
|
|
356
|
+
else if (key === NAME_KEY && data !== '') {
|
|
277
357
|
paths.forEach((path) => {
|
|
278
358
|
const formValue = _get(formData, path);
|
|
279
|
-
|
|
280
|
-
// or an empty object/array
|
|
281
|
-
if (
|
|
282
|
-
|
|
283
|
-
(Array.isArray(formValue) && formValue.every((val) => typeof val !== 'object'))) {
|
|
359
|
+
const isLeaf = objKeys.length === 1;
|
|
360
|
+
// adds path to fieldNames if it points to a value or an empty object/array which is not a leaf
|
|
361
|
+
if (formValueHasData(formValue, isLeaf) ||
|
|
362
|
+
(Array.isArray(formValue) && formValue.every((val) => formValueHasData(val, isLeaf)))) {
|
|
284
363
|
acc.push(path);
|
|
285
364
|
}
|
|
286
365
|
});
|
|
@@ -300,116 +379,133 @@ export default class Form extends Component {
|
|
|
300
379
|
const retrievedSchema = schemaUtils.retrieveSchema(schema, formData);
|
|
301
380
|
const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData);
|
|
302
381
|
const fieldNames = this.getFieldNames(pathSchema, formData);
|
|
303
|
-
|
|
304
|
-
return newFormData;
|
|
382
|
+
return this.getUsedFormData(formData, fieldNames);
|
|
305
383
|
};
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return errors;
|
|
322
|
-
}
|
|
323
|
-
return errors.filter((error) => {
|
|
324
|
-
return !prevCustomErrors.includes(error);
|
|
325
|
-
});
|
|
326
|
-
};
|
|
327
|
-
// Removing undefined, null and empty errors.
|
|
328
|
-
const filterNilOrEmptyErrors = (errors, previousCustomValidateErrors = {}) => {
|
|
329
|
-
_forEach(errors, (errorAtKey, errorKey) => {
|
|
330
|
-
const prevCustomValidateErrorAtKey = previousCustomValidateErrors[errorKey];
|
|
331
|
-
if (_isNil(errorAtKey) || (Array.isArray(errorAtKey) && errorAtKey.length === 0)) {
|
|
332
|
-
delete errors[errorKey];
|
|
333
|
-
}
|
|
334
|
-
else if (isObject(errorAtKey) &&
|
|
335
|
-
isObject(prevCustomValidateErrorAtKey) &&
|
|
336
|
-
Array.isArray(prevCustomValidateErrorAtKey?.__errors)) {
|
|
337
|
-
// if previous customValidate error is an object and has __errors array, filter out the errors previous customValidate errors.
|
|
338
|
-
errors[errorKey] = filterPreviousCustomErrors(errorAtKey.__errors, prevCustomValidateErrorAtKey.__errors);
|
|
339
|
-
}
|
|
340
|
-
else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey.__errors)) {
|
|
341
|
-
filterNilOrEmptyErrors(errorAtKey, previousCustomValidateErrors[errorKey]);
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
return errors;
|
|
345
|
-
};
|
|
346
|
-
return filterNilOrEmptyErrors(filteredErrors, prevCustomValidateErrors);
|
|
347
|
-
}
|
|
348
|
-
/** Function to handle changes made to a field in the `Form`. This handler receives an entirely new copy of the
|
|
349
|
-
* `formData` along with a new `ErrorSchema`. It will first update the `formData` with any missing default fields and
|
|
350
|
-
* then, if `omitExtraData` and `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not
|
|
351
|
-
* in a form field. Then, the resulting formData will be validated if required. The state will be updated with the new
|
|
352
|
-
* updated (potentially filtered) `formData`, any errors that resulted from validation. Finally the `onChange`
|
|
353
|
-
* callback will be called if specified with the updated state.
|
|
384
|
+
/** Allows a user to set a value for the provided `fieldPath`, which must be either a dotted path to the field OR a
|
|
385
|
+
* `FieldPathList`. To set the root element, used either `''` or `[]` for the path. Passing undefined will clear the
|
|
386
|
+
* value in the field.
|
|
387
|
+
*
|
|
388
|
+
* @param fieldPath - Either a dotted path to the field or the `FieldPathList` to the field
|
|
389
|
+
* @param [newValue] - The new value for the field
|
|
390
|
+
*/
|
|
391
|
+
setFieldValue = (fieldPath, newValue) => {
|
|
392
|
+
const { registry } = this.state;
|
|
393
|
+
const path = Array.isArray(fieldPath) ? fieldPath : fieldPath.split('.');
|
|
394
|
+
const fieldPathId = toFieldPathId('', registry.globalFormOptions, path);
|
|
395
|
+
this.onChange(newValue, path, undefined, fieldPathId[ID_KEY]);
|
|
396
|
+
};
|
|
397
|
+
/** Pushes the given change information into the `pendingChanges` array and then calls `processPendingChanges()` if
|
|
398
|
+
* the array only contains a single pending change.
|
|
354
399
|
*
|
|
355
|
-
* @param
|
|
356
|
-
* @param
|
|
357
|
-
* @param
|
|
400
|
+
* @param newValue - The new form data from a change to a field
|
|
401
|
+
* @param path - The path to the change into which to set the formData
|
|
402
|
+
* @param [newErrorSchema] - The new `ErrorSchema` based on the field change
|
|
403
|
+
* @param [id] - The id of the field that caused the change
|
|
358
404
|
*/
|
|
359
|
-
onChange = (
|
|
405
|
+
onChange = (newValue, path, newErrorSchema, id) => {
|
|
406
|
+
this.pendingChanges.push({ newValue, path, newErrorSchema, id });
|
|
407
|
+
if (this.pendingChanges.length === 1) {
|
|
408
|
+
this.processPendingChange();
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
/** Function to handle changes made to a field in the `Form`. This handler gets the first change from the
|
|
412
|
+
* `pendingChanges` list, containing the `newValue` for the `formData` and the `path` at which the `newValue` is to be
|
|
413
|
+
* updated, along with a new, optional `ErrorSchema` for that same `path` and potentially the `id` of the field being
|
|
414
|
+
* changed. It will first update the `formData` with any missing default fields and then, if `omitExtraData` and
|
|
415
|
+
* `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not in a form field. Then, the
|
|
416
|
+
* resulting `formData` will be validated if required. The state will be updated with the new updated (potentially
|
|
417
|
+
* filtered) `formData`, any errors that resulted from validation. Finally the `onChange` callback will be called, if
|
|
418
|
+
* specified, with the updated state and the `processPendingChange()` function is called again.
|
|
419
|
+
*/
|
|
420
|
+
processPendingChange() {
|
|
421
|
+
if (this.pendingChanges.length === 0) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const { newValue, path, id } = this.pendingChanges[0];
|
|
425
|
+
const { newErrorSchema } = this.pendingChanges[0];
|
|
360
426
|
const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;
|
|
361
|
-
const { schemaUtils, schema } = this.state;
|
|
427
|
+
const { formData: oldFormData, schemaUtils, schema, fieldPathId, schemaValidationErrorSchema, errors } = this.state;
|
|
428
|
+
let { customErrors, errorSchema: originalErrorSchema } = this.state;
|
|
429
|
+
const rootPathId = fieldPathId.path[0] || '';
|
|
430
|
+
const isRootPath = !path || path.length === 0 || (path.length === 1 && path[0] === rootPathId);
|
|
362
431
|
let retrievedSchema = this.state.retrievedSchema;
|
|
432
|
+
let formData = isRootPath ? newValue : _cloneDeep(oldFormData);
|
|
363
433
|
if (isObject(formData) || Array.isArray(formData)) {
|
|
364
|
-
|
|
434
|
+
if (!isRootPath) {
|
|
435
|
+
// If the newValue is not on the root path, then set it into the form data
|
|
436
|
+
_set(formData, path, newValue);
|
|
437
|
+
}
|
|
438
|
+
// Pass true to skip live validation in `getStateFromProps()` since we will do it a bit later
|
|
439
|
+
const newState = this.getStateFromProps(this.props, formData, undefined, undefined, undefined, true);
|
|
365
440
|
formData = newState.formData;
|
|
366
441
|
retrievedSchema = newState.retrievedSchema;
|
|
367
442
|
}
|
|
368
|
-
const mustValidate = !noValidate && liveValidate;
|
|
443
|
+
const mustValidate = !noValidate && (liveValidate === true || liveValidate === 'onChange');
|
|
369
444
|
let state = { formData, schema };
|
|
370
445
|
let newFormData = formData;
|
|
371
|
-
if (omitExtraData === true && liveOmit === true) {
|
|
446
|
+
if (omitExtraData === true && (liveOmit === true || liveOmit === 'onChange')) {
|
|
372
447
|
newFormData = this.omitExtraData(formData);
|
|
373
448
|
state = {
|
|
374
449
|
formData: newFormData,
|
|
375
450
|
};
|
|
376
451
|
}
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
452
|
+
if (newErrorSchema) {
|
|
453
|
+
// First check to see if there is an existing validation error on this path...
|
|
454
|
+
// @ts-expect-error TS2590, because getting from the error schema is confusing TS
|
|
455
|
+
const oldValidationError = !isRootPath ? _get(schemaValidationErrorSchema, path) : schemaValidationErrorSchema;
|
|
456
|
+
// If there is an old validation error for this path, assume we are updating it directly
|
|
457
|
+
if (!_isEmpty(oldValidationError)) {
|
|
458
|
+
// Update the originalErrorSchema "in place" or replace it if it is the root
|
|
459
|
+
if (!isRootPath) {
|
|
460
|
+
_set(originalErrorSchema, path, newErrorSchema);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
originalErrorSchema = newErrorSchema;
|
|
464
|
+
}
|
|
387
465
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
466
|
+
else {
|
|
467
|
+
if (!customErrors) {
|
|
468
|
+
customErrors = new ErrorSchemaBuilder();
|
|
469
|
+
}
|
|
470
|
+
if (isRootPath) {
|
|
471
|
+
const errors = _get(newErrorSchema, ERRORS_KEY);
|
|
472
|
+
if (errors) {
|
|
473
|
+
// only set errors when there are some
|
|
474
|
+
customErrors.setErrors(errors);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
_set(customErrors.ErrorSchema, path, newErrorSchema);
|
|
479
|
+
}
|
|
392
480
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
481
|
+
}
|
|
482
|
+
else if (customErrors && _get(customErrors.ErrorSchema, [...path, ERRORS_KEY])) {
|
|
483
|
+
// If we have custom errors and the path has an error, then we need to clear it
|
|
484
|
+
customErrors.clearErrors(path);
|
|
485
|
+
}
|
|
486
|
+
// If there are pending changes in the queue, skip live validation since it will happen with the last change
|
|
487
|
+
if (mustValidate && this.pendingChanges.length === 1) {
|
|
488
|
+
const liveValidation = this.liveValidate(schema, schemaUtils, originalErrorSchema, newFormData, extraErrors, customErrors, retrievedSchema);
|
|
489
|
+
state = { formData: newFormData, ...liveValidation, customErrors };
|
|
400
490
|
}
|
|
401
491
|
else if (!noValidate && newErrorSchema) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
: newErrorSchema;
|
|
492
|
+
// Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors.
|
|
493
|
+
const mergedErrors = this.mergeErrors({ errorSchema: originalErrorSchema, errors }, extraErrors, customErrors);
|
|
405
494
|
state = {
|
|
406
495
|
formData: newFormData,
|
|
407
|
-
|
|
408
|
-
|
|
496
|
+
...mergedErrors,
|
|
497
|
+
customErrors,
|
|
409
498
|
};
|
|
410
499
|
}
|
|
411
|
-
this.setState(state, () =>
|
|
412
|
-
|
|
500
|
+
this.setState(state, () => {
|
|
501
|
+
if (onChange) {
|
|
502
|
+
onChange(toIChangeEvent({ ...this.state, ...state }), id);
|
|
503
|
+
}
|
|
504
|
+
// Now remove the change we just completed and call this again
|
|
505
|
+
this.pendingChanges.shift();
|
|
506
|
+
this.processPendingChange();
|
|
507
|
+
});
|
|
508
|
+
}
|
|
413
509
|
/**
|
|
414
510
|
* If the retrievedSchema has changed the new retrievedSchema is returned.
|
|
415
511
|
* Otherwise, the old retrievedSchema is returned to persist reference.
|
|
@@ -430,8 +526,9 @@ export default class Form extends Component {
|
|
|
430
526
|
*
|
|
431
527
|
*/
|
|
432
528
|
reset = () => {
|
|
433
|
-
|
|
434
|
-
const
|
|
529
|
+
// Cast the IS_RESET symbol to T to avoid type issues, we use this symbol to detect reset mode
|
|
530
|
+
const { formData: propsFormData, initialFormData = IS_RESET, onChange } = this.props;
|
|
531
|
+
const newState = this.getStateFromProps(this.props, propsFormData ?? initialFormData, undefined, undefined, undefined, true);
|
|
435
532
|
const newFormData = newState.formData;
|
|
436
533
|
const state = {
|
|
437
534
|
formData: newFormData,
|
|
@@ -439,20 +536,51 @@ export default class Form extends Component {
|
|
|
439
536
|
errors: [],
|
|
440
537
|
schemaValidationErrors: [],
|
|
441
538
|
schemaValidationErrorSchema: {},
|
|
539
|
+
initialDefaultsGenerated: false,
|
|
540
|
+
customErrors: undefined,
|
|
442
541
|
};
|
|
443
|
-
this.setState(state, () => onChange && onChange({ ...this.state, ...state }));
|
|
542
|
+
this.setState(state, () => onChange && onChange(toIChangeEvent({ ...this.state, ...state })));
|
|
444
543
|
};
|
|
445
544
|
/** Callback function to handle when a field on the form is blurred. Calls the `onBlur` callback for the `Form` if it
|
|
446
|
-
* was provided.
|
|
545
|
+
* was provided. Also runs any live validation and/or live omit operations if the flags indicate they should happen
|
|
546
|
+
* during `onBlur`.
|
|
447
547
|
*
|
|
448
548
|
* @param id - The unique `id` of the field that was blurred
|
|
449
549
|
* @param data - The data associated with the field that was blurred
|
|
450
550
|
*/
|
|
451
551
|
onBlur = (id, data) => {
|
|
452
|
-
const { onBlur } = this.props;
|
|
552
|
+
const { onBlur, omitExtraData, liveOmit, liveValidate } = this.props;
|
|
453
553
|
if (onBlur) {
|
|
454
554
|
onBlur(id, data);
|
|
455
555
|
}
|
|
556
|
+
if ((omitExtraData === true && liveOmit === 'onBlur') || liveValidate === 'onBlur') {
|
|
557
|
+
const { onChange, extraErrors } = this.props;
|
|
558
|
+
const { formData } = this.state;
|
|
559
|
+
let newFormData = formData;
|
|
560
|
+
let state = { formData: newFormData };
|
|
561
|
+
if (omitExtraData === true && liveOmit === 'onBlur') {
|
|
562
|
+
newFormData = this.omitExtraData(formData);
|
|
563
|
+
state = { formData: newFormData };
|
|
564
|
+
}
|
|
565
|
+
if (liveValidate === 'onBlur') {
|
|
566
|
+
const { schema, schemaUtils, errorSchema, customErrors, retrievedSchema } = this.state;
|
|
567
|
+
const liveValidation = this.liveValidate(schema, schemaUtils, errorSchema, newFormData, extraErrors, customErrors, retrievedSchema);
|
|
568
|
+
state = { formData: newFormData, ...liveValidation, customErrors };
|
|
569
|
+
}
|
|
570
|
+
const hasChanges = Object.keys(state)
|
|
571
|
+
// Filter out `schemaValidationErrors` and `schemaValidationErrorSchema` since they aren't IChangeEvent props
|
|
572
|
+
.filter((key) => !key.startsWith('schemaValidation'))
|
|
573
|
+
.some((key) => {
|
|
574
|
+
const oldData = _get(this.state, key);
|
|
575
|
+
const newData = _get(state, key);
|
|
576
|
+
return !deepEquals(oldData, newData);
|
|
577
|
+
});
|
|
578
|
+
this.setState(state, () => {
|
|
579
|
+
if (onChange && hasChanges) {
|
|
580
|
+
onChange(toIChangeEvent({ ...this.state, ...state }), id);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
}
|
|
456
584
|
};
|
|
457
585
|
/** Callback function to handle when a field on the form is focused. Calls the `onFocus` callback for the `Form` if it
|
|
458
586
|
* was provided.
|
|
@@ -498,32 +626,50 @@ export default class Form extends Component {
|
|
|
498
626
|
schemaValidationErrorSchema: {},
|
|
499
627
|
}, () => {
|
|
500
628
|
if (onSubmit) {
|
|
501
|
-
onSubmit({ ...this.state, formData: newFormData,
|
|
629
|
+
onSubmit(toIChangeEvent({ ...this.state, formData: newFormData }, 'submitted'), event);
|
|
502
630
|
}
|
|
503
631
|
});
|
|
504
632
|
}
|
|
505
633
|
};
|
|
506
|
-
/**
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
634
|
+
/** Extracts the `GlobalFormOptions` from the given Form `props`
|
|
635
|
+
*
|
|
636
|
+
* @param props - The form props to extract the global form options from
|
|
637
|
+
* @returns - The `GlobalFormOptions` from the props
|
|
638
|
+
* @private
|
|
639
|
+
*/
|
|
640
|
+
getGlobalFormOptions(props) {
|
|
641
|
+
const { uiSchema = {}, experimental_componentUpdateStrategy, idSeparator = DEFAULT_ID_SEPARATOR, idPrefix = DEFAULT_ID_PREFIX, nameGenerator, useFallbackUiForUnsupportedType = false, } = props;
|
|
642
|
+
const rootFieldId = uiSchema['ui:rootFieldId'];
|
|
643
|
+
// Omit any options that are undefined or null
|
|
644
|
+
return {
|
|
645
|
+
idPrefix: rootFieldId || idPrefix,
|
|
646
|
+
idSeparator,
|
|
647
|
+
useFallbackUiForUnsupportedType,
|
|
648
|
+
...(experimental_componentUpdateStrategy !== undefined && { experimental_componentUpdateStrategy }),
|
|
649
|
+
...(nameGenerator !== undefined && { nameGenerator }),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
/** Computed the registry for the form using the given `props`, `schema` and `schemaUtils` */
|
|
653
|
+
getRegistry(props, schema, schemaUtils) {
|
|
654
|
+
const { translateString: customTranslateString, uiSchema = {} } = props;
|
|
510
655
|
const { fields, templates, widgets, formContext, translateString } = getDefaultRegistry();
|
|
511
656
|
return {
|
|
512
|
-
fields: { ...fields, ...
|
|
657
|
+
fields: { ...fields, ...props.fields },
|
|
513
658
|
templates: {
|
|
514
659
|
...templates,
|
|
515
|
-
...
|
|
660
|
+
...props.templates,
|
|
516
661
|
ButtonTemplates: {
|
|
517
662
|
...templates.ButtonTemplates,
|
|
518
|
-
...
|
|
663
|
+
...props.templates?.ButtonTemplates,
|
|
519
664
|
},
|
|
520
665
|
},
|
|
521
|
-
widgets: { ...widgets, ...
|
|
522
|
-
rootSchema:
|
|
523
|
-
formContext:
|
|
666
|
+
widgets: { ...widgets, ...props.widgets },
|
|
667
|
+
rootSchema: schema,
|
|
668
|
+
formContext: props.formContext || formContext,
|
|
524
669
|
schemaUtils,
|
|
525
670
|
translateString: customTranslateString || translateString,
|
|
526
671
|
globalUiOptions: uiSchema[UI_GLOBAL_OPTIONS_KEY],
|
|
672
|
+
globalFormOptions: this.getGlobalFormOptions(props),
|
|
527
673
|
};
|
|
528
674
|
}
|
|
529
675
|
/** Provides a function that can be used to programmatically submit the `Form` */
|
|
@@ -640,9 +786,8 @@ export default class Form extends Component {
|
|
|
640
786
|
* needed along with the submit button or any children of the form.
|
|
641
787
|
*/
|
|
642
788
|
render() {
|
|
643
|
-
const { children, id,
|
|
644
|
-
const { schema, uiSchema, formData, errorSchema,
|
|
645
|
-
const registry = this.getRegistry();
|
|
789
|
+
const { children, id, className = '', tagName, name, method, target, action, autoComplete, enctype, acceptCharset, noHtml5Validate = false, disabled, readonly, showErrorList = 'top', _internalFormWrapper, } = this.props;
|
|
790
|
+
const { schema, uiSchema, formData, errorSchema, fieldPathId, registry } = this.state;
|
|
646
791
|
const { SchemaField: _SchemaField } = registry.fields;
|
|
647
792
|
const { SubmitButton } = registry.templates.ButtonTemplates;
|
|
648
793
|
// The `semantic-ui` and `material-ui` themes have `_internalFormWrapper`s that take an `as` prop that is the
|
|
@@ -655,6 +800,6 @@ export default class Form extends Component {
|
|
|
655
800
|
submitOptions = { ...submitOptions, props: { ...submitOptions.props, disabled: true } };
|
|
656
801
|
}
|
|
657
802
|
const submitUiSchema = { [UI_OPTIONS_KEY]: { [SUBMIT_BTN_OPTIONS_KEY]: submitOptions } };
|
|
658
|
-
return (_jsxs(FormTag, { className: className ? className : 'rjsf', id: id, name: name, method: method, target: target, action: action, autoComplete: autoComplete, encType: enctype, acceptCharset: acceptCharset, noValidate: noHtml5Validate, onSubmit: this.onSubmit, as: as, ref: this.formElement, children: [showErrorList === 'top' && this.renderErrors(registry), _jsx(_SchemaField, { name: '', schema: schema, uiSchema: uiSchema, errorSchema: errorSchema,
|
|
803
|
+
return (_jsxs(FormTag, { className: className ? className : 'rjsf', id: id, name: name, method: method, target: target, action: action, autoComplete: autoComplete, encType: enctype, acceptCharset: acceptCharset, noValidate: noHtml5Validate, onSubmit: this.onSubmit, as: as, ref: this.formElement, children: [showErrorList === 'top' && this.renderErrors(registry), _jsx(_SchemaField, { name: '', schema: schema, uiSchema: uiSchema, errorSchema: errorSchema, fieldPathId: fieldPathId, formData: formData, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, registry: registry, disabled: disabled, readonly: readonly }), children ? children : _jsx(SubmitButton, { uiSchema: submitUiSchema, registry: registry }), showErrorList === 'bottom' && this.renderErrors(registry)] }));
|
|
659
804
|
}
|
|
660
805
|
}
|