@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/src/components/Form.tsx
CHANGED
|
@@ -4,13 +4,15 @@ import {
|
|
|
4
4
|
CustomValidator,
|
|
5
5
|
deepEquals,
|
|
6
6
|
ErrorSchema,
|
|
7
|
+
ErrorSchemaBuilder,
|
|
7
8
|
ErrorTransformer,
|
|
9
|
+
FieldPathId,
|
|
10
|
+
FieldPathList,
|
|
8
11
|
FormContextType,
|
|
9
12
|
GenericObjectType,
|
|
10
13
|
getChangedFields,
|
|
11
14
|
getTemplate,
|
|
12
15
|
getUiOptions,
|
|
13
|
-
IdSchema,
|
|
14
16
|
isObject,
|
|
15
17
|
mergeObjects,
|
|
16
18
|
NAME_KEY,
|
|
@@ -27,6 +29,7 @@ import {
|
|
|
27
29
|
SUBMIT_BTN_OPTIONS_KEY,
|
|
28
30
|
TemplatesType,
|
|
29
31
|
toErrorList,
|
|
32
|
+
toFieldPathId,
|
|
30
33
|
UiSchema,
|
|
31
34
|
UI_GLOBAL_OPTIONS_KEY,
|
|
32
35
|
UI_OPTIONS_KEY,
|
|
@@ -35,18 +38,25 @@ import {
|
|
|
35
38
|
ValidatorType,
|
|
36
39
|
Experimental_DefaultFormStateBehavior,
|
|
37
40
|
Experimental_CustomMergeAllOf,
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
DEFAULT_ID_SEPARATOR,
|
|
42
|
+
DEFAULT_ID_PREFIX,
|
|
43
|
+
GlobalFormOptions,
|
|
44
|
+
ERRORS_KEY,
|
|
45
|
+
ID_KEY,
|
|
46
|
+
NameGeneratorFunction,
|
|
40
47
|
} from '@rjsf/utils';
|
|
41
|
-
import
|
|
48
|
+
import _cloneDeep from 'lodash/cloneDeep';
|
|
42
49
|
import _get from 'lodash/get';
|
|
43
50
|
import _isEmpty from 'lodash/isEmpty';
|
|
44
|
-
import _isNil from 'lodash/isNil';
|
|
45
51
|
import _pick from 'lodash/pick';
|
|
52
|
+
import _set from 'lodash/set';
|
|
46
53
|
import _toPath from 'lodash/toPath';
|
|
47
54
|
|
|
48
55
|
import getDefaultRegistry from '../getDefaultRegistry';
|
|
49
56
|
|
|
57
|
+
/** Internal only symbol used by the `reset()` function to indicate that a reset operation is happening */
|
|
58
|
+
const IS_RESET = Symbol('reset');
|
|
59
|
+
|
|
50
60
|
/** The properties that are passed to the `Form` */
|
|
51
61
|
export interface FormProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> {
|
|
52
62
|
/** The JSON schema object for the form */
|
|
@@ -57,8 +67,14 @@ export interface FormProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
|
|
|
57
67
|
children?: ReactNode;
|
|
58
68
|
/** The uiSchema for the form */
|
|
59
69
|
uiSchema?: UiSchema<T, S, F>;
|
|
60
|
-
/** The data for the form, used to
|
|
70
|
+
/** The data for the form, used to load a "controlled" form with its current data. If you want an "uncontrolled" form
|
|
71
|
+
* with initial data, then use `initialFormData` instead.
|
|
72
|
+
*/
|
|
61
73
|
formData?: T;
|
|
74
|
+
/** The initial data for the form, used to fill an "uncontrolled" form with existing data on the initial render and
|
|
75
|
+
* when `reset()` is called programmatically.
|
|
76
|
+
*/
|
|
77
|
+
initialFormData?: T;
|
|
62
78
|
// Form presentation and behavior modifiers
|
|
63
79
|
/** You can provide a `formContext` object to the form, which is passed down to all fields and widgets. Useful for
|
|
64
80
|
* implementing context aware fields and widgets.
|
|
@@ -161,14 +177,28 @@ export interface FormProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
|
|
|
161
177
|
* @deprecated - In a future release, this switch may be replaced by making `validator` prop optional
|
|
162
178
|
*/
|
|
163
179
|
noValidate?: boolean;
|
|
164
|
-
/**
|
|
165
|
-
* rather than just on submit
|
|
180
|
+
/** Flag that describes when live validation will be performed. Live validation means that the form will perform
|
|
181
|
+
* validation and show any validation errors whenever the form data is updated, rather than just on submit.
|
|
182
|
+
*
|
|
183
|
+
* If no value (or `false`) is provided, then live validation will not happen. If `true` or `onChange` is provided for
|
|
184
|
+
* the flag, then live validation will be performed after processing of all pending changes has completed. If `onBlur`
|
|
185
|
+
* is provided, then live validation will be performed when a field that was updated is blurred (as a performance
|
|
186
|
+
* optimization).
|
|
187
|
+
*
|
|
188
|
+
* @deprecated - In a future major release, the `boolean` options for this flag will be removed
|
|
166
189
|
*/
|
|
167
|
-
liveValidate?: boolean;
|
|
168
|
-
/**
|
|
169
|
-
*
|
|
190
|
+
liveValidate?: boolean | 'onChange' | 'onBlur';
|
|
191
|
+
/** Flag that describes when live omit will be performed. Live omit happens only when `omitExtraData` is also set to
|
|
192
|
+
* to `true` and the form's data is updated by the user.
|
|
193
|
+
*
|
|
194
|
+
* If no value (or `false`) is provided, then live omit will not happen. If `true` or `onChange` is provided for
|
|
195
|
+
* the flag, then live omit will be performed after processing of all pending changes has completed. If `onBlur`
|
|
196
|
+
* is provided, then live omit will be performed when a field that was updated is blurred (as a performance
|
|
197
|
+
* optimization).
|
|
198
|
+
*
|
|
199
|
+
* @deprecated - In a future major release, the `boolean` options for this flag will be removed
|
|
170
200
|
*/
|
|
171
|
-
liveOmit?: boolean;
|
|
201
|
+
liveOmit?: boolean | 'onChange' | 'onBlur';
|
|
172
202
|
/** If set to true, then extra form data values that are not in any form field will be removed whenever `onSubmit` is
|
|
173
203
|
* called. Set to `false` by default.
|
|
174
204
|
*/
|
|
@@ -190,11 +220,29 @@ export interface FormProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
|
|
|
190
220
|
* to put the second parameter before the first in its translation.
|
|
191
221
|
*/
|
|
192
222
|
translateString?: Registry['translateString'];
|
|
223
|
+
/** Optional function to generate custom HTML `name` attributes for form fields.
|
|
224
|
+
*/
|
|
225
|
+
nameGenerator?: NameGeneratorFunction;
|
|
226
|
+
/** Optional flag that, when set to true, will cause the `FallbackField` to render a type selector for unsupported
|
|
227
|
+
* fields instead of the default UnsupportedField error UI.
|
|
228
|
+
*/
|
|
229
|
+
useFallbackUiForUnsupportedType?: boolean;
|
|
193
230
|
/** Optional configuration object with flags, if provided, allows users to override default form state behavior
|
|
194
231
|
* Currently only affecting minItems on array fields and handling of setting defaults based on the value of
|
|
195
232
|
* `emptyObjectFields`
|
|
196
233
|
*/
|
|
197
234
|
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior;
|
|
235
|
+
/**
|
|
236
|
+
* Controls the component update strategy used by the Form's `shouldComponentUpdate` lifecycle method.
|
|
237
|
+
*
|
|
238
|
+
* - `'customDeep'`: Uses RJSF's custom deep equality checks via the `deepEquals` utility function,
|
|
239
|
+
* which treats all functions as equivalent and provides optimized performance for form data comparisons.
|
|
240
|
+
* - `'shallow'`: Uses shallow comparison of props and state (only compares direct properties). This matches React's PureComponent behavior.
|
|
241
|
+
* - `'always'`: Always rerenders when called. This matches React's Component behavior.
|
|
242
|
+
*
|
|
243
|
+
* @default 'customDeep'
|
|
244
|
+
*/
|
|
245
|
+
experimental_componentUpdateStrategy?: 'customDeep' | 'shallow' | 'always';
|
|
198
246
|
/** Optional function that allows for custom merging of `allOf` schemas
|
|
199
247
|
*/
|
|
200
248
|
experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;
|
|
@@ -226,10 +274,10 @@ export interface FormState<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
|
|
|
226
274
|
schema: S;
|
|
227
275
|
/** The uiSchema for the form */
|
|
228
276
|
uiSchema: UiSchema<T, S, F>;
|
|
229
|
-
/** The `
|
|
277
|
+
/** The `FieldPathId` for the form, computed from the `schema`, the `rootFieldId`, the `idPrefix` and
|
|
230
278
|
* `idSeparator` props.
|
|
231
279
|
*/
|
|
232
|
-
|
|
280
|
+
fieldPathId: FieldPathId;
|
|
233
281
|
/** The schemaUtils implementation used by the `Form`, created from the `validator` and the `schema` */
|
|
234
282
|
schemaUtils: SchemaUtilsType<T, S, F>;
|
|
235
283
|
/** The current data for the form, computed from the `formData` prop and the changes made by the user */
|
|
@@ -240,26 +288,64 @@ export interface FormState<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
|
|
|
240
288
|
errors: RJSFValidationError[];
|
|
241
289
|
/** The current errors, in `ErrorSchema` format, for the form, includes `extraErrors` */
|
|
242
290
|
errorSchema: ErrorSchema<T>;
|
|
291
|
+
// Private
|
|
243
292
|
/** The current list of errors for the form directly from schema validation, does NOT include `extraErrors` */
|
|
244
293
|
schemaValidationErrors: RJSFValidationError[];
|
|
245
294
|
/** The current errors, in `ErrorSchema` format, for the form directly from schema validation, does NOT include
|
|
246
295
|
* `extraErrors`
|
|
247
296
|
*/
|
|
248
297
|
schemaValidationErrorSchema: ErrorSchema<T>;
|
|
249
|
-
|
|
298
|
+
/** A container used to handle custom errors provided via `onChange` */
|
|
299
|
+
customErrors?: ErrorSchemaBuilder<T>;
|
|
250
300
|
/** @description result of schemaUtils.retrieveSchema(schema, formData). This a memoized value to avoid re calculate at internal functions (getStateFromProps, onChange) */
|
|
251
301
|
retrievedSchema: S;
|
|
302
|
+
/** Flag indicating whether the initial form defaults have been generated */
|
|
303
|
+
initialDefaultsGenerated: boolean;
|
|
304
|
+
/** The registry (re)computed only when props changed */
|
|
305
|
+
registry: Registry<T, S, F>;
|
|
252
306
|
}
|
|
253
307
|
|
|
254
308
|
/** The event data passed when changes have been made to the form, includes everything from the `FormState` except
|
|
255
309
|
* the schema validation errors. An additional `status` is added when returned from `onSubmit`
|
|
256
310
|
*/
|
|
257
311
|
export interface IChangeEvent<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>
|
|
258
|
-
extends
|
|
312
|
+
extends Pick<
|
|
313
|
+
FormState<T, S, F>,
|
|
314
|
+
'schema' | 'uiSchema' | 'fieldPathId' | 'schemaUtils' | 'formData' | 'edit' | 'errors' | 'errorSchema'
|
|
315
|
+
> {
|
|
259
316
|
/** The status of the form when submitted */
|
|
260
317
|
status?: 'submitted';
|
|
261
318
|
}
|
|
262
319
|
|
|
320
|
+
/** Converts the full `FormState` into the `IChangeEvent` version by picking out the public values
|
|
321
|
+
*
|
|
322
|
+
* @param state - The state of the form
|
|
323
|
+
* @param status - The status provided by the onSubmit
|
|
324
|
+
* @returns - The `IChangeEvent` for the state
|
|
325
|
+
*/
|
|
326
|
+
function toIChangeEvent<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
|
|
327
|
+
state: FormState<T, S, F>,
|
|
328
|
+
status?: IChangeEvent['status'],
|
|
329
|
+
): IChangeEvent<T, S, F> {
|
|
330
|
+
return {
|
|
331
|
+
..._pick(state, ['schema', 'uiSchema', 'fieldPathId', 'schemaUtils', 'formData', 'edit', 'errors', 'errorSchema']),
|
|
332
|
+
...(status !== undefined && { status }),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** The definition of a pending change that will be processed in the `onChange` handler
|
|
337
|
+
*/
|
|
338
|
+
interface PendingChange<T> {
|
|
339
|
+
/** The path into the formData/errorSchema at which the `newValue`/`newErrorSchema` will be set */
|
|
340
|
+
path: FieldPathList;
|
|
341
|
+
/** The new value to set into the formData */
|
|
342
|
+
newValue?: T;
|
|
343
|
+
/** The new errors to be set into the errorSchema, if any */
|
|
344
|
+
newErrorSchema?: ErrorSchema<T>;
|
|
345
|
+
/** The optional id of the field for which the change is being made */
|
|
346
|
+
id?: string;
|
|
347
|
+
}
|
|
348
|
+
|
|
263
349
|
/** The `Form` component renders the outer form and all the fields defined in the `schema` */
|
|
264
350
|
export default class Form<
|
|
265
351
|
T = any,
|
|
@@ -271,6 +357,10 @@ export default class Form<
|
|
|
271
357
|
*/
|
|
272
358
|
formElement: RefObject<any>;
|
|
273
359
|
|
|
360
|
+
/** The list of pending changes
|
|
361
|
+
*/
|
|
362
|
+
pendingChanges: PendingChange<T>[] = [];
|
|
363
|
+
|
|
274
364
|
/** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the
|
|
275
365
|
* `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the
|
|
276
366
|
* state construction.
|
|
@@ -284,9 +374,11 @@ export default class Form<
|
|
|
284
374
|
throw new Error('A validator is required for Form functionality to work');
|
|
285
375
|
}
|
|
286
376
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
377
|
+
const { formData: propsFormData, initialFormData, onChange } = props;
|
|
378
|
+
const formData = propsFormData ?? initialFormData;
|
|
379
|
+
this.state = this.getStateFromProps(props, formData, undefined, undefined, undefined, true);
|
|
380
|
+
if (onChange && !deepEquals(this.state.formData, formData)) {
|
|
381
|
+
onChange(toIChangeEvent(this.state));
|
|
290
382
|
}
|
|
291
383
|
this.formElement = createRef();
|
|
292
384
|
}
|
|
@@ -314,12 +406,18 @@ export default class Form<
|
|
|
314
406
|
prevState: FormState<T, S, F>,
|
|
315
407
|
): { nextState: FormState<T, S, F>; shouldUpdate: true } | { shouldUpdate: false } {
|
|
316
408
|
if (!deepEquals(this.props, prevProps)) {
|
|
409
|
+
// Compare the previous props formData against the current props formData
|
|
317
410
|
const formDataChangedFields = getChangedFields(this.props.formData, prevProps.formData);
|
|
411
|
+
// Compare the current props formData against the current state's formData to determine if the new props were the
|
|
412
|
+
// result of the onChange from the existing state formData
|
|
413
|
+
const stateDataChangedFields = getChangedFields(this.props.formData, this.state.formData);
|
|
318
414
|
const isSchemaChanged = !deepEquals(prevProps.schema, this.props.schema);
|
|
319
415
|
// When formData is not an object, getChangedFields returns an empty array.
|
|
320
416
|
// In this case, deepEquals is most needed to check again.
|
|
321
417
|
const isFormDataChanged =
|
|
322
418
|
formDataChangedFields.length > 0 || !deepEquals(prevProps.formData, this.props.formData);
|
|
419
|
+
const isStateDataChanged =
|
|
420
|
+
stateDataChangedFields.length > 0 || !deepEquals(this.state.formData, this.props.formData);
|
|
323
421
|
const nextState = this.getStateFromProps(
|
|
324
422
|
this.props,
|
|
325
423
|
this.props.formData,
|
|
@@ -329,6 +427,8 @@ export default class Form<
|
|
|
329
427
|
isSchemaChanged || isFormDataChanged ? undefined : this.state.retrievedSchema,
|
|
330
428
|
isSchemaChanged,
|
|
331
429
|
formDataChangedFields,
|
|
430
|
+
// Skip live validation for this request if no form data has changed from the last state
|
|
431
|
+
!isStateDataChanged,
|
|
332
432
|
);
|
|
333
433
|
const shouldUpdate = !deepEquals(nextState, prevState);
|
|
334
434
|
return { nextState, shouldUpdate };
|
|
@@ -355,13 +455,12 @@ export default class Form<
|
|
|
355
455
|
) {
|
|
356
456
|
if (snapshot.shouldUpdate) {
|
|
357
457
|
const { nextState } = snapshot;
|
|
358
|
-
|
|
359
458
|
if (
|
|
360
459
|
!deepEquals(nextState.formData, this.props.formData) &&
|
|
361
460
|
!deepEquals(nextState.formData, prevState.formData) &&
|
|
362
461
|
this.props.onChange
|
|
363
462
|
) {
|
|
364
|
-
this.props.onChange(nextState);
|
|
463
|
+
this.props.onChange(toIChangeEvent(nextState));
|
|
365
464
|
}
|
|
366
465
|
this.setState(nextState);
|
|
367
466
|
}
|
|
@@ -376,6 +475,7 @@ export default class Form<
|
|
|
376
475
|
* @param retrievedSchema - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`.
|
|
377
476
|
* @param isSchemaChanged - A flag indicating whether the schema has changed.
|
|
378
477
|
* @param formDataChangedFields - The changed fields of `formData`
|
|
478
|
+
* @param skipLiveValidate - Optional flag, if true, means that we are not running live validation
|
|
379
479
|
* @returns - The new state for the `Form`
|
|
380
480
|
*/
|
|
381
481
|
getStateFromProps(
|
|
@@ -384,14 +484,16 @@ export default class Form<
|
|
|
384
484
|
retrievedSchema?: S,
|
|
385
485
|
isSchemaChanged = false,
|
|
386
486
|
formDataChangedFields: string[] = [],
|
|
487
|
+
skipLiveValidate = false,
|
|
387
488
|
): FormState<T, S, F> {
|
|
388
489
|
const state: FormState<T, S, F> = this.state || {};
|
|
389
490
|
const schema = 'schema' in props ? props.schema : this.props.schema;
|
|
491
|
+
const validator = 'validator' in props ? props.validator : this.props.validator;
|
|
390
492
|
const uiSchema: UiSchema<T, S, F> = ('uiSchema' in props ? props.uiSchema! : this.props.uiSchema!) || {};
|
|
493
|
+
const isUncontrolled = props.formData === undefined && this.props.formData === undefined;
|
|
391
494
|
const edit = typeof inputFormData !== 'undefined';
|
|
392
495
|
const liveValidate = 'liveValidate' in props ? props.liveValidate : this.props.liveValidate;
|
|
393
496
|
const mustValidate = edit && !props.noValidate && liveValidate;
|
|
394
|
-
const rootSchema = schema;
|
|
395
497
|
const experimental_defaultFormStateBehavior =
|
|
396
498
|
'experimental_defaultFormStateBehavior' in props
|
|
397
499
|
? props.experimental_defaultFormStateBehavior
|
|
@@ -404,22 +506,37 @@ export default class Form<
|
|
|
404
506
|
if (
|
|
405
507
|
!schemaUtils ||
|
|
406
508
|
schemaUtils.doesSchemaUtilsDiffer(
|
|
407
|
-
|
|
408
|
-
|
|
509
|
+
validator,
|
|
510
|
+
schema,
|
|
409
511
|
experimental_defaultFormStateBehavior,
|
|
410
512
|
experimental_customMergeAllOf,
|
|
411
513
|
)
|
|
412
514
|
) {
|
|
413
515
|
schemaUtils = createSchemaUtils<T, S, F>(
|
|
414
|
-
|
|
415
|
-
|
|
516
|
+
validator,
|
|
517
|
+
schema,
|
|
416
518
|
experimental_defaultFormStateBehavior,
|
|
417
519
|
experimental_customMergeAllOf,
|
|
418
520
|
);
|
|
419
521
|
}
|
|
420
|
-
|
|
522
|
+
|
|
523
|
+
const rootSchema = schemaUtils.getRootSchema();
|
|
524
|
+
|
|
525
|
+
// Compute the formData for getDefaultFormState() function based on the inputFormData, isUncontrolled and state
|
|
526
|
+
let defaultsFormData = inputFormData;
|
|
527
|
+
if (inputFormData === IS_RESET) {
|
|
528
|
+
defaultsFormData = undefined;
|
|
529
|
+
} else if (inputFormData === undefined && isUncontrolled) {
|
|
530
|
+
defaultsFormData = state.formData;
|
|
531
|
+
}
|
|
532
|
+
const formData: T = schemaUtils.getDefaultFormState(
|
|
533
|
+
rootSchema,
|
|
534
|
+
defaultsFormData,
|
|
535
|
+
false,
|
|
536
|
+
state.initialDefaultsGenerated,
|
|
537
|
+
) as T;
|
|
421
538
|
const _retrievedSchema = this.updateRetrievedSchema(
|
|
422
|
-
retrievedSchema ?? schemaUtils.retrieveSchema(
|
|
539
|
+
retrievedSchema ?? schemaUtils.retrieveSchema(rootSchema, formData),
|
|
423
540
|
);
|
|
424
541
|
|
|
425
542
|
const getCurrentErrors = (): ValidationData<T> => {
|
|
@@ -442,27 +559,30 @@ export default class Form<
|
|
|
442
559
|
let errorSchema: ErrorSchema<T> | undefined;
|
|
443
560
|
let schemaValidationErrors: RJSFValidationError[] = state.schemaValidationErrors;
|
|
444
561
|
let schemaValidationErrorSchema: ErrorSchema<T> = state.schemaValidationErrorSchema;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
562
|
+
// If we are skipping live validate, it means that the state has already been updated with live validation errors
|
|
563
|
+
if (mustValidate && !skipLiveValidate) {
|
|
564
|
+
const liveValidation = this.liveValidate(
|
|
565
|
+
rootSchema,
|
|
566
|
+
schemaUtils,
|
|
567
|
+
state.errorSchema,
|
|
568
|
+
formData,
|
|
569
|
+
undefined,
|
|
570
|
+
state.customErrors,
|
|
571
|
+
retrievedSchema,
|
|
572
|
+
// If retrievedSchema is undefined which means the schema or formData has changed, we do not merge state.
|
|
573
|
+
// Else in the case where it hasn't changed,
|
|
574
|
+
retrievedSchema !== undefined,
|
|
575
|
+
);
|
|
576
|
+
errors = liveValidation.errors;
|
|
577
|
+
errorSchema = liveValidation.errorSchema;
|
|
578
|
+
schemaValidationErrors = liveValidation.schemaValidationErrors;
|
|
579
|
+
schemaValidationErrorSchema = liveValidation.schemaValidationErrorSchema;
|
|
461
580
|
} else {
|
|
462
581
|
const currentErrors = getCurrentErrors();
|
|
463
582
|
errors = currentErrors.errors;
|
|
464
583
|
errorSchema = currentErrors.errorSchema;
|
|
465
|
-
if
|
|
584
|
+
// We only update the error schema for changed fields if mustValidate is false
|
|
585
|
+
if (formDataChangedFields.length > 0 && !mustValidate) {
|
|
466
586
|
const newErrorSchema = formDataChangedFields.reduce(
|
|
467
587
|
(acc, key) => {
|
|
468
588
|
acc[key] = undefined;
|
|
@@ -476,25 +596,24 @@ export default class Form<
|
|
|
476
596
|
'preventDuplicates',
|
|
477
597
|
) as ErrorSchema<T>;
|
|
478
598
|
}
|
|
599
|
+
const mergedErrors = this.mergeErrors({ errorSchema, errors }, props.extraErrors, state.customErrors);
|
|
600
|
+
errors = mergedErrors.errors;
|
|
601
|
+
errorSchema = mergedErrors.errorSchema;
|
|
479
602
|
}
|
|
480
603
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
formData,
|
|
490
|
-
props.idPrefix,
|
|
491
|
-
props.idSeparator,
|
|
492
|
-
);
|
|
604
|
+
// Only store a new registry when the props cause a different one to be created
|
|
605
|
+
const newRegistry = this.getRegistry(props, rootSchema, schemaUtils);
|
|
606
|
+
const registry = deepEquals(state.registry, newRegistry) ? state.registry : newRegistry;
|
|
607
|
+
// Only compute a new `fieldPathId` when the `idPrefix` is different than the existing fieldPathId's ID_KEY
|
|
608
|
+
const fieldPathId =
|
|
609
|
+
state.fieldPathId && state.fieldPathId?.[ID_KEY] === registry.globalFormOptions.idPrefix
|
|
610
|
+
? state.fieldPathId
|
|
611
|
+
: toFieldPathId('', registry.globalFormOptions);
|
|
493
612
|
const nextState: FormState<T, S, F> = {
|
|
494
613
|
schemaUtils,
|
|
495
|
-
schema,
|
|
614
|
+
schema: rootSchema,
|
|
496
615
|
uiSchema,
|
|
497
|
-
|
|
616
|
+
fieldPathId,
|
|
498
617
|
formData,
|
|
499
618
|
edit,
|
|
500
619
|
errors,
|
|
@@ -502,6 +621,8 @@ export default class Form<
|
|
|
502
621
|
schemaValidationErrors,
|
|
503
622
|
schemaValidationErrorSchema,
|
|
504
623
|
retrievedSchema: _retrievedSchema,
|
|
624
|
+
initialDefaultsGenerated: true,
|
|
625
|
+
registry,
|
|
505
626
|
};
|
|
506
627
|
return nextState;
|
|
507
628
|
}
|
|
@@ -513,23 +634,8 @@ export default class Form<
|
|
|
513
634
|
* @returns - True if the component should be updated, false otherwise
|
|
514
635
|
*/
|
|
515
636
|
shouldComponentUpdate(nextProps: FormProps<T, S, F>, nextState: FormState<T, S, F>): boolean {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
/** Gets the previously raised customValidate errors.
|
|
520
|
-
*
|
|
521
|
-
* @returns the previous customValidate errors
|
|
522
|
-
*/
|
|
523
|
-
private getPreviousCustomValidateErrors(): ErrorSchema<T> {
|
|
524
|
-
const { customValidate, uiSchema } = this.props;
|
|
525
|
-
const prevFormData = this.state.formData as T;
|
|
526
|
-
let customValidateErrors = {};
|
|
527
|
-
if (typeof customValidate === 'function') {
|
|
528
|
-
const errorHandler = customValidate(prevFormData, createErrorHandler<T>(prevFormData), uiSchema);
|
|
529
|
-
const userErrorSchema = unwrapErrorHandler<T>(errorHandler);
|
|
530
|
-
customValidateErrors = userErrorSchema;
|
|
531
|
-
}
|
|
532
|
-
return customValidateErrors;
|
|
637
|
+
const { experimental_componentUpdateStrategy = 'customDeep' } = this.props;
|
|
638
|
+
return shouldRender(this, nextProps, nextState, experimental_componentUpdateStrategy);
|
|
533
639
|
}
|
|
534
640
|
|
|
535
641
|
/** Validates the `formData` against the `schema` using the `altSchemaUtils` (if provided otherwise it uses the
|
|
@@ -537,11 +643,12 @@ export default class Form<
|
|
|
537
643
|
*
|
|
538
644
|
* @param formData - The new form data to validate
|
|
539
645
|
* @param schema - The schema used to validate against
|
|
540
|
-
* @param altSchemaUtils - The alternate schemaUtils to use for validation
|
|
646
|
+
* @param [altSchemaUtils] - The alternate schemaUtils to use for validation
|
|
647
|
+
* @param [retrievedSchema] - An optionally retrieved schema for per
|
|
541
648
|
*/
|
|
542
649
|
validate(
|
|
543
650
|
formData: T | undefined,
|
|
544
|
-
schema = this.
|
|
651
|
+
schema = this.state.schema,
|
|
545
652
|
altSchemaUtils?: SchemaUtilsType<T, S, F>,
|
|
546
653
|
retrievedSchema?: S,
|
|
547
654
|
): ValidationData<T> {
|
|
@@ -556,7 +663,6 @@ export default class Form<
|
|
|
556
663
|
/** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */
|
|
557
664
|
renderErrors(registry: Registry<T, S, F>) {
|
|
558
665
|
const { errors, errorSchema, schema, uiSchema } = this.state;
|
|
559
|
-
const { formContext } = this.props;
|
|
560
666
|
const options = getUiOptions<T, S, F>(uiSchema);
|
|
561
667
|
const ErrorListTemplate = getTemplate<'ErrorListTemplate', T, S, F>('ErrorListTemplate', registry, options);
|
|
562
668
|
|
|
@@ -567,7 +673,6 @@ export default class Form<
|
|
|
567
673
|
errorSchema={errorSchema || {}}
|
|
568
674
|
schema={schema}
|
|
569
675
|
uiSchema={uiSchema}
|
|
570
|
-
formContext={formContext}
|
|
571
676
|
registry={registry}
|
|
572
677
|
/>
|
|
573
678
|
);
|
|
@@ -575,6 +680,75 @@ export default class Form<
|
|
|
575
680
|
return null;
|
|
576
681
|
}
|
|
577
682
|
|
|
683
|
+
/** Merges any `extraErrors` or `customErrors` into the given `schemaValidation` object, returning the result
|
|
684
|
+
*
|
|
685
|
+
* @param schemaValidation - The `ValidationData` object into which additional errors are merged
|
|
686
|
+
* @param [extraErrors] - The extra errors from the props
|
|
687
|
+
* @param [customErrors] - The customErrors from custom components
|
|
688
|
+
* @return - The `extraErrors` and `customErrors` merged into the `schemaValidation`
|
|
689
|
+
* @private
|
|
690
|
+
*/
|
|
691
|
+
private mergeErrors(
|
|
692
|
+
schemaValidation: ValidationData<T>,
|
|
693
|
+
extraErrors?: FormProps['extraErrors'],
|
|
694
|
+
customErrors?: ErrorSchemaBuilder,
|
|
695
|
+
): ValidationData<T> {
|
|
696
|
+
let errorSchema: ErrorSchema<T> = schemaValidation.errorSchema;
|
|
697
|
+
let errors: RJSFValidationError[] = schemaValidation.errors;
|
|
698
|
+
if (extraErrors) {
|
|
699
|
+
const merged = validationDataMerge(schemaValidation, extraErrors);
|
|
700
|
+
errorSchema = merged.errorSchema;
|
|
701
|
+
errors = merged.errors;
|
|
702
|
+
}
|
|
703
|
+
if (customErrors) {
|
|
704
|
+
const merged = validationDataMerge(schemaValidation, customErrors.ErrorSchema, true);
|
|
705
|
+
errorSchema = merged.errorSchema;
|
|
706
|
+
errors = merged.errors;
|
|
707
|
+
}
|
|
708
|
+
return { errors, errorSchema };
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/** Performs live validation and then updates and returns the errors and error schemas by potentially merging in
|
|
712
|
+
* `extraErrors` and `customErrors`.
|
|
713
|
+
*
|
|
714
|
+
* @param rootSchema - The `rootSchema` from the state
|
|
715
|
+
* @param schemaUtils - The `SchemaUtilsType` from the state
|
|
716
|
+
* @param originalErrorSchema - The original `ErrorSchema` from the state
|
|
717
|
+
* @param [formData] - The new form data to validate
|
|
718
|
+
* @param [extraErrors] - The extra errors from the props
|
|
719
|
+
* @param [customErrors] - The customErrors from custom components
|
|
720
|
+
* @param [retrievedSchema] - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`
|
|
721
|
+
* @param [mergeIntoOriginalErrorSchema=false] - Optional flag indicating whether we merge into original schema
|
|
722
|
+
* @returns - An object containing `errorSchema`, `errors`, `schemaValidationErrors` and `schemaValidationErrorSchema`
|
|
723
|
+
* @private
|
|
724
|
+
*/
|
|
725
|
+
private liveValidate(
|
|
726
|
+
rootSchema: S,
|
|
727
|
+
schemaUtils: SchemaUtilsType<T, S, F>,
|
|
728
|
+
originalErrorSchema: ErrorSchema<S>,
|
|
729
|
+
formData?: T,
|
|
730
|
+
extraErrors?: FormProps['extraErrors'],
|
|
731
|
+
customErrors?: ErrorSchemaBuilder<T>,
|
|
732
|
+
retrievedSchema?: S,
|
|
733
|
+
mergeIntoOriginalErrorSchema = false,
|
|
734
|
+
) {
|
|
735
|
+
const schemaValidation = this.validate(formData, rootSchema, schemaUtils, retrievedSchema);
|
|
736
|
+
const errors = schemaValidation.errors;
|
|
737
|
+
let errorSchema = schemaValidation.errorSchema;
|
|
738
|
+
// We merge 'originalErrorSchema' with 'schemaValidation.errorSchema.'; This done to display the raised field error.
|
|
739
|
+
if (mergeIntoOriginalErrorSchema) {
|
|
740
|
+
errorSchema = mergeObjects(
|
|
741
|
+
originalErrorSchema,
|
|
742
|
+
schemaValidation.errorSchema,
|
|
743
|
+
'preventDuplicates',
|
|
744
|
+
) as ErrorSchema<T>;
|
|
745
|
+
}
|
|
746
|
+
const schemaValidationErrors = errors;
|
|
747
|
+
const schemaValidationErrorSchema = errorSchema;
|
|
748
|
+
const mergedErrors = this.mergeErrors({ errorSchema, errors }, extraErrors, customErrors);
|
|
749
|
+
return { ...mergedErrors, schemaValidationErrors, schemaValidationErrorSchema };
|
|
750
|
+
}
|
|
751
|
+
|
|
578
752
|
/** Returns the `formData` with only the elements specified in the `fields` list
|
|
579
753
|
*
|
|
580
754
|
* @param formData - The data for the `Form`
|
|
@@ -601,25 +775,28 @@ export default class Form<
|
|
|
601
775
|
* @param [formData] - The form data to use while checking for empty objects/arrays
|
|
602
776
|
*/
|
|
603
777
|
getFieldNames = (pathSchema: PathSchema<T>, formData?: T): string[][] => {
|
|
778
|
+
const formValueHasData = (value: T, isLeaf: boolean) =>
|
|
779
|
+
typeof value !== 'object' || _isEmpty(value) || (isLeaf && !_isEmpty(value));
|
|
604
780
|
const getAllPaths = (_obj: GenericObjectType, acc: string[][] = [], paths: string[][] = [[]]) => {
|
|
605
|
-
Object.keys(_obj)
|
|
606
|
-
|
|
781
|
+
const objKeys = Object.keys(_obj);
|
|
782
|
+
objKeys.forEach((key: string) => {
|
|
783
|
+
const data = _obj[key];
|
|
784
|
+
if (typeof data === 'object') {
|
|
607
785
|
const newPaths = paths.map((path) => [...path, key]);
|
|
608
786
|
// If an object is marked with additionalProperties, all its keys are valid
|
|
609
|
-
if (
|
|
610
|
-
acc.push(
|
|
787
|
+
if (data[RJSF_ADDITIONAL_PROPERTIES_FLAG] && data[NAME_KEY] !== '') {
|
|
788
|
+
acc.push(data[NAME_KEY]);
|
|
611
789
|
} else {
|
|
612
|
-
getAllPaths(
|
|
790
|
+
getAllPaths(data, acc, newPaths);
|
|
613
791
|
}
|
|
614
|
-
} else if (key === NAME_KEY &&
|
|
792
|
+
} else if (key === NAME_KEY && data !== '') {
|
|
615
793
|
paths.forEach((path) => {
|
|
616
794
|
const formValue = _get(formData, path);
|
|
617
|
-
|
|
618
|
-
// or an empty object/array
|
|
795
|
+
const isLeaf = objKeys.length === 1;
|
|
796
|
+
// adds path to fieldNames if it points to a value or an empty object/array which is not a leaf
|
|
619
797
|
if (
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
(Array.isArray(formValue) && formValue.every((val) => typeof val !== 'object'))
|
|
798
|
+
formValueHasData(formValue, isLeaf) ||
|
|
799
|
+
(Array.isArray(formValue) && formValue.every((val) => formValueHasData(val, isLeaf)))
|
|
623
800
|
) {
|
|
624
801
|
acc.push(path);
|
|
625
802
|
}
|
|
@@ -642,124 +819,143 @@ export default class Form<
|
|
|
642
819
|
const retrievedSchema = schemaUtils.retrieveSchema(schema, formData);
|
|
643
820
|
const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData);
|
|
644
821
|
const fieldNames = this.getFieldNames(pathSchema, formData);
|
|
645
|
-
|
|
646
|
-
return newFormData;
|
|
822
|
+
return this.getUsedFormData(formData, fieldNames);
|
|
647
823
|
};
|
|
648
824
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
// Filtering out the previous raised customValidate errors so that they are cleared when no longer valid.
|
|
663
|
-
const filterPreviousCustomErrors = (errors: string[] = [], prevCustomErrors: string[]) => {
|
|
664
|
-
if (errors.length === 0) {
|
|
665
|
-
return errors;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
return errors.filter((error) => {
|
|
669
|
-
return !prevCustomErrors.includes(error);
|
|
670
|
-
});
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
// Removing undefined, null and empty errors.
|
|
674
|
-
const filterNilOrEmptyErrors = (errors: any, previousCustomValidateErrors: any = {}): ErrorSchema<T> => {
|
|
675
|
-
_forEach(errors, (errorAtKey: ErrorSchema<T>['__errors'] | undefined, errorKey: keyof typeof errors) => {
|
|
676
|
-
const prevCustomValidateErrorAtKey: ErrorSchema<T> | undefined = previousCustomValidateErrors[errorKey];
|
|
677
|
-
if (_isNil(errorAtKey) || (Array.isArray(errorAtKey) && errorAtKey.length === 0)) {
|
|
678
|
-
delete errors[errorKey];
|
|
679
|
-
} else if (
|
|
680
|
-
isObject(errorAtKey) &&
|
|
681
|
-
isObject(prevCustomValidateErrorAtKey) &&
|
|
682
|
-
Array.isArray(prevCustomValidateErrorAtKey?.__errors)
|
|
683
|
-
) {
|
|
684
|
-
// if previous customValidate error is an object and has __errors array, filter out the errors previous customValidate errors.
|
|
685
|
-
errors[errorKey] = filterPreviousCustomErrors(errorAtKey.__errors, prevCustomValidateErrorAtKey.__errors);
|
|
686
|
-
} else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey.__errors)) {
|
|
687
|
-
filterNilOrEmptyErrors(errorAtKey, previousCustomValidateErrors[errorKey]);
|
|
688
|
-
}
|
|
689
|
-
});
|
|
690
|
-
return errors;
|
|
691
|
-
};
|
|
692
|
-
return filterNilOrEmptyErrors(filteredErrors, prevCustomValidateErrors);
|
|
693
|
-
}
|
|
825
|
+
/** Allows a user to set a value for the provided `fieldPath`, which must be either a dotted path to the field OR a
|
|
826
|
+
* `FieldPathList`. To set the root element, used either `''` or `[]` for the path. Passing undefined will clear the
|
|
827
|
+
* value in the field.
|
|
828
|
+
*
|
|
829
|
+
* @param fieldPath - Either a dotted path to the field or the `FieldPathList` to the field
|
|
830
|
+
* @param [newValue] - The new value for the field
|
|
831
|
+
*/
|
|
832
|
+
setFieldValue = (fieldPath: string | FieldPathList, newValue?: T) => {
|
|
833
|
+
const { registry } = this.state;
|
|
834
|
+
const path = Array.isArray(fieldPath) ? fieldPath : fieldPath.split('.');
|
|
835
|
+
const fieldPathId = toFieldPathId('', registry.globalFormOptions, path);
|
|
836
|
+
this.onChange(newValue, path, undefined, fieldPathId[ID_KEY]);
|
|
837
|
+
};
|
|
694
838
|
|
|
695
|
-
/**
|
|
696
|
-
*
|
|
697
|
-
* then, if `omitExtraData` and `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not
|
|
698
|
-
* in a form field. Then, the resulting formData will be validated if required. The state will be updated with the new
|
|
699
|
-
* updated (potentially filtered) `formData`, any errors that resulted from validation. Finally the `onChange`
|
|
700
|
-
* callback will be called if specified with the updated state.
|
|
839
|
+
/** Pushes the given change information into the `pendingChanges` array and then calls `processPendingChanges()` if
|
|
840
|
+
* the array only contains a single pending change.
|
|
701
841
|
*
|
|
702
|
-
* @param
|
|
703
|
-
* @param
|
|
704
|
-
* @param
|
|
842
|
+
* @param newValue - The new form data from a change to a field
|
|
843
|
+
* @param path - The path to the change into which to set the formData
|
|
844
|
+
* @param [newErrorSchema] - The new `ErrorSchema` based on the field change
|
|
845
|
+
* @param [id] - The id of the field that caused the change
|
|
846
|
+
*/
|
|
847
|
+
onChange = (newValue: T | undefined, path: FieldPathList, newErrorSchema?: ErrorSchema<T>, id?: string) => {
|
|
848
|
+
this.pendingChanges.push({ newValue, path, newErrorSchema, id });
|
|
849
|
+
if (this.pendingChanges.length === 1) {
|
|
850
|
+
this.processPendingChange();
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
/** Function to handle changes made to a field in the `Form`. This handler gets the first change from the
|
|
855
|
+
* `pendingChanges` list, containing the `newValue` for the `formData` and the `path` at which the `newValue` is to be
|
|
856
|
+
* updated, along with a new, optional `ErrorSchema` for that same `path` and potentially the `id` of the field being
|
|
857
|
+
* changed. It will first update the `formData` with any missing default fields and then, if `omitExtraData` and
|
|
858
|
+
* `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not in a form field. Then, the
|
|
859
|
+
* resulting `formData` will be validated if required. The state will be updated with the new updated (potentially
|
|
860
|
+
* filtered) `formData`, any errors that resulted from validation. Finally the `onChange` callback will be called, if
|
|
861
|
+
* specified, with the updated state and the `processPendingChange()` function is called again.
|
|
705
862
|
*/
|
|
706
|
-
|
|
863
|
+
processPendingChange() {
|
|
864
|
+
if (this.pendingChanges.length === 0) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
const { newValue, path, id } = this.pendingChanges[0];
|
|
868
|
+
const { newErrorSchema } = this.pendingChanges[0];
|
|
707
869
|
const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;
|
|
708
|
-
const { schemaUtils, schema } = this.state;
|
|
870
|
+
const { formData: oldFormData, schemaUtils, schema, fieldPathId, schemaValidationErrorSchema, errors } = this.state;
|
|
871
|
+
let { customErrors, errorSchema: originalErrorSchema } = this.state;
|
|
872
|
+
const rootPathId = fieldPathId.path[0] || '';
|
|
709
873
|
|
|
874
|
+
const isRootPath = !path || path.length === 0 || (path.length === 1 && path[0] === rootPathId);
|
|
710
875
|
let retrievedSchema = this.state.retrievedSchema;
|
|
876
|
+
let formData = isRootPath ? newValue : _cloneDeep(oldFormData);
|
|
711
877
|
if (isObject(formData) || Array.isArray(formData)) {
|
|
712
|
-
|
|
878
|
+
if (!isRootPath) {
|
|
879
|
+
// If the newValue is not on the root path, then set it into the form data
|
|
880
|
+
_set(formData, path, newValue);
|
|
881
|
+
}
|
|
882
|
+
// Pass true to skip live validation in `getStateFromProps()` since we will do it a bit later
|
|
883
|
+
const newState = this.getStateFromProps(this.props, formData, undefined, undefined, undefined, true);
|
|
713
884
|
formData = newState.formData;
|
|
714
885
|
retrievedSchema = newState.retrievedSchema;
|
|
715
886
|
}
|
|
716
887
|
|
|
717
|
-
const mustValidate = !noValidate && liveValidate;
|
|
888
|
+
const mustValidate = !noValidate && (liveValidate === true || liveValidate === 'onChange');
|
|
718
889
|
let state: Partial<FormState<T, S, F>> = { formData, schema };
|
|
719
890
|
let newFormData = formData;
|
|
720
891
|
|
|
721
|
-
if (omitExtraData === true && liveOmit === true) {
|
|
892
|
+
if (omitExtraData === true && (liveOmit === true || liveOmit === 'onChange')) {
|
|
722
893
|
newFormData = this.omitExtraData(formData);
|
|
723
894
|
state = {
|
|
724
895
|
formData: newFormData,
|
|
725
896
|
};
|
|
726
897
|
}
|
|
727
898
|
|
|
728
|
-
if (
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
899
|
+
if (newErrorSchema) {
|
|
900
|
+
// First check to see if there is an existing validation error on this path...
|
|
901
|
+
// @ts-expect-error TS2590, because getting from the error schema is confusing TS
|
|
902
|
+
const oldValidationError = !isRootPath ? _get(schemaValidationErrorSchema, path) : schemaValidationErrorSchema;
|
|
903
|
+
// If there is an old validation error for this path, assume we are updating it directly
|
|
904
|
+
if (!_isEmpty(oldValidationError)) {
|
|
905
|
+
// Update the originalErrorSchema "in place" or replace it if it is the root
|
|
906
|
+
if (!isRootPath) {
|
|
907
|
+
_set(originalErrorSchema, path, newErrorSchema);
|
|
908
|
+
} else {
|
|
909
|
+
originalErrorSchema = newErrorSchema;
|
|
910
|
+
}
|
|
911
|
+
} else {
|
|
912
|
+
if (!customErrors) {
|
|
913
|
+
customErrors = new ErrorSchemaBuilder<T>();
|
|
914
|
+
}
|
|
915
|
+
if (isRootPath) {
|
|
916
|
+
const errors = _get(newErrorSchema, ERRORS_KEY);
|
|
917
|
+
if (errors) {
|
|
918
|
+
// only set errors when there are some
|
|
919
|
+
customErrors.setErrors(errors);
|
|
920
|
+
}
|
|
921
|
+
} else {
|
|
922
|
+
_set(customErrors.ErrorSchema, path, newErrorSchema);
|
|
923
|
+
}
|
|
743
924
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
925
|
+
} else if (customErrors && _get(customErrors.ErrorSchema, [...path, ERRORS_KEY])) {
|
|
926
|
+
// If we have custom errors and the path has an error, then we need to clear it
|
|
927
|
+
customErrors.clearErrors(path);
|
|
928
|
+
}
|
|
929
|
+
// If there are pending changes in the queue, skip live validation since it will happen with the last change
|
|
930
|
+
if (mustValidate && this.pendingChanges.length === 1) {
|
|
931
|
+
const liveValidation = this.liveValidate(
|
|
932
|
+
schema,
|
|
933
|
+
schemaUtils,
|
|
934
|
+
originalErrorSchema,
|
|
935
|
+
newFormData,
|
|
936
|
+
extraErrors,
|
|
937
|
+
customErrors,
|
|
938
|
+
retrievedSchema,
|
|
939
|
+
);
|
|
940
|
+
state = { formData: newFormData, ...liveValidation, customErrors };
|
|
751
941
|
} else if (!noValidate && newErrorSchema) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
: newErrorSchema;
|
|
942
|
+
// Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors.
|
|
943
|
+
const mergedErrors = this.mergeErrors({ errorSchema: originalErrorSchema, errors }, extraErrors, customErrors);
|
|
755
944
|
state = {
|
|
756
945
|
formData: newFormData,
|
|
757
|
-
|
|
758
|
-
|
|
946
|
+
...mergedErrors,
|
|
947
|
+
customErrors,
|
|
759
948
|
};
|
|
760
949
|
}
|
|
761
|
-
this.setState(state as FormState<T, S, F>, () =>
|
|
762
|
-
|
|
950
|
+
this.setState(state as FormState<T, S, F>, () => {
|
|
951
|
+
if (onChange) {
|
|
952
|
+
onChange(toIChangeEvent({ ...this.state, ...state }), id);
|
|
953
|
+
}
|
|
954
|
+
// Now remove the change we just completed and call this again
|
|
955
|
+
this.pendingChanges.shift();
|
|
956
|
+
this.processPendingChange();
|
|
957
|
+
});
|
|
958
|
+
}
|
|
763
959
|
|
|
764
960
|
/**
|
|
765
961
|
* If the retrievedSchema has changed the new retrievedSchema is returned.
|
|
@@ -782,8 +978,16 @@ export default class Form<
|
|
|
782
978
|
*
|
|
783
979
|
*/
|
|
784
980
|
reset = () => {
|
|
785
|
-
|
|
786
|
-
const
|
|
981
|
+
// Cast the IS_RESET symbol to T to avoid type issues, we use this symbol to detect reset mode
|
|
982
|
+
const { formData: propsFormData, initialFormData = IS_RESET as T, onChange } = this.props;
|
|
983
|
+
const newState = this.getStateFromProps(
|
|
984
|
+
this.props,
|
|
985
|
+
propsFormData ?? initialFormData,
|
|
986
|
+
undefined,
|
|
987
|
+
undefined,
|
|
988
|
+
undefined,
|
|
989
|
+
true,
|
|
990
|
+
);
|
|
787
991
|
const newFormData = newState.formData;
|
|
788
992
|
const state = {
|
|
789
993
|
formData: newFormData,
|
|
@@ -791,22 +995,61 @@ export default class Form<
|
|
|
791
995
|
errors: [] as unknown,
|
|
792
996
|
schemaValidationErrors: [] as unknown,
|
|
793
997
|
schemaValidationErrorSchema: {},
|
|
998
|
+
initialDefaultsGenerated: false,
|
|
999
|
+
customErrors: undefined,
|
|
794
1000
|
} as FormState<T, S, F>;
|
|
795
1001
|
|
|
796
|
-
this.setState(state, () => onChange && onChange({ ...this.state, ...state }));
|
|
1002
|
+
this.setState(state, () => onChange && onChange(toIChangeEvent({ ...this.state, ...state })));
|
|
797
1003
|
};
|
|
798
1004
|
|
|
799
1005
|
/** Callback function to handle when a field on the form is blurred. Calls the `onBlur` callback for the `Form` if it
|
|
800
|
-
* was provided.
|
|
1006
|
+
* was provided. Also runs any live validation and/or live omit operations if the flags indicate they should happen
|
|
1007
|
+
* during `onBlur`.
|
|
801
1008
|
*
|
|
802
1009
|
* @param id - The unique `id` of the field that was blurred
|
|
803
1010
|
* @param data - The data associated with the field that was blurred
|
|
804
1011
|
*/
|
|
805
1012
|
onBlur = (id: string, data: any) => {
|
|
806
|
-
const { onBlur } = this.props;
|
|
1013
|
+
const { onBlur, omitExtraData, liveOmit, liveValidate } = this.props;
|
|
807
1014
|
if (onBlur) {
|
|
808
1015
|
onBlur(id, data);
|
|
809
1016
|
}
|
|
1017
|
+
if ((omitExtraData === true && liveOmit === 'onBlur') || liveValidate === 'onBlur') {
|
|
1018
|
+
const { onChange, extraErrors } = this.props;
|
|
1019
|
+
const { formData } = this.state;
|
|
1020
|
+
let newFormData: T | undefined = formData;
|
|
1021
|
+
let state: Partial<FormState<T, S, F>> = { formData: newFormData };
|
|
1022
|
+
if (omitExtraData === true && liveOmit === 'onBlur') {
|
|
1023
|
+
newFormData = this.omitExtraData(formData);
|
|
1024
|
+
state = { formData: newFormData };
|
|
1025
|
+
}
|
|
1026
|
+
if (liveValidate === 'onBlur') {
|
|
1027
|
+
const { schema, schemaUtils, errorSchema, customErrors, retrievedSchema } = this.state;
|
|
1028
|
+
const liveValidation = this.liveValidate(
|
|
1029
|
+
schema,
|
|
1030
|
+
schemaUtils,
|
|
1031
|
+
errorSchema,
|
|
1032
|
+
newFormData,
|
|
1033
|
+
extraErrors,
|
|
1034
|
+
customErrors,
|
|
1035
|
+
retrievedSchema,
|
|
1036
|
+
);
|
|
1037
|
+
state = { formData: newFormData, ...liveValidation, customErrors };
|
|
1038
|
+
}
|
|
1039
|
+
const hasChanges = Object.keys(state)
|
|
1040
|
+
// Filter out `schemaValidationErrors` and `schemaValidationErrorSchema` since they aren't IChangeEvent props
|
|
1041
|
+
.filter((key) => !key.startsWith('schemaValidation'))
|
|
1042
|
+
.some((key) => {
|
|
1043
|
+
const oldData = _get(this.state, key);
|
|
1044
|
+
const newData = _get(state, key);
|
|
1045
|
+
return !deepEquals(oldData, newData);
|
|
1046
|
+
});
|
|
1047
|
+
this.setState(state as FormState<T, S, F>, () => {
|
|
1048
|
+
if (onChange && hasChanges) {
|
|
1049
|
+
onChange(toIChangeEvent({ ...this.state, ...state }), id);
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
810
1053
|
};
|
|
811
1054
|
|
|
812
1055
|
/** Callback function to handle when a field on the form is focused. Calls the `onFocus` callback for the `Form` if it
|
|
@@ -859,34 +1102,60 @@ export default class Form<
|
|
|
859
1102
|
},
|
|
860
1103
|
() => {
|
|
861
1104
|
if (onSubmit) {
|
|
862
|
-
onSubmit({ ...this.state, formData: newFormData,
|
|
1105
|
+
onSubmit(toIChangeEvent({ ...this.state, formData: newFormData }, 'submitted'), event);
|
|
863
1106
|
}
|
|
864
1107
|
},
|
|
865
1108
|
);
|
|
866
1109
|
}
|
|
867
1110
|
};
|
|
868
1111
|
|
|
869
|
-
/**
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1112
|
+
/** Extracts the `GlobalFormOptions` from the given Form `props`
|
|
1113
|
+
*
|
|
1114
|
+
* @param props - The form props to extract the global form options from
|
|
1115
|
+
* @returns - The `GlobalFormOptions` from the props
|
|
1116
|
+
* @private
|
|
1117
|
+
*/
|
|
1118
|
+
private getGlobalFormOptions(props: FormProps<T, S, F>): GlobalFormOptions {
|
|
1119
|
+
const {
|
|
1120
|
+
uiSchema = {},
|
|
1121
|
+
experimental_componentUpdateStrategy,
|
|
1122
|
+
idSeparator = DEFAULT_ID_SEPARATOR,
|
|
1123
|
+
idPrefix = DEFAULT_ID_PREFIX,
|
|
1124
|
+
nameGenerator,
|
|
1125
|
+
useFallbackUiForUnsupportedType = false,
|
|
1126
|
+
} = props;
|
|
1127
|
+
const rootFieldId = uiSchema['ui:rootFieldId'];
|
|
1128
|
+
// Omit any options that are undefined or null
|
|
1129
|
+
return {
|
|
1130
|
+
idPrefix: rootFieldId || idPrefix,
|
|
1131
|
+
idSeparator,
|
|
1132
|
+
useFallbackUiForUnsupportedType,
|
|
1133
|
+
...(experimental_componentUpdateStrategy !== undefined && { experimental_componentUpdateStrategy }),
|
|
1134
|
+
...(nameGenerator !== undefined && { nameGenerator }),
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/** Computed the registry for the form using the given `props`, `schema` and `schemaUtils` */
|
|
1139
|
+
getRegistry(props: FormProps<T, S, F>, schema: S, schemaUtils: SchemaUtilsType<T, S, F>): Registry<T, S, F> {
|
|
1140
|
+
const { translateString: customTranslateString, uiSchema = {} } = props;
|
|
873
1141
|
const { fields, templates, widgets, formContext, translateString } = getDefaultRegistry<T, S, F>();
|
|
874
1142
|
return {
|
|
875
|
-
fields: { ...fields, ...
|
|
1143
|
+
fields: { ...fields, ...props.fields },
|
|
876
1144
|
templates: {
|
|
877
1145
|
...templates,
|
|
878
|
-
...
|
|
1146
|
+
...props.templates,
|
|
879
1147
|
ButtonTemplates: {
|
|
880
1148
|
...templates.ButtonTemplates,
|
|
881
|
-
...
|
|
1149
|
+
...props.templates?.ButtonTemplates,
|
|
882
1150
|
},
|
|
883
1151
|
},
|
|
884
|
-
widgets: { ...widgets, ...
|
|
885
|
-
rootSchema:
|
|
886
|
-
formContext:
|
|
1152
|
+
widgets: { ...widgets, ...props.widgets },
|
|
1153
|
+
rootSchema: schema,
|
|
1154
|
+
formContext: props.formContext || formContext,
|
|
887
1155
|
schemaUtils,
|
|
888
1156
|
translateString: customTranslateString || translateString,
|
|
889
1157
|
globalUiOptions: uiSchema[UI_GLOBAL_OPTIONS_KEY],
|
|
1158
|
+
globalFormOptions: this.getGlobalFormOptions(props),
|
|
890
1159
|
};
|
|
891
1160
|
}
|
|
892
1161
|
|
|
@@ -1011,8 +1280,6 @@ export default class Form<
|
|
|
1011
1280
|
const {
|
|
1012
1281
|
children,
|
|
1013
1282
|
id,
|
|
1014
|
-
idPrefix,
|
|
1015
|
-
idSeparator,
|
|
1016
1283
|
className = '',
|
|
1017
1284
|
tagName,
|
|
1018
1285
|
name,
|
|
@@ -1025,13 +1292,11 @@ export default class Form<
|
|
|
1025
1292
|
noHtml5Validate = false,
|
|
1026
1293
|
disabled,
|
|
1027
1294
|
readonly,
|
|
1028
|
-
formContext,
|
|
1029
1295
|
showErrorList = 'top',
|
|
1030
1296
|
_internalFormWrapper,
|
|
1031
1297
|
} = this.props;
|
|
1032
1298
|
|
|
1033
|
-
const { schema, uiSchema, formData, errorSchema,
|
|
1034
|
-
const registry = this.getRegistry();
|
|
1299
|
+
const { schema, uiSchema, formData, errorSchema, fieldPathId, registry } = this.state;
|
|
1035
1300
|
const { SchemaField: _SchemaField } = registry.fields;
|
|
1036
1301
|
const { SubmitButton } = registry.templates.ButtonTemplates;
|
|
1037
1302
|
// The `semantic-ui` and `material-ui` themes have `_internalFormWrapper`s that take an `as` prop that is the
|
|
@@ -1068,10 +1333,7 @@ export default class Form<
|
|
|
1068
1333
|
schema={schema}
|
|
1069
1334
|
uiSchema={uiSchema}
|
|
1070
1335
|
errorSchema={errorSchema}
|
|
1071
|
-
|
|
1072
|
-
idPrefix={idPrefix}
|
|
1073
|
-
idSeparator={idSeparator}
|
|
1074
|
-
formContext={formContext}
|
|
1336
|
+
fieldPathId={fieldPathId}
|
|
1075
1337
|
formData={formData}
|
|
1076
1338
|
onChange={this.onChange}
|
|
1077
1339
|
onBlur={this.onBlur}
|