@rjsf/utils 6.0.0-beta.9 → 6.0.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.
Files changed (113) hide show
  1. package/dist/{index.js → index.cjs} +563 -200
  2. package/dist/index.cjs.map +7 -0
  3. package/dist/utils.esm.js +562 -199
  4. package/dist/utils.esm.js.map +4 -4
  5. package/dist/utils.umd.js +533 -193
  6. package/lib/ErrorSchemaBuilder.d.ts +2 -2
  7. package/lib/constants.d.ts +3 -0
  8. package/lib/constants.js +3 -0
  9. package/lib/constants.js.map +1 -1
  10. package/lib/createSchemaUtils.js +25 -18
  11. package/lib/createSchemaUtils.js.map +1 -1
  12. package/lib/enums.d.ts +13 -3
  13. package/lib/enums.js +13 -3
  14. package/lib/enums.js.map +1 -1
  15. package/lib/findSchemaDefinition.d.ts +6 -0
  16. package/lib/findSchemaDefinition.js +44 -3
  17. package/lib/findSchemaDefinition.js.map +1 -1
  18. package/lib/getDateElementProps.d.ts +1 -2
  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 +16 -6
  30. package/lib/index.js +13 -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/nameGenerators.d.ts +13 -0
  41. package/lib/nameGenerators.js +30 -0
  42. package/lib/nameGenerators.js.map +1 -0
  43. package/lib/schema/getDefaultFormState.d.ts +17 -3
  44. package/lib/schema/getDefaultFormState.js +66 -26
  45. package/lib/schema/getDefaultFormState.js.map +1 -1
  46. package/lib/schema/getDisplayLabel.js +2 -2
  47. package/lib/schema/getDisplayLabel.js.map +1 -1
  48. package/lib/schema/index.d.ts +1 -2
  49. package/lib/schema/index.js +1 -2
  50. package/lib/schema/index.js.map +1 -1
  51. package/lib/schema/retrieveSchema.d.ts +10 -5
  52. package/lib/schema/retrieveSchema.js +40 -17
  53. package/lib/schema/retrieveSchema.js.map +1 -1
  54. package/lib/shallowEquals.d.ts +8 -0
  55. package/lib/shallowEquals.js +36 -0
  56. package/lib/shallowEquals.js.map +1 -0
  57. package/lib/shouldRender.d.ts +8 -2
  58. package/lib/shouldRender.js +17 -2
  59. package/lib/shouldRender.js.map +1 -1
  60. package/lib/shouldRenderOptionalField.d.ts +18 -0
  61. package/lib/shouldRenderOptionalField.js +47 -0
  62. package/lib/shouldRenderOptionalField.js.map +1 -0
  63. package/lib/toFieldPathId.d.ts +14 -0
  64. package/lib/toFieldPathId.js +26 -0
  65. package/lib/toFieldPathId.js.map +1 -0
  66. package/lib/tsconfig.tsbuildinfo +1 -1
  67. package/lib/types.d.ts +196 -105
  68. package/lib/useAltDateWidgetProps.d.ts +39 -0
  69. package/lib/useAltDateWidgetProps.js +71 -0
  70. package/lib/useAltDateWidgetProps.js.map +1 -0
  71. package/lib/useDeepCompareMemo.d.ts +8 -0
  72. package/lib/useDeepCompareMemo.js +17 -0
  73. package/lib/useDeepCompareMemo.js.map +1 -0
  74. package/lib/useFileWidgetProps.d.ts +29 -0
  75. package/lib/useFileWidgetProps.js +119 -0
  76. package/lib/useFileWidgetProps.js.map +1 -0
  77. package/lib/validationDataMerge.d.ts +2 -1
  78. package/lib/validationDataMerge.js +3 -2
  79. package/lib/validationDataMerge.js.map +1 -1
  80. package/package.json +13 -14
  81. package/src/ErrorSchemaBuilder.ts +2 -2
  82. package/src/constants.ts +3 -0
  83. package/src/createSchemaUtils.ts +25 -26
  84. package/src/enums.ts +13 -3
  85. package/src/findSchemaDefinition.ts +51 -3
  86. package/src/getDateElementProps.ts +1 -1
  87. package/src/getTestIds.ts +2 -2
  88. package/src/getUiOptions.ts +4 -0
  89. package/src/getWidget.tsx +3 -3
  90. package/src/idGenerators.ts +35 -25
  91. package/src/index.ts +36 -5
  92. package/src/isFormDataAvailable.ts +13 -0
  93. package/src/isRootSchema.ts +30 -0
  94. package/src/mergeDefaultsWithFormData.ts +16 -2
  95. package/src/nameGenerators.ts +43 -0
  96. package/src/schema/getDefaultFormState.ts +87 -31
  97. package/src/schema/getDisplayLabel.ts +2 -2
  98. package/src/schema/index.ts +0 -2
  99. package/src/schema/retrieveSchema.ts +43 -7
  100. package/src/shallowEquals.ts +41 -0
  101. package/src/shouldRender.ts +27 -2
  102. package/src/shouldRenderOptionalField.ts +56 -0
  103. package/src/toFieldPathId.ts +34 -0
  104. package/src/types.ts +229 -113
  105. package/src/useAltDateWidgetProps.tsx +163 -0
  106. package/src/useDeepCompareMemo.ts +17 -0
  107. package/src/useFileWidgetProps.ts +155 -0
  108. package/src/validationDataMerge.ts +7 -1
  109. package/dist/index.js.map +0 -7
  110. package/lib/schema/toIdSchema.d.ts +0 -14
  111. package/lib/schema/toIdSchema.js +0 -62
  112. package/lib/schema/toIdSchema.js.map +0 -1
  113. 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,6 +233,7 @@ 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
