Skip to content

Commit 8a331f9

Browse files
committed
feat(widgets): add opt-in useRealOptionValues for native form submission
Add enumOptionValueEncoder/Decoder utilities to @rjsf/utils that allow rendering real enum values in option attributes instead of array indices. Controlled via ui:globalOptions or per-field ui:options. When enabled, primitives use String(value) for DOM attributes with reverse lookup decoding. Objects/arrays fall back to index encoding. Default behavior (index-based) is fully preserved. Applied across SelectWidget, RadioWidget, and CheckboxesWidget in all 10 theme packages.
1 parent 557d562 commit 8a331f9

59 files changed

Lines changed: 1839 additions & 2825 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/chakra-ui/src/CheckboxesWidget/CheckboxesWidget.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { CheckboxGroup, FieldsetRoot, Stack, Text, FieldsetLegend } from '@chakra-ui/react';
22
import {
33
ariaDescribedByIds,
4+
enumOptionValueDecoder,
5+
enumOptionValueEncoder,
46
enumOptionsIndexForValue,
5-
enumOptionsValueForIndex,
67
FormContextType,
78
optionId,
89
RJSFSchema,
@@ -37,11 +38,12 @@ export default function CheckboxesWidget<
3738
uiSchema,
3839
} = props;
3940
const { enumOptions, enumDisabled, emptyValue } = options;
41+
const useRealValues = !!options.useRealOptionValues;
4042

4143
const _onBlur = ({ target }: FocusEvent<HTMLInputElement | any>) =>
42-
onBlur(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, emptyValue));
44+
onBlur(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, useRealValues, emptyValue));
4345
const _onFocus = ({ target }: FocusEvent<HTMLInputElement | any>) =>
44-
onFocus(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, emptyValue));
46+
onFocus(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, useRealValues, emptyValue));
4547

4648
const row = options ? options.inline : false;
4749
const selectedIndexes = enumOptionsIndexForValue<S>(value, enumOptions, true) as string[];
@@ -57,8 +59,8 @@ export default function CheckboxesWidget<
5759
>
5860
{!hideLabel && label && <FieldsetLegend>{labelValue(label)}</FieldsetLegend>}
5961
<CheckboxGroup
60-
onValueChange={(option) => onChange(enumOptionsValueForIndex<S>(option, enumOptions, emptyValue))}
61-
value={selectedIndexes}
62+
onValueChange={(option) => onChange(enumOptionValueDecoder<S>(option, enumOptions, useRealValues, emptyValue))}
63+
value={useRealValues ? (Array.isArray(value) ? value.map(String) : []) : selectedIndexes}
6264
aria-describedby={ariaDescribedByIds(id)}
6365
readOnly={readonly}
6466
invalid={required && value.length === 0}
@@ -72,7 +74,7 @@ export default function CheckboxesWidget<
7274
key={index}
7375
id={optionId(id, index)}
7476
name={htmlName || id}
75-
value={String(index)}
77+
value={enumOptionValueEncoder(option.value, index, useRealValues)}
7678
disabled={disabled || itemDisabled || readonly}
7779
onBlur={_onBlur}
7880
onFocus={_onFocus}

