@rjsf/utils 5.21.2 → 5.22.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.
@@ -1,6 +1,7 @@
1
1
  import deepEquals from './deepEquals';
2
2
  import {
3
3
  ErrorSchema,
4
+ Experimental_CustomMergeAllOf,
4
5
  Experimental_DefaultFormStateBehavior,
5
6
  FormContextType,
6
7
  GlobalUISchemaOptions,
@@ -30,9 +31,10 @@ import {
30
31
  } from './schema';
31
32
 
32
33
  /** The `SchemaUtils` class provides a wrapper around the publicly exported APIs in the `utils/schema` directory such
33
- * that one does not have to explicitly pass the `validator`, `rootSchema`, or `experimental_defaultFormStateBehavior` to each method.
34
- * Since these generally do not change across a `Form`, this allows for providing a simplified set of APIs to the
35
- * `@rjsf/core` components and the various themes as well. This class implements the `SchemaUtilsType` interface.
34
+ * that one does not have to explicitly pass the `validator`, `rootSchema`, `experimental_defaultFormStateBehavior` or
35
+ * `experimental_customMergeAllOf` to each method. Since these generally do not change across a `Form`, this allows for
36
+ * providing a simplified set of APIs to the `@rjsf/core` components and the various themes as well. This class
37
+ * implements the `SchemaUtilsType` interface.
36
38
  */
37
39
  class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>
38
40
  implements SchemaUtilsType<T, S, F>
@@ -40,21 +42,25 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
40
42
  rootSchema: S;
41
43
  validator: ValidatorType<T, S, F>;
42
44
  experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior;
45
+ experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;
43
46
 
44
47
  /** Constructs the `SchemaUtils` instance with the given `validator` and `rootSchema` stored as instance variables
45
48
  *
46
49
  * @param validator - An implementation of the `ValidatorType` interface that will be forwarded to all the APIs
47
50
  * @param rootSchema - The root schema that will be forwarded to all the APIs
48
51
  * @param experimental_defaultFormStateBehavior - Configuration flags to allow users to override default form state behavior
52
+ * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
49
53
  */
50
54
  constructor(
51
55
  validator: ValidatorType<T, S, F>,
52
56
  rootSchema: S,
53
- experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior
57
+ experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior,
58
+ experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>
54
59
  ) {
55
60
  this.rootSchema = rootSchema;
56
61
  this.validator = validator;
57
62
  this.experimental_defaultFormStateBehavior = experimental_defaultFormStateBehavior;
63
+ this.experimental_customMergeAllOf = experimental_customMergeAllOf;
58
64
  }
59
65
 
60
66
  /** Returns the `ValidatorType` in the `SchemaUtilsType`
@@ -72,12 +78,14 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
72
78
  * @param validator - An implementation of the `ValidatorType` interface that will be compared against the current one
73
79
  * @param rootSchema - The root schema that will be compared against the current one
74
80
  * @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
81
+ * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
75
82
  * @returns - True if the `SchemaUtilsType` differs from the given `validator` or `rootSchema`
76
83
  */
77
84
  doesSchemaUtilsDiffer(
78
85
  validator: ValidatorType<T, S, F>,
79
86
  rootSchema: S,
80
- experimental_defaultFormStateBehavior = {}
87
+ experimental_defaultFormStateBehavior = {},
88
+ experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>
81
89
  ): boolean {
82
90
  if (!validator || !rootSchema) {
83
91
  return false;
@@ -85,7 +93,8 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
85
93
  return (
86
94
  this.validator !== validator ||
87
95
  !deepEquals(this.rootSchema, rootSchema) ||
88
- !deepEquals(this.experimental_defaultFormStateBehavior, experimental_defaultFormStateBehavior)
96
+ !deepEquals(this.experimental_defaultFormStateBehavior, experimental_defaultFormStateBehavior) ||
97
+ this.experimental_customMergeAllOf !== experimental_customMergeAllOf
89
98
  );
90
99
  }
91
100
 
@@ -110,7 +119,8 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
110
119
  formData,
111
120
  this.rootSchema,
112
121
  includeUndefinedValues,
113
- this.experimental_defaultFormStateBehavior
122
+ this.experimental_defaultFormStateBehavior,
123
+ this.experimental_customMergeAllOf
114
124
  );
115
125
  }
116
126
 
