Skip to content

Commit

Permalink
Fix #4197 in various themes by showing empty option in SelectWidget w…
Browse files Browse the repository at this point in the history
…hen appropriate (#4200)
  • Loading branch information
nickgros authored Jul 8, 2024
1 parent 1338ded commit b56da64
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 36 deletions.
27 changes: 26 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,36 @@ should change the heading of the (upcoming) version to include a major version b
-->

# 5.19.2
# 5.19.3

## @rjsf/antd

- SelectWidget now displays an empty option when appropriate, fixing [#4197](https://github.com/rjsf-team/react-jsonschema-form/issues/4197)

## @rjsf/chakra-ui

- SelectWidget now displays an empty option when appropriate, fixing [#4197](https://github.com/rjsf-team/react-jsonschema-form/issues/4197)

## @rjsf/core

- Removed `.only` on tests that was accidentally added in `5.19.0`

## @rjsf/fluentui-rc

- SelectWidget now displays an empty option when appropriate, fixing [#4197](https://github.com/rjsf-team/react-jsonschema-form/issues/4197)

## @rjsf/material-ui

- SelectWidget now displays an empty option when appropriate, fixing [#4197](https://github.com/rjsf-team/react-jsonschema-form/issues/4197)

## @rjsf/mui

- SelectWidget now displays an empty option when appropriate, fixing [#4197](https://github.com/rjsf-team/react-jsonschema-form/issues/4197)

## @rjsf/semantic-ui

- SelectWidget now displays an empty option when appropriate, fixing [#4197](https://github.com/rjsf-team/react-jsonschema-form/issues/4197)

# 5.19.1

## Dev / docs / playground
Expand Down Expand Up @@ -92,6 +116,7 @@ should change the heading of the (upcoming) version to include a major version b
- Fix case where NumberField would not properly reset the field when using programmatic form reset (#4202)[https://github.com/rjsf-team/react-jsonschema-form/issues/4202]
- Updated widgets to handle undefined `target` in `onFocus` and `onBlur` handlers
- Fix field disable or readonly property can't cover globalOptions corresponding property (#4212)[https://github.com/rjsf-team/react-jsonschema-form/pull/4212]
- Added support for `default` values in `additionalProperties` in [#4199](https://github.com/rjsf-team/react-jsonschema-form/issues/4199), fixing [#3195](https://github.com/rjsf-team/react-jsonschema-form/issues/3915)

## @rjsf/fluent-ui

Expand Down
34 changes: 24 additions & 10 deletions packages/antd/src/widgets/SelectWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
WidgetProps,
} from '@rjsf/utils';
import isString from 'lodash/isString';
import { DefaultOptionType } from 'antd/es/select';
import { useMemo } from 'react';

const SELECT_STYLE = {
width: '100%',
Expand Down Expand Up @@ -37,6 +39,7 @@ export default function SelectWidget<
placeholder,
readonly,
value,
schema,
}: WidgetProps<T, S, F>) {
const { readonlyAsDisabled = true } = formContext as GenericObjectType;

Expand Down Expand Up @@ -65,6 +68,26 @@ export default function SelectWidget<
const extraProps = {
name: id,
};

const showPlaceholderOption = !multiple && schema.default === undefined;

const selectOptions: DefaultOptionType[] | undefined = useMemo(() => {
if (Array.isArray(enumOptions)) {
const options: DefaultOptionType[] = enumOptions.map(({ value: optionValue, label: optionLabel }, index) => ({
disabled: Array.isArray(enumDisabled) && enumDisabled.indexOf(optionValue) !== -1,
key: String(index),
value: String(index),
label: optionLabel,
}));

if (showPlaceholderOption) {
options.unshift({ value: '', label: placeholder || '' });
}
return options;
}
return undefined;
}, [enumDisabled, enumOptions, placeholder, showPlaceholderOption]);

return (
<Select
autoFocus={autofocus}
Expand All @@ -81,16 +104,7 @@ export default function SelectWidget<
{...extraProps}
filterOption={filterOption}
aria-describedby={ariaDescribedByIds<T>(id)}
options={
Array.isArray(enumOptions)
? enumOptions.map(({ value: optionValue, label: optionLabel }, index) => ({
disabled: Array.isArray(enumDisabled) && enumDisabled.indexOf(optionValue) !== -1,
key: String(index),
value: String(index),
label: optionLabel,
}))
: undefined
}
options={selectOptions}
/>
);
}
3 changes: 2 additions & 1 deletion packages/bootstrap-4/src/SelectWidget/SelectWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default function SelectWidget<
}
}
const selectedIndexes = enumOptionsIndexForValue<S>(value, enumOptions, multiple);
const showPlaceholderOption = !multiple && schema.default === undefined;

return (
<Form.Control
Expand Down Expand Up @@ -78,7 +79,7 @@ export default function SelectWidget<
}}
aria-describedby={ariaDescribedByIds<T>(id)}
>
{!multiple && schema.default === undefined && <option value=''>{placeholder}</option>}
{showPlaceholderOption && <option value=''>{placeholder}</option>}
{(enumOptions as any).map(({ value, label }: any, i: number) => {
const disabled: any = Array.isArray(enumDisabled) && (enumDisabled as any).indexOf(value) != -1;
return (
Expand Down
30 changes: 21 additions & 9 deletions packages/chakra-ui/src/SelectWidget/SelectWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FocusEvent } from 'react';
import { FocusEvent, useMemo } from 'react';
import { FormControl, FormLabel } from '@chakra-ui/react';
import {
ariaDescribedByIds,
Expand Down Expand Up @@ -34,6 +34,7 @@ export default function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFS
onFocus,
rawErrors = [],
uiSchema,
schema,
} = props;
const { enumOptions, enumDisabled, emptyValue } = options;
const chakraProps = getChakra({ uiSchema });
Expand All @@ -60,30 +61,41 @@ export default function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFS
const _onFocus = ({ target }: FocusEvent<HTMLInputElement>) =>
onFocus(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, emptyValue));

const _valueLabelMap: any = {};
const displayEnumOptions: OptionsOrGroups<any, any> = Array.isArray(enumOptions)
? enumOptions.map((option: EnumOptionsType<S>, index: number) => {
const showPlaceholderOption = !multiple && schema.default === undefined;
const { valueLabelMap, displayEnumOptions } = useMemo((): {
valueLabelMap: Record<string | number, string>;
displayEnumOptions: OptionsOrGroups<any, any>;
} => {
const valueLabelMap: Record<string | number, string> = {};
let displayEnumOptions: OptionsOrGroups<any, any> = [];
if (Array.isArray(enumOptions)) {
displayEnumOptions = enumOptions.map((option: EnumOptionsType<S>, index: number) => {
const { value, label } = option;
_valueLabelMap[index] = label || String(value);
valueLabelMap[index] = label || String(value);
return {
label,
value: String(index),
isDisabled: Array.isArray(enumDisabled) && enumDisabled.indexOf(value) !== -1,
};
})
: [];
});
if (showPlaceholderOption) {
(displayEnumOptions as any[]).unshift({ value: '', label: placeholder || '' });
}
}
return { valueLabelMap: valueLabelMap, displayEnumOptions: displayEnumOptions };
}, [enumDisabled, enumOptions, placeholder, showPlaceholderOption]);

const isMultiple = typeof multiple !== 'undefined' && multiple !== false && Boolean(enumOptions);
const selectedIndex = enumOptionsIndexForValue<S>(value, enumOptions, isMultiple);
const formValue: any = isMultiple
? ((selectedIndex as string[]) || []).map((i: string) => {
return {
label: _valueLabelMap[i],
label: valueLabelMap[i],
value: i,
};
})
: {
label: _valueLabelMap[selectedIndex as string] || '',
label: valueLabelMap[selectedIndex as string] || '',
selectedIndex,
};

Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/components/widgets/SelectWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,27 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
const newValue = getValue(event, multiple);
return onFocus(id, enumOptionsValueForIndex<S>(newValue, enumOptions, optEmptyVal));
},
[onFocus, id, schema, multiple, options]
[onFocus, id, schema, multiple, enumOptions, optEmptyVal]
);

const handleBlur = useCallback(
(event: FocusEvent<HTMLSelectElement>) => {
const newValue = getValue(event, multiple);
return onBlur(id, enumOptionsValueForIndex<S>(newValue, enumOptions, optEmptyVal));
},
[onBlur, id, schema, multiple, options]
[onBlur, id, schema, multiple, enumOptions, optEmptyVal]
);

const handleChange = useCallback(
(event: ChangeEvent<HTMLSelectElement>) => {
const newValue = getValue(event, multiple);
return onChange(enumOptionsValueForIndex<S>(newValue, enumOptions, optEmptyVal));
},
[onChange, schema, multiple, options]
[onChange, schema, multiple, enumOptions, optEmptyVal]
);

const selectedIndexes = enumOptionsIndexForValue<S>(value, enumOptions, multiple);
const showPlaceholderOption = !multiple && schema.default === undefined;

return (
<select
Expand All @@ -83,7 +84,7 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
onChange={handleChange}
aria-describedby={ariaDescribedByIds<T>(id)}
>
{!multiple && schema.default === undefined && <option value=''>{placeholder}</option>}
{showPlaceholderOption && <option value=''>{placeholder}</option>}
{Array.isArray(enumOptions) &&
enumOptions.map(({ value, label }, i) => {
const disabled = enumDisabled && enumDisabled.indexOf(value) !== -1;
Expand Down
4 changes: 4 additions & 0 deletions packages/fluentui-rc/src/SelectWidget/SelectWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
onChange,
onBlur,
onFocus,
schema,
placeholder,
}: WidgetProps<T, S, F>) {
const { enumOptions, enumDisabled, emptyValue: optEmptyVal } = options;

Expand All @@ -60,6 +62,7 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
const newValue = getValue(data, multiple);
return onChange(enumOptionsValueForIndex<S>(newValue, enumOptions, optEmptyVal));
};
const showPlaceholderOption = !multiple && schema.default === undefined;

return (
<Field
Expand All @@ -81,6 +84,7 @@ function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extend
selectedOptions={selectedIndexesAsArray}
aria-describedby={ariaDescribedByIds<T>(id)}
>
{showPlaceholderOption && <Option value=''>{placeholder || ''}</Option>}
{Array.isArray(enumOptions) &&
enumOptions.map(({ value, label }, i) => {
const disabled = enumDisabled && enumDisabled.indexOf(value) !== -1;
Expand Down
2 changes: 2 additions & 0 deletions packages/material-ui/src/SelectWidget/SelectWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default function SelectWidget<
const _onFocus = ({ target }: FocusEvent<HTMLInputElement>) =>
onFocus(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, optEmptyVal));
const selectedIndexes = enumOptionsIndexForValue<S>(value, enumOptions, multiple);
const showPlaceholderOption = !multiple && schema.default === undefined;

return (
<TextField
Expand Down Expand Up @@ -86,6 +87,7 @@ export default function SelectWidget<
}}
aria-describedby={ariaDescribedByIds<T>(id)}
>
{showPlaceholderOption && <MenuItem value=''>{placeholder}</MenuItem>}
{Array.isArray(enumOptions) &&
enumOptions.map(({ value, label }, i: number) => {
const disabled: boolean = Array.isArray(enumDisabled) && enumDisabled.indexOf(value) !== -1;
Expand Down
2 changes: 2 additions & 0 deletions packages/mui/src/SelectWidget/SelectWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default function SelectWidget<
onFocus(id, enumOptionsValueForIndex<S>(target && target.value, enumOptions, optEmptyVal));
const selectedIndexes = enumOptionsIndexForValue<S>(value, enumOptions, multiple);
const { InputLabelProps, SelectProps, autocomplete, ...textFieldRemainingProps } = textFieldProps;
const showPlaceholderOption = !multiple && schema.default === undefined;

return (
<TextField
Expand Down Expand Up @@ -89,6 +90,7 @@ export default function SelectWidget<
}}
aria-describedby={ariaDescribedByIds<T>(id)}
>
{showPlaceholderOption && <MenuItem value=''>{placeholder}</MenuItem>}
{Array.isArray(enumOptions) &&
enumOptions.map(({ value, label }, i: number) => {
const disabled: boolean = Array.isArray(enumDisabled) && enumDisabled.indexOf(value) !== -1;
Expand Down
24 changes: 19 additions & 5 deletions packages/semantic-ui/src/SelectWidget/SelectWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,33 @@ import {
UIOptionsType,
} from '@rjsf/utils';
import map from 'lodash/map';
import { Form, DropdownProps } from 'semantic-ui-react';
import { Form, DropdownProps, DropdownItemProps } from 'semantic-ui-react';
import { getSemanticProps } from '../util';

/**
* Returns and creates an array format required for semantic drop down
* @param {array} enumOptions- array of items for the dropdown
* @param {array} enumOptions - array of items for the dropdown
* @param {array} enumDisabled - array of enum option values to disable
* @param {boolean} showPlaceholderOption - whether to show a placeholder option
* @param {string} placeholder - placeholder option label
* @returns {*}
*/
function createDefaultValueOptionsForDropDown<S extends StrictRJSFSchema = RJSFSchema>(
enumOptions?: EnumOptionsType<S>[],
enumDisabled?: UIOptionsType['enumDisabled']
enumDisabled?: UIOptionsType['enumDisabled'],
showPlaceholderOption?: boolean,
placeholder?: string
) {
const disabledOptions = enumDisabled || [];
const options = map(enumOptions, ({ label, value }, index) => ({
const options: DropdownItemProps[] = map(enumOptions, ({ label, value }, index) => ({
disabled: disabledOptions.indexOf(value) !== -1,
key: label,
text: label,
value: String(index),
}));
if (showPlaceholderOption) {
options.unshift({ value: '', text: placeholder || '' });
}
return options;
}

Expand Down Expand Up @@ -61,6 +68,7 @@ export default function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFS
onBlur,
onFocus,
rawErrors = [],
schema,
} = props;
const semanticProps = getSemanticProps<T, S, F>({
uiSchema,
Expand All @@ -76,7 +84,13 @@ export default function SelectWidget<T = any, S extends StrictRJSFSchema = RJSFS
});
const { enumDisabled, enumOptions, emptyValue: optEmptyVal } = options;
const emptyValue = multiple ? [] : '';
const dropdownOptions = createDefaultValueOptionsForDropDown<S>(enumOptions, enumDisabled);
const showPlaceholderOption = !multiple && schema.default === undefined;
const dropdownOptions = createDefaultValueOptionsForDropDown<S>(
enumOptions,
enumDisabled,
showPlaceholderOption,
placeholder
);
const _onChange = (_: SyntheticEvent<HTMLElement>, { value }: DropdownProps) =>
onChange(enumOptionsValueForIndex<S>(value as string[], enumOptions, optEmptyVal));
// eslint-disable-next-line no-shadow
Expand Down
Loading

0 comments on commit b56da64

Please sign in to comment.