packages/chakra-ui/src/RadioWidget/RadioWidget.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { ChangeEvent, FocusEvent } from 'react';
22
import { Stack } from '@chakra-ui/react';
33
import {
44
ariaDescribedByIds,
5+
enumOptionValueDecoder,
6+
enumOptionValueEncoder,
57
enumOptionsIndexForValue,
6-
enumOptionsValueForIndex,
78
labelValue,
89
optionId,
910
FormContextType,
@@ -32,13 +33,14 @@ export default function RadioWidget<T = any, S extends StrictRJSFSchema = RJSFSc
3233
uiSchema,
3334
}: WidgetProps<T, S, F>) {
3435
const { enumOptions, enumDisabled, emptyValue } = options;
36+
const useRealValues = !!options.useRealOptionValues;
3537

3638
const _onChange = ({ target: { value } }: ChangeEvent<HTMLInputElement>) =>
37-
onChange(enumOptionsValueForIndex<S>(value, enumOptions, emptyValue));
39+
onChange(enumOptionValueDecoder<S>(value, enumOptions, useRealValues, emptyValue));
3840
const _onBlur = ({ target: { value } }: FocusEvent<HTMLInputElement>) =>
39-
onBlur(id, enumOptionsValueForIndex<S>(value, enumOptions, emptyValue));
41+
onBlur(id, enumOptionValueDecoder<S>(value, enumOptions, useRealValues, emptyValue));
4042
const _onFocus = ({ target: { value } }: FocusEvent<HTMLInputElement>) =>
41-
onFocus(id, enumOptionsValueForIndex<S>(value, enumOptions, emptyValue));
43+
onFocus(id, enumOptionValueDecoder<S>(value, enumOptions, useRealValues, emptyValue));
4244

4345
const row = options ? options.inline : false;
4446
const selectedIndex = (enumOptionsIndexForValue<S>(value, enumOptions) as string) ?? null;
@@ -58,7 +60,7 @@ export default function RadioWidget<T = any, S extends StrictRJSFSchema = RJSFSc
5860
onChange={_onChange}
5961
onBlur={_onBlur}
6062
onFocus={_onFocus}
61-
value={selectedIndex}
63+
value={useRealValues ? (value != null ? String(value) : null) : selectedIndex}
6264
name={htmlName || id}
6365
aria-describedby={ariaDescribedByIds(id)}
6466
>
@@ -69,7 +71,7 @@ export default function RadioWidget<T = any, S extends StrictRJSFSchema = RJSFSc
6971

7072
return (
7173
<Radio
72-
value={String(index)}
74+
value={enumOptionValueEncoder(option.value, index, useRealValues)}
7375
key={index}
7476
id={optionId(id, index)}
7577
disabled={disabled || itemDisabled || readonly}

packages/chakra-ui/src/SelectWidget/SelectWidget.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { FocusEvent, useMemo, useRef } from 'react';
33
import {
44
ariaDescribedByIds,
55
EnumOptionsType,
6+
enumOptionValueDecoder,
7+
enumOptionValueEncoder,
68
enumOptionsIndexForValue,
7-
enumOptionsValueForIndex,
89
labelValue,
910
FormContextType,
1011
RJSFSchema,
@@ -42,21 +43,22 @@ export default function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFS
4243
uiSchema,
4344
} = props;
4445
const { enumOptions, enumDisabled, emptyValue } = options;
46+
const useRealValues = !!options.useRealOptionValues;
4547

4648
const _onMultiChange = ({ value }: SelectValueChangeDetails) => {
47-
return onChange(enumOptionsValueForIndex<S>(value, enumOptions, emptyValue));
49+
return onChange(enumOptionValueDecoder<S>(value, enumOptions, useRealValues, emptyValue));
4850
};
4951

5052
const _onSingleChange = ({ value }: SelectValueChangeDetails) => {
51-
const selected = enumOptionsValueForIndex<S>(value, enumOptions, emptyValue);
53+
const selected = enumOptionValueDecoder<S>(value, enumOptions, useRealValues, emptyValue);
5254
return onChange(Array.isArray(selected) && selected.length === 1 ? selected[0] : selected);
5355
};
5456

5557
const _onBlur = ({ target }: FocusEvent<HTMLInputElement>) =>
56-
onBlur(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, emptyValue));
58+
onBlur(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, useRealValues, emptyValue));
5759

5860
const _onFocus = ({ target }: FocusEvent<HTMLInputElement>) =>
59-
onFocus(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, emptyValue));
61+
onFocus(id, enumOptionValueDecoder<S>(target && target.value, enumOptions, useRealValues, emptyValue));
6062

6163
const showPlaceholderOption = !multiple && schema.default === undefined;
6264
const { valueLabelMap, displayEnumOptions } = useMemo((): {
@@ -71,7 +73,7 @@ export default function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFS
7173
valueLabelMap[index] = label || String(value);
7274
return {
7375
label,
74-
value: String(index),
76+
value: enumOptionValueEncoder(value, index, useRealValues),
7577
disabled: Array.isArray(enumDisabled) && enumDisabled.indexOf(value) !== -1,
7678
};
7779
});
@@ -80,7 +82,7 @@ export default function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFS
8082
}
8183
}
8284
return { valueLabelMap: valueLabelMap, displayEnumOptions: displayEnumOptions };
83-
}, [enumDisabled, enumOptions, placeholder, showPlaceholderOption]);
85+
}, [enumDisabled, enumOptions, placeholder, showPlaceholderOption, useRealValues]);
8486

8587
const isMultiple = typeof multiple !== 'undefined' && multiple !== false && Boolean(enumOptions);
8688
const selectedIndex = enumOptionsIndexForValue<S>(value, enumOptions, isMultiple);
@@ -103,7 +105,19 @@ export default function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFS
103105
]
104106
: [];
105107

106-
const formValue = (isMultiple ? getMultiValue() : getSingleValue()).map((item) => item.value);
108+
let formValue: string[];
109+
if (useRealValues) {
110+
const emptyVal = isMultiple ? [] : '';
111+
const isEmpty =
112+
typeof value === 'undefined' || (isMultiple && value.length < 1) || (!isMultiple && value === emptyVal);
113+
if (isEmpty) {
114+
formValue = isMultiple ? [] : [''];
115+
} else {
116+
formValue = isMultiple ? value.map(String) : [String(value)];
117+
}
118+
} else {
119+
formValue = (isMultiple ? getMultiValue() : getSingleValue()).map((item) => item.value);
120+
}
107121