238
  let formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
208
239
  const schema: S = isObject(rawSchema) ? rawSchema : ({} as S);
@@ -213,7 +244,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
213
244
  let experimental_dfsb_to_compute = experimental_defaultFormStateBehavior;
214
245
  let updatedRecurseList = _recurseList;
215
246
  if (
216
- schema[CONST_KEY] &&
247
+ schema[CONST_KEY] !== undefined &&
217
248
  experimental_defaultFormStateBehavior?.constAsDefaults !== 'never' &&
218
249
  !constIsAjvDataReference(schema)
219
250
  ) {
@@ -334,9 +365,10 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
334
365
  experimental_defaultFormStateBehavior: experimental_dfsb_to_compute,
335
366
  experimental_customMergeAllOf,
336
367
  parentDefaults: defaults as T | undefined,
337
- rawFormData: formData as T,
368
+ rawFormData: (rawFormData ?? formData) as T,
338
369
  required,
339
370
  shouldMergeDefaultsIntoFormData,
371
+ initialDefaultsGenerated,
340
372
  });
341
373
  }
342
374
 
@@ -437,8 +469,9 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
437
469
  experimental_customMergeAllOf = undefined,
438
470
  required,
439
471
  shouldMergeDefaultsIntoFormData,
472
+ initialDefaultsGenerated,
440
473
  }: ComputeDefaultsProps<T, S> = {},
441
- defaults?: T | T[] | undefined,
474
+ defaults?: T | T[],
442
475
  ): T {
443
476
  {
444
477
  const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
@@ -472,7 +505,9 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
472
505
  rawFormData: get(formData, [key]),
473
506
  required: retrievedSchema.required?.includes(key),
474
507
  shouldMergeDefaultsIntoFormData,
508
+ initialDefaultsGenerated,
475
509
  });
510
+
476
511
  maybeAddDefaultToObject<T>(
477
512
  acc,
478
513
  key,
@@ -483,11 +518,12 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
483
518
  experimental_defaultFormStateBehavior,
484
519
  hasConst,
485
520
  );
521
+
486
522
  return acc;
487
523
  },
488
524
  {},
489
525
  ) as T;
490
- if (retrievedSchema.additionalProperties) {
526
+ if (retrievedSchema.additionalProperties && !initialDefaultsGenerated) {
491
527
  // as per spec additionalProperties may be either schema or boolean
492
528
  const additionalPropertiesSchema = isObject(retrievedSchema.additionalProperties)
493
529
  ? retrievedSchema.additionalProperties
@@ -517,6 +553,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
517
553
  rawFormData: get(formData, [key]),
518
554
  required: retrievedSchema.required?.includes(key),
519
555
  shouldMergeDefaultsIntoFormData,
556
+ initialDefaultsGenerated,
520
557
  });
521
558
  // Since these are additional properties we don't need to add the `experimental_defaultFormStateBehavior` prop
522
559
  maybeAddDefaultToObject<T>(
@@ -529,7 +566,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
529
566
  );
530
567
  });
531
568
  }
532
- return objectDefaults;
569
+ return computeDefaultBasedOnSchemaTypeAndDefaults<T, S>(rawSchema, objectDefaults);
533
570
  }
534
571
  }
535
572
 
@@ -551,10 +588,12 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
551
588
  experimental_defaultFormStateBehavior = undefined,
552
589
  experimental_customMergeAllOf = undefined,
553
590
  required,
591
+ requiredAsRoot = false,
554
592
  shouldMergeDefaultsIntoFormData,
