@rjsf/utils 6.0.0-beta.2 → 6.0.0-beta.21

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.
Files changed (99) hide show
  1. package/dist/{index.js → index.cjs} +396 -264
  2. package/dist/index.cjs.map +7 -0
  3. package/dist/utils.esm.js +395 -263
  4. package/dist/utils.esm.js.map +4 -4
  5. package/dist/utils.umd.js +368 -249
  6. package/lib/ErrorSchemaBuilder.d.ts +2 -2
  7. package/lib/canExpand.d.ts +1 -1
  8. package/lib/constants.d.ts +3 -0
  9. package/lib/constants.js +3 -0
  10. package/lib/constants.js.map +1 -1
  11. package/lib/createSchemaUtils.js +25 -18
  12. package/lib/createSchemaUtils.js.map +1 -1
  13. package/lib/enums.d.ts +9 -3
  14. package/lib/enums.js +9 -3
  15. package/lib/enums.js.map +1 -1
  16. package/lib/findSchemaDefinition.d.ts +7 -1
  17. package/lib/findSchemaDefinition.js +48 -6
  18. package/lib/findSchemaDefinition.js.map +1 -1
  19. package/lib/getTestIds.js +2 -2
  20. package/lib/getTestIds.js.map +1 -1
  21. package/lib/getUiOptions.js +4 -0
  22. package/lib/getUiOptions.js.map +1 -1
  23. package/lib/getWidget.js +3 -3
  24. package/lib/getWidget.js.map +1 -1
  25. package/lib/guessType.d.ts +1 -1
  26. package/lib/idGenerators.d.ts +22 -15
  27. package/lib/idGenerators.js +17 -8
  28. package/lib/idGenerators.js.map +1 -1
  29. package/lib/index.d.ts +10 -4
  30. package/lib/index.js +9 -4
  31. package/lib/index.js.map +1 -1
  32. package/lib/isFormDataAvailable.d.ts +7 -0
  33. package/lib/isFormDataAvailable.js +13 -0
  34. package/lib/isFormDataAvailable.js.map +1 -0
  35. package/lib/isRootSchema.d.ts +13 -0
  36. package/lib/isRootSchema.js +25 -0
  37. package/lib/isRootSchema.js.map +1 -0
  38. package/lib/mergeDefaultsWithFormData.js +14 -2
  39. package/lib/mergeDefaultsWithFormData.js.map +1 -1
  40. package/lib/schema/findFieldInSchema.d.ts +1 -1
  41. package/lib/schema/findFieldInSchema.js +1 -1
  42. package/lib/schema/getDefaultFormState.d.ts +17 -3
  43. package/lib/schema/getDefaultFormState.js +73 -27
  44. package/lib/schema/getDefaultFormState.js.map +1 -1
  45. package/lib/schema/getDisplayLabel.js +2 -2
  46. package/lib/schema/getDisplayLabel.js.map +1 -1
  47. package/lib/schema/index.d.ts +1 -2
  48. package/lib/schema/index.js +1 -2
  49. package/lib/schema/index.js.map +1 -1
  50. package/lib/schema/retrieveSchema.d.ts +11 -6
  51. package/lib/schema/retrieveSchema.js +42 -19
  52. package/lib/schema/retrieveSchema.js.map +1 -1
  53. package/lib/shallowEquals.d.ts +8 -0
  54. package/lib/shallowEquals.js +36 -0
  55. package/lib/shallowEquals.js.map +1 -0
  56. package/lib/shouldRender.d.ts +8 -2
  57. package/lib/shouldRender.js +17 -2
  58. package/lib/shouldRender.js.map +1 -1
  59. package/lib/shouldRenderOptionalField.d.ts +18 -0
  60. package/lib/shouldRenderOptionalField.js +47 -0
  61. package/lib/shouldRenderOptionalField.js.map +1 -0
  62. package/lib/toFieldPathId.d.ts +12 -0
  63. package/lib/toFieldPathId.js +19 -0
  64. package/lib/toFieldPathId.js.map +1 -0
  65. package/lib/tsconfig.tsbuildinfo +1 -1
  66. package/lib/types.d.ts +136 -81
  67. package/lib/validationDataMerge.d.ts +2 -1
  68. package/lib/validationDataMerge.js +3 -2
  69. package/lib/validationDataMerge.js.map +1 -1
  70. package/package.json +13 -14
  71. package/src/ErrorSchemaBuilder.ts +2 -2
  72. package/src/constants.ts +3 -0
  73. package/src/createSchemaUtils.ts +25 -26
  74. package/src/enums.ts +9 -3
  75. package/src/findSchemaDefinition.ts +55 -6
  76. package/src/getTestIds.ts +2 -2
  77. package/src/getUiOptions.ts +4 -0
  78. package/src/getWidget.tsx +3 -3
  79. package/src/idGenerators.ts +35 -25
  80. package/src/index.ts +16 -2
  81. package/src/isFormDataAvailable.ts +13 -0
  82. package/src/isRootSchema.ts +30 -0
  83. package/src/mergeDefaultsWithFormData.ts +16 -2
  84. package/src/schema/findFieldInSchema.ts +1 -1
  85. package/src/schema/getDefaultFormState.ts +95 -33
  86. package/src/schema/getDisplayLabel.ts +2 -2
  87. package/src/schema/index.ts +0 -2
  88. package/src/schema/retrieveSchema.ts +46 -10
  89. package/src/shallowEquals.ts +41 -0
  90. package/src/shouldRender.ts +27 -2
  91. package/src/shouldRenderOptionalField.ts +56 -0
  92. package/src/toFieldPathId.ts +24 -0
  93. package/src/types.ts +156 -84
  94. package/src/validationDataMerge.ts +7 -1
  95. package/dist/index.js.map +0 -7
  96. package/lib/schema/toIdSchema.d.ts +0 -14
  97. package/lib/schema/toIdSchema.js +0 -62
  98. package/lib/schema/toIdSchema.js.map +0 -1
  99. package/src/schema/toIdSchema.ts +0 -131
