@rjsf/utils
Advanced tools
Comparing version 5.20.1 to 5.21.0
import deepEquals from './deepEquals'; | ||
import { getDefaultFormState, getDisplayLabel, getClosestMatchingOption, getFirstMatchingOption, getMatchingOption, isFilesArray, isMultiSelect, isSelect, mergeValidationData, retrieveSchema, sanitizeDataForNewSchema, toIdSchema, toPathSchema, } from './schema'; | ||
import { getClosestMatchingOption, getDefaultFormState, getDisplayLabel, getFirstMatchingOption, getMatchingOption, isFilesArray, isMultiSelect, isSelect, mergeValidationData, retrieveSchema, sanitizeDataForNewSchema, toIdSchema, toPathSchema, } from './schema'; | ||
/** The `SchemaUtils` class provides a wrapper around the publicly exported APIs in the `utils/schema` directory such | ||
@@ -4,0 +4,0 @@ * that one does not have to explicitly pass the `validator`, `rootSchema`, or `experimental_defaultFormStateBehavior` to each method. |
@@ -1,2 +0,2 @@ | ||
/** Implements a deep equals using the `lodash.isEqualWith` function, that provides a customized comparator that | ||
/** Implements a deep equals using the `fast-equal.createCustomEqual` function, that provides a customized comparator that | ||
* assumes all functions are equivalent. | ||
@@ -3,0 +3,0 @@ * |
@@ -1,3 +0,31 @@ | ||
import isEqualWith from 'lodash/isEqualWith'; | ||
/** Implements a deep equals using the `lodash.isEqualWith` function, that provides a customized comparator that | ||
import { createCustomEqual } from 'fast-equals'; | ||
/** Check if all parameters are typeof function. | ||
* | ||
* @param a - The first element to check typeof | ||
* @param b - The second element to check typeof | ||
* @returns - if typeof a and b are equal to function return true, otherwise false | ||
*/ | ||
function isFunctions(a, b) { | ||
return typeof a === 'function' && typeof b === 'function'; | ||
} | ||
/** Implements a deep equals using the `fast-equal.createCustomEqual` function, that provides a customized comparator that | ||
* assumes all functions in objects are equivalent. | ||
* | ||
* @param a - The first element to compare | ||
* @param b - The second element to compare | ||
* @returns - True if the `a` and `b` are deeply equal, false otherwise | ||
*/ | ||
const customDeepEqual = createCustomEqual({ | ||
createInternalComparator: (comparator) => { | ||
return (a, b, _idxA, _idxB, _parentA, _parentB, state) => { | ||
if (isFunctions(a, b)) { | ||
// Assume all functions are equivalent | ||
// see https://github.com/rjsf-team/react-jsonschema-form/issues/255 | ||
return true; | ||
} | ||
return comparator(a, b, state); | ||
}; | ||
}, | ||
}); | ||
/** Implements a deep equals using the `fast-equal.createCustomEqual` function, that provides a customized comparator that | ||
* assumes all functions are equivalent. | ||
@@ -10,11 +38,7 @@ * | ||
export default function deepEquals(a, b) { | ||
return isEqualWith(a, b, (obj, other) => { | ||
if (typeof obj === 'function' && typeof other === 'function') { | ||
// Assume all functions are equivalent | ||
// see https://github.com/rjsf-team/react-jsonschema-form/issues/255 | ||
return true; | ||
} | ||
return undefined; // fallback to default isEquals behavior | ||
}); | ||
if (isFunctions(a, b)) { | ||
return true; | ||
} | ||
return customDeepEqual(a, b); | ||
} | ||
//# sourceMappingURL=deepEquals.js.map |
@@ -1,3 +0,3 @@ | ||
import isEqual from 'lodash/isEqual'; | ||
import enumOptionsValueForIndex from './enumOptionsValueForIndex'; | ||
import deepEquals from './deepEquals'; | ||
/** Removes the enum option value at the `valueIndex` from the currently `selected` (list of) value(s). If `selected` is | ||
@@ -18,6 +18,6 @@ * a list, then that list is updated to remove the enum option value with the `valueIndex` in `allEnumOptions`. If it is | ||
if (Array.isArray(selected)) { | ||
return selected.filter((v) => !isEqual(v, value)); | ||
return selected.filter((v) => !deepEquals(v, value)); | ||
} | ||
return isEqual(value, selected) ? undefined : selected; | ||
return deepEquals(value, selected) ? undefined : selected; | ||
} | ||
//# sourceMappingURL=enumOptionsDeselectValue.js.map |
@@ -1,2 +0,2 @@ | ||
import isEqual from 'lodash/isEqual'; | ||
import deepEquals from './deepEquals'; | ||
/** Determines whether the given `value` is (one of) the `selected` value(s). | ||
@@ -10,6 +10,6 @@ * | ||
if (Array.isArray(selected)) { | ||
return selected.some((sel) => isEqual(sel, value)); | ||
return selected.some((sel) => deepEquals(sel, value)); | ||
} | ||
return isEqual(selected, value); | ||
return deepEquals(selected, value); | ||
} | ||
//# sourceMappingURL=enumOptionsIsSelected.js.map |
import get from 'lodash/get'; | ||
import isEqual from 'lodash/isEqual'; | ||
import { ID_KEY } from '../constants'; | ||
import hashForSchema from '../hashForSchema'; | ||
import deepEquals from '../deepEquals'; | ||
/** An implementation of the `ValidatorType` interface that is designed for use in capturing schemas used by the | ||
@@ -43,3 +43,3 @@ * `isValid()` function. The rest of the implementation of the interface throws errors when it is attempted to be used. | ||
} | ||
else if (!isEqual(existing, identifiedSchema)) { | ||
else if (!deepEquals(existing, identifiedSchema)) { | ||
console.error('existing schema:', JSON.stringify(existing, null, 2)); | ||
@@ -64,3 +64,3 @@ console.error('new schema:', JSON.stringify(identifiedSchema, null, 2)); | ||
isValid(schema, _formData, rootSchema) { | ||
if (!isEqual(rootSchema, this.rootSchema)) { | ||
if (!deepEquals(rootSchema, this.rootSchema)) { | ||
throw new Error('Unexpectedly calling isValid() with a rootSchema that differs from the construction rootSchema'); | ||
@@ -67,0 +67,0 @@ } |
import forEach from 'lodash/forEach'; | ||
import isEqual from 'lodash/isEqual'; | ||
import { PROPERTIES_KEY, ITEMS_KEY } from '../constants'; | ||
import { ITEMS_KEY, PROPERTIES_KEY } from '../constants'; | ||
import ParserValidator from './ParserValidator'; | ||
import { retrieveSchemaInternal, resolveAnyOrOneOfSchemas } from '../schema/retrieveSchema'; | ||
import { resolveAnyOrOneOfSchemas, retrieveSchemaInternal } from '../schema/retrieveSchema'; | ||
import deepEquals from '../deepEquals'; | ||
/** Recursive function used to parse the given `schema` belonging to the `rootSchema`. The `validator` is used to | ||
@@ -19,3 +19,3 @@ * capture the sub-schemas that the `isValid()` function is called with. For each schema returned by the | ||
schemas.forEach((schema) => { | ||
const sameSchemaIndex = recurseList.findIndex((item) => isEqual(item, schema)); | ||
const sameSchemaIndex = recurseList.findIndex((item) => deepEquals(item, schema)); | ||
if (sameSchemaIndex === -1) { | ||
@@ -22,0 +22,0 @@ recurseList.push(schema); |
@@ -26,8 +26,18 @@ import { Experimental_DefaultFormStateBehavior, FormContextType, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types'; | ||
interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema> { | ||
/** Any defaults provided by the parent field in the schema */ | ||
parentDefaults?: T; | ||
/** The options root schema, used to primarily to look up `$ref`s */ | ||
rootSchema?: S; | ||
/** The current formData, if any, onto which to provide any missing defaults */ | ||
rawFormData?: T; | ||
/** Optional flag, if true, cause undefined values to be added as defaults. | ||
* If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as | ||
* false when computing defaults for any nested object properties. | ||
*/ | ||
includeUndefinedValues?: boolean | 'excludeObjectChildren'; | ||
/** The list of ref names currently being recursed, used to prevent infinite recursion */ | ||
_recurseList?: string[]; | ||
/** Optional configuration object, if provided, allows users to override default form state behavior */ | ||
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior; | ||
/** Optional flag, if true, indicates this schema was required in the parent schema. */ | ||
required?: boolean; | ||
@@ -40,15 +50,33 @@ } | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param [props] - Optional props for this function | ||
* @param [props.parentDefaults] - Any defaults provided by the parent field in the schema | ||
* @param [props.rootSchema] - The options root schema, used to primarily to look up `$ref`s | ||
* @param [props.rawFormData] - The current formData, if any, onto which to provide any missing defaults | ||
* @param [props.includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults. | ||
* If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as | ||
* false when computing defaults for any nested object properties. | ||
* @param [props._recurseList=[]] - The list of ref names currently being recursed, used to prevent infinite recursion | ||
* @param [props.experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior | ||
* @param [props.required] - Optional flag, if true, indicates this schema was required in the parent schema. | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @returns - The resulting `formData` with all the defaults provided | ||
*/ | ||
export declare function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(validator: ValidatorType<T, S, F>, rawSchema: S, { parentDefaults, rawFormData, rootSchema, includeUndefinedValues, _recurseList, experimental_defaultFormStateBehavior, required, }?: ComputeDefaultsProps<T, S>): T | T[] | undefined; | ||
export declare function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(validator: ValidatorType<T, S, F>, rawSchema: S, computeDefaultsProps?: ComputeDefaultsProps<T, S>): T | T[] | undefined; | ||
/** Computes the default value for objects. | ||
* | ||
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @param defaults - Optional props for this function | ||
* @returns - The default value based on the schema type if they are defined for object or array schemas. | ||
*/ | ||
export declare function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(validator: ValidatorType<T, S, F>, rawSchema: S, { rawFormData, rootSchema, includeUndefinedValues, _recurseList, experimental_defaultFormStateBehavior, required, }?: ComputeDefaultsProps<T, S>, defaults?: T | T[] | undefined): T; | ||
/** Computes the default value for arrays. | ||
* | ||
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @param defaults - Optional props for this function | ||
* @returns - The default value based on the schema type if they are defined for object or array schemas. | ||
*/ | ||
export declare function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(validator: ValidatorType<T, S, F>, rawSchema: S, { rawFormData, rootSchema, _recurseList, experimental_defaultFormStateBehavior, required, }?: ComputeDefaultsProps<T, S>, defaults?: T | T[] | undefined): T | T[] | undefined; | ||
/** Computes the default value based on the schema type. | ||
* | ||
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @param defaults - Optional props for this function | ||
* @returns - The default value based on the schema type if they are defined for object or array schemas. | ||
*/ | ||
export declare function getDefaultBasedOnSchemaType<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(validator: ValidatorType<T, S, F>, rawSchema: S, computeDefaultsProps?: ComputeDefaultsProps<T, S>, defaults?: T | T[] | undefined): T | T[] | void; | ||
/** Returns the superset of `formData` that includes the given set updated to include any missing fields that have | ||
@@ -55,0 +83,0 @@ * computed to have defaults provided in the `schema`. |
@@ -115,16 +115,7 @@ import get from 'lodash/get'; | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param [props] - Optional props for this function | ||
* @param [props.parentDefaults] - Any defaults provided by the parent field in the schema | ||
* @param [props.rootSchema] - The options root schema, used to primarily to look up `$ref`s | ||
* @param [props.rawFormData] - The current formData, if any, onto which to provide any missing defaults | ||
* @param [props.includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults. | ||
* If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as | ||
* false when computing defaults for any nested object properties. | ||
* @param [props._recurseList=[]] - The list of ref names currently being recursed, used to prevent infinite recursion | ||
* @param [props.experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior | ||
* @param [props.required] - Optional flag, if true, indicates this schema was required in the parent schema. | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @returns - The resulting `formData` with all the defaults provided | ||
*/ | ||
export function computeDefaults(validator, rawSchema, { parentDefaults, rawFormData, rootSchema = {}, includeUndefinedValues = false, _recurseList = [], experimental_defaultFormStateBehavior = undefined, required, } = {}) { | ||
var _a, _b, _c, _d; | ||
export function computeDefaults(validator, rawSchema, computeDefaultsProps = {}) { | ||
const { parentDefaults, rawFormData, rootSchema = {}, includeUndefinedValues = false, _recurseList = [], experimental_defaultFormStateBehavior = undefined, required, } = computeDefaultsProps; | ||
const formData = (isObject(rawFormData) ? rawFormData : {}); | ||
@@ -154,3 +145,8 @@ const schema = isObject(rawSchema) ? rawSchema : {}; | ||
else if (DEPENDENCIES_KEY in schema) { | ||
const resolvedSchema = resolveDependencies(validator, schema, rootSchema, false, [], formData); | ||
// Get the default if set from properties to ensure the dependencies conditions are resolved based on it | ||
const defaultFormData = { | ||
...formData, | ||
...getDefaultBasedOnSchemaType(validator, schema, computeDefaultsProps, defaults), | ||
}; | ||
const resolvedSchema = resolveDependencies(validator, schema, rootSchema, false, [], defaultFormData); | ||
schemaToCompute = resolvedSchema[0]; // pick the first element from resolve dependencies | ||
@@ -202,15 +198,59 @@ } | ||
} | ||
switch (getSchemaType(schema)) { | ||
// We need to recurse for object schema inner default values. | ||
case 'object': { | ||
// This is a custom addition that fixes this issue: | ||
// https://github.com/rjsf-team/react-jsonschema-form/issues/3832 | ||
const retrievedSchema = (experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.allOf) === 'populateDefaults' && ALL_OF_KEY in schema | ||
? retrieveSchema(validator, schema, rootSchema, formData) | ||
: schema; | ||
const objectDefaults = Object.keys(retrievedSchema.properties || {}).reduce((acc, key) => { | ||
const defaultBasedOnSchemaType = getDefaultBasedOnSchemaType(validator, schema, computeDefaultsProps, defaults); | ||
return defaultBasedOnSchemaType !== null && defaultBasedOnSchemaType !== void 0 ? defaultBasedOnSchemaType : defaults; | ||
} | ||
/** Computes the default value for objects. | ||
* | ||
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @param defaults - Optional props for this function | ||
* @returns - The default value based on the schema type if they are defined for object or array schemas. | ||
*/ | ||
export function getObjectDefaults(validator, rawSchema, { rawFormData, rootSchema = {}, includeUndefinedValues = false, _recurseList = [], experimental_defaultFormStateBehavior = undefined, required, } = {}, defaults) { | ||
{ | ||
const formData = (isObject(rawFormData) ? rawFormData : {}); | ||
const schema = rawSchema; | ||
// This is a custom addition that fixes this issue: | ||
// https://github.com/rjsf-team/react-jsonschema-form/issues/3832 | ||
const retrievedSchema = (experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.allOf) === 'populateDefaults' && ALL_OF_KEY in schema | ||
? retrieveSchema(validator, schema, rootSchema, formData) | ||
: schema; | ||
const objectDefaults = Object.keys(retrievedSchema.properties || {}).reduce((acc, key) => { | ||
var _a; | ||
// Compute the defaults for this node, with the parent defaults we might | ||
// have from a previous run: defaults[key]. | ||
const computedDefault = computeDefaults(validator, get(retrievedSchema, [PROPERTIES_KEY, key]), { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
includeUndefinedValues: includeUndefinedValues === true, | ||
parentDefaults: get(defaults, [key]), | ||
rawFormData: get(formData, [key]), | ||
required: (_a = retrievedSchema.required) === null || _a === void 0 ? void 0 : _a.includes(key), | ||
}); | ||
maybeAddDefaultToObject(acc, key, computedDefault, includeUndefinedValues, required, retrievedSchema.required, experimental_defaultFormStateBehavior); | ||
return acc; | ||
}, {}); | ||
if (retrievedSchema.additionalProperties) { | ||
// as per spec additionalProperties may be either schema or boolean | ||
const additionalPropertiesSchema = isObject(retrievedSchema.additionalProperties) | ||
? retrievedSchema.additionalProperties | ||
: {}; | ||
const keys = new Set(); | ||
if (isObject(defaults)) { | ||
Object.keys(defaults) | ||
.filter((key) => !retrievedSchema.properties || !retrievedSchema.properties[key]) | ||
.forEach((key) => keys.add(key)); | ||
} | ||
const formDataRequired = []; | ||
Object.keys(formData) | ||
.filter((key) => !retrievedSchema.properties || !retrievedSchema.properties[key]) | ||
.forEach((key) => { | ||
keys.add(key); | ||
formDataRequired.push(key); | ||
}); | ||
keys.forEach((key) => { | ||
var _a; | ||
// Compute the defaults for this node, with the parent defaults we might | ||
// have from a previous run: defaults[key]. | ||
const computedDefault = computeDefaults(validator, get(retrievedSchema, [PROPERTIES_KEY, key]), { | ||
const computedDefault = computeDefaults(validator, additionalPropertiesSchema, { | ||
rootSchema, | ||
@@ -224,110 +264,105 @@ _recurseList, | ||
}); | ||
maybeAddDefaultToObject(acc, key, computedDefault, includeUndefinedValues, required, retrievedSchema.required, experimental_defaultFormStateBehavior); | ||
return acc; | ||
}, {}); | ||
if (retrievedSchema.additionalProperties) { | ||
// as per spec additionalProperties may be either schema or boolean | ||
const additionalPropertiesSchema = isObject(retrievedSchema.additionalProperties) | ||
? retrievedSchema.additionalProperties | ||
: {}; | ||
const keys = new Set(); | ||
if (isObject(defaults)) { | ||
Object.keys(defaults) | ||
.filter((key) => !retrievedSchema.properties || !retrievedSchema.properties[key]) | ||
.forEach((key) => keys.add(key)); | ||
} | ||
const formDataRequired = []; | ||
Object.keys(formData) | ||
.filter((key) => !retrievedSchema.properties || !retrievedSchema.properties[key]) | ||
.forEach((key) => { | ||
keys.add(key); | ||
formDataRequired.push(key); | ||
}); | ||
keys.forEach((key) => { | ||
var _a; | ||
const computedDefault = computeDefaults(validator, additionalPropertiesSchema, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
includeUndefinedValues: includeUndefinedValues === true, | ||
parentDefaults: get(defaults, [key]), | ||
rawFormData: get(formData, [key]), | ||
required: (_a = retrievedSchema.required) === null || _a === void 0 ? void 0 : _a.includes(key), | ||
}); | ||
// Since these are additional properties we don't need to add the `experimental_defaultFormStateBehavior` prop | ||
maybeAddDefaultToObject(objectDefaults, key, computedDefault, includeUndefinedValues, required, formDataRequired); | ||
}); | ||
} | ||
return objectDefaults; | ||
// Since these are additional properties we don't need to add the `experimental_defaultFormStateBehavior` prop | ||
maybeAddDefaultToObject(objectDefaults, key, computedDefault, includeUndefinedValues, required, formDataRequired); | ||
}); | ||
} | ||
case 'array': { | ||
const neverPopulate = ((_a = experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.arrayMinItems) === null || _a === void 0 ? void 0 : _a.populate) === 'never'; | ||
const ignoreMinItemsFlagSet = ((_b = experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.arrayMinItems) === null || _b === void 0 ? void 0 : _b.populate) === 'requiredOnly'; | ||
const isSkipEmptyDefaults = (experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.emptyObjectFields) === 'skipEmptyDefaults'; | ||
const computeSkipPopulate = (_d = (_c = experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.arrayMinItems) === null || _c === void 0 ? void 0 : _c.computeSkipPopulate) !== null && _d !== void 0 ? _d : (() => false); | ||
const emptyDefault = isSkipEmptyDefaults ? undefined : []; | ||
// Inject defaults into existing array defaults | ||
if (Array.isArray(defaults)) { | ||
defaults = defaults.map((item, idx) => { | ||
const schemaItem = getInnerSchemaForArrayItem(schema, AdditionalItemsHandling.Fallback, idx); | ||
return computeDefaults(validator, schemaItem, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
parentDefaults: item, | ||
required, | ||
}); | ||
}); | ||
} | ||
// Deeply inject defaults into already existing form data | ||
if (Array.isArray(rawFormData)) { | ||
const schemaItem = getInnerSchemaForArrayItem(schema); | ||
if (neverPopulate) { | ||
defaults = rawFormData; | ||
} | ||
else { | ||
defaults = rawFormData.map((item, idx) => { | ||
return computeDefaults(validator, schemaItem, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
rawFormData: item, | ||
parentDefaults: get(defaults, [idx]), | ||
required, | ||
}); | ||
}); | ||
} | ||
} | ||
if (neverPopulate) { | ||
return defaults !== null && defaults !== void 0 ? defaults : emptyDefault; | ||
} | ||
if (ignoreMinItemsFlagSet && !required) { | ||
// If no form data exists or defaults are set leave the field empty/non-existent, otherwise | ||
// return form data/defaults | ||
return defaults ? defaults : undefined; | ||
} | ||
const defaultsLength = Array.isArray(defaults) ? defaults.length : 0; | ||
if (!schema.minItems || | ||
isMultiSelect(validator, schema, rootSchema) || | ||
computeSkipPopulate(validator, schema, rootSchema) || | ||
schema.minItems <= defaultsLength) { | ||
return defaults ? defaults : emptyDefault; | ||
} | ||
const defaultEntries = (defaults || []); | ||
const fillerSchema = getInnerSchemaForArrayItem(schema, AdditionalItemsHandling.Invert); | ||
const fillerDefault = fillerSchema.default; | ||
// Calculate filler entries for remaining items (minItems - existing raw data/defaults) | ||
const fillerEntries = new Array(schema.minItems - defaultsLength).fill(computeDefaults(validator, fillerSchema, { | ||
parentDefaults: fillerDefault, | ||
return objectDefaults; | ||
} | ||
} | ||
/** Computes the default value for arrays. | ||
* | ||
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @param defaults - Optional props for this function | ||
* @returns - The default value based on the schema type if they are defined for object or array schemas. | ||
*/ | ||
export function getArrayDefaults(validator, rawSchema, { rawFormData, rootSchema = {}, _recurseList = [], experimental_defaultFormStateBehavior = undefined, required, } = {}, defaults) { | ||
var _a, _b, _c, _d; | ||
const schema = rawSchema; | ||
const neverPopulate = ((_a = experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.arrayMinItems) === null || _a === void 0 ? void 0 : _a.populate) === 'never'; | ||
const ignoreMinItemsFlagSet = ((_b = experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.arrayMinItems) === null || _b === void 0 ? void 0 : _b.populate) === 'requiredOnly'; | ||
const isSkipEmptyDefaults = (experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.emptyObjectFields) === 'skipEmptyDefaults'; | ||
const computeSkipPopulate = (_d = (_c = experimental_defaultFormStateBehavior === null || experimental_defaultFormStateBehavior === void 0 ? void 0 : experimental_defaultFormStateBehavior.arrayMinItems) === null || _c === void 0 ? void 0 : _c.computeSkipPopulate) !== null && _d !== void 0 ? _d : (() => false); | ||
const emptyDefault = isSkipEmptyDefaults ? undefined : []; | ||
// Inject defaults into existing array defaults | ||
if (Array.isArray(defaults)) { | ||
defaults = defaults.map((item, idx) => { | ||
const schemaItem = getInnerSchemaForArrayItem(schema, AdditionalItemsHandling.Fallback, idx); | ||
return computeDefaults(validator, schemaItem, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
parentDefaults: item, | ||
required, | ||
})); | ||
// then fill up the rest with either the item default or empty, up to minItems | ||
return defaultEntries.concat(fillerEntries); | ||
}); | ||
}); | ||
} | ||
// Deeply inject defaults into already existing form data | ||
if (Array.isArray(rawFormData)) { | ||
const schemaItem = getInnerSchemaForArrayItem(schema); | ||
if (neverPopulate) { | ||
defaults = rawFormData; | ||
} | ||
else { | ||
defaults = rawFormData.map((item, idx) => { | ||
return computeDefaults(validator, schemaItem, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
rawFormData: item, | ||
parentDefaults: get(defaults, [idx]), | ||
required, | ||
}); | ||
}); | ||
} | ||
} | ||
return defaults; | ||
if (neverPopulate) { | ||
return defaults !== null && defaults !== void 0 ? defaults : emptyDefault; | ||
} | ||
if (ignoreMinItemsFlagSet && !required) { | ||
// If no form data exists or defaults are set leave the field empty/non-existent, otherwise | ||
// return form data/defaults | ||
return defaults ? defaults : undefined; | ||
} | ||
const defaultsLength = Array.isArray(defaults) ? defaults.length : 0; | ||
if (!schema.minItems || | ||
isMultiSelect(validator, schema, rootSchema) || | ||
computeSkipPopulate(validator, schema, rootSchema) || | ||
schema.minItems <= defaultsLength) { | ||
return defaults ? defaults : emptyDefault; | ||
} | ||
const defaultEntries = (defaults || []); | ||
const fillerSchema = getInnerSchemaForArrayItem(schema, AdditionalItemsHandling.Invert); | ||
const fillerDefault = fillerSchema.default; | ||
// Calculate filler entries for remaining items (minItems - existing raw data/defaults) | ||
const fillerEntries = new Array(schema.minItems - defaultsLength).fill(computeDefaults(validator, fillerSchema, { | ||
parentDefaults: fillerDefault, | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
required, | ||
})); | ||
// then fill up the rest with either the item default or empty, up to minItems | ||
return defaultEntries.concat(fillerEntries); | ||
} | ||
/** Computes the default value based on the schema type. | ||
* | ||
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @param defaults - Optional props for this function | ||
* @returns - The default value based on the schema type if they are defined for object or array schemas. | ||
*/ | ||
export function getDefaultBasedOnSchemaType(validator, rawSchema, computeDefaultsProps = {}, defaults) { | ||
switch (getSchemaType(rawSchema)) { | ||
// We need to recurse for object schema inner default values. | ||
case 'object': { | ||
return getObjectDefaults(validator, rawSchema, computeDefaultsProps, defaults); | ||
} | ||
case 'array': { | ||
return getArrayDefaults(validator, rawSchema, computeDefaultsProps, defaults); | ||
} | ||
} | ||
} | ||
/** Returns the superset of `formData` that includes the given set updated to include any missing fields that have | ||
@@ -334,0 +369,0 @@ * computed to have defaults provided in the `schema`. |
import get from 'lodash/get'; | ||
import isEqual from 'lodash/isEqual'; | ||
import set from 'lodash/set'; | ||
@@ -10,3 +9,3 @@ import times from 'lodash/times'; | ||
import mergeAllOf from 'json-schema-merge-allof'; | ||
import { ADDITIONAL_PROPERTIES_KEY, ADDITIONAL_PROPERTY_FLAG, ALL_OF_KEY, ANY_OF_KEY, DEPENDENCIES_KEY, IF_KEY, ONE_OF_KEY, REF_KEY, PROPERTIES_KEY, ITEMS_KEY, } from '../constants'; | ||
import { ADDITIONAL_PROPERTIES_KEY, ADDITIONAL_PROPERTY_FLAG, ALL_OF_KEY, ANY_OF_KEY, DEPENDENCIES_KEY, IF_KEY, ITEMS_KEY, ONE_OF_KEY, PROPERTIES_KEY, REF_KEY, } from '../constants'; | ||
import findSchemaDefinition, { splitKeyElementFromObject } from '../findSchemaDefinition'; | ||
@@ -18,2 +17,3 @@ import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema'; | ||
import getFirstMatchingOption from './getFirstMatchingOption'; | ||
import deepEquals from '../deepEquals'; | ||
/** Retrieves an expanded schema that has had all of its conditions, additional properties, references and dependencies | ||
@@ -125,3 +125,6 @@ * resolved and merged into the `schema` given a `validator`, `rootSchema` and `rawFormData` that is used to do the | ||
const allPermutations = getAllPermutationsOfXxxOf(allOfSchemaElements); | ||
return allPermutations.map((permutation) => ({ ...schema, allOf: permutation })); | ||
return allPermutations.map((permutation) => ({ | ||
...schema, | ||
allOf: permutation, | ||
})); | ||
} | ||
@@ -194,3 +197,3 @@ // No $ref or dependencies or allOf attribute was found, returning the original schema. | ||
} | ||
return isEqual(schema, resolvedSchema) ? schema : resolvedSchema; | ||
return deepEquals(schema, resolvedSchema) ? schema : resolvedSchema; | ||
} | ||
@@ -197,0 +200,0 @@ /** Creates new 'properties' items for each key in the `formData` |
import get from 'lodash/get'; | ||
import isEqual from 'lodash/isEqual'; | ||
import { ALL_OF_KEY, DEPENDENCIES_KEY, ID_KEY, ITEMS_KEY, PROPERTIES_KEY, REF_KEY } from '../constants'; | ||
@@ -7,2 +6,3 @@ import isObject from '../isObject'; | ||
import getSchemaType from '../getSchemaType'; | ||
import deepEquals from '../deepEquals'; | ||
/** An internal helper that generates an `IdSchema` object for the `schema`, recursively with protection against | ||
@@ -24,3 +24,3 @@ * infinite recursion | ||
const _schema = retrieveSchema(validator, schema, rootSchema, formData); | ||
const sameSchemaIndex = _recurseList.findIndex((item) => isEqual(item, _schema)); | ||
const sameSchemaIndex = _recurseList.findIndex((item) => deepEquals(item, _schema)); | ||
if (sameSchemaIndex === -1) { | ||
@@ -27,0 +27,0 @@ return toIdSchemaInternal(validator, _schema, idPrefix, idSeparator, id, rootSchema, formData, _recurseList.concat(_schema)); |
import get from 'lodash/get'; | ||
import isEqual from 'lodash/isEqual'; | ||
import set from 'lodash/set'; | ||
import { ALL_OF_KEY, ANY_OF_KEY, ADDITIONAL_PROPERTIES_KEY, DEPENDENCIES_KEY, ITEMS_KEY, NAME_KEY, ONE_OF_KEY, PROPERTIES_KEY, REF_KEY, RJSF_ADDITIONAL_PROPERTIES_FLAG, } from '../constants'; | ||
import { ADDITIONAL_PROPERTIES_KEY, ALL_OF_KEY, ANY_OF_KEY, DEPENDENCIES_KEY, ITEMS_KEY, NAME_KEY, ONE_OF_KEY, PROPERTIES_KEY, REF_KEY, RJSF_ADDITIONAL_PROPERTIES_FLAG, } from '../constants'; | ||
import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema'; | ||
import getClosestMatchingOption from './getClosestMatchingOption'; | ||
import retrieveSchema from './retrieveSchema'; | ||
import deepEquals from '../deepEquals'; | ||
/** An internal helper that generates an `PathSchema` object for the `schema`, recursively with protection against | ||
@@ -22,3 +22,3 @@ * infinite recursion | ||
const _schema = retrieveSchema(validator, schema, rootSchema, formData); | ||
const sameSchemaIndex = _recurseList.findIndex((item) => isEqual(item, _schema)); | ||
const sameSchemaIndex = _recurseList.findIndex((item) => deepEquals(item, _schema)); | ||
if (sameSchemaIndex === -1) { | ||
@@ -25,0 +25,0 @@ return toPathSchemaInternal(validator, _schema, name, rootSchema, formData, _recurseList.concat(_schema)); |
{ | ||
"name": "@rjsf/utils", | ||
"version": "5.20.1", | ||
"version": "5.21.0", | ||
"main": "dist/index.js", | ||
@@ -39,2 +39,3 @@ "module": "lib/index.js", | ||
"dependencies": { | ||
"fast-equals": "^5.0.1", | ||
"json-schema-merge-allof": "^0.8.1", | ||
@@ -89,3 +90,3 @@ "jsonpointer": "^5.0.1", | ||
"license": "Apache-2.0", | ||
"gitHead": "9ebd1a997311bf9406549bfce3a8d2b24df192af" | ||
"gitHead": "76f66b3a397af9ea3877dc94e4ff22a097d089f6" | ||
} |
@@ -17,5 +17,5 @@ import deepEquals from './deepEquals'; | ||
import { | ||
getClosestMatchingOption, | ||
getDefaultFormState, | ||
getDisplayLabel, | ||
getClosestMatchingOption, | ||
getFirstMatchingOption, | ||
@@ -22,0 +22,0 @@ getMatchingOption, |
@@ -1,4 +0,35 @@ | ||
import isEqualWith from 'lodash/isEqualWith'; | ||
import { createCustomEqual, State } from 'fast-equals'; | ||
/** Implements a deep equals using the `lodash.isEqualWith` function, that provides a customized comparator that | ||
/** Check if all parameters are typeof function. | ||
* | ||
* @param a - The first element to check typeof | ||
* @param b - The second element to check typeof | ||
* @returns - if typeof a and b are equal to function return true, otherwise false | ||
*/ | ||
function isFunctions(a: any, b: any) { | ||
return typeof a === 'function' && typeof b === 'function'; | ||
} | ||
/** Implements a deep equals using the `fast-equal.createCustomEqual` function, that provides a customized comparator that | ||
* assumes all functions in objects are equivalent. | ||
* | ||
* @param a - The first element to compare | ||
* @param b - The second element to compare | ||
* @returns - True if the `a` and `b` are deeply equal, false otherwise | ||
*/ | ||
const customDeepEqual = createCustomEqual({ | ||
createInternalComparator: (comparator: (a: any, b: any, state: State<any>) => boolean) => { | ||
return (a: any, b: any, _idxA: any, _idxB: any, _parentA: any, _parentB: any, state: State<any>) => { | ||
if (isFunctions(a, b)) { | ||
// Assume all functions are equivalent | ||
// see https://github.com/rjsf-team/react-jsonschema-form/issues/255 | ||
return true; | ||
} | ||
return comparator(a, b, state); | ||
}; | ||
}, | ||
}); | ||
/** Implements a deep equals using the `fast-equal.createCustomEqual` function, that provides a customized comparator that | ||
* assumes all functions are equivalent. | ||
@@ -11,10 +42,6 @@ * | ||
export default function deepEquals(a: any, b: any): boolean { | ||
return isEqualWith(a, b, (obj: any, other: any) => { | ||
if (typeof obj === 'function' && typeof other === 'function') { | ||
// Assume all functions are equivalent | ||
// see https://github.com/rjsf-team/react-jsonschema-form/issues/255 | ||
return true; | ||
} | ||
return undefined; // fallback to default isEquals behavior | ||
}); | ||
if (isFunctions(a, b)) { | ||
return true; | ||
} | ||
return customDeepEqual(a, b); | ||
} |
@@ -1,5 +0,4 @@ | ||
import isEqual from 'lodash/isEqual'; | ||
import { EnumOptionsType, RJSFSchema, StrictRJSFSchema } from './types'; | ||
import enumOptionsValueForIndex from './enumOptionsValueForIndex'; | ||
import deepEquals from './deepEquals'; | ||
@@ -25,5 +24,5 @@ /** Removes the enum option value at the `valueIndex` from the currently `selected` (list of) value(s). If `selected` is | ||
if (Array.isArray(selected)) { | ||
return selected.filter((v) => !isEqual(v, value)); | ||
return selected.filter((v) => !deepEquals(v, value)); | ||
} | ||
return isEqual(value, selected) ? undefined : selected; | ||
return deepEquals(value, selected) ? undefined : selected; | ||
} |
@@ -1,3 +0,2 @@ | ||
import isEqual from 'lodash/isEqual'; | ||
import deepEquals from './deepEquals'; | ||
import { EnumOptionsType, RJSFSchema, StrictRJSFSchema } from './types'; | ||
@@ -16,5 +15,5 @@ | ||
if (Array.isArray(selected)) { | ||
return selected.some((sel) => isEqual(sel, value)); | ||
return selected.some((sel) => deepEquals(sel, value)); | ||
} | ||
return isEqual(selected, value); | ||
return deepEquals(selected, value); | ||
} |
import get from 'lodash/get'; | ||
import isEqual from 'lodash/isEqual'; | ||
@@ -18,2 +17,3 @@ import { ID_KEY } from '../constants'; | ||
} from '../types'; | ||
import deepEquals from '../deepEquals'; | ||
@@ -71,3 +71,3 @@ /** The type of the map of schema hash to schema | ||
this.schemaMap[key] = identifiedSchema; | ||
} else if (!isEqual(existing, identifiedSchema)) { | ||
} else if (!deepEquals(existing, identifiedSchema)) { | ||
console.error('existing schema:', JSON.stringify(existing, null, 2)); | ||
@@ -96,3 +96,3 @@ console.error('new schema:', JSON.stringify(identifiedSchema, null, 2)); | ||
isValid(schema: S, _formData: T, rootSchema: S): boolean { | ||
if (!isEqual(rootSchema, this.rootSchema)) { | ||
if (!deepEquals(rootSchema, this.rootSchema)) { | ||
throw new Error('Unexpectedly calling isValid() with a rootSchema that differs from the construction rootSchema'); | ||
@@ -99,0 +99,0 @@ } |
import forEach from 'lodash/forEach'; | ||
import isEqual from 'lodash/isEqual'; | ||
import { FormContextType, RJSFSchema, StrictRJSFSchema } from '../types'; | ||
import { PROPERTIES_KEY, ITEMS_KEY } from '../constants'; | ||
import { ITEMS_KEY, PROPERTIES_KEY } from '../constants'; | ||
import ParserValidator, { SchemaMap } from './ParserValidator'; | ||
import { retrieveSchemaInternal, resolveAnyOrOneOfSchemas } from '../schema/retrieveSchema'; | ||
import { resolveAnyOrOneOfSchemas, retrieveSchemaInternal } from '../schema/retrieveSchema'; | ||
import deepEquals from '../deepEquals'; | ||
@@ -27,3 +27,3 @@ /** Recursive function used to parse the given `schema` belonging to the `rootSchema`. The `validator` is used to | ||
schemas.forEach((schema) => { | ||
const sameSchemaIndex = recurseList.findIndex((item) => isEqual(item, schema)); | ||
const sameSchemaIndex = recurseList.findIndex((item) => deepEquals(item, schema)); | ||
if (sameSchemaIndex === -1) { | ||
@@ -30,0 +30,0 @@ recurseList.push(schema); |
@@ -144,8 +144,18 @@ import get from 'lodash/get'; | ||
interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema> { | ||
/** Any defaults provided by the parent field in the schema */ | ||
parentDefaults?: T; | ||
/** The options root schema, used to primarily to look up `$ref`s */ | ||
rootSchema?: S; | ||
/** The current formData, if any, onto which to provide any missing defaults */ | ||
rawFormData?: T; | ||
/** Optional flag, if true, cause undefined values to be added as defaults. | ||
* If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as | ||
* false when computing defaults for any nested object properties. | ||
*/ | ||
includeUndefinedValues?: boolean | 'excludeObjectChildren'; | ||
/** The list of ref names currently being recursed, used to prevent infinite recursion */ | ||
_recurseList?: string[]; | ||
/** Optional configuration object, if provided, allows users to override default form state behavior */ | ||
experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior; | ||
/** Optional flag, if true, indicates this schema was required in the parent schema. */ | ||
required?: boolean; | ||
@@ -159,12 +169,3 @@ } | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param [props] - Optional props for this function | ||
* @param [props.parentDefaults] - Any defaults provided by the parent field in the schema | ||
* @param [props.rootSchema] - The options root schema, used to primarily to look up `$ref`s | ||
* @param [props.rawFormData] - The current formData, if any, onto which to provide any missing defaults | ||
* @param [props.includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults. | ||
* If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as | ||
* false when computing defaults for any nested object properties. | ||
* @param [props._recurseList=[]] - The list of ref names currently being recursed, used to prevent infinite recursion | ||
* @param [props.experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior | ||
* @param [props.required] - Optional flag, if true, indicates this schema was required in the parent schema. | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @returns - The resulting `formData` with all the defaults provided | ||
@@ -175,3 +176,5 @@ */ | ||
rawSchema: S, | ||
{ | ||
computeDefaultsProps: ComputeDefaultsProps<T, S> = {} | ||
): T | T[] | undefined { | ||
const { | ||
parentDefaults, | ||
@@ -184,4 +187,3 @@ rawFormData, | ||
required, | ||
}: ComputeDefaultsProps<T, S> = {} | ||
): T | T[] | undefined { | ||
} = computeDefaultsProps; | ||
const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T; | ||
@@ -209,3 +211,8 @@ const schema: S = isObject(rawSchema) ? rawSchema : ({} as S); | ||
} else if (DEPENDENCIES_KEY in schema) { | ||
const resolvedSchema = resolveDependencies<T, S, F>(validator, schema, rootSchema, false, [], formData); | ||
// Get the default if set from properties to ensure the dependencies conditions are resolved based on it | ||
const defaultFormData: T = { | ||
...formData, | ||
...getDefaultBasedOnSchemaType(validator, schema, computeDefaultsProps, defaults), | ||
}; | ||
const resolvedSchema = resolveDependencies<T, S, F>(validator, schema, rootSchema, false, [], defaultFormData); | ||
schemaToCompute = resolvedSchema[0]; // pick the first element from resolve dependencies | ||
@@ -277,162 +284,236 @@ } else if (isFixedItems(schema)) { | ||
switch (getSchemaType<S>(schema)) { | ||
// We need to recurse for object schema inner default values. | ||
case 'object': { | ||
// This is a custom addition that fixes this issue: | ||
// https://github.com/rjsf-team/react-jsonschema-form/issues/3832 | ||
const retrievedSchema = | ||
experimental_defaultFormStateBehavior?.allOf === 'populateDefaults' && ALL_OF_KEY in schema | ||
? retrieveSchema<T, S, F>(validator, schema, rootSchema, formData) | ||
: schema; | ||
const objectDefaults = Object.keys(retrievedSchema.properties || {}).reduce( | ||
(acc: GenericObjectType, key: string) => { | ||
// Compute the defaults for this node, with the parent defaults we might | ||
// have from a previous run: defaults[key]. | ||
const computedDefault = computeDefaults<T, S, F>(validator, get(retrievedSchema, [PROPERTIES_KEY, key]), { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
includeUndefinedValues: includeUndefinedValues === true, | ||
parentDefaults: get(defaults, [key]), | ||
rawFormData: get(formData, [key]), | ||
required: retrievedSchema.required?.includes(key), | ||
}); | ||
maybeAddDefaultToObject<T>( | ||
acc, | ||
key, | ||
computedDefault, | ||
includeUndefinedValues, | ||
required, | ||
retrievedSchema.required, | ||
experimental_defaultFormStateBehavior | ||
); | ||
return acc; | ||
}, | ||
{} | ||
) as T; | ||
if (retrievedSchema.additionalProperties) { | ||
// as per spec additionalProperties may be either schema or boolean | ||
const additionalPropertiesSchema = isObject(retrievedSchema.additionalProperties) | ||
? retrievedSchema.additionalProperties | ||
: {}; | ||
const defaultBasedOnSchemaType = getDefaultBasedOnSchemaType(validator, schema, computeDefaultsProps, defaults); | ||
const keys = new Set<string>(); | ||
if (isObject(defaults)) { | ||
Object.keys(defaults as GenericObjectType) | ||
.filter((key) => !retrievedSchema.properties || !retrievedSchema.properties[key]) | ||
.forEach((key) => keys.add(key)); | ||
} | ||
const formDataRequired: string[] = []; | ||
Object.keys(formData as GenericObjectType) | ||
return defaultBasedOnSchemaType ?? defaults; | ||
} | ||
/** Computes the default value for objects. | ||
* | ||
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @param defaults - Optional props for this function | ||
* @returns - The default value based on the schema type if they are defined for object or array schemas. | ||
*/ | ||
export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>( | ||
validator: ValidatorType<T, S, F>, | ||
rawSchema: S, | ||
{ | ||
rawFormData, | ||
rootSchema = {} as S, | ||
includeUndefinedValues = false, | ||
_recurseList = [], | ||
experimental_defaultFormStateBehavior = undefined, | ||
required, | ||
}: ComputeDefaultsProps<T, S> = {}, | ||
defaults?: T | T[] | undefined | ||
): T { | ||
{ | ||
const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T; | ||
const schema: S = rawSchema; | ||
// This is a custom addition that fixes this issue: | ||
// https://github.com/rjsf-team/react-jsonschema-form/issues/3832 | ||
const retrievedSchema = | ||
experimental_defaultFormStateBehavior?.allOf === 'populateDefaults' && ALL_OF_KEY in schema | ||
? retrieveSchema<T, S, F>(validator, schema, rootSchema, formData) | ||
: schema; | ||
const objectDefaults = Object.keys(retrievedSchema.properties || {}).reduce( | ||
(acc: GenericObjectType, key: string) => { | ||
// Compute the defaults for this node, with the parent defaults we might | ||
// have from a previous run: defaults[key]. | ||
const computedDefault = computeDefaults<T, S, F>(validator, get(retrievedSchema, [PROPERTIES_KEY, key]), { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
includeUndefinedValues: includeUndefinedValues === true, | ||
parentDefaults: get(defaults, [key]), | ||
rawFormData: get(formData, [key]), | ||
required: retrievedSchema.required?.includes(key), | ||
}); | ||
maybeAddDefaultToObject<T>( | ||
acc, | ||
key, | ||
computedDefault, | ||
includeUndefinedValues, | ||
required, | ||
retrievedSchema.required, | ||
experimental_defaultFormStateBehavior | ||
); | ||
return acc; | ||
}, | ||
{} | ||
) as T; | ||
if (retrievedSchema.additionalProperties) { | ||
// as per spec additionalProperties may be either schema or boolean | ||
const additionalPropertiesSchema = isObject(retrievedSchema.additionalProperties) | ||
? retrievedSchema.additionalProperties | ||
: {}; | ||
const keys = new Set<string>(); | ||
if (isObject(defaults)) { | ||
Object.keys(defaults as GenericObjectType) | ||
.filter((key) => !retrievedSchema.properties || !retrievedSchema.properties[key]) | ||
.forEach((key) => { | ||
keys.add(key); | ||
formDataRequired.push(key); | ||
}); | ||
keys.forEach((key) => { | ||
const computedDefault = computeDefaults(validator, additionalPropertiesSchema as S, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
includeUndefinedValues: includeUndefinedValues === true, | ||
parentDefaults: get(defaults, [key]), | ||
rawFormData: get(formData, [key]), | ||
required: retrievedSchema.required?.includes(key), | ||
}); | ||
// Since these are additional properties we don't need to add the `experimental_defaultFormStateBehavior` prop | ||
maybeAddDefaultToObject<T>( | ||
objectDefaults as GenericObjectType, | ||
key, | ||
computedDefault, | ||
includeUndefinedValues, | ||
required, | ||
formDataRequired | ||
); | ||
.forEach((key) => keys.add(key)); | ||
} | ||
const formDataRequired: string[] = []; | ||
Object.keys(formData as GenericObjectType) | ||
.filter((key) => !retrievedSchema.properties || !retrievedSchema.properties[key]) | ||
.forEach((key) => { | ||
keys.add(key); | ||
formDataRequired.push(key); | ||
}); | ||
} | ||
return objectDefaults; | ||
keys.forEach((key) => { | ||
const computedDefault = computeDefaults(validator, additionalPropertiesSchema as S, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
includeUndefinedValues: includeUndefinedValues === true, | ||
parentDefaults: get(defaults, [key]), | ||
rawFormData: get(formData, [key]), | ||
required: retrievedSchema.required?.includes(key), | ||
}); | ||
// Since these are additional properties we don't need to add the `experimental_defaultFormStateBehavior` prop | ||
maybeAddDefaultToObject<T>( | ||
objectDefaults as GenericObjectType, | ||
key, | ||
computedDefault, | ||
includeUndefinedValues, | ||
required, | ||
formDataRequired | ||
); | ||
}); | ||
} | ||
case 'array': { | ||
const neverPopulate = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'never'; | ||
const ignoreMinItemsFlagSet = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'requiredOnly'; | ||
const isSkipEmptyDefaults = experimental_defaultFormStateBehavior?.emptyObjectFields === 'skipEmptyDefaults'; | ||
const computeSkipPopulate = | ||
experimental_defaultFormStateBehavior?.arrayMinItems?.computeSkipPopulate ?? (() => false); | ||
return objectDefaults; | ||
} | ||
} | ||
const emptyDefault = isSkipEmptyDefaults ? undefined : []; | ||
/** Computes the default value for arrays. | ||
* | ||
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @param defaults - Optional props for this function | ||
* @returns - The default value based on the schema type if they are defined for object or array schemas. | ||
*/ | ||
export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>( | ||
validator: ValidatorType<T, S, F>, | ||
rawSchema: S, | ||
{ | ||
rawFormData, | ||
rootSchema = {} as S, | ||
_recurseList = [], | ||
experimental_defaultFormStateBehavior = undefined, | ||
required, | ||
}: ComputeDefaultsProps<T, S> = {}, | ||
defaults?: T | T[] | undefined | ||
): T | T[] | undefined { | ||
const schema: S = rawSchema; | ||
// Inject defaults into existing array defaults | ||
if (Array.isArray(defaults)) { | ||
defaults = defaults.map((item, idx) => { | ||
const schemaItem: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Fallback, idx); | ||
return computeDefaults<T, S, F>(validator, schemaItem, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
parentDefaults: item, | ||
required, | ||
}); | ||
}) as T[]; | ||
} | ||
const neverPopulate = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'never'; | ||
const ignoreMinItemsFlagSet = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'requiredOnly'; | ||
const isSkipEmptyDefaults = experimental_defaultFormStateBehavior?.emptyObjectFields === 'skipEmptyDefaults'; | ||
const computeSkipPopulate = | ||
experimental_defaultFormStateBehavior?.arrayMinItems?.computeSkipPopulate ?? (() => false); | ||
// Deeply inject defaults into already existing form data | ||
if (Array.isArray(rawFormData)) { | ||
const schemaItem: S = getInnerSchemaForArrayItem<S>(schema); | ||
if (neverPopulate) { | ||
defaults = rawFormData; | ||
} else { | ||
defaults = rawFormData.map((item: T, idx: number) => { | ||
return computeDefaults<T, S, F>(validator, schemaItem, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
rawFormData: item, | ||
parentDefaults: get(defaults, [idx]), | ||
required, | ||
}); | ||
}) as T[]; | ||
} | ||
} | ||
const emptyDefault = isSkipEmptyDefaults ? undefined : []; | ||
if (neverPopulate) { | ||
return defaults ?? emptyDefault; | ||
} | ||
if (ignoreMinItemsFlagSet && !required) { | ||
// If no form data exists or defaults are set leave the field empty/non-existent, otherwise | ||
// return form data/defaults | ||
return defaults ? defaults : undefined; | ||
} | ||
// Inject defaults into existing array defaults | ||
if (Array.isArray(defaults)) { | ||
defaults = defaults.map((item, idx) => { | ||
const schemaItem: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Fallback, idx); | ||
return computeDefaults<T, S, F>(validator, schemaItem, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
parentDefaults: item, | ||
required, | ||
}); | ||
}) as T[]; | ||
} | ||
const defaultsLength = Array.isArray(defaults) ? defaults.length : 0; | ||
if ( | ||
!schema.minItems || | ||
isMultiSelect<T, S, F>(validator, schema, rootSchema) || | ||
computeSkipPopulate<T, S, F>(validator, schema, rootSchema) || | ||
schema.minItems <= defaultsLength | ||
) { | ||
return defaults ? defaults : emptyDefault; | ||
} | ||
const defaultEntries: T[] = (defaults || []) as T[]; | ||
const fillerSchema: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Invert); | ||
const fillerDefault = fillerSchema.default; | ||
// Calculate filler entries for remaining items (minItems - existing raw data/defaults) | ||
const fillerEntries: T[] = new Array(schema.minItems - defaultsLength).fill( | ||
computeDefaults<any, S, F>(validator, fillerSchema, { | ||
parentDefaults: fillerDefault, | ||
// Deeply inject defaults into already existing form data | ||
if (Array.isArray(rawFormData)) { | ||
const schemaItem: S = getInnerSchemaForArrayItem<S>(schema); | ||
if (neverPopulate) { | ||
defaults = rawFormData; | ||
} else { | ||
defaults = rawFormData.map((item: T, idx: number) => { | ||
return computeDefaults<T, S, F>(validator, schemaItem, { | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
rawFormData: item, | ||
parentDefaults: get(defaults, [idx]), | ||
required, | ||
}) | ||
) as T[]; | ||
// then fill up the rest with either the item default or empty, up to minItems | ||
return defaultEntries.concat(fillerEntries); | ||
}); | ||
}) as T[]; | ||
} | ||
} | ||
return defaults; | ||
if (neverPopulate) { | ||
return defaults ?? emptyDefault; | ||
} | ||
if (ignoreMinItemsFlagSet && !required) { | ||
// If no form data exists or defaults are set leave the field empty/non-existent, otherwise | ||
// return form data/defaults | ||
return defaults ? defaults : undefined; | ||
} | ||
const defaultsLength = Array.isArray(defaults) ? defaults.length : 0; | ||
if ( | ||
!schema.minItems || | ||
isMultiSelect<T, S, F>(validator, schema, rootSchema) || | ||
computeSkipPopulate<T, S, F>(validator, schema, rootSchema) || | ||
schema.minItems <= defaultsLength | ||
) { | ||
return defaults ? defaults : emptyDefault; | ||
} | ||
const defaultEntries: T[] = (defaults || []) as T[]; | ||
const fillerSchema: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Invert); | ||
const fillerDefault = fillerSchema.default; | ||
// Calculate filler entries for remaining items (minItems - existing raw data/defaults) | ||
const fillerEntries: T[] = new Array(schema.minItems - defaultsLength).fill( | ||
computeDefaults<any, S, F>(validator, fillerSchema, { | ||
parentDefaults: fillerDefault, | ||
rootSchema, | ||
_recurseList, | ||
experimental_defaultFormStateBehavior, | ||
required, | ||
}) | ||
) as T[]; | ||
// then fill up the rest with either the item default or empty, up to minItems | ||
return defaultEntries.concat(fillerEntries); | ||
} | ||
/** Computes the default value based on the schema type. | ||
* | ||
* @param validator - an implementation of the `ValidatorType` interface that will be used when necessary | ||
* @param rawSchema - The schema for which the default state is desired | ||
* @param {ComputeDefaultsProps} computeDefaultsProps - Optional props for this function | ||
* @param defaults - Optional props for this function | ||
* @returns - The default value based on the schema type if they are defined for object or array schemas. | ||
*/ | ||
export function getDefaultBasedOnSchemaType< | ||
T = any, | ||
S extends StrictRJSFSchema = RJSFSchema, | ||
F extends FormContextType = any | ||
>( | ||
validator: ValidatorType<T, S, F>, | ||
rawSchema: S, | ||
computeDefaultsProps: ComputeDefaultsProps<T, S> = {}, | ||
defaults?: T | T[] | undefined | ||
): T | T[] | void { | ||
switch (getSchemaType<S>(rawSchema)) { | ||
// We need to recurse for object schema inner default values. | ||
case 'object': { | ||
return getObjectDefaults(validator, rawSchema, computeDefaultsProps, defaults); | ||
} | ||
case 'array': { | ||
return getArrayDefaults(validator, rawSchema, computeDefaultsProps, defaults); | ||
} | ||
} | ||
} | ||
/** Returns the superset of `formData` that includes the given set updated to include any missing fields that have | ||
@@ -439,0 +520,0 @@ * computed to have defaults provided in the `schema`. |
import get from 'lodash/get'; | ||
import isEqual from 'lodash/isEqual'; | ||
import set from 'lodash/set'; | ||
@@ -18,6 +17,6 @@ import times from 'lodash/times'; | ||
IF_KEY, | ||
ITEMS_KEY, | ||
ONE_OF_KEY, | ||
PROPERTIES_KEY, | ||
REF_KEY, | ||
PROPERTIES_KEY, | ||
ITEMS_KEY, | ||
} from '../constants'; | ||
@@ -31,2 +30,3 @@ import findSchemaDefinition, { splitKeyElementFromObject } from '../findSchemaDefinition'; | ||
import getFirstMatchingOption from './getFirstMatchingOption'; | ||
import deepEquals from '../deepEquals'; | ||
@@ -201,3 +201,6 @@ /** Retrieves an expanded schema that has had all of its conditions, additional properties, references and dependencies | ||
const allPermutations = getAllPermutationsOfXxxOf<S>(allOfSchemaElements); | ||
return allPermutations.map((permutation) => ({ ...schema, allOf: permutation })); | ||
return allPermutations.map((permutation) => ({ | ||
...schema, | ||
allOf: permutation, | ||
})); | ||
} | ||
@@ -299,3 +302,3 @@ // No $ref or dependencies or allOf attribute was found, returning the original schema. | ||
return isEqual(schema, resolvedSchema) ? schema : resolvedSchema; | ||
return deepEquals(schema, resolvedSchema) ? schema : resolvedSchema; | ||
} | ||
@@ -302,0 +305,0 @@ |
import get from 'lodash/get'; | ||
import isEqual from 'lodash/isEqual'; | ||
@@ -9,2 +8,3 @@ import { ALL_OF_KEY, DEPENDENCIES_KEY, ID_KEY, ITEMS_KEY, PROPERTIES_KEY, REF_KEY } from '../constants'; | ||
import getSchemaType from '../getSchemaType'; | ||
import deepEquals from '../deepEquals'; | ||
@@ -36,3 +36,3 @@ /** An internal helper that generates an `IdSchema` object for the `schema`, recursively with protection against | ||
const _schema = retrieveSchema<T, S, F>(validator, schema, rootSchema, formData); | ||
const sameSchemaIndex = _recurseList.findIndex((item) => isEqual(item, _schema)); | ||
const sameSchemaIndex = _recurseList.findIndex((item) => deepEquals(item, _schema)); | ||
if (sameSchemaIndex === -1) { | ||
@@ -39,0 +39,0 @@ return toIdSchemaInternal<T, S, F>( |
import get from 'lodash/get'; | ||
import isEqual from 'lodash/isEqual'; | ||
import set from 'lodash/set'; | ||
import { | ||
ADDITIONAL_PROPERTIES_KEY, | ||
ALL_OF_KEY, | ||
ANY_OF_KEY, | ||
ADDITIONAL_PROPERTIES_KEY, | ||
DEPENDENCIES_KEY, | ||
@@ -21,2 +20,3 @@ ITEMS_KEY, | ||
import retrieveSchema from './retrieveSchema'; | ||
import deepEquals from '../deepEquals'; | ||
@@ -44,3 +44,3 @@ /** An internal helper that generates an `PathSchema` object for the `schema`, recursively with protection against | ||
const _schema = retrieveSchema<T, S, F>(validator, schema, rootSchema, formData); | ||
const sameSchemaIndex = _recurseList.findIndex((item) => isEqual(item, _schema)); | ||
const sameSchemaIndex = _recurseList.findIndex((item) => deepEquals(item, _schema)); | ||
if (sameSchemaIndex === -1) { | ||
@@ -47,0 +47,0 @@ return toPathSchemaInternal<T, S, F>( |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1668972
20109
7
+ Addedfast-equals@^5.0.1
+ Addedfast-equals@5.0.1(transitive)