@rjsf/utils 5.11.2 → 5.12.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 (303) hide show
  1. package/dist/index.js +2544 -5
  2. package/dist/index.js.map +7 -0
  3. package/dist/utils.esm.js +1228 -2113
  4. package/dist/utils.esm.js.map +7 -1
  5. package/dist/utils.umd.js +2414 -0
  6. package/lib/ErrorSchemaBuilder.d.ts +60 -0
  7. package/lib/ErrorSchemaBuilder.js +103 -0
  8. package/lib/ErrorSchemaBuilder.js.map +1 -0
  9. package/lib/allowAdditionalItems.d.ts +8 -0
  10. package/lib/allowAdditionalItems.js +14 -0
  11. package/lib/allowAdditionalItems.js.map +1 -0
  12. package/lib/asNumber.d.ts +10 -0
  13. package/lib/asNumber.js +36 -0
  14. package/lib/asNumber.js.map +1 -0
  15. package/lib/canExpand.d.ts +11 -0
  16. package/lib/canExpand.js +26 -0
  17. package/lib/canExpand.js.map +1 -0
  18. package/lib/constants.d.ts +31 -0
  19. package/lib/constants.js +32 -0
  20. package/lib/constants.js.map +1 -0
  21. package/lib/createErrorHandler.d.ts +7 -0
  22. package/lib/createErrorHandler.js +31 -0
  23. package/lib/createErrorHandler.js.map +1 -0
  24. package/lib/createSchemaUtils.d.ts +10 -0
  25. package/lib/createSchemaUtils.js +207 -0
  26. package/lib/createSchemaUtils.js.map +1 -0
  27. package/lib/dataURItoBlob.d.ts +16 -0
  28. package/lib/dataURItoBlob.js +43 -0
  29. package/lib/dataURItoBlob.js.map +1 -0
  30. package/lib/deepEquals.d.ts +8 -0
  31. package/lib/deepEquals.js +19 -0
  32. package/lib/deepEquals.js.map +1 -0
  33. package/lib/englishStringTranslator.d.ts +10 -0
  34. package/lib/englishStringTranslator.js +13 -0
  35. package/lib/englishStringTranslator.js.map +1 -0
  36. package/lib/enumOptionsDeselectValue.d.ts +14 -0
  37. package/lib/enumOptionsDeselectValue.js +22 -0
  38. package/lib/enumOptionsDeselectValue.js.map +1 -0
  39. package/lib/enumOptionsIndexForValue.d.ts +13 -0
  40. package/lib/enumOptionsIndexForValue.js +22 -0
  41. package/lib/enumOptionsIndexForValue.js.map +1 -0
  42. package/lib/enumOptionsIsSelected.d.ts +8 -0
  43. package/lib/enumOptionsIsSelected.js +14 -0
  44. package/lib/enumOptionsIsSelected.js.map +1 -0
  45. package/lib/enumOptionsSelectValue.d.ts +10 -0
  46. package/lib/enumOptionsSelectValue.js +23 -0
  47. package/lib/enumOptionsSelectValue.js.map +1 -0
  48. package/lib/enumOptionsValueForIndex.d.ts +13 -0
  49. package/lib/enumOptionsValueForIndex.js +21 -0
  50. package/lib/enumOptionsValueForIndex.js.map +1 -0
  51. package/lib/enums.d.ts +72 -0
  52. package/lib/enums.js +76 -0
  53. package/lib/enums.js.map +1 -0
  54. package/lib/findSchemaDefinition.d.ts +20 -0
  55. package/lib/findSchemaDefinition.js +49 -0
  56. package/lib/findSchemaDefinition.js.map +1 -0
  57. package/lib/getDiscriminatorFieldFromSchema.d.ts +8 -0
  58. package/lib/getDiscriminatorFieldFromSchema.js +20 -0
  59. package/lib/getDiscriminatorFieldFromSchema.js.map +1 -0
  60. package/lib/getInputProps.d.ts +10 -0
  61. package/lib/getInputProps.js +41 -0
  62. package/lib/getInputProps.js.map +1 -0
  63. package/lib/getSchemaType.d.ts +13 -0
  64. package/lib/getSchemaType.js +29 -0
  65. package/lib/getSchemaType.js.map +1 -0
  66. package/lib/getSubmitButtonOptions.d.ts +10 -0
  67. package/lib/getSubmitButtonOptions.js +25 -0
  68. package/lib/getSubmitButtonOptions.js.map +1 -0
  69. package/lib/getTemplate.d.ts +10 -0
  70. package/lib/getTemplate.js +19 -0
  71. package/lib/getTemplate.js.map +1 -0
  72. package/lib/getUiOptions.d.ts +9 -0
  73. package/lib/getUiOptions.js +25 -0
  74. package/lib/getUiOptions.js.map +1 -0
  75. package/lib/getWidget.d.ts +13 -0
  76. package/lib/getWidget.js +118 -0
  77. package/lib/getWidget.js.map +1 -0
  78. package/lib/guessType.d.ts +7 -0
  79. package/lib/guessType.js +29 -0
  80. package/lib/guessType.js.map +1 -0
  81. package/lib/hasWidget.d.ts +10 -0
  82. package/lib/hasWidget.js +23 -0
  83. package/lib/hasWidget.js.map +1 -0
  84. package/lib/hashForSchema.d.ts +8 -0
  85. package/lib/hashForSchema.js +29 -0
  86. package/lib/hashForSchema.js.map +1 -0
  87. package/lib/idGenerators.d.ts +47 -0
  88. package/lib/idGenerators.js +73 -0
  89. package/lib/idGenerators.js.map +1 -0
  90. package/lib/index.d.ts +57 -0
  91. package/lib/index.js +58 -0
  92. package/lib/index.js.map +1 -0
  93. package/lib/isConstant.d.ts +8 -0
  94. package/lib/isConstant.js +11 -0
  95. package/lib/isConstant.js.map +1 -0
  96. package/lib/isCustomWidget.d.ts +7 -0
  97. package/lib/isCustomWidget.js +13 -0
  98. package/lib/isCustomWidget.js.map +1 -0
  99. package/lib/isFixedItems.d.ts +8 -0
  100. package/lib/isFixedItems.js +11 -0
  101. package/lib/isFixedItems.js.map +1 -0
  102. package/lib/isObject.d.ts +7 -0
  103. package/lib/isObject.js +16 -0
  104. package/lib/isObject.js.map +1 -0
  105. package/lib/labelValue.d.ts +13 -0
  106. package/lib/labelValue.js +4 -0
  107. package/lib/labelValue.js.map +1 -0
  108. package/lib/localToUTC.d.ts +6 -0
  109. package/lib/localToUTC.js +9 -0
  110. package/lib/localToUTC.js.map +1 -0
  111. package/lib/mergeDefaultsWithFormData.d.ts +17 -0
  112. package/lib/mergeDefaultsWithFormData.js +43 -0
  113. package/lib/mergeDefaultsWithFormData.js.map +1 -0
  114. package/lib/mergeObjects.d.ts +11 -0
  115. package/lib/mergeObjects.js +35 -0
  116. package/lib/mergeObjects.js.map +1 -0
  117. package/lib/mergeSchemas.d.ts +10 -0
  118. package/lib/mergeSchemas.js +35 -0
  119. package/lib/mergeSchemas.js.map +1 -0
  120. package/lib/optionsList.d.ts +10 -0
  121. package/lib/optionsList.js +36 -0
  122. package/lib/optionsList.js.map +1 -0
  123. package/lib/orderProperties.d.ts +11 -0
  124. package/lib/orderProperties.js +38 -0
  125. package/lib/orderProperties.js.map +1 -0
  126. package/lib/pad.d.ts +7 -0
  127. package/lib/pad.js +14 -0
  128. package/lib/pad.js.map +1 -0
  129. package/lib/parseDateString.d.ts +9 -0
  130. package/lib/parseDateString.js +32 -0
  131. package/lib/parseDateString.js.map +1 -0
  132. package/lib/parser/ParserValidator.d.ts +70 -0
  133. package/lib/parser/ParserValidator.js +93 -0
  134. package/lib/parser/ParserValidator.js.map +1 -0
  135. package/lib/parser/index.d.ts +4 -0
  136. package/lib/parser/index.js +3 -0
  137. package/lib/parser/index.js.map +1 -0
  138. package/lib/parser/schemaParser.d.ts +9 -0
  139. package/lib/parser/schemaParser.js +48 -0
  140. package/lib/parser/schemaParser.js.map +1 -0
  141. package/lib/rangeSpec.d.ts +9 -0
  142. package/lib/rangeSpec.js +20 -0
  143. package/lib/rangeSpec.js.map +1 -0
  144. package/lib/replaceStringParameters.d.ts +9 -0
  145. package/lib/replaceStringParameters.js +23 -0
  146. package/lib/replaceStringParameters.js.map +1 -0
  147. package/lib/schema/getClosestMatchingOption.d.ts +49 -0
  148. package/lib/schema/getClosestMatchingOption.js +154 -0
  149. package/lib/schema/getClosestMatchingOption.js.map +1 -0
  150. package/lib/schema/getDefaultFormState.d.ts +66 -0
  151. package/lib/schema/getDefaultFormState.js +351 -0
  152. package/lib/schema/getDefaultFormState.js.map +1 -0
  153. package/lib/schema/getDisplayLabel.d.ts +12 -0
  154. package/lib/schema/getDisplayLabel.js +39 -0
  155. package/lib/schema/getDisplayLabel.js.map +1 -0
  156. package/lib/schema/getFirstMatchingOption.d.ts +13 -0
  157. package/lib/schema/getFirstMatchingOption.js +16 -0
  158. package/lib/schema/getFirstMatchingOption.js.map +1 -0
  159. package/lib/schema/getMatchingOption.d.ts +14 -0
  160. package/lib/schema/getMatchingOption.js +80 -0
  161. package/lib/schema/getMatchingOption.js.map +1 -0
  162. package/lib/schema/index.d.ts +14 -0
  163. package/lib/schema/index.js +15 -0
  164. package/lib/schema/index.js.map +1 -0
  165. package/lib/schema/isFilesArray.d.ts +10 -0
  166. package/lib/schema/isFilesArray.js +21 -0
  167. package/lib/schema/isFilesArray.js.map +1 -0
  168. package/lib/schema/isMultiSelect.d.ts +9 -0
  169. package/lib/schema/isMultiSelect.js +15 -0
  170. package/lib/schema/isMultiSelect.js.map +1 -0
  171. package/lib/schema/isSelect.d.ts +9 -0
  172. package/lib/schema/isSelect.js +21 -0
  173. package/lib/schema/isSelect.js.map +1 -0
  174. package/lib/schema/mergeValidationData.d.ts +14 -0
  175. package/lib/schema/mergeValidationData.js +28 -0
  176. package/lib/schema/mergeValidationData.js.map +1 -0
  177. package/lib/schema/retrieveSchema.d.ts +170 -0
  178. package/lib/schema/retrieveSchema.js +437 -0
  179. package/lib/schema/retrieveSchema.js.map +1 -0
  180. package/lib/schema/sanitizeDataForNewSchema.d.ts +49 -0
  181. package/lib/schema/sanitizeDataForNewSchema.js +173 -0
  182. package/lib/schema/sanitizeDataForNewSchema.js.map +1 -0
  183. package/lib/schema/toIdSchema.d.ts +13 -0
  184. package/lib/schema/toIdSchema.js +59 -0
  185. package/lib/schema/toIdSchema.js.map +1 -0
  186. package/lib/schema/toPathSchema.d.ts +11 -0
  187. package/lib/schema/toPathSchema.js +68 -0
  188. package/lib/schema/toPathSchema.js.map +1 -0
  189. package/lib/schemaRequiresTrueValue.d.ts +11 -0
  190. package/lib/schemaRequiresTrueValue.js +34 -0
  191. package/lib/schemaRequiresTrueValue.js.map +1 -0
  192. package/lib/shouldRender.d.ts +10 -0
  193. package/lib/shouldRender.js +14 -0
  194. package/lib/shouldRender.js.map +1 -0
  195. package/lib/toConstant.d.ts +9 -0
  196. package/lib/toConstant.js +18 -0
  197. package/lib/toConstant.js.map +1 -0
  198. package/lib/toDateString.d.ts +9 -0
  199. package/lib/toDateString.js +14 -0
  200. package/lib/toDateString.js.map +1 -0
  201. package/lib/toErrorList.d.ts +8 -0
  202. package/lib/toErrorList.js +34 -0
  203. package/lib/toErrorList.js.map +1 -0
  204. package/lib/toErrorSchema.d.ts +21 -0
  205. package/lib/toErrorSchema.js +41 -0
  206. package/lib/toErrorSchema.js.map +1 -0
  207. package/lib/types.d.ts +982 -0
  208. package/lib/types.js +2 -0
  209. package/lib/types.js.map +1 -0
  210. package/lib/unwrapErrorHandler.d.ts +7 -0
  211. package/lib/unwrapErrorHandler.js +21 -0
  212. package/lib/unwrapErrorHandler.js.map +1 -0
  213. package/lib/utcToLocal.d.ts +6 -0
  214. package/lib/utcToLocal.js +26 -0
  215. package/lib/utcToLocal.js.map +1 -0
  216. package/lib/validationDataMerge.d.ts +11 -0
  217. package/lib/validationDataMerge.js +26 -0
  218. package/lib/validationDataMerge.js.map +1 -0
  219. package/lib/withIdRefPrefix.d.ts +8 -0
  220. package/lib/withIdRefPrefix.js +47 -0
  221. package/lib/withIdRefPrefix.js.map +1 -0
  222. package/package.json +20 -13
  223. package/src/ErrorSchemaBuilder.ts +112 -0
  224. package/src/allowAdditionalItems.ts +15 -0
  225. package/src/asNumber.ts +38 -0
  226. package/src/canExpand.ts +31 -0
  227. package/src/constants.ts +31 -0
  228. package/src/createErrorHandler.ts +33 -0
  229. package/src/createSchemaUtils.ts +298 -0
  230. package/src/dataURItoBlob.ts +42 -0
  231. package/src/deepEquals.ts +19 -0
  232. package/src/englishStringTranslator.ts +14 -0
  233. package/src/enumOptionsDeselectValue.ts +28 -0
  234. package/src/enumOptionsIndexForValue.ts +27 -0
  235. package/src/enumOptionsIsSelected.ts +19 -0
  236. package/src/enumOptionsSelectValue.ts +28 -0
  237. package/src/enumOptionsValueForIndex.ts +26 -0
  238. package/src/enums.ts +74 -0
  239. package/src/findSchemaDefinition.ts +54 -0
  240. package/src/getDiscriminatorFieldFromSchema.ts +21 -0
  241. package/src/getInputProps.ts +55 -0
  242. package/src/getSchemaType.ts +37 -0
  243. package/src/getSubmitButtonOptions.ts +32 -0
  244. package/src/getTemplate.ts +26 -0
  245. package/src/getUiOptions.ts +32 -0
  246. package/src/getWidget.tsx +133 -0
  247. package/src/guessType.ts +28 -0
  248. package/src/hasWidget.ts +27 -0
  249. package/src/hashForSchema.ts +31 -0
  250. package/src/idGenerators.ts +81 -0
  251. package/src/index.ts +118 -0
  252. package/src/isConstant.ts +12 -0
  253. package/src/isCustomWidget.ts +19 -0
  254. package/src/isFixedItems.ts +12 -0
  255. package/src/isObject.ts +15 -0
  256. package/src/labelValue.ts +16 -0
  257. package/src/localToUTC.ts +8 -0
  258. package/src/mergeDefaultsWithFormData.ts +53 -0
  259. package/src/mergeObjects.ts +39 -0
  260. package/src/mergeSchemas.ts +38 -0
  261. package/src/optionsList.ts +41 -0
  262. package/src/orderProperties.ts +44 -0
  263. package/src/pad.ts +13 -0
  264. package/src/parseDateString.ts +33 -0
  265. package/src/parser/ParserValidator.ts +132 -0
  266. package/src/parser/index.ts +6 -0
  267. package/src/parser/schemaParser.ts +60 -0
  268. package/src/rangeSpec.ts +22 -0
  269. package/src/replaceStringParameters.ts +22 -0
  270. package/src/schema/getClosestMatchingOption.ts +191 -0
  271. package/src/schema/getDefaultFormState.ts +447 -0
  272. package/src/schema/getDisplayLabel.ts +59 -0
  273. package/src/schema/getFirstMatchingOption.ts +27 -0
  274. package/src/schema/getMatchingOption.ts +95 -0
  275. package/src/schema/index.ts +29 -0
  276. package/src/schema/isFilesArray.ts +27 -0
  277. package/src/schema/isMultiSelect.ts +21 -0
  278. package/src/schema/isSelect.ts +26 -0
  279. package/src/schema/mergeValidationData.ts +38 -0
  280. package/src/schema/retrieveSchema.ts +614 -0
  281. package/src/schema/sanitizeDataForNewSchema.ts +197 -0
  282. package/src/schema/toIdSchema.ts +105 -0
  283. package/src/schema/toPathSchema.ts +121 -0
  284. package/src/schemaRequiresTrueValue.ts +40 -0
  285. package/src/shouldRender.ts +16 -0
  286. package/src/toConstant.ts +19 -0
  287. package/src/toDateString.ts +15 -0
  288. package/src/toErrorList.ts +41 -0
  289. package/src/toErrorSchema.ts +43 -0
  290. package/src/types.ts +1139 -0
  291. package/src/unwrapErrorHandler.ts +25 -0
  292. package/src/utcToLocal.ts +30 -0
  293. package/src/validationDataMerge.ts +31 -0
  294. package/src/withIdRefPrefix.ts +49 -0
  295. package/dist/index.d.ts +0 -1911
  296. package/dist/utils.cjs.development.js +0 -3522
  297. package/dist/utils.cjs.development.js.map +0 -1
  298. package/dist/utils.cjs.production.min.js +0 -2
  299. package/dist/utils.cjs.production.min.js.map +0 -1
  300. package/dist/utils.umd.development.js +0 -3504
  301. package/dist/utils.umd.development.js.map +0 -1
  302. package/dist/utils.umd.production.min.js +0 -2
  303. package/dist/utils.umd.production.min.js.map +0 -1
