@rjsf/core 6.0.0-beta.9 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +2 -0
  2. package/dist/core.umd.js +2042 -1987
  3. package/dist/index.cjs +4909 -0
  4. package/dist/index.cjs.map +7 -0
  5. package/dist/index.esm.js +2509 -2389
  6. package/dist/index.esm.js.map +4 -4
  7. package/lib/components/Form.d.ts +137 -34
  8. package/lib/components/Form.d.ts.map +1 -1
  9. package/lib/components/Form.js +318 -173
  10. package/lib/components/fields/ArrayField.d.ts +2 -187
  11. package/lib/components/fields/ArrayField.d.ts.map +1 -1
  12. package/lib/components/fields/ArrayField.js +526 -492
  13. package/lib/components/fields/BooleanField.d.ts.map +1 -1
  14. package/lib/components/fields/BooleanField.js +8 -3
  15. package/lib/components/fields/FallbackField.d.ts +7 -0
  16. package/lib/components/fields/FallbackField.d.ts.map +1 -0
  17. package/lib/components/fields/FallbackField.js +72 -0
  18. package/lib/components/fields/LayoutGridField.d.ts +109 -186
  19. package/lib/components/fields/LayoutGridField.d.ts.map +1 -1
  20. package/lib/components/fields/LayoutGridField.js +426 -426
  21. package/lib/components/fields/LayoutHeaderField.d.ts +1 -1
  22. package/lib/components/fields/LayoutHeaderField.js +3 -3
  23. package/lib/components/fields/LayoutMultiSchemaField.d.ts.map +1 -1
  24. package/lib/components/fields/LayoutMultiSchemaField.js +6 -6
  25. package/lib/components/fields/MultiSchemaField.d.ts.map +1 -1
  26. package/lib/components/fields/MultiSchemaField.js +16 -10
  27. package/lib/components/fields/NullField.js +3 -3
  28. package/lib/components/fields/NumberField.d.ts.map +1 -1
  29. package/lib/components/fields/NumberField.js +3 -3
  30. package/lib/components/fields/ObjectField.d.ts +2 -68
  31. package/lib/components/fields/ObjectField.d.ts.map +1 -1
  32. package/lib/components/fields/ObjectField.js +163 -163
  33. package/lib/components/fields/OptionalDataControlsField.d.ts +8 -0
  34. package/lib/components/fields/OptionalDataControlsField.d.ts.map +1 -0
  35. package/lib/components/fields/OptionalDataControlsField.js +43 -0
  36. package/lib/components/fields/SchemaField.d.ts.map +1 -1
  37. package/lib/components/fields/SchemaField.js +52 -30
  38. package/lib/components/fields/StringField.d.ts.map +1 -1
  39. package/lib/components/fields/StringField.js +8 -3
  40. package/lib/components/fields/index.d.ts.map +1 -1
  41. package/lib/components/fields/index.js +4 -0
  42. package/lib/components/templates/ArrayFieldDescriptionTemplate.d.ts +1 -1
  43. package/lib/components/templates/ArrayFieldDescriptionTemplate.js +3 -3
  44. package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts +3 -3
  45. package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts.map +1 -1
  46. package/lib/components/templates/ArrayFieldItemButtonsTemplate.js +3 -8
  47. package/lib/components/templates/ArrayFieldItemTemplate.d.ts +3 -3
  48. package/lib/components/templates/ArrayFieldItemTemplate.d.ts.map +1 -1
  49. package/lib/components/templates/ArrayFieldItemTemplate.js +1 -1
  50. package/lib/components/templates/ArrayFieldTemplate.d.ts +1 -1
  51. package/lib/components/templates/ArrayFieldTemplate.d.ts.map +1 -1
  52. package/lib/components/templates/ArrayFieldTemplate.js +4 -5
  53. package/lib/components/templates/ArrayFieldTitleTemplate.d.ts +1 -1
  54. package/lib/components/templates/ArrayFieldTitleTemplate.d.ts.map +1 -1
  55. package/lib/components/templates/ArrayFieldTitleTemplate.js +3 -3
  56. package/lib/components/templates/BaseInputTemplate.js +2 -2
  57. package/lib/components/templates/ButtonTemplates/AddButton.d.ts +1 -1
  58. package/lib/components/templates/ButtonTemplates/AddButton.d.ts.map +1 -1
  59. package/lib/components/templates/ButtonTemplates/AddButton.js +2 -2
  60. package/lib/components/templates/FallbackFieldTemplate.d.ts +7 -0
  61. package/lib/components/templates/FallbackFieldTemplate.d.ts.map +1 -0
  62. package/lib/components/templates/FallbackFieldTemplate.js +12 -0
  63. package/lib/components/templates/FieldErrorTemplate.js +2 -2
  64. package/lib/components/templates/FieldHelpTemplate.js +2 -2
  65. package/lib/components/templates/MultiSchemaFieldTemplate.d.ts +8 -0
  66. package/lib/components/templates/MultiSchemaFieldTemplate.d.ts.map +1 -0
  67. package/lib/components/templates/MultiSchemaFieldTemplate.js +10 -0
  68. package/lib/components/templates/ObjectFieldTemplate.d.ts.map +1 -1
  69. package/lib/components/templates/ObjectFieldTemplate.js +3 -2
  70. package/lib/components/templates/OptionalDataControlsTemplate.d.ts +11 -0
  71. package/lib/components/templates/OptionalDataControlsTemplate.d.ts.map +1 -0
  72. package/lib/components/templates/OptionalDataControlsTemplate.js +20 -0
  73. package/lib/components/templates/TitleField.d.ts.map +1 -1
  74. package/lib/components/templates/TitleField.js +2 -2
  75. package/lib/components/templates/UnsupportedField.js +3 -3
  76. package/lib/components/templates/WrapIfAdditionalTemplate.js +2 -2
  77. package/lib/components/templates/index.d.ts.map +1 -1
  78. package/lib/components/templates/index.js +6 -0
  79. package/lib/components/widgets/AltDateWidget.d.ts +1 -1
  80. package/lib/components/widgets/AltDateWidget.d.ts.map +1 -1
  81. package/lib/components/widgets/AltDateWidget.js +5 -46
  82. package/lib/components/widgets/CheckboxWidget.d.ts +1 -1
  83. package/lib/components/widgets/CheckboxWidget.d.ts.map +1 -1
  84. package/lib/components/widgets/CheckboxWidget.js +2 -2
  85. package/lib/components/widgets/CheckboxesWidget.d.ts +1 -1
  86. package/lib/components/widgets/CheckboxesWidget.d.ts.map +1 -1
  87. package/lib/components/widgets/CheckboxesWidget.js +4 -4
  88. package/lib/components/widgets/FileWidget.d.ts.map +1 -1
  89. package/lib/components/widgets/FileWidget.js +7 -87
  90. package/lib/components/widgets/HiddenWidget.d.ts +1 -1
  91. package/lib/components/widgets/HiddenWidget.d.ts.map +1 -1
  92. package/lib/components/widgets/HiddenWidget.js +2 -2
  93. package/lib/components/widgets/RadioWidget.d.ts +1 -1
  94. package/lib/components/widgets/RadioWidget.d.ts.map +1 -1
  95. package/lib/components/widgets/RadioWidget.js +2 -2
  96. package/lib/components/widgets/RatingWidget.d.ts +1 -1
  97. package/lib/components/widgets/RatingWidget.d.ts.map +1 -1
  98. package/lib/components/widgets/RatingWidget.js +2 -2
  99. package/lib/components/widgets/SelectWidget.d.ts +1 -1
  100. package/lib/components/widgets/SelectWidget.d.ts.map +1 -1
  101. package/lib/components/widgets/SelectWidget.js +2 -2
  102. package/lib/components/widgets/TextareaWidget.d.ts +1 -1
  103. package/lib/components/widgets/TextareaWidget.d.ts.map +1 -1
  104. package/lib/components/widgets/TextareaWidget.js +2 -2
  105. package/lib/getDefaultRegistry.d.ts.map +1 -1
  106. package/lib/getDefaultRegistry.js +6 -1
  107. package/lib/getTestRegistry.d.ts +5 -0
  108. package/lib/getTestRegistry.d.ts.map +1 -0
  109. package/lib/getTestRegistry.js +23 -0
  110. package/lib/index.d.ts +2 -1
  111. package/lib/index.d.ts.map +1 -1
  112. package/lib/index.js +2 -1
  113. package/lib/tsconfig.tsbuildinfo +1 -1
  114. package/package.json +35 -20
  115. package/src/components/Form.tsx +468 -206
  116. package/src/components/fields/ArrayField.tsx +871 -723
  117. package/src/components/fields/BooleanField.tsx +14 -5
  118. package/src/components/fields/FallbackField.tsx +157 -0
  119. package/src/components/fields/LayoutGridField.tsx +626 -603
  120. package/src/components/fields/LayoutHeaderField.tsx +3 -3
  121. package/src/components/fields/LayoutMultiSchemaField.tsx +9 -10
  122. package/src/components/fields/MultiSchemaField.tsx +57 -36
  123. package/src/components/fields/NullField.tsx +3 -3
  124. package/src/components/fields/NumberField.tsx +11 -3
  125. package/src/components/fields/ObjectField.tsx +308 -239
  126. package/src/components/fields/OptionalDataControlsField.tsx +84 -0
  127. package/src/components/fields/SchemaField.tsx +75 -94
  128. package/src/components/fields/StringField.tsx +14 -5
  129. package/src/components/fields/index.ts +4 -0
  130. package/src/components/templates/ArrayFieldDescriptionTemplate.tsx +3 -3
  131. package/src/components/templates/ArrayFieldItemButtonsTemplate.tsx +16 -21
  132. package/src/components/templates/ArrayFieldItemTemplate.tsx +3 -3
  133. package/src/components/templates/ArrayFieldTemplate.tsx +11 -18
  134. package/src/components/templates/ArrayFieldTitleTemplate.tsx +4 -3
  135. package/src/components/templates/BaseInputTemplate.tsx +5 -5
  136. package/src/components/templates/ButtonTemplates/AddButton.tsx +2 -0
  137. package/src/components/templates/FallbackFieldTemplate.tsx +28 -0
  138. package/src/components/templates/FieldErrorTemplate.tsx +2 -2
  139. package/src/components/templates/FieldHelpTemplate.tsx +2 -2
  140. package/src/components/templates/MultiSchemaFieldTemplate.tsx +20 -0
  141. package/src/components/templates/ObjectFieldTemplate.tsx +12 -7
  142. package/src/components/templates/OptionalDataControlsTemplate.tsx +43 -0
  143. package/src/components/templates/TitleField.tsx +6 -1
  144. package/src/components/templates/UnsupportedField.tsx +3 -3
  145. package/src/components/templates/WrapIfAdditionalTemplate.tsx +5 -5
  146. package/src/components/templates/index.ts +6 -0
  147. package/src/components/widgets/AltDateWidget.tsx +8 -126
  148. package/src/components/widgets/CheckboxWidget.tsx +4 -3
  149. package/src/components/widgets/CheckboxesWidget.tsx +5 -4
  150. package/src/components/widgets/FileWidget.tsx +11 -102
  151. package/src/components/widgets/HiddenWidget.tsx +2 -1
  152. package/src/components/widgets/RadioWidget.tsx +3 -2
  153. package/src/components/widgets/RatingWidget.tsx +2 -1
  154. package/src/components/widgets/SelectWidget.tsx +3 -2
  155. package/src/components/widgets/TextareaWidget.tsx +3 -2
  156. package/src/getDefaultRegistry.ts +14 -1
  157. package/src/getTestRegistry.tsx +38 -0
  158. package/src/index.ts +2 -1
  159. package/dist/index.js +0 -4834
  160. package/dist/index.js.map +0 -7
