@rjsf/core 6.4.1 → 6.5.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 (30) hide show
  1. package/dist/core.umd.js +102 -38
  2. package/dist/index.cjs +102 -38
  3. package/dist/index.cjs.map +3 -3
  4. package/dist/index.esm.js +114 -44
  5. package/dist/index.esm.js.map +3 -3
  6. package/lib/components/Form.d.ts +6 -0
  7. package/lib/components/Form.d.ts.map +1 -1
  8. package/lib/components/Form.js +30 -14
  9. package/lib/components/fields/ArrayField.d.ts.map +1 -1
  10. package/lib/components/fields/ArrayField.js +5 -1
  11. package/lib/components/fields/ObjectField.d.ts.map +1 -1
  12. package/lib/components/fields/ObjectField.js +29 -5
  13. package/lib/components/templates/WrapIfAdditionalTemplate.d.ts.map +1 -1
  14. package/lib/components/templates/WrapIfAdditionalTemplate.js +1 -1
  15. package/lib/components/widgets/CheckboxesWidget.d.ts +1 -1
  16. package/lib/components/widgets/CheckboxesWidget.d.ts.map +1 -1
  17. package/lib/components/widgets/CheckboxesWidget.js +7 -5
  18. package/lib/components/widgets/RadioWidget.d.ts.map +1 -1
  19. package/lib/components/widgets/RadioWidget.js +5 -4
  20. package/lib/components/widgets/SelectWidget.d.ts.map +1 -1
  21. package/lib/components/widgets/SelectWidget.js +11 -10
  22. package/lib/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +7 -7
  24. package/src/components/Form.tsx +59 -15
  25. package/src/components/fields/ArrayField.tsx +6 -2
  26. package/src/components/fields/ObjectField.tsx +30 -5
  27. package/src/components/templates/WrapIfAdditionalTemplate.tsx +1 -0
  28. package/src/components/widgets/CheckboxesWidget.tsx +12 -7
  29. package/src/components/widgets/RadioWidget.tsx +9 -6
  30. package/src/components/widgets/SelectWidget.tsx +14 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rjsf/core",
3
- "version": "6.4.1",
3
+ "version": "6.5.0",
4
4
  "description": "A simple React component capable of building HTML forms out of a JSON schema.",
5
5
  "scripts": {
6
6
  "compileReplacer": "tsc -p tsconfig.replacer.json && move-file lodashReplacer.js lodashReplacer.cjs",
@@ -70,20 +70,20 @@
70
70
  "react": ">=18"
71
71
  },
72
72
  "dependencies": {
73
- "lodash": "^4.17.23",
74
- "lodash-es": "^4.17.23",
73
+ "lodash": "^4.18.1",
74
+ "lodash-es": "^4.18.1",
75
75
  "markdown-to-jsx": "^8.0.0",
76
76
  "prop-types": "^15.8.1"
77
77
  },
