@rjsf/utils 5.0.0-beta.16 → 5.0.0-beta.18

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.
package/dist/utils.esm.js CHANGED
@@ -3,6 +3,11 @@ import get from 'lodash-es/get';
3
3
  import isEmpty from 'lodash-es/isEmpty';
4
4
  import jsonpointer from 'jsonpointer';
5
5
  import omit from 'lodash-es/omit';
6
+ import has from 'lodash-es/has';
7
+ import isObject$1 from 'lodash-es/isObject';
8
+ import isString from 'lodash-es/isString';
9
+ import reduce from 'lodash-es/reduce';
10
+ import times from 'lodash-es/times';
6
11
  import set from 'lodash-es/set';
7
12
  import mergeAllOf from 'json-schema-merge-allof';
8
13
  import union from 'lodash-es/union';
@@ -81,7 +86,7 @@ function _defineProperties(target, props) {
81
86
  descriptor.enumerable = descriptor.enumerable || false;
82
87
  descriptor.configurable = true;
83
88
  if ("value" in descriptor) descriptor.writable = true;
84
- Object.defineProperty(target, descriptor.key, descriptor);
89
+ Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
85
90
  }
86
91
  }
87
92
  function _createClass(Constructor, protoProps, staticProps) {
@@ -121,6 +126,20 @@ function _objectWithoutPropertiesLoose(source, excluded) {
121
126
  }
122
127
  return target;
123
128
  }
129
+ function _toPrimitive(input, hint) {
130
+ if (typeof input !== "object" || input === null) return input;
131
+ var prim = input[Symbol.toPrimitive];
132
+ if (prim !== undefined) {
133
+ var res = prim.call(input, hint || "default");
134
+ if (typeof res !== "object") return res;
135
+ throw new TypeError("@@toPrimitive must return a primitive value.");
136
+ }
137
+ return (hint === "string" ? String : Number)(input);
138
+ }
139
+ function _toPropertyKey(arg) {
140
+ var key = _toPrimitive(arg, "string");
141
+ return typeof key === "symbol" ? key : String(key);
142
+ }
124
143
 
125
144
  /** Below are the list of all the keys into various elements of a RJSFSchema or UiSchema that are used by the various
126
145
  * utility functions. In addition to those keys, there are the special `ADDITIONAL_PROPERTY_FLAG` and
@@ -275,12 +294,14 @@ function findSchemaDefinition($ref, rootSchema) {
275
294
  }
276
295
 
277
296
  /** Given the `formData` and list of `options`, attempts to find the index of the option that best matches the data.
297
+ * Deprecated, use `getFirstMatchingOption()` instead.
278
298
  *
279
299
  * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
280
300
  * @param formData - The current formData, if any, used to figure out a match
281
301
  * @param options - The list of options to find a matching options from
282
302
  * @param rootSchema - The root schema, used to primarily to look up `$ref`s
283
303
  * @returns - The index of the matched option or 0 if none is available
304
+ * @deprecated
284
305
  */