@@ -1,19 +1,36 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Component, createRef } from 'react';
3
- import { createSchemaUtils, deepEquals, getChangedFields, getTemplate, getUiOptions, isObject, mergeObjects, NAME_KEY, RJSF_ADDITIONAL_PROPERTIES_FLAG, shouldRender, SUBMIT_BTN_OPTIONS_KEY, toErrorList, UI_GLOBAL_OPTIONS_KEY, UI_OPTIONS_KEY, validationDataMerge, createErrorHandler, unwrapErrorHandler, } from '@rjsf/utils';
4
- import _forEach from 'lodash-es/forEach.js';
3
+ import { createSchemaUtils, deepEquals, ErrorSchemaBuilder, getChangedFields, getTemplate, getUiOptions, isObject, mergeObjects, NAME_KEY, RJSF_ADDITIONAL_PROPERTIES_FLAG, shouldRender, SUBMIT_BTN_OPTIONS_KEY, toErrorList, toFieldPathId, UI_GLOBAL_OPTIONS_KEY, UI_OPTIONS_KEY, validationDataMerge, DEFAULT_ID_SEPARATOR, DEFAULT_ID_PREFIX, ERRORS_KEY, ID_KEY, } from '@rjsf/utils';
4
+ import _cloneDeep from 'lodash-es/cloneDeep.js';
5
5
  import _get from 'lodash-es/get.js';
6
6
  import _isEmpty from 'lodash-es/isEmpty.js';
7
- import _isNil from 'lodash-es/isNil.js';
8
7
  import _pick from 'lodash-es/pick.js';
8
+ import _set from 'lodash-es/set.js';
9
9
  import _toPath from 'lodash-es/toPath.js';
10
10
  import getDefaultRegistry from '../getDefaultRegistry.js';
11
+ /** Internal only symbol used by the `reset()` function to indicate that a reset operation is happening */
12
+ const IS_RESET = Symbol('reset');
13
+ /** Converts the full `FormState` into the `IChangeEvent` version by picking out the public values
14
+ *
15
+ * @param state - The state of the form
16
+ * @param status - The status provided by the onSubmit
17
+ * @returns - The `IChangeEvent` for the state
18
+ */
19
+ function toIChangeEvent(state, status) {
20
+ return {
21
+ ..._pick(state, ['schema', 'uiSchema', 'fieldPathId', 'schemaUtils', 'formData', 'edit', 'errors', 'errorSchema']),
22
+ ...(status !== undefined && { status }),
23
+ };
24
+ }
11
25
  /** The `Form` component renders the outer form and all the fields defined in the `schema` */
