@rjsf/utils 6.0.0-alpha.0 → 6.0.0-beta.10

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 (279) hide show
  1. package/dist/index.js +1347 -642
  2. package/dist/index.js.map +4 -4
  3. package/dist/utils.esm.js +1324 -619
  4. package/dist/utils.esm.js.map +4 -4
  5. package/dist/utils.umd.js +1266 -590
  6. package/lib/ErrorSchemaBuilder.d.ts +8 -4
  7. package/lib/ErrorSchemaBuilder.js +10 -8
  8. package/lib/ErrorSchemaBuilder.js.map +1 -1
  9. package/lib/allowAdditionalItems.d.ts +1 -1
  10. package/lib/allowAdditionalItems.js +1 -1
  11. package/lib/allowAdditionalItems.js.map +1 -1
  12. package/lib/asNumber.js.map +1 -1
  13. package/lib/canExpand.d.ts +2 -2
  14. package/lib/canExpand.js +2 -2
  15. package/lib/canExpand.js.map +1 -1
  16. package/lib/constIsAjvDataReference.d.ts +9 -0
  17. package/lib/constIsAjvDataReference.js +15 -0
  18. package/lib/constIsAjvDataReference.js.map +1 -0
  19. package/lib/constants.d.ts +15 -3
  20. package/lib/constants.js +15 -3
  21. package/lib/constants.js.map +1 -1
  22. package/lib/createErrorHandler.d.ts +1 -1
  23. package/lib/createErrorHandler.js +2 -2
  24. package/lib/createErrorHandler.js.map +1 -1
  25. package/lib/createSchemaUtils.d.ts +3 -2
  26. package/lib/createSchemaUtils.js +56 -46
  27. package/lib/createSchemaUtils.js.map +1 -1
  28. package/lib/dataURItoBlob.js.map +1 -1
  29. package/lib/dateRangeOptions.d.ts +1 -1
  30. package/lib/dateRangeOptions.js +1 -1
  31. package/lib/dateRangeOptions.js.map +1 -1
  32. package/lib/deepEquals.js +1 -1
  33. package/lib/deepEquals.js.map +1 -1
  34. package/lib/englishStringTranslator.d.ts +1 -1
  35. package/lib/englishStringTranslator.js +1 -1
  36. package/lib/enumOptionsDeselectValue.d.ts +1 -1
  37. package/lib/enumOptionsDeselectValue.js +4 -4
  38. package/lib/enumOptionsDeselectValue.js.map +1 -1
  39. package/lib/enumOptionsIndexForValue.d.ts +1 -1
  40. package/lib/enumOptionsIndexForValue.js +1 -1
  41. package/lib/enumOptionsIndexForValue.js.map +1 -1
  42. package/lib/enumOptionsIsSelected.d.ts +1 -1
  43. package/lib/enumOptionsIsSelected.js +3 -3
  44. package/lib/enumOptionsIsSelected.js.map +1 -1
  45. package/lib/enumOptionsSelectValue.d.ts +1 -1
  46. package/lib/enumOptionsSelectValue.js +2 -2
  47. package/lib/enumOptionsSelectValue.js.map +1 -1
  48. package/lib/enumOptionsValueForIndex.d.ts +1 -1
  49. package/lib/enumOptionsValueForIndex.js.map +1 -1
  50. package/lib/enums.d.ts +2 -0
  51. package/lib/enums.js +2 -0
  52. package/lib/enums.js.map +1 -1
  53. package/lib/findSchemaDefinition.d.ts +5 -3
  54. package/lib/findSchemaDefinition.js +54 -11
  55. package/lib/findSchemaDefinition.js.map +1 -1
  56. package/lib/getChangedFields.d.ts +17 -0
  57. package/lib/getChangedFields.js +42 -0
  58. package/lib/getChangedFields.js.map +1 -0
  59. package/lib/getDateElementProps.d.ts +1 -1
  60. package/lib/getDateElementProps.js.map +1 -1
  61. package/lib/getDiscriminatorFieldFromSchema.d.ts +1 -1
  62. package/lib/getDiscriminatorFieldFromSchema.js +4 -3
  63. package/lib/getDiscriminatorFieldFromSchema.js.map +1 -1
  64. package/lib/getInputProps.d.ts +1 -1
  65. package/lib/getInputProps.js +4 -1
  66. package/lib/getInputProps.js.map +1 -1
  67. package/lib/getOptionMatchingSimpleDiscriminator.d.ts +1 -1
  68. package/lib/getOptionMatchingSimpleDiscriminator.js +2 -2
  69. package/lib/getOptionMatchingSimpleDiscriminator.js.map +1 -1
  70. package/lib/getSchemaType.d.ts +2 -1
  71. package/lib/getSchemaType.js +3 -2
  72. package/lib/getSchemaType.js.map +1 -1
  73. package/lib/getSubmitButtonOptions.d.ts +1 -1
  74. package/lib/getSubmitButtonOptions.js +2 -2
  75. package/lib/getSubmitButtonOptions.js.map +1 -1
  76. package/lib/getTemplate.d.ts +1 -1
  77. package/lib/getTemplate.js +9 -0
  78. package/lib/getTemplate.js.map +1 -1
  79. package/lib/getTestIds.d.ts +17 -0
  80. package/lib/getTestIds.js +34 -0
  81. package/lib/getTestIds.js.map +1 -0
  82. package/lib/getUiOptions.d.ts +1 -1
  83. package/lib/getUiOptions.js +2 -2
  84. package/lib/getUiOptions.js.map +1 -1
  85. package/lib/getWidget.d.ts +1 -1
  86. package/lib/getWidget.js +3 -3
  87. package/lib/getWidget.js.map +1 -1
  88. package/lib/guessType.d.ts +1 -1
  89. package/lib/guessType.js.map +1 -1
  90. package/lib/hasWidget.d.ts +1 -1
  91. package/lib/hasWidget.js +1 -1
  92. package/lib/hasWidget.js.map +1 -1
  93. package/lib/hashForSchema.d.ts +23 -1
  94. package/lib/hashForSchema.js +24 -6
  95. package/lib/hashForSchema.js.map +1 -1
  96. package/lib/idGenerators.d.ts +8 -1
  97. package/lib/idGenerators.js +11 -2
  98. package/lib/idGenerators.js.map +1 -1
  99. package/lib/index.d.ts +63 -60
  100. package/lib/index.js +63 -60
  101. package/lib/index.js.map +1 -1
  102. package/lib/isConstant.d.ts +1 -1
  103. package/lib/isConstant.js +1 -1
  104. package/lib/isCustomWidget.d.ts +1 -1
  105. package/lib/isCustomWidget.js +1 -1
  106. package/lib/isFixedItems.d.ts +1 -1
  107. package/lib/isFixedItems.js +1 -1
  108. package/lib/isObject.d.ts +2 -2
  109. package/lib/isObject.js +11 -4
  110. package/lib/isObject.js.map +1 -1
  111. package/lib/lookupFromFormContext.d.ts +11 -0
  112. package/lib/lookupFromFormContext.js +20 -0
  113. package/lib/lookupFromFormContext.js.map +1 -0
  114. package/lib/mergeDefaultsWithFormData.d.ts +8 -2
  115. package/lib/mergeDefaultsWithFormData.js +39 -10
  116. package/lib/mergeDefaultsWithFormData.js.map +1 -1
  117. package/lib/mergeObjects.d.ts +1 -1
  118. package/lib/mergeObjects.js +1 -1
  119. package/lib/mergeObjects.js.map +1 -1
  120. package/lib/mergeSchemas.d.ts +1 -1
  121. package/lib/mergeSchemas.js +4 -4
  122. package/lib/mergeSchemas.js.map +1 -1
  123. package/lib/optionsList.d.ts +9 -7
  124. package/lib/optionsList.js +30 -19
  125. package/lib/optionsList.js.map +1 -1
  126. package/lib/orderProperties.js.map +1 -1
  127. package/lib/pad.js.map +1 -1
  128. package/lib/parseDateString.d.ts +1 -1
  129. package/lib/parseDateString.js +1 -1
  130. package/lib/parseDateString.js.map +1 -1
  131. package/lib/parser/ParserValidator.d.ts +1 -1
  132. package/lib/parser/ParserValidator.js +6 -6
  133. package/lib/parser/ParserValidator.js.map +1 -1
  134. package/lib/parser/index.d.ts +2 -2
  135. package/lib/parser/index.js +1 -1
  136. package/lib/parser/schemaParser.d.ts +2 -2
  137. package/lib/parser/schemaParser.js +6 -6
  138. package/lib/parser/schemaParser.js.map +1 -1
  139. package/lib/rangeSpec.d.ts +2 -2
  140. package/lib/rangeSpec.js.map +1 -1
  141. package/lib/replaceStringParameters.js.map +1 -1
  142. package/lib/schema/findFieldInSchema.d.ts +19 -0
  143. package/lib/schema/findFieldInSchema.js +61 -0
  144. package/lib/schema/findFieldInSchema.js.map +1 -0
  145. package/lib/schema/findSelectedOptionInXxxOf.d.ts +16 -0
  146. package/lib/schema/findSelectedOptionInXxxOf.js +34 -0
  147. package/lib/schema/findSelectedOptionInXxxOf.js.map +1 -0
  148. package/lib/schema/getClosestMatchingOption.d.ts +5 -3
  149. package/lib/schema/getClosestMatchingOption.js +28 -20
  150. package/lib/schema/getClosestMatchingOption.js.map +1 -1
  151. package/lib/schema/getDefaultFormState.d.ts +60 -13
  152. package/lib/schema/getDefaultFormState.js +316 -167
  153. package/lib/schema/getDefaultFormState.js.map +1 -1
  154. package/lib/schema/getDisplayLabel.d.ts +3 -2
  155. package/lib/schema/getDisplayLabel.js +10 -9
  156. package/lib/schema/getDisplayLabel.js.map +1 -1
  157. package/lib/schema/getFirstMatchingOption.d.ts +1 -1
  158. package/lib/schema/getFirstMatchingOption.js +70 -2
  159. package/lib/schema/getFirstMatchingOption.js.map +1 -1
  160. package/lib/schema/getFromSchema.d.ts +14 -0
  161. package/lib/schema/getFromSchema.js +39 -0
  162. package/lib/schema/getFromSchema.js.map +1 -0
  163. package/lib/schema/index.d.ts +15 -14
  164. package/lib/schema/index.js +15 -14
  165. package/lib/schema/index.js.map +1 -1
  166. package/lib/schema/isFilesArray.d.ts +3 -2
  167. package/lib/schema/isFilesArray.js +5 -4
  168. package/lib/schema/isFilesArray.js.map +1 -1
  169. package/lib/schema/isMultiSelect.d.ts +3 -2
  170. package/lib/schema/isMultiSelect.js +4 -3
  171. package/lib/schema/isMultiSelect.js.map +1 -1
  172. package/lib/schema/isSelect.d.ts +3 -2
  173. package/lib/schema/isSelect.js +5 -4
  174. package/lib/schema/isSelect.js.map +1 -1
  175. package/lib/schema/retrieveSchema.d.ts +30 -12
  176. package/lib/schema/retrieveSchema.js +153 -70
  177. package/lib/schema/retrieveSchema.js.map +1 -1
  178. package/lib/schema/sanitizeDataForNewSchema.d.ts +3 -2
  179. package/lib/schema/sanitizeDataForNewSchema.js +12 -11
  180. package/lib/schema/sanitizeDataForNewSchema.js.map +1 -1
  181. package/lib/schema/toIdSchema.d.ts +3 -2
  182. package/lib/schema/toIdSchema.js +30 -27
  183. package/lib/schema/toIdSchema.js.map +1 -1
  184. package/lib/schema/toPathSchema.d.ts +3 -2
  185. package/lib/schema/toPathSchema.js +22 -20
  186. package/lib/schema/toPathSchema.js.map +1 -1
  187. package/lib/schemaRequiresTrueValue.d.ts +1 -1
  188. package/lib/schemaRequiresTrueValue.js.map +1 -1
  189. package/lib/shouldRender.js +1 -1
  190. package/lib/toConstant.d.ts +1 -1
  191. package/lib/toConstant.js +1 -1
  192. package/lib/toConstant.js.map +1 -1
  193. package/lib/toDateString.d.ts +1 -1
  194. package/lib/toErrorList.d.ts +1 -1
  195. package/lib/toErrorList.js +2 -2
  196. package/lib/toErrorList.js.map +1 -1
  197. package/lib/toErrorSchema.d.ts +1 -1
  198. package/lib/toErrorSchema.js +2 -2
  199. package/lib/toErrorSchema.js.map +1 -1
  200. package/lib/tsconfig.tsbuildinfo +1 -1
  201. package/lib/types.d.ts +172 -142
  202. package/lib/unwrapErrorHandler.d.ts +1 -1
  203. package/lib/unwrapErrorHandler.js +1 -1
  204. package/lib/unwrapErrorHandler.js.map +1 -1
  205. package/lib/utcToLocal.js +1 -1
  206. package/lib/utcToLocal.js.map +1 -1
  207. package/lib/validationDataMerge.d.ts +1 -1
  208. package/lib/validationDataMerge.js +3 -3
  209. package/lib/validationDataMerge.js.map +1 -1
  210. package/lib/withIdRefPrefix.d.ts +1 -1
  211. package/lib/withIdRefPrefix.js +2 -2
  212. package/lib/withIdRefPrefix.js.map +1 -1
  213. package/package.json +37 -26
  214. package/src/ErrorSchemaBuilder.ts +15 -8
  215. package/src/canExpand.ts +2 -2
  216. package/src/constIsAjvDataReference.ts +17 -0
  217. package/src/constants.ts +17 -3
  218. package/src/createSchemaUtils.ts +140 -50
  219. package/src/dataURItoBlob.ts +1 -1
  220. package/src/dateRangeOptions.ts +1 -1
  221. package/src/enumOptionsDeselectValue.ts +4 -5
  222. package/src/enumOptionsIndexForValue.ts +1 -1
  223. package/src/enumOptionsIsSelected.ts +4 -5
  224. package/src/enumOptionsSelectValue.ts +1 -1
  225. package/src/enumOptionsValueForIndex.ts +1 -1
  226. package/src/enums.ts +2 -0
  227. package/src/findSchemaDefinition.ts +55 -10
  228. package/src/getChangedFields.ts +40 -0
  229. package/src/getDateElementProps.ts +2 -2
  230. package/src/getDiscriminatorFieldFromSchema.ts +2 -1
  231. package/src/getInputProps.ts +6 -2
  232. package/src/getOptionMatchingSimpleDiscriminator.ts +2 -2
  233. package/src/getSchemaType.ts +3 -2
  234. package/src/getSubmitButtonOptions.ts +1 -1
  235. package/src/getTemplate.ts +12 -1
  236. package/src/getTestIds.ts +40 -0
  237. package/src/getUiOptions.ts +2 -2
  238. package/src/getWidget.tsx +2 -2
  239. package/src/hasWidget.ts +1 -1
  240. package/src/hashForSchema.ts +26 -6
  241. package/src/idGenerators.ts +10 -0
  242. package/src/index.ts +21 -2
  243. package/src/isCustomWidget.ts +1 -1
  244. package/src/isObject.ts +12 -5
  245. package/src/labelValue.ts +2 -2
  246. package/src/lookupFromFormContext.ts +26 -0
  247. package/src/mergeDefaultsWithFormData.ts +54 -9
  248. package/src/mergeObjects.ts +24 -21
  249. package/src/optionsList.ts +31 -22
  250. package/src/parser/ParserValidator.ts +5 -5
  251. package/src/parser/schemaParser.ts +6 -6
  252. package/src/schema/findFieldInSchema.ts +138 -0
  253. package/src/schema/findSelectedOptionInXxxOf.ts +53 -0
  254. package/src/schema/getClosestMatchingOption.ts +38 -11
  255. package/src/schema/getDefaultFormState.ts +461 -193
  256. package/src/schema/getDisplayLabel.ts +7 -4
  257. package/src/schema/getFirstMatchingOption.ts +79 -4
  258. package/src/schema/getFromSchema.ts +100 -0
  259. package/src/schema/index.ts +6 -4
  260. package/src/schema/isFilesArray.ts +18 -3
  261. package/src/schema/isMultiSelect.ts +10 -4
  262. package/src/schema/isSelect.ts +5 -3
  263. package/src/schema/retrieveSchema.ts +268 -78
  264. package/src/schema/sanitizeDataForNewSchema.ts +52 -11
  265. package/src/schema/toIdSchema.ts +69 -43
  266. package/src/schema/toPathSchema.ts +49 -16
  267. package/src/toErrorList.ts +2 -2
  268. package/src/types.ts +278 -184
  269. package/src/validationDataMerge.ts +1 -1
  270. package/src/withIdRefPrefix.ts +1 -1
  271. package/LICENSE.md +0 -201
  272. package/lib/schema/getMatchingOption.d.ts +0 -14
  273. package/lib/schema/getMatchingOption.js +0 -85
  274. package/lib/schema/getMatchingOption.js.map +0 -1
  275. package/lib/schema/mergeValidationData.d.ts +0 -14
  276. package/lib/schema/mergeValidationData.js +0 -28
  277. package/lib/schema/mergeValidationData.js.map +0 -1
  278. package/src/schema/getMatchingOption.ts +0 -103
  279. package/src/schema/mergeValidationData.ts +0 -38