@@ -84,6 +84,24 @@ export function getInnerSchemaForArrayItem<S extends StrictRJSFSchema = RJSFSche
84
84
  return {} as S;
85
85
  }
86
86
 
87
+ /** Checks if the given `schema` contains the `null` type along with another type AND if the `default` contained within
88
+ * the schema is `null` AND the `computedDefault` is empty. If all of those conditions are true, then the `schema`'s
89
+ * default should be `null` rather than `computedDefault`.
90
+ *
91
+ * @param schema - The schema to inspect
92
+ * @param computedDefault - The computed default for the schema
93
+ * @returns - Flag indicating whether a null should be returned instead of the computedDefault
94
+ */
95
+ export function computeDefaultBasedOnSchemaTypeAndDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema>(
96
+ schema: S,
97
+ computedDefault: T,
98
+ ) {
99
+ const { default: schemaDefault, type } = schema;
100
+ const shouldReturnNullAsDefault =
101
+ Array.isArray(type) && type.includes('null') && isEmpty(computedDefault) && schemaDefault === null;
102
+ return shouldReturnNullAsDefault ? (null as T) : computedDefault;
103
+ }
104
+
87
105
  /** Either add `computedDefault` at `key` into `obj` or not add it based on its value, the value of
88
106
  * `includeUndefinedValues`, the value of `emptyObjectFields` and if its parent field is required. Generally undefined
89
107
  * `computedDefault` values are added only when `includeUndefinedValues` is either true/"excludeObjectChildren". If `
@@ -115,10 +133,18 @@ function maybeAddDefaultToObject<T = any>(
115
133
  isConst = false,
116
134
  ) {
117
135
  const { emptyObjectFields = 'populateAllDefaults' } = experimental_defaultFormStateBehavior;
118
- if (includeUndefinedValues || isConst) {
119
- // If includeUndefinedValues
136
+
137
+ if (includeUndefinedValues === true || isConst) {
138
+ // If includeUndefinedValues is explicitly true
120
139
  // Or if the schema has a const property defined, then we should always return the computedDefault since it's coming from the const.
121
140
  obj[key] = computedDefault;
141
+ } else if (includeUndefinedValues === 'excludeObjectChildren') {
142
+ // Fix for Issue #4709: When in 'excludeObjectChildren' mode, don't set primitive fields to empty objects
143
+ // Only add the computed default if it's not an empty object placeholder for a primitive field
144
+ if (!isObject(computedDefault) || !isEmpty(computedDefault)) {
145
+ obj[key] = computedDefault;
146
+ }
147
+ // If computedDefault is an empty object {}, don't add it - let the field stay undefined
122
148
  } else if (emptyObjectFields !== 'skipDefaults') {
123
149
  // If isParentRequired is undefined, then we are at the root level of the schema so defer to the requiredness of
124
150
  // the field key itself in the `requiredField` list
@@ -174,10 +200,14 @@ interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema>
174
200
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>;
175
201
  /** Optional flag, if true, indicates this schema was required in the parent schema. */
176
202
  required?: boolean;
203
+ /** Optional flag, if true, indicates this schema was required because it is the root. */
204
+ requiredAsRoot?: boolean;
177
205
  /** Optional flag, if true, It will merge defaults into formData.
178
206
  * The formData should take precedence unless it's not valid. This is useful when for example the value from formData does not exist in the schema 'enum' property, in such cases we take the value from the defaults because the value from the formData is not valid.
179
207
  */
