@rjsf/core 6.0.0-beta.2 → 6.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.umd.js +469 -360
- package/dist/{index.js → index.cjs} +640 -519
- package/dist/index.cjs.map +7 -0
- package/dist/index.esm.js +706 -566
- package/dist/index.esm.js.map +4 -4
- package/lib/components/Form.d.ts +66 -16
- package/lib/components/Form.d.ts.map +1 -1
- package/lib/components/Form.js +138 -59
- package/lib/components/fields/ArrayField.d.ts +17 -7
- package/lib/components/fields/ArrayField.d.ts.map +1 -1
- package/lib/components/fields/ArrayField.js +92 -59
- package/lib/components/fields/BooleanField.d.ts.map +1 -1
- package/lib/components/fields/BooleanField.js +7 -2
- package/lib/components/fields/LayoutGridField.d.ts +27 -25
- package/lib/components/fields/LayoutGridField.d.ts.map +1 -1
- package/lib/components/fields/LayoutGridField.js +83 -53
- package/lib/components/fields/LayoutHeaderField.d.ts +1 -1
- package/lib/components/fields/LayoutHeaderField.js +3 -3
- package/lib/components/fields/LayoutMultiSchemaField.js +6 -5
- package/lib/components/fields/MultiSchemaField.d.ts.map +1 -1
- package/lib/components/fields/MultiSchemaField.js +13 -9
- 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 +3 -3
- package/lib/components/fields/ObjectField.d.ts.map +1 -1
- package/lib/components/fields/ObjectField.js +18 -25
- package/lib/components/fields/SchemaField.d.ts.map +1 -1
- package/lib/components/fields/SchemaField.js +17 -17
- package/lib/components/fields/StringField.d.ts.map +1 -1
- package/lib/components/fields/StringField.js +7 -2
- package/lib/components/templates/ArrayFieldDescriptionTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldDescriptionTemplate.js +3 -3
- package/lib/components/templates/ArrayFieldItemButtonsTemplate.js +2 -2
- package/lib/components/templates/ArrayFieldTemplate.js +3 -3
- package/lib/components/templates/ArrayFieldTitleTemplate.d.ts +1 -1
- package/lib/components/templates/ArrayFieldTitleTemplate.js +3 -3
- 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.js +2 -2
- package/lib/components/templates/UnsupportedField.js +3 -3
- package/lib/components/templates/index.d.ts.map +1 -1
- package/lib/components/templates/index.js +2 -0
- package/lib/components/widgets/AltDateWidget.d.ts.map +1 -1
- package/lib/components/widgets/AltDateWidget.js +15 -18
- package/lib/components/widgets/CheckboxesWidget.js +2 -2
- package/lib/getDefaultRegistry.d.ts.map +1 -1
- package/lib/getDefaultRegistry.js +2 -1
- package/lib/getTestRegistry.d.ts +5 -0
- package/lib/getTestRegistry.d.ts.map +1 -0
- package/lib/getTestRegistry.js +19 -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 +18 -19
- package/src/components/Form.tsx +183 -73
- package/src/components/fields/ArrayField.tsx +99 -67
- package/src/components/fields/BooleanField.tsx +12 -3
- package/src/components/fields/LayoutGridField.tsx +95 -82
- package/src/components/fields/LayoutHeaderField.tsx +3 -3
- package/src/components/fields/LayoutMultiSchemaField.tsx +5 -5
- package/src/components/fields/MultiSchemaField.tsx +51 -35
- package/src/components/fields/NullField.tsx +3 -3
- package/src/components/fields/NumberField.tsx +11 -3
- package/src/components/fields/ObjectField.tsx +19 -36
- package/src/components/fields/SchemaField.tsx +24 -30
- package/src/components/fields/StringField.tsx +12 -3
- package/src/components/templates/ArrayFieldDescriptionTemplate.tsx +3 -3
- package/src/components/templates/ArrayFieldItemButtonsTemplate.tsx +5 -5
- package/src/components/templates/ArrayFieldTemplate.tsx +5 -5
- package/src/components/templates/ArrayFieldTitleTemplate.tsx +3 -3
- package/src/components/templates/BaseInputTemplate.tsx +3 -3
- 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 +5 -5
- package/src/components/templates/UnsupportedField.tsx +3 -3
- package/src/components/templates/WrapIfAdditionalTemplate.tsx +1 -1
- package/src/components/templates/index.ts +2 -0
- package/src/components/widgets/AltDateWidget.tsx +21 -23
- package/src/components/widgets/CheckboxWidget.tsx +2 -2
- package/src/components/widgets/CheckboxesWidget.tsx +3 -3
- package/src/components/widgets/RadioWidget.tsx +1 -1
- package/src/components/widgets/SelectWidget.tsx +1 -1
- package/src/components/widgets/TextareaWidget.tsx +1 -1
- package/src/getDefaultRegistry.ts +10 -1
- package/src/getTestRegistry.tsx +34 -0
- package/src/index.ts +2 -1
- package/dist/index.js.map +0 -7
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rjsf/core",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.20",
|
|
4
4
|
"description": "A simple React component capable of building HTML forms out of a JSON schema.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"compileReplacer": "tsc -p tsconfig.replacer.json && move-file lodashReplacer.js lodashReplacer.cjs",
|
|
7
7
|
"build:ts": "npm run compileReplacer && rimraf ./lib && tsc -b tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
8
|
-
"build:cjs": "esbuild ./src/index.ts --bundle --outfile=dist/index.
|
|
8
|
+
"build:cjs": "esbuild ./src/index.ts --bundle --outfile=dist/index.cjs --sourcemap --packages=external --format=cjs",
|
|
9
9
|
"build:esm": "esbuild ./src/index.ts --bundle --outfile=dist/index.esm.js --sourcemap --packages=external --format=esm",
|
|
10
10
|
"build:umd": "rollup dist/index.esm.js --format=umd --file=dist/core.umd.js --name=JSONSchemaForm",
|
|
11
11
|
"build": "npm run build:ts && npm run build:cjs && npm run build:esm && npm run build:umd",
|
|
@@ -32,27 +32,27 @@
|
|
|
32
32
|
"exports": {
|
|
33
33
|
".": {
|
|
34
34
|
"types": "./lib/index.d.ts",
|
|
35
|
-
"require": "./dist/index.
|
|
35
|
+
"require": "./dist/index.cjs",
|
|
36
36
|
"import": "./lib/index.js"
|
|
37
37
|
},
|
|
38
38
|
"./lib": {
|
|
39
39
|
"types": "./lib/index.d.ts",
|
|
40
|
-
"require": "./dist/index.
|
|
40
|
+
"require": "./dist/index.cjs",
|
|
41
41
|
"import": "./lib/index.js"
|
|
42
42
|
},
|
|
43
43
|
"./lib/*.js": {
|
|
44
44
|
"types": "./lib/*.d.ts",
|
|
45
|
-
"require": "./dist/*.
|
|
45
|
+
"require": "./dist/*.cjs",
|
|
46
46
|
"import": "./lib/*.js"
|
|
47
47
|
},
|
|
48
48
|
"./dist": {
|
|
49
49
|
"types": "./lib/index.d.ts",
|
|
50
|
-
"require": "./dist/index.
|
|
50
|
+
"require": "./dist/index.cjs",
|
|
51
51
|
"import": "./lib/index.js"
|
|
52
52
|
},
|
|
53
|
-
"./dist/*.
|
|
53
|
+
"./dist/*.cjs": {
|
|
54
54
|
"types": "./lib/*.d.ts",
|
|
55
|
-
"require": "./dist/*.
|
|
55
|
+
"require": "./dist/*.cjs",
|
|
56
56
|
"import": "./lib/*.js"
|
|
57
57
|
}
|
|
58
58
|
},
|
|
@@ -66,30 +66,29 @@
|
|
|
66
66
|
"node": ">=20"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@rjsf/utils": "^6.0.0-beta.
|
|
69
|
+
"@rjsf/utils": "^6.0.0-beta.20",
|
|
70
70
|
"react": ">=18"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"lodash": "^4.17.21",
|
|
74
74
|
"lodash-es": "^4.17.21",
|
|
75
|
-
"markdown-to-jsx": "^7.7.
|
|
76
|
-
"nanoid": "^5.1.5",
|
|
75
|
+
"markdown-to-jsx": "^7.7.13",
|
|
77
76
|
"prop-types": "^15.8.1"
|
|
78
77
|
},
|
|
79
78
|
"devDependencies": {
|
|
80
|
-
"@rjsf/snapshot-tests": "^6.0.0-beta.
|
|
81
|
-
"@rjsf/utils": "^6.0.0-beta.
|
|
82
|
-
"@rjsf/validator-ajv8": "^6.0.0-beta.
|
|
83
|
-
"@testing-library/jest-dom": "^6.
|
|
84
|
-
"@testing-library/react": "^16.
|
|
79
|
+
"@rjsf/snapshot-tests": "^6.0.0-beta.20",
|
|
80
|
+
"@rjsf/utils": "^6.0.0-beta.20",
|
|
81
|
+
"@rjsf/validator-ajv8": "^6.0.0-beta.20",
|
|
82
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
83
|
+
"@testing-library/react": "^16.3.0",
|
|
85
84
|
"@testing-library/user-event": "^14.6.1",
|
|
86
|
-
"ajv": "^8.
|
|
85
|
+
"ajv": "^8.17.1",
|
|
87
86
|
"atob": "^2.1.2",
|
|
88
87
|
"chai": "^3.5.0",
|
|
89
|
-
"eslint": "^8.
|
|
88
|
+
"eslint": "^8.57.1",
|
|
90
89
|
"html": "^1.0.0",
|
|
91
90
|
"jsdom": "^20.0.3",
|
|
92
|
-
"mocha": "^10.2
|
|
91
|
+
"mocha": "^10.8.2",
|
|
93
92
|
"react-portal": "^4.3.0",
|
|
94
93
|
"sinon": "^9.2.4"
|
|
95
94
|
},
|
package/src/components/Form.tsx
CHANGED
|
@@ -3,14 +3,16 @@ import {
|
|
|
3
3
|
createSchemaUtils,
|
|
4
4
|
CustomValidator,
|
|
5
5
|
deepEquals,
|
|
6
|
+
ERRORS_KEY,
|
|
6
7
|
ErrorSchema,
|
|
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,
|
|
@@ -37,12 +40,17 @@ import {
|
|
|
37
40
|
Experimental_CustomMergeAllOf,
|
|
38
41
|
createErrorHandler,
|
|
39
42
|
unwrapErrorHandler,
|
|
43
|
+
DEFAULT_ID_SEPARATOR,
|
|
44
|
+
DEFAULT_ID_PREFIX,
|
|
45
|
+
GlobalFormOptions,
|
|
40
46
|
} from '@rjsf/utils';
|
|
47
|
+
import _cloneDeep from 'lodash/cloneDeep';
|
|
41
48
|
import _forEach from 'lodash/forEach';
|
|
42
49
|
import _get from 'lodash/get';
|
|
43
50
|
import _isEmpty from 'lodash/isEmpty';
|
|
44
51
|
import _isNil from 'lodash/isNil';
|
|
45
52
|
import _pick from 'lodash/pick';
|
|
53
|
+
import _set from 'lodash/set';
|
|
46
54
|
import _toPath from 'lodash/toPath';
|
|
47
55
|
|
|
48
56
|
import getDefaultRegistry from '../getDefaultRegistry';
|
|
@@ -195,8 +203,20 @@ export interface FormProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
|
|
|
195
203
|
* `emptyObjectFields`
|
|
196
204
|
*/
|
|
197
205
|
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior;
|
|
206
|
+
/**
|
|
207
|
+
* Controls the component update strategy used by the Form's `shouldComponentUpdate` lifecycle method.
|
|
208
|
+
*
|
|
209
|
+
* - `'customDeep'`: Uses RJSF's custom deep equality checks via the `deepEquals` utility function,
|
|
210
|
+
* which treats all functions as equivalent and provides optimized performance for form data comparisons.
|
|
211
|
+
* - `'shallow'`: Uses shallow comparison of props and state (only compares direct properties). This matches React's PureComponent behavior.
|
|
212
|
+
* - `'always'`: Always rerenders when called. This matches React's Component behavior.
|
|
213
|
+
*
|
|
214
|
+
* @default 'customDeep'
|
|
215
|
+
*/
|
|
216
|
+
experimental_componentUpdateStrategy?: 'customDeep' | 'shallow' | 'always';
|
|
198
217
|
/** Optional function that allows for custom merging of `allOf` schemas
|
|
199
218
|
*/
|
|
219
|
+
|
|
200
220
|
experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;
|
|
201
221
|
// Private
|
|
202
222
|
/**
|
|
@@ -226,10 +246,10 @@ export interface FormState<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
|
|
|
226
246
|
schema: S;
|
|
227
247
|
/** The uiSchema for the form */
|
|
228
248
|
uiSchema: UiSchema<T, S, F>;
|
|
229
|
-
/** The `
|
|
249
|
+
/** The `FieldPathId` for the form, computed from the `schema`, the `rootFieldId`, the `idPrefix` and
|
|
230
250
|
* `idSeparator` props.
|
|
231
251
|
*/
|
|
232
|
-
|
|
252
|
+
fieldPathId: FieldPathId;
|
|
233
253
|
/** The schemaUtils implementation used by the `Form`, created from the `validator` and the `schema` */
|
|
234
254
|
schemaUtils: SchemaUtilsType<T, S, F>;
|
|
235
255
|
/** The current data for the form, computed from the `formData` prop and the changes made by the user */
|
|
@@ -260,6 +280,19 @@ export interface IChangeEvent<T = any, S extends StrictRJSFSchema = RJSFSchema,
|
|
|
260
280
|
status?: 'submitted';
|
|
261
281
|
}
|
|
262
282
|
|
|
283
|
+
/** The definition of a pending change that will be processed in the `onChange` handler
|
|
284
|
+
*/
|
|
285
|
+
interface PendingChange<T> {
|
|
286
|
+
/** The path into the formData/errorSchema at which the `newValue`/`newErrorSchema` will be set */
|
|
287
|
+
path: FieldPathList;
|
|
288
|
+
/** The new value to set into the formData */
|
|
289
|
+
newValue?: T;
|
|
290
|
+
/** The new errors to be set into the errorSchema, if any */
|
|
291
|
+
newErrorSchema?: ErrorSchema<T>;
|
|
292
|
+
/** The optional id of the field for which the change is being made */
|
|
293
|
+
id?: string;
|
|
294
|
+
}
|
|
295
|
+
|
|
263
296
|
/** The `Form` component renders the outer form and all the fields defined in the `schema` */
|
|
264
297
|
export default class Form<
|
|
265
298
|
T = any,
|
|
@@ -271,6 +304,10 @@ export default class Form<
|
|
|
271
304
|
*/
|
|
272
305
|
formElement: RefObject<any>;
|
|
273
306
|
|
|
307
|
+
/** The list of pending changes
|
|
308
|
+
*/
|
|
309
|
+
pendingChanges: PendingChange<T>[] = [];
|
|
310
|
+
|
|
274
311
|
/** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the
|
|
275
312
|
* `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the
|
|
276
313
|
* state construction.
|
|
@@ -314,12 +351,18 @@ export default class Form<
|
|
|
314
351
|
prevState: FormState<T, S, F>,
|
|
315
352
|
): { nextState: FormState<T, S, F>; shouldUpdate: true } | { shouldUpdate: false } {
|
|
316
353
|
if (!deepEquals(this.props, prevProps)) {
|
|
354
|
+
// Compare the previous props formData against the current props formData
|
|
317
355
|
const formDataChangedFields = getChangedFields(this.props.formData, prevProps.formData);
|
|
356
|
+
// Compare the current props formData against the current state's formData to determine if the new props were the
|
|
357
|
+
// result of the onChange from the existing state formData
|
|
358
|
+
const stateDataChangedFields = getChangedFields(this.props.formData, this.state.formData);
|
|
318
359
|
const isSchemaChanged = !deepEquals(prevProps.schema, this.props.schema);
|
|
319
360
|
// When formData is not an object, getChangedFields returns an empty array.
|
|
320
361
|
// In this case, deepEquals is most needed to check again.
|
|
321
362
|
const isFormDataChanged =
|
|
322
363
|
formDataChangedFields.length > 0 || !deepEquals(prevProps.formData, this.props.formData);
|
|
364
|
+
const isStateDataChanged =
|
|
365
|
+
stateDataChangedFields.length > 0 || !deepEquals(this.state.formData, this.props.formData);
|
|
323
366
|
const nextState = this.getStateFromProps(
|
|
324
367
|
this.props,
|
|
325
368
|
this.props.formData,
|
|
@@ -329,6 +372,8 @@ export default class Form<
|
|
|
329
372
|
isSchemaChanged || isFormDataChanged ? undefined : this.state.retrievedSchema,
|
|
330
373
|
isSchemaChanged,
|
|
331
374
|
formDataChangedFields,
|
|
375
|
+
// Skip live validation for this request if no form data has changed from the last state
|
|
376
|
+
!isStateDataChanged,
|
|
332
377
|
);
|
|
333
378
|
const shouldUpdate = !deepEquals(nextState, prevState);
|
|
334
379
|
return { nextState, shouldUpdate };
|
|
@@ -376,6 +421,7 @@ export default class Form<
|
|
|
376
421
|
* @param retrievedSchema - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`.
|
|
377
422
|
* @param isSchemaChanged - A flag indicating whether the schema has changed.
|
|
378
423
|
* @param formDataChangedFields - The changed fields of `formData`
|
|
424
|
+
* @param skipLiveValidate - Optional flag, if true, means that we are not running live validation
|
|
379
425
|
* @returns - The new state for the `Form`
|
|
380
426
|
*/
|
|
381
427
|
getStateFromProps(
|
|
@@ -384,14 +430,15 @@ export default class Form<
|
|
|
384
430
|
retrievedSchema?: S,
|
|
385
431
|
isSchemaChanged = false,
|
|
386
432
|
formDataChangedFields: string[] = [],
|
|
433
|
+
skipLiveValidate = false,
|
|
387
434
|
): FormState<T, S, F> {
|
|
388
435
|
const state: FormState<T, S, F> = this.state || {};
|
|
389
436
|
const schema = 'schema' in props ? props.schema : this.props.schema;
|
|
437
|
+
const validator = 'validator' in props ? props.validator : this.props.validator;
|
|
390
438
|
const uiSchema: UiSchema<T, S, F> = ('uiSchema' in props ? props.uiSchema! : this.props.uiSchema!) || {};
|
|
391
439
|
const edit = typeof inputFormData !== 'undefined';
|
|
392
440
|
const liveValidate = 'liveValidate' in props ? props.liveValidate : this.props.liveValidate;
|
|
393
441
|
const mustValidate = edit && !props.noValidate && liveValidate;
|
|
394
|
-
const rootSchema = schema;
|
|
395
442
|
const experimental_defaultFormStateBehavior =
|
|
396
443
|
'experimental_defaultFormStateBehavior' in props
|
|
397
444
|
? props.experimental_defaultFormStateBehavior
|
|
@@ -404,22 +451,23 @@ export default class Form<
|
|
|
404
451
|
if (
|
|
405
452
|
!schemaUtils ||
|
|
406
453
|
schemaUtils.doesSchemaUtilsDiffer(
|
|
407
|
-
|
|
408
|
-
|
|
454
|
+
validator,
|
|
455
|
+
schema,
|
|
409
456
|
experimental_defaultFormStateBehavior,
|
|
410
457
|
experimental_customMergeAllOf,
|
|
411
458
|
)
|
|
412
459
|
) {
|
|
413
460
|
schemaUtils = createSchemaUtils<T, S, F>(
|
|
414
|
-
|
|
415
|
-
|
|
461
|
+
validator,
|
|
462
|
+
schema,
|
|
416
463
|
experimental_defaultFormStateBehavior,
|
|
417
464
|
experimental_customMergeAllOf,
|
|
418
465
|
);
|
|
419
466
|
}
|
|
420
|
-
const
|
|
467
|
+
const rootSchema = schemaUtils.getRootSchema();
|
|
468
|
+
const formData: T = schemaUtils.getDefaultFormState(rootSchema, inputFormData) as T;
|
|
421
469
|
const _retrievedSchema = this.updateRetrievedSchema(
|
|
422
|
-
retrievedSchema ?? schemaUtils.retrieveSchema(
|
|
470
|
+
retrievedSchema ?? schemaUtils.retrieveSchema(rootSchema, formData),
|
|
423
471
|
);
|
|
424
472
|
|
|
425
473
|
const getCurrentErrors = (): ValidationData<T> => {
|
|
@@ -442,8 +490,9 @@ export default class Form<
|
|
|
442
490
|
let errorSchema: ErrorSchema<T> | undefined;
|
|
443
491
|
let schemaValidationErrors: RJSFValidationError[] = state.schemaValidationErrors;
|
|
444
492
|
let schemaValidationErrorSchema: ErrorSchema<T> = state.schemaValidationErrorSchema;
|
|
445
|
-
|
|
446
|
-
|
|
493
|
+
// If we are skipping live validate, it means that the state has already been updated with live validation errors
|
|
494
|
+
if (mustValidate && !skipLiveValidate) {
|
|
495
|
+
const schemaValidation = this.validate(formData, rootSchema, schemaUtils, _retrievedSchema);
|
|
447
496
|
errors = schemaValidation.errors;
|
|
448
497
|
// If retrievedSchema is undefined which means the schema or formData has changed, we do not merge state.
|
|
449
498
|
// Else in the case where it hasn't changed, we merge 'state.errorSchema' with 'schemaValidation.errorSchema.' This done to display the raised field error.
|
|
@@ -462,7 +511,8 @@ export default class Form<
|
|
|
462
511
|
const currentErrors = getCurrentErrors();
|
|
463
512
|
errors = currentErrors.errors;
|
|
464
513
|
errorSchema = currentErrors.errorSchema;
|
|
465
|
-
if
|
|
514
|
+
// We only update the error schema for changed fields if mustValidate is false
|
|
515
|
+
if (formDataChangedFields.length > 0 && !mustValidate) {
|
|
466
516
|
const newErrorSchema = formDataChangedFields.reduce(
|
|
467
517
|
(acc, key) => {
|
|
468
518
|
acc[key] = undefined;
|
|
@@ -483,18 +533,12 @@ export default class Form<
|
|
|
483
533
|
errorSchema = merged.errorSchema;
|
|
484
534
|
errors = merged.errors;
|
|
485
535
|
}
|
|
486
|
-
const
|
|
487
|
-
_retrievedSchema,
|
|
488
|
-
uiSchema['ui:rootFieldId'],
|
|
489
|
-
formData,
|
|
490
|
-
props.idPrefix,
|
|
491
|
-
props.idSeparator,
|
|
492
|
-
);
|
|
536
|
+
const fieldPathId = toFieldPathId('', this.getGlobalFormOptions(this.props));
|
|
493
537
|
const nextState: FormState<T, S, F> = {
|
|
494
538
|
schemaUtils,
|
|
495
|
-
schema,
|
|
539
|
+
schema: rootSchema,
|
|
496
540
|
uiSchema,
|
|
497
|
-
|
|
541
|
+
fieldPathId,
|
|
498
542
|
formData,
|
|
499
543
|
edit,
|
|
500
544
|
errors,
|
|
@@ -513,9 +557,9 @@ export default class Form<
|
|
|
513
557
|
* @returns - True if the component should be updated, false otherwise
|
|
514
558
|
*/
|
|
515
559
|
shouldComponentUpdate(nextProps: FormProps<T, S, F>, nextState: FormState<T, S, F>): boolean {
|
|
516
|
-
|
|
560
|
+
const { experimental_componentUpdateStrategy = 'customDeep' } = this.props;
|
|
561
|
+
return shouldRender(this, nextProps, nextState, experimental_componentUpdateStrategy);
|
|
517
562
|
}
|
|
518
|
-
|
|
519
563
|
/** Gets the previously raised customValidate errors.
|
|
520
564
|
*
|
|
521
565
|
* @returns the previous customValidate errors
|
|
@@ -526,8 +570,7 @@ export default class Form<
|
|
|
526
570
|
let customValidateErrors = {};
|
|
527
571
|
if (typeof customValidate === 'function') {
|
|
528
572
|
const errorHandler = customValidate(prevFormData, createErrorHandler<T>(prevFormData), uiSchema);
|
|
529
|
-
|
|
530
|
-
customValidateErrors = userErrorSchema;
|
|
573
|
+
customValidateErrors = unwrapErrorHandler<T>(errorHandler);
|
|
531
574
|
}
|
|
532
575
|
return customValidateErrors;
|
|
533
576
|
}
|
|
@@ -537,11 +580,12 @@ export default class Form<
|
|
|
537
580
|
*
|
|
538
581
|
* @param formData - The new form data to validate
|
|
539
582
|
* @param schema - The schema used to validate against
|
|
540
|
-
* @param altSchemaUtils - The alternate schemaUtils to use for validation
|
|
583
|
+
* @param [altSchemaUtils] - The alternate schemaUtils to use for validation
|
|
584
|
+
* @param [retrievedSchema] - An optionally retrieved schema for per
|
|
541
585
|
*/
|
|
542
586
|
validate(
|
|
543
587
|
formData: T | undefined,
|
|
544
|
-
schema = this.
|
|
588
|
+
schema = this.state.schema,
|
|
545
589
|
altSchemaUtils?: SchemaUtilsType<T, S, F>,
|
|
546
590
|
retrievedSchema?: S,
|
|
547
591
|
): ValidationData<T> {
|
|
@@ -556,7 +600,6 @@ export default class Form<
|
|
|
556
600
|
/** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */
|
|
557
601
|
renderErrors(registry: Registry<T, S, F>) {
|
|
558
602
|
const { errors, errorSchema, schema, uiSchema } = this.state;
|
|
559
|
-
const { formContext } = this.props;
|
|
560
603
|
const options = getUiOptions<T, S, F>(uiSchema);
|
|
561
604
|
const ErrorListTemplate = getTemplate<'ErrorListTemplate', T, S, F>('ErrorListTemplate', registry, options);
|
|
562
605
|
|
|
@@ -567,7 +610,6 @@ export default class Form<
|
|
|
567
610
|
errorSchema={errorSchema || {}}
|
|
568
611
|
schema={schema}
|
|
569
612
|
uiSchema={uiSchema}
|
|
570
|
-
formContext={formContext}
|
|
571
613
|
registry={registry}
|
|
572
614
|
/>
|
|
573
615
|
);
|
|
@@ -601,25 +643,28 @@ export default class Form<
|
|
|
601
643
|
* @param [formData] - The form data to use while checking for empty objects/arrays
|
|
602
644
|
*/
|
|
603
645
|
getFieldNames = (pathSchema: PathSchema<T>, formData?: T): string[][] => {
|
|
646
|
+
const formValueHasData = (value: T, isLeaf: boolean) =>
|
|
647
|
+
typeof value !== 'object' || _isEmpty(value) || (isLeaf && !_isEmpty(value));
|
|
604
648
|
const getAllPaths = (_obj: GenericObjectType, acc: string[][] = [], paths: string[][] = [[]]) => {
|
|
605
|
-
Object.keys(_obj)
|
|
606
|
-
|
|
649
|
+
const objKeys = Object.keys(_obj);
|
|
650
|
+
objKeys.forEach((key: string) => {
|
|
651
|
+
const data = _obj[key];
|
|
652
|
+
if (typeof data === 'object') {
|
|
607
653
|
const newPaths = paths.map((path) => [...path, key]);
|
|
608
654
|
// If an object is marked with additionalProperties, all its keys are valid
|
|
609
|
-
if (
|
|
610
|
-
acc.push(
|
|
655
|
+
if (data[RJSF_ADDITIONAL_PROPERTIES_FLAG] && data[NAME_KEY] !== '') {
|
|
656
|
+
acc.push(data[NAME_KEY]);
|
|
611
657
|
} else {
|
|
612
|
-
getAllPaths(
|
|
658
|
+
getAllPaths(data, acc, newPaths);
|
|
613
659
|
}
|
|
614
|
-
} else if (key === NAME_KEY &&
|
|
660
|
+
} else if (key === NAME_KEY && data !== '') {
|
|
615
661
|
paths.forEach((path) => {
|
|
616
662
|
const formValue = _get(formData, path);
|
|
617
|
-
|
|
618
|
-
// or an empty object/array
|
|
663
|
+
const isLeaf = objKeys.length === 1;
|
|
664
|
+
// adds path to fieldNames if it points to a value or an empty object/array which is not a leaf
|
|
619
665
|
if (
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
(Array.isArray(formValue) && formValue.every((val) => typeof val !== 'object'))
|
|
666
|
+
formValueHasData(formValue, isLeaf) ||
|
|
667
|
+
(Array.isArray(formValue) && formValue.every((val) => formValueHasData(val, isLeaf)))
|
|
623
668
|
) {
|
|
624
669
|
acc.push(path);
|
|
625
670
|
}
|
|
@@ -642,11 +687,16 @@ export default class Form<
|
|
|
642
687
|
const retrievedSchema = schemaUtils.retrieveSchema(schema, formData);
|
|
643
688
|
const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData);
|
|
644
689
|
const fieldNames = this.getFieldNames(pathSchema, formData);
|
|
645
|
-
|
|
646
|
-
return newFormData;
|
|
690
|
+
return this.getUsedFormData(formData, fieldNames);
|
|
647
691
|
};
|
|
648
692
|
|
|
649
|
-
|
|
693
|
+
/** Filtering errors based on your retrieved schema to only show errors for properties in the selected branch.
|
|
694
|
+
*
|
|
695
|
+
* @param schemaErrors - The schema errors to filter
|
|
696
|
+
* @param [resolvedSchema] - An optionally resolved schema to use for performance reasons
|
|
697
|
+
* @param [formData] - The formData to help filter errors
|
|
698
|
+
* @private
|
|
699
|
+
*/
|
|
650
700
|
private filterErrorsBasedOnSchema(schemaErrors: ErrorSchema<T>, resolvedSchema?: S, formData?: any): ErrorSchema<T> {
|
|
651
701
|
const { retrievedSchema, schemaUtils } = this.state;
|
|
652
702
|
const _retrievedSchema = resolvedSchema ?? retrievedSchema;
|
|
@@ -655,7 +705,7 @@ export default class Form<
|
|
|
655
705
|
const filteredErrors: ErrorSchema<T> = _pick(schemaErrors, fieldNames as unknown as string[]);
|
|
656
706
|
// If the root schema is of a primitive type, do not filter out the __errors
|
|
657
707
|
if (resolvedSchema?.type !== 'object' && resolvedSchema?.type !== 'array') {
|
|
658
|
-
filteredErrors
|
|
708
|
+
filteredErrors[ERRORS_KEY] = schemaErrors[ERRORS_KEY];
|
|
659
709
|
}
|
|
660
710
|
|
|
661
711
|
const prevCustomValidateErrors = this.getPreviousCustomValidateErrors();
|
|
@@ -679,11 +729,16 @@ export default class Form<
|
|
|
679
729
|
} else if (
|
|
680
730
|
isObject(errorAtKey) &&
|
|
681
731
|
isObject(prevCustomValidateErrorAtKey) &&
|
|
682
|
-
Array.isArray(prevCustomValidateErrorAtKey?.
|
|
732
|
+
Array.isArray(prevCustomValidateErrorAtKey?.[ERRORS_KEY])
|
|
683
733
|
) {
|
|
684
734
|
// if previous customValidate error is an object and has __errors array, filter out the errors previous customValidate errors.
|
|
685
|
-
errors[errorKey] =
|
|
686
|
-
|
|
735
|
+
errors[errorKey] = {
|
|
736
|
+
[ERRORS_KEY]: filterPreviousCustomErrors(
|
|
737
|
+
errorAtKey[ERRORS_KEY],
|
|
738
|
+
prevCustomValidateErrorAtKey?.[ERRORS_KEY],
|
|
739
|
+
),
|
|
740
|
+
};
|
|
741
|
+
} else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey[ERRORS_KEY])) {
|
|
687
742
|
filterNilOrEmptyErrors(errorAtKey, previousCustomValidateErrors[errorKey]);
|
|
688
743
|
}
|
|
689
744
|
});
|
|
@@ -692,24 +747,50 @@ export default class Form<
|
|
|
692
747
|
return filterNilOrEmptyErrors(filteredErrors, prevCustomValidateErrors);
|
|
693
748
|
}
|
|
694
749
|
|
|
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.
|
|
750
|
+
/** Pushes the given change information into the `pendingChanges` array and then calls `processPendingChanges()` if
|
|
751
|
+
* the array only contains a single pending change.
|
|
701
752
|
*
|
|
702
|
-
* @param
|
|
703
|
-
* @param
|
|
704
|
-
* @param
|
|
753
|
+
* @param newValue - The new form data from a change to a field
|
|
754
|
+
* @param path - The path to the change into which to set the formData
|
|
755
|
+
* @param [newErrorSchema] - The new `ErrorSchema` based on the field change
|
|
756
|
+
* @param [id] - The id of the field that caused the change
|
|
757
|
+
*/
|
|
758
|
+
onChange = (newValue: T | undefined, path: FieldPathList, newErrorSchema?: ErrorSchema<T>, id?: string) => {
|
|
759
|
+
this.pendingChanges.push({ newValue, path, newErrorSchema, id });
|
|
760
|
+
if (this.pendingChanges.length === 1) {
|
|
761
|
+
this.processPendingChange();
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
/** Function to handle changes made to a field in the `Form`. This handler gets the first change from the
|
|
766
|
+
* `pendingChanges` list, containing the `newValue` for the `formData` and the `path` at which the `newValue` is to be
|
|
767
|
+
* updated, along with a new, optional `ErrorSchema` for that same `path` and potentially the `id` of the field being
|
|
768
|
+
* changed. It will first update the `formData` with any missing default fields and then, if `omitExtraData` and
|
|
769
|
+
* `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not in a form field. Then, the
|
|
770
|
+
* resulting `formData` will be validated if required. The state will be updated with the new updated (potentially
|
|
771
|
+
* filtered) `formData`, any errors that resulted from validation. Finally the `onChange` callback will be called, if
|
|
772
|
+
* specified, with the updated state and the `processPendingChange()` function is called again.
|
|
705
773
|
*/
|
|
706
|
-
|
|
774
|
+
processPendingChange() {
|
|
775
|
+
if (this.pendingChanges.length === 0) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const { newValue, path, id } = this.pendingChanges[0];
|
|
779
|
+
let { newErrorSchema } = this.pendingChanges[0];
|
|
707
780
|
const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;
|
|
708
|
-
const { schemaUtils, schema } = this.state;
|
|
781
|
+
const { formData: oldFormData, schemaUtils, schema, errorSchema, fieldPathId } = this.state;
|
|
782
|
+
const rootPathId = fieldPathId.path[0] || '';
|
|
709
783
|
|
|
784
|
+
const isRootPath = !path || path.length === 0 || (path.length === 1 && path[0] === rootPathId);
|
|
710
785
|
let retrievedSchema = this.state.retrievedSchema;
|
|
786
|
+
let formData = isRootPath ? newValue : _cloneDeep(oldFormData);
|
|
711
787
|
if (isObject(formData) || Array.isArray(formData)) {
|
|
712
|
-
|
|
788
|
+
if (!isRootPath) {
|
|
789
|
+
// If the newValue is not on the root path, then set it into the form data
|
|
790
|
+
_set(formData, path, newValue);
|
|
791
|
+
}
|
|
792
|
+
// Pass true to skip live validation in `getStateFromProps()` since we will do it a bit later
|
|
793
|
+
const newState = this.getStateFromProps(this.props, formData, undefined, undefined, undefined, true);
|
|
713
794
|
formData = newState.formData;
|
|
714
795
|
retrievedSchema = newState.retrievedSchema;
|
|
715
796
|
}
|
|
@@ -725,7 +806,15 @@ export default class Form<
|
|
|
725
806
|
};
|
|
726
807
|
}
|
|
727
808
|
|
|
728
|
-
if
|
|
809
|
+
// First update the value in the newErrorSchema in a copy of the old error schema if it was specified and the path
|
|
810
|
+
// is not the root
|
|
811
|
+
if (newErrorSchema && !isRootPath) {
|
|
812
|
+
const errorSchemaCopy = _cloneDeep(errorSchema);
|
|
813
|
+
_set(errorSchemaCopy, path, newErrorSchema);
|
|
814
|
+
newErrorSchema = errorSchemaCopy;
|
|
815
|
+
}
|
|
816
|
+
// If there are pending changes in the queue, skip live validation since it will happen with the last change
|
|
817
|
+
if (mustValidate && this.pendingChanges.length === 1) {
|
|
729
818
|
const schemaValidation = this.validate(newFormData, schema, schemaUtils, retrievedSchema);
|
|
730
819
|
let errors = schemaValidation.errors;
|
|
731
820
|
let errorSchema = schemaValidation.errorSchema;
|
|
@@ -749,6 +838,7 @@ export default class Form<
|
|
|
749
838
|
schemaValidationErrorSchema,
|
|
750
839
|
};
|
|
751
840
|
} else if (!noValidate && newErrorSchema) {
|
|
841
|
+
// Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors.
|
|
752
842
|
const errorSchema = extraErrors
|
|
753
843
|
? (mergeObjects(newErrorSchema, extraErrors, 'preventDuplicates') as ErrorSchema<T>)
|
|
754
844
|
: newErrorSchema;
|
|
@@ -758,8 +848,15 @@ export default class Form<
|
|
|
758
848
|
errors: toErrorList(errorSchema),
|
|
759
849
|
};
|
|
760
850
|
}
|
|
761
|
-
this.setState(state as FormState<T, S, F>, () =>
|
|
762
|
-
|
|
851
|
+
this.setState(state as FormState<T, S, F>, () => {
|
|
852
|
+
if (onChange) {
|
|
853
|
+
onChange({ ...this.state, ...state }, id);
|
|
854
|
+
}
|
|
855
|
+
// Now remove the change we just completed and call this again
|
|
856
|
+
this.pendingChanges.shift();
|
|
857
|
+
this.processPendingChange();
|
|
858
|
+
});
|
|
859
|
+
}
|
|
763
860
|
|
|
764
861
|
/**
|
|
765
862
|
* If the retrievedSchema has changed the new retrievedSchema is returned.
|
|
@@ -866,10 +963,28 @@ export default class Form<
|
|
|
866
963
|
}
|
|
867
964
|
};
|
|
868
965
|
|
|
966
|
+
/** Extracts the `GlobalFormOptions` from the given Form `props`
|
|
967
|
+
*
|
|
968
|
+
* @param props - The form props to extract the global form options from
|
|
969
|
+
* @returns - The `GlobalFormOptions` from the props
|
|
970
|
+
* @private
|
|
971
|
+
*/
|
|
972
|
+
private getGlobalFormOptions(props: FormProps<T, S, F>): GlobalFormOptions {
|
|
973
|
+
const {
|
|
974
|
+
uiSchema = {},
|
|
975
|
+
experimental_componentUpdateStrategy,
|
|
976
|
+
idSeparator = DEFAULT_ID_SEPARATOR,
|
|
977
|
+
idPrefix = DEFAULT_ID_PREFIX,
|
|
978
|
+
} = props;
|
|
979
|
+
const rootFieldId = uiSchema['ui:rootFieldId'];
|
|
980
|
+
// Omit any options that are undefined or null
|
|
981
|
+
return { idPrefix: rootFieldId || idPrefix, idSeparator, experimental_componentUpdateStrategy };
|
|
982
|
+
}
|
|
983
|
+
|
|
869
984
|
/** Returns the registry for the form */
|
|
870
985
|
getRegistry(): Registry<T, S, F> {
|
|
871
986
|
const { translateString: customTranslateString, uiSchema = {} } = this.props;
|
|
872
|
-
const { schemaUtils } = this.state;
|
|
987
|
+
const { schema, schemaUtils } = this.state;
|
|
873
988
|
const { fields, templates, widgets, formContext, translateString } = getDefaultRegistry<T, S, F>();
|
|
874
989
|
return {
|
|
875
990
|
fields: { ...fields, ...this.props.fields },
|
|
@@ -882,11 +997,12 @@ export default class Form<
|
|
|
882
997
|
},
|
|
883
998
|
},
|
|
884
999
|
widgets: { ...widgets, ...this.props.widgets },
|
|
885
|
-
rootSchema:
|
|
1000
|
+
rootSchema: schema,
|
|
886
1001
|
formContext: this.props.formContext || formContext,
|
|
887
1002
|
schemaUtils,
|
|
888
1003
|
translateString: customTranslateString || translateString,
|
|
889
1004
|
globalUiOptions: uiSchema[UI_GLOBAL_OPTIONS_KEY],
|
|
1005
|
+
globalFormOptions: this.getGlobalFormOptions(this.props),
|
|
890
1006
|
};
|
|
891
1007
|
}
|
|
892
1008
|
|
|
@@ -1011,8 +1127,6 @@ export default class Form<
|
|
|
1011
1127
|
const {
|
|
1012
1128
|
children,
|
|
1013
1129
|
id,
|
|
1014
|
-
idPrefix,
|
|
1015
|
-
idSeparator,
|
|
1016
1130
|
className = '',
|
|
1017
1131
|
tagName,
|
|
1018
1132
|
name,
|
|
@@ -1025,12 +1139,11 @@ export default class Form<
|
|
|
1025
1139
|
noHtml5Validate = false,
|
|
1026
1140
|
disabled,
|
|
1027
1141
|
readonly,
|
|
1028
|
-
formContext,
|
|
1029
1142
|
showErrorList = 'top',
|
|
1030
1143
|
_internalFormWrapper,
|
|
1031
1144
|
} = this.props;
|
|
1032
1145
|
|
|
1033
|
-
const { schema, uiSchema, formData, errorSchema,
|
|
1146
|
+
const { schema, uiSchema, formData, errorSchema, fieldPathId } = this.state;
|
|
1034
1147
|
const registry = this.getRegistry();
|
|
1035
1148
|
const { SchemaField: _SchemaField } = registry.fields;
|
|
1036
1149
|
const { SubmitButton } = registry.templates.ButtonTemplates;
|
|
@@ -1068,10 +1181,7 @@ export default class Form<
|
|
|
1068
1181
|
schema={schema}
|
|
1069
1182
|
uiSchema={uiSchema}
|
|
1070
1183
|
errorSchema={errorSchema}
|
|
1071
|
-
|
|
1072
|
-
idPrefix={idPrefix}
|
|
1073
|
-
idSeparator={idSeparator}
|
|
1074
|
-
formContext={formContext}
|
|
1184
|
+
fieldPathId={fieldPathId}
|
|
1075
1185
|
formData={formData}
|
|
1076
1186
|
onChange={this.onChange}
|
|
1077
1187
|
onBlur={this.onBlur}
|