@rjsf/core 6.6.2 → 6.7.1

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 (162) hide show
  1. package/LICENSE.md +201 -0
  2. package/dist/core.umd.js +312 -252
  3. package/dist/index.cjs +947 -878
  4. package/dist/index.cjs.map +4 -4
  5. package/dist/index.esm.js +741 -667
  6. package/dist/index.esm.js.map +4 -4
  7. package/lib/components/Form.d.ts +2 -2
  8. package/lib/components/Form.d.ts.map +1 -1
  9. package/lib/components/Form.js +30 -15
  10. package/lib/components/RichDescription.d.ts +1 -1
  11. package/lib/components/RichDescription.d.ts.map +1 -1
  12. package/lib/components/RichHelp.d.ts +1 -1
  13. package/lib/components/RichHelp.d.ts.map +1 -1
  14. package/lib/components/SchemaExamples.d.ts +1 -1
  15. package/lib/components/SchemaExamples.d.ts.map +1 -1
  16. package/lib/components/fields/ArrayField.d.ts +1 -1
  17. package/lib/components/fields/ArrayField.d.ts.map +1 -1
  18. package/lib/components/fields/BooleanField.d.ts +1 -1
  19. package/lib/components/fields/BooleanField.d.ts.map +1 -1
  20. package/lib/components/fields/CyclicSchemaField.d.ts +8 -0
  21. package/lib/components/fields/CyclicSchemaField.d.ts.map +1 -0
  22. package/lib/components/fields/CyclicSchemaField.js +21 -0
  23. package/lib/components/fields/FallbackField.d.ts +1 -1
  24. package/lib/components/fields/FallbackField.d.ts.map +1 -1
  25. package/lib/components/fields/LayoutGridField.d.ts +1 -1
  26. package/lib/components/fields/LayoutGridField.d.ts.map +1 -1
  27. package/lib/components/fields/LayoutHeaderField.d.ts +1 -1
  28. package/lib/components/fields/LayoutHeaderField.d.ts.map +1 -1
  29. package/lib/components/fields/LayoutMultiSchemaField.d.ts +1 -1
  30. package/lib/components/fields/LayoutMultiSchemaField.d.ts.map +1 -1
  31. package/lib/components/fields/MultiSchemaField.d.ts +1 -46
  32. package/lib/components/fields/MultiSchemaField.d.ts.map +1 -1
  33. package/lib/components/fields/MultiSchemaField.js +111 -135
  34. package/lib/components/fields/NumberField.d.ts +1 -1
  35. package/lib/components/fields/NumberField.d.ts.map +1 -1
  36. package/lib/components/fields/NumberField.js +26 -15
  37. package/lib/components/fields/ObjectField.d.ts +1 -1
  38. package/lib/components/fields/ObjectField.d.ts.map +1 -1
  39. package/lib/components/fields/ObjectField.js +20 -5
  40. package/lib/components/fields/OptionalDataControlsField.d.ts +1 -1
  41. package/lib/components/fields/OptionalDataControlsField.d.ts.map +1 -1
  42. package/lib/components/fields/SchemaField.d.ts +13 -7
  43. package/lib/components/fields/SchemaField.d.ts.map +1 -1
  44. package/lib/components/fields/SchemaField.js +37 -24
  45. package/lib/components/fields/StringField.d.ts +1 -1
  46. package/lib/components/fields/StringField.d.ts.map +1 -1
  47. package/lib/components/fields/index.d.ts.map +1 -1
  48. package/lib/components/fields/index.js +2 -0
  49. package/lib/components/templates/ArrayFieldDescriptionTemplate.d.ts +1 -1
  50. package/lib/components/templates/ArrayFieldDescriptionTemplate.d.ts.map +1 -1
  51. package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts +1 -1
  52. package/lib/components/templates/ArrayFieldItemButtonsTemplate.d.ts.map +1 -1
  53. package/lib/components/templates/ArrayFieldItemTemplate.d.ts +1 -1
  54. package/lib/components/templates/ArrayFieldItemTemplate.d.ts.map +1 -1
  55. package/lib/components/templates/ArrayFieldTemplate.d.ts +1 -1
  56. package/lib/components/templates/ArrayFieldTemplate.d.ts.map +1 -1
  57. package/lib/components/templates/ArrayFieldTitleTemplate.d.ts +1 -1
  58. package/lib/components/templates/ArrayFieldTitleTemplate.d.ts.map +1 -1
  59. package/lib/components/templates/BaseInputTemplate.d.ts +1 -1
  60. package/lib/components/templates/BaseInputTemplate.d.ts.map +1 -1
  61. package/lib/components/templates/BaseInputTemplate.js +2 -2
  62. package/lib/components/templates/ButtonTemplates/AddButton.d.ts +1 -1
  63. package/lib/components/templates/ButtonTemplates/AddButton.d.ts.map +1 -1
  64. package/lib/components/templates/ButtonTemplates/IconButton.d.ts +6 -6
  65. package/lib/components/templates/ButtonTemplates/IconButton.d.ts.map +1 -1
  66. package/lib/components/templates/ButtonTemplates/SubmitButton.d.ts +1 -1
  67. package/lib/components/templates/ButtonTemplates/SubmitButton.d.ts.map +1 -1
  68. package/lib/components/templates/CyclicSchemaExpandTemplate.d.ts +7 -0
  69. package/lib/components/templates/CyclicSchemaExpandTemplate.d.ts.map +1 -0
  70. package/lib/components/templates/CyclicSchemaExpandTemplate.js +12 -0
  71. package/lib/components/templates/DescriptionField.d.ts +1 -1
  72. package/lib/components/templates/DescriptionField.d.ts.map +1 -1
  73. package/lib/components/templates/ErrorList.d.ts +1 -1
  74. package/lib/components/templates/ErrorList.d.ts.map +1 -1
  75. package/lib/components/templates/FallbackFieldTemplate.d.ts +1 -1
  76. package/lib/components/templates/FallbackFieldTemplate.d.ts.map +1 -1
  77. package/lib/components/templates/FieldErrorTemplate.d.ts +1 -1
  78. package/lib/components/templates/FieldErrorTemplate.d.ts.map +1 -1
  79. package/lib/components/templates/FieldHelpTemplate.d.ts +1 -1
  80. package/lib/components/templates/FieldHelpTemplate.d.ts.map +1 -1
  81. package/lib/components/templates/FieldTemplate/FieldTemplate.d.ts +1 -1
  82. package/lib/components/templates/FieldTemplate/FieldTemplate.d.ts.map +1 -1
  83. package/lib/components/templates/FieldTemplate/Label.d.ts +1 -1
  84. package/lib/components/templates/FieldTemplate/Label.d.ts.map +1 -1
  85. package/lib/components/templates/GridTemplate.d.ts +1 -1
  86. package/lib/components/templates/GridTemplate.d.ts.map +1 -1
  87. package/lib/components/templates/MultiSchemaFieldTemplate.d.ts +1 -1
  88. package/lib/components/templates/MultiSchemaFieldTemplate.d.ts.map +1 -1
  89. package/lib/components/templates/ObjectFieldTemplate.d.ts +1 -1
  90. package/lib/components/templates/ObjectFieldTemplate.d.ts.map +1 -1
  91. package/lib/components/templates/OptionalDataControlsTemplate.d.ts +1 -1
  92. package/lib/components/templates/OptionalDataControlsTemplate.d.ts.map +1 -1
  93. package/lib/components/templates/TitleField.d.ts +1 -1
  94. package/lib/components/templates/TitleField.d.ts.map +1 -1
  95. package/lib/components/templates/UnsupportedField.d.ts +1 -1
  96. package/lib/components/templates/UnsupportedField.d.ts.map +1 -1
  97. package/lib/components/templates/UnsupportedField.js +1 -1
  98. package/lib/components/templates/WrapIfAdditionalTemplate.d.ts +1 -1
  99. package/lib/components/templates/WrapIfAdditionalTemplate.d.ts.map +1 -1
  100. package/lib/components/templates/index.d.ts.map +1 -1
  101. package/lib/components/templates/index.js +2 -0
  102. package/lib/components/widgets/AltDateTimeWidget.d.ts +1 -1
  103. package/lib/components/widgets/AltDateTimeWidget.d.ts.map +1 -1
  104. package/lib/components/widgets/AltDateWidget.d.ts +1 -1
  105. package/lib/components/widgets/AltDateWidget.d.ts.map +1 -1
  106. package/lib/components/widgets/CheckboxWidget.d.ts +1 -1
  107. package/lib/components/widgets/CheckboxWidget.d.ts.map +1 -1
  108. package/lib/components/widgets/CheckboxesWidget.d.ts +1 -1
  109. package/lib/components/widgets/CheckboxesWidget.d.ts.map +1 -1
  110. package/lib/components/widgets/CheckboxesWidget.js +2 -2
  111. package/lib/components/widgets/ColorWidget.d.ts +1 -1
  112. package/lib/components/widgets/ColorWidget.d.ts.map +1 -1
  113. package/lib/components/widgets/DateTimeWidget.d.ts +1 -1
  114. package/lib/components/widgets/DateTimeWidget.d.ts.map +1 -1
  115. package/lib/components/widgets/DateWidget.d.ts +1 -1
  116. package/lib/components/widgets/DateWidget.d.ts.map +1 -1
  117. package/lib/components/widgets/EmailWidget.d.ts +1 -1
  118. package/lib/components/widgets/EmailWidget.d.ts.map +1 -1
  119. package/lib/components/widgets/FileWidget.d.ts +1 -1
  120. package/lib/components/widgets/FileWidget.d.ts.map +1 -1
  121. package/lib/components/widgets/HiddenWidget.d.ts +1 -1
  122. package/lib/components/widgets/HiddenWidget.d.ts.map +1 -1
  123. package/lib/components/widgets/PasswordWidget.d.ts +1 -1
  124. package/lib/components/widgets/PasswordWidget.d.ts.map +1 -1
  125. package/lib/components/widgets/RadioWidget.d.ts +1 -1
  126. package/lib/components/widgets/RadioWidget.d.ts.map +1 -1
  127. package/lib/components/widgets/RadioWidget.js +2 -2
  128. package/lib/components/widgets/RangeWidget.d.ts +1 -1
  129. package/lib/components/widgets/RangeWidget.d.ts.map +1 -1
  130. package/lib/components/widgets/RatingWidget.d.ts +1 -1
  131. package/lib/components/widgets/RatingWidget.d.ts.map +1 -1
  132. package/lib/components/widgets/SelectWidget.d.ts +1 -1
  133. package/lib/components/widgets/SelectWidget.d.ts.map +1 -1
  134. package/lib/components/widgets/SelectWidget.js +9 -8
  135. package/lib/components/widgets/TextWidget.d.ts +1 -1
  136. package/lib/components/widgets/TextWidget.d.ts.map +1 -1
  137. package/lib/components/widgets/TextareaWidget.d.ts +1 -1
  138. package/lib/components/widgets/TextareaWidget.d.ts.map +1 -1
  139. package/lib/components/widgets/TextareaWidget.js +3 -3
  140. package/lib/components/widgets/TimeWidget.d.ts +1 -1
  141. package/lib/components/widgets/TimeWidget.d.ts.map +1 -1
  142. package/lib/components/widgets/URLWidget.d.ts +1 -1
  143. package/lib/components/widgets/URLWidget.d.ts.map +1 -1
  144. package/lib/components/widgets/UpDownWidget.d.ts +1 -1
  145. package/lib/components/widgets/UpDownWidget.d.ts.map +1 -1
  146. package/lib/tsconfig.tsbuildinfo +1 -1
  147. package/package.json +28 -27
  148. package/src/components/Form.tsx +34 -15
  149. package/src/components/fields/CyclicSchemaField.tsx +40 -0
  150. package/src/components/fields/MultiSchemaField.tsx +201 -230
  151. package/src/components/fields/NumberField.tsx +30 -16
  152. package/src/components/fields/ObjectField.tsx +29 -5
  153. package/src/components/fields/SchemaField.tsx +46 -34
  154. package/src/components/fields/index.ts +2 -0
  155. package/src/components/templates/BaseInputTemplate.tsx +2 -5
  156. package/src/components/templates/CyclicSchemaExpandTemplate.tsx +31 -0
  157. package/src/components/templates/UnsupportedField.tsx +1 -1
  158. package/src/components/templates/index.ts +2 -0
  159. package/src/components/widgets/CheckboxesWidget.tsx +2 -2
  160. package/src/components/widgets/RadioWidget.tsx +2 -2
  161. package/src/components/widgets/SelectWidget.tsx +45 -30
  162. package/src/components/widgets/TextareaWidget.tsx +3 -3