593
+ initialDefaultsGenerated,
555
594
  }: ComputeDefaultsProps<T, S> = {},
556
- defaults?: T | T[] | undefined,
557
- ): T | T[] | undefined {
595
+ defaults?: T[],
596
+ ): T[] | undefined {
558
597
  const schema: S = rawSchema;
559
598
 
560
599
  const arrayMinItemsStateBehavior = experimental_defaultFormStateBehavior?.arrayMinItems ?? {};
@@ -566,7 +605,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
566
605
  const computeSkipPopulate = arrayMinItemsStateBehavior?.computeSkipPopulate ?? (() => false);
567
606
  const isSkipEmptyDefaults = experimental_defaultFormStateBehavior?.emptyObjectFields === 'skipEmptyDefaults';
568
607
 
569
- const emptyDefault = isSkipEmptyDefaults ? undefined : [];
608
+ const emptyDefault: T[] | undefined = isSkipEmptyDefaults ? undefined : [];
570
609
 
571
610
  // Inject defaults into existing array defaults
572
611
  if (Array.isArray(defaults)) {
@@ -580,6 +619,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
580
619
  parentDefaults: item,
581
620
  required,
582
621
  shouldMergeDefaultsIntoFormData,
622
+ initialDefaultsGenerated,
583
623
  });
584
624
  }) as T[];
585
625
  }
@@ -588,7 +628,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
588
628
  if (Array.isArray(rawFormData)) {
589
629
  const schemaItem: S = getInnerSchemaForArrayItem<S>(schema);
590
630
  if (neverPopulate) {
591
- defaults = rawFormData;
631
+ defaults = rawFormData as typeof defaults;
592
632
  } else {
593
633
  const itemDefaults = rawFormData.map((item: T, idx: number) => {
594
634
  return computeDefaults<T, S, F>(validator, schemaItem, {
@@ -600,6 +640,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
600
640
  parentDefaults: get(defaults, [idx]),
601
641
  required,
602
642
  shouldMergeDefaultsIntoFormData,
643
+ initialDefaultsGenerated,
603
644
  });
604
645
  }) as T[];
605
646
 
@@ -625,6 +666,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
625
666
  }
626
667
  }
627
668
 
669
+ let arrayDefault: T[] | undefined;
628
670
  const defaultsLength = Array.isArray(defaults) ? defaults.length : 0;
629
671
  if (
630
672
  !schema.minItems ||
@@ -632,27 +674,30 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
632
674
  computeSkipPopulate<T, S, F>(validator, schema, rootSchema) ||
633
675
  schema.minItems <= defaultsLength
634
676
  ) {
635
- 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);
636
698
  }
637
699
 
638
- const defaultEntries: T[] = (defaults || []) as T[];
639
- const fillerSchema: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Invert);
640
- const fillerDefault = fillerSchema.default;
641
-
642
- // Calculate filler entries for remaining items (minItems - existing raw data/defaults)
643
- const fillerEntries: T[] = new Array(schema.minItems - defaultsLength).fill(
644
- computeDefaults<any, S, F>(validator, fillerSchema, {
645
- parentDefaults: fillerDefault,
646
- rootSchema,
647
- _recurseList,
648
- experimental_defaultFormStateBehavior,
649
- experimental_customMergeAllOf,
650
- required,
651
- shouldMergeDefaultsIntoFormData,
652
- }),
653
- ) as T[];
654
- // then fill up the rest with either the item default or empty, up to minItems
655
- return defaultEntries.concat(fillerEntries);
700
+ return computeDefaultBasedOnSchemaTypeAndDefaults<T[] | undefined, S>(rawSchema, arrayDefault);
656
701
  }
657
702
 
658
703
  /** Computes the default value based on the schema type.
@@ -679,7 +724,7 @@ export function getDefaultBasedOnSchemaType<
679
724
  return getObjectDefaults(validator, rawSchema, computeDefaultsProps, defaults);
680
725
  }
681
726
  case 'array': {
682
- return getArrayDefaults(validator, rawSchema, computeDefaultsProps, defaults);
727
+ return getArrayDefaults(validator, rawSchema, computeDefaultsProps, defaults as T[]);
683
728
  }
684
729
  }
685
730
  }
@@ -696,6 +741,7 @@ export function getDefaultBasedOnSchemaType<
696
741
  * false when computing defaults for any nested object properties.
697
742
  * @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
698
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
699
745
  * @returns - The resulting `formData` with all the defaults provided
700
746
  */
701
747
  export default function getDefaultFormState<