285
306
  function getMatchingOption(validator, formData, options, rootSchema) {
286
307
  // For performance, skip validating subschemas if formData is undefined. We just
@@ -336,6 +357,19 @@ function getMatchingOption(validator, formData, options, rootSchema) {
336
357
  return 0;
337
358
  }
338
359
 
360
+ /** Given the `formData` and list of `options`, attempts to find the index of the first option that matches the data.
361
+ * Always returns the first option if there is nothing that matches.
362
+ *
363
+ * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
364
+ * @param formData - The current formData, if any, used to figure out a match
365
+ * @param options - The list of options to find a matching options from
366
+ * @param rootSchema - The root schema, used to primarily to look up `$ref`s
367
+ * @returns - The index of the first matched option or 0 if none is available
368
+ */
369
+ function getFirstMatchingOption(validator, formData, options, rootSchema) {
370
+ return getMatchingOption(validator, formData, options, rootSchema);
371
+ }
372
+
339
373
  /** Given a specific `value` attempts to guess the type of a schema element. In the case where we have to implicitly
340
374
  * create a schema, it is useful to know what type to use based on the data we are defining.
341
375
  *
@@ -387,6 +421,12 @@ function getSchemaType(schema) {
387
421
  if (!type && (schema.properties || schema.additionalProperties)) {
388
422
  return "object";
389
423
  }
424
+ if (!type && Array.isArray(schema.oneOf) && schema.oneOf.length) {
425
+ return getSchemaType(schema.oneOf[0]);
426
+ }
427
+ if (!type && Array.isArray(schema.anyOf) && schema.anyOf.length) {
428
+ return getSchemaType(schema.anyOf[0]);
429
+ }
390
430
  if (Array.isArray(type) && type.length === 2 && type.includes("null")) {
391
431
  type = type.find(function (type) {
392
432
  return type !== "null";
@@ -395,99 +435,6 @@ function getSchemaType(schema) {
395
435
  return type;
396
436
  }
397
437
 
398
- /** Detects whether the given `schema` contains fixed items. This is the case when `schema.items` is a non-empty array
399
- * that only contains objects.
400
- *
401
- * @param schema - The schema in which to check for fixed items
402
- * @returns - True if there are fixed items in the schema, false otherwise
403
- */
404
- function isFixedItems(schema) {
405
- return Array.isArray(schema.items) && schema.items.length > 0 && schema.items.every(function (item) {
406
- return isObject(item);
407
- });
408
- }
409
-
410
- /** Merges the `defaults` object of type `T` into the `formData` of type `T`
411
- *
412
- * When merging defaults and form data, we want to merge in this specific way:
413
- * - objects are deeply merged
414
- * - arrays are merged in such a way that:
415
- * - when the array is set in form data, only array entries set in form data
416
- * are deeply merged; additional entries from the defaults are ignored
417
- * - when the array is not set in form data, the default is copied over
418
- * - scalars are overwritten/set by form data
419
- *
420
- * @param defaults - The defaults to merge
421
- * @param formData - The form data into which the defaults will be merged
422
- * @returns - The resulting merged form data with defaults
423
- */
424
- function mergeDefaultsWithFormData(defaults, formData) {
425
- if (Array.isArray(formData)) {
426
- var defaultsArray = Array.isArray(defaults) ? defaults : [];
427
- var mapped = formData.map(function (value, idx) {
428
- if (defaultsArray[idx]) {
429
- return mergeDefaultsWithFormData(defaultsArray[idx], value);
430
- }
431
- return value;
432
- });
433
- return mapped;
434
- }
435
- if (isObject(formData)) {
436
- var acc = Object.assign({}, defaults); // Prevent mutation of source object.
437
- return Object.keys(formData).reduce(function (acc, key) {
438
- acc[key] = mergeDefaultsWithFormData(defaults ? get(defaults, key) : {}, get(formData, key));
439
- return acc;
440
- }, acc);
441
- }
442
- return formData;
443
- }
444
-
445
- /** Recursively merge deeply nested objects.
446
- *
447
- * @param obj1 - The first object to merge
448
- * @param obj2 - The second object to merge
449
- * @param [concatArrays=false] - Optional flag that, when true, will cause arrays to be concatenated. Use
450
- * "preventDuplicates" to merge arrays in a manner that prevents any duplicate entries from being merged.
451
- * NOTE: Uses shallow comparison for the duplicate checking.
452
- * @returns - A new object that is the merge of the two given objects
453
- */
454
- function mergeObjects(obj1, obj2, concatArrays) {
455
- if (concatArrays === void 0) {
456
- concatArrays = false;
457
- }
458
- return Object.keys(obj2).reduce(function (acc, key) {
459
- var left = obj1 ? obj1[key] : {},
460
- right = obj2[key];
461
- if (obj1 && key in obj1 && isObject(right)) {
462
- acc[key] = mergeObjects(left, right, concatArrays);
463
- } else if (concatArrays && Array.isArray(left) && Array.isArray(right)) {
464
- var toMerge = right;
465
- if (concatArrays === "preventDuplicates") {
466
- toMerge = right.reduce(function (result, value) {
467
- if (!left.includes(value)) {
468
- result.push(value);
469
- }
470
- return result;
471
- }, []);
472
- }
473
- acc[key] = left.concat(toMerge);
474
- } else {
475
- acc[key] = right;
476
- }
477
- return acc;
478
- }, Object.assign({}, obj1)); // Prevent mutation of source object.
479
- }
480
-
481
- /** This function checks if the given `schema` matches a single constant value. This happens when either the schema has
482
- * an `enum` array with a single value or there is a `const` defined.
483
- *
484
- * @param schema - The schema for a field
485
- * @returns - True if the `schema` has a single constant value, false otherwise
486
- */
487
- function isConstant(schema) {
488
- return Array.isArray(schema["enum"]) && schema["enum"].length === 1 || CONST_KEY in schema;
489
- }
490
-
491
438
  /** Recursively merge deeply nested schemas. The difference between `mergeSchemas` and `mergeObjects` is that
492
439
  * `mergeSchemas` only concats arrays for values under the 'required' keyword, and when it does, it doesn't include
493
440
  * duplicate values.
@@ -524,7 +471,7 @@ var _excluded$1 = ["if", "then", "else"],
524
471
  * @param validator - An implementation of the `ValidatorType<T, S>` interface that is used to detect valid schema conditions
525
472
  * @param schema - The schema for which resolving a condition is desired
526
473
  * @param rootSchema - The root schema that will be forwarded to all the APIs
527
- * @param formData - The current formData to assist retrieving a schema
474
+ * @param [formData] - The current formData to assist retrieving a schema
528
475
  * @returns - A schema with the appropriate condition resolved
529
476
  */
530
477
  function resolveCondition(validator, schema, rootSchema, formData) {
@@ -612,6 +559,10 @@ function stubExistingAdditionalProperties(validator, theSchema, rootSchema, aFor
612
559
  }, rootSchema, formData);
613
560
  } else if ("type" in schema.additionalProperties) {
614
561
  additionalProperties = _extends({}, schema.additionalProperties);
562
+ } else if (ANY_OF_KEY in schema.additionalProperties || ONE_OF_KEY in schema.additionalProperties) {
563
+ additionalProperties = _extends({
564
+ type: "object"
565
+ }, schema.additionalProperties);
615
566
  } else {
616
567
  additionalProperties = {
617
568
  type: guessType(get(formData, [key]))
@@ -683,9 +634,9 @@ function resolveDependencies(validator, schema, rootSchema, formData) {
683
634
  remainingSchema = _objectWithoutPropertiesLoose(schema, _excluded4);
684
635
  var resolvedSchema = remainingSchema;
685
636
  if (Array.isArray(resolvedSchema.oneOf)) {
686
- resolvedSchema = resolvedSchema.oneOf[getMatchingOption(validator, formData, resolvedSchema.oneOf, rootSchema)];
637
+ resolvedSchema = resolvedSchema.oneOf[getFirstMatchingOption(validator, formData, resolvedSchema.oneOf, rootSchema)];
687
638
  } else if (Array.isArray(resolvedSchema.anyOf)) {
688
- resolvedSchema = resolvedSchema.anyOf[getMatchingOption(validator, formData, resolvedSchema.anyOf, rootSchema)];
639
+ resolvedSchema = resolvedSchema.anyOf[getFirstMatchingOption(validator, formData, resolvedSchema.anyOf, rootSchema)];
689
640
  }
690
641
  return processDependencies(validator, dependencies, resolvedSchema, rootSchema, formData);
691
642
  }
@@ -806,6 +757,242 @@ function withExactlyOneSubschema(validator, schema, rootSchema, dependencyKey, o
806
757
  return mergeSchemas(schema, retrieveSchema(validator, dependentSchema, rootSchema, formData));
807
758
  }
808
759
 
760
+ /** A junk option used to determine when the getFirstMatchingOption call really matches an option rather than returning
761
+ * the first item
762
+ */
763
+ var JUNK_OPTION = {
764
+ type: "object",
765
+ properties: {
766
+ __not_really_there__: {
767
+ type: "number"
768
+ }
769
+ }
770
+ };
771
+ /** Recursive function that calculates the score of a `formData` against the given `schema`. The computation is fairly
772
+ * simple. Initially the total score is 0. When `schema.properties` object exists, then all the `key/value` pairs within
773
+ * the object are processed as follows after obtaining the formValue from `formData` using the `key`:
774
+ * - If the `value` contains a `$ref`, `calculateIndexScore()` is called recursively with the formValue and the new
775
+ * schema that is the result of the ref in the schema being resolved and that sub-schema's resulting score is added to
776
+ * the total.
777
+ * - If the `value` contains a `oneOf` and there is a formValue, then score based on the index returned from calling
778
+ * `getClosestMatchingOption()` of that oneOf.
779
+ * - If the type of the `value` is 'object', `calculateIndexScore()` is called recursively with the formValue and the
780
+ * `value` itself as the sub-schema, and the score is added to the total.
781
+ * - If the type of the `value` matches the guessed-type of the `formValue`, the score is incremented by 1, UNLESS the
782
+ * value has a `default` or `const`. In those case, if the `default` or `const` and the `formValue` match, the score
783
+ * is incremented by another 1 otherwise it is decremented by 1.
784
+ *
785
+ * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
786
+ * @param rootSchema - The root JSON schema of the entire form
787
+ * @param schema - The schema for which the score is being calculated
788
+ * @param formData - The form data associated with the schema, used to calculate the score
789
+ * @returns - The score a schema against the formData
790
+ */
791
+ function calculateIndexScore(validator, rootSchema, schema, formData) {
792
+ if (formData === void 0) {
793
+ formData = {};
794
+ }
795
+ var totalScore = 0;
796
+ if (schema) {
797
+ if (isObject$1(schema.properties)) {
798
+ totalScore += reduce(schema.properties, function (score, value, key) {
799
+ var formValue = get(formData, key);
800
+ if (typeof value === "boolean") {
801
+ return score;
802
+ }
803
+ if (has(value, REF_KEY)) {
804
+ var newSchema = retrieveSchema(validator, value, rootSchema, formValue);
805
+ return score + calculateIndexScore(validator, rootSchema, newSchema, formValue || {});
806
+ }
807
+ if (has(value, ONE_OF_KEY) && formValue) {
808
+ return score + getClosestMatchingOption(validator, rootSchema, formValue, get(value, ONE_OF_KEY));
809
+ }
810
+ if (value.type === "object") {
811
+ return score + calculateIndexScore(validator, rootSchema, value, formValue || {});
812
+ }
813
+ if (value.type === guessType(formValue)) {
814
+ // If the types match, then we bump the score by one
815
+ var newScore = score + 1;
816
+ if (value["default"]) {
817
+ // If the schema contains a readonly default value score the value that matches the default higher and
818
+ // any non-matching value lower
819
+ newScore += formValue === value["default"] ? 1 : -1;
820
+ } else if (value["const"]) {
821
+ // If the schema contains a const value score the value that matches the default higher and
822
+ // any non-matching value lower
823
+ newScore += formValue === value["const"] ? 1 : -1;
824
+ }
825
+ // TODO eventually, deal with enums/arrays
826
+ return newScore;
827
+ }
828
+ return score;
829
+ }, 0);
830
+ } else if (isString(schema.type) && schema.type === guessType(formData)) {
831
+ totalScore += 1;
832
+ }
833
+ }
834
+ return totalScore;
835
+ }
836
+ /** Determines which of the given `options` provided most closely matches the `formData`. Using
837
+ * `getFirstMatchingOption()` to match two schemas that differ only by the readOnly, default or const value of a field
838
+ * based on the `formData` and returns 0 when there is no match. Rather than passing in all the `options` at once to
839
+ * this utility, instead an array of valid option indexes is created by iterating over the list of options, call
840
+ * `getFirstMatchingOptions` with a list of one junk option and one good option, seeing if the good option is considered
841
+ * matched.
842
+ *
843
+ * Once the list of valid indexes is created, if there is only one valid index, just return it. Otherwise, if there are
844
+ * no valid indexes, then fill the valid indexes array with the indexes of all the options. Next, the index of the
845
+ * option with the highest score is determined by iterating over the list of valid options, calling
846
+ * `calculateIndexScore()` on each, comparing it against the current best score, and returning the index of the one that
847
+ * eventually has the best score.
848
+ *
849
+ * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
850
+ * @param rootSchema - The root JSON schema of the entire form
851
+ * @param formData - The form data associated with the schema
852
+ * @param options - The list of options that can be selected from
853
+ * @param [selectedOption=-1] - The index of the currently selected option, defaulted to -1 if not specified
854
+ * @returns - The index of the option that is the closest match to the `formData` or the `selectedOption` if no match
855
+ */
856
+ function getClosestMatchingOption(validator, rootSchema, formData, options, selectedOption) {
857
+ if (selectedOption === void 0) {
858
+ selectedOption = -1;
859
+ }
860
+ // Reduce the array of options down to a list of the indexes that are considered matching options
861
+ var allValidIndexes = options.reduce(function (validList, option, index) {
862
+ var testOptions = [JUNK_OPTION, option];
863
+ var match = getFirstMatchingOption(validator, formData, testOptions, rootSchema);
864
+ // The match is the real option, so add its index to list of valid indexes
865
+ if (match === 1) {
866
+ validList.push(index);
867
+ }
868
+ return validList;
869
+ }, []);
870
+ // There is only one valid index, so return it!
871
+ if (allValidIndexes.length === 1) {
872
+ return allValidIndexes[0];
873
+ }
874
+ if (!allValidIndexes.length) {
875
+ // No indexes were valid, so we'll score all the options, add all the indexes
876
+ times(options.length, function (i) {
877
+ return allValidIndexes.push(i);
878
+ });
879
+ }
880
+ // Score all the options in the list of valid indexes and return the index with the best score
881
+ var _allValidIndexes$redu = allValidIndexes.reduce(function (scoreData, index) {
882
+ var bestScore = scoreData.bestScore;
883
+ var option = options[index];
884
+ if (has(option, REF_KEY)) {
885
+ option = retrieveSchema(validator, option, rootSchema, formData);
886
+ }
887
+ var score = calculateIndexScore(validator, rootSchema, option, formData);
888
+ if (score > bestScore) {
889
+ return {
890
+ bestIndex: index,
891
+ bestScore: score
892
+ };
893
+ }
894
+ return scoreData;
895
+ }, {
896
+ bestIndex: selectedOption,
897
+ bestScore: 0
898
+ }),
899
+ bestIndex = _allValidIndexes$redu.bestIndex;
900
+ return bestIndex;
901
+ }
902
+
903
+ /** Detects whether the given `schema` contains fixed items. This is the case when `schema.items` is a non-empty array
904
+ * that only contains objects.
905
+ *
906
+ * @param schema - The schema in which to check for fixed items
907
+ * @returns - True if there are fixed items in the schema, false otherwise
908
+ */
909
+ function isFixedItems(schema) {
910
+ return Array.isArray(schema.items) && schema.items.length > 0 && schema.items.every(function (item) {
911
+ return isObject(item);
912
+ });
913
+ }
914
+
915
+ /** Merges the `defaults` object of type `T` into the `formData` of type `T`
916
+ *
917
+ * When merging defaults and form data, we want to merge in this specific way:
918
+ * - objects are deeply merged
919
+ * - arrays are merged in such a way that:
920
+ * - when the array is set in form data, only array entries set in form data
921
+ * are deeply merged; additional entries from the defaults are ignored
922
+ * - when the array is not set in form data, the default is copied over
923
+ * - scalars are overwritten/set by form data
924
+ *
925
+ * @param [defaults] - The defaults to merge
926
+ * @param [formData] - The form data into which the defaults will be merged
927
+ * @returns - The resulting merged form data with defaults
928
+ */
929
+ function mergeDefaultsWithFormData(defaults, formData) {
930
+ if (Array.isArray(formData)) {
931
+ var defaultsArray = Array.isArray(defaults) ? defaults : [];
932
+ var mapped = formData.map(function (value, idx) {
933
+ if (defaultsArray[idx]) {
934
+ return mergeDefaultsWithFormData(defaultsArray[idx], value);
935
+ }
936
+ return value;
937
+ });
938
+ return mapped;
939
+ }
940
+ if (isObject(formData)) {
941
+ var acc = Object.assign({}, defaults); // Prevent mutation of source object.
942
+ return Object.keys(formData).reduce(function (acc, key) {
943
+ acc[key] = mergeDefaultsWithFormData(defaults ? get(defaults, key) : {}, get(formData, key));
944
+ return acc;
945
+ }, acc);
946
+ }
947
+ return formData;
948
+ }
949
+
950
+ /** Recursively merge deeply nested objects.
951
+ *
952
+ * @param obj1 - The first object to merge
953
+ * @param obj2 - The second object to merge
954
+ * @param [concatArrays=false] - Optional flag that, when true, will cause arrays to be concatenated. Use
955
+ * "preventDuplicates" to merge arrays in a manner that prevents any duplicate entries from being merged.
956
+ * NOTE: Uses shallow comparison for the duplicate checking.
957
+ * @returns - A new object that is the merge of the two given objects
958
+ */
959
+ function mergeObjects(obj1, obj2, concatArrays) {
960
+ if (concatArrays === void 0) {
961
+ concatArrays = false;
962
+ }
963
+ return Object.keys(obj2).reduce(function (acc, key) {
964
+ var left = obj1 ? obj1[key] : {},
965
+ right = obj2[key];
966
+ if (obj1 && key in obj1 && isObject(right)) {
967
+ acc[key] = mergeObjects(left, right, concatArrays);
968
+ } else if (concatArrays && Array.isArray(left) && Array.isArray(right)) {
969
+ var toMerge = right;
970
+ if (concatArrays === "preventDuplicates") {
971
+ toMerge = right.reduce(function (result, value) {
972
+ if (!left.includes(value)) {
973
+ result.push(value);
974
+ }
975
+ return result;
976
+ }, []);
977
+ }
978
+ acc[key] = left.concat(toMerge);
979
+ } else {
980
+ acc[key] = right;
981
+ }
982
+ return acc;
983
+ }, Object.assign({}, obj1)); // Prevent mutation of source object.
984
+ }
985
+
986
+ /** This function checks if the given `schema` matches a single constant value. This happens when either the schema has
987
+ * an `enum` array with a single value or there is a `const` defined.
988
+ *
989
+ * @param schema - The schema for a field
990
+ * @returns - True if the `schema` has a single constant value, false otherwise
991
+ */
992
+ function isConstant(schema) {
993
+ return Array.isArray(schema["enum"]) && schema["enum"].length === 1 || CONST_KEY in schema;
994
+ }
995
+
809
996
  /** Checks to see if the `schema` combination represents a select
810
997
  *
811
998
  * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
@@ -931,9 +1118,9 @@ function computeDefaults(validator, rawSchema, parentDefaults, rootSchema, rawFo
931
1118
  return computeDefaults(validator, itemSchema, Array.isArray(parentDefaults) ? parentDefaults[idx] : undefined, rootSchema, formData, includeUndefinedValues);
932
1119
  });
933
1120
  } else if (ONE_OF_KEY in schema) {
934
- schema = schema.oneOf[getMatchingOption(validator, isEmpty(formData) ? undefined : formData, schema.oneOf, rootSchema)];
1121
+ schema = schema.oneOf[getClosestMatchingOption(validator, rootSchema, isEmpty(formData) ? undefined : formData, schema.oneOf, 0)];
935
1122
  } else if (ANY_OF_KEY in schema) {
936
- schema = schema.anyOf[getMatchingOption(validator, isEmpty(formData) ? undefined : formData, schema.anyOf, rootSchema)];
1123
+ schema = schema.anyOf[getClosestMatchingOption(validator, rootSchema, isEmpty(formData) ? undefined : formData, schema.anyOf, 0)];
937
1124
  }
938
1125
  // Not defaults defined for this node, fallback to generic typed ones.
939
1126
  if (typeof defaults === "undefined") {
@@ -1125,6 +1312,169 @@ function mergeValidationData(validator, validationData, additionalErrorSchema) {
1125
1312
  };
1126
1313
  }
1127
1314
 
1315
+ var NO_VALUE = /*#__PURE__*/Symbol("no Value");
1316
+ /** Sanitize the `data` associated with the `oldSchema` so it is considered appropriate for the `newSchema`. If the new
1317
+ * schema does not contain any properties, then `undefined` is returned to clear all the form data. Due to the nature
1318
+ * of schemas, this sanitization happens recursively for nested objects of data. Also, any properties in the old schema
1319
+ * that are non-existent in the new schema are set to `undefined`. The data sanitization process has the following flow:
1320
+ *
1321
+ * - If the new schema is an object that contains a `properties` object then:
1322
+ * - Create a `removeOldSchemaData` object, setting each key in the `oldSchema.properties` having `data` to undefined
1323
+ * - Create an empty `nestedData` object for use in the key filtering below:
1324
+ * - Iterate over each key in the `newSchema.properties` as follows:
1325
+ * - Get the `formValue` of the key from the `data`
1326
+ * - Get the `oldKeySchema` and `newKeyedSchema` for the key, defaulting to `{}` when it doesn't exist
1327
+ * - Retrieve the schema for any refs within each `oldKeySchema` and/or `newKeySchema`
1328
+ * - Get the types of the old and new keyed schemas and if the old doesn't exist or the old & new are the same then:
1329
+ * - If `removeOldSchemaData` has an entry for the key, delete it since the new schema has the same property
1330
+ * - If type of the key in the new schema is `object`:
1331
+ * - Store the value from the recursive `sanitizeDataForNewSchema` call in `nestedData[key]`
1332
+ * - Otherwise, check for default or const values:
1333
+ * - Get the old and new `default` values from the schema and check:
1334
+ * - If the new `default` value does not match the form value:
1335
+ * - If the old `default` value DOES match the form value, then:
1336
+ * - Replace `removeOldSchemaData[key]` with the new `default`
1337
+ * - Otherwise, if the new schema is `readOnly` then replace `removeOldSchemaData[key]` with undefined
1338
+ * - Get the old and new `const` values from the schema and check:
1339
+ * - If the new `const` value does not match the form value:
1340
+ * - If the old `const` value DOES match the form value, then:
1341
+ * - Replace `removeOldSchemaData[key]` with the new `const`
1342
+ * - Otherwise, replace `removeOldSchemaData[key]` with undefined
1343
+ * - Once all keys have been processed, return an object built as follows:
1344
+ * - `{ ...removeOldSchemaData, ...nestedData, ...pick(data, keysToKeep) }`
1345
+ * - If the new and old schema types are array and the `data` is an array then:
1346
+ * - If the type of the old and new schema `items` are a non-array objects:
1347
+ * - Retrieve the schema for any refs within each `oldKeySchema.items` and/or `newKeySchema.items`
1348
+ * - If the `type`s of both items are the same (or the old does not have a type):
1349
+ * - If the type is "object", then:
1350
+ * - For each element in the `data` recursively sanitize the data, stopping at `maxItems` if specified
1351
+ * - Otherwise, just return the `data` removing any values after `maxItems` if it is set
1352
+ * - If the type of the old and new schema `items` are booleans of the same value, return `data` as is
1353
+ * - Otherwise return `undefined`
1354
+ *
1355
+ * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
1356
+ * @param rootSchema - The root JSON schema of the entire form
1357
+ * @param [newSchema] - The new schema for which the data is being sanitized
1358
+ * @param [oldSchema] - The old schema from which the data originated
1359
+ * @param [data={}] - The form data associated with the schema, defaulting to an empty object when undefined
1360
+ * @returns - The new form data, with all the fields uniquely associated with the old schema set
1361
+ * to `undefined`. Will return `undefined` if the new schema is not an object containing properties.
1362
+ */
1363
+ function sanitizeDataForNewSchema(validator, rootSchema, newSchema, oldSchema, data) {
1364
+ if (data === void 0) {
1365
+ data = {};
1366
+ }
1367
+ // By default, we will clear the form data
1368
+ var newFormData;
1369
+ // If the new schema is of type object and that object contains a list of properties
1370
+ if (has(newSchema, PROPERTIES_KEY)) {
1371
+ // Create an object containing root-level keys in the old schema, setting each key to undefined to remove the data
1372
+ var removeOldSchemaData = {};
1373
+ if (has(oldSchema, PROPERTIES_KEY)) {
1374
+ var properties = get(oldSchema, PROPERTIES_KEY, {});
1375
+ Object.keys(properties).forEach(function (key) {
1376
+ if (has(data, key)) {
1377
+ removeOldSchemaData[key] = undefined;
1378
+ }
1379
+ });
1380
+ }
1381
+ var keys = Object.keys(get(newSchema, PROPERTIES_KEY, {}));
1382
+ // Create a place to store nested data that will be a side-effect of the filter
1383
+ var nestedData = {};
1384
+ keys.forEach(function (key) {
1385
+ var formValue = get(data, key);
1386
+ var oldKeyedSchema = get(oldSchema, [PROPERTIES_KEY, key], {});
1387
+ var newKeyedSchema = get(newSchema, [PROPERTIES_KEY, key], {});
1388
+ // Resolve the refs if they exist
1389
+ if (has(oldKeyedSchema, REF_KEY)) {
1390
+ oldKeyedSchema = retrieveSchema(validator, oldKeyedSchema, rootSchema, formValue);
1391
+ }
1392
+ if (has(newKeyedSchema, REF_KEY)) {
1393
+ newKeyedSchema = retrieveSchema(validator, newKeyedSchema, rootSchema, formValue);
1394
+ }
1395
+ // Now get types and see if they are the same
1396
+ var oldSchemaTypeForKey = get(oldKeyedSchema, "type");
1397
+ var newSchemaTypeForKey = get(newKeyedSchema, "type");
1398
+ // Check if the old option has the same key with the same type
1399
+ if (!oldSchemaTypeForKey || oldSchemaTypeForKey === newSchemaTypeForKey) {
1400
+ if (has(removeOldSchemaData, key)) {
1401
+ // SIDE-EFFECT: remove the undefined value for a key that has the same type between the old and new schemas
1402
+ delete removeOldSchemaData[key];
1403
+ }
1404
+ // If it is an object, we'll recurse and store the resulting sanitized data for the key
1405
+ if (newSchemaTypeForKey === "object" || newSchemaTypeForKey === "array" && Array.isArray(formValue)) {
1406
+ // SIDE-EFFECT: process the new schema type of object recursively to save iterations
1407
+ var itemData = sanitizeDataForNewSchema(validator, rootSchema, newKeyedSchema, oldKeyedSchema, formValue);
1408
+ if (itemData !== undefined || newSchemaTypeForKey === "array") {
1409
+ // only put undefined values for the array type and not the object type
1410
+ nestedData[key] = itemData;
1411
+ }
1412
+ } else {
1413
+ // Ok, the non-object types match, let's make sure that a default or a const of a different value is replaced
1414
+ // with the new default or const. This allows the case where two schemas differ that only by the default/const
1415
+ // value to be properly selected
1416
+ var newOptionDefault = get(newKeyedSchema, "default", NO_VALUE);
1417
+ var oldOptionDefault = get(oldKeyedSchema, "default", NO_VALUE);
1418
+ if (newOptionDefault !== NO_VALUE && newOptionDefault !== formValue) {
1419
+ if (oldOptionDefault === formValue) {
1420
+ // If the old default matches the formValue, we'll update the new value to match the new default
1421
+ removeOldSchemaData[key] = newOptionDefault;
1422
+ } else if (get(newKeyedSchema, "readOnly") === true) {
1423
+ // If the new schema has the default set to read-only, treat it like a const and remove the value
1424
+ removeOldSchemaData[key] = undefined;
1425
+ }
1426
+ }
1427
+ var newOptionConst = get(newKeyedSchema, "const", NO_VALUE);
1428
+ var oldOptionConst = get(oldKeyedSchema, "const", NO_VALUE);
1429
+ if (newOptionConst !== NO_VALUE && newOptionConst !== formValue) {
1430
+ // Since this is a const, if the old value matches, replace the value with the new const otherwise clear it
1431
+ removeOldSchemaData[key] = oldOptionConst === formValue ? newOptionConst : undefined;
1432
+ }
1433
+ }
1434
+ }
1435
+ });
1436
+ newFormData = _extends({}, data, removeOldSchemaData, nestedData);
1437
+ // First apply removing the old schema data, then apply the nested data, then apply the old data keys to keep
1438
+ } else if (get(oldSchema, "type") === "array" && get(newSchema, "type") === "array" && Array.isArray(data)) {
1439
+ var oldSchemaItems = get(oldSchema, "items");
1440
+ var newSchemaItems = get(newSchema, "items");
1441
+ // If any of the array types `items` are arrays (remember arrays are objects) then we'll just drop the data
1442
+ // Eventually, we may want to deal with when either of the `items` are arrays since those tuple validations
1443
+ if (typeof oldSchemaItems === "object" && typeof newSchemaItems === "object" && !Array.isArray(oldSchemaItems) && !Array.isArray(newSchemaItems)) {
1444
+ if (has(oldSchemaItems, REF_KEY)) {
1445
+ oldSchemaItems = retrieveSchema(validator, oldSchemaItems, rootSchema, data);
1446
+ }
1447
+ if (has(newSchemaItems, REF_KEY)) {
1448
+ newSchemaItems = retrieveSchema(validator, newSchemaItems, rootSchema, data);
1449
+ }
1450
+ // Now get types and see if they are the same
1451
+ var oldSchemaType = get(oldSchemaItems, "type");
1452
+ var newSchemaType = get(newSchemaItems, "type");
1453
+ // Check if the old option has the same key with the same type
1454
+ if (!oldSchemaType || oldSchemaType === newSchemaType) {
1455
+ var maxItems = get(newSchema, "maxItems", -1);
1456
+ if (newSchemaType === "object") {
1457
+ newFormData = data.reduce(function (newValue, aValue) {
1458
+ var itemValue = sanitizeDataForNewSchema(validator, rootSchema, newSchemaItems, oldSchemaItems, aValue);
1459
+ if (itemValue !== undefined && (maxItems < 0 || newValue.length < maxItems)) {
1460
+ newValue.push(itemValue);
1461
+ }
1462
+ return newValue;
1463
+ }, []);
1464
+ } else {
1465
+ newFormData = maxItems > 0 && data.length > maxItems ? data.slice(0, maxItems) : data;
1466
+ }
1467
+ }
1468
+ } else if (typeof oldSchemaItems === "boolean" && typeof newSchemaItems === "boolean" && oldSchemaItems === newSchemaItems) {
1469
+ // If they are both booleans and have the same value just return the data as is otherwise fall-thru to undefined
1470
+ newFormData = data;
1471
+ }
1472
+ // Also probably want to deal with `prefixItems` as tuples with the latest 2020 draft
1473
+ }
1474
+
1475
+ return newFormData;
1476
+ }
1477
+
1128
1478
  /** Generates an `IdSchema` object for the `schema`, recursively
1129
1479
  *
1130
1480
  * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
@@ -1270,11 +1620,37 @@ var SchemaUtils = /*#__PURE__*/function () {
1270
1620
  _proto.getDisplayLabel = function getDisplayLabel$1(schema, uiSchema) {
1271
1621
  return getDisplayLabel(this.validator, schema, uiSchema, this.rootSchema);
1272
1622
  }
1623
+ /** Determines which of the given `options` provided most closely matches the `formData`.
1624
+ * Returns the index of the option that is valid and is the closest match, or 0 if there is no match.
1625
+ *
1626
+ * The closest match is determined using the number of matching properties, and more heavily favors options with
1627
+ * matching readOnly, default, or const values.
1628
+ *
1629
+ * @param formData - The form data associated with the schema
1630
+ * @param options - The list of options that can be selected from
1631
+ * @param [selectedOption] - The index of the currently selected option, defaulted to -1 if not specified
1632
+ * @returns - The index of the option that is the closest match to the `formData` or the `selectedOption` if no match
1633
+ */;
1634
+ _proto.getClosestMatchingOption = function getClosestMatchingOption$1(formData, options, selectedOption) {
1635
+ return getClosestMatchingOption(this.validator, this.rootSchema, formData, options, selectedOption);
1636
+ }
1637
+ /** Given the `formData` and list of `options`, attempts to find the index of the first option that matches the data.
1638
+ * Always returns the first option if there is nothing that matches.
1639
+ *
1640
+ * @param formData - The current formData, if any, used to figure out a match
1641
+ * @param options - The list of options to find a matching options from
1642
+ * @returns - The firstindex of the matched option or 0 if none is available
1643
+ */;
1644
+ _proto.getFirstMatchingOption = function getFirstMatchingOption$1(formData, options) {
1645
+ return getFirstMatchingOption(this.validator, formData, options, this.rootSchema);
1646
+ }
1273
1647
  /** Given the `formData` and list of `options`, attempts to find the index of the option that best matches the data.
1648
+ * Deprecated, use `getFirstMatchingOption()` instead.
1274
1649
  *
1275
1650
  * @param formData - The current formData, if any, onto which to provide any missing defaults
1276
1651
  * @param options - The list of options to find a matching options from
1277
1652
  * @returns - The index of the matched option or 0 if none is available
1653
+ * @deprecated
1278
1654
  */;
1279
1655
  _proto.getMatchingOption = function getMatchingOption$1(formData, options) {
1280
1656
  return getMatchingOption(this.validator, formData, options, this.rootSchema);
@@ -1327,6 +1703,20 @@ var SchemaUtils = /*#__PURE__*/function () {
1327
1703
  _proto.retrieveSchema = function retrieveSchema$1(schema, rawFormData) {
1328
1704
  return retrieveSchema(this.validator, schema, this.rootSchema, rawFormData);
1329
1705
  }
1706
+ /** Sanitize the `data` associated with the `oldSchema` so it is considered appropriate for the `newSchema`. If the
1707
+ * new schema does not contain any properties, then `undefined` is returned to clear all the form data. Due to the
1708
+ * nature of schemas, this sanitization happens recursively for nested objects of data. Also, any properties in the
1709
+ * old schemas that are non-existent in the new schema are set to `undefined`.
1710
+ *
1711
+ * @param [newSchema] - The new schema for which the data is being sanitized
1712
+ * @param [oldSchema] - The old schema from which the data originated
1713
+ * @param [data={}] - The form data associated with the schema, defaulting to an empty object when undefined
1714
+ * @returns - The new form data, with all the fields uniquely associated with the old schema set
1715
+ * to `undefined`. Will return `undefined` if the new schema is not an object containing properties.
1716
+ */;
1717
+ _proto.sanitizeDataForNewSchema = function sanitizeDataForNewSchema$1(newSchema, oldSchema, data) {
1718
+ return sanitizeDataForNewSchema(this.validator, this.rootSchema, newSchema, oldSchema, data);
1719
+ }
1330
1720
  /** Generates an `IdSchema` object for the `schema`, recursively
1331
1721
  *
1332
1722
  * @param schema - The schema for which the display label flag is desired
@@ -1410,6 +1800,43 @@ function dataURItoBlob(dataURI) {
1410
1800
  };
1411
1801
  }
1412
1802
 
1803
+ /** Removes the `value` from the currently `selected` list of values
1804
+ *
1805
+ * @param value - The value to be removed from the selected list
1806
+ * @param selected - The current list of selected values
1807
+ * @returns - The updated `selected` list with the `value` removed from it
1808
+ */
1809
+ function enumOptionsDeselectValue(value, selected) {
1810
+ return selected.filter(function (v) {
1811
+ return v !== value;
1812
+ });
1813
+ }
1814
+
1815
+ /** Add the `value` to the list of `selected` values in the proper order as defined by `allEnumOptions`
1816
+ *
1817
+ * @param value - The value that should be selected
1818
+ * @param selected - The current list of selected values
1819
+ * @param allEnumOptions - The list of all the known enumOptions
1820
+ * @returns - The updated list of selected enum values with `value` added to it in the proper location
1821
+ */
1822
+ function enumOptionsSelectValue(value, selected, allEnumOptions) {
1823
+ if (allEnumOptions === void 0) {
1824
+ allEnumOptions = [];
1825
+ }
1826
+ var all = allEnumOptions.map(function (_ref) {
1827
+ var value = _ref.value;
1828
+ return value;
1829
+ });
1830
+ var at = all.indexOf(value);
1831
+ // If location of the value is not in the list of all enum values, just put it at the end
1832
+ var updated = at === -1 ? selected.concat(value) : selected.slice(0, at).concat(value, selected.slice(at));
1833
+ // As inserting values at predefined index positions doesn't work with empty
1834
+ // arrays, we need to reorder the updated selection to match the initial order
1835
+ return updated.sort(function (a, b) {
1836
+ return Number(all.indexOf(a) > all.indexOf(b));
1837
+ });
1838
+ }
1839
+
1413
1840
  /** The `ErrorSchemaBuilder<T>` is used to build an `ErrorSchema<T>` since the definition of the `ErrorSchema` type is
1414
1841
  * designed for reading information rather than writing it. Use this class to add, replace or clear errors in an error
1415
1842
  * schema by using either dotted path or an array of path names. Once you are done building the `ErrorSchema`, you can
@@ -1769,6 +2196,80 @@ function hasWidget(schema, widget, registeredWidgets) {
1769
2196
  }
1770
2197
  }
1771
2198
 
2199
+ /** Generates a consistent `id` pattern for a given `id` and a `suffix`
2200
+ *
2201
+ * @param id - Either simple string id or an IdSchema from which to extract it
2202
+ * @param suffix - The suffix to append to the id
2203
+ */
2204
+ function idGenerator(id, suffix) {
2205
+ var theId = isString(id) ? id : id[ID_KEY];
2206
+ return theId + "__" + suffix;
2207
+ }
2208
+ /** Return a consistent `id` for the field description element
2209
+ *
2210
+ * @param id - Either simple string id or an IdSchema from which to extract it
2211
+ * @returns - The consistent id for the field description element from the given `id`
2212
+ */
2213
+ function descriptionId(id) {
2214
+ return idGenerator(id, "description");
2215
+ }
2216
+ /** Return a consistent `id` for the field error element
2217
+ *
2218
+ * @param id - Either simple string id or an IdSchema from which to extract it
2219
+ * @returns - The consistent id for the field error element from the given `id`
2220
+ */
2221
+ function errorId(id) {
2222
+ return idGenerator(id, "error");
2223
+ }
2224
+ /** Return a consistent `id` for the field examples element
2225
+ *
2226
+ * @param id - Either simple string id or an IdSchema from which to extract it
2227
+ * @returns - The consistent id for the field examples element from the given `id`
2228
+ */
2229
+ function examplesId(id) {
2230
+ return idGenerator(id, "examples");
2231
+ }
2232
+ /** Return a consistent `id` for the field help element
2233
+ *
2234
+ * @param id - Either simple string id or an IdSchema from which to extract it
2235
+ * @returns - The consistent id for the field help element from the given `id`
2236
+ */
2237
+ function helpId(id) {
2238
+ return idGenerator(id, "help");
2239
+ }
2240
+ /** Return a consistent `id` for the field title element
2241
+ *
2242
+ * @param id - Either simple string id or an IdSchema from which to extract it
2243
+ * @returns - The consistent id for the field title element from the given `id`
2244
+ */
2245
+ function titleId(id) {
2246
+ return idGenerator(id, "title");
2247
+ }
2248
+ /** Return a list of element ids that contain additional information about the field that can be used to as the aria
2249
+ * description of the field. This is correctly omitting `titleId` which would be "labeling" rather than "describing" the
2250
+ * element.
2251
+ *
2252
+ * @param id - Either simple string id or an IdSchema from which to extract it
2253
+ * @param [includeExamples=false] - Optional flag, if true, will add the `examplesId` into the list
2254
+ * @returns - The string containing the list of ids for use in an `aria-describedBy` attribute
2255
+ */
2256
+ function ariaDescribedByIds(id, includeExamples) {
2257
+ if (includeExamples === void 0) {
2258
+ includeExamples = false;
2259
+ }
2260
+ var examples = includeExamples ? " " + examplesId(id) : "";
2261
+ return errorId(id) + " " + descriptionId(id) + " " + helpId(id) + examples;
2262
+ }
2263
+ /** Return a consistent `id` for the `option`s of a `Radio` or `Checkboxes` widget
2264
+ *
2265
+ * @param id - The id of the parent component for the option
2266
+ * @param option - The option for which the id is desired
2267
+ * @returns - An id for the option based on the parent `id`
2268
+ */
2269
+ function optionId(id, option) {
2270
+ return id + "-" + option.value;
2271
+ }
2272
+
1772
2273
  /** Converts a local Date string into a UTC date string
1773
2274
  *
1774
2275
  * @param dateString - The string representation of a date as accepted by the `Date()` constructor
@@ -2070,5 +2571,5 @@ function utcToLocal(jsonDate) {
2070
2571
  return yyyy + "-" + MM + "-" + dd + "T" + hh + ":" + mm + ":" + ss + "." + SSS;
2071
2572
  }
2072
2573
 
2073
- export { ADDITIONAL_PROPERTIES_KEY, ADDITIONAL_PROPERTY_FLAG, ALL_OF_KEY, ANY_OF_KEY, CONST_KEY, DEFAULT_KEY, DEFINITIONS_KEY, DEPENDENCIES_KEY, ENUM_KEY, ERRORS_KEY, ErrorSchemaBuilder, ID_KEY, ITEMS_KEY, NAME_KEY, ONE_OF_KEY, PROPERTIES_KEY, REF_KEY, REQUIRED_KEY, RJSF_ADDITONAL_PROPERTIES_FLAG, SUBMIT_BTN_OPTIONS_KEY, UI_FIELD_KEY, UI_OPTIONS_KEY, UI_WIDGET_KEY, allowAdditionalItems, asNumber, canExpand, createSchemaUtils, dataURItoBlob, deepEquals, findSchemaDefinition, getDefaultFormState, getDisplayLabel, getInputProps, getMatchingOption, getSchemaType, getSubmitButtonOptions, getTemplate, getUiOptions, getWidget, guessType, hasWidget, isConstant, isCustomWidget, isFilesArray, isFixedItems, isMultiSelect, isObject, isSelect, localToUTC, mergeDefaultsWithFormData, mergeObjects, mergeSchemas, mergeValidationData, optionsList, orderProperties, pad, parseDateString, processSelectValue, rangeSpec, retrieveSchema, schemaRequiresTrueValue, shouldRender, toConstant, toDateString, toIdSchema, toPathSchema, utcToLocal };
2574
+ export { ADDITIONAL_PROPERTIES_KEY, ADDITIONAL_PROPERTY_FLAG, ALL_OF_KEY, ANY_OF_KEY, CONST_KEY, DEFAULT_KEY, DEFINITIONS_KEY, DEPENDENCIES_KEY, ENUM_KEY, ERRORS_KEY, ErrorSchemaBuilder, ID_KEY, ITEMS_KEY, NAME_KEY, ONE_OF_KEY, PROPERTIES_KEY, REF_KEY, REQUIRED_KEY, RJSF_ADDITONAL_PROPERTIES_FLAG, SUBMIT_BTN_OPTIONS_KEY, UI_FIELD_KEY, UI_OPTIONS_KEY, UI_WIDGET_KEY, allowAdditionalItems, ariaDescribedByIds, asNumber, canExpand, createSchemaUtils, dataURItoBlob, deepEquals, descriptionId, enumOptionsDeselectValue, enumOptionsSelectValue, errorId, examplesId, findSchemaDefinition, getClosestMatchingOption, getDefaultFormState, getDisplayLabel, getFirstMatchingOption, getInputProps, getMatchingOption, getSchemaType, getSubmitButtonOptions, getTemplate, getUiOptions, getWidget, guessType, hasWidget, helpId, isConstant, isCustomWidget, isFilesArray, isFixedItems, isMultiSelect, isObject, isSelect, localToUTC, mergeDefaultsWithFormData, mergeObjects, mergeSchemas, mergeValidationData, optionId, optionsList, orderProperties, pad, parseDateString, processSelectValue, rangeSpec, retrieveSchema, sanitizeDataForNewSchema, schemaRequiresTrueValue, shouldRender, titleId, toConstant, toDateString, toIdSchema, toPathSchema, utcToLocal };
2074
2575
  //# sourceMappingURL=utils.esm.js.map