@@ -12,12 +12,12 @@ import { FormContextType, InputPropsType, RJSFSchema, StrictRJSFSchema, UIOption
12
12
  export default function getInputProps<
13
13
  T = any,
14
14
  S extends StrictRJSFSchema = RJSFSchema,
15
- F extends FormContextType = any
15
+ F extends FormContextType = any,
16
16
  >(
17
17
  schema: RJSFSchema,
18
18
  defaultType?: string,
19
19
  options: UIOptionsType<T, S, F> = {},
20
- autoDefaultStepAny = true
20
+ autoDefaultStepAny = true,
21
21
  ): InputPropsType {
22
22
  const inputProps: InputPropsType = {
23
23
  type: defaultType || 'text',
@@ -51,5 +51,9 @@ export default function getInputProps<
51
51
  inputProps.autoComplete = options.autocomplete;
52
52
  }
53
53
 
54
+ if (options.accept) {
55
+ inputProps.accept = options.accept as string;
56
+ }
57
+
54
58
  return inputProps;
55
59
  }
@@ -15,7 +15,7 @@ import { RJSFSchema, StrictRJSFSchema } from './types';
15
15
  export default function getOptionMatchingSimpleDiscriminator<T = any, S extends StrictRJSFSchema = RJSFSchema>(
16
16
  formData: T | undefined,
17
17
  options: S[],
18
- discriminatorField?: string
18
+ discriminatorField?: string,
19
19
  ): number | undefined {
20
20
  if (formData && discriminatorField) {
21
21
  const value = get(formData, discriminatorField);
@@ -26,7 +26,7 @@ export default function getOptionMatchingSimpleDiscriminator<T = any, S extends
26
26
 
27
27
  for (let i = 0; i < options.length; i++) {
28
28
  const option = options[i];
29
- const discriminator = get(option, [PROPERTIES_KEY, discriminatorField], {});
29
+ const discriminator: S = get(option, [PROPERTIES_KEY, discriminatorField], {}) as S;
30
30
 
31
31
  if (discriminator.type === 'object' || discriminator.type === 'array') {
32
32
  continue;
@@ -7,13 +7,14 @@ import { RJSFSchema, StrictRJSFSchema } from './types';
7
7
  * - schema.enum: Returns `string`
8
8
  * - schema.properties: Returns `object`
9
9
  * - schema.additionalProperties: Returns `object`
10
+ * - schema.patternProperties: Returns `object`
10
11
  * - type is an array with a length of 2 and one type is 'null': Returns the other type
11
12
  *
12
13
  * @param schema - The schema for which to get the type
13
14
  * @returns - The type of the schema
14
15
  */
15
16
  export default function getSchemaType<S extends StrictRJSFSchema = RJSFSchema>(
16
- schema: S
17
+ schema: S,
17
18
  ): string | string[] | undefined {
18
19
  let { type } = schema;
19
20
 
@@ -25,7 +26,7 @@ export default function getSchemaType<S extends StrictRJSFSchema = RJSFSchema>(
25
26
  return 'string';
26
27
  }
27
28
 
28
- if (!type && (schema.properties || schema.additionalProperties)) {
29
+ if (!type && (schema.properties || schema.additionalProperties || schema.patternProperties)) {
29
30
  return 'object';
30
31
  }
31
32
 
@@ -20,7 +20,7 @@ export const DEFAULT_OPTIONS: UISchemaSubmitButtonOptions = {
20
20
  export default function getSubmitButtonOptions<
21
21
  T = any,
22
22
  S extends StrictRJSFSchema = RJSFSchema,
23
- F extends FormContextType = any
23
+ F extends FormContextType = any,
24
24
  >(uiSchema: UiSchema<T, S, F> = {}): UISchemaSubmitButtonOptions {
25
25
  const uiOptions = getUiOptions<T, S, F>(uiSchema);
26
26
  if (uiOptions && uiOptions[SUBMIT_BTN_OPTIONS_KEY]) {
@@ -12,12 +12,23 @@ export default function getTemplate<
12
12
  Name extends keyof TemplatesType<T, S, F>,
13
13
  T = any,
14
14
  S extends StrictRJSFSchema = RJSFSchema,
15
- F extends FormContextType = any
15
+ F extends FormContextType = any,
16
16
  >(name: Name, registry: Registry<T, S, F>, uiOptions: UIOptionsType<T, S, F> = {}): TemplatesType<T, S, F>[Name] {
17
17
  const { templates } = registry;
18
18
  if (name === 'ButtonTemplates') {
19
19
  return templates[name];
20
20
  }
21
+ // Allow templates to be customized per-field by using string keys from the registry
22
+ if (
23
+ Object.hasOwn(uiOptions, name) &&
24
+ typeof uiOptions[name] === 'string' &&
25
+ Object.hasOwn(templates, uiOptions[name] as string)
26
+ ) {
27
+ const key = uiOptions[name];
28
+ // Evaluating templates[key] results in TS2590: Expression produces a union type that is too complex to represent
29
+ // To avoid that, we cast templates to `any` before accessing the key field
30
+ return (templates as any)[key];
31
+ }
21
32
  return (
22
33
  // Evaluating uiOptions[name] results in TS2590: Expression produces a union type that is too complex to represent
23
34
  // To avoid that, we cast uiOptions to `any` before accessing the name field
@@ -0,0 +1,40 @@
1
+ import { nanoid } from 'nanoid';
2
+ import get from 'lodash/get';
3
+
4
+ import { TestIdShape } from './types';
5
+
6
+ /** Returns an object of test IDs that can only be used in test mode. If the function is called in a test environment
7
+ * (`NODE_ENV === 'test'`, this is set by jest) then a Proxy object will be returned. If a key within the returned
8
+ * object is accessed, if the value already exists the object will return that value, otherwise it will create that key
9
+ * with a generated `uuid` value and return the generated ID. If it is called outside of a test environment, the
10
+ * function will return an empty object, therefore returning `undefined` for any property within the object and
11
+ * excluding the prop from the rendered output of the component in which it is used.
12
+ * This implementation was adapted from the following blog post: https://www.matthewsessions.com/blog/react-test-id/
13
+ * To use this helper, you will want to generate a separate object for each component to avoid potential overlapping of
14
+ * ID names. You will also want to export the object for use in tests, because the keys will be generated in the
15
+ * component file, and used in the test file. Within the component file, add:
16
+ * `export const TEST_IDS = getTestIds();`
17
+ * Then pass `TEST_IDS.examplePropertyName` as the value of the test ID attribute of the intended component. This will
18
+ * allow you to use `TEST_IDS.examplePropertyName` within your tests, while keeping the test IDs out of your rendered
19
+ * output.
20
+ */
21
+ export default function getTestIds(): TestIdShape {
22
+ // For some reason, even though process.env contains the value of `test` for NODE_ENV, accessing it directly returns
23
+ // 'development'. Using `get()` does, in fact, return test so sticking with it
24
+ if (typeof process === 'undefined' || get(process, 'env.NODE_ENV') !== 'test') {
25
+ return {};
26
+ }
27
+
28
+ const ids = new Map();
29
+ return new Proxy(
30
+ {},
31
+ {
32
+ get(_obj, prop) {
33
+ if (!ids.has(prop)) {
34
+ ids.set(prop, nanoid());
35
+ }
36
+ return ids.get(prop);
37
+ },
38
+ },
39
+ );
40
+ }
@@ -11,7 +11,7 @@ import { FormContextType, GlobalUISchemaOptions, RJSFSchema, StrictRJSFSchema, U
11
11
  */
12
12
  export default function getUiOptions<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
13
13
  uiSchema: UiSchema<T, S, F> = {},
14
- globalOptions: GlobalUISchemaOptions = {}
14
+ globalOptions: GlobalUISchemaOptions = {},
15
15
  ): UIOptionsType<T, S, F> {
16
16
  return Object.keys(uiSchema)
17
17
  .filter((key) => key.indexOf('ui:') === 0)
@@ -27,6 +27,6 @@ export default function getUiOptions<T = any, S extends StrictRJSFSchema = RJSFS
27
27
  }
28
28
  return { ...options, [key.substring(3)]: value };
29
29
  },
30
- { ...globalOptions }
30
+ { ...globalOptions },
31
31
  );
32
32
  }
package/src/getWidget.tsx CHANGED
@@ -69,7 +69,7 @@ const widgetMap: { [k: string]: { [j: string]: string } } = {
69
69
  * @returns - The wrapper widget
70
70
  */
71
71
  function mergeWidgetOptions<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
72
- AWidget: Widget<T, S, F>
72
+ AWidget: Widget<T, S, F>,
73
73
  ) {
74
74
  let MergedWidget: Widget<T, S, F> | undefined = get(AWidget, 'MergedWidget');
75
75
  // cache return value as property of widget for proper react reconciliation
@@ -97,7 +97,7 @@ function mergeWidgetOptions<T = any, S extends StrictRJSFSchema = RJSFSchema, F
97
97
  export default function getWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
98
98
  schema: RJSFSchema,
99
99
  widget?: Widget<T, S, F> | string,
100
- registeredWidgets: RegistryWidgetsType<T, S, F> = {}
100
+ registeredWidgets: RegistryWidgetsType<T, S, F> = {},
101
101
  ): Widget<T, S, F> {
102
102
  const type = getSchemaType(schema);
103
103
 
package/src/hasWidget.ts CHANGED
@@ -12,7 +12,7 @@ import { FormContextType, RegistryWidgetsType, RJSFSchema, StrictRJSFSchema, Wid
12
12
  export default function hasWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
13
13
  schema: RJSFSchema,
14
14
  widget: Widget<T, S, F> | string,
15
- registeredWidgets: RegistryWidgetsType<T, S, F> = {}
15
+ registeredWidgets: RegistryWidgetsType<T, S, F> = {},
16
16
  ) {
17
17
  try {
18
18
  getWidget(schema, widget, registeredWidgets);
@@ -1,13 +1,14 @@
1
1
  import { RJSFSchema, StrictRJSFSchema } from './types';
2
2
 
3
- /** JS has no built-in hashing function, so rolling our own
3
+ /** Hashes a string using the algorithm based on Java's hashing function.
4
+ * JS has no built-in hashing function, so rolling our own
4
5
  * based on Java's hashing fn:
5
6
  * http://www.java2s.com/example/nodejs-utility-method/string-hash/hashcode-4dc2b.html
6
7
  *
7
8
  * @param string - The string for which to get the hash
8
9
  * @returns - The resulting hash of the string in hex format
9
10
  */
10
- function hashString(string: string): string {
11
+ export function hashString(string: string): string {
11
12
  let hash = 0;
12
13
  for (let i = 0; i < string.length; i += 1) {
13
14
  const chr = string.charCodeAt(i);
@@ -17,6 +18,28 @@ function hashString(string: string): string {
17
18
  return hash.toString(16);
18
19
  }
19
20
 
21
+ /** Stringifies an `object`, sorts object fields in consistent order before stringifying it.
22
+ *
23
+ * @param object - The object for which the sorted stringify is desired
24
+ * @returns - The stringified object with keys sorted in a consistent order
25
+ */
26
+ export function sortedJSONStringify(object: unknown): string {
27
+ const allKeys = new Set<string>();
28
+ // solution source: https://stackoverflow.com/questions/16167581/sort-object-properties-and-json-stringify/53593328#53593328
29
+ JSON.stringify(object, (key, value) => (allKeys.add(key), value));
30
+ return JSON.stringify(object, Array.from(allKeys).sort());
31
+ }
32
+
33
+ /** Stringifies an `object` and returns the hash of the resulting string. Sorts object fields
34
+ * in consistent order before stringify to prevent different hash ids for the same object.
35
+ *
36
+ * @param object - The object for which the hash is desired
37
+ * @returns - The string obtained from the hash of the stringified object
38
+ */
39
+ export function hashObject(object: unknown): string {
40
+ return hashString(sortedJSONStringify(object));
41
+ }
42
+
20
43
  /** Stringifies the schema and returns the hash of the resulting string. Sorts schema fields
21
44
  * in consistent order before stringify to prevent different hash ids for the same schema.
22
45
  *
@@ -24,8 +47,5 @@ function hashString(string: string): string {
24
47
  * @returns - The string obtained from the hash of the stringified schema
25
48
  */
26
49
  export default function hashForSchema<S extends StrictRJSFSchema = RJSFSchema>(schema: S) {
27
- const allKeys = new Set<string>();
28
- // solution source: https://stackoverflow.com/questions/16167581/sort-object-properties-and-json-stringify/53593328#53593328
29
- JSON.stringify(schema, (key, value) => (allKeys.add(key), value));
30
- return hashString(JSON.stringify(schema, Array.from(allKeys).sort()));
50
+ return hashObject(schema);
31
51
  }
@@ -79,3 +79,13 @@ export function ariaDescribedByIds<T = any>(id: IdSchema<T> | string, includeExa
79
79
  export function optionId(id: string, optionIndex: number) {
80
80
  return `${id}-${optionIndex}`;
81
81
  }
82
+
83
+ /** Return a consistent `id` for the `btn` button element
84
+ *
85
+ * @param id - Either simple string id or an IdSchema from which to extract it
86
+ * @param btn - The button type for which to generate the id
87
+ * @returns - The consistent id for the button from the given `id` and `btn` type
88
+ */
89
+ export function buttonId<T = any>(id: IdSchema<T> | string, btn: 'add' | 'copy' | 'moveDown' | 'moveUp' | 'remove') {
90
+ return idGenerator<T>(id, btn);
91
+ }
package/src/index.ts CHANGED
@@ -20,18 +20,29 @@ import getInputProps from './getInputProps';
20
20
  import getSchemaType from './getSchemaType';
21
21
  import getSubmitButtonOptions from './getSubmitButtonOptions';
22
22
  import getTemplate from './getTemplate';
23
+ import getTestIds from './getTestIds';
23
24
  import getUiOptions from './getUiOptions';
24
25
  import getWidget from './getWidget';
25
26
  import guessType from './guessType';
26
- import hashForSchema from './hashForSchema';
27
+ import hashForSchema, { hashObject, hashString, sortedJSONStringify } from './hashForSchema';
27
28
  import hasWidget from './hasWidget';
28
- import { ariaDescribedByIds, descriptionId, errorId, examplesId, helpId, optionId, titleId } from './idGenerators';
29
+ import {
30
+ ariaDescribedByIds,
31
+ buttonId,
32
+ descriptionId,
33
+ errorId,
34
+ examplesId,
35
+ helpId,
36
+ optionId,
37
+ titleId,
38
+ } from './idGenerators';
29
39
  import isConstant from './isConstant';
30
40
  import isCustomWidget from './isCustomWidget';
31
41
  import isFixedItems from './isFixedItems';
32
42
  import isObject from './isObject';
33
43
  import labelValue from './labelValue';
34
44
  import localToUTC from './localToUTC';
45
+ import lookupFromFormContext from './lookupFromFormContext';
35
46
  import mergeDefaultsWithFormData from './mergeDefaultsWithFormData';
36
47
  import mergeObjects from './mergeObjects';
37
48
  import mergeSchemas from './mergeSchemas';
@@ -52,6 +63,7 @@ import utcToLocal from './utcToLocal';
52
63
  import validationDataMerge from './validationDataMerge';
53
64
  import withIdRefPrefix from './withIdRefPrefix';
54
65
  import getOptionMatchingSimpleDiscriminator from './getOptionMatchingSimpleDiscriminator';
66
+ import getChangedFields from './getChangedFields';
55
67
 
56
68
  export * from './types';
57
69
  export * from './enums';
@@ -64,6 +76,7 @@ export {
64
76
  allowAdditionalItems,
65
77
  ariaDescribedByIds,
66
78
  asNumber,
79
+ buttonId,
67
80
  canExpand,
68
81
  createErrorHandler,
69
82
  createSchemaUtils,
@@ -82,6 +95,7 @@ export {
82
95
  examplesId,
83
96
  ErrorSchemaBuilder,
84
97
  findSchemaDefinition,
98
+ getChangedFields,
85
99
  getDateElementProps,
86
100
  getDiscriminatorFieldFromSchema,
87
101
  getInputProps,
@@ -89,11 +103,14 @@ export {
89
103
  getSchemaType,
90
104
  getSubmitButtonOptions,
91
105
  getTemplate,
106
+ getTestIds,
92
107
  getUiOptions,
93
108
  getWidget,
94
109
  guessType,
95
110
  hasWidget,
96
111
  hashForSchema,
112
+ hashObject,
113
+ hashString,
97
114
  helpId,
98
115
  isConstant,
99
116
  isCustomWidget,
@@ -101,6 +118,7 @@ export {
101
118
  isObject,
102
119
  labelValue,
103
120
  localToUTC,
121
+ lookupFromFormContext,
104
122
  mergeDefaultsWithFormData,
105
123
  mergeObjects,
106
124
  mergeSchemas,
@@ -113,6 +131,7 @@ export {
113
131
  replaceStringParameters,
114
132
  schemaRequiresTrueValue,
115
133
  shouldRender,
134
+ sortedJSONStringify,
116
135
  titleId,
117
136
  toConstant,
118
137
  toDateString,
@@ -9,7 +9,7 @@ import { FormContextType, RJSFSchema, StrictRJSFSchema, UiSchema } from './types
9
9
  export default function isCustomWidget<
10
10
  T = any,
11
11
  S extends StrictRJSFSchema = RJSFSchema,
12
- F extends FormContextType = any
12
+ F extends FormContextType = any,
13
13
  >(uiSchema: UiSchema<T, S, F> = {}) {
14
14
  return (
15
15
  // TODO: Remove the `&& uiSchema['ui:widget'] !== 'hidden'` once we support hidden widgets for arrays.
package/src/isObject.ts CHANGED
@@ -1,15 +1,22 @@
1
- /** Determines whether a `thing` is an object for the purposes of RSJF. In this case, `thing` is an object if it has
1
+ /** Determines whether a `thing` is an object for the purposes of RJSF. In this case, `thing` is an object if it has
2
2
  * the type `object` but is NOT null, an array or a File.
3
3
  *
4
4
  * @param thing - The thing to check to see whether it is an object
5
5
  * @returns - True if it is a non-null, non-array, non-File object
6
6
  */
7
- export default function isObject(thing: any) {
8
- if (typeof File !== 'undefined' && thing instanceof File) {
7
+ export default function isObject(thing: any): thing is object {
8
+ if (typeof thing !== 'object' || thing === null) {
9
9
  return false;
10
10
  }
11
- if (typeof Date !== 'undefined' && thing instanceof Date) {
11
+ // lastModified is guaranteed to be a number on a File instance
12
+ // as per https://w3c.github.io/FileAPI/#dfn-lastModified
13
+ if (typeof thing.lastModified === 'number' && typeof File !== 'undefined' && thing instanceof File) {
12
14
  return false;
13
15
  }
14
- return typeof thing === 'object' && thing !== null && !Array.isArray(thing);
16
+ // getMonth is guaranteed to be a method on a Date instance
17
+ // as per https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date.prototype.getmonth
18
+ if (typeof thing.getMonth === 'function' && typeof Date !== 'undefined' && thing instanceof Date) {
19
+ return false;
20
+ }
21
+ return !Array.isArray(thing);
15
22
  }
package/src/labelValue.ts CHANGED
@@ -17,12 +17,12 @@ export default function labelValue(label?: ReactElement, hideLabel?: boolean, fa
17
17
  export default function labelValue(
18
18
  label?: ReactElement,
19
19
  hideLabel?: boolean,
20
- fallback?: false
20
+ fallback?: false,
21
21
  ): undefined | false | ReactElement;
22
22
  export default function labelValue(
23
23
  label?: string | ReactElement,
24
24
  hideLabel?: boolean,
25
- fallback?: false | ''
25
+ fallback?: false | '',
26
26
  ): undefined | false | string | ReactElement {
27
27
  return hideLabel ? fallback : label;
28
28
  }
@@ -0,0 +1,26 @@
1
+ import get from 'lodash/get';
2
+ import has from 'lodash/has';
3
+
4
+ import { FORM_CONTEXT_NAME, LOOKUP_MAP_NAME } from './constants';
5
+ import { FormContextType, RJSFSchema, Registry, StrictRJSFSchema } from './types';
6
+
7
+ /** Given a React JSON Schema Form registry or formContext object, return the value associated with `toLookup`. This
8
+ * might be contained within the lookup map in the formContext. If no such value exists, return the `fallback`
9
+ * value.
10
+ *
11
+ * @param regOrFc - The @rjsf registry or form context in which the lookup will occur
12
+ * @param toLookup - The name of the field in the lookup map in the form context to get the value for
13
+ * @param [fallback] - The fallback value to use if the form context does not contain a value for `toLookup`
14
+ * @returns - The value associated with `toLookup` in the form context or `fallback`
15
+ */
16
+ export default function lookupFromFormContext<
17
+ T = any,
18
+ S extends StrictRJSFSchema = RJSFSchema,
19
+ F extends FormContextType = any,
20
+ >(regOrFc: Registry<T, S, F> | Registry<T, S, F>['formContext'], toLookup: string, fallback?: unknown) {
21
+ const lookupPath = [LOOKUP_MAP_NAME];
22
+ if (has(regOrFc, FORM_CONTEXT_NAME)) {
23
+ lookupPath.unshift(FORM_CONTEXT_NAME);
24
+ }
25
+ return get(regOrFc, [...lookupPath, toLookup], fallback);
26
+ }
@@ -2,6 +2,7 @@ import get from 'lodash/get';
2
2
 
3
3
  import isObject from './isObject';
4
4
  import { GenericObjectType } from '../src';
5
+ import isNil from 'lodash/isNil';
5
6
 
6
7
  /** Merges the `defaults` object of type `T` into the `formData` of type `T`
7
8
  *
@@ -12,42 +13,86 @@ import { GenericObjectType } from '../src';
12
13
  * are deeply merged; additional entries from the defaults are ignored unless `mergeExtraArrayDefaults` is true, in
13
14
  * which case the extras are appended onto the end of the form data
14
15
  * - when the array is not set in form data, the default is copied over
15
- * - scalars are overwritten/set by form data
16
+ * - scalars are overwritten/set by form data unless undefined and there is a default AND `defaultSupercedesUndefined`
17
+ * is true
16
18
  *
17
19
  * @param [defaults] - The defaults to merge
18
20
  * @param [formData] - The form data into which the defaults will be merged
19
21
  * @param [mergeExtraArrayDefaults=false] - If true, any additional default array entries are appended onto the formData
22
+ * @param [defaultSupercedesUndefined=false] - If true, an explicit undefined value will be overwritten by the default value
23
+ * @param [overrideFormDataWithDefaults=false] - If true, the default value will overwrite the form data value. If the value
24
+ * doesn't exist in the default, we take it from formData and in the case where the value is set to undefined in formData.
25
+ * This is useful when we have already merged formData with defaults and want to add an additional field from formData
26
+ * that does not exist in defaults.
20
27
  * @returns - The resulting merged form data with defaults
21
28
  */
22
29
  export default function mergeDefaultsWithFormData<T = any>(
23
30
  defaults?: T,
24
31
  formData?: T,
25
- mergeExtraArrayDefaults = false
32
+ mergeExtraArrayDefaults = false,
33
+ defaultSupercedesUndefined = false,
34
+ overrideFormDataWithDefaults = false,
26
35
  ): T | undefined {
27
36
  if (Array.isArray(formData)) {
28
37
  const defaultsArray = Array.isArray(defaults) ? defaults : [];
29
- const mapped = formData.map((value, idx) => {
30
- if (defaultsArray[idx]) {
31
- return mergeDefaultsWithFormData<any>(defaultsArray[idx], value, mergeExtraArrayDefaults);
38
+
39
+ // If overrideFormDataWithDefaults is true, we want to override the formData with the defaults
40
+ const overrideArray = overrideFormDataWithDefaults ? defaultsArray : formData;
41
+ const overrideOppositeArray = overrideFormDataWithDefaults ? formData : defaultsArray;
42
+
43
+ const mapped = overrideArray.map((value, idx) => {
44
+ // We want to explicitly make sure that the value is NOT undefined since null, 0 and empty space are valid values
45
+ if (overrideOppositeArray[idx] !== undefined) {
46
+ return mergeDefaultsWithFormData<any>(
47
+ defaultsArray[idx],
48
+ formData[idx],
49
+ mergeExtraArrayDefaults,
50
+ defaultSupercedesUndefined,
51
+ overrideFormDataWithDefaults,
52
+ );
32
53
  }
33
54
  return value;
34
55
  });
56
+
35
57
  // Merge any extra defaults when mergeExtraArrayDefaults is true
36
- if (mergeExtraArrayDefaults && mapped.length < defaultsArray.length) {
37
- mapped.push(...defaultsArray.slice(mapped.length));
58
+ // Or when overrideFormDataWithDefaults is true and the default array is shorter than the formData array
59
+ if ((mergeExtraArrayDefaults || overrideFormDataWithDefaults) && mapped.length < overrideOppositeArray.length) {
60
+ mapped.push(...overrideOppositeArray.slice(mapped.length));
38
61
  }
39
62
  return mapped as unknown as T;
40
63
  }
41
64
  if (isObject(formData)) {
42
65
  const acc: { [key in keyof T]: any } = Object.assign({}, defaults); // Prevent mutation of source object.
43
66
  return Object.keys(formData as GenericObjectType).reduce((acc, key) => {
67
+ const keyValue = get(formData, key);
68
+ const keyExistsInDefaults = isObject(defaults) && key in (defaults as GenericObjectType);
69
+ const keyExistsInFormData = key in (formData as GenericObjectType);
44
70
  acc[key as keyof T] = mergeDefaultsWithFormData<T>(
45
71
  defaults ? get(defaults, key) : {},
46
- get(formData, key),
47
- mergeExtraArrayDefaults
72
+ keyValue,
73
+ mergeExtraArrayDefaults,
74
+ defaultSupercedesUndefined,
75
+ // overrideFormDataWithDefaults can be true only when the key value exists in defaults
76
+ // Or if the key value doesn't exist in formData
77
+ overrideFormDataWithDefaults && (keyExistsInDefaults || !keyExistsInFormData),
48
78
  );
49
79
  return acc;
50
80
  }, acc);
51
81
  }
82
+
83
+ /**
84
+ * If the defaultSupercedesUndefined flag is true
85
+ * And formData is set to undefined or null and defaults are defined
86
+ * Or if formData is a number and is NaN return defaults
87
+ * Or if overrideFormDataWithDefaults flag is true and formData is set to not undefined/null return defaults
88
+ */
89
+ if (
90
+ (defaultSupercedesUndefined &&
91
+ ((!isNil(defaults) && isNil(formData)) || (typeof formData === 'number' && isNaN(formData)))) ||
92
+ (overrideFormDataWithDefaults && !isNil(formData))
93
+ ) {
94
+ return defaults;
95
+ }
96
+
52
97
  return formData;
53
98
  }
@@ -13,27 +13,30 @@ import { GenericObjectType } from './types';
13
13
  export default function mergeObjects(
14
14
  obj1: GenericObjectType,
15
15
  obj2: GenericObjectType,
16
- concatArrays: boolean | 'preventDuplicates' = false
16
+ concatArrays: boolean | 'preventDuplicates' = false,
17
17
  ) {
18
- return Object.keys(obj2).reduce((acc, key) => {
19
- const left = obj1 ? obj1[key] : {},
20
- right = obj2[key];
21
- if (obj1 && key in obj1 && isObject(right)) {
22
- acc[key] = mergeObjects(left, right, concatArrays);
23
- } else if (concatArrays && Array.isArray(left) && Array.isArray(right)) {
24
- let toMerge = right;
25
- if (concatArrays === 'preventDuplicates') {
26
- toMerge = right.reduce((result, value) => {
27
- if (!left.includes(value)) {
28
- result.push(value);
29
- }
30
- return result;
31
- }, []);
18
+ return Object.keys(obj2).reduce(
19
+ (acc, key) => {
20
+ const left = obj1 ? obj1[key] : {},
21
+ right = obj2[key];
22
+ if (obj1 && key in obj1 && isObject(right)) {
23
+ acc[key] = mergeObjects(left, right, concatArrays);
24
+ } else if (concatArrays && Array.isArray(left) && Array.isArray(right)) {
25
+ let toMerge = right;
26
+ if (concatArrays === 'preventDuplicates') {
27
+ toMerge = right.reduce((result, value) => {
28
+ if (!left.includes(value)) {
29
+ result.push(value);
30
+ }
31
+ return result;
32
+ }, []);
33
+ }
34
+ acc[key] = left.concat(toMerge);
35
+ } else {
36
+ acc[key] = right;
32
37
  }
33
- acc[key] = left.concat(toMerge);
34
- } else {
35
- acc[key] = right;
36
- }
37
- return acc;
38
- }, Object.assign({}, obj1)); // Prevent mutation of source object.
38
+ return acc;
39
+ },
40
+ Object.assign({}, obj1),
41
+ ); // Prevent mutation of source object.
39
42
  }