Skip to content

Commit a10f879

Browse files
EmilyyyLiu刘欢
andauthored
feat: Merge searchValue,autoClearSearchValue and onSearch into the showSearch field (#593)
* feat: Merge searchValue and onSearch into the showSearch field * feat: change ts( ShowSearchType=>SearchConfig) and combine autoClearSearchValue * feat: combine autoClearSearchValue ,and add test,add doc * docs: showSearch describe --------- Co-authored-by: 刘欢 <lh01217311@antgroup.com>
1 parent e3ade1a commit a10f879

File tree

6 files changed

+119
-24
lines changed

6 files changed

+119
-24
lines changed

README.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,6 @@ React.render(
114114
</tr>
115115
</thead>
116116
<tbody>
117-
<tr>
118-
<td>autoClearSearchValue</td>
119-
<td>boolean</td>
120-
<td>true</td>
121-
<td>Whether the current search will be cleared on selecting an item. Only applies when checkable</td>
122-
</tr>
123117
<tr>
124118
<td>options</td>
125119
<td>Object</td>
@@ -234,9 +228,28 @@ React.render(
234228
<td>>true</td>
235229
<td>hide popup on select</td>
236230
</tr>
231+
<tr>
232+
<td>showSearch</td>
233+
<td>boolean | object</td>
234+
<td>false</td>
235+
<td>Whether show search input in single mode</td>
236+
</tr>
237237
</tbody>
238238
</table>
239239

240+
### showSearch
241+
242+
| Property | Description | Type | Default | Version |
243+
| --- | --- | --- | --- | --- |
244+
| autoClearSearchValue | Whether the current search will be cleared on selecting an item. Only applies when checkable| boolean | true |
245+
| filter | The function will receive two arguments, inputValue and option, if the function returns true, the option will be included in the filtered set; Otherwise, it will be excluded | function(inputValue, path): boolean | - | |
246+
| limit | Set the count of filtered items | number \| false | 50 | |
247+
| matchInputWidth | Whether the width of list matches input, ([how it looks](https://github.com/ant-design/ant-design/issues/25779)) | boolean | true | |
248+
| render | Used to render filtered options | function(inputValue, path): ReactNode | - | |
249+
| sort | Used to sort filtered options | function(a, b, inputValue) | - | |
250+
| searchValue | The current input "search" text | string | - | - |
251+
| onSearch | called when input changed | function | - | - |
252+
240253
### option
241254

242255
<table class="table table-bordered table-striped">

src/Cascader.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface BaseOptionType {
4040

4141
export type DefaultOptionType = BaseOptionType & Record<string, any>;
4242

43-
export interface ShowSearchType<
43+
export interface SearchConfig<
4444
OptionType extends DefaultOptionType = DefaultOptionType,
4545
ValueField extends keyof OptionType = keyof OptionType,
4646
> {
@@ -63,6 +63,9 @@ export interface ShowSearchType<
6363
) => number;
6464
matchInputWidth?: boolean;
6565
limit?: number | false;
66+
searchValue?: string;
67+
onSearch?: (value: string) => void;
68+
autoClearSearchValue?: boolean;
6669
}
6770

6871
export type ShowCheckedStrategy = typeof SHOW_PARENT | typeof SHOW_CHILD;
@@ -88,9 +91,12 @@ interface BaseCascaderProps<
8891
showCheckedStrategy?: ShowCheckedStrategy;
8992

9093
// Search
94+
/** @deprecated please use showSearch.autoClearSearchValue */
9195
autoClearSearchValue?: boolean;
92-
showSearch?: boolean | ShowSearchType<OptionType>;
96+
showSearch?: boolean | SearchConfig<OptionType>;
97+
/** @deprecated please use showSearch.searchValue */
9398
searchValue?: string;
99+
/** @deprecated please use showSearch.onSearch */
94100
onSearch?: (value: string) => void;
95101

96102
// Trigger
@@ -205,9 +211,6 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
205211
checkable,
206212

207213
// Search
208-
autoClearSearchValue = true,
209-
searchValue,
210-
onSearch,
211214
showSearch,
212215

213216
// Trigger
@@ -267,21 +270,20 @@ const Cascader = React.forwardRef<CascaderRef, InternalCascaderProps>((props, re
267270
);
268271

269272
// =========================== Search ===========================
273+
const [mergedShowSearch, searchConfig] = useSearchConfig(showSearch, props);
274+
const { autoClearSearchValue = true, searchValue, onSearch } = searchConfig;
270275
const [mergedSearchValue, setSearchValue] = useMergedState('', {
271276
value: searchValue,
272277
postState: search => search || '',
273278
});
274279

275280
const onInternalSearch: BaseSelectProps['onSearch'] = (searchText, info) => {
276281
setSearchValue(searchText);
277-
278282
if (info.source !== 'blur' && onSearch) {
279283
onSearch(searchText);
280284
}
281285
};
282286

283-
const [mergedShowSearch, searchConfig] = useSearchConfig(showSearch);
284-
285287
const searchOptions = useSearchOptions(
286288
mergedSearchValue,
287289
mergedOptions,

src/hooks/useSearchConfig.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import warning from '@rc-component/util/lib/warning';
22
import * as React from 'react';
3-
import type { CascaderProps, ShowSearchType } from '../Cascader';
3+
import type { CascaderProps, SearchConfig } from '../Cascader';
44

55
// Convert `showSearch` to unique config
6-
export default function useSearchConfig(showSearch?: CascaderProps['showSearch']) {
7-
return React.useMemo<[boolean, ShowSearchType]>(() => {
6+
export default function useSearchConfig(showSearch?: CascaderProps['showSearch'], props?: any) {
7+
const { autoClearSearchValue, searchValue, onSearch } = props;
8+
return React.useMemo<[boolean, SearchConfig]>(() => {
89
if (!showSearch) {
910
return [false, {}];
1011
}
1112

12-
let searchConfig: ShowSearchType = {
13+
let searchConfig: SearchConfig = {
1314
matchInputWidth: true,
1415
limit: 50,
16+
autoClearSearchValue,
17+
searchValue,
18+
onSearch,
1519
};
1620

1721
if (showSearch && typeof showSearch === 'object') {
@@ -30,5 +34,5 @@ export default function useSearchConfig(showSearch?: CascaderProps['showSearch']
3034
}
3135

3236
return [true, searchConfig];
33-
}, [showSearch]);
37+
}, [showSearch, autoClearSearchValue, searchValue, onSearch]);
3438
}

src/hooks/useSearchOptions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import * as React from 'react';
2-
import type { DefaultOptionType, InternalFieldNames, ShowSearchType } from '../Cascader';
2+
import type { DefaultOptionType, InternalFieldNames, SearchConfig } from '../Cascader';
33

44
export const SEARCH_MARK = '__rc_cascader_search_mark__';
55

6-
const defaultFilter: ShowSearchType['filter'] = (search, options, { label = '' }) =>
6+
const defaultFilter: SearchConfig['filter'] = (search, options, { label = '' }) =>
77
options.some(opt => String(opt[label]).toLowerCase().includes(search.toLowerCase()));
88

9-
const defaultRender: ShowSearchType['render'] = (inputValue, path, prefixCls, fieldNames) =>
9+
const defaultRender: SearchConfig['render'] = (inputValue, path, prefixCls, fieldNames) =>
1010
path.map(opt => opt[fieldNames.label as string]).join(' / ');
1111

1212
const useSearchOptions = (
1313
search: string,
1414
options: DefaultOptionType[],
1515
fieldNames: InternalFieldNames,
1616
prefixCls: string,
17-
config: ShowSearchType,
17+
config: SearchConfig,
1818
enableHalfPath?: boolean,
1919
) => {
2020
const { filter = defaultFilter, render = defaultRender, limit = 50, sort } = config;

src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export type {
66
DefaultOptionType,
77
CascaderProps,
88
FieldNames,
9-
ShowSearchType,
9+
SearchConfig,
1010
CascaderRef,
1111
} from './Cascader';
1212
export { Panel };

tests/search.spec.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ describe('Cascader.Search', () => {
308308
<Cascader
309309
open
310310
searchValue="little"
311+
showSearch
311312
options={[
312313
{
313314
label: 'bamboo',
@@ -352,4 +353,79 @@ describe('Cascader.Search', () => {
352353
'{"label":"bamboo","disabled":true,"value":"bamboo"}',
353354
);
354355
});
356+
357+
it('onSearch and searchValue in showSearch', () => {
358+
const onSearch = jest.fn();
359+
const wrapper = mount(<Cascader options={options} open showSearch={{ onSearch }} />);
360+
361+
// Leaf
362+
doSearch(wrapper, 'toy');
363+
let itemList = wrapper.find('div.rc-cascader-menu-item-content');
364+
expect(itemList).toHaveLength(2);
365+
expect(itemList.at(0).text()).toEqual('Label Bamboo / Label Little / Toy Fish');
366+
expect(itemList.at(1).text()).toEqual('Label Bamboo / Label Little / Toy Cards');
367+
expect(onSearch).toHaveBeenCalledWith('toy');
368+
369+
// Parent
370+
doSearch(wrapper, 'Label Little');
371+
itemList = wrapper.find('div.rc-cascader-menu-item-content');
372+
expect(itemList).toHaveLength(2);
373+
expect(itemList.at(0).text()).toEqual('Label Bamboo / Label Little / Toy Fish');
374+
expect(itemList.at(1).text()).toEqual('Label Bamboo / Label Little / Toy Cards');
375+
expect(onSearch).toHaveBeenCalledWith('Label Little');
376+
});
377+
378+
it('searchValue in showSearch', () => {
379+
const { container } = render(
380+
<Cascader
381+
open
382+
showSearch={{ searchValue: 'little' }}
383+
options={[
384+
{
385+
label: 'bamboo',
386+
value: 'bamboo',
387+
children: [
388+
{
389+
label: 'little',
390+
value: 'little',
391+
},
392+
],
393+
},
394+
]}
395+
/>,
396+
);
397+
expect(container.querySelectorAll('.rc-cascader-menu-item')).toHaveLength(1);
398+
expect(
399+
(container.querySelector('.rc-cascader-selection-search-input') as HTMLInputElement)?.value,
400+
).toBe('little');
401+
});
402+
it('autoClearSearchValue in showSearch', () => {
403+
const { container } = render(
404+
<Cascader
405+
open
406+
checkable
407+
showSearch={{ autoClearSearchValue: false }}
408+
options={[
409+
{
410+
label: 'bamboo',
411+
value: 'bamboo',
412+
children: [
413+
{
414+
label: 'little',
415+
value: 'little',
416+
},
417+
],
418+
},
419+
]}
420+
/>,
421+
);
422+
423+
const inputNode = container.querySelector<HTMLInputElement>(
424+
'.rc-cascader-selection-search-input',
425+
);
426+
fireEvent.change(inputNode as HTMLInputElement, { target: { value: 'little' } });
427+
expect(inputNode).toHaveValue('little');
428+
fireEvent.click(document.querySelector('.rc-cascader-checkbox') as HTMLElement);
429+
expect(inputNode).toHaveValue('little');
430+
});
355431
});

0 commit comments

Comments
 (0)