@@ -8,6 +8,7 @@ import type {
8
8
  FormContextType,
9
9
  GenericObjectType,
10
10
  Registry,
11
+ RJSFMarkedSchema,
11
12
  RJSFSchema,
12
13
  StrictRJSFSchema,
13
14
  } from '@rjsf/utils';
@@ -71,6 +72,16 @@ function getDefaultValue<T = any, S extends StrictRJSFSchema = RJSFSchema, F ext
71
72
  }
72
73
  }
73
74
 
75
+ function isAdditionalPropertySchema(schema: unknown) {
76
+ return Boolean((schema as RJSFMarkedSchema)?.[ADDITIONAL_PROPERTY_FLAG]);
77
+ }
78
+
79
+ function getAdditionalPropertyOrder<S extends StrictRJSFSchema = RJSFSchema>(
80
+ schemaProperties: NonNullable<S['properties']>,
81
+ ) {
82
+ return Object.keys(schemaProperties).filter((property) => isAdditionalPropertySchema(schemaProperties[property]));
83
+ }
84
+
74
85
  /** Props for the `ObjectFieldProperty` component */
75
86
  interface ObjectFieldPropertyProps<
76
87
  T = any,
@@ -233,10 +244,17 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
233
244
  [schemaUtils, rawSchema, formData],