@@ -710,6 +756,7 @@ export default function getDefaultFormState<
710
756
  includeUndefinedValues: boolean | 'excludeObjectChildren' = false,
711
757
  experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior,
712
758
  experimental_customMergeAllOf?: Experimental_CustomMergeAllOf<S>,
759
+ initialDefaultsGenerated?: boolean,
713
760
  ) {
714
761
  if (!isObject(theSchema)) {
715
762
  throw new Error('Invalid schema: ' + theSchema);
@@ -726,8 +773,17 @@ export default function getDefaultFormState<
726
773
  experimental_customMergeAllOf,
727
774
  rawFormData: formData,
728
775
  shouldMergeDefaultsIntoFormData: true,
776
+ initialDefaultsGenerated,
777
+ requiredAsRoot: true,
729
778
  });
730
779
 
780
+ if (schema.type !== 'object' && isObject(schema.default)) {
781
+ return {
782
+ ...defaults,
783
+ ...formData,
784
+ } as T;
785
+ }
786
+
731
787
  // If the formData is an object or an array, add additional properties from formData and override formData with
732
788
  // defaults since the defaults are already merged with formData.
733
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];
@@ -335,6 +346,7 @@ export function resolveReference<T = any, S extends StrictRJSFSchema = RJSFSchem
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
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,7 +463,7 @@ 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
468
  get(formData, [key]) as T,
436
469
  experimental_customMergeAllOf,
@@ -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;
@@ -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
  }
@@ -0,0 +1,56 @@
1
+ import isObject from 'lodash/isObject';
2
+ import uniq from 'lodash/uniq';
3
+
4
+ import { FormContextType, Registry, RJSFSchema, StrictRJSFSchema, UiSchema } from './types';
5
+ import getSchemaType from './getSchemaType';
6
+ import getUiOptions from './getUiOptions';
7
+ import isRootSchema from './isRootSchema';
8
+ import { ANY_OF_KEY, ONE_OF_KEY } from './constants';
9
+
10
+ /** Returns the unique list of schema types for all of the options in a anyOf/oneOf
11
+ *
12
+ * @param schemas - The list of schemas representing the XxxOf options
13
+ * @returns - All of the unique types contained within the oneOf list
14
+ */
15
+ export function getSchemaTypesForXxxOf<S extends StrictRJSFSchema = RJSFSchema>(schemas: S[]): string | string[] {
16
+ const allTypes: string[] = uniq(
17
+ schemas
18
+ .map((s) => (isObject(s) ? getSchemaType(s) : undefined))
19
+ .flat()
20
+ .filter((t) => t !== undefined),
21
+ );
22
+ return allTypes.length === 1 ? allTypes[0] : allTypes;
23
+ }
24
+
25
+ /** Determines whether the field information from the combination of `schema` and `required` along with the
26
+ * `enableOptionalDataFieldForType` settings from the global UI options in the `registry` all indicate that this field
27
+ * should be rendered with the Optional Data Controls UI.
28
+ *
29
+ * @param registry - The `registry` object
30
+ * @param schema - The schema for the field
31
+ * @param required - Flag indicating whether the field is required
32
+ * @param [uiSchema] - The uiSchema for the field
33
+ * @return - True if the field should be rendered with the optional field UI, otherwise false
34
+ */
35
+ export default function shouldRenderOptionalField<
36
+ T = any,
37
+ S extends StrictRJSFSchema = RJSFSchema,
38
+ F extends FormContextType = any,
39
+ >(registry: Registry<T, S, F>, schema: S, required: boolean, uiSchema?: UiSchema<T, S, F>): boolean {
40
+ const { enableOptionalDataFieldForType = [] } = getUiOptions<T, S, F>(uiSchema, registry.globalUiOptions);
41
+ let schemaType: ReturnType<typeof getSchemaType<S>>;
42
+ if (ANY_OF_KEY in schema && Array.isArray(schema[ANY_OF_KEY])) {
43
+ schemaType = getSchemaTypesForXxxOf<S>(schema[ANY_OF_KEY] as S[]);
44
+ } else if (ONE_OF_KEY in schema && Array.isArray(schema[ONE_OF_KEY])) {
45
+ schemaType = getSchemaTypesForXxxOf<S>(schema[ONE_OF_KEY] as S[]);
46
+ } else {
47
+ schemaType = getSchemaType<S>(schema);
48
+ }
49
+ return (
50
+ !isRootSchema<T, S, F>(registry, schema) &&
51
+ !required &&
52
+ !!schemaType &&
53
+ !Array.isArray(schemaType) &&
54
+ !!enableOptionalDataFieldForType.find((val) => val === schemaType)
55
+ );
56
+ }