Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
15 changes: 8 additions & 7 deletions docs/examples/optionFilterProp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,33 @@ import Select, { Option } from '@rc-component/select';
import '../../assets/index.less';

const Test = () => {
const [value, setValue] = React.useState<string>('');
const [value, setValue] = React.useState<string>('张三');

return (
<div>
<h2>Select optionFilterProp</h2>
<div style={{ width: 300 }}>
<Select
defaultValue="张三"
style={{ width: 500 }}
showSearch={{ optionFilterProp: ['value', 'pinyin'] }}
placeholder="placeholder"
optionFilterProp="desc"
onChange={(val: string) => {
setValue(val);
}}
value={value}
showSearch
>
<Option value="张三" desc="张三 zhang san">
<Option value="张三" pinyin="zhangsan">
张三
</Option>
<Option value="李四" desc="李四 li si">
<Option value="李四" pinyin="lisi">
李四
</Option>
<Option value="王五" desc="王五 wang wu">
<Option value="王五" pinyin="wangwu">
王五
</Option>
<Option value="lisa" pinyin="lisa">
lisa
</Option>
</Select>
</div>
{value}
Expand Down
29 changes: 21 additions & 8 deletions src/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,12 @@ export interface SearchConfig<OptionType> {
onSearch?: (value: string) => void;
filterOption?: boolean | FilterFunc<OptionType>;
filterSort?: (optionA: OptionType, optionB: OptionType, info: { searchValue: string }) => number;
optionFilterProp?: string;
optionFilterProp?: string | string[];
}
export interface SelectProps<ValueType = any, OptionType extends BaseOptionType = DefaultOptionType>
extends Omit<BaseSelectPropsWithoutPrivate, 'showSearch'> {
export interface SelectProps<
ValueType = any,
OptionType extends BaseOptionType = DefaultOptionType,
> extends Omit<BaseSelectPropsWithoutPrivate, 'showSearch'> {
prefixCls?: string;
id?: string;

Expand Down Expand Up @@ -152,7 +154,7 @@ export interface SelectProps<ValueType = any, OptionType extends BaseOptionType
/** @deprecated please use showSearch.filterSort */
filterSort?: SearchConfig<OptionType>['filterSort'];
/** @deprecated please use showSearch.optionFilterProp */
optionFilterProp?: string;
optionFilterProp?: string | string[];
optionLabelProp?: string;

children?: React.ReactNode;
Expand Down Expand Up @@ -248,6 +250,11 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
autoClearSearchValue = true,
} = searchConfig;

const normalizedOptionFilterProp = React.useMemo(() => {
if (!optionFilterProp) return [];
return Array.isArray(optionFilterProp) ? optionFilterProp : [optionFilterProp];
}, [optionFilterProp]);

const mergedId = useId(id);
const multiple = isMultiple(mode);
const childrenAsData = !!(!options && children);
Expand Down Expand Up @@ -280,7 +287,7 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
options,
children,
mergedFieldNames,
optionFilterProp,
normalizedOptionFilterProp,
optionLabelProp,
);
const { valueOptions, labelOptions, options: mergedOptions } = parsedOptions;
Expand Down Expand Up @@ -432,15 +439,21 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
mergedFieldNames,
mergedSearchValue,
mergedFilterOption,
optionFilterProp,
normalizedOptionFilterProp,
);

// Fill options with search value if needed
const filledSearchOptions = React.useMemo(() => {
const hasItemMatchingSearch = (item: DefaultOptionType) => {
if (normalizedOptionFilterProp.length) {
return normalizedOptionFilterProp.some((prop) => item?.[prop] === mergedSearchValue);
}
return item?.value === mergedSearchValue;
};
if (
mode !== 'tags' ||
!mergedSearchValue ||
filteredOptions.some((item) => item[optionFilterProp || 'value'] === mergedSearchValue)
filteredOptions.some((item) => hasItemMatchingSearch(item))
) {
return filteredOptions;
}
Expand All @@ -452,7 +465,7 @@ const Select = React.forwardRef<BaseSelectRef, SelectProps<any, DefaultOptionTyp
return [createTagOption(mergedSearchValue), ...filteredOptions];
}, [
createTagOption,
optionFilterProp,
normalizedOptionFilterProp,
mode,
filteredOptions,
mergedSearchValue,
Expand Down
11 changes: 6 additions & 5 deletions src/hooks/useFilterOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export default (
fieldNames: FieldNames,
searchValue?: string,
filterOption?: SelectProps['filterOption'],
optionFilterProp?: string,
) =>
React.useMemo(() => {
optionFilterProp?: string[],
) => {
return React.useMemo(() => {
if (!searchValue || filterOption === false) {
return options;
}
Expand All @@ -29,8 +29,8 @@ export default (
? filterOption
: (_: string, option: DefaultOptionType) => {
// Use provided `optionFilterProp`
if (optionFilterProp) {
return includes(option[optionFilterProp], upperSearch);
if (optionFilterProp && optionFilterProp.length) {
return optionFilterProp.some((prop) => includes(option[prop], upperSearch));
}

// Auto select `label` or `value` by option type
Expand Down Expand Up @@ -76,3 +76,4 @@ export default (

return filteredOptions;
}, [options, filterOption, optionFilterProp, searchValue, fieldNames]);
};
6 changes: 4 additions & 2 deletions src/hooks/useOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const useOptions = <OptionType>(
options: OptionType[],
children: React.ReactNode,
fieldNames: FieldNames,
optionFilterProp: string,
optionFilterProp: string[],
optionLabelProp: string,
) => {
return React.useMemo(() => {
Expand Down Expand Up @@ -42,7 +42,9 @@ const useOptions = <OptionType>(
valueOptions.set(option[fieldNames.value], option);
setLabelOptions(labelOptions, option, fieldNames.label);
// https://github.com/ant-design/ant-design/issues/35304
setLabelOptions(labelOptions, option, optionFilterProp);
optionFilterProp.forEach((prop) => {
setLabelOptions(labelOptions, option, prop);
});
setLabelOptions(labelOptions, option, optionLabelProp);
} else {
dig(option[fieldNames.options], true);
Expand Down
42 changes: 42 additions & 0 deletions tests/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,48 @@ describe('Select.Basic', () => {
expect(container.querySelector('.rc-select-item-option-content').textContent).toBe('2');
});

it('supports optionFilterProp as array for multiple field searching (value + pinyin)', () => {
const { container } = render(
<Select showSearch={{ optionFilterProp: ['value', 'pinyin'] }} style={{ width: 300 }}>
<Option value="张三" pinyin="zhangsan">
张三
</Option>
<Option value="李四" pinyin="lisi">
李四
</Option>
<Option value="王五" pinyin="wangwu">
王五
</Option>
<Option value="lisa" pinyin="lisa">
lisa
</Option>
</Select>,
);

const input = container.querySelector('input');

fireEvent.change(input, { target: { value: 'zhang' } });

let items = container.querySelectorAll('.rc-select-item-option-content');
expect(items).toHaveLength(1);
expect(items[0].textContent).toBe('张三');

fireEvent.change(input, { target: { value: '王' } });

items = container.querySelectorAll('.rc-select-item-option-content');
expect(items).toHaveLength(1);
expect(items[0].textContent).toBe('王五');

fireEvent.change(input, { target: { value: 'li' } });

items = container.querySelectorAll('.rc-select-item-option-content');
const texts = Array.from(items).map((item) => item.textContent);

expect(items).toHaveLength(2);
expect(texts).toContain('李四');
expect(texts).toContain('lisa');
});

it('filter array children', () => {
const { container } = render(
<Select optionFilterProp="children" showSearch>
Expand Down
Loading