12
26
  export default class Form extends Component {
13
27
  /** The ref used to hold the `form` element, this needs to be `any` because `tagName` or `_internalFormWrapper` can
14
28
  * provide any possible type here
15
29
  */
16
30
  formElement;
31
+ /** The list of pending changes
32
+ */
33
+ pendingChanges = [];
17
34
  /** Constructs the `Form` from the `props`. Will setup the initial state from the props. It will also call the
18
35
  * `onChange` handler if the initially provided `formData` is modified to add missing default values as part of the
19
36
  * state construction.
@@ -25,9 +42,11 @@ export default class Form extends Component {
25
42
  if (!props.validator) {
26
43
  throw new Error('A validator is required for Form functionality to work');
27
44
  }
28
- this.state = this.getStateFromProps(props, props.formData);
29
- if (this.props.onChange && !deepEquals(this.state.formData, this.props.formData)) {
30
- this.props.onChange(this.state);
45
+ const { formData: propsFormData, initialFormData, onChange } = props;
46
+ const formData = propsFormData ?? initialFormData;
47
+ this.state = this.getStateFromProps(props, formData, undefined, undefined, undefined, true);
48
+ if (onChange && !deepEquals(this.state.formData, formData)) {
49
+ onChange(toIChangeEvent(this.state));
31
50
  }
32
51
  this.formElement = createRef();
33
52
  }
@@ -51,16 +70,23 @@ export default class Form extends Component {
51
70
  */
52
71
  getSnapshotBeforeUpdate(prevProps, prevState) {
53
72
  if (!deepEquals(this.props, prevProps)) {
73
+ // Compare the previous props formData against the current props formData
54
74
  const formDataChangedFields = getChangedFields(this.props.formData, prevProps.formData);
75
+ // Compare the current props formData against the current state's formData to determine if the new props were the
76
+ // result of the onChange from the existing state formData
77
+ const stateDataChangedFields = getChangedFields(this.props.formData, this.state.formData);
55
78
  const isSchemaChanged = !deepEquals(prevProps.schema, this.props.schema);
56
79
  // When formData is not an object, getChangedFields returns an empty array.
57
80
  // In this case, deepEquals is most needed to check again.
58
81
  const isFormDataChanged = formDataChangedFields.length > 0 || !deepEquals(prevProps.formData, this.props.formData);
82
+ const isStateDataChanged = stateDataChangedFields.length > 0 || !deepEquals(this.state.formData, this.props.formData);
59
83
  const nextState = this.getStateFromProps(this.props, this.props.formData,
60
84
  // If the `schema` has changed, we need to update the retrieved schema.
61
85
  // Or if the `formData` changes, for example in the case of a schema with dependencies that need to
62
86
  // match one of the subSchemas, the retrieved schema must be updated.
63
- isSchemaChanged || isFormDataChanged ? undefined : this.state.retrievedSchema, isSchemaChanged, formDataChangedFields);
87
+ isSchemaChanged || isFormDataChanged ? undefined : this.state.retrievedSchema, isSchemaChanged, formDataChangedFields,
88
+ // Skip live validation for this request if no form data has changed from the last state
89
+ !isStateDataChanged);
64
90
  const shouldUpdate = !deepEquals(nextState, prevState);
65
91
  return { nextState, shouldUpdate };
66
92
  }
@@ -84,7 +110,7 @@ export default class Form extends Component {
84
110
  if (!deepEquals(nextState.formData, this.props.formData) &&
85
111
  !deepEquals(nextState.formData, prevState.formData) &&
86
112
  this.props.onChange) {
87
- this.props.onChange(nextState);
113
+ this.props.onChange(toIChangeEvent(nextState));
88
114
  }
89
115
  this.setState(nextState);
90
116
  }
@@ -98,16 +124,18 @@ export default class Form extends Component {
98
124
  * @param retrievedSchema - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`.
99
125
  * @param isSchemaChanged - A flag indicating whether the schema has changed.
100
126
  * @param formDataChangedFields - The changed fields of `formData`
127
+ * @param skipLiveValidate - Optional flag, if true, means that we are not running live validation
101
128
  * @returns - The new state for the `Form`
102
129
  */
103
- getStateFromProps(props, inputFormData, retrievedSchema, isSchemaChanged = false, formDataChangedFields = []) {
130
+ getStateFromProps(props, inputFormData, retrievedSchema, isSchemaChanged = false, formDataChangedFields = [], skipLiveValidate = false) {
104
131
  const state = this.state || {};
105
132
  const schema = 'schema' in props ? props.schema : this.props.schema;
133
+ const validator = 'validator' in props ? props.validator : this.props.validator;
106
134
  const uiSchema = ('uiSchema' in props ? props.uiSchema : this.props.uiSchema) || {};
135
+ const isUncontrolled = props.formData === undefined && this.props.formData === undefined;
107
136
  const edit = typeof inputFormData !== 'undefined';
108
137
  const liveValidate = 'liveValidate' in props ? props.liveValidate : this.props.liveValidate;
109
138
  const mustValidate = edit && !props.noValidate && liveValidate;
110
- const rootSchema = schema;
111
139
  const experimental_defaultFormStateBehavior = 'experimental_defaultFormStateBehavior' in props
112
140
  ? props.experimental_defaultFormStateBehavior
113
141
  : this.props.experimental_defaultFormStateBehavior;
@@ -116,11 +144,20 @@ export default class Form extends Component {
116
144
  : this.props.experimental_customMergeAllOf;
117
145
  let schemaUtils = state.schemaUtils;
118
146
  if (!schemaUtils ||
119
- schemaUtils.doesSchemaUtilsDiffer(props.validator, rootSchema, experimental_defaultFormStateBehavior, experimental_customMergeAllOf)) {
120
- schemaUtils = createSchemaUtils(props.validator, rootSchema, experimental_defaultFormStateBehavior, experimental_customMergeAllOf);
147
+ schemaUtils.doesSchemaUtilsDiffer(validator, schema, experimental_defaultFormStateBehavior, experimental_customMergeAllOf)) {
148
+ schemaUtils = createSchemaUtils(validator, schema, experimental_defaultFormStateBehavior, experimental_customMergeAllOf);
121
149
  }
122
- const formData = schemaUtils.getDefaultFormState(schema, inputFormData);
123
- const _retrievedSchema = this.updateRetrievedSchema(retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData));
150
+ const rootSchema = schemaUtils.getRootSchema();
151
+ // Compute the formData for getDefaultFormState() function based on the inputFormData, isUncontrolled and state
152
+ let defaultsFormData = inputFormData;
153
+ if (inputFormData === IS_RESET) {
154
+ defaultsFormData = undefined;
155
+ }
156
+ else if (inputFormData === undefined && isUncontrolled) {
157
+ defaultsFormData = state.formData;
158
+ }
159
+ const formData = schemaUtils.getDefaultFormState(rootSchema, defaultsFormData, false, state.initialDefaultsGenerated);
160
+ const _retrievedSchema = this.updateRetrievedSchema(retrievedSchema ?? schemaUtils.retrieveSchema(rootSchema, formData));
124
161
  const getCurrentErrors = () => {
125
162
  // If the `props.noValidate` option is set or the schema has changed, we reset the error state.
126
163
  if (props.noValidate || isSchemaChanged) {
@@ -141,43 +178,45 @@ export default class Form extends Component {
141
178
  let errorSchema;
142
179
  let schemaValidationErrors = state.schemaValidationErrors;
143
180
  let schemaValidationErrorSchema = state.schemaValidationErrorSchema;
144
- if (mustValidate) {
145
- const schemaValidation = this.validate(formData, schema, schemaUtils, _retrievedSchema);
146
- errors = schemaValidation.errors;
181
+ // If we are skipping live validate, it means that the state has already been updated with live validation errors
182
+ if (mustValidate && !skipLiveValidate) {
183
+ const liveValidation = this.liveValidate(rootSchema, schemaUtils, state.errorSchema, formData, undefined, state.customErrors, retrievedSchema,
147
184
  // If retrievedSchema is undefined which means the schema or formData has changed, we do not merge state.
148
- // Else in the case where it hasn't changed, we merge 'state.errorSchema' with 'schemaValidation.errorSchema.' This done to display the raised field error.
149
- if (retrievedSchema === undefined) {
150
- errorSchema = schemaValidation.errorSchema;
151
- }
152
- else {
153
- errorSchema = mergeObjects(this.state?.errorSchema, schemaValidation.errorSchema, 'preventDuplicates');
154
- }
155
- schemaValidationErrors = errors;
156
- schemaValidationErrorSchema = errorSchema;
185
+ // Else in the case where it hasn't changed,
186
+ retrievedSchema !== undefined);
187
+ errors = liveValidation.errors;
188
+ errorSchema = liveValidation.errorSchema;
189
+ schemaValidationErrors = liveValidation.schemaValidationErrors;
190
+ schemaValidationErrorSchema = liveValidation.schemaValidationErrorSchema;
157
191
  }
158
192
  else {
159
193
  const currentErrors = getCurrentErrors();
160
194
  errors = currentErrors.errors;
161
195
  errorSchema = currentErrors.errorSchema;
162
- if (formDataChangedFields.length > 0) {
196
+ // We only update the error schema for changed fields if mustValidate is false
197
+ if (formDataChangedFields.length > 0 && !mustValidate) {
163
198
  const newErrorSchema = formDataChangedFields.reduce((acc, key) => {
164
199
  acc[key] = undefined;
165
200
  return acc;
166
201
  }, {});
167
202
  errorSchema = schemaValidationErrorSchema = mergeObjects(currentErrors.errorSchema, newErrorSchema, 'preventDuplicates');
168
203
  }
169
- }
170
- if (props.extraErrors) {
171
- const merged = validationDataMerge({ errorSchema, errors }, props.extraErrors);
172
- errorSchema = merged.errorSchema;
173
- errors = merged.errors;
174
- }
175
- const idSchema = schemaUtils.toIdSchema(_retrievedSchema, uiSchema['ui:rootFieldId'], formData, props.idPrefix, props.idSeparator);
204
+ const mergedErrors = this.mergeErrors({ errorSchema, errors }, props.extraErrors, state.customErrors);
205
+ errors = mergedErrors.errors;
206
+ errorSchema = mergedErrors.errorSchema;
207
+ }
208
+ // Only store a new registry when the props cause a different one to be created
209
+ const newRegistry = this.getRegistry(props, rootSchema, schemaUtils);
210
+ const registry = deepEquals(state.registry, newRegistry) ? state.registry : newRegistry;
211
+ // Only compute a new `fieldPathId` when the `idPrefix` is different than the existing fieldPathId's ID_KEY
212
+ const fieldPathId = state.fieldPathId && state.fieldPathId?.[ID_KEY] === registry.globalFormOptions.idPrefix
213
+ ? state.fieldPathId
214
+ : toFieldPathId('', registry.globalFormOptions);
176
215
  const nextState = {
177
216
  schemaUtils,
178
- schema,
217
+ schema: rootSchema,
179
218
  uiSchema,
180
- idSchema,
219
+ fieldPathId,
181
220
  formData,
182
221
  edit,
183
222
  errors,
@@ -185,6 +224,8 @@ export default class Form extends Component {
185
224
  schemaValidationErrors,
186
225
  schemaValidationErrorSchema,
187
226
  retrievedSchema: _retrievedSchema,
227
+ initialDefaultsGenerated: true,
228
+ registry,
188
229
  };
189
230
  return nextState;
190
231
  }
@@ -195,31 +236,18 @@ export default class Form extends Component {
195
236
  * @returns - True if the component should be updated, false otherwise
196
237
  */
197
238
  shouldComponentUpdate(nextProps, nextState) {
198
- return shouldRender(this, nextProps, nextState);
199
- }
200
- /** Gets the previously raised customValidate errors.
201
- *
202
- * @returns the previous customValidate errors
203
- */
204
- getPreviousCustomValidateErrors() {
205
- const { customValidate, uiSchema } = this.props;
206
- const prevFormData = this.state.formData;
207
- let customValidateErrors = {};
208
- if (typeof customValidate === 'function') {
209
- const errorHandler = customValidate(prevFormData, createErrorHandler(prevFormData), uiSchema);
210
- const userErrorSchema = unwrapErrorHandler(errorHandler);
211
- customValidateErrors = userErrorSchema;
212
- }
213
- return customValidateErrors;
239
+ const { experimental_componentUpdateStrategy = 'customDeep' } = this.props;
240
+ return shouldRender(this, nextProps, nextState, experimental_componentUpdateStrategy);
214
241
  }
215
242
  /** Validates the `formData` against the `schema` using the `altSchemaUtils` (if provided otherwise it uses the
216
243
  * `schemaUtils` in the state), returning the results.
217
244
  *
218
245
  * @param formData - The new form data to validate
219
246
  * @param schema - The schema used to validate against
220
- * @param altSchemaUtils - The alternate schemaUtils to use for validation
247
+ * @param [altSchemaUtils] - The alternate schemaUtils to use for validation
248
+ * @param [retrievedSchema] - An optionally retrieved schema for per
221
249
  */
222
- validate(formData, schema = this.props.schema, altSchemaUtils, retrievedSchema) {
250
+ validate(formData, schema = this.state.schema, altSchemaUtils, retrievedSchema) {
223
251
  const schemaUtils = altSchemaUtils ? altSchemaUtils : this.state.schemaUtils;
224
252
  const { customValidate, transformErrors, uiSchema } = this.props;
225
253
  const resolvedSchema = retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData);
@@ -230,14 +258,63 @@ export default class Form extends Component {
230
258
  /** Renders any errors contained in the `state` in using the `ErrorList`, if not disabled by `showErrorList`. */
231
259
  renderErrors(registry) {
232
260
  const { errors, errorSchema, schema, uiSchema } = this.state;
233
- const { formContext } = this.props;
234
261
  const options = getUiOptions(uiSchema);
235
262
  const ErrorListTemplate = getTemplate('ErrorListTemplate', registry, options);
236
263
  if (errors && errors.length) {
237
- return (_jsx(ErrorListTemplate, { errors: errors, errorSchema: errorSchema || {}, schema: schema, uiSchema: uiSchema, formContext: formContext, registry: registry }));
264
+ return (_jsx(ErrorListTemplate, { errors: errors, errorSchema: errorSchema || {}, schema: schema, uiSchema: uiSchema, registry: registry }));
238
265
  }
239
266
  return null;
240
267
  }
268
+ /** Merges any `extraErrors` or `customErrors` into the given `schemaValidation` object, returning the result
269
+ *
270
+ * @param schemaValidation - The `ValidationData` object into which additional errors are merged
271
+ * @param [extraErrors] - The extra errors from the props
272
+ * @param [customErrors] - The customErrors from custom components
273
+ * @return - The `extraErrors` and `customErrors` merged into the `schemaValidation`
274
+ * @private
275
+ */
276
+ mergeErrors(schemaValidation, extraErrors, customErrors) {
277
+ let errorSchema = schemaValidation.errorSchema;
278
+ let errors = schemaValidation.errors;
279
+ if (extraErrors) {
280
+ const merged = validationDataMerge(schemaValidation, extraErrors);
281
+ errorSchema = merged.errorSchema;
282
+ errors = merged.errors;
283
+ }
284
+ if (customErrors) {
285
+ const merged = validationDataMerge(schemaValidation, customErrors.ErrorSchema, true);
286
+ errorSchema = merged.errorSchema;
287
+ errors = merged.errors;
288
+ }
289
+ return { errors, errorSchema };
290
+ }
291
+ /** Performs live validation and then updates and returns the errors and error schemas by potentially merging in
292
+ * `extraErrors` and `customErrors`.
293
+ *
294
+ * @param rootSchema - The `rootSchema` from the state
295
+ * @param schemaUtils - The `SchemaUtilsType` from the state
296
+ * @param originalErrorSchema - The original `ErrorSchema` from the state
297
+ * @param [formData] - The new form data to validate
298
+ * @param [extraErrors] - The extra errors from the props
299
+ * @param [customErrors] - The customErrors from custom components
300
+ * @param [retrievedSchema] - An expanded schema, if not provided, it will be retrieved from the `schema` and `formData`
301
+ * @param [mergeIntoOriginalErrorSchema=false] - Optional flag indicating whether we merge into original schema
302
+ * @returns - An object containing `errorSchema`, `errors`, `schemaValidationErrors` and `schemaValidationErrorSchema`
303
+ * @private
304
+ */
305
+ liveValidate(rootSchema, schemaUtils, originalErrorSchema, formData, extraErrors, customErrors, retrievedSchema, mergeIntoOriginalErrorSchema = false) {
306
+ const schemaValidation = this.validate(formData, rootSchema, schemaUtils, retrievedSchema);
307
+ const errors = schemaValidation.errors;
308
+ let errorSchema = schemaValidation.errorSchema;
309
+ // We merge 'originalErrorSchema' with 'schemaValidation.errorSchema.'; This done to display the raised field error.
310
+ if (mergeIntoOriginalErrorSchema) {
311
+ errorSchema = mergeObjects(originalErrorSchema, schemaValidation.errorSchema, 'preventDuplicates');
312
+ }
313
+ const schemaValidationErrors = errors;
314
+ const schemaValidationErrorSchema = errorSchema;
315
+ const mergedErrors = this.mergeErrors({ errorSchema, errors }, extraErrors, customErrors);
316
+ return { ...mergedErrors, schemaValidationErrors, schemaValidationErrorSchema };
317
+ }
241
318
  /** Returns the `formData` with only the elements specified in the `fields` list
242
319
  *
243
320
  * @param formData - The data for the `Form`
@@ -261,26 +338,28 @@ export default class Form extends Component {
261
338
  * @param [formData] - The form data to use while checking for empty objects/arrays
262
339
  */
263
340
  getFieldNames = (pathSchema, formData) => {
341
+ const formValueHasData = (value, isLeaf) => typeof value !== 'object' || _isEmpty(value) || (isLeaf && !_isEmpty(value));
264
342
  const getAllPaths = (_obj, acc = [], paths = [[]]) => {
265
- Object.keys(_obj).forEach((key) => {
266
- if (typeof _obj[key] === 'object') {
343
+ const objKeys = Object.keys(_obj);
344
+ objKeys.forEach((key) => {
345
+ const data = _obj[key];
346
+ if (typeof data === 'object') {
267
347
  const newPaths = paths.map((path) => [...path, key]);
268
348
  // If an object is marked with additionalProperties, all its keys are valid
269
- if (_obj[key][RJSF_ADDITIONAL_PROPERTIES_FLAG] && _obj[key][NAME_KEY] !== '') {
270
- acc.push(_obj[key][NAME_KEY]);
349
+ if (data[RJSF_ADDITIONAL_PROPERTIES_FLAG] && data[NAME_KEY] !== '') {
350
+ acc.push(data[NAME_KEY]);
271
351
  }
272
352
  else {
273
- getAllPaths(_obj[key], acc, newPaths);
353
+ getAllPaths(data, acc, newPaths);
274
354
  }
275
355
  }
276
- else if (key === NAME_KEY && _obj[key] !== '') {
356
+ else if (key === NAME_KEY && data !== '') {
277
357
  paths.forEach((path) => {
278
358
  const formValue = _get(formData, path);
279
- // adds path to fieldNames if it points to a value
280
- // or an empty object/array
281
- if (typeof formValue !== 'object' ||
282
- _isEmpty(formValue) ||
283
- (Array.isArray(formValue) && formValue.every((val) => typeof val !== 'object'))) {
359
+ const isLeaf = objKeys.length === 1;
360
+ // adds path to fieldNames if it points to a value or an empty object/array which is not a leaf
361
+ if (formValueHasData(formValue, isLeaf) ||
362
+ (Array.isArray(formValue) && formValue.every((val) => formValueHasData(val, isLeaf)))) {
284
363
  acc.push(path);
285
364
  }
286
365
  });
@@ -300,116 +379,133 @@ export default class Form extends Component {
300
379
  const retrievedSchema = schemaUtils.retrieveSchema(schema, formData);
301
380
  const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData);
302
381
  const fieldNames = this.getFieldNames(pathSchema, formData);
303
- const newFormData = this.getUsedFormData(formData, fieldNames);
304
- return newFormData;
382
+ return this.getUsedFormData(formData, fieldNames);
305
383
  };
306
- // Filtering errors based on your retrieved schema to only show errors for properties in the selected branch.
307
- filterErrorsBasedOnSchema(schemaErrors, resolvedSchema, formData) {
308
- const { retrievedSchema, schemaUtils } = this.state;
309
- const _retrievedSchema = resolvedSchema ?? retrievedSchema;
310
- const pathSchema = schemaUtils.toPathSchema(_retrievedSchema, '', formData);
311
- const fieldNames = this.getFieldNames(pathSchema, formData);
312
- const filteredErrors = _pick(schemaErrors, fieldNames);
313
- // If the root schema is of a primitive type, do not filter out the __errors
314
- if (resolvedSchema?.type !== 'object' && resolvedSchema?.type !== 'array') {
315
- filteredErrors.__errors = schemaErrors.__errors;
316
- }
317
- const prevCustomValidateErrors = this.getPreviousCustomValidateErrors();
318
- // Filtering out the previous raised customValidate errors so that they are cleared when no longer valid.
319
- const filterPreviousCustomErrors = (errors = [], prevCustomErrors) => {
320
- if (errors.length === 0) {
321
- return errors;
322
- }
323
- return errors.filter((error) => {
324
- return !prevCustomErrors.includes(error);
325
- });
326
- };
327
- // Removing undefined, null and empty errors.
328
- const filterNilOrEmptyErrors = (errors, previousCustomValidateErrors = {}) => {
329
- _forEach(errors, (errorAtKey, errorKey) => {
330
- const prevCustomValidateErrorAtKey = previousCustomValidateErrors[errorKey];
331
- if (_isNil(errorAtKey) || (Array.isArray(errorAtKey) && errorAtKey.length === 0)) {
332
- delete errors[errorKey];
333
- }
334
- else if (isObject(errorAtKey) &&
335
- isObject(prevCustomValidateErrorAtKey) &&
336
- Array.isArray(prevCustomValidateErrorAtKey?.__errors)) {
337
- // if previous customValidate error is an object and has __errors array, filter out the errors previous customValidate errors.
338
- errors[errorKey] = filterPreviousCustomErrors(errorAtKey.__errors, prevCustomValidateErrorAtKey.__errors);
339
- }
340
- else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey.__errors)) {
341
- filterNilOrEmptyErrors(errorAtKey, previousCustomValidateErrors[errorKey]);
342
- }
343
- });
344
- return errors;
345
- };
346
- return filterNilOrEmptyErrors(filteredErrors, prevCustomValidateErrors);
347
- }
348
- /** Function to handle changes made to a field in the `Form`. This handler receives an entirely new copy of the
349
- * `formData` along with a new `ErrorSchema`. It will first update the `formData` with any missing default fields and
350
- * then, if `omitExtraData` and `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not
351
- * in a form field. Then, the resulting formData will be validated if required. The state will be updated with the new
352
- * updated (potentially filtered) `formData`, any errors that resulted from validation. Finally the `onChange`
353
- * callback will be called if specified with the updated state.
384
+ /** Allows a user to set a value for the provided `fieldPath`, which must be either a dotted path to the field OR a
385
+ * `FieldPathList`. To set the root element, used either `''` or `[]` for the path. Passing undefined will clear the
386
+ * value in the field.
387
+ *
388
+ * @param fieldPath - Either a dotted path to the field or the `FieldPathList` to the field
389
+ * @param [newValue] - The new value for the field
390
+ */
391
+ setFieldValue = (fieldPath, newValue) => {
392
+ const { registry } = this.state;
393
+ const path = Array.isArray(fieldPath) ? fieldPath : fieldPath.split('.');
394
+ const fieldPathId = toFieldPathId('', registry.globalFormOptions, path);
395
+ this.onChange(newValue, path, undefined, fieldPathId[ID_KEY]);
396
+ };
397
+ /** Pushes the given change information into the `pendingChanges` array and then calls `processPendingChanges()` if
398
+ * the array only contains a single pending change.
354
399
  *
355
- * @param formData - The new form data from a change to a field
356
- * @param newErrorSchema - The new `ErrorSchema` based on the field change
357
- * @param id - The id of the field that caused the change
400
+ * @param newValue - The new form data from a change to a field
401
+ * @param path - The path to the change into which to set the formData
402
+ * @param [newErrorSchema] - The new `ErrorSchema` based on the field change
403
+ * @param [id] - The id of the field that caused the change
358
404
  */
359
- onChange = (formData, newErrorSchema, id) => {
405
+ onChange = (newValue, path, newErrorSchema, id) => {
406
+ this.pendingChanges.push({ newValue, path, newErrorSchema, id });
407
+ if (this.pendingChanges.length === 1) {
408
+ this.processPendingChange();
409
+ }
410
+ };
411
+ /** Function to handle changes made to a field in the `Form`. This handler gets the first change from the
412
+ * `pendingChanges` list, containing the `newValue` for the `formData` and the `path` at which the `newValue` is to be
413
+ * updated, along with a new, optional `ErrorSchema` for that same `path` and potentially the `id` of the field being
414
+ * changed. It will first update the `formData` with any missing default fields and then, if `omitExtraData` and
415
+ * `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not in a form field. Then, the
416
+ * resulting `formData` will be validated if required. The state will be updated with the new updated (potentially
417
+ * filtered) `formData`, any errors that resulted from validation. Finally the `onChange` callback will be called, if
418
+ * specified, with the updated state and the `processPendingChange()` function is called again.
419
+ */
420
+ processPendingChange() {
421
+ if (this.pendingChanges.length === 0) {
422
+ return;
423
+ }
424
+ const { newValue, path, id } = this.pendingChanges[0];
425
+ const { newErrorSchema } = this.pendingChanges[0];
360
426
  const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;
361
- const { schemaUtils, schema } = this.state;
427
+ const { formData: oldFormData, schemaUtils, schema, fieldPathId, schemaValidationErrorSchema, errors } = this.state;
428
+ let { customErrors, errorSchema: originalErrorSchema } = this.state;
429
+ const rootPathId = fieldPathId.path[0] || '';
430
+ const isRootPath = !path || path.length === 0 || (path.length === 1 && path[0] === rootPathId);
362
431
  let retrievedSchema = this.state.retrievedSchema;
432
+ let formData = isRootPath ? newValue : _cloneDeep(oldFormData);
363
433
  if (isObject(formData) || Array.isArray(formData)) {
364
- const newState = this.getStateFromProps(this.props, formData);
434
+ if (!isRootPath) {
435
+ // If the newValue is not on the root path, then set it into the form data
436
+ _set(formData, path, newValue);
437
+ }
438
+ // Pass true to skip live validation in `getStateFromProps()` since we will do it a bit later
439
+ const newState = this.getStateFromProps(this.props, formData, undefined, undefined, undefined, true);
365
440
  formData = newState.formData;
366
441
  retrievedSchema = newState.retrievedSchema;
367
442
  }
368
- const mustValidate = !noValidate && liveValidate;
443
+ const mustValidate = !noValidate && (liveValidate === true || liveValidate === 'onChange');
369
444
  let state = { formData, schema };
370
445
  let newFormData = formData;
371
- if (omitExtraData === true && liveOmit === true) {
446
+ if (omitExtraData === true && (liveOmit === true || liveOmit === 'onChange')) {
372
447
  newFormData = this.omitExtraData(formData);
373
448
  state = {
374
449
  formData: newFormData,
375
450
  };
376
451
  }
377
- if (mustValidate) {
378
- const schemaValidation = this.validate(newFormData, schema, schemaUtils, retrievedSchema);
379
- let errors = schemaValidation.errors;
380
- let errorSchema = schemaValidation.errorSchema;
381
- const schemaValidationErrors = errors;
382
- const schemaValidationErrorSchema = errorSchema;
383
- if (extraErrors) {
384
- const merged = validationDataMerge(schemaValidation, extraErrors);
385
- errorSchema = merged.errorSchema;
386
- errors = merged.errors;
452
+ if (newErrorSchema) {
453
+ // First check to see if there is an existing validation error on this path...
454
+ // @ts-expect-error TS2590, because getting from the error schema is confusing TS
455
+ const oldValidationError = !isRootPath ? _get(schemaValidationErrorSchema, path) : schemaValidationErrorSchema;
456
+ // If there is an old validation error for this path, assume we are updating it directly
457
+ if (!_isEmpty(oldValidationError)) {
458
+ // Update the originalErrorSchema "in place" or replace it if it is the root
459
+ if (!isRootPath) {
460
+ _set(originalErrorSchema, path, newErrorSchema);
461
+ }
462
+ else {
463
+ originalErrorSchema = newErrorSchema;
464
+ }
387
465
  }
388
- // Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors.
389
- if (newErrorSchema) {
390
- const filteredErrors = this.filterErrorsBasedOnSchema(newErrorSchema, retrievedSchema, newFormData);
391
- errorSchema = mergeObjects(errorSchema, filteredErrors, 'preventDuplicates');
466
+ else {
467
+ if (!customErrors) {
468
+ customErrors = new ErrorSchemaBuilder();
469
+ }
470
+ if (isRootPath) {
471
+ const errors = _get(newErrorSchema, ERRORS_KEY);
472
+ if (errors) {
473
+ // only set errors when there are some
474
+ customErrors.setErrors(errors);
475
+ }
476
+ }
477
+ else {
478
+ _set(customErrors.ErrorSchema, path, newErrorSchema);
479
+ }
392
480
  }
393
- state = {
394
- formData: newFormData,
395
- errors,
396
- errorSchema,
397
- schemaValidationErrors,
398
- schemaValidationErrorSchema,
399
- };
481
+ }
482
+ else if (customErrors && _get(customErrors.ErrorSchema, [...path, ERRORS_KEY])) {
483
+ // If we have custom errors and the path has an error, then we need to clear it
484
+ customErrors.clearErrors(path);
485
+ }
486
+ // If there are pending changes in the queue, skip live validation since it will happen with the last change
487
+ if (mustValidate && this.pendingChanges.length === 1) {
488
+ const liveValidation = this.liveValidate(schema, schemaUtils, originalErrorSchema, newFormData, extraErrors, customErrors, retrievedSchema);
489
+ state = { formData: newFormData, ...liveValidation, customErrors };
400
490
  }
401
491
  else if (!noValidate && newErrorSchema) {
402
- const errorSchema = extraErrors
403
- ? mergeObjects(newErrorSchema, extraErrors, 'preventDuplicates')
404
- : newErrorSchema;
492
+ // Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors.
493
+ const mergedErrors = this.mergeErrors({ errorSchema: originalErrorSchema, errors }, extraErrors, customErrors);
405
494
  state = {
406
495
  formData: newFormData,
407
- errorSchema: errorSchema,
408
- errors: toErrorList(errorSchema),
496
+ ...mergedErrors,
497
+ customErrors,
409
498
  };
410
499
  }
411
- this.setState(state, () => onChange && onChange({ ...this.state, ...state }, id));
412
- };
500
+ this.setState(state, () => {
501
+ if (onChange) {
502
+ onChange(toIChangeEvent({ ...this.state, ...state }), id);
503
+ }
504
+ // Now remove the change we just completed and call this again
505
+ this.pendingChanges.shift();
506
+ this.processPendingChange();
507
+ });
508
+ }
413
509
  /**
414
510
  * If the retrievedSchema has changed the new retrievedSchema is returned.
415
511
  * Otherwise, the old retrievedSchema is returned to persist reference.
@@ -430,8 +526,9 @@ export default class Form extends Component {
430
526
  *
431
527
  */
432
528
  reset = () => {
433
- const { onChange } = this.props;
434
- const newState = this.getStateFromProps(this.props, undefined);
529
+ // Cast the IS_RESET symbol to T to avoid type issues, we use this symbol to detect reset mode
530
+ const { formData: propsFormData, initialFormData = IS_RESET, onChange } = this.props;
531
+ const newState = this.getStateFromProps(this.props, propsFormData ?? initialFormData, undefined, undefined, undefined, true);
435
532
  const newFormData = newState.formData;
436
533
  const state = {
437
534
  formData: newFormData,
@@ -439,20 +536,51 @@ export default class Form extends Component {
439
536
  errors: [],
440
537
  schemaValidationErrors: [],
441
538
  schemaValidationErrorSchema: {},
539
+ initialDefaultsGenerated: false,
540
+ customErrors: undefined,
442
541
  };
443
- this.setState(state, () => onChange && onChange({ ...this.state, ...state }));
542
+ this.setState(state, () => onChange && onChange(toIChangeEvent({ ...this.state, ...state })));
444
543
  };
445
544
  /** Callback function to handle when a field on the form is blurred. Calls the `onBlur` callback for the `Form` if it
446
- * was provided.
545
+ * was provided. Also runs any live validation and/or live omit operations if the flags indicate they should happen
546
+ * during `onBlur`.
447
547
  *
448
548
  * @param id - The unique `id` of the field that was blurred
449
549
  * @param data - The data associated with the field that was blurred
450
550
  */
451
551
  onBlur = (id, data) => {
452
- const { onBlur } = this.props;
552
+ const { onBlur, omitExtraData, liveOmit, liveValidate } = this.props;
453
553
  if (onBlur) {
454
554
  onBlur(id, data);
455
555
  }
556
+ if ((omitExtraData === true && liveOmit === 'onBlur') || liveValidate === 'onBlur') {
557
+ const { onChange, extraErrors } = this.props;
558
+ const { formData } = this.state;
559
+ let newFormData = formData;
560
+ let state = { formData: newFormData };
561
+ if (omitExtraData === true && liveOmit === 'onBlur') {
562
+ newFormData = this.omitExtraData(formData);
563
+ state = { formData: newFormData };
564
+ }
565
+ if (liveValidate === 'onBlur') {
566
+ const { schema, schemaUtils, errorSchema, customErrors, retrievedSchema } = this.state;
567
+ const liveValidation = this.liveValidate(schema, schemaUtils, errorSchema, newFormData, extraErrors, customErrors, retrievedSchema);
568
+ state = { formData: newFormData, ...liveValidation, customErrors };
569
+ }
570
+ const hasChanges = Object.keys(state)
571
+ // Filter out `schemaValidationErrors` and `schemaValidationErrorSchema` since they aren't IChangeEvent props
572
+ .filter((key) => !key.startsWith('schemaValidation'))
573
+ .some((key) => {
574
+ const oldData = _get(this.state, key);
575
+ const newData = _get(state, key);
576
+ return !deepEquals(oldData, newData);
577
+ });
578
+ this.setState(state, () => {
579
+ if (onChange && hasChanges) {
580
+ onChange(toIChangeEvent({ ...this.state, ...state }), id);
581
+ }
582
+ });
583
+ }
456
584
  };
457
585
  /** Callback function to handle when a field on the form is focused. Calls the `onFocus` callback for the `Form` if it
458
586
  * was provided.
@@ -498,32 +626,50 @@ export default class Form extends Component {
498
626
  schemaValidationErrorSchema: {},
499
627
  }, () => {
500
628
  if (onSubmit) {
501
- onSubmit({ ...this.state, formData: newFormData, status: 'submitted' }, event);
629
+ onSubmit(toIChangeEvent({ ...this.state, formData: newFormData }, 'submitted'), event);
502
630
  }
503
631
  });
504
632
  }
505
633
  };
506
- /** Returns the registry for the form */
507
- getRegistry() {
508
- const { translateString: customTranslateString, uiSchema = {} } = this.props;
509
- const { schemaUtils } = this.state;
634
+ /** Extracts the `GlobalFormOptions` from the given Form `props`
635
+ *
636
+ * @param props - The form props to extract the global form options from
637
+ * @returns - The `GlobalFormOptions` from the props
638
+ * @private
639
+ */
640
+ getGlobalFormOptions(props) {
641
+ const { uiSchema = {}, experimental_componentUpdateStrategy, idSeparator = DEFAULT_ID_SEPARATOR, idPrefix = DEFAULT_ID_PREFIX, nameGenerator, useFallbackUiForUnsupportedType = false, } = props;
642
+ const rootFieldId = uiSchema['ui:rootFieldId'];
643
+ // Omit any options that are undefined or null
644
+ return {
645
+ idPrefix: rootFieldId || idPrefix,
646
+ idSeparator,
647
+ useFallbackUiForUnsupportedType,
648
+ ...(experimental_componentUpdateStrategy !== undefined && { experimental_componentUpdateStrategy }),
649
+ ...(nameGenerator !== undefined && { nameGenerator }),
650
+ };
651
+ }
652
+ /** Computed the registry for the form using the given `props`, `schema` and `schemaUtils` */
653
+ getRegistry(props, schema, schemaUtils) {
654
+ const { translateString: customTranslateString, uiSchema = {} } = props;
510
655
  const { fields, templates, widgets, formContext, translateString } = getDefaultRegistry();
511
656
  return {
512
- fields: { ...fields, ...this.props.fields },
657
+ fields: { ...fields, ...props.fields },
513
658
  templates: {
514
659
  ...templates,
515
- ...this.props.templates,
660
+ ...props.templates,
516
661
  ButtonTemplates: {
517
662
  ...templates.ButtonTemplates,
518
- ...this.props.templates?.ButtonTemplates,
663
+ ...props.templates?.ButtonTemplates,
519
664
  },
520
665
  },
521
- widgets: { ...widgets, ...this.props.widgets },
522
- rootSchema: this.props.schema,
523
- formContext: this.props.formContext || formContext,
666
+ widgets: { ...widgets, ...props.widgets },
667
+ rootSchema: schema,
668
+ formContext: props.formContext || formContext,
524
669
  schemaUtils,
525
670
  translateString: customTranslateString || translateString,
526
671
  globalUiOptions: uiSchema[UI_GLOBAL_OPTIONS_KEY],
672
+ globalFormOptions: this.getGlobalFormOptions(props),
527
673
  };
528
674
  }
529
675
  /** Provides a function that can be used to programmatically submit the `Form` */
@@ -640,9 +786,8 @@ export default class Form extends Component {
640
786
  * needed along with the submit button or any children of the form.
641
787
  */
642
788
  render() {
643
- const { children, id, idPrefix, idSeparator, className = '', tagName, name, method, target, action, autoComplete, enctype, acceptCharset, noHtml5Validate = false, disabled, readonly, formContext, showErrorList = 'top', _internalFormWrapper, } = this.props;
644
- const { schema, uiSchema, formData, errorSchema, idSchema } = this.state;
645
- const registry = this.getRegistry();
789
+ const { children, id, className = '', tagName, name, method, target, action, autoComplete, enctype, acceptCharset, noHtml5Validate = false, disabled, readonly, showErrorList = 'top', _internalFormWrapper, } = this.props;
790
+ const { schema, uiSchema, formData, errorSchema, fieldPathId, registry } = this.state;
646
791
  const { SchemaField: _SchemaField } = registry.fields;
647
792
  const { SubmitButton } = registry.templates.ButtonTemplates;
648
793
  // The `semantic-ui` and `material-ui` themes have `_internalFormWrapper`s that take an `as` prop that is the
@@ -655,6 +800,6 @@ export default class Form extends Component {
655
800
  submitOptions = { ...submitOptions, props: { ...submitOptions.props, disabled: true } };
656
801
  }
657
802
  const submitUiSchema = { [UI_OPTIONS_KEY]: { [SUBMIT_BTN_OPTIONS_KEY]: submitOptions } };
658
- return (_jsxs(FormTag, { className: className ? className : 'rjsf', id: id, name: name, method: method, target: target, action: action, autoComplete: autoComplete, encType: enctype, acceptCharset: acceptCharset, noValidate: noHtml5Validate, onSubmit: this.onSubmit, as: as, ref: this.formElement, children: [showErrorList === 'top' && this.renderErrors(registry), _jsx(_SchemaField, { name: '', schema: schema, uiSchema: uiSchema, errorSchema: errorSchema, idSchema: idSchema, idPrefix: idPrefix, idSeparator: idSeparator, formContext: formContext, formData: formData, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, registry: registry, disabled: disabled, readonly: readonly }), children ? children : _jsx(SubmitButton, { uiSchema: submitUiSchema, registry: registry }), showErrorList === 'bottom' && this.renderErrors(registry)] }));
803
+ return (_jsxs(FormTag, { className: className ? className : 'rjsf', id: id, name: name, method: method, target: target, action: action, autoComplete: autoComplete, encType: enctype, acceptCharset: acceptCharset, noValidate: noHtml5Validate, onSubmit: this.onSubmit, as: as, ref: this.formElement, children: [showErrorList === 'top' && this.renderErrors(registry), _jsx(_SchemaField, { name: '', schema: schema, uiSchema: uiSchema, errorSchema: errorSchema, fieldPathId: fieldPathId, formData: formData, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, registry: registry, disabled: disabled, readonly: readonly }), children ? children : _jsx(SubmitButton, { uiSchema: submitUiSchema, registry: registry }), showErrorList === 'bottom' && this.renderErrors(registry)] }));
659
804
  }
660
805
  }