@rjsf/utils 5.11.2 → 5.12.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/dist/index.js +2544 -5
- package/dist/index.js.map +7 -0
- package/dist/utils.esm.js +1228 -2113
- package/dist/utils.esm.js.map +7 -1
- package/dist/utils.umd.js +2414 -0
- package/lib/ErrorSchemaBuilder.d.ts +60 -0
- package/lib/ErrorSchemaBuilder.js +103 -0
- package/lib/ErrorSchemaBuilder.js.map +1 -0
- package/lib/allowAdditionalItems.d.ts +8 -0
- package/lib/allowAdditionalItems.js +14 -0
- package/lib/allowAdditionalItems.js.map +1 -0
- package/lib/asNumber.d.ts +10 -0
- package/lib/asNumber.js +36 -0
- package/lib/asNumber.js.map +1 -0
- package/lib/canExpand.d.ts +11 -0
- package/lib/canExpand.js +26 -0
- package/lib/canExpand.js.map +1 -0
- package/lib/constants.d.ts +31 -0
- package/lib/constants.js +32 -0
- package/lib/constants.js.map +1 -0
- package/lib/createErrorHandler.d.ts +7 -0
- package/lib/createErrorHandler.js +31 -0
- package/lib/createErrorHandler.js.map +1 -0
- package/lib/createSchemaUtils.d.ts +10 -0
- package/lib/createSchemaUtils.js +207 -0
- package/lib/createSchemaUtils.js.map +1 -0
- package/lib/dataURItoBlob.d.ts +16 -0
- package/lib/dataURItoBlob.js +43 -0
- package/lib/dataURItoBlob.js.map +1 -0
- package/lib/deepEquals.d.ts +8 -0
- package/lib/deepEquals.js +19 -0
- package/lib/deepEquals.js.map +1 -0
- package/lib/englishStringTranslator.d.ts +10 -0
- package/lib/englishStringTranslator.js +13 -0
- package/lib/englishStringTranslator.js.map +1 -0
- package/lib/enumOptionsDeselectValue.d.ts +14 -0
- package/lib/enumOptionsDeselectValue.js +22 -0
- package/lib/enumOptionsDeselectValue.js.map +1 -0
- package/lib/enumOptionsIndexForValue.d.ts +13 -0
- package/lib/enumOptionsIndexForValue.js +22 -0
- package/lib/enumOptionsIndexForValue.js.map +1 -0
- package/lib/enumOptionsIsSelected.d.ts +8 -0
- package/lib/enumOptionsIsSelected.js +14 -0
- package/lib/enumOptionsIsSelected.js.map +1 -0
- package/lib/enumOptionsSelectValue.d.ts +10 -0
- package/lib/enumOptionsSelectValue.js +23 -0
- package/lib/enumOptionsSelectValue.js.map +1 -0
- package/lib/enumOptionsValueForIndex.d.ts +13 -0
- package/lib/enumOptionsValueForIndex.js +21 -0
- package/lib/enumOptionsValueForIndex.js.map +1 -0
- package/lib/enums.d.ts +72 -0
- package/lib/enums.js +76 -0
- package/lib/enums.js.map +1 -0
- package/lib/findSchemaDefinition.d.ts +20 -0
- package/lib/findSchemaDefinition.js +49 -0
- package/lib/findSchemaDefinition.js.map +1 -0
- package/lib/getDiscriminatorFieldFromSchema.d.ts +8 -0
- package/lib/getDiscriminatorFieldFromSchema.js +20 -0
- package/lib/getDiscriminatorFieldFromSchema.js.map +1 -0
- package/lib/getInputProps.d.ts +10 -0
- package/lib/getInputProps.js +41 -0
- package/lib/getInputProps.js.map +1 -0
- package/lib/getSchemaType.d.ts +13 -0
- package/lib/getSchemaType.js +29 -0
- package/lib/getSchemaType.js.map +1 -0
- package/lib/getSubmitButtonOptions.d.ts +10 -0
- package/lib/getSubmitButtonOptions.js +25 -0
- package/lib/getSubmitButtonOptions.js.map +1 -0
- package/lib/getTemplate.d.ts +10 -0
- package/lib/getTemplate.js +19 -0
- package/lib/getTemplate.js.map +1 -0
- package/lib/getUiOptions.d.ts +9 -0
- package/lib/getUiOptions.js +25 -0
- package/lib/getUiOptions.js.map +1 -0
- package/lib/getWidget.d.ts +13 -0
- package/lib/getWidget.js +118 -0
- package/lib/getWidget.js.map +1 -0
- package/lib/guessType.d.ts +7 -0
- package/lib/guessType.js +29 -0
- package/lib/guessType.js.map +1 -0
- package/lib/hasWidget.d.ts +10 -0
- package/lib/hasWidget.js +23 -0
- package/lib/hasWidget.js.map +1 -0
- package/lib/hashForSchema.d.ts +8 -0
- package/lib/hashForSchema.js +29 -0
- package/lib/hashForSchema.js.map +1 -0
- package/lib/idGenerators.d.ts +47 -0
- package/lib/idGenerators.js +73 -0
- package/lib/idGenerators.js.map +1 -0
- package/lib/index.d.ts +57 -0
- package/lib/index.js +58 -0
- package/lib/index.js.map +1 -0
- package/lib/isConstant.d.ts +8 -0
- package/lib/isConstant.js +11 -0
- package/lib/isConstant.js.map +1 -0
- package/lib/isCustomWidget.d.ts +7 -0
- package/lib/isCustomWidget.js +13 -0
- package/lib/isCustomWidget.js.map +1 -0
- package/lib/isFixedItems.d.ts +8 -0
- package/lib/isFixedItems.js +11 -0
- package/lib/isFixedItems.js.map +1 -0
- package/lib/isObject.d.ts +7 -0
- package/lib/isObject.js +16 -0
- package/lib/isObject.js.map +1 -0
- package/lib/labelValue.d.ts +13 -0
- package/lib/labelValue.js +4 -0
- package/lib/labelValue.js.map +1 -0
- package/lib/localToUTC.d.ts +6 -0
- package/lib/localToUTC.js +9 -0
- package/lib/localToUTC.js.map +1 -0
- package/lib/mergeDefaultsWithFormData.d.ts +17 -0
- package/lib/mergeDefaultsWithFormData.js +43 -0
- package/lib/mergeDefaultsWithFormData.js.map +1 -0
- package/lib/mergeObjects.d.ts +11 -0
- package/lib/mergeObjects.js +35 -0
- package/lib/mergeObjects.js.map +1 -0
- package/lib/mergeSchemas.d.ts +10 -0
- package/lib/mergeSchemas.js +35 -0
- package/lib/mergeSchemas.js.map +1 -0
- package/lib/optionsList.d.ts +10 -0
- package/lib/optionsList.js +36 -0
- package/lib/optionsList.js.map +1 -0
- package/lib/orderProperties.d.ts +11 -0
- package/lib/orderProperties.js +38 -0
- package/lib/orderProperties.js.map +1 -0
- package/lib/pad.d.ts +7 -0
- package/lib/pad.js +14 -0
- package/lib/pad.js.map +1 -0
- package/lib/parseDateString.d.ts +9 -0
- package/lib/parseDateString.js +32 -0
- package/lib/parseDateString.js.map +1 -0
- package/lib/parser/ParserValidator.d.ts +70 -0
- package/lib/parser/ParserValidator.js +93 -0
- package/lib/parser/ParserValidator.js.map +1 -0
- package/lib/parser/index.d.ts +4 -0
- package/lib/parser/index.js +3 -0
- package/lib/parser/index.js.map +1 -0
- package/lib/parser/schemaParser.d.ts +9 -0
- package/lib/parser/schemaParser.js +48 -0
- package/lib/parser/schemaParser.js.map +1 -0
- package/lib/rangeSpec.d.ts +9 -0
- package/lib/rangeSpec.js +20 -0
- package/lib/rangeSpec.js.map +1 -0
- package/lib/replaceStringParameters.d.ts +9 -0
- package/lib/replaceStringParameters.js +23 -0
- package/lib/replaceStringParameters.js.map +1 -0
- package/lib/schema/getClosestMatchingOption.d.ts +49 -0
- package/lib/schema/getClosestMatchingOption.js +154 -0
- package/lib/schema/getClosestMatchingOption.js.map +1 -0
- package/lib/schema/getDefaultFormState.d.ts +66 -0
- package/lib/schema/getDefaultFormState.js +351 -0
- package/lib/schema/getDefaultFormState.js.map +1 -0
- package/lib/schema/getDisplayLabel.d.ts +12 -0
- package/lib/schema/getDisplayLabel.js +39 -0
- package/lib/schema/getDisplayLabel.js.map +1 -0
- package/lib/schema/getFirstMatchingOption.d.ts +13 -0
- package/lib/schema/getFirstMatchingOption.js +16 -0
- package/lib/schema/getFirstMatchingOption.js.map +1 -0
- package/lib/schema/getMatchingOption.d.ts +14 -0
- package/lib/schema/getMatchingOption.js +80 -0
- package/lib/schema/getMatchingOption.js.map +1 -0
- package/lib/schema/index.d.ts +14 -0
- package/lib/schema/index.js +15 -0
- package/lib/schema/index.js.map +1 -0
- package/lib/schema/isFilesArray.d.ts +10 -0
- package/lib/schema/isFilesArray.js +21 -0
- package/lib/schema/isFilesArray.js.map +1 -0
- package/lib/schema/isMultiSelect.d.ts +9 -0
- package/lib/schema/isMultiSelect.js +15 -0
- package/lib/schema/isMultiSelect.js.map +1 -0
- package/lib/schema/isSelect.d.ts +9 -0
- package/lib/schema/isSelect.js +21 -0
- package/lib/schema/isSelect.js.map +1 -0
- package/lib/schema/mergeValidationData.d.ts +14 -0
- package/lib/schema/mergeValidationData.js +28 -0
- package/lib/schema/mergeValidationData.js.map +1 -0
- package/lib/schema/retrieveSchema.d.ts +170 -0
- package/lib/schema/retrieveSchema.js +437 -0
- package/lib/schema/retrieveSchema.js.map +1 -0
- package/lib/schema/sanitizeDataForNewSchema.d.ts +49 -0
- package/lib/schema/sanitizeDataForNewSchema.js +173 -0
- package/lib/schema/sanitizeDataForNewSchema.js.map +1 -0
- package/lib/schema/toIdSchema.d.ts +13 -0
- package/lib/schema/toIdSchema.js +59 -0
- package/lib/schema/toIdSchema.js.map +1 -0
- package/lib/schema/toPathSchema.d.ts +11 -0
- package/lib/schema/toPathSchema.js +68 -0
- package/lib/schema/toPathSchema.js.map +1 -0
- package/lib/schemaRequiresTrueValue.d.ts +11 -0
- package/lib/schemaRequiresTrueValue.js +34 -0
- package/lib/schemaRequiresTrueValue.js.map +1 -0
- package/lib/shouldRender.d.ts +10 -0
- package/lib/shouldRender.js +14 -0
- package/lib/shouldRender.js.map +1 -0
- package/lib/toConstant.d.ts +9 -0
- package/lib/toConstant.js +18 -0
- package/lib/toConstant.js.map +1 -0
- package/lib/toDateString.d.ts +9 -0
- package/lib/toDateString.js +14 -0
- package/lib/toDateString.js.map +1 -0
- package/lib/toErrorList.d.ts +8 -0
- package/lib/toErrorList.js +34 -0
- package/lib/toErrorList.js.map +1 -0
- package/lib/toErrorSchema.d.ts +21 -0
- package/lib/toErrorSchema.js +41 -0
- package/lib/toErrorSchema.js.map +1 -0
- package/lib/types.d.ts +982 -0
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -0
- package/lib/unwrapErrorHandler.d.ts +7 -0
- package/lib/unwrapErrorHandler.js +21 -0
- package/lib/unwrapErrorHandler.js.map +1 -0
- package/lib/utcToLocal.d.ts +6 -0
- package/lib/utcToLocal.js +26 -0
- package/lib/utcToLocal.js.map +1 -0
- package/lib/validationDataMerge.d.ts +11 -0
- package/lib/validationDataMerge.js +26 -0
- package/lib/validationDataMerge.js.map +1 -0
- package/lib/withIdRefPrefix.d.ts +8 -0
- package/lib/withIdRefPrefix.js +47 -0
- package/lib/withIdRefPrefix.js.map +1 -0
- package/package.json +20 -13
- package/src/ErrorSchemaBuilder.ts +112 -0
- package/src/allowAdditionalItems.ts +15 -0
- package/src/asNumber.ts +38 -0
- package/src/canExpand.ts +31 -0
- package/src/constants.ts +31 -0
- package/src/createErrorHandler.ts +33 -0
- package/src/createSchemaUtils.ts +298 -0
- package/src/dataURItoBlob.ts +42 -0
- package/src/deepEquals.ts +19 -0
- package/src/englishStringTranslator.ts +14 -0
- package/src/enumOptionsDeselectValue.ts +28 -0
- package/src/enumOptionsIndexForValue.ts +27 -0
- package/src/enumOptionsIsSelected.ts +19 -0
- package/src/enumOptionsSelectValue.ts +28 -0
- package/src/enumOptionsValueForIndex.ts +26 -0
- package/src/enums.ts +74 -0
- package/src/findSchemaDefinition.ts +54 -0
- package/src/getDiscriminatorFieldFromSchema.ts +21 -0
- package/src/getInputProps.ts +55 -0
- package/src/getSchemaType.ts +37 -0
- package/src/getSubmitButtonOptions.ts +32 -0
- package/src/getTemplate.ts +26 -0
- package/src/getUiOptions.ts +32 -0
- package/src/getWidget.tsx +133 -0
- package/src/guessType.ts +28 -0
- package/src/hasWidget.ts +27 -0
- package/src/hashForSchema.ts +31 -0
- package/src/idGenerators.ts +81 -0
- package/src/index.ts +118 -0
- package/src/isConstant.ts +12 -0
- package/src/isCustomWidget.ts +19 -0
- package/src/isFixedItems.ts +12 -0
- package/src/isObject.ts +15 -0
- package/src/labelValue.ts +16 -0
- package/src/localToUTC.ts +8 -0
- package/src/mergeDefaultsWithFormData.ts +53 -0
- package/src/mergeObjects.ts +39 -0
- package/src/mergeSchemas.ts +38 -0
- package/src/optionsList.ts +41 -0
- package/src/orderProperties.ts +44 -0
- package/src/pad.ts +13 -0
- package/src/parseDateString.ts +33 -0
- package/src/parser/ParserValidator.ts +132 -0
- package/src/parser/index.ts +6 -0
- package/src/parser/schemaParser.ts +60 -0
- package/src/rangeSpec.ts +22 -0
- package/src/replaceStringParameters.ts +22 -0
- package/src/schema/getClosestMatchingOption.ts +191 -0
- package/src/schema/getDefaultFormState.ts +447 -0
- package/src/schema/getDisplayLabel.ts +59 -0
- package/src/schema/getFirstMatchingOption.ts +27 -0
- package/src/schema/getMatchingOption.ts +95 -0
- package/src/schema/index.ts +29 -0
- package/src/schema/isFilesArray.ts +27 -0
- package/src/schema/isMultiSelect.ts +21 -0
- package/src/schema/isSelect.ts +26 -0
- package/src/schema/mergeValidationData.ts +38 -0
- package/src/schema/retrieveSchema.ts +614 -0
- package/src/schema/sanitizeDataForNewSchema.ts +197 -0
- package/src/schema/toIdSchema.ts +105 -0
- package/src/schema/toPathSchema.ts +121 -0
- package/src/schemaRequiresTrueValue.ts +40 -0
- package/src/shouldRender.ts +16 -0
- package/src/toConstant.ts +19 -0
- package/src/toDateString.ts +15 -0
- package/src/toErrorList.ts +41 -0
- package/src/toErrorSchema.ts +43 -0
- package/src/types.ts +1139 -0
- package/src/unwrapErrorHandler.ts +25 -0
- package/src/utcToLocal.ts +30 -0
- package/src/validationDataMerge.ts +31 -0
- package/src/withIdRefPrefix.ts +49 -0
- package/dist/index.d.ts +0 -1911
- package/dist/utils.cjs.development.js +0 -3522
- package/dist/utils.cjs.development.js.map +0 -1
- package/dist/utils.cjs.production.min.js +0 -2
- package/dist/utils.cjs.production.min.js.map +0 -1
- package/dist/utils.umd.development.js +0 -3504
- package/dist/utils.umd.development.js.map +0 -1
- package/dist/utils.umd.production.min.js +0 -2
- package/dist/utils.umd.production.min.js.map +0 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
import has from 'lodash/has';
|
|
3
|
+
import isObject from 'lodash/isObject';
|
|
4
|
+
import isString from 'lodash/isString';
|
|
5
|
+
import reduce from 'lodash/reduce';
|
|
6
|
+
import times from 'lodash/times';
|
|
7
|
+
|
|
8
|
+
import getFirstMatchingOption from './getFirstMatchingOption';
|
|
9
|
+
import retrieveSchema, { resolveAllReferences } from './retrieveSchema';
|
|
10
|
+
import { ONE_OF_KEY, REF_KEY, JUNK_OPTION_ID, ANY_OF_KEY } from '../constants';
|
|
11
|
+
import guessType from '../guessType';
|
|
12
|
+
import { FormContextType, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types';
|
|
13
|
+
import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
|
|
14
|
+
|
|
15
|
+
/** A junk option used to determine when the getFirstMatchingOption call really matches an option rather than returning
|
|
16
|
+
* the first item
|
|
17
|
+
*/
|
|
18
|
+
export const JUNK_OPTION: StrictRJSFSchema = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
$id: JUNK_OPTION_ID,
|
|
21
|
+
properties: {
|
|
22
|
+
__not_really_there__: {
|
|
23
|
+
type: 'number',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Recursive function that calculates the score of a `formData` against the given `schema`. The computation is fairly
|
|
29
|
+
* simple. Initially the total score is 0. When `schema.properties` object exists, then all the `key/value` pairs within
|
|
30
|
+
* the object are processed as follows after obtaining the formValue from `formData` using the `key`:
|
|
31
|
+
* - If the `value` contains a `$ref`, `calculateIndexScore()` is called recursively with the formValue and the new
|
|
32
|
+
* schema that is the result of the ref in the schema being resolved and that sub-schema's resulting score is added to
|
|
33
|
+
* the total.
|
|
34
|
+
* - If the `value` contains a `oneOf` and there is a formValue, then score based on the index returned from calling
|
|
35
|
+
* `getClosestMatchingOption()` of that oneOf.
|
|
36
|
+
* - If the type of the `value` is 'object', `calculateIndexScore()` is called recursively with the formValue and the
|
|
37
|
+
* `value` itself as the sub-schema, and the score is added to the total.
|
|
38
|
+
* - If the type of the `value` matches the guessed-type of the `formValue`, the score is incremented by 1, UNLESS the
|
|
39
|
+
* value has a `default` or `const`. In those case, if the `default` or `const` and the `formValue` match, the score
|
|
40
|
+
* is incremented by another 1 otherwise it is decremented by 1.
|
|
41
|
+
*
|
|
42
|
+
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
|
|
43
|
+
* @param rootSchema - The root JSON schema of the entire form
|
|
44
|
+
* @param schema - The schema for which the score is being calculated
|
|
45
|
+
* @param formData - The form data associated with the schema, used to calculate the score
|
|
46
|
+
* @returns - The score a schema against the formData
|
|
47
|
+
*/
|
|
48
|
+
export function calculateIndexScore<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
|
|
49
|
+
validator: ValidatorType<T, S, F>,
|
|
50
|
+
rootSchema: S,
|
|
51
|
+
schema?: S,
|
|
52
|
+
formData: any = {}
|
|
53
|
+
): number {
|
|
54
|
+
let totalScore = 0;
|
|
55
|
+
if (schema) {
|
|
56
|
+
if (isObject(schema.properties)) {
|
|
57
|
+
totalScore += reduce(
|
|
58
|
+
schema.properties,
|
|
59
|
+
(score, value, key) => {
|
|
60
|
+
const formValue = get(formData, key);
|
|
61
|
+
if (typeof value === 'boolean') {
|
|
62
|
+
return score;
|
|
63
|
+
}
|
|
64
|
+
if (has(value, REF_KEY)) {
|
|
65
|
+
const newSchema = retrieveSchema<T, S, F>(validator, value as S, rootSchema, formValue);
|
|
66
|
+
return score + calculateIndexScore<T, S, F>(validator, rootSchema, newSchema, formValue || {});
|
|
67
|
+
}
|
|
68
|
+
if ((has(value, ONE_OF_KEY) || has(value, ANY_OF_KEY)) && formValue) {
|
|
69
|
+
const key = has(value, ONE_OF_KEY) ? ONE_OF_KEY : ANY_OF_KEY;
|
|
70
|
+
const discriminator = getDiscriminatorFieldFromSchema<S>(value as S);
|
|
71
|
+
return (
|
|
72
|
+
score +
|
|
73
|
+
getClosestMatchingOption<T, S, F>(
|
|
74
|
+
validator,
|
|
75
|
+
rootSchema,
|
|
76
|
+
formValue,
|
|
77
|
+
get(value, key) as S[],
|
|
78
|
+
-1,
|
|
79
|
+
discriminator
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (value.type === 'object') {
|
|
84
|
+
return score + calculateIndexScore<T, S, F>(validator, rootSchema, value as S, formValue || {});
|
|
85
|
+
}
|
|
86
|
+
if (value.type === guessType(formValue)) {
|
|
87
|
+
// If the types match, then we bump the score by one
|
|
88
|
+
let newScore = score + 1;
|
|
89
|
+
if (value.default) {
|
|
90
|
+
// If the schema contains a readonly default value score the value that matches the default higher and
|
|
91
|
+
// any non-matching value lower
|
|
92
|
+
newScore += formValue === value.default ? 1 : -1;
|
|
93
|
+
} else if (value.const) {
|
|
94
|
+
// If the schema contains a const value score the value that matches the default higher and
|
|
95
|
+
// any non-matching value lower
|
|
96
|
+
newScore += formValue === value.const ? 1 : -1;
|
|
97
|
+
}
|
|
98
|
+
// TODO eventually, deal with enums/arrays
|
|
99
|
+
return newScore;
|
|
100
|
+
}
|
|
101
|
+
return score;
|
|
102
|
+
},
|
|
103
|
+
0
|
|
104
|
+
);
|
|
105
|
+
} else if (isString(schema.type) && schema.type === guessType(formData)) {
|
|
106
|
+
totalScore += 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return totalScore;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Determines which of the given `options` provided most closely matches the `formData`. Using
|
|
113
|
+
* `getFirstMatchingOption()` to match two schemas that differ only by the readOnly, default or const value of a field
|
|
114
|
+
* based on the `formData` and returns 0 when there is no match. Rather than passing in all the `options` at once to
|
|
115
|
+
* this utility, instead an array of valid option indexes is created by iterating over the list of options, call
|
|
116
|
+
* `getFirstMatchingOptions` with a list of one junk option and one good option, seeing if the good option is considered
|
|
117
|
+
* matched.
|
|
118
|
+
*
|
|
119
|
+
* Once the list of valid indexes is created, if there is only one valid index, just return it. Otherwise, if there are
|
|
120
|
+
* no valid indexes, then fill the valid indexes array with the indexes of all the options. Next, the index of the
|
|
121
|
+
* option with the highest score is determined by iterating over the list of valid options, calling
|
|
122
|
+
* `calculateIndexScore()` on each, comparing it against the current best score, and returning the index of the one that
|
|
123
|
+
* eventually has the best score.
|
|
124
|
+
*
|
|
125
|
+
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
|
|
126
|
+
* @param rootSchema - The root JSON schema of the entire form
|
|
127
|
+
* @param formData - The form data associated with the schema
|
|
128
|
+
* @param options - The list of options that can be selected from
|
|
129
|
+
* @param [selectedOption=-1] - The index of the currently selected option, defaulted to -1 if not specified
|
|
130
|
+
* @param [discriminatorField] - The optional name of the field within the options object whose value is used to
|
|
131
|
+
* determine which option is selected
|
|
132
|
+
* @returns - The index of the option that is the closest match to the `formData` or the `selectedOption` if no match
|
|
133
|
+
*/
|
|
134
|
+
export default function getClosestMatchingOption<
|
|
135
|
+
T = any,
|
|
136
|
+
S extends StrictRJSFSchema = RJSFSchema,
|
|
137
|
+
F extends FormContextType = any
|
|
138
|
+
>(
|
|
139
|
+
validator: ValidatorType<T, S, F>,
|
|
140
|
+
rootSchema: S,
|
|
141
|
+
formData: T | undefined,
|
|
142
|
+
options: S[],
|
|
143
|
+
selectedOption = -1,
|
|
144
|
+
discriminatorField?: string
|
|
145
|
+
): number {
|
|
146
|
+
// First resolve any refs in the options
|
|
147
|
+
const resolvedOptions = options.map((option) => {
|
|
148
|
+
return resolveAllReferences(option, rootSchema);
|
|
149
|
+
});
|
|
150
|
+
// Reduce the array of options down to a list of the indexes that are considered matching options
|
|
151
|
+
const allValidIndexes = resolvedOptions.reduce((validList: number[], option, index: number) => {
|
|
152
|
+
const testOptions: S[] = [JUNK_OPTION as S, option];
|
|
153
|
+
const match = getFirstMatchingOption<T, S, F>(validator, formData, testOptions, rootSchema, discriminatorField);
|
|
154
|
+
// The match is the real option, so add its index to list of valid indexes
|
|
155
|
+
if (match === 1) {
|
|
156
|
+
validList.push(index);
|
|
157
|
+
}
|
|
158
|
+
return validList;
|
|
159
|
+
}, []);
|
|
160
|
+
|
|
161
|
+
// There is only one valid index, so return it!
|
|
162
|
+
if (allValidIndexes.length === 1) {
|
|
163
|
+
return allValidIndexes[0];
|
|
164
|
+
}
|
|
165
|
+
if (!allValidIndexes.length) {
|
|
166
|
+
// No indexes were valid, so we'll score all the options, add all the indexes
|
|
167
|
+
times(resolvedOptions.length, (i) => allValidIndexes.push(i));
|
|
168
|
+
}
|
|
169
|
+
type BestType = { bestIndex: number; bestScore: number };
|
|
170
|
+
const scoreCount = new Set<number>();
|
|
171
|
+
// Score all the options in the list of valid indexes and return the index with the best score
|
|
172
|
+
const { bestIndex }: BestType = allValidIndexes.reduce(
|
|
173
|
+
(scoreData: BestType, index: number) => {
|
|
174
|
+
const { bestScore } = scoreData;
|
|
175
|
+
const option = resolvedOptions[index];
|
|
176
|
+
const score = calculateIndexScore(validator, rootSchema, option, formData);
|
|
177
|
+
scoreCount.add(score);
|
|
178
|
+
if (score > bestScore) {
|
|
179
|
+
return { bestIndex: index, bestScore: score };
|
|
180
|
+
}
|
|
181
|
+
return scoreData;
|
|
182
|
+
},
|
|
183
|
+
{ bestIndex: selectedOption, bestScore: 0 }
|
|
184
|
+
);
|
|
185
|
+
// if all scores are the same go with selectedOption
|
|
186
|
+
if (scoreCount.size === 1 && selectedOption >= 0) {
|
|
187
|
+
return selectedOption;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return bestIndex;
|
|
191
|
+
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
import isEmpty from 'lodash/isEmpty';
|
|
3
|
+
|
|
4
|
+
import { ANY_OF_KEY, DEFAULT_KEY, DEPENDENCIES_KEY, PROPERTIES_KEY, ONE_OF_KEY, REF_KEY } from '../constants';
|
|
5
|
+
import findSchemaDefinition from '../findSchemaDefinition';
|
|
6
|
+
import getClosestMatchingOption from './getClosestMatchingOption';
|
|
7
|
+
import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
|
|
8
|
+
import getSchemaType from '../getSchemaType';
|
|
9
|
+
import isObject from '../isObject';
|
|
10
|
+
import isFixedItems from '../isFixedItems';
|
|
11
|
+
import mergeDefaultsWithFormData from '../mergeDefaultsWithFormData';
|
|
12
|
+
import mergeObjects from '../mergeObjects';
|
|
13
|
+
import mergeSchemas from '../mergeSchemas';
|
|
14
|
+
import {
|
|
15
|
+
Experimental_DefaultFormStateBehavior,
|
|
16
|
+
FormContextType,
|
|
17
|
+
GenericObjectType,
|
|
18
|
+
RJSFSchema,
|
|
19
|
+
StrictRJSFSchema,
|
|
20
|
+
ValidatorType,
|
|
21
|
+
} from '../types';
|
|
22
|
+
import isMultiSelect from './isMultiSelect';
|
|
23
|
+
import retrieveSchema, { resolveDependencies } from './retrieveSchema';
|
|
24
|
+
|
|
25
|
+
/** Enum that indicates how `schema.additionalItems` should be handled by the `getInnerSchemaForArrayItem()` function.
|
|
26
|
+
*/
|
|
27
|
+
export enum AdditionalItemsHandling {
|
|
28
|
+
Ignore,
|
|
29
|
+
Invert,
|
|
30
|
+
Fallback,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Given a `schema` will return an inner schema that for an array item. This is computed differently based on the
|
|
34
|
+
* `additionalItems` enum and the value of `idx`. There are four possible returns:
|
|
35
|
+
* 1. If `idx` is >= 0, then if `schema.items` is an array the `idx`th element of the array is returned if it is a valid
|
|
36
|
+
* index and not a boolean, otherwise it falls through to 3.
|
|
37
|
+
* 2. If `schema.items` is not an array AND truthy and not a boolean, then `schema.items` is returned since it actually
|
|
38
|
+
* is a schema, otherwise it falls through to 3.
|
|
39
|
+
* 3. If `additionalItems` is not `AdditionalItemsHandling.Ignore` and `schema.additionalItems` is an object, then
|
|
40
|
+
* `schema.additionalItems` is returned since it actually is a schema, otherwise it falls through to 4.
|
|
41
|
+
* 4. {} is returned representing an empty schema
|
|
42
|
+
*
|
|
43
|
+
* @param schema - The schema from which to get the particular item
|
|
44
|
+
* @param [additionalItems=AdditionalItemsHandling.Ignore] - How do we want to handle additional items?
|
|
45
|
+
* @param [idx=-1] - Index, if non-negative, will be used to return the idx-th element in a `schema.items` array
|
|
46
|
+
* @returns - The best fit schema object from the `schema` given the `additionalItems` and `idx` modifiers
|
|
47
|
+
*/
|
|
48
|
+
export function getInnerSchemaForArrayItem<S extends StrictRJSFSchema = RJSFSchema>(
|
|
49
|
+
schema: S,
|
|
50
|
+
additionalItems: AdditionalItemsHandling = AdditionalItemsHandling.Ignore,
|
|
51
|
+
idx = -1
|
|
52
|
+
): S {
|
|
53
|
+
if (idx >= 0) {
|
|
54
|
+
if (Array.isArray(schema.items) && idx < schema.items.length) {
|
|
55
|
+
const item = schema.items[idx];
|
|
56
|
+
if (typeof item !== 'boolean') {
|
|
57
|
+
return item as S;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} else if (schema.items && !Array.isArray(schema.items) && typeof schema.items !== 'boolean') {
|
|
61
|
+
return schema.items as S;
|
|
62
|
+
}
|
|
63
|
+
if (additionalItems !== AdditionalItemsHandling.Ignore && isObject(schema.additionalItems)) {
|
|
64
|
+
return schema.additionalItems as S;
|
|
65
|
+
}
|
|
66
|
+
return {} as S;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Either add `computedDefault` at `key` into `obj` or not add it based on its value, the value of
|
|
70
|
+
* `includeUndefinedValues`, the value of `emptyObjectFields` and if its parent field is required. Generally undefined
|
|
71
|
+
* `computedDefault` values are added only when `includeUndefinedValues` is either true/"excludeObjectChildren". If `
|
|
72
|
+
* includeUndefinedValues` is false and `emptyObjectFields` is not "skipDefaults", then non-undefined and non-empty-object
|
|
73
|
+
* values will be added based on certain conditions.
|
|
74
|
+
*
|
|
75
|
+
* @param obj - The object into which the computed default may be added
|
|
76
|
+
* @param key - The key into the object at which the computed default may be added
|
|
77
|
+
* @param computedDefault - The computed default value that maybe should be added to the obj
|
|
78
|
+
* @param includeUndefinedValues - Optional flag, if true, cause undefined values to be added as defaults.
|
|
79
|
+
* If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as
|
|
80
|
+
* false when computing defaults for any nested object properties. If "allowEmptyObject", prevents undefined
|
|
81
|
+
* values in this object while allow the object itself to be empty and passing `includeUndefinedValues` as
|
|
82
|
+
* false when computing defaults for any nested object properties.
|
|
83
|
+
* @param isParentRequired - The optional boolean that indicates whether the parent field is required
|
|
84
|
+
* @param requiredFields - The list of fields that are required
|
|
85
|
+
* @param experimental_defaultFormStateBehavior - Optional configuration object, if provided, allows users to override
|
|
86
|
+
* default form state behavior
|
|
87
|
+
*/
|
|
88
|
+
function maybeAddDefaultToObject<T = any>(
|
|
89
|
+
obj: GenericObjectType,
|
|
90
|
+
key: string,
|
|
91
|
+
computedDefault: T | T[] | undefined,
|
|
92
|
+
includeUndefinedValues: boolean | 'excludeObjectChildren',
|
|
93
|
+
isParentRequired?: boolean,
|
|
94
|
+
requiredFields: string[] = [],
|
|
95
|
+
experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior = {}
|
|
96
|
+
) {
|
|
97
|
+
const { emptyObjectFields = 'populateAllDefaults' } = experimental_defaultFormStateBehavior;
|
|
98
|
+
if (includeUndefinedValues) {
|
|
99
|
+
obj[key] = computedDefault;
|
|
100
|
+
} else if (emptyObjectFields !== 'skipDefaults') {
|
|
101
|
+
if (isObject(computedDefault)) {
|
|
102
|
+
// If isParentRequired is undefined, then we are at the root level of the schema so defer to the requiredness of
|
|
103
|
+
// the field key itself in the `requiredField` list
|
|
104
|
+
const isSelfOrParentRequired = isParentRequired === undefined ? requiredFields.includes(key) : isParentRequired;
|
|
105
|
+
// Store computedDefault if it's a non-empty object(e.g. not {}) and satisfies certain conditions
|
|
106
|
+
// Condition 1: If computedDefault is not empty or if the key is a required field
|
|
107
|
+
// Condition 2: If the parent object is required or emptyObjectFields is not 'populateRequiredDefaults'
|
|
108
|
+
if (
|
|
109
|
+
(!isEmpty(computedDefault) || requiredFields.includes(key)) &&
|
|
110
|
+
(isSelfOrParentRequired || emptyObjectFields !== 'populateRequiredDefaults')
|
|
111
|
+
) {
|
|
112
|
+
obj[key] = computedDefault;
|
|
113
|
+
}
|
|
114
|
+
} else if (
|
|
115
|
+
// Store computedDefault if it's a defined primitive (e.g., true) and satisfies certain conditions
|
|
116
|
+
// Condition 1: computedDefault is not undefined
|
|
117
|
+
// Condition 2: If emptyObjectFields is 'populateAllDefaults' or if the key is a required field
|
|
118
|
+
computedDefault !== undefined &&
|
|
119
|
+
(emptyObjectFields === 'populateAllDefaults' || requiredFields.includes(key))
|
|
120
|
+
) {
|
|
121
|
+
obj[key] = computedDefault;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema> {
|
|
127
|
+
parentDefaults?: T;
|
|
128
|
+
rootSchema?: S;
|
|
129
|
+
rawFormData?: T;
|
|
130
|
+
includeUndefinedValues?: boolean | 'excludeObjectChildren';
|
|
131
|
+
_recurseList?: string[];
|
|
132
|
+
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior;
|
|
133
|
+
required?: boolean;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Computes the defaults for the current `schema` given the `rawFormData` and `parentDefaults` if any. This drills into
|
|
137
|
+
* each level of the schema, recursively, to fill out every level of defaults provided by the schema.
|
|
138
|
+
*
|
|
139
|
+
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary
|
|
140
|
+
* @param rawSchema - The schema for which the default state is desired
|
|
141
|
+
* @param [props] - Optional props for this function
|
|
142
|
+
* @param [props.parentDefaults] - Any defaults provided by the parent field in the schema
|
|
143
|
+
* @param [props.rootSchema] - The options root schema, used to primarily to look up `$ref`s
|
|
144
|
+
* @param [props.rawFormData] - The current formData, if any, onto which to provide any missing defaults
|
|
145
|
+
* @param [props.includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults.
|
|
146
|
+
* If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as
|
|
147
|
+
* false when computing defaults for any nested object properties.
|
|
148
|
+
* @param [props._recurseList=[]] - The list of ref names currently being recursed, used to prevent infinite recursion
|
|
149
|
+
* @param [props.experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
|
|
150
|
+
* @param [props.required] - Optional flag, if true, indicates this schema was required in the parent schema.
|
|
151
|
+
* @returns - The resulting `formData` with all the defaults provided
|
|
152
|
+
*/
|
|
153
|
+
export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
|
|
154
|
+
validator: ValidatorType<T, S, F>,
|
|
155
|
+
rawSchema: S,
|
|
156
|
+
{
|
|
157
|
+
parentDefaults,
|
|
158
|
+
rawFormData,
|
|
159
|
+
rootSchema = {} as S,
|
|
160
|
+
includeUndefinedValues = false,
|
|
161
|
+
_recurseList = [],
|
|
162
|
+
experimental_defaultFormStateBehavior = undefined,
|
|
163
|
+
required,
|
|
164
|
+
}: ComputeDefaultsProps<T, S> = {}
|
|
165
|
+
): T | T[] | undefined {
|
|
166
|
+
const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
|
|
167
|
+
const schema: S = isObject(rawSchema) ? rawSchema : ({} as S);
|
|
168
|
+
// Compute the defaults recursively: give highest priority to deepest nodes.
|
|
169
|
+
let defaults: T | T[] | undefined = parentDefaults;
|
|
170
|
+
// If we get a new schema, then we need to recompute defaults again for the new schema found.
|
|
171
|
+
let schemaToCompute: S | null = null;
|
|
172
|
+
let updatedRecurseList = _recurseList;
|
|
173
|
+
|
|
174
|
+
if (isObject(defaults) && isObject(schema.default)) {
|
|
175
|
+
// For object defaults, only override parent defaults that are defined in
|
|
176
|
+
// schema.default.
|
|
177
|
+
defaults = mergeObjects(defaults!, schema.default as GenericObjectType) as T;
|
|
178
|
+
} else if (DEFAULT_KEY in schema) {
|
|
179
|
+
defaults = schema.default as unknown as T;
|
|
180
|
+
} else if (REF_KEY in schema) {
|
|
181
|
+
const refName = schema[REF_KEY];
|
|
182
|
+
// Use referenced schema defaults for this node.
|
|
183
|
+
if (!_recurseList.includes(refName!)) {
|
|
184
|
+
updatedRecurseList = _recurseList.concat(refName!);
|
|
185
|
+
schemaToCompute = findSchemaDefinition<S>(refName, rootSchema);
|
|
186
|
+
}
|
|
187
|
+
} else if (DEPENDENCIES_KEY in schema) {
|
|
188
|
+
const resolvedSchema = resolveDependencies<T, S, F>(validator, schema, rootSchema, false, formData);
|
|
189
|
+
schemaToCompute = resolvedSchema[0]; // pick the first element from resolve dependencies
|
|
190
|
+
} else if (isFixedItems(schema)) {
|
|
191
|
+
defaults = (schema.items! as S[]).map((itemSchema: S, idx: number) =>
|
|
192
|
+
computeDefaults<T, S>(validator, itemSchema, {
|
|
193
|
+
rootSchema,
|
|
194
|
+
includeUndefinedValues,
|
|
195
|
+
_recurseList,
|
|
196
|
+
experimental_defaultFormStateBehavior,
|
|
197
|
+
parentDefaults: Array.isArray(parentDefaults) ? parentDefaults[idx] : undefined,
|
|
198
|
+
rawFormData: formData as T,
|
|
199
|
+
required,
|
|
200
|
+
})
|
|
201
|
+
) as T[];
|
|
202
|
+
} else if (ONE_OF_KEY in schema) {
|
|
203
|
+
const { oneOf, ...remaining } = schema;
|
|
204
|
+
if (oneOf!.length === 0) {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
|
|
208
|
+
schemaToCompute = oneOf![
|
|
209
|
+
getClosestMatchingOption<T, S, F>(
|
|
210
|
+
validator,
|
|
211
|
+
rootSchema,
|
|
212
|
+
isEmpty(formData) ? undefined : formData,
|
|
213
|
+
oneOf as S[],
|
|
214
|
+
0,
|
|
215
|
+
discriminator
|
|
216
|
+
)
|
|
217
|
+
] as S;
|
|
218
|
+
schemaToCompute = mergeSchemas(remaining, schemaToCompute) as S;
|
|
219
|
+
} else if (ANY_OF_KEY in schema) {
|
|
220
|
+
const { anyOf, ...remaining } = schema;
|
|
221
|
+
if (anyOf!.length === 0) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
|
|
225
|
+
schemaToCompute = anyOf![
|
|
226
|
+
getClosestMatchingOption<T, S, F>(
|
|
227
|
+
validator,
|
|
228
|
+
rootSchema,
|
|
229
|
+
isEmpty(formData) ? undefined : formData,
|
|
230
|
+
anyOf as S[],
|
|
231
|
+
0,
|
|
232
|
+
discriminator
|
|
233
|
+
)
|
|
234
|
+
] as S;
|
|
235
|
+
schemaToCompute = mergeSchemas(remaining, schemaToCompute) as S;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (schemaToCompute) {
|
|
239
|
+
return computeDefaults<T, S, F>(validator, schemaToCompute, {
|
|
240
|
+
rootSchema,
|
|
241
|
+
includeUndefinedValues,
|
|
242
|
+
_recurseList: updatedRecurseList,
|
|
243
|
+
experimental_defaultFormStateBehavior,
|
|
244
|
+
parentDefaults: defaults as T | undefined,
|
|
245
|
+
rawFormData: formData as T,
|
|
246
|
+
required,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// No defaults defined for this node, fallback to generic typed ones.
|
|
251
|
+
if (defaults === undefined) {
|
|
252
|
+
defaults = schema.default as unknown as T;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
switch (getSchemaType<S>(schema)) {
|
|
256
|
+
// We need to recurse for object schema inner default values.
|
|
257
|
+
case 'object': {
|
|
258
|
+
const objectDefaults = Object.keys(schema.properties || {}).reduce((acc: GenericObjectType, key: string) => {
|
|
259
|
+
// Compute the defaults for this node, with the parent defaults we might
|
|
260
|
+
// have from a previous run: defaults[key].
|
|
261
|
+
const computedDefault = computeDefaults<T, S, F>(validator, get(schema, [PROPERTIES_KEY, key]), {
|
|
262
|
+
rootSchema,
|
|
263
|
+
_recurseList,
|
|
264
|
+
experimental_defaultFormStateBehavior,
|
|
265
|
+
includeUndefinedValues: includeUndefinedValues === true,
|
|
266
|
+
parentDefaults: get(defaults, [key]),
|
|
267
|
+
rawFormData: get(formData, [key]),
|
|
268
|
+
required: schema.required?.includes(key),
|
|
269
|
+
});
|
|
270
|
+
maybeAddDefaultToObject<T>(
|
|
271
|
+
acc,
|
|
272
|
+
key,
|
|
273
|
+
computedDefault,
|
|
274
|
+
includeUndefinedValues,
|
|
275
|
+
required,
|
|
276
|
+
schema.required,
|
|
277
|
+
experimental_defaultFormStateBehavior
|
|
278
|
+
);
|
|
279
|
+
return acc;
|
|
280
|
+
}, {}) as T;
|
|
281
|
+
if (schema.additionalProperties) {
|
|
282
|
+
// as per spec additionalProperties may be either schema or boolean
|
|
283
|
+
const additionalPropertiesSchema = isObject(schema.additionalProperties) ? schema.additionalProperties : {};
|
|
284
|
+
const keys = new Set<string>();
|
|
285
|
+
if (isObject(defaults)) {
|
|
286
|
+
Object.keys(defaults as GenericObjectType)
|
|
287
|
+
.filter((key) => !schema.properties || !schema.properties[key])
|
|
288
|
+
.forEach((key) => keys.add(key));
|
|
289
|
+
}
|
|
290
|
+
let formDataRequired: string[];
|
|
291
|
+
if (isObject(formData)) {
|
|
292
|
+
formDataRequired = [];
|
|
293
|
+
Object.keys(formData as GenericObjectType)
|
|
294
|
+
.filter((key) => !schema.properties || !schema.properties[key])
|
|
295
|
+
.forEach((key) => {
|
|
296
|
+
keys.add(key);
|
|
297
|
+
formDataRequired.push(key);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
keys.forEach((key) => {
|
|
301
|
+
const computedDefault = computeDefaults(validator, additionalPropertiesSchema as S, {
|
|
302
|
+
rootSchema,
|
|
303
|
+
_recurseList,
|
|
304
|
+
experimental_defaultFormStateBehavior,
|
|
305
|
+
includeUndefinedValues: includeUndefinedValues === true,
|
|
306
|
+
parentDefaults: get(defaults, [key]),
|
|
307
|
+
rawFormData: get(formData, [key]),
|
|
308
|
+
required: schema.required?.includes(key),
|
|
309
|
+
});
|
|
310
|
+
// Since these are additional properties we don’t need to add the `experimental_defaultFormStateBehavior` prop
|
|
311
|
+
maybeAddDefaultToObject<T>(
|
|
312
|
+
objectDefaults as GenericObjectType,
|
|
313
|
+
key,
|
|
314
|
+
computedDefault,
|
|
315
|
+
includeUndefinedValues,
|
|
316
|
+
required,
|
|
317
|
+
formDataRequired
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return objectDefaults;
|
|
322
|
+
}
|
|
323
|
+
case 'array': {
|
|
324
|
+
const neverPopulate = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'never';
|
|
325
|
+
const ignoreMinItemsFlagSet = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'requiredOnly';
|
|
326
|
+
|
|
327
|
+
// Inject defaults into existing array defaults
|
|
328
|
+
if (Array.isArray(defaults)) {
|
|
329
|
+
defaults = defaults.map((item, idx) => {
|
|
330
|
+
const schemaItem: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Fallback, idx);
|
|
331
|
+
return computeDefaults<T, S, F>(validator, schemaItem, {
|
|
332
|
+
rootSchema,
|
|
333
|
+
_recurseList,
|
|
334
|
+
experimental_defaultFormStateBehavior,
|
|
335
|
+
parentDefaults: item,
|
|
336
|
+
required,
|
|
337
|
+
});
|
|
338
|
+
}) as T[];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Deeply inject defaults into already existing form data
|
|
342
|
+
if (Array.isArray(rawFormData)) {
|
|
343
|
+
const schemaItem: S = getInnerSchemaForArrayItem<S>(schema);
|
|
344
|
+
if (neverPopulate) {
|
|
345
|
+
defaults = rawFormData;
|
|
346
|
+
} else {
|
|
347
|
+
defaults = rawFormData.map((item: T, idx: number) => {
|
|
348
|
+
return computeDefaults<T, S, F>(validator, schemaItem, {
|
|
349
|
+
rootSchema,
|
|
350
|
+
_recurseList,
|
|
351
|
+
experimental_defaultFormStateBehavior,
|
|
352
|
+
rawFormData: item,
|
|
353
|
+
parentDefaults: get(defaults, [idx]),
|
|
354
|
+
required,
|
|
355
|
+
});
|
|
356
|
+
}) as T[];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (neverPopulate) {
|
|
361
|
+
return defaults ?? [];
|
|
362
|
+
}
|
|
363
|
+
if (ignoreMinItemsFlagSet && !required) {
|
|
364
|
+
// If no form data exists or defaults are set leave the field empty/non-existent, otherwise
|
|
365
|
+
// return form data/defaults
|
|
366
|
+
return defaults ? defaults : undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const defaultsLength = Array.isArray(defaults) ? defaults.length : 0;
|
|
370
|
+
if (
|
|
371
|
+
!schema.minItems ||
|
|
372
|
+
isMultiSelect<T, S, F>(validator, schema, rootSchema) ||
|
|
373
|
+
schema.minItems <= defaultsLength
|
|
374
|
+
) {
|
|
375
|
+
return defaults ? defaults : [];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const defaultEntries: T[] = (defaults || []) as T[];
|
|
379
|
+
const fillerSchema: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Invert);
|
|
380
|
+
const fillerDefault = fillerSchema.default;
|
|
381
|
+
|
|
382
|
+
// Calculate filler entries for remaining items (minItems - existing raw data/defaults)
|
|
383
|
+
const fillerEntries: T[] = new Array(schema.minItems - defaultsLength).fill(
|
|
384
|
+
computeDefaults<any, S, F>(validator, fillerSchema, {
|
|
385
|
+
parentDefaults: fillerDefault,
|
|
386
|
+
rootSchema,
|
|
387
|
+
_recurseList,
|
|
388
|
+
experimental_defaultFormStateBehavior,
|
|
389
|
+
required,
|
|
390
|
+
})
|
|
391
|
+
) as T[];
|
|
392
|
+
// then fill up the rest with either the item default or empty, up to minItems
|
|
393
|
+
return defaultEntries.concat(fillerEntries);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return defaults;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** Returns the superset of `formData` that includes the given set updated to include any missing fields that have
|
|
401
|
+
* computed to have defaults provided in the `schema`.
|
|
402
|
+
*
|
|
403
|
+
* @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
|
|
404
|
+
* @param theSchema - The schema for which the default state is desired
|
|
405
|
+
* @param [formData] - The current formData, if any, onto which to provide any missing defaults
|
|
406
|
+
* @param [rootSchema] - The root schema, used to primarily to look up `$ref`s
|
|
407
|
+
* @param [includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults.
|
|
408
|
+
* If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as
|
|
409
|
+
* false when computing defaults for any nested object properties.
|
|
410
|
+
* @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
|
|
411
|
+
* @returns - The resulting `formData` with all the defaults provided
|
|
412
|
+
*/
|
|
413
|
+
export default function getDefaultFormState<
|
|
414
|
+
T = any,
|
|
415
|
+
S extends StrictRJSFSchema = RJSFSchema,
|
|
416
|
+
F extends FormContextType = any
|
|
417
|
+
>(
|
|
418
|
+
validator: ValidatorType<T, S, F>,
|
|
419
|
+
theSchema: S,
|
|
420
|
+
formData?: T,
|
|
421
|
+
rootSchema?: S,
|
|
422
|
+
includeUndefinedValues: boolean | 'excludeObjectChildren' = false,
|
|
423
|
+
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior
|
|
424
|
+
) {
|
|
425
|
+
if (!isObject(theSchema)) {
|
|
426
|
+
throw new Error('Invalid schema: ' + theSchema);
|
|
427
|
+
}
|
|
428
|
+
const schema = retrieveSchema<T, S, F>(validator, theSchema, rootSchema, formData);
|
|
429
|
+
const defaults = computeDefaults<T, S, F>(validator, schema, {
|
|
430
|
+
rootSchema,
|
|
431
|
+
includeUndefinedValues,
|
|
432
|
+
experimental_defaultFormStateBehavior,
|
|
433
|
+
rawFormData: formData,
|
|
434
|
+
});
|
|
435
|
+
if (formData === undefined || formData === null || (typeof formData === 'number' && isNaN(formData))) {
|
|
436
|
+
// No form data? Use schema defaults.
|
|
437
|
+
return defaults;
|
|
438
|
+
}
|
|
439
|
+
const { mergeExtraDefaults } = experimental_defaultFormStateBehavior?.arrayMinItems || {};
|
|
440
|
+
if (isObject(formData)) {
|
|
441
|
+
return mergeDefaultsWithFormData<T>(defaults as T, formData, mergeExtraDefaults);
|
|
442
|
+
}
|
|
443
|
+
if (Array.isArray(formData)) {
|
|
444
|
+
return mergeDefaultsWithFormData<T[]>(defaults as T[], formData, mergeExtraDefaults);
|
|
445
|
+
}
|
|
446
|
+
return formData;
|
|
447
|
+
}
|