234
245
  );
235
246
  const uiOptions = useMemo(() => getUiOptions<T, S, F>(uiSchema, globalUiOptions), [uiSchema, globalUiOptions]);
236
- const { properties: schemaProperties = {} } = schema;
247
+ const schemaProperties = useMemo(() => schema.properties ?? {}, [schema.properties]);
237
248
  // All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
238
249
  const childFieldPathId = props.childFieldPathId ?? fieldPathId;
239
250
  const lastRenamedProperty = useRef({ previousKey: '', currentKey: undefined as string | undefined });
251
+ const [additionalPropertyOrder, setAdditionalPropertyOrder] = useState(() =>
252
+ getAdditionalPropertyOrder<S>(schemaProperties),
253
+ );
254
+ const definedPropertyOrder = useMemo(() => {
255
+ const additionalPropertySet = new Set(getAdditionalPropertyOrder<S>(schemaProperties));
256
+ return Object.keys(schemaProperties).filter((property) => !additionalPropertySet.has(property));
257
+ }, [schemaProperties]);
240
258
 
241
259
  const templateTitle = uiOptions.title ?? schema.title ?? title ?? name;
242
260
  const description = uiOptions.description ?? schema.description;
@@ -291,7 +309,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
291
309
  apSchema = schemaUtils.retrieveSchema({ [REF_KEY]: apSchema[REF_KEY] } as S, formData);