180
208
  shouldMergeDefaultsIntoFormData?: boolean;
209
+ /** Indicates whether initial defaults have been generated */
210
+ initialDefaultsGenerated?: boolean;
181
211
  }
182
212
 
183
213
  /** Computes the defaults for the current `schema` given the `rawFormData` and `parentDefaults` if any. This drills into
@@ -203,8 +233,9 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
203
233
  experimental_customMergeAllOf = undefined,
204
234
  required,
205
235
  shouldMergeDefaultsIntoFormData = false,
236
+ initialDefaultsGenerated,
206
237
  } = computeDefaultsProps;
207
- const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
238
+ let formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
208
239
  const schema: S = isObject(rawSchema) ? rawSchema : ({} as S);
209
240
  // Compute the defaults recursively: give highest priority to deepest nodes.
210
241
  let defaults: T | T[] | undefined = parentDefaults;
@@ -212,9 +243,8 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
212
243
  let schemaToCompute: S | null = null;
213
244
  let experimental_dfsb_to_compute = experimental_defaultFormStateBehavior;
214
245
  let updatedRecurseList = _recurseList;
215
-
216
246
  if (
217
- schema[CONST_KEY] &&
247
+ schema[CONST_KEY] !== undefined &&
218
248
  experimental_defaultFormStateBehavior?.constAsDefaults !== 'never' &&
219
249
  !constIsAjvDataReference(schema)
220
250
  ) {
@@ -240,6 +270,13 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
240
270
  if (schemaToCompute && !defaults) {
241
271
  defaults = schema.default as T | undefined;
242
272
  }
273
+
274
+ // If shouldMergeDefaultsIntoFormData is true
275
+ // And the schemaToCompute is set and the rawFormData is not an object
276
+ // Then set the formData to the rawFormData
277
+ if (shouldMergeDefaultsIntoFormData && schemaToCompute && !isObject(rawFormData)) {
278
+ formData = rawFormData as T;
279
+ }
243
280
  } else if (DEPENDENCIES_KEY in schema) {
244
281
  // Get the default if set from properties to ensure the dependencies conditions are resolved based on it
245
282
  const defaultFormData: T = {
@@ -328,9 +365,10 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
328
365
  experimental_defaultFormStateBehavior: experimental_dfsb_to_compute,
329
366
  experimental_customMergeAllOf,
330
367
  parentDefaults: defaults as T | undefined,
331
- rawFormData: formData as T,
368
+ rawFormData: (rawFormData ?? formData) as T,
332
369
  required,
333
370
  shouldMergeDefaultsIntoFormData,
371
+ initialDefaultsGenerated,
334
372
  });
335
373
  }
336
374
 
@@ -431,8 +469,9 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
431
469
  experimental_customMergeAllOf = undefined,
432
470
  required,
433
471
  shouldMergeDefaultsIntoFormData,
472
+ initialDefaultsGenerated,
434
473
  }: ComputeDefaultsProps<T, S> = {},
435
- defaults?: T | T[] | undefined,
474
+ defaults?: T | T[],
436
475
  ): T {
437
476
  {
438
477
  const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
@@ -466,7 +505,9 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
466
505
  rawFormData: get(formData, [key]),
467
506
  required: retrievedSchema.required?.includes(key),
468
507
  shouldMergeDefaultsIntoFormData,
508
+ initialDefaultsGenerated,
469
509
  });
510
+
470
511
  maybeAddDefaultToObject<T>(
471
512
  acc,
472
513
  key,
@@ -477,11 +518,12 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
477
518
  experimental_defaultFormStateBehavior,
478
519
  hasConst,
479
520
  );
521
+
480
522
  return acc;
481
523
  },
482
524
  {},
483
525
  ) as T;
484
- if (retrievedSchema.additionalProperties) {
526
+ if (retrievedSchema.additionalProperties && !initialDefaultsGenerated) {
485
527
  // as per spec additionalProperties may be either schema or boolean
486
528
  const additionalPropertiesSchema = isObject(retrievedSchema.additionalProperties)
487
529
  ? retrievedSchema.additionalProperties
@@ -511,6 +553,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
511
553
  rawFormData: get(formData, [key]),
512
554
  required: retrievedSchema.required?.includes(key),
513
555
  shouldMergeDefaultsIntoFormData,
556
+ initialDefaultsGenerated,
514
557
  });
515
558
  // Since these are additional properties we don't need to add the `experimental_defaultFormStateBehavior` prop
516
559
  maybeAddDefaultToObject<T>(
@@ -523,7 +566,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
523
566
  );
524
567
  });
525
568
  }
526
- return objectDefaults;
569
+ return computeDefaultBasedOnSchemaTypeAndDefaults<T, S>(rawSchema, objectDefaults);
527
570
  }
528
571
  }
529
572
 
@@ -545,10 +588,12 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
545
588
  experimental_defaultFormStateBehavior = undefined,
546
589
  experimental_customMergeAllOf = undefined,
547
590
  required,
591
+ requiredAsRoot = false,
548
592
  shouldMergeDefaultsIntoFormData,
593
+ initialDefaultsGenerated,
549
594
  }: ComputeDefaultsProps<T, S> = {},
550
- defaults?: T | T[] | undefined,
551
- ): T | T[] | undefined {
595
+ defaults?: T[],
596
+ ): T[] | undefined {
552
597
  const schema: S = rawSchema;
553
598
 
554
599
  const arrayMinItemsStateBehavior = experimental_defaultFormStateBehavior?.arrayMinItems ?? {};
@@ -560,7 +605,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
560
605
  const computeSkipPopulate = arrayMinItemsStateBehavior?.computeSkipPopulate ?? (() => false);
561
606
  const isSkipEmptyDefaults = experimental_defaultFormStateBehavior?.emptyObjectFields === 'skipEmptyDefaults';
562
607
 
563
- const emptyDefault = isSkipEmptyDefaults ? undefined : [];
608
+ const emptyDefault: T[] | undefined = isSkipEmptyDefaults ? undefined : [];
564
609
 
565
610
  // Inject defaults into existing array defaults
566
611
  if (Array.isArray(defaults)) {
@@ -574,6 +619,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
574
619
  parentDefaults: item,
575
620
  required,
576
621
  shouldMergeDefaultsIntoFormData,
622
+ initialDefaultsGenerated,
577
623
  });
578
624
  }) as T[];
579
625
  }
@@ -582,7 +628,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
582
628
  if (Array.isArray(rawFormData)) {
583
629
  const schemaItem: S = getInnerSchemaForArrayItem<S>(schema);
584
630
  if (neverPopulate) {
585
- defaults = rawFormData;
631
+ defaults = rawFormData as typeof defaults;
586
632
  } else {
587
633
  const itemDefaults = rawFormData.map((item: T, idx: number) => {
588
634
  return computeDefaults<T, S, F>(validator, schemaItem, {
@@ -594,6 +640,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
594
640
  parentDefaults: get(defaults, [idx]),
595
641
  required,
596
642
  shouldMergeDefaultsIntoFormData,
643
+ initialDefaultsGenerated,
597
644
  });
598
645
  }) as T[];
599
646
 
@@ -619,6 +666,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
619
666
  }
620
667
  }
621
668
 
669
+ let arrayDefault: T[] | undefined;
622
670
  const defaultsLength = Array.isArray(defaults) ? defaults.length : 0;
623
671
  if (
624
672
  !schema.minItems ||
@@ -626,27 +674,30 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
626
674
  computeSkipPopulate<T, S, F>(validator, schema, rootSchema) ||
627
675
  schema.minItems <= defaultsLength
628
676
  ) {
629
- return defaults ? defaults : emptyDefault;
677
+ // we don't want undefined defaults unless it is both not required or not required as root
678
+ arrayDefault = defaults || (!required && !requiredAsRoot) ? defaults : emptyDefault;
679
+ } else {
680
+ const defaultEntries: T[] = (defaults || []) as T[];
681
+ const fillerSchema: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Invert);
682
+ const fillerDefault = fillerSchema.default;
683
+
684
+ // Calculate filler entries for remaining items (minItems - existing raw data/defaults)
685
+ const fillerEntries: T[] = Array.from({ length: schema.minItems - defaultsLength }, () =>
686
+ computeDefaults<any, S, F>(validator, fillerSchema, {
687
+ parentDefaults: fillerDefault,
688
+ rootSchema,
689
+ _recurseList,
690
+ experimental_defaultFormStateBehavior,
691
+ experimental_customMergeAllOf,
692
+ required,
693
+ shouldMergeDefaultsIntoFormData,
694
+ }),
695
+ ) as T[];
696
+ // then fill up the rest with either the item default or empty, up to minItems
697
+ arrayDefault = defaultEntries.concat(fillerEntries);
630
698
  }
631
699
 
632
- const defaultEntries: T[] = (defaults || []) as T[];
633
- const fillerSchema: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Invert);
634
- const fillerDefault = fillerSchema.default;
635
-
636
- // Calculate filler entries for remaining items (minItems - existing raw data/defaults)
637
- const fillerEntries: T[] = new Array(schema.minItems - defaultsLength).fill(
638
- computeDefaults<any, S, F>(validator, fillerSchema, {
639
- parentDefaults: fillerDefault,
640
- rootSchema,
641
- _recurseList,
642
- experimental_defaultFormStateBehavior,
643
- experimental_customMergeAllOf,
644
- required,
645
- shouldMergeDefaultsIntoFormData,
646
- }),
647
- ) as T[];
648
- // then fill up the rest with either the item default or empty, up to minItems
649
- return defaultEntries.concat(fillerEntries);
700
+ return computeDefaultBasedOnSchemaTypeAndDefaults<T[] | undefined, S>(rawSchema, arrayDefault);
650
701
  }
651
702
 
652
703
  /** Computes the default value based on the schema type.
@@ -673,7 +724,7 @@ export function getDefaultBasedOnSchemaType<
673
724
  return getObjectDefaults(validator, rawSchema, computeDefaultsProps, defaults);
674
725
  }
675
726
  case 'array': {
676
- return getArrayDefaults(validator, rawSchema, computeDefaultsProps, defaults);
727
+ return getArrayDefaults(validator, rawSchema, computeDefaultsProps, defaults as T[]);
677
728
  }
678
729
  }
679
730
  }
@@ -690,6 +741,7 @@ export function getDefaultBasedOnSchemaType<
690
741
  * false when computing defaults for any nested object properties.
691
742
  * @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
692
743
  * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
744
+ * @param initialDefaultsGenerated - Optional flag, indicates whether or not initial defaults have been generated
693
745
  * @returns - The resulting `formData` with all the defaults provided
694
746
  */
