diff --git a/CHANGELOG.md b/CHANGELOG.md index 5233dddb83..c3c5d9ea45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,17 +17,24 @@ should change the heading of the (upcoming) version to include a major version b --> # 5.12.0 -## Dev / playground +## @rjsf/utils + +- Experimental feature: + - Added `experimental_defaultFormStateBehavior = { arrayMinItems: { populate: 'never' } }` (feature [#3796](https://github.com/rjsf-team/react-jsonschema-form/issues/3796)) + +## @rjsf/validator-ajv8 + +- Exposing new function `compileSchemaValidatorsCode` to allow creating precompiled validator without a file. This is useful in case when precompiled validator is to be created dynamically. [#3793](https://github.com/rjsf-team/react-jsonschema-form/pull/3793) + +## Dev / docs / playground - update playground vite config to use sources directly, allowing to reload changes in it without additional build step - moving from `dts-cli` to use individual dev tools directly, updating package publish config - tsc for generating type definitions and esm modules - esbuild for CJS bundle - rollup for UMD bundle - -## @rjsf/validator-ajv8 - -- Exposing new function `compileSchemaValidatorsCode` to allow creating precompiled validator without a file. This is useful in case when precompiled validator is to be created dynamically. [#3793](https://github.com/rjsf-team/react-jsonschema-form/pull/3793) +- Updated the `form-props` documentation `arrayMinItems`, added description for `never`. +- Updated the `playground` to add the option for the new `arrayMinItems.populate = 'never'`. # 5.11.2 @@ -105,7 +112,7 @@ should change the heading of the (upcoming) version to include a major version b - Updated the `utility-functions` documentation to add the new `mergeExtraArrayDefaults` flag for the `mergeDefaultWithFormData()` function - Updated the `form-props` documentation to update the `arrayMinItems` documentation for the new object behavior -- Updated the `playground` to add a checkbox for the new `arrayMinItems.mergeExtraDefaults` flag +- Updated the `playground` to add a checkbox for the new `arrayMinItems.mergeExtraDefaults` flag # 5.8.2 diff --git a/packages/docs/docs/api-reference/form-props.md b/packages/docs/docs/api-reference/form-props.md index ca35faf0a0..147c1b6b29 100644 --- a/packages/docs/docs/api-reference/form-props.md +++ b/packages/docs/docs/api-reference/form-props.md @@ -77,10 +77,11 @@ When not specified, it defaults to `{ populate: 'all', mergeExtraDefaults: false Optional enumerated flag controlling how array minItems are populated, defaulting to `all`: -| Flag Value | Description | -| -------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| `all` | Legacy behavior - populate minItems entries with default values initially and include empty array when no values have been defined. | -| `requiredOnly` | Ignore `minItems` on a field when calculating defaults unless the field is required. | +| Flag Value | Description | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `all` | Legacy behavior - populate minItems entries with default values initially and include empty array when no values have been defined. | +| `requiredOnly` | Ignore `minItems` on a field when calculating defaults unless the field is required. | +| `never` | Ignore `minItems` on a field when calculating defaults for required and non-required. Value will set only if defined `default` and from `formData` | #### `arrayMinItems.mergeExtraDefaults` diff --git a/packages/playground/src/components/Header.tsx b/packages/playground/src/components/Header.tsx index fe46917b93..782eacccb1 100644 --- a/packages/playground/src/components/Header.tsx +++ b/packages/playground/src/components/Header.tsx @@ -93,9 +93,14 @@ const liveSettingsSelectSchema: RJSFSchema = { }, { type: 'string', - title: 'Ignore minItems unless field is required', + title: 'Only populate minItems with default values when field is required', enum: ['requiredOnly'], }, + { + type: 'string', + title: 'Never populate minItems with default values', + enum: ['never'], + }, ], }, mergeExtraDefaults: { diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index 3e41094770..de7402dd8e 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -321,6 +321,9 @@ export function computeDefaults { @@ -338,19 +341,25 @@ export function computeDefaults(schema); - defaults = rawFormData.map((item: T, idx: number) => { - return computeDefaults(validator, schemaItem, { - rootSchema, - _recurseList, - experimental_defaultFormStateBehavior, - rawFormData: item, - parentDefaults: get(defaults, [idx]), - required, - }); - }) as T[]; + if (neverPopulate) { + defaults = rawFormData; + } else { + defaults = rawFormData.map((item: T, idx: number) => { + return computeDefaults(validator, schemaItem, { + rootSchema, + _recurseList, + experimental_defaultFormStateBehavior, + rawFormData: item, + parentDefaults: get(defaults, [idx]), + required, + }); + }) as T[]; + } } - const ignoreMinItemsFlagSet = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'requiredOnly'; + if (neverPopulate) { + return defaults ?? []; + } if (ignoreMinItemsFlagSet && !required) { // If no form data exists or defaults are set leave the field empty/non-existent, otherwise // return form data/defaults diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index f40e98c34b..4e40c0d26b 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -38,8 +38,9 @@ export type Experimental_ArrayMinItems = { * - `all`: Legacy behavior, populate minItems entries with default values initially and include an empty array when * no values have been defined. * - `requiredOnly`: Ignore `minItems` on a field when calculating defaults unless the field is required. + * - `never`: Ignore `minItems` on a field even the field is required. */ - populate?: 'all' | 'requiredOnly'; + populate?: 'all' | 'requiredOnly' | 'never'; /** When `formData` is provided and does not contain `minItems` worth of data, this flag (`false` by default) controls * whether the extra data provided by the defaults is appended onto the existing `formData` items to ensure the * `minItems` condition is met. When false (legacy behavior), only the `formData` provided is merged into the default diff --git a/packages/utils/test/schema/getDefaultFormStateTest.ts b/packages/utils/test/schema/getDefaultFormStateTest.ts index 86ad2aa2c2..b844bdc5f6 100644 --- a/packages/utils/test/schema/getDefaultFormStateTest.ts +++ b/packages/utils/test/schema/getDefaultFormStateTest.ts @@ -412,6 +412,226 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType ).toEqual({ requiredArray: ['default0', 'default0'] }); }); }); + describe('default form state behavior: arrayMinItems.populate = "never"', () => { + it('should not be filled if minItems defined and required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + requiredArray: { + type: 'array', + items: { type: 'string' }, + minItems: 1, + }, + }, + required: ['requiredArray'], + }; + + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ requiredArray: [] }); + }); + it('should not be filled if minItems defined and non required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + items: { type: 'string' }, + minItems: 1, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: [] }); + }); + + it('should be filled with default values if required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + requiredArray: { + type: 'array', + default: ['raw0'], + items: { type: 'string' }, + minItems: 1, + }, + }, + required: ['requiredArray'], + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ requiredArray: ['raw0'] }); + }); + + it('should be filled with default values if non required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + default: ['raw0'], + items: { type: 'string' }, + minItems: 1, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: ['raw0'] }); + }); + + it('should be filled with default values partly and not fill others', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + default: ['raw0'], + items: { type: 'string' }, + minItems: 2, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + rawFormData: { nonRequiredArray: ['raw1'] }, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: ['raw1'] }); + }); + + it('should not add items to formData', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + items: { type: 'string' }, + minItems: 2, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + rawFormData: { nonRequiredArray: ['not add'] }, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: ['not add'] }); + }); + + it('should be empty array if minItems not defined and required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + requiredArray: { + type: 'array', + items: { type: 'string' }, + }, + }, + required: ['requiredArray'], + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ requiredArray: [] }); + }); + it('should be empty array if minItems not defined and non required', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + items: { type: 'string' }, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: [] }); + }); + + it('injecting data should be stopped at the top level of tree', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + minItems: 2, + items: { + type: 'object', + properties: { + nestedValue: { type: 'string' }, + nestedArray: { type: 'array', items: { type: 'string' } }, + }, + }, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ nonRequiredArray: [] }); + }); + it('no injecting for childs', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + nonRequiredArray: { + type: 'array', + minItems: 2, + items: { + type: 'object', + properties: { + nestedValue: { type: 'string' }, + nestedArray: { type: 'array', minItems: 3, items: { type: 'string' } }, + }, + }, + }, + }, + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + rawFormData: { + nonRequiredArray: [ + { + nestedArray: ['raw0'], + }, + ], + }, + experimental_defaultFormStateBehavior: { arrayMinItems: { populate: 'never' } }, + }) + ).toStrictEqual({ + nonRequiredArray: [ + { + nestedArray: ['raw0'], + }, + ], + }); + }); + }); describe('default form state behavior: emptyObjectFields = "populateRequiredDefaults"', () => { it('test an object with an optional property that has a nested required property', () => { const schema: RJSFSchema = {