78
78
  "devDependencies": {
79
- "@rjsf/snapshot-tests": "6.4.1",
80
- "@rjsf/utils": "6.4.1",
81
- "@rjsf/validator-ajv8": "6.4.1",
79
+ "@rjsf/snapshot-tests": "6.5.0",
80
+ "@rjsf/utils": "6.5.0",
81
+ "@rjsf/validator-ajv8": "6.5.0",
82
82
  "@testing-library/jest-dom": "^6.9.1",
83
83
  "@testing-library/react": "^16.3.2",
84
84
  "@testing-library/user-event": "^14.6.1",
85
85
  "@types/react-portal": "^4.0.7",
86
- "ajv": "^8.17.1",
86
+ "ajv": "^8.18.0",
87
87
  "atob": "^2.1.2",
88
88
  "eslint": "^8.57.1",
89
89
  "jsdom": "^27.0.1",
@@ -6,7 +6,6 @@ import {
6
6
  ErrorSchema,
7
7
  ErrorSchemaBuilder,
8
8
  ErrorTransformer,
9
- expandUiSchemaDefinitions,
10
9
  FieldPathId,
11
10
  FieldPathList,
12
11
  FormContextType,
@@ -22,6 +21,7 @@ import {
22
21
  RegistryWidgetsType,
23
22
  RJSFSchema,
24
23
  RJSFValidationError,
24
+ removeOptionalEmptyObjects,
25
25
  SchemaUtilsType,
26
26
  shouldRender,
27
27
  SUBMIT_BTN_OPTIONS_KEY,
@@ -208,6 +208,12 @@ export interface FormProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
208
208
  * called. Set to `false` by default.
209
209
  */
210
210
  omitExtraData?: boolean;
211
+ /** If set to true, optional object properties whose fields are all empty (undefined, null, or empty string)
212
+ * will be automatically removed from formData. This prevents the scenario where interacting with fields inside
213
+ * an optional object "activates" it permanently, making the form unsubmittable when the optional object has
214
+ * required inner fields. This works independently of `omitExtraData`. Set to `false` by default.
215
+ */
216
+ removeEmptyOptionalObjects?: boolean;
211
217
  /** When this prop is set to `top` or 'bottom', a list of errors (or the custom error list defined in the `ErrorList`) will also
212
218
  * show. When set to false, only inline input validation errors will be shown. Set to `top` by default
213
219
  */
@@ -663,11 +669,6 @@ export default class Form<
663
669
  const newRegistry = this.getRegistry(props, rootSchema, schemaUtils);
664
670
  const registry = deepEquals(state.registry, newRegistry) ? state.registry : newRegistry;
665
671
 
666
- // Pre-expand ui:definitions into the uiSchema structure (must happen after registry is created)
667
- const expandedUiSchema: UiSchema<T, S, F> = registry.uiSchemaDefinitions
668
- ? expandUiSchemaDefinitions<T, S, F>(rootSchema, uiSchema, registry)
669
- : uiSchema;
670
-
671
672
  // Only compute a new `fieldPathId` when the `idPrefix` is different than the existing fieldPathId's ID_KEY
672
673
  const fieldPathId =
673
674
  state.fieldPathId && state.fieldPathId?.[ID_KEY] === registry.globalFormOptions.idPrefix
@@ -676,7 +677,7 @@ export default class Form<
676
677
  const nextState: FormState<T, S, F> = {
677
678
  schemaUtils,
678
679
  schema: rootSchema,
679
- uiSchema: expandedUiSchema,
680
+ uiSchema,
680
681
  fieldPathId,
681
682
  formData,
682
683
  edit,
@@ -765,7 +766,7 @@ export default class Form<
765
766
  errors = merged.errors;
766
767
  }
767
768
  if (customErrors) {
768
- const merged = validationDataMerge(schemaValidation, customErrors.ErrorSchema, true);
769
+ const merged = validationDataMerge({ errors, errorSchema }, customErrors.ErrorSchema, true);
769
770
  errorSchema = merged.errorSchema;
770
771
  errors = merged.errors;
771
772
  }
@@ -892,7 +893,8 @@ export default class Form<
892
893
  this._isProcessingUserChange = true;
893
894
  const { newValue, path, id } = this.pendingChanges[0];
894
895
  const { newErrorSchema } = this.pendingChanges[0];
895
- const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange } = this.props;
896
+ const { extraErrors, omitExtraData, liveOmit, noValidate, liveValidate, onChange, removeEmptyOptionalObjects } =
897
+ this.props;
896
898
  const { formData: oldFormData, schemaUtils, schema, fieldPathId, schemaValidationErrorSchema, errors } = this.state;
897
899
  let { customErrors, errorSchema: originalErrorSchema } = this.state;
898
900
  const rootPathId = fieldPathId.path[0] || '';
@@ -937,6 +939,19 @@ export default class Form<
937
939
  };
938
940
  }
939
941
 