@@ -0,0 +1,191 @@
1
+ import get from 'lodash/get';
2
+ import has from 'lodash/has';
3
+ import isObject from 'lodash/isObject';
4
+ import isString from 'lodash/isString';
5
+ import reduce from 'lodash/reduce';
6
+ import times from 'lodash/times';
7
+
8
+ import getFirstMatchingOption from './getFirstMatchingOption';
9
+ import retrieveSchema, { resolveAllReferences } from './retrieveSchema';
10
+ import { ONE_OF_KEY, REF_KEY, JUNK_OPTION_ID, ANY_OF_KEY } from '../constants';
11
+ import guessType from '../guessType';
12
+ import { FormContextType, RJSFSchema, StrictRJSFSchema, ValidatorType } from '../types';
13
+ import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
14
+
15
+ /** A junk option used to determine when the getFirstMatchingOption call really matches an option rather than returning
16
+ * the first item
17
+ */
18
+ export const JUNK_OPTION: StrictRJSFSchema = {
19
+ type: 'object',
20
+ $id: JUNK_OPTION_ID,
21
+ properties: {
22
+ __not_really_there__: {
23
+ type: 'number',
24
+ },
25
+ },
26
+ };
27
+
28
+ /** Recursive function that calculates the score of a `formData` against the given `schema`. The computation is fairly
29
+ * simple. Initially the total score is 0. When `schema.properties` object exists, then all the `key/value` pairs within
30
+ * the object are processed as follows after obtaining the formValue from `formData` using the `key`:
31
+ * - If the `value` contains a `$ref`, `calculateIndexScore()` is called recursively with the formValue and the new
32
+ * schema that is the result of the ref in the schema being resolved and that sub-schema's resulting score is added to
33
+ * the total.
34
+ * - If the `value` contains a `oneOf` and there is a formValue, then score based on the index returned from calling
35
+ * `getClosestMatchingOption()` of that oneOf.
36
+ * - If the type of the `value` is 'object', `calculateIndexScore()` is called recursively with the formValue and the
37
+ * `value` itself as the sub-schema, and the score is added to the total.
38
+ * - If the type of the `value` matches the guessed-type of the `formValue`, the score is incremented by 1, UNLESS the
39
+ * value has a `default` or `const`. In those case, if the `default` or `const` and the `formValue` match, the score
40
+ * is incremented by another 1 otherwise it is decremented by 1.
41
+ *
42
+ * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
43
+ * @param rootSchema - The root JSON schema of the entire form
44
+ * @param schema - The schema for which the score is being calculated
45
+ * @param formData - The form data associated with the schema, used to calculate the score
46
+ * @returns - The score a schema against the formData
47
+ */
48
+ export function calculateIndexScore<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
49
+ validator: ValidatorType<T, S, F>,
50
+ rootSchema: S,
51
+ schema?: S,
52
+ formData: any = {}
53
+ ): number {
54
+ let totalScore = 0;
55
+ if (schema) {
56
+ if (isObject(schema.properties)) {
57
+ totalScore += reduce(
58
+ schema.properties,
59
+ (score, value, key) => {
60
+ const formValue = get(formData, key);
61
+ if (typeof value === 'boolean') {
62
+ return score;
63
+ }
64
+ if (has(value, REF_KEY)) {
65
+ const newSchema = retrieveSchema<T, S, F>(validator, value as S, rootSchema, formValue);
66
+ return score + calculateIndexScore<T, S, F>(validator, rootSchema, newSchema, formValue || {});
67
+ }
68
+ if ((has(value, ONE_OF_KEY) || has(value, ANY_OF_KEY)) && formValue) {
69
+ const key = has(value, ONE_OF_KEY) ? ONE_OF_KEY : ANY_OF_KEY;
70
+ const discriminator = getDiscriminatorFieldFromSchema<S>(value as S);
71
+ return (
72
+ score +
73
+ getClosestMatchingOption<T, S, F>(
74
+ validator,
75
+ rootSchema,
76
+ formValue,
77
+ get(value, key) as S[],
78
+ -1,
79
+ discriminator
80
+ )
81
+ );
82
+ }
83
+ if (value.type === 'object') {
84
+ return score + calculateIndexScore<T, S, F>(validator, rootSchema, value as S, formValue || {});
85
+ }
86
+ if (value.type === guessType(formValue)) {
87
+ // If the types match, then we bump the score by one
88
+ let newScore = score + 1;
89
+ if (value.default) {
90
+ // If the schema contains a readonly default value score the value that matches the default higher and
91
+ // any non-matching value lower
92
+ newScore += formValue === value.default ? 1 : -1;
93
+ } else if (value.const) {
94
+ // If the schema contains a const value score the value that matches the default higher and
95
+ // any non-matching value lower
96
+ newScore += formValue === value.const ? 1 : -1;
97
+ }
98
+ // TODO eventually, deal with enums/arrays
99
+ return newScore;
100
+ }
101
+ return score;
102
+ },
103
+ 0
104
+ );
105
+ } else if (isString(schema.type) && schema.type === guessType(formData)) {
106
+ totalScore += 1;
107
+ }
108
+ }
109
+ return totalScore;
110
+ }
111
+
112
+ /** Determines which of the given `options` provided most closely matches the `formData`. Using
113
+ * `getFirstMatchingOption()` to match two schemas that differ only by the readOnly, default or const value of a field
114
+ * based on the `formData` and returns 0 when there is no match. Rather than passing in all the `options` at once to
115
+ * this utility, instead an array of valid option indexes is created by iterating over the list of options, call
116
+ * `getFirstMatchingOptions` with a list of one junk option and one good option, seeing if the good option is considered
117
+ * matched.
118
+ *
119
+ * Once the list of valid indexes is created, if there is only one valid index, just return it. Otherwise, if there are
120
+ * no valid indexes, then fill the valid indexes array with the indexes of all the options. Next, the index of the
121
+ * option with the highest score is determined by iterating over the list of valid options, calling
122
+ * `calculateIndexScore()` on each, comparing it against the current best score, and returning the index of the one that
123
+ * eventually has the best score.
124
+ *
125
+ * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
126
+ * @param rootSchema - The root JSON schema of the entire form
127
+ * @param formData - The form data associated with the schema
128
+ * @param options - The list of options that can be selected from
129
+ * @param [selectedOption=-1] - The index of the currently selected option, defaulted to -1 if not specified
130
+ * @param [discriminatorField] - The optional name of the field within the options object whose value is used to
131
+ * determine which option is selected
132
+ * @returns - The index of the option that is the closest match to the `formData` or the `selectedOption` if no match
133
+ */
134
+ export default function getClosestMatchingOption<
135
+ T = any,
136
+ S extends StrictRJSFSchema = RJSFSchema,
137
+ F extends FormContextType = any
138
+ >(
139
+ validator: ValidatorType<T, S, F>,
140
+ rootSchema: S,
141
+ formData: T | undefined,
142
+ options: S[],
143
+ selectedOption = -1,
144
+ discriminatorField?: string
145
+ ): number {
146
+ // First resolve any refs in the options
147
+ const resolvedOptions = options.map((option) => {
148
+ return resolveAllReferences(option, rootSchema);
149
+ });
150
+ // Reduce the array of options down to a list of the indexes that are considered matching options
151
+ const allValidIndexes = resolvedOptions.reduce((validList: number[], option, index: number) => {
152
+ const testOptions: S[] = [JUNK_OPTION as S, option];
153
+ const match = getFirstMatchingOption<T, S, F>(validator, formData, testOptions, rootSchema, discriminatorField);
154
+ // The match is the real option, so add its index to list of valid indexes
155
+ if (match === 1) {
156
+ validList.push(index);
157
+ }
158
+ return validList;
159
+ }, []);
160
+
161
+ // There is only one valid index, so return it!
162
+ if (allValidIndexes.length === 1) {
163
+ return allValidIndexes[0];
164
+ }
165
+ if (!allValidIndexes.length) {
166
+ // No indexes were valid, so we'll score all the options, add all the indexes
167
+ times(resolvedOptions.length, (i) => allValidIndexes.push(i));
168
+ }
169
+ type BestType = { bestIndex: number; bestScore: number };
170
+ const scoreCount = new Set<number>();
171
+ // Score all the options in the list of valid indexes and return the index with the best score
172
+ const { bestIndex }: BestType = allValidIndexes.reduce(
173
+ (scoreData: BestType, index: number) => {
174
+ const { bestScore } = scoreData;
175
+ const option = resolvedOptions[index];
176
+ const score = calculateIndexScore(validator, rootSchema, option, formData);
177
+ scoreCount.add(score);
178
+ if (score > bestScore) {
179
+ return { bestIndex: index, bestScore: score };
180
+ }
181
+ return scoreData;
182
+ },
183
+ { bestIndex: selectedOption, bestScore: 0 }
184
+ );
185
+ // if all scores are the same go with selectedOption
186
+ if (scoreCount.size === 1 && selectedOption >= 0) {
187
+ return selectedOption;
188
+ }
189
+
190
+ return bestIndex;
191
+ }
@@ -0,0 +1,447 @@
1
+ import get from 'lodash/get';
2
+ import isEmpty from 'lodash/isEmpty';
3
+
4
+ import { ANY_OF_KEY, DEFAULT_KEY, DEPENDENCIES_KEY, PROPERTIES_KEY, ONE_OF_KEY, REF_KEY } from '../constants';
5
+ import findSchemaDefinition from '../findSchemaDefinition';
6
+ import getClosestMatchingOption from './getClosestMatchingOption';
7
+ import getDiscriminatorFieldFromSchema from '../getDiscriminatorFieldFromSchema';
8
+ import getSchemaType from '../getSchemaType';
9
+ import isObject from '../isObject';
10
+ import isFixedItems from '../isFixedItems';
11
+ import mergeDefaultsWithFormData from '../mergeDefaultsWithFormData';
12
+ import mergeObjects from '../mergeObjects';
13
+ import mergeSchemas from '../mergeSchemas';
14
+ import {
15
+ Experimental_DefaultFormStateBehavior,
16
+ FormContextType,
17
+ GenericObjectType,
18
+ RJSFSchema,
19
+ StrictRJSFSchema,
20
+ ValidatorType,
21
+ } from '../types';
22
+ import isMultiSelect from './isMultiSelect';
23
+ import retrieveSchema, { resolveDependencies } from './retrieveSchema';
24
+
25
+ /** Enum that indicates how `schema.additionalItems` should be handled by the `getInnerSchemaForArrayItem()` function.
26
+ */
27
+ export enum AdditionalItemsHandling {
28
+ Ignore,
29
+ Invert,
30
+ Fallback,
31
+ }
32
+
33
+ /** Given a `schema` will return an inner schema that for an array item. This is computed differently based on the
34
+ * `additionalItems` enum and the value of `idx`. There are four possible returns:
35
+ * 1. If `idx` is >= 0, then if `schema.items` is an array the `idx`th element of the array is returned if it is a valid
36
+ * index and not a boolean, otherwise it falls through to 3.
37
+ * 2. If `schema.items` is not an array AND truthy and not a boolean, then `schema.items` is returned since it actually
38
+ * is a schema, otherwise it falls through to 3.
39
+ * 3. If `additionalItems` is not `AdditionalItemsHandling.Ignore` and `schema.additionalItems` is an object, then
40
+ * `schema.additionalItems` is returned since it actually is a schema, otherwise it falls through to 4.
41
+ * 4. {} is returned representing an empty schema
42
+ *
43
+ * @param schema - The schema from which to get the particular item
44
+ * @param [additionalItems=AdditionalItemsHandling.Ignore] - How do we want to handle additional items?
45
+ * @param [idx=-1] - Index, if non-negative, will be used to return the idx-th element in a `schema.items` array
46
+ * @returns - The best fit schema object from the `schema` given the `additionalItems` and `idx` modifiers
47
+ */
48
+ export function getInnerSchemaForArrayItem<S extends StrictRJSFSchema = RJSFSchema>(
49
+ schema: S,
50
+ additionalItems: AdditionalItemsHandling = AdditionalItemsHandling.Ignore,
51
+ idx = -1
52
+ ): S {
53
+ if (idx >= 0) {
54
+ if (Array.isArray(schema.items) && idx < schema.items.length) {
55
+ const item = schema.items[idx];
56
+ if (typeof item !== 'boolean') {
57
+ return item as S;
58
+ }
59
+ }
60
+ } else if (schema.items && !Array.isArray(schema.items) && typeof schema.items !== 'boolean') {
61
+ return schema.items as S;
62
+ }
63
+ if (additionalItems !== AdditionalItemsHandling.Ignore && isObject(schema.additionalItems)) {
64
+ return schema.additionalItems as S;
65
+ }
66
+ return {} as S;
67
+ }
68
+
69
+ /** Either add `computedDefault` at `key` into `obj` or not add it based on its value, the value of
70
+ * `includeUndefinedValues`, the value of `emptyObjectFields` and if its parent field is required. Generally undefined
71
+ * `computedDefault` values are added only when `includeUndefinedValues` is either true/"excludeObjectChildren". If `
72
+ * includeUndefinedValues` is false and `emptyObjectFields` is not "skipDefaults", then non-undefined and non-empty-object
73
+ * values will be added based on certain conditions.
74
+ *
75
+ * @param obj - The object into which the computed default may be added
76
+ * @param key - The key into the object at which the computed default may be added
77
+ * @param computedDefault - The computed default value that maybe should be added to the obj
78
+ * @param includeUndefinedValues - Optional flag, if true, cause undefined values to be added as defaults.
79
+ * If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as
80
+ * false when computing defaults for any nested object properties. If "allowEmptyObject", prevents undefined
81
+ * values in this object while allow the object itself to be empty and passing `includeUndefinedValues` as
82
+ * false when computing defaults for any nested object properties.
83
+ * @param isParentRequired - The optional boolean that indicates whether the parent field is required
84
+ * @param requiredFields - The list of fields that are required
85
+ * @param experimental_defaultFormStateBehavior - Optional configuration object, if provided, allows users to override
86
+ * default form state behavior
87
+ */
88
+ function maybeAddDefaultToObject<T = any>(
89
+ obj: GenericObjectType,
90
+ key: string,
91
+ computedDefault: T | T[] | undefined,
92
+ includeUndefinedValues: boolean | 'excludeObjectChildren',
93
+ isParentRequired?: boolean,
94
+ requiredFields: string[] = [],
95
+ experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior = {}
96
+ ) {
97
+ const { emptyObjectFields = 'populateAllDefaults' } = experimental_defaultFormStateBehavior;
98
+ if (includeUndefinedValues) {
99
+ obj[key] = computedDefault;
100
+ } else if (emptyObjectFields !== 'skipDefaults') {
101
+ if (isObject(computedDefault)) {
102
+ // If isParentRequired is undefined, then we are at the root level of the schema so defer to the requiredness of
103
+ // the field key itself in the `requiredField` list
104
+ const isSelfOrParentRequired = isParentRequired === undefined ? requiredFields.includes(key) : isParentRequired;
105
+ // Store computedDefault if it's a non-empty object(e.g. not {}) and satisfies certain conditions
106
+ // Condition 1: If computedDefault is not empty or if the key is a required field
107
+ // Condition 2: If the parent object is required or emptyObjectFields is not 'populateRequiredDefaults'
108
+ if (
109
+ (!isEmpty(computedDefault) || requiredFields.includes(key)) &&
110
+ (isSelfOrParentRequired || emptyObjectFields !== 'populateRequiredDefaults')
111
+ ) {
112
+ obj[key] = computedDefault;
113
+ }
114
+ } else if (
115
+ // Store computedDefault if it's a defined primitive (e.g., true) and satisfies certain conditions
116
+ // Condition 1: computedDefault is not undefined
117
+ // Condition 2: If emptyObjectFields is 'populateAllDefaults' or if the key is a required field
118
+ computedDefault !== undefined &&
119
+ (emptyObjectFields === 'populateAllDefaults' || requiredFields.includes(key))
120
+ ) {
121
+ obj[key] = computedDefault;
122
+ }
123
+ }
124
+ }
125
+
126
+ interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema> {
127
+ parentDefaults?: T;
128
+ rootSchema?: S;
129
+ rawFormData?: T;
130
+ includeUndefinedValues?: boolean | 'excludeObjectChildren';
131
+ _recurseList?: string[];
132
+ experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior;
133
+ required?: boolean;
134
+ }
135
+
136
+ /** Computes the defaults for the current `schema` given the `rawFormData` and `parentDefaults` if any. This drills into
137
+ * each level of the schema, recursively, to fill out every level of defaults provided by the schema.
138
+ *
139
+ * @param validator - an implementation of the `ValidatorType` interface that will be used when necessary
140
+ * @param rawSchema - The schema for which the default state is desired
141
+ * @param [props] - Optional props for this function
142
+ * @param [props.parentDefaults] - Any defaults provided by the parent field in the schema
143
+ * @param [props.rootSchema] - The options root schema, used to primarily to look up `$ref`s
144
+ * @param [props.rawFormData] - The current formData, if any, onto which to provide any missing defaults
145
+ * @param [props.includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults.
146
+ * If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as
147
+ * false when computing defaults for any nested object properties.
148
+ * @param [props._recurseList=[]] - The list of ref names currently being recursed, used to prevent infinite recursion
149
+ * @param [props.experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
150
+ * @param [props.required] - Optional flag, if true, indicates this schema was required in the parent schema.
151
+ * @returns - The resulting `formData` with all the defaults provided
152
+ */
153
+ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
154
+ validator: ValidatorType<T, S, F>,
155
+ rawSchema: S,
156
+ {
157
+ parentDefaults,
158
+ rawFormData,
159
+ rootSchema = {} as S,
160
+ includeUndefinedValues = false,
161
+ _recurseList = [],
162
+ experimental_defaultFormStateBehavior = undefined,
163
+ required,
164
+ }: ComputeDefaultsProps<T, S> = {}
165
+ ): T | T[] | undefined {
166
+ const formData: T = (isObject(rawFormData) ? rawFormData : {}) as T;
167
+ const schema: S = isObject(rawSchema) ? rawSchema : ({} as S);
168
+ // Compute the defaults recursively: give highest priority to deepest nodes.
169
+ let defaults: T | T[] | undefined = parentDefaults;
170
+ // If we get a new schema, then we need to recompute defaults again for the new schema found.
171
+ let schemaToCompute: S | null = null;
172
+ let updatedRecurseList = _recurseList;
173
+
174
+ if (isObject(defaults) && isObject(schema.default)) {
175
+ // For object defaults, only override parent defaults that are defined in
176
+ // schema.default.
177
+ defaults = mergeObjects(defaults!, schema.default as GenericObjectType) as T;
178
+ } else if (DEFAULT_KEY in schema) {
179
+ defaults = schema.default as unknown as T;
180
+ } else if (REF_KEY in schema) {
181
+ const refName = schema[REF_KEY];
182
+ // Use referenced schema defaults for this node.
183
+ if (!_recurseList.includes(refName!)) {
184
+ updatedRecurseList = _recurseList.concat(refName!);
185
+ schemaToCompute = findSchemaDefinition<S>(refName, rootSchema);
186
+ }
187
+ } else if (DEPENDENCIES_KEY in schema) {
188
+ const resolvedSchema = resolveDependencies<T, S, F>(validator, schema, rootSchema, false, formData);
189
+ schemaToCompute = resolvedSchema[0]; // pick the first element from resolve dependencies
190
+ } else if (isFixedItems(schema)) {
191
+ defaults = (schema.items! as S[]).map((itemSchema: S, idx: number) =>
192
+ computeDefaults<T, S>(validator, itemSchema, {
193
+ rootSchema,
194
+ includeUndefinedValues,
195
+ _recurseList,
196
+ experimental_defaultFormStateBehavior,
197
+ parentDefaults: Array.isArray(parentDefaults) ? parentDefaults[idx] : undefined,
198
+ rawFormData: formData as T,
199
+ required,
200
+ })
201
+ ) as T[];
202
+ } else if (ONE_OF_KEY in schema) {
203
+ const { oneOf, ...remaining } = schema;
204
+ if (oneOf!.length === 0) {
205
+ return undefined;
206
+ }
207
+ const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
208
+ schemaToCompute = oneOf![
209
+ getClosestMatchingOption<T, S, F>(
210
+ validator,
211
+ rootSchema,
212
+ isEmpty(formData) ? undefined : formData,
213
+ oneOf as S[],
214
+ 0,
215
+ discriminator
216
+ )
217
+ ] as S;
218
+ schemaToCompute = mergeSchemas(remaining, schemaToCompute) as S;
219
+ } else if (ANY_OF_KEY in schema) {
220
+ const { anyOf, ...remaining } = schema;
221
+ if (anyOf!.length === 0) {
222
+ return undefined;
223
+ }
224
+ const discriminator = getDiscriminatorFieldFromSchema<S>(schema);
225
+ schemaToCompute = anyOf![
226
+ getClosestMatchingOption<T, S, F>(
227
+ validator,
228
+ rootSchema,
229
+ isEmpty(formData) ? undefined : formData,
230
+ anyOf as S[],
231
+ 0,
232
+ discriminator
233
+ )
234
+ ] as S;
235
+ schemaToCompute = mergeSchemas(remaining, schemaToCompute) as S;
236
+ }
237
+
238
+ if (schemaToCompute) {
239
+ return computeDefaults<T, S, F>(validator, schemaToCompute, {
240
+ rootSchema,
241
+ includeUndefinedValues,
242
+ _recurseList: updatedRecurseList,
243
+ experimental_defaultFormStateBehavior,
244
+ parentDefaults: defaults as T | undefined,
245
+ rawFormData: formData as T,
246
+ required,
247
+ });
248
+ }
249
+
250
+ // No defaults defined for this node, fallback to generic typed ones.
251
+ if (defaults === undefined) {
252
+ defaults = schema.default as unknown as T;
253
+ }
254
+
255
+ switch (getSchemaType<S>(schema)) {
256
+ // We need to recurse for object schema inner default values.
257
+ case 'object': {
258
+ const objectDefaults = Object.keys(schema.properties || {}).reduce((acc: GenericObjectType, key: string) => {
259
+ // Compute the defaults for this node, with the parent defaults we might
260
+ // have from a previous run: defaults[key].
261
+ const computedDefault = computeDefaults<T, S, F>(validator, get(schema, [PROPERTIES_KEY, key]), {
262
+ rootSchema,
263
+ _recurseList,
264
+ experimental_defaultFormStateBehavior,
265
+ includeUndefinedValues: includeUndefinedValues === true,
266
+ parentDefaults: get(defaults, [key]),
267
+ rawFormData: get(formData, [key]),
268
+ required: schema.required?.includes(key),
269
+ });
270
+ maybeAddDefaultToObject<T>(
271
+ acc,
272
+ key,
273
+ computedDefault,
274
+ includeUndefinedValues,
275
+ required,
276
+ schema.required,
277
+ experimental_defaultFormStateBehavior
278
+ );
279
+ return acc;
280
+ }, {}) as T;
281
+ if (schema.additionalProperties) {
282
+ // as per spec additionalProperties may be either schema or boolean
283
+ const additionalPropertiesSchema = isObject(schema.additionalProperties) ? schema.additionalProperties : {};
284
+ const keys = new Set<string>();
285
+ if (isObject(defaults)) {
286
+ Object.keys(defaults as GenericObjectType)
287
+ .filter((key) => !schema.properties || !schema.properties[key])
288
+ .forEach((key) => keys.add(key));
289
+ }
290
+ let formDataRequired: string[];
291
+ if (isObject(formData)) {
292
+ formDataRequired = [];
293
+ Object.keys(formData as GenericObjectType)
294
+ .filter((key) => !schema.properties || !schema.properties[key])
295
+ .forEach((key) => {
296
+ keys.add(key);
297
+ formDataRequired.push(key);
298
+ });
299
+ }
300
+ keys.forEach((key) => {
301
+ const computedDefault = computeDefaults(validator, additionalPropertiesSchema as S, {
302
+ rootSchema,
303
+ _recurseList,
304
+ experimental_defaultFormStateBehavior,
305
+ includeUndefinedValues: includeUndefinedValues === true,
306
+ parentDefaults: get(defaults, [key]),
307
+ rawFormData: get(formData, [key]),
308
+ required: schema.required?.includes(key),
309
+ });
310
+ // Since these are additional properties we don’t need to add the `experimental_defaultFormStateBehavior` prop
311
+ maybeAddDefaultToObject<T>(
312
+ objectDefaults as GenericObjectType,
313
+ key,
314
+ computedDefault,
315
+ includeUndefinedValues,
316
+ required,
317
+ formDataRequired
318
+ );
319
+ });
320
+ }
321
+ return objectDefaults;
322
+ }
323
+ case 'array': {
324
+ const neverPopulate = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'never';
325
+ const ignoreMinItemsFlagSet = experimental_defaultFormStateBehavior?.arrayMinItems?.populate === 'requiredOnly';
326
+
327
+ // Inject defaults into existing array defaults
328
+ if (Array.isArray(defaults)) {
329
+ defaults = defaults.map((item, idx) => {
330
+ const schemaItem: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Fallback, idx);
331
+ return computeDefaults<T, S, F>(validator, schemaItem, {
332
+ rootSchema,
333
+ _recurseList,
334
+ experimental_defaultFormStateBehavior,
335
+ parentDefaults: item,
336
+ required,
337
+ });
338
+ }) as T[];
339
+ }
340
+
341
+ // Deeply inject defaults into already existing form data
342
+ if (Array.isArray(rawFormData)) {
343
+ const schemaItem: S = getInnerSchemaForArrayItem<S>(schema);
344
+ if (neverPopulate) {
345
+ defaults = rawFormData;
346
+ } else {
347
+ defaults = rawFormData.map((item: T, idx: number) => {
348
+ return computeDefaults<T, S, F>(validator, schemaItem, {
349
+ rootSchema,
350
+ _recurseList,
351
+ experimental_defaultFormStateBehavior,
352
+ rawFormData: item,
353
+ parentDefaults: get(defaults, [idx]),
354
+ required,
355
+ });
356
+ }) as T[];
357
+ }
358
+ }
359
+
360
+ if (neverPopulate) {
361
+ return defaults ?? [];
362
+ }
363
+ if (ignoreMinItemsFlagSet && !required) {
364
+ // If no form data exists or defaults are set leave the field empty/non-existent, otherwise
365
+ // return form data/defaults
366
+ return defaults ? defaults : undefined;
367
+ }
368
+
369
+ const defaultsLength = Array.isArray(defaults) ? defaults.length : 0;
370
+ if (
371
+ !schema.minItems ||
372
+ isMultiSelect<T, S, F>(validator, schema, rootSchema) ||
373
+ schema.minItems <= defaultsLength
374
+ ) {
375
+ return defaults ? defaults : [];
376
+ }
377
+
378
+ const defaultEntries: T[] = (defaults || []) as T[];
379
+ const fillerSchema: S = getInnerSchemaForArrayItem<S>(schema, AdditionalItemsHandling.Invert);
380
+ const fillerDefault = fillerSchema.default;
381
+
382
+ // Calculate filler entries for remaining items (minItems - existing raw data/defaults)
383
+ const fillerEntries: T[] = new Array(schema.minItems - defaultsLength).fill(
384
+ computeDefaults<any, S, F>(validator, fillerSchema, {
385
+ parentDefaults: fillerDefault,
386
+ rootSchema,
387
+ _recurseList,
388
+ experimental_defaultFormStateBehavior,
389
+ required,
390
+ })
391
+ ) as T[];
392
+ // then fill up the rest with either the item default or empty, up to minItems
393
+ return defaultEntries.concat(fillerEntries);
394
+ }
395
+ }
396
+
397
+ return defaults;
398
+ }
399
+
400
+ /** Returns the superset of `formData` that includes the given set updated to include any missing fields that have
401
+ * computed to have defaults provided in the `schema`.
402
+ *
403
+ * @param validator - An implementation of the `ValidatorType` interface that will be used when necessary
404
+ * @param theSchema - The schema for which the default state is desired
405
+ * @param [formData] - The current formData, if any, onto which to provide any missing defaults
406
+ * @param [rootSchema] - The root schema, used to primarily to look up `$ref`s
407
+ * @param [includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults.
408
+ * If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as
409
+ * false when computing defaults for any nested object properties.
410
+ * @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior
411
+ * @returns - The resulting `formData` with all the defaults provided
412
+ */
413
+ export default function getDefaultFormState<
414
+ T = any,
415
+ S extends StrictRJSFSchema = RJSFSchema,
416
+ F extends FormContextType = any
417
+ >(
418
+ validator: ValidatorType<T, S, F>,
419
+ theSchema: S,
420
+ formData?: T,
421
+ rootSchema?: S,
422
+ includeUndefinedValues: boolean | 'excludeObjectChildren' = false,
423
+ experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior
424
+ ) {
425
+ if (!isObject(theSchema)) {
426
+ throw new Error('Invalid schema: ' + theSchema);
427
+ }
428
+ const schema = retrieveSchema<T, S, F>(validator, theSchema, rootSchema, formData);
429
+ const defaults = computeDefaults<T, S, F>(validator, schema, {
430
+ rootSchema,
431
+ includeUndefinedValues,
432
+ experimental_defaultFormStateBehavior,
433
+ rawFormData: formData,
434
+ });
435
+ if (formData === undefined || formData === null || (typeof formData === 'number' && isNaN(formData))) {
436
+ // No form data? Use schema defaults.
437
+ return defaults;
438
+ }
439
+ const { mergeExtraDefaults } = experimental_defaultFormStateBehavior?.arrayMinItems || {};
440
+ if (isObject(formData)) {
441
+ return mergeDefaultsWithFormData<T>(defaults as T, formData, mergeExtraDefaults);
442
+ }
443
+ if (Array.isArray(formData)) {
444
+ return mergeDefaultsWithFormData<T[]>(defaults as T[], formData, mergeExtraDefaults);
445
+ }
446
+ return formData;
447
+ }