292
310
  type = apSchema.type;
293
311
  constValue = apSchema.const;
294
- defaultValue = apSchema.default;
312
+ defaultValue = schemaUtils.getDefaultFormState(apSchema as S, defaultValue as T) as RJSFSchema['default'];
295
313
  }
296
314
  if (!type && (ANY_OF_KEY in apSchema || ONE_OF_KEY in apSchema)) {
297
315
  type = 'object';
@@ -307,6 +325,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
307
325
  lastRenamedProperty.current.currentKey = newKey;
308
326
  lastRenamedProperty.current.previousKey = getAvailableKey(newKey, newFormData);
309
327
  }
328
+ setAdditionalPropertyOrder((order) => [...order, newKey]);
310
329
  onChange(newFormData, childFieldPathId.path);
311
330
  }, [formData, onChange, translateString, schemaUtils, childFieldPathId, getAvailableKey, schema]);
312
331
 
@@ -338,6 +357,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
338
357
  lastRenamedProperty.current.previousKey = oldKey;
339
358
  }
340
359
  lastRenamedProperty.current.currentKey = actualNewKey;
360
+ setAdditionalPropertyOrder((order) => order.map((property) => (property === oldKey ? actualNewKey : property)));
341
361
  onChange(renamedObj, childFieldPathId.path);
342
362
  }
343
363
  },
@@ -349,6 +369,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
349
369
  */