942
+ if (removeEmptyOptionalObjects) {
943
+ newFormData = removeOptionalEmptyObjects(
944
+ schemaUtils.getValidator(),
945
+ schema,
946
+ schemaUtils.getRootSchema(),
947
+ newFormData,
948
+ ) as T;
949
+ state = {
950
+ ...state,
951
+ formData: newFormData,
952
+ };
953
+ }
954
+
940
955
  if (newErrorSchema) {
941
956
  // First check to see if there is an existing validation error on this path...
942
957
  // @ts-expect-error TS2590, because getting from the error schema is confusing TS
@@ -1051,19 +1066,28 @@ export default class Form<
1051
1066
  * @param data - The data associated with the field that was blurred
1052
1067
  */
1053
1068
  onBlur = (id: string, data: any) => {
1054
- const { onBlur, omitExtraData, liveOmit, liveValidate } = this.props;
1069
+ const { onBlur, omitExtraData, liveOmit, liveValidate, removeEmptyOptionalObjects } = this.props;
1055
1070
  if (onBlur) {
1056
1071
  onBlur(id, data);
1057
1072
  }
1058
1073
  if ((omitExtraData === true && liveOmit === 'onBlur') || liveValidate === 'onBlur') {
1059
1074
  const { onChange, extraErrors } = this.props;
1060
- const { formData } = this.state;
1075
+ const { formData, schemaUtils, schema } = this.state;
1061
1076
  let newFormData: T | undefined = formData;
1062
1077
  let state: Partial<FormState<T, S, F>> = { formData: newFormData };
1063
1078
  if (omitExtraData === true && liveOmit === 'onBlur') {
1064
1079
  newFormData = this.omitExtraData(formData);
1065
1080
  state = { formData: newFormData };
1066
1081
  }
1082
+ if (removeEmptyOptionalObjects) {
1083
+ newFormData = removeOptionalEmptyObjects(
1084
+ schemaUtils.getValidator(),
1085
+ schema,
1086
+ schemaUtils.getRootSchema(),
1087
+ newFormData,
1088
+ ) as T;
1089
+ state = { ...state, formData: newFormData };
1090
+ }
1067
1091
  if (liveValidate === 'onBlur') {
1068
1092
  const { schema, schemaUtils, errorSchema, customErrors, retrievedSchema } = this.state;
1069
1093
  const liveValidation = this.liveValidate(
@@ -1121,13 +1145,23 @@ export default class Form<
1121
1145
  }
1122
1146
 
1123
1147
  event.persist();
1124
- const { omitExtraData, extraErrors, noValidate, onSubmit } = this.props;
1148
+ const { omitExtraData, extraErrors, noValidate, onSubmit, removeEmptyOptionalObjects } = this.props;
1125
1149
  let { formData: newFormData } = this.state;
1126
1150
 
1127
1151
  if (omitExtraData === true) {
1128
1152
  newFormData = this.omitExtraData(newFormData);
1129
1153
  }
1130
1154
 
1155
+ if (removeEmptyOptionalObjects) {
1156
+ const { schemaUtils, schema } = this.state;
1157
+ newFormData = removeOptionalEmptyObjects(
1158
+ schemaUtils.getValidator(),
1159
+ schema,
1160
+ schemaUtils.getRootSchema(),
1161
+ newFormData,
1162
+ ) as T;
1163
+ }
1164
+
1131
1165
  if (noValidate || this.validateFormWithFormData(newFormData)) {
1132
1166
  // There are no errors generated through schema validation.
1133
1167
  // Check for user provided errors and update state accordingly.
@@ -1234,8 +1268,9 @@ export default class Form<
1234
1268
  const elementId = path.join(idSeparator);
1235
1269
  let field = this.formElement.current.elements[elementId];
1236
1270
  if (!field) {
1237
- // if not an exact match, try finding an input starting with the element id (like radio buttons or checkboxes)
1238
- field = this.formElement.current.querySelector(`input[id^="${elementId}"`);
1271
+ // if not an exact match, try finding a focusable element starting with the element id (like radio buttons or checkboxes)
1272
+ // some themes (e.g. shadcn) use button elements instead of native inputs for radio groups
1273
+ field = this.formElement.current.querySelector(`input[id^="${elementId}"], button[id^="${elementId}"]`);
1239
1274
  }
1240
1275
  if (field && field.length) {
1241
1276
  // If we got a list with length > 0
@@ -1310,11 +1345,20 @@ export default class Form<
1310
1345
  * @returns - True if the form is valid, false otherwise.
1311
1346
  */
1312
1347
  validateForm() {
1313
- const { omitExtraData } = this.props;
1348
+ const { omitExtraData, removeEmptyOptionalObjects } = this.props;
1314
1349
  let { formData: newFormData } = this.state;
1315
1350
  if (omitExtraData === true) {
1316
1351
  newFormData = this.omitExtraData(newFormData);
1317
1352
  }
1353
+ if (removeEmptyOptionalObjects) {
1354
+ const { schemaUtils, schema } = this.state;
1355
+ newFormData = removeOptionalEmptyObjects(
1356
+ schemaUtils.getValidator(),
1357
+ schema,
1358
+ schemaUtils.getRootSchema(),
1359
+ newFormData,
1360
+ ) as T;
1361
+ }
1318
1362
  return this.validateFormWithFormData(newFormData);
1319
1363
  }
1320
1364
 
@@ -199,7 +199,9 @@ function ArrayAsMultiSelect<T = any, S extends StrictRJSFSchema = RJSFSchema, F
199
199
  } = props;
200
200
  const { widgets, schemaUtils, globalFormOptions, globalUiOptions } = registry;
201
201
  const itemsSchema = schemaUtils.retrieveSchema(schema.items as S, items);
202
- const itemsUiSchema = (uiSchema?.items ?? {}) as UiSchema<T[], S, F>;
202
+ // For computing `enumOptions`, fallback to the array property's uiSchema if there is no `items` schema
203
+ // Avoids a breaking change reported in https://github.com/rjsf-team/react-jsonschema-form/issues/4985
204
+ const itemsUiSchema = (uiSchema?.items ?? uiSchema) as UiSchema<T[], S, F>;
203
205
  const enumOptions = optionsList<T[], S, F>(itemsSchema, itemsUiSchema);
204
206
  const { widget = 'select', title: uiTitle, ...options } = getUiOptions<T[], S, F>(uiSchema, globalUiOptions);
205
207
  const Widget = getWidget<T[], S, F>(schema, widget, widgets);
@@ -356,7 +358,7 @@ function ArrayFieldItem<T = any, S extends StrictRJSFSchema = RJSFSchema, F exte
356
358
  hideError: boolean;
357
359
  registry: Registry<T[], S, F>;
358
360
  uiOptions: UIOptionsType<T[], S, F>;
359
- parentUiSchema?: UiSchema<T[], S, F>;
361
+ parentUiSchema: UiSchema<T[], S, F>;
360
362
  title: string | undefined;
361
363
  canAdd: boolean;
362
364
  canRemove?: boolean;
@@ -608,6 +610,7 @@ function NormalArray<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
608
610
  name: name && `${name}-${index}`,
609
611
  registry,
610
612
  uiOptions,
613
+ parentUiSchema: uiSchema,
611
614
  hideError,
612
615
  readonly,
613
616
  disabled,
@@ -752,6 +755,7 @@ function FixedArray<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
752
755
  name: name && `${name}-${index}`,
753
756
  registry,
754
757
  uiOptions,
758
+ parentUiSchema: uiSchema,
755
759
  hideError,
756
760
  readonly,
757
761
  disabled,
@@ -1,4 +1,4 @@
1
- import { FocusEvent, useCallback, useState } from 'react';
1
+ import { FocusEvent, useCallback, useRef, useState } from 'react';
2
2
  import {
3
3
  ADDITIONAL_PROPERTY_FLAG,
4
4
  ANY_OF_KEY,
@@ -220,11 +220,14 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
220
220
  } = props;
221
221
  const { fields, schemaUtils, translateString, globalUiOptions } = registry;
222
222
  const { OptionalDataControlsField } = fields;
223
+ const formDataRef = useRef(formData);
224
+ formDataRef.current = formData;
223
225
  const schema: S = schemaUtils.retrieveSchema(rawSchema, formData, true);
224
226
  const uiOptions = getUiOptions<T, S, F>(uiSchema, globalUiOptions);
225
227
  const { properties: schemaProperties = {} } = schema;
226
228
  // All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
227
229
  const childFieldPathId = props.childFieldPathId ?? fieldPathId;
230
+ const lastRenamedProperty = useRef({ previousKey: '', currentKey: undefined as string | undefined });
228
231
 
229
232
  const templateTitle = uiOptions.title ?? schema.title ?? title ?? name;
230
233
  const description = uiOptions.description ?? schema.description;
@@ -292,6 +295,10 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
292
295
  set(newFormData as GenericObjectType, newKey, newValue);
293
296
  }
294
297
 
298
+ if (lastRenamedProperty.current.previousKey === newKey) {
299
+ lastRenamedProperty.current.currentKey = newKey;
300
+ lastRenamedProperty.current.previousKey = getAvailableKey(newKey, newFormData);
301
+ }
295
302
  onChange(newFormData, childFieldPathId.path);
296
303
  }, [formData, onChange, registry, childFieldPathId, getAvailableKey, schema]);
297
304
 
@@ -305,9 +312,10 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
305
312
  const handleKeyRename = useCallback(
306
313
  (oldKey: string, newKey: string) => {
307
314
  if (oldKey !== newKey) {
308
- const actualNewKey = getAvailableKey(newKey, formData);
315
+ const currentFormData = formDataRef.current;
316
+ const actualNewKey = getAvailableKey(newKey, currentFormData);
309
317
  const newFormData: GenericObjectType = {
310
- ...(formData as GenericObjectType),
318
+ ...(currentFormData as GenericObjectType),
311
319
  };
312
320
  const newKeys: GenericObjectType = { [oldKey]: actualNewKey };
313
321
  const keyValues = Object.keys(newFormData).map((key) => {
@@ -316,10 +324,15 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
316
324
  });
317
325
  const renamedObj = Object.assign({}, ...keyValues);
318
326
 
327
+ formDataRef.current = renamedObj as T;
328
+ if (oldKey !== lastRenamedProperty.current.currentKey) {
329
+ lastRenamedProperty.current.previousKey = oldKey;
330
+ }
331
+ lastRenamedProperty.current.currentKey = actualNewKey;
319
332
  onChange(renamedObj, childFieldPathId.path);
320
333
  }
321
334
  },
322
- [formData, onChange, childFieldPathId, getAvailableKey],
335
+ [onChange, childFieldPathId, getAvailableKey],
323
336
  );
324
337
 
325
338
  /** Handles the remove click which calls the `onChange` callback with the special ADDITIONAL_PROPERTY_FIELD_REMOVE
@@ -332,6 +345,18 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
332
345
  [onChange, childFieldPathId],
333
346
  );
334
347
 
348
+ /** Returns the stable React key for a property. For the most recently renamed
349
+ * additional property, returns the previous key so that React reuses the
350
+ * existing component instance instead of unmounting/remounting it. This
351
+ * preserves DOM focus naturally without manual focus management.
352
+ */
353
+ const getStableKey = useCallback((property: string) => {
354
+ if (lastRenamedProperty.current.currentKey === property) {
355
+ return lastRenamedProperty.current.previousKey;
356
+ }
357
+ return property;
358
+ }, []);
359
+
335
360
  if (!renderOptionalField || hasFormData) {
336
361
  try {
337
362
  const properties = Object.keys(schemaProperties);
@@ -365,7 +390,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
365
390
  const hidden = getUiOptions<T, S, F>(fieldUiSchema).widget === 'hidden';
366
391
  const content = (
367
392
  <ObjectFieldProperty<T, S, F>
368
- key={name}
393
+ key={getStableKey(name)}
369
394
  propertyName={name}
370
395
  required={isRequired<S>(schema, name)}
371
396
  schema={get(schema, [PROPERTIES_KEY, name], {}) as S}
@@ -68,6 +68,7 @@ export default function WrapIfAdditionalTemplate<
68
68
  {displayLabel && <Label label={keyLabel} required={required} id={`${id}-key`} />}
69
69
  {displayLabel && rawDescription && <div>&nbsp;</div>}
70
70
  <input
71
+ key={label}
71
72
  className='form-control'
72
73
  type='text'
73
74
  id={`${id}-key`}
@@ -1,10 +1,12 @@
1
1
  import { ChangeEvent, FocusEvent, useCallback } from 'react';
2
2
  import {
3
3
  ariaDescribedByIds,
4
+ enumOptionValueDecoder,
5
+ enumOptionValueEncoder,
4
6
  enumOptionsDeselectValue,
5
7
  enumOptionsIsSelected,
6
8
  enumOptionsSelectValue,
7
- enumOptionsValueForIndex,
9
+ getOptionValueFormat,
8
10
  optionId,
9
11
  FormContextType,
10
12
  WidgetProps,
@@ -20,7 +22,7 @@ import {
20
22
  function CheckboxesWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>({
21
23
  id,
22
24
  disabled,
23
- options: { inline = false, enumOptions, enumDisabled, emptyValue },
25
+ options,
24
26
  value,
25
27
  autofocus = false,
26
28
  readonly,
@@ -29,19 +31,22 @@ function CheckboxesWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F ex
29
31
  onFocus,
30
32
  htmlName,
31
33
  }: WidgetProps<T, S, F>) {
34
+ const { inline = false, enumOptions, enumDisabled, emptyValue } = options;
35
+ const optionValueFormat = getOptionValueFormat(options);
32
36
  const checkboxesValues = Array.isArray(value) ? value : [value];
33
37
 
34
38
  const handleBlur = useCallback(
35
39
  ({ target }: FocusEvent<HTMLInputElement>) =>
36
- onBlur(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, emptyValue)),
37
- [onBlur, id, enumOptions, emptyValue],
40
+ onBlur(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, optionValueFormat, emptyValue)),
41
+ [onBlur, id, enumOptions, emptyValue, optionValueFormat],
38
42
  );
39
43
 
40
44
  const handleFocus = useCallback(
41
45
  ({ target }: FocusEvent<HTMLInputElement>) =>
42
- onFocus(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, emptyValue)),
43
- [onFocus, id, enumOptions, emptyValue],
46
+ onFocus(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, optionValueFormat, emptyValue)),
47
+ [onFocus, id, enumOptions, emptyValue, optionValueFormat],
44
48
  );
49
+
45
50
  return (
46
51
  <div className='checkboxes' id={id}>
47
52
  {Array.isArray(enumOptions) &&
@@ -65,7 +70,7 @@ function CheckboxesWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F ex
65
70
  id={optionId(id, index)}
66
71
  name={htmlName || id}
67
72
  checked={checked}
68
- value={String(index)}
73
+ value={enumOptionValueEncoder(option.value, index, optionValueFormat)}
69
74
  disabled={disabled || itemDisabled || readonly}
70
75
  autoFocus={autofocus && index === 0}
71
76
  onChange={handleChange}
@@ -1,8 +1,10 @@
1
1
  import { FocusEvent, useCallback } from 'react';
2
2
  import {
3
3
  ariaDescribedByIds,
4
+ enumOptionValueDecoder,
5
+ enumOptionValueEncoder,
4
6
  enumOptionsIsSelected,
5
- enumOptionsValueForIndex,
7
+ getOptionValueFormat,
6
8
  optionId,
7
9
  FormContextType,
8
10
  RJSFSchema,
@@ -29,17 +31,18 @@ function RadioWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
29
31
  htmlName,
30
32
  }: WidgetProps<T, S, F>) {
31
33
  const { enumOptions, enumDisabled, inline, emptyValue } = options;
34
+ const optionValueFormat = getOptionValueFormat(options);
32
35
 
33
36
  const handleBlur = useCallback(
34
37
  ({ target }: FocusEvent<HTMLInputElement>) =>
35
- onBlur(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, emptyValue)),
36
- [onBlur, enumOptions, emptyValue, id],
38
+ onBlur(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, optionValueFormat, emptyValue)),
39
+ [onBlur, enumOptions, emptyValue, id, optionValueFormat],
37
40
  );
38
41
 
39
42
  const handleFocus = useCallback(
40
43
  ({ target }: FocusEvent<HTMLInputElement>) =>
41
- onFocus(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, emptyValue)),
42
- [onFocus, enumOptions, emptyValue, id],
44
+ onFocus(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, optionValueFormat, emptyValue)),
45
+ [onFocus, enumOptions, emptyValue, id, optionValueFormat],
43
46
  );
44
47
 
45
48
  return (
@@ -60,7 +63,7 @@ function RadioWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
60
63
  checked={checked}
61
64
  name={htmlName || id}
62
65
  required={required}
63
- value={String(i)}
66
+ value={enumOptionValueEncoder(option.value, i, optionValueFormat)}
64
67
  disabled={disabled || itemDisabled || readonly}
65
68
  autoFocus={autofocus && i === 0}
66
69
  onChange={handleChange}
@@ -1,8 +1,10 @@
1
1
  import { ChangeEvent, FocusEvent, SyntheticEvent, useCallback } from 'react';
2
2
  import {
3
3
  ariaDescribedByIds,
4
- enumOptionsIndexForValue,
5
- enumOptionsValueForIndex,
4
+ enumOptionSelectedValue,
5
+ enumOptionValueDecoder,
6
+ enumOptionValueEncoder,
7
+ getOptionValueFormat,
6
8
  FormContextType,
7
9
  RJSFSchema,
8
10
  StrictRJSFSchema,
@@ -42,32 +44,33 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
42
44
  }: WidgetProps<T, S, F>) {
43
45
  const { enumOptions, enumDisabled, emptyValue: optEmptyVal } = options;
44
46
  const emptyValue = multiple ? [] : '';
47
+ const optionValueFormat = getOptionValueFormat(options);
45
48
 
46
49
  const handleFocus = useCallback(
47
50
  (event: FocusEvent<HTMLSelectElement>) => {
48
51
  const newValue = getValue(event, multiple);
49
- return onFocus(id, enumOptionsValueForIndex<S>(newValue, enumOptions, optEmptyVal));
52
+ return onFocus(id, enumOptionValueDecoder<S>(newValue, enumOptions, optionValueFormat, optEmptyVal));
50
53
  },
51
- [onFocus, id, multiple, enumOptions, optEmptyVal],
54
+ [onFocus, id, multiple, enumOptions, optEmptyVal, optionValueFormat],
52
55
  );
53
56
 
54
57
  const handleBlur = useCallback(
55
58
  (event: FocusEvent<HTMLSelectElement>) => {
56
59
  const newValue = getValue(event, multiple);
57
- return onBlur(id, enumOptionsValueForIndex<S>(newValue, enumOptions, optEmptyVal));
60
+ return onBlur(id, enumOptionValueDecoder<S>(newValue, enumOptions, optionValueFormat, optEmptyVal));
58
61
  },
59
- [onBlur, id, multiple, enumOptions, optEmptyVal],
62
+ [onBlur, id, multiple, enumOptions, optEmptyVal, optionValueFormat],
60
63
  );
61
64
 
62
65
  const handleChange = useCallback(
63
66
  (event: ChangeEvent<HTMLSelectElement>) => {
64
67
  const newValue = getValue(event, multiple);
65
- return onChange(enumOptionsValueForIndex<S>(newValue, enumOptions, optEmptyVal));
68
+ return onChange(enumOptionValueDecoder<S>(newValue, enumOptions, optionValueFormat, optEmptyVal));
66
69
  },
67
- [onChange, multiple, enumOptions, optEmptyVal],
70
+ [onChange, multiple, enumOptions, optEmptyVal, optionValueFormat],
68
71
  );
69
72
 
70
- const selectedIndexes = enumOptionsIndexForValue<S>(value, enumOptions, multiple);
73
+ const selectValue = enumOptionSelectedValue<S>(value, enumOptions, multiple, optionValueFormat, emptyValue);
71
74
  const showPlaceholderOption = !multiple && schema.default === undefined;
72
75
 
73
76
  return (
@@ -77,7 +80,7 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
77
80
  multiple={multiple}
78
81
  role='combobox'
79
82
  className='form-control'
80
- value={typeof selectedIndexes === 'undefined' ? emptyValue : selectedIndexes}
83
+ value={selectValue}
81
84
  required={required}
82
85
  disabled={disabled || readonly}
83
86
  autoFocus={autofocus}
@@ -91,7 +94,7 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
91
94
  enumOptions.map(({ value, label }, i) => {
92
95
  const disabled = enumDisabled && enumDisabled.indexOf(value) !== -1;
93
96
  return (
94
- <option key={i} value={String(i)} disabled={disabled}>
97
+ <option key={i} value={enumOptionValueEncoder(value, i, optionValueFormat)} disabled={disabled}>
95
98
  {label}
96
99
  </option>
97
100
  );