695
747
  export default function getDefaultFormState<
@@ -704,6 +756,7 @@ export default function getDefaultFormState<
704
756
  includeUndefinedValues: boolean | 'excludeObjectChildren' = false,
705
757
  experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior,
706
758
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
759
+ initialDefaultsGenerated?: boolean,
707
760
  ) {
708
761
  if (!isObject(theSchema)) {
709
762
  throw new Error('Invalid schema: ' + theSchema);
@@ -720,8 +773,17 @@ export default function getDefaultFormState<
720
773
  experimental_customMergeAllOf,
721
774
  rawFormData: formData,
722
775
  shouldMergeDefaultsIntoFormData: true,
776
+ initialDefaultsGenerated,
777
+ requiredAsRoot: true,
723
778
  });
724
779
 
780
+ if (schema.type !== 'object' && isObject(schema.default)) {
781
+ return {
782
+ ...defaults,
783
+ ...formData,
784
+ } as T;
785
+ }
786
+
725
787
  // If the formData is an object or an array, add additional properties from formData and override formData with
726
788
  // defaults since the defaults are already merged with formData.
727
789
  if (isObject(formData) || Array.isArray(formData)) {
@@ -52,10 +52,10 @@ export default function getDisplayLabel<
52
52
  if (schemaType === 'object') {
53
53
  displayLabel = false;
54
54
  }
55
- if (schemaType === 'boolean' && !uiSchema[UI_WIDGET_KEY]) {
55
+ if (schemaType === 'boolean' && uiSchema && !uiSchema[UI_WIDGET_KEY]) {
56
56
  displayLabel = false;
57
57
  }
58
- if (uiSchema[UI_FIELD_KEY]) {
58
+ if (uiSchema && uiSchema[UI_FIELD_KEY]) {
59
59
  displayLabel = false;
60
60
  }
61
61
  return displayLabel;
@@ -10,7 +10,6 @@ import isMultiSelect from './isMultiSelect';
10
10
  import isSelect from './isSelect';
11
11
  import retrieveSchema from './retrieveSchema';
12
12
  import sanitizeDataForNewSchema from './sanitizeDataForNewSchema';
13
- import toIdSchema from './toIdSchema';
14
13
  import toPathSchema from './toPathSchema';
15
14
 
16
15
  export {
@@ -26,6 +25,5 @@ export {
26
25
  isSelect,
27
26
  retrieveSchema,
28
27
  sanitizeDataForNewSchema,
29
- toIdSchema,
30
28
  toPathSchema,
31
29
  };
@@ -47,6 +47,7 @@ import isEmpty from 'lodash/isEmpty';
47
47
  * @param [rootSchema={}] - The root schema that will be forwarded to all the APIs
48
48
  * @param [rawFormData] - The current formData, if any, to assist retrieving a schema
49
49
  * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
50
+ * @param [resolveAnyOfOrOneOfRefs = false] - Optional flag indicating whether to resolved refs in anyOf/oneOf lists
50
51
  * @returns - The schema having its conditions, additional properties, references and dependencies resolved
51
52
  */
52
53
  export default function retrieveSchema<
@@ -59,6 +60,7 @@ export default function retrieveSchema<
59
60
  rootSchema: S = {} as S,
60
61
  rawFormData?: T,
61
62
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
63
+ resolveAnyOfOrOneOfRefs = false,
62
64
  ): S {
63
65
  return retrieveSchemaInternal<T, S, F>(
64
66
  validator,
@@ -68,6 +70,7 @@ export default function retrieveSchema<
68
70
  undefined,
69
71
  undefined,
70
72
  experimental_customMergeAllOf,
73
+ resolveAnyOfOrOneOfRefs,
71
74
  )[0];
72
75
  }
73
76
 
@@ -223,6 +226,7 @@ export function getMatchingPatternProperties<S extends StrictRJSFSchema = RJSFSc
223
226
  * @param recurseList - The list of recursive references already processed
224
227
  * @param [formData] - The current formData, if any, to assist retrieving a schema
225
228
  * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
229
+ * @param [resolveAnyOfOrOneOfRefs] - Optional flag indicating whether to resolved refs in anyOf/oneOf lists
226
230
  * @returns - The list of schemas having its references, dependencies and allOf schemas resolved
227
231
  */
228
232
  export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
@@ -233,6 +237,7 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
233
237
  recurseList: string[],
234
238
  formData?: T,
235
239
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
240
+ resolveAnyOfOrOneOfRefs?: boolean,
236
241
  ): S[] {
237
242
  const updatedSchemas = resolveReference<T, S, F>(
238
243
  validator,
@@ -241,6 +246,8 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
241
246
  expandAllBranches,
242
247
  recurseList,
243
248
  formData,
249
+ experimental_customMergeAllOf,
250
+ resolveAnyOfOrOneOfRefs,
244
251
  );
245
252
  if (updatedSchemas.length > 1 || updatedSchemas[0] !== schema) {
246
253
  // return the updatedSchemas array if it has either multiple schemas within it
@@ -255,6 +262,7 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
255
262
  expandAllBranches,
256
263
  recurseList,
257
264
  formData,
265
+ experimental_customMergeAllOf,
258
266
  );
259
267
  return resolvedSchemas.flatMap((s) => {
260
268
  return retrieveSchemaInternal<T, S, F>(
@@ -268,7 +276,7 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
268
276
  );
269
277
  });
270
278
  }
271
- if (ALL_OF_KEY in schema && Array.isArray(schema.allOf)) {
279
+ if (ALL_OF_KEY in schema && Array.isArray(schema[ALL_OF_KEY])) {
272
280
  const allOfSchemaElements: S[][] = schema.allOf.map((allOfSubschema) =>
273
281
  retrieveSchemaInternal<T, S, F>(
274
282
  validator,
@@ -302,6 +310,7 @@ export function resolveSchema<T = any, S extends StrictRJSFSchema = RJSFSchema,
302
310
  * @param recurseList - The list of recursive references already processed
303
311
  * @param [formData] - The current formData, if any, to assist retrieving a schema
304
312
  * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
313
+ * @param [resolveAnyOfOrOneOfRefs] - Optional flag indicating whether to resolved refs in anyOf/oneOf lists
305
314
  * @returns - The list schemas retrieved after having all references resolved
306
315
  */
307
316
  export function resolveReference<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
@@ -312,8 +321,9 @@ export function resolveReference<T = any, S extends StrictRJSFSchema = RJSFSchem
312
321
  recurseList: string[],
313
322
  formData?: T,
314
323
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
324
+ resolveAnyOfOrOneOfRefs?: boolean,
315
325
  ): S[] {
316
- const updatedSchema = resolveAllReferences<S>(schema, rootSchema, recurseList);
326
+ const updatedSchema = resolveAllReferences<S>(schema, rootSchema, recurseList, undefined, resolveAnyOfOrOneOfRefs);
317
327
  if (updatedSchema !== schema) {
318
328
  // Only call this if the schema was actually changed by the `resolveAllReferences()` function
319
329
  return retrieveSchemaInternal<T, S, F>(
@@ -324,6 +334,7 @@ export function resolveReference<T = any, S extends StrictRJSFSchema = RJSFSchem
324
334
  expandAllBranches,
325
335
  recurseList,
326
336
  experimental_customMergeAllOf,
337
+ resolveAnyOfOrOneOfRefs,
327
338
  );
328
339
  }
329
340
  return [schema];
@@ -334,7 +345,8 @@ export function resolveReference<T = any, S extends StrictRJSFSchema = RJSFSchem
334
345
  * @param schema - The schema for which resolving all references is desired
335
346
  * @param rootSchema - The root schema that will be forwarded to all the APIs
336
347
  * @param recurseList - List of $refs already resolved to prevent recursion
337
- * @param baseURI - The base URI to be used for resolving relative references
348
+ * @param [baseURI] - The base URI to be used for resolving relative references
349
+ * @param [resolveAnyOfOrOneOfRefs] - Optional flag indicating whether to resolved refs in anyOf/oneOf lists
338
350
  * @returns - given schema will all references resolved or the original schema if no internal `$refs` were resolved
339
351
  */
340
352
  export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(
@@ -342,6 +354,7 @@ export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(
342
354
  rootSchema: S,
343
355
  recurseList: string[],
344
356
  baseURI?: string,
357
+ resolveAnyOfOrOneOfRefs?: boolean,
345
358
  ): S {
346
359
  if (!isObject(schema)) {
347
360
  return schema;
@@ -369,7 +382,7 @@ export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(
369
382
  resolvedSchema[PROPERTIES_KEY]!,
370
383
  (result, value, key: string) => {
371
384
  const childList: string[] = [...recurseList];
372
- result[key] = resolveAllReferences(value as S, rootSchema, childList, baseURI);
385
+ result[key] = resolveAllReferences(value as S, rootSchema, childList, baseURI, resolveAnyOfOrOneOfRefs);
373
386
  childrenLists.push(childList);
374
387
  },
375
388
  {} as RJSFSchema,
@@ -385,10 +398,30 @@ export function resolveAllReferences<S extends StrictRJSFSchema = RJSFSchema>(
385
398
  ) {
386
399
  resolvedSchema = {
387
400
  ...resolvedSchema,
388
- items: resolveAllReferences(resolvedSchema.items as S, rootSchema, recurseList, baseURI),
401
+ items: resolveAllReferences(resolvedSchema.items as S, rootSchema, recurseList, baseURI, resolveAnyOfOrOneOfRefs),
389
402
  };
390
403
  }
391
404
 
405
+ if (resolveAnyOfOrOneOfRefs) {
406
+ let key: 'anyOf' | 'oneOf' | undefined;
407
+ let schemas: S[] | undefined;
408
+ if (ANY_OF_KEY in schema && Array.isArray(schema[ANY_OF_KEY])) {
409
+ key = ANY_OF_KEY;
410
+ schemas = resolvedSchema[ANY_OF_KEY] as S[];
411
+ } else if (ONE_OF_KEY in schema && Array.isArray(schema[ONE_OF_KEY])) {
412
+ key = ONE_OF_KEY;
413
+ schemas = resolvedSchema[ONE_OF_KEY] as S[];
414
+ }
415
+ if (key && schemas) {
416
+ resolvedSchema = {
417
+ ...resolvedSchema,
418
+ [key]: schemas.map((s: S) =>
419
+ resolveAllReferences(s, rootSchema, recurseList, baseURI, resolveAnyOfOrOneOfRefs),
420
+ ),
421
+ };
422
+ }
423
+ }
424
+
392
425
  return deepEquals(schema, resolvedSchema) ? schema : resolvedSchema;
393
426
  }
394
427
 
@@ -430,9 +463,9 @@ export function stubExistingAdditionalProperties<
430
463
  if (!isEmpty(matchingProperties)) {
431
464
  schema.properties[key] = retrieveSchema<T, S, F>(
432
465
  validator,
433
- { allOf: Object.values(matchingProperties) } as S,
466
+ { [ALL_OF_KEY]: Object.values(matchingProperties) } as S,
434
467
  rootSchema,
435
- formData as T,
468
+ get(formData, [key]) as T,
436
469
  experimental_customMergeAllOf,
437
470
  );
438
471
  set(schema.properties, [key, ADDITIONAL_PROPERTY_FLAG], true);
@@ -440,12 +473,12 @@ export function stubExistingAdditionalProperties<
440
473
  }
441
474
  }
442
475
  if (ADDITIONAL_PROPERTIES_KEY in schema && schema.additionalProperties !== false) {
443
- let additionalProperties: S['additionalProperties'] = {};
476
+ let additionalProperties: S['additionalProperties'];
444
477
  if (typeof schema.additionalProperties !== 'boolean') {
445
478
  if (REF_KEY in schema.additionalProperties!) {
446
479
  additionalProperties = retrieveSchema<T, S, F>(
447
480
  validator,
448
- { $ref: get(schema.additionalProperties, [REF_KEY]) } as S,
481
+ { [REF_KEY]: get(schema.additionalProperties, [REF_KEY]) } as S,
449
482
  rootSchema,
450
483
  formData as T,
451
484
  experimental_customMergeAllOf,
@@ -492,6 +525,7 @@ export function stubExistingAdditionalProperties<
492
525
  * dependencies as a list of schemas
493
526
  * @param [recurseList=[]] - The optional, list of recursive references already processed
494
527
  * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas
528
+ * @param [resolveAnyOfOrOneOfRefs] - Optional flag indicating whether to resolved refs in anyOf/oneOf lists
495
529
  * @returns - The schema(s) resulting from having its conditions, additional properties, references and dependencies
496
530
  * resolved. Multiple schemas may be returned if `expandAllBranches` is true.
497
531
  */
@@ -507,6 +541,7 @@ export function retrieveSchemaInternal<
507
541
  expandAllBranches = false,
508
542
  recurseList: string[] = [],
509
543
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
544
+ resolveAnyOfOrOneOfRefs?: boolean,
510
545
  ): S[] {
511
546
  if (!isObject(schema)) {
512
547
  return [{} as S];
@@ -519,6 +554,7 @@ export function retrieveSchemaInternal<
519
554
  recurseList,
520
555
  rawFormData,
521
556
  experimental_customMergeAllOf,
557
+ resolveAnyOfOrOneOfRefs,
522
558
  );
523
559
  return resolvedSchemas.flatMap((s: S) => {
524
560
  let resolvedSchema = s;
@@ -578,7 +614,7 @@ export function retrieveSchemaInternal<
578
614
  validator,
579
615
  { allOf: [schema.properties[key], ...Object.values(matchingProperties)] } as S,
580
616
  rootSchema,
581
- rawFormData as T,
617
+ get(rawFormData, [key]) as T,
582
618
  experimental_customMergeAllOf,
583
619
  );
584
620
  }
@@ -0,0 +1,41 @@
1
+ /** Implements a shallow equals comparison that uses Object.is() for comparing values.
2
+ * This function compares objects by checking if all keys and their values are equal using Object.is().
3
+ *
4
+ * @param a - The first element to compare
5
+ * @param b - The second element to compare
6
+ * @returns - True if the `a` and `b` are shallow equal, false otherwise
7
+ */
8
+ export default function shallowEquals(a: any, b: any): boolean {
9
+ // If they're the same reference, they're equal
10
+ if (Object.is(a, b)) {
11
+ return true;
12
+ }
13
+
14
+ // If either is null or undefined, they're not equal (since we know they're not the same reference)
15
+ if (a == null || b == null) {
16
+ return false;
17
+ }
18
+
19
+ // If they're not objects, they're not equal (since Object.is already checked)
20
+ if (typeof a !== 'object' || typeof b !== 'object') {
21
+ return false;
22
+ }
23
+
24
+ const keysA = Object.keys(a);
25
+ const keysB = Object.keys(b);
26
+
27
+ // Different number of keys means not equal
28
+ if (keysA.length !== keysB.length) {
29
+ return false;
30
+ }
31
+
32
+ // Check if all keys and values are equal
33
+ for (let i = 0; i < keysA.length; i++) {
34
+ const key = keysA[i];
35
+ if (!Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key])) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ return true;
41
+ }
@@ -1,16 +1,41 @@
1
1
  import React from 'react';
2
2
 
3
3
  import deepEquals from './deepEquals';
4
+ import shallowEquals from './shallowEquals';
5
+
6
+ /** The supported component update strategies */
7
+ export type ComponentUpdateStrategy = 'customDeep' | 'shallow' | 'always';
4
8
 
5
9
  /** Determines whether the given `component` should be rerendered by comparing its current set of props and state
6
- * against the next set. If either of those two sets are not the same, then the component should be rerendered.
10
+ * against the next set. The comparison strategy can be controlled via the `updateStrategy` parameter.
7
11
  *
8
12
  * @param component - A React component being checked
9
13
  * @param nextProps - The next set of props against which to check
10
14
  * @param nextState - The next set of state against which to check
15
+ * @param updateStrategy - The strategy to use for comparison:
16
+ * - 'customDeep': Uses RJSF's custom deep equality checks (default)
17
+ * - 'shallow': Uses shallow comparison of props and state
18
+ * - 'always': Always returns true (React's default behavior)
11
19
  * @returns - True if the component should be re-rendered, false otherwise
12
20
  */
13
- export default function shouldRender(component: React.Component, nextProps: any, nextState: any) {
21
+ export default function shouldRender(
22
+ component: React.Component,
23
+ nextProps: any,
24
+ nextState: any,
25
+ updateStrategy: ComponentUpdateStrategy = 'customDeep',
26
+ ) {
27
+ if (updateStrategy === 'always') {
28
+ // Use React's default behavior: always update if state or props change (no shouldComponentUpdate optimization)
29
+ return true;
30
+ }
31
+
32
+ if (updateStrategy === 'shallow') {
33
+ // Use shallow comparison for props and state
34
+ const { props, state } = component;
35
+ return !shallowEquals(props, nextProps) || !shallowEquals(state, nextState);
36
+ }
37
+
38
+ // Use custom deep equality checks (default 'customDeep' strategy)
14
39
  const { props, state } = component;
15
40
  return !deepEquals(props, nextProps) || !deepEquals(state, nextState);
16
41
  }