350
370
  const handleRemoveProperty = useCallback(
351
371
  (key: string) => {
372
+ setAdditionalPropertyOrder((order) => order.filter((property) => property !== key));
352
373
  onChange(ADDITIONAL_PROPERTY_KEY_REMOVE as T, [...childFieldPathId.path, key]);
353
374
  },
354
375
  [onChange, childFieldPathId],
@@ -368,8 +389,11 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
368
389
 
369
390
  if (!renderOptionalField || hasFormData) {
370
391
  try {
371
- const properties = Object.keys(schemaProperties);
372
- orderedProperties = orderProperties(properties, uiOptions.order);
392
+ const definedPropertySet = new Set(definedPropertyOrder);
393
+ const currentAdditionalProperties = additionalPropertyOrder.filter(
394
+ (property) => Object.hasOwn(schemaProperties, property) && !definedPropertySet.has(property),
395
+ );
396
+ orderedProperties = orderProperties([...definedPropertyOrder, ...currentAdditionalProperties], uiOptions.order);
373
397
  } catch (err) {
374
398
  return (
375
399
  <div>
@@ -394,7 +418,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
394
418
  title: uiOptions.label === false ? '' : templateTitle,
395
419
  description: uiOptions.label === false ? undefined : description,
396
420
  properties: orderedProperties.map((propertyName) => {
397
- const addedByAdditionalProperties = has(schema, [PROPERTIES_KEY, propertyName, ADDITIONAL_PROPERTY_FLAG]);
421
+ const addedByAdditionalProperties = isAdditionalPropertySchema(schema.properties?.[propertyName]);
398
422
  const fieldUiSchema = addedByAdditionalProperties ? uiSchema.additionalProperties : uiSchema[propertyName];
399
423
  const hidden = getUiOptions<T, S, F>(fieldUiSchema).widget === 'hidden';
400
424
  const content = (
@@ -1,5 +1,5 @@
1
1
  import type { ComponentType } from 'react';
2
- import { useCallback, Component } from 'react';
2
+ import { useCallback, memo } from 'react';
3
3
  import type {
4
4
  ErrorSchema,
5
5
  Field,
@@ -9,6 +9,7 @@ import type {
9
9
  FieldTemplateProps,
10
10
  FormContextType,
11
11
  Registry,
12
+ RJSFMarkedSchema,
12
13
  RJSFSchema,
13
14
  StrictRJSFSchema,
14
15
  UIOptionsType,
@@ -16,6 +17,7 @@ import type {
16
17
  import {
17
18
  ADDITIONAL_PROPERTY_FLAG,
18
19
  ANY_OF_KEY,
20
+ deepEquals,
19
21
  descriptionId,
20
22
  getSchemaType,
21
23
  getTemplate,
@@ -24,7 +26,8 @@ import {
24
26
  isFormDataAvailable,
25
27
  ONE_OF_KEY,
26
28
  resolveUiSchema,
27
- shouldRender,
29
+ RJSF_REF_CYCLE_KEY,
30
+ shallowEquals,
28
31
  shouldRenderOptionalField,
29
32
  toFieldPathId,
30
33
  TranslatableString,
@@ -59,7 +62,7 @@ function getFieldComponent<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
59
62
  registry: Registry<T, S, F>,
60
63
  ): ComponentType<FieldProps<T, S, F>> {
61
64
  const { field } = uiOptions;
62
- const { fields } = registry;
65
+ const { fields, schemaUtils } = registry;
63
66
  if (typeof field === 'function') {
64
67
  return field;
65
68
  }
@@ -77,9 +80,13 @@ function getFieldComponent<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
77
80
  componentName = schemaId;
78
81
  }
79
82
 
80
- // If the type is not defined and the schema uses 'anyOf' or 'oneOf', don't
81
- // render a field and let the MultiSchemaField component handle the form display
82
- if (!componentName && (schema.anyOf || schema.oneOf)) {
83
+ // If the schema uses 'anyOf' or 'oneOf' and is not a pure select (all-constant options),
84
+ // let the MultiSchemaField component handle the form display entirely.
85
+ // ObjectField is excluded: it renders shared properties (defined at the parent schema
86
+ // level) alongside the XxxOfField option selector.
87
+ // All other field types — including primitives and arrays — have no shared renderable
88
+ // properties, so the outer FieldComponent would only produce a spurious duplicate input.
89
+ if ((schema.anyOf || schema.oneOf) && !schemaUtils.isSelect(schema) && componentName !== 'ObjectField') {
83
90
  return () => null;
84
91
  }
85
92
 
@@ -111,18 +118,7 @@ function SchemaFieldRender<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
111
118
  wasPropertyKeyModified = false,
112
119
  } = props;
113
120
  const { schemaUtils, globalFormOptions, globalUiOptions, fields } = registry;
114
- const { AnyOfField: _AnyOfField, OneOfField: _OneOfField } = fields;
115
- const uiSchema = resolveUiSchema<T, S, F>(_schema, _uiSchema, registry);
116
- const uiOptions = getUiOptions<T, S, F>(uiSchema, globalUiOptions);
117
- const FieldTemplate = getTemplate<'FieldTemplate', T, S, F>('FieldTemplate', registry, uiOptions);
118
- const DescriptionFieldTemplate = getTemplate<'DescriptionFieldTemplate', T, S, F>(
119
- 'DescriptionFieldTemplate',
120
- registry,
121
- uiOptions,
122
- );
123
- const FieldHelpTemplate = getTemplate<'FieldHelpTemplate', T, S, F>('FieldHelpTemplate', registry, uiOptions);
124
- const FieldErrorTemplate = getTemplate<'FieldErrorTemplate', T, S, F>('FieldErrorTemplate', registry, uiOptions);
125
- const schema = schemaUtils.retrieveSchema(_schema, formData);
121
+ const { AnyOfField: _AnyOfField, OneOfField: _OneOfField, CyclicSchemaField } = fields;
126
122
  const fieldId = fieldPathId[ID_KEY];
127
123
 
128
124
  /** Intermediary `onChange` handler for field components that will inject the `id` of the current field into the
@@ -136,6 +132,24 @@ function SchemaFieldRender<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
136
132
  [fieldId, onChange],
137
133
  );
138
134
 
135
+ // Stop $ref cycles: when resolveAllReferences detects a repeated property $ref it tags the schema with this flag.
136
+ // The check must come after all hook calls to satisfy React's rules of hooks.
137
+ if ((_schema as RJSFMarkedSchema)[RJSF_REF_CYCLE_KEY]) {
138
+ return <CyclicSchemaField {...props} />;
139
+ }
140
+
141
+ const uiSchema = resolveUiSchema<T, S, F>(_schema, _uiSchema, registry);
142
+ const uiOptions = getUiOptions<T, S, F>(uiSchema, globalUiOptions);
143
+ const FieldTemplate = getTemplate<'FieldTemplate', T, S, F>('FieldTemplate', registry, uiOptions);
144
+ const DescriptionFieldTemplate = getTemplate<'DescriptionFieldTemplate', T, S, F>(
145
+ 'DescriptionFieldTemplate',
146
+ registry,
147
+ uiOptions,
148
+ );
149
+ const FieldHelpTemplate = getTemplate<'FieldHelpTemplate', T, S, F>('FieldHelpTemplate', registry, uiOptions);
150
+ const FieldErrorTemplate = getTemplate<'FieldErrorTemplate', T, S, F>('FieldErrorTemplate', registry, uiOptions);
151
+ const schema = schemaUtils.retrieveSchema(_schema, formData);
152
+
139
153
  const FieldComponent = getFieldComponent<T, S, F>(schema, uiOptions, registry);
140
154
 
141
155
  const isDeprecated = Boolean(schema.deprecated);
@@ -327,24 +341,22 @@ function SchemaFieldRender<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
327
341
  );
328
342
  }
329
343
 
330
- /** The `SchemaField` component determines whether it is necessary to rerender the component based on any props changes
331
- * and if so, calls the `SchemaFieldRender` component with the props.
344
+ /** The `SchemaField` component wraps `SchemaFieldRender` with a custom memoization comparator that determines whether it is necessary to rerender the component based on any props changes
345
+ * using `experimental_componentUpdateStrategy`.
346
+ *
347
+ * The cast to `typeof SchemaFieldRender` preserves the generic type signature (<T, S, F>) for consumers,
348
+ * since React.memo's return type erases generic parameters.
332
349
  */
333
- class SchemaField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> extends Component<
334
- FieldProps<T, S, F>
335
- > {
336
- shouldComponentUpdate(nextProps: Readonly<FieldProps<T, S, F>>) {
337
- const {
338
- registry: { globalFormOptions },
339
- } = this.props;
340
- const { experimental_componentUpdateStrategy = 'customDeep' } = globalFormOptions;
341
-
342
- return shouldRender(this, nextProps, this.state, experimental_componentUpdateStrategy);
350
+ const SchemaField = memo(SchemaFieldRender, (prevProps, nextProps) => {
351
+ const { experimental_componentUpdateStrategy = 'customDeep' } = nextProps.registry.globalFormOptions;
352
+ if (experimental_componentUpdateStrategy === 'always') {
353
+ return false; // always re-render — never consider props equal
343
354
  }
344
-
345
- render() {
346
- return <SchemaFieldRender<T, S, F> {...this.props} />;
355
+ if (experimental_componentUpdateStrategy === 'shallow') {
356
+ return shallowEquals(prevProps, nextProps);
347
357
  }
348
- }
358
+ // default: 'customDeep'
359
+ return deepEquals(prevProps, nextProps);
360
+ }) as unknown as typeof SchemaFieldRender;
349
361
 
350
362
  export default SchemaField;
@@ -2,6 +2,7 @@ import type { Field, FormContextType, RegistryFieldsType, RJSFSchema, StrictRJSF
2
2
 
3
3
  import ArrayField from './ArrayField';
4
4
  import BooleanField from './BooleanField';
5
+ import CyclicSchemaField from './CyclicSchemaField';
5
6
  import FallbackField from './FallbackField';
6
7
  import LayoutGridField from './LayoutGridField';
7
8
  import LayoutHeaderField from './LayoutHeaderField';
@@ -24,6 +25,7 @@ function fields<
24
25
  ArrayField: ArrayField as unknown as Field<T, S, F>,
25
26
  // ArrayField falls back to SchemaField if ArraySchemaField is not defined, which it isn't by default
26
27
  BooleanField,
28
+ CyclicSchemaField,
27
29
  FallbackField,
28
30
  LayoutGridField,
29
31
  LayoutHeaderField,
@@ -64,12 +64,9 @@ export default function BaseInputTemplate<
64
64
  onChange(newValue === '' ? options.emptyValue : newValue),
65
65
  [onChange, options],
66
66
  );
67
- const handleBlur = useCallback(
68
- ({ target }: FocusEvent<HTMLInputElement>) => onBlur(id, target && target.value),
69
- [onBlur, id],
70
- );
67
+ const handleBlur = useCallback(({ target }: FocusEvent<HTMLInputElement>) => onBlur(id, target?.value), [onBlur, id]);
71
68
  const handleFocus = useCallback(
72
- ({ target }: FocusEvent<HTMLInputElement>) => onFocus(id, target && target.value),
69
+ ({ target }: FocusEvent<HTMLInputElement>) => onFocus(id, target?.value),
73
70
  [onFocus, id],
74
71
  );
75
72
  const handleClear = useCallback(
@@ -0,0 +1,31 @@
1
+ import type { CyclicSchemaExpandProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils';
2
+ import { ID_KEY, TranslatableString } from '@rjsf/utils';
3
+
4
+ /** The `CyclicSchemaExpandTemplate` is the template to use to render the cyclic schema expand message and controls
5
+ *
6
+ * @param props - The `DescriptionFieldProps` for this component
7
+ */
8
+ export default function CyclicSchemaExpandTemplate<
9
+ T = any,
10
+ S extends StrictRJSFSchema = RJSFSchema,
11
+ F extends FormContextType = any,
12
+ >(props: CyclicSchemaExpandProps<T, S, F>) {
13
+ const { name, fieldPathId, registry, onExpand } = props;
14
+ const { translateString } = registry;
15
+ const buttonId = `${fieldPathId[ID_KEY]}-button`;
16
+ return (
17
+ <div style={{ marginTop: '1rem' }}>
18
+ <div className='text-danger'>{translateString(TranslatableString.CycleDetected, [name])}</div>
19
+ <div>
20
+ <button
21
+ id={buttonId}
22
+ type='button'
23
+ className='btn btn-sm btn-warning'
24
+ onClick={() => onExpand(fieldPathId[ID_KEY])}
25
+ >
26
+ {translateString(TranslatableString.ExpandButton)}
27
+ </button>{' '}
28
+ </div>
29
+ </div>
30
+ );
31
+ }
@@ -14,7 +14,7 @@ function UnsupportedField<T = any, S extends StrictRJSFSchema = RJSFSchema, F ex
14
14
  const { translateString } = registry;
15
15
  let translateEnum: TranslatableString = TranslatableString.UnsupportedField;
16
16
  const translateParams: string[] = [];
17
- if (fieldPathId && fieldPathId.$id) {
17
+ if (fieldPathId?.$id) {
18
18
  translateEnum = TranslatableString.UnsupportedFieldWithId;
19
19
  translateParams.push(fieldPathId.$id);
20
20
  }
@@ -7,6 +7,7 @@ import ArrayFieldTemplate from './ArrayFieldTemplate';
7
7
  import ArrayFieldTitleTemplate from './ArrayFieldTitleTemplate';
8
8
  import BaseInputTemplate from './BaseInputTemplate';
9
9
  import ButtonTemplates from './ButtonTemplates';
10
+ import CyclicSchemaExpandTemplate from './CyclicSchemaExpandTemplate';
10
11
  import DescriptionField from './DescriptionField';
11
12
  import ErrorList from './ErrorList';
12
13
  import FallbackFieldTemplate from './FallbackFieldTemplate';
@@ -34,6 +35,7 @@ function templates<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends F
34
35
  ArrayFieldTitleTemplate,
35
36
  ButtonTemplates: ButtonTemplates<T, S, F>(),
36
37
  BaseInputTemplate,
38
+ CyclicSchemaExpandTemplate,
37
39
  DescriptionFieldTemplate: DescriptionField,
38
40
  ErrorListTemplate: ErrorList,
39
41
  FallbackFieldTemplate,
@@ -35,13 +35,13 @@ function CheckboxesWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F ex
35
35
 
36
36
  const handleBlur = useCallback(
37
37
  ({ target }: FocusEvent<HTMLInputElement>) =>
38
- onBlur(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, optionValueFormat, emptyValue)),
38
+ onBlur(id, enumOptionValueDecoder<S>(target?.value, enumOptions, optionValueFormat, emptyValue)),
39
39
  [onBlur, id, enumOptions, emptyValue, optionValueFormat],
40
40
  );
41
41
 
42
42
  const handleFocus = useCallback(
43
43
  ({ target }: FocusEvent<HTMLInputElement>) =>
44
- onFocus(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, optionValueFormat, emptyValue)),
44
+ onFocus(id, enumOptionValueDecoder<S>(target?.value, enumOptions, optionValueFormat, emptyValue)),
45
45
  [onFocus, id, enumOptions, emptyValue, optionValueFormat],
46
46
  );
47
47
 
@@ -33,13 +33,13 @@ function RadioWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
33
33
 
34
34
  const handleBlur = useCallback(
35
35
  ({ target }: FocusEvent<HTMLInputElement>) =>
36
- onBlur(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, optionValueFormat, emptyValue)),
36
+ onBlur(id, enumOptionValueDecoder<S>(target?.value, enumOptions, optionValueFormat, emptyValue)),
37
37
  [onBlur, enumOptions, emptyValue, id, optionValueFormat],
38
38
  );
39
39
 
40
40
  const handleFocus = useCallback(
41
41
  ({ target }: FocusEvent<HTMLInputElement>) =>
42
- onFocus(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, optionValueFormat, emptyValue)),
42
+ onFocus(id, enumOptionValueDecoder<S>(target?.value, enumOptions, optionValueFormat, emptyValue)),
43
43
  [onFocus, enumOptions, emptyValue, id, optionValueFormat],
44
44
  );
45
45
 
@@ -7,6 +7,8 @@ import {
7
7
  enumOptionValueDecoder,
8
8
  enumOptionValueEncoder,
9
9
  getOptionValueFormat,
10
+ logUnsupportedDefaultForEnum,
11
+ SelectedOptionDescription,
10
12
  } from '@rjsf/utils';
11
13
 
12
14
  function getValue(event: SyntheticEvent<HTMLSelectElement>, multiple: boolean) {
@@ -39,6 +41,8 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
39
41
  onFocus,
40
42
  placeholder,
41
43
  htmlName,
44
+ registry,
45
+ uiSchema,
42
46
  }: WidgetProps<T, S, F>) {
43
47
  const { enumOptions, enumDisabled, emptyValue: optEmptyVal } = options;
44
48
  const emptyValue = multiple ? [] : '';
@@ -70,38 +74,49 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
70
74
 
71
75
  const selectValue = enumOptionSelectedValue<S>(value, enumOptions, multiple, optionValueFormat, emptyValue);
72
76
  const showPlaceholderOption = !multiple && schema.default === undefined;
77
+ logUnsupportedDefaultForEnum<S>(id, schema, enumOptions, multiple);
73
78
 
74
79
  return (
75
- // oxlint-disable-next-line jsx-a11y/no-autofocus
76
- <select
77
- id={id}
78
- name={htmlName || id}
79
- multiple={multiple}
80
- className='form-control'
81
- value={selectValue}
82
- required={required}
83
- disabled={disabled || readonly}
84
- autoFocus={autofocus}
85
- onBlur={handleBlur}
86
- onFocus={handleFocus}
87
- onChange={handleChange}
88
- aria-describedby={ariaDescribedByIds(id)}
89
- >
90
- {showPlaceholderOption && <option value=''>{placeholder}</option>}
91
- {Array.isArray(enumOptions) &&
92
- enumOptions.map(({ value: enumValue, label: enumLabel }, i) => {
93
- const isDisabled = enumDisabled && enumDisabled.includes(enumValue);
94
- return (
95
- <option
96
- key={String(enumValue)}
97
- value={enumOptionValueEncoder(enumValue, i, optionValueFormat)}
98
- disabled={isDisabled}
99
- >
100
- {enumLabel}
101
- </option>
102
- );
103
- })}
104
- </select>
80
+ <>
81
+ <SelectedOptionDescription
82
+ id={id}
83
+ multiple={multiple}
84
+ options={options}
85
+ registry={registry}
86
+ uiSchema={uiSchema}
87
+ value={value}
88
+ />
89
+ {/* oxlint-disable-next-line jsx-a11y/no-autofocus */}
90
+ <select
91
+ id={id}
92
+ name={htmlName || id}
93
+ multiple={multiple}
94
+ className='form-control'
95
+ value={selectValue}
96
+ required={required}
97
+ disabled={disabled || readonly}
98
+ autoFocus={autofocus}
99
+ onBlur={handleBlur}
100
+ onFocus={handleFocus}
101
+ onChange={handleChange}
102
+ aria-describedby={ariaDescribedByIds(id)}
103
+ >
104
+ {showPlaceholderOption && <option value=''>{placeholder}</option>}
105
+ {Array.isArray(enumOptions) &&
106
+ enumOptions.map(({ value: enumValue, label: enumLabel }, i) => {
107
+ const isDisabled = enumDisabled && enumDisabled.includes(enumValue);
108
+ return (
109
+ <option
110
+ key={String(enumValue)}
111
+ value={enumOptionValueEncoder(enumValue, i, optionValueFormat)}
112
+ disabled={isDisabled}
113
+ >
114
+ {enumLabel}
115
+ </option>
116
+ );
117
+ })}
118
+ </select>
119
+ </>
105
120
  );
106
121
  }
107
122
 
@@ -9,7 +9,7 @@ import { ariaDescribedByIds } from '@rjsf/utils';
9
9
  */
10
10
  function TextareaWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>({
11
11
  id,
12
- options = {},
12
+ options,
13
13
  placeholder,
14
14
  value,
15
15
  required,
@@ -28,12 +28,12 @@ function TextareaWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F exte
28
28
  );
29
29
 
30
30
  const handleBlur = useCallback(
31
- ({ target }: FocusEvent<HTMLTextAreaElement>) => onBlur(id, target && target.value),
31
+ ({ target }: FocusEvent<HTMLTextAreaElement>) => onBlur(id, target?.value),
32
32
  [onBlur, id],
33
33
  );
34
34
 
35
35
  const handleFocus = useCallback(
36
- ({ target }: FocusEvent<HTMLTextAreaElement>) => onFocus(id, target && target.value),
36
+ ({ target }: FocusEvent<HTMLTextAreaElement>) => onFocus(id, target?.value),
37
37
  [id, onFocus],
38
38
  );
39
39