Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #4197 in various themes by showing empty option in SelectWidget when appropriate #4200

Merged
merged 6 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,34 @@ should change the heading of the (upcoming) version to include a major version b

# 5.19.2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've released 5.19.2 so you'll have to bump this

nickgros marked this conversation as resolved.
Show resolved Hide resolved

## @rjsf/antd

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

## @rjsf/chakra-ui

- SelectWidget now displays an empty option when appropriate, fixing [#4197](https:/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:/rjsf-team/react-jsonschema-form/issues/4197)

## @rjsf/material-ui

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

## @rjsf/mui

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

## @rjsf/semantic-ui

- SelectWidget now displays an empty option when appropriate, fixing [#4197](https:/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:/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:/rjsf-team/react-jsonschema-form/pull/4212]
- Added support for `default` values in `additionalProperties` in [#4199](https:/rjsf-team/react-jsonschema-form/issues/4199), fixing [#3195](https:/rjsf-team/react-jsonschema-form/issues/3915)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did this come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think when I picked this up, we decided I would add this missing changelog message, assuming this would be a faster change to implement. I didn't realize it was missing until I revisited this.


## @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