@@ -234,7 +244,13 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
234
244
  * @returns - The schema having its conditions, additional properties, references and dependencies resolved
235
245
  */
236
246
  retrieveSchema(schema: S, rawFormData?: T) {
237
- return retrieveSchema<T, S, F>(this.validator, schema, this.rootSchema, rawFormData);
247
+ return retrieveSchema<T, S, F>(
248
+ this.validator,
249
+ schema,
250
+ this.rootSchema,
251
+ rawFormData,
252
+ this.experimental_customMergeAllOf
253
+ );
238
254
  }
239
255
 
240
256
  /** Sanitize the `data` associated with the `oldSchema` so it is considered appropriate for the `newSchema`. If the
@@ -262,7 +278,16 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
262
278
  * @returns - The `IdSchema` object for the `schema`
263
279
  */
264
280
  toIdSchema(schema: S, id?: string | null, formData?: T, idPrefix = 'root', idSeparator = '_'): IdSchema<T> {
265
- return toIdSchema<T, S, F>(this.validator, schema, id, this.rootSchema, formData, idPrefix, idSeparator);
281
+ return toIdSchema<T, S, F>(
282
+ this.validator,
283
+ schema,
284
+ id,
285
+ this.rootSchema,
286
+ formData,
287
+ idPrefix,
288
+ idSeparator,
289
+ this.experimental_customMergeAllOf
290
+ );
266
291
  }
267
292
 
268
293
  /** Generates an `PathSchema` object for the `schema`, recursively
@@ -283,6 +308,7 @@ class SchemaUtils<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
283
308
  * @param validator - an implementation of the `ValidatorType` interface that will be forwarded to all the APIs
284
309
  * @param rootSchema - The root schema that will be forwarded to all the APIs
285
310
  * @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
311
+ * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
286
312
  * @returns - An implementation of a `SchemaUtilsType` interface
287
313
  */
288
314
  export default function createSchemaUtils<
@@ -292,7 +318,13 @@ export default function createSchemaUtils<
292
318
  >(
293
319
  validator: ValidatorType<T, S, F>,
294
320
  rootSchema: S,
295
- experimental_defaultFormStateBehavior = {}
321
+ experimental_defaultFormStateBehavior = {},
322
+ experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>
296
323
  ): SchemaUtilsType<T, S, F> {
297
- return new SchemaUtils<T, S, F>(validator, rootSchema, experimental_defaultFormStateBehavior);
324
+ return new SchemaUtils<T, S, F>(
325
+ validator,
326
+ rootSchema,
327
+ experimental_defaultFormStateBehavior,
328
+ experimental_customMergeAllOf
329
+ );
298
330
  }
@@ -12,23 +12,31 @@ import { GenericObjectType } from '../src';
12
12
  * are deeply merged; additional entries from the defaults are ignored unless `mergeExtraArrayDefaults` is true, in
13
13
  * which case the extras are appended onto the end of the form data
14
14
  * - when the array is not set in form data, the default is copied over
15
- * - scalars are overwritten/set by form data
15
+ * - scalars are overwritten/set by form data unless undefined and there is a default AND `defaultSupercedesUndefined`
16
+ * is true
16
17
  *
17
18
  * @param [defaults] - The defaults to merge
18
19
  * @param [formData] - The form data into which the defaults will be merged
19
20
  * @param [mergeExtraArrayDefaults=false] - If true, any additional default array entries are appended onto the formData
21
+ * @param [defaultSupercedesUndefined=false] - If true, an explicit undefined value will be overwritten by the default value
20
22
  * @returns - The resulting merged form data with defaults
21
23
  */
22
24
  export default function mergeDefaultsWithFormData<T = any>(
23
25
  defaults?: T,
24
26
  formData?: T,
25
- mergeExtraArrayDefaults = false
27
+ mergeExtraArrayDefaults = false,
28
+ defaultSupercedesUndefined = false
26
29
  ): T | undefined {
27
30
  if (Array.isArray(formData)) {
28
31
  const defaultsArray = Array.isArray(defaults) ? defaults : [];
29
32
  const mapped = formData.map((value, idx) => {
30
33
  if (defaultsArray[idx]) {
31
- return mergeDefaultsWithFormData<any>(defaultsArray[idx], value, mergeExtraArrayDefaults);
34
+ return mergeDefaultsWithFormData<any>(
35
+ defaultsArray[idx],
36
+ value,
37
+ mergeExtraArrayDefaults,
38
+ defaultSupercedesUndefined
39
+ );
32
40
  }
33
41
  return value;
34
42
  });
@@ -44,10 +52,14 @@ export default function mergeDefaultsWithFormData<T = any>(
44
52
  acc[key as keyof T] = mergeDefaultsWithFormData<T>(
45
53
  defaults ? get(defaults, key) : {},
46
54
  get(formData, key),
47
- mergeExtraArrayDefaults
55
+ mergeExtraArrayDefaults,
56
+ defaultSupercedesUndefined
48
57
  );
49
58
  return acc;
50
59
  }, acc);
51
60
  }