108122
const selectOptions = createListCollection({
109123
items: displayEnumOptions.filter((item) => item.value),

packages/chakra-ui/test/__snapshots__/Array.test.tsx.snap

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,8 +1225,7 @@ exports[`array fields checkboxes 1`] = `
12251225
-webkit-flex-direction: column;
12261226
-ms-flex-direction: column;
12271227
flex-direction: column;
1228-
--select-z-index: var(--chakra-z-index-popover);
1229-
z-index: calc(var(--select-z-index) + var(--layer-index, 0));
1228+
z-index: var(--chakra-z-index-dropdown);
12301229
border-radius: var(--chakra-radii-l2);
12311230
outline: 0;
12321231
max-height: var(--chakra-sizes-96);
@@ -2388,7 +2387,6 @@ exports[`array fields has errors 1`] = `
23882387
--chakra-colors-color-palette-emphasized: var(--chakra-colors-red-emphasized);
23892388
--chakra-colors-color-palette-solid: var(--chakra-colors-red-solid);
23902389
--chakra-colors-color-palette-focus-ring: var(--chakra-colors-red-focus-ring);
2391-
--chakra-colors-color-palette-border: var(--chakra-colors-red-border);
23922390
background: var(--chakra-colors-color-palette-subtle);
23932391
--bg-currentcolor: var(--chakra-colors-color-palette-subtle);
23942392
color: var(--chakra-colors-color-palette-fg);
@@ -5032,8 +5030,7 @@ exports[`nameGenerator bracketNameGenerator checkboxes with nameGenerator 1`] =
50325030
-webkit-flex-direction: column;
50335031
-ms-flex-direction: column;
50345032
flex-direction: column;
5035-
--select-z-index: var(--chakra-z-index-popover);
5036-
z-index: calc(var(--select-z-index) + var(--layer-index, 0));
5033+
z-index: var(--chakra-z-index-dropdown);
50375034
border-radius: var(--chakra-radii-l2);
50385035
outline: 0;
50395036
max-height: var(--chakra-sizes-96);
@@ -5706,7 +5703,6 @@ exports[`nameGenerator bracketNameGenerator fixed array 1`] = `
57065703
--chakra-colors-color-palette-emphasized: var(--chakra-colors-red-emphasized);
57075704
--chakra-colors-color-palette-solid: var(--chakra-colors-red-solid);
57085705
--chakra-colors-color-palette-focus-ring: var(--chakra-colors-red-focus-ring);
5709-
--chakra-colors-color-palette-border: var(--chakra-colors-red-border);
57105706
border-color: var(--chakra-colors-border-error);
57115707
}
57125708

@@ -10433,8 +10429,7 @@ exports[`with title and description checkboxes 1`] = `
1043310429
-webkit-flex-direction: column;
1043410430
-ms-flex-direction: column;
1043510431
flex-direction: column;
10436-
--select-z-index: var(--chakra-z-index-popover);
10437-
z-index: calc(var(--select-z-index) + var(--layer-index, 0));
10432+
z-index: var(--chakra-z-index-dropdown);
1043810433
border-radius: var(--chakra-radii-l2);
1043910434
outline: 0;
1044010435
max-height: var(--chakra-sizes-96);
@@ -12909,8 +12904,7 @@ exports[`with title and description from both checkboxes 1`] = `
1290912904
-webkit-flex-direction: column;
1291012905
-ms-flex-direction: column;
1291112906
flex-direction: column;
12912-
--select-z-index: var(--chakra-z-index-popover);
12913-
z-index: calc(var(--select-z-index) + var(--layer-index, 0));
12907+
z-index: var(--chakra-z-index-dropdown);
1291412908
border-radius: var(--chakra-radii-l2);
1291512909
outline: 0;
1291612910
max-height: var(--chakra-sizes-96);
@@ -15385,8 +15379,7 @@ exports[`with title and description from uiSchema checkboxes 1`] = `
1538515379
-webkit-flex-direction: column;
1538615380
-ms-flex-direction: column;
1538715381
flex-direction: column;
15388-
--select-z-index: var(--chakra-z-index-popover);
15389-
z-index: calc(var(--select-z-index) + var(--layer-index, 0));
15382+
z-index: var(--chakra-z-index-dropdown);
1539015383
border-radius: var(--chakra-radii-l2);
1539115384
outline: 0;
1539215385
max-height: var(--chakra-sizes-96);
@@ -17608,8 +17601,7 @@ exports[`with title and description with global label off checkboxes 1`] = `
1760817601
-webkit-flex-direction: column;
1760917602
-ms-flex-direction: column;
1761017603
flex-direction: column;
17611-
--select-z-index: var(--chakra-z-index-popover);
17612-
z-index: calc(var(--select-z-index) + var(--layer-index, 0));
17604+
z-index: var(--chakra-z-index-dropdown);
1761317605
border-radius: var(--chakra-radii-l2);
1761417606
outline: 0;
1761517607
max-height: var(--chakra-sizes-96);

0 commit comments

Comments
 (0)