61
+ if (defaultSupercedesUndefined && formData === undefined) {
62
+ return defaults;
63
+ }
52
64
  return formData;
53
65
  }
@@ -51,7 +51,7 @@ export function calculateIndexScore<T = any, S extends StrictRJSFSchema = RJSFSc
51
51
  validator: ValidatorType<T, S, F>,
52
52
  rootSchema: S,
53
53
  schema?: S,
54
- formData: any = {}
54
+ formData?: any
55
55
  ): number {
56
56
  let totalScore = 0;
57
57
  if (schema) {
@@ -83,7 +83,11 @@ export function calculateIndexScore<T = any, S extends StrictRJSFSchema = RJSFSc
83
83
  );
84
84
  }
85
85
  if (value.type === 'object') {
86
- return score + calculateIndexScore<T, S, F>(validator, rootSchema, value as S, formValue || {});
86
+ if (isObject(formValue)) {
87
+ // If the structure is matching then give it a little boost in score
88
+ score += 1;
89
+ }
90
+ return score + calculateIndexScore<T, S, F>(validator, rootSchema, value as S, formValue);
87
91
  }
88
92
  if (value.type === guessType(formValue)) {
89
93
  // If the types match, then we bump the score by one
@@ -3,6 +3,7 @@ import isEmpty from 'lodash/isEmpty';
3
3
 
4
4
  import {
5
5
  ANY_OF_KEY,
6
+ CONST_KEY,
6
7
  DEFAULT_KEY,
7
8
  DEPENDENCIES_KEY,
8
9
  PROPERTIES_KEY,
@@ -20,6 +21,7 @@ import mergeDefaultsWithFormData from '../mergeDefaultsWithFormData';
20
21
  import mergeObjects from '../mergeObjects';
21
22
  import mergeSchemas from '../mergeSchemas';
22
23
  import {
24
+ Experimental_CustomMergeAllOf,
23
25
  Experimental_DefaultFormStateBehavior,
24
26
  FormContextType,
25
27
  GenericObjectType,
@@ -29,6 +31,8 @@ import {
29
31
  } from '../types';
30
32
  import isMultiSelect from './isMultiSelect';
31
33
  import retrieveSchema, { resolveDependencies } from './retrieveSchema';
34
+ import isConstant from '../isConstant';
35
+ import { JSONSchema7Object } from 'json-schema';
32
36
 
33
37
  /** Enum that indicates how `schema.additionalItems` should be handled by the `getInnerSchemaForArrayItem()` function.
34
38
  */
@@ -92,6 +96,7 @@ export function getInnerSchemaForArrayItem<S extends StrictRJSFSchema = RJSFSche
92
96
  * @param requiredFields - The list of fields that are required
93
97
  * @param experimental_defaultFormStateBehavior - Optional configuration object, if provided, allows users to override
94
98
  * default form state behavior
99
+ * @param isConst - Optional flag, if true, indicates that the schema has a const property defined, thus we should always return the computedDefault since it's coming from the const.
95
100
  */
96
101
  function maybeAddDefaultToObject<T = any>(
97
102
  obj: GenericObjectType,
@@ -100,10 +105,13 @@ function maybeAddDefaultToObject<T = any>(
100
105
  includeUndefinedValues: boolean | 'excludeObjectChildren',
101
106
  isParentRequired?: boolean,
102
107
  requiredFields: string[] = [],
103
- experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior = {}
108
+ experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior = {},
109
+ isConst = false
104
110
  ) {
105
111
  const { emptyObjectFields = 'populateAllDefaults' } = experimental_defaultFormStateBehavior;
106
- if (includeUndefinedValues) {
112
+ if (includeUndefinedValues || isConst) {
113
+ // If includeUndefinedValues
114
+ // Or if the schema has a const property defined, then we should always return the computedDefault since it's coming from the const.
107
115
  obj[key] = computedDefault;
108
116
  } else if (emptyObjectFields !== 'skipDefaults') {
109
117
  if (isObject(computedDefault)) {
@@ -156,6 +164,8 @@ interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema>
156
164
  _recurseList?: string[];
157
165
  /** Optional configuration object, if provided, allows users to override default form state behavior */
158
166
  experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior;
167
+ /** Optional function that allows for custom merging of `allOf` schemas */
168
+ experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;
159
169
  /** Optional flag, if true, indicates this schema was required in the parent schema. */
160
170
  required?: boolean;
161
171
  }
@@ -180,6 +190,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
180
190
  includeUndefinedValues = false,
181
191
  _recurseList = [],
182
192
  experimental_defaultFormStateBehavior = undefined,
193
+ experimental_customMergeAllOf = undefined,
183
194
  required,
184
195
  } = computeDefaultsProps;
185
196
  const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
@@ -190,7 +201,9 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
190
201
  let schemaToCompute: S | null = null;
191
202
  let updatedRecurseList = _recurseList;
192
203
 
193
- if (isObject(defaults) && isObject(schema.default)) {
204
+ if (isConstant(schema)) {
205
+ defaults = schema.const as unknown as T;
206
+ } else if (isObject(defaults) && isObject(schema.default)) {
194
207
  // For object defaults, only override parent defaults that are defined in
195
208
  // schema.default.
196
209
  defaults = mergeObjects(defaults!, schema.default as GenericObjectType) as T;
@@ -209,7 +222,15 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
209
222
  ...formData,
210
223
  ...getDefaultBasedOnSchemaType(validator, schema, computeDefaultsProps, defaults),
211
224
  };
212
- const resolvedSchema = resolveDependencies<T, S, F>(validator, schema, rootSchema, false, [], defaultFormData);
225
+ const resolvedSchema = resolveDependencies<T, S, F>(
226
+ validator,
227
+ schema,
228
+ rootSchema,
229
+ false,
230
+ [],
231
+ defaultFormData,
232
+ experimental_customMergeAllOf
233
+ );
213
234
  schemaToCompute = resolvedSchema[0]; // pick the first element from resolve dependencies
214
235
  } else if (isFixedItems(schema)) {
215
236
  defaults = (schema.items! as S[]).map((itemSchema: S, idx: number) =>
@@ -298,6 +319,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
298
319
  includeUndefinedValues = false,
299
320
  _recurseList = [],
300
321
  experimental_defaultFormStateBehavior = undefined,
322
+ experimental_customMergeAllOf = undefined,
301
323
  required,
302
324
  }: ComputeDefaultsProps<T, S> = {},
303
325
  defaults?: T | T[] | undefined
@@ -309,16 +331,22 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
309
331
  // https://github.com/rjsf-team/react-jsonschema-form/issues/3832
310
332
  const retrievedSchema =
311
333
  experimental_defaultFormStateBehavior?.allOf === 'populateDefaults' && ALL_OF_KEY in schema
312
- ? retrieveSchema<T, S, F>(validator, schema, rootSchema, formData)
334
+ ? retrieveSchema<T, S, F>(validator, schema, rootSchema, formData, experimental_customMergeAllOf)
313
335
  : schema;
336
+ const parentConst = retrievedSchema[CONST_KEY];
314
337
  const objectDefaults = Object.keys(retrievedSchema.properties || {}).reduce(
315
338
  (acc: GenericObjectType, key: string) => {
339
+ const propertySchema = get(retrievedSchema, [PROPERTIES_KEY, key]);
340
+ // Check if the parent schema has a const property defined, then we should always return the computedDefault since it's coming from the const.
341
+ const hasParentConst = isObject(parentConst) && (parentConst as JSONSchema7Object)[key] !== undefined;
342
+ const hasConst = (isObject(propertySchema) && CONST_KEY in propertySchema) || hasParentConst;
316
343
  // Compute the defaults for this node, with the parent defaults we might
317
344
  // have from a previous run: defaults[key].
318
- const computedDefault = computeDefaults<T, S, F>(validator, get(retrievedSchema, [PROPERTIES_KEY, key]), {
345
+ const computedDefault = computeDefaults<T, S, F>(validator, propertySchema, {
319
346
  rootSchema,
320
347
  _recurseList,
321
348
  experimental_defaultFormStateBehavior,
349
+ experimental_customMergeAllOf,
322
350
  includeUndefinedValues: includeUndefinedValues === true,
323
351
  parentDefaults: get(defaults, [key]),
324
352
  rawFormData: get(formData, [key]),
@@ -331,7 +359,8 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
331
359
  includeUndefinedValues,
332
360
  required,
333
361
  retrievedSchema.required,
334
- experimental_defaultFormStateBehavior
362
+ experimental_defaultFormStateBehavior,
363
+ hasConst
335
364
  );
336
365
  return acc;
337
366
  },
@@ -444,13 +473,17 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
444
473
  }
445
474
  }
446
475
 
447
- if (neverPopulate) {
448
- return defaults ?? emptyDefault;
449
- }
450
- if (ignoreMinItemsFlagSet && !required) {
451
- // If no form data exists or defaults are set leave the field empty/non-existent, otherwise
452
- // return form data/defaults
453
- return defaults ? defaults : undefined;
476
+ // Check if the schema has a const property defined, then we should always return the computedDefault since it's coming from the const.
477
+ const hasConst = isObject(schema) && CONST_KEY in schema;
478
+ if (hasConst === false) {
479
+ if (neverPopulate) {
480
+ return defaults ?? emptyDefault;
481
+ }
482
+ if (ignoreMinItemsFlagSet && !required) {
483
+ // If no form data exists or defaults are set leave the field empty/non-existent, otherwise
484
+ // return form data/defaults
485
+ return defaults ? defaults : undefined;
486
+ }
454
487
  }
455
488
 
456
489
  const defaultsLength = Array.isArray(defaults) ? defaults.length : 0;
@@ -521,6 +554,7 @@ export function getDefaultBasedOnSchemaType<
521
554
  * If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as
522
555
  * false when computing defaults for any nested object properties.
523
556
  * @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
557
+ * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
524
558
  * @returns - The resulting `formData` with all the defaults provided
525
559
  */
526
560
  export default function getDefaultFormState<
@@ -533,28 +567,33 @@ export default function getDefaultFormState<
533
567
  formData?: T,
534
568
  rootSchema?: S,
535
569
  includeUndefinedValues: boolean | 'excludeObjectChildren' = false,
536
- experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior
570
+ experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior,
571
+ experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>
537
572
  ) {
538
573
  if (!isObject(theSchema)) {
539
574
  throw new Error('Invalid schema: ' + theSchema);
540
575
  }
541
- const schema = retrieveSchema<T, S, F>(validator, theSchema, rootSchema, formData);
576
+ const schema = retrieveSchema<T, S, F>(validator, theSchema, rootSchema, formData, experimental_customMergeAllOf);
542
577
  const defaults = computeDefaults<T, S, F>(validator, schema, {
543
578
  rootSchema,
544
579
  includeUndefinedValues,
545
580
  experimental_defaultFormStateBehavior,
581
+ experimental_customMergeAllOf,
546
582
  rawFormData: formData,
547
583
  });
584
+
548
585
  if (formData === undefined || formData === null || (typeof formData === 'number' && isNaN(formData))) {
549
586
  // No form data? Use schema defaults.
550
587
  return defaults;
551
588
  }
552
- const { mergeExtraDefaults } = experimental_defaultFormStateBehavior?.arrayMinItems || {};
589
+ const { mergeDefaultsIntoFormData, arrayMinItems = {} } = experimental_defaultFormStateBehavior || {};
590
+ const { mergeExtraDefaults } = arrayMinItems;
591
+ const defaultSupercedesUndefined = mergeDefaultsIntoFormData === 'useDefaultIfFormDataUndefined';
553
592
  if (isObject(formData)) {
554
- return mergeDefaultsWithFormData<T>(defaults as T, formData, mergeExtraDefaults);
593
+ return mergeDefaultsWithFormData<T>(defaults as T, formData, mergeExtraDefaults, defaultSupercedesUndefined);
555
594
  }
556
595
  if (Array.isArray(formData)) {
557
- return mergeDefaultsWithFormData<T[]>(defaults as T[], formData, mergeExtraDefaults);
596
+ return mergeDefaultsWithFormData<T[]>(defaults as T[], formData, mergeExtraDefaults, defaultSupercedesUndefined);
558
597
  }
559
598
  return formData;
560
599
  }