mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 02:59:04 +08:00
feat(cascader): support onSearch callback (#28011)
* feat(cascader): support onSearch callback * fix(cascader): move onSearch into showSearch * fix(cascader): rename onSearch to onChange * fix(cascader): rename onSearch to onChange * Update index.zh-CN.md * Update index.en-US.md Co-authored-by: rgbhuang <rgbhuang@tencent.com> Co-authored-by: 偏右 <afc163@gmail.com>
This commit is contained in:
parent
33b96d3461
commit
cff73d1f08
@ -190,6 +190,134 @@ exports[`renders ./components/cascader/demo/custom-render.md correctly 1`] = `
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/cascader/demo/custom-search.md correctly 1`] = `
|
||||
<div
|
||||
class="ant-row"
|
||||
style="margin-left:-6px;margin-right:-6px;margin-top:-9px;margin-bottom:9px"
|
||||
>
|
||||
<div
|
||||
class="ant-col ant-col-24"
|
||||
style="padding-left:6px;padding-right:6px;padding-top:9px;padding-bottom:9px"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-picker ant-cascader-picker-show-search"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-picker-label"
|
||||
/>
|
||||
<input
|
||||
autocomplete="off"
|
||||
class="ant-input ant-cascader-input "
|
||||
placeholder="Please select"
|
||||
tabindex="-1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-cascader-picker-arrow"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-24"
|
||||
style="padding-left:6px;padding-right:6px;padding-top:9px;padding-bottom:9px"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-picker ant-cascader-picker-show-search"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-picker-label"
|
||||
/>
|
||||
<input
|
||||
autocomplete="off"
|
||||
class="ant-input ant-cascader-input "
|
||||
placeholder="Please select"
|
||||
tabindex="-1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-cascader-picker-arrow"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-col ant-col-24"
|
||||
style="padding-left:6px;padding-right:6px;padding-top:9px;padding-bottom:9px"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-picker ant-cascader-picker-show-search"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="ant-cascader-picker-label"
|
||||
/>
|
||||
<input
|
||||
autocomplete="off"
|
||||
class="ant-input ant-cascader-input "
|
||||
placeholder="Please select"
|
||||
tabindex="-1"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<span
|
||||
aria-label="down"
|
||||
class="anticon anticon-down ant-cascader-picker-arrow"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="down"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/cascader/demo/custom-trigger.md correctly 1`] = `
|
||||
<span>
|
||||
Unselect
|
||||
|
@ -542,4 +542,11 @@ describe('Cascader', () => {
|
||||
.simulate('click');
|
||||
expect(onChange).toHaveBeenCalledWith(['Zhejiang', 'Hangzhou', 'West Lake'], expect.anything());
|
||||
});
|
||||
|
||||
it('showSearch.onChange should work correctly', () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = mount(<Cascader options={options} showSearch={{ onChange }} />);
|
||||
wrapper.find('input').simulate('change', { target: { value: '123' } });
|
||||
expect(onChange).toHaveBeenCalledWith('123');
|
||||
});
|
||||
});
|
||||
|
166
components/cascader/demo/custom-search.md
Normal file
166
components/cascader/demo/custom-search.md
Normal file
@ -0,0 +1,166 @@
|
||||
---
|
||||
order: 12
|
||||
title:
|
||||
zh-CN: 自定义搜索
|
||||
en-US: Custom Search
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
可以直接监听`showSearch.onChange`事件,自己实现搜索逻辑
|
||||
|
||||
## en-US
|
||||
|
||||
You can directly listen `showSearch.onChange` events and implement search logic yourself
|
||||
|
||||
```jsx
|
||||
import { Cascader, Row, Col } from 'antd';
|
||||
import { useState } from 'react';
|
||||
|
||||
const CustomSearchCascaders = () => {
|
||||
const originOptions = [
|
||||
{
|
||||
value: 'zhejiang',
|
||||
label: 'Zhejiang',
|
||||
children: [
|
||||
{
|
||||
value: 'hangzhou',
|
||||
label: 'Hangzhou',
|
||||
children: [
|
||||
{
|
||||
value: 'xihu',
|
||||
label: 'West Lake',
|
||||
},
|
||||
{
|
||||
value: 'xiasha',
|
||||
label: 'Xia Sha',
|
||||
disabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'jiangsu',
|
||||
label: 'Jiangsu',
|
||||
children: [
|
||||
{
|
||||
value: 'nanjing',
|
||||
label: 'Nanjing',
|
||||
children: [
|
||||
{
|
||||
value: 'zhonghuamen',
|
||||
label: 'Zhong Hua men',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const [options1, setOptions1] = useState(originOptions);
|
||||
const [options2, setOptions2] = useState(originOptions);
|
||||
const [options3, setOptions3] = useState(originOptions);
|
||||
function flattenTree(opts, props, ancestor = []) {
|
||||
let flattenOptions = [];
|
||||
opts.forEach(option => {
|
||||
const path = ancestor.concat(option);
|
||||
if (props.changeOnSelect || !option.children || !option.children.length) {
|
||||
flattenOptions.push(path);
|
||||
}
|
||||
if (option.children) {
|
||||
flattenOptions = flattenOptions.concat(flattenTree(option.children, props, path));
|
||||
}
|
||||
});
|
||||
return flattenOptions;
|
||||
}
|
||||
function onChange(value, selectedOptions) {
|
||||
console.log(value, selectedOptions);
|
||||
}
|
||||
|
||||
function filter(inputValue, path) {
|
||||
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
|
||||
}
|
||||
function highlightKeyword(str, keyword) {
|
||||
return str.split(keyword).map((node, index) =>
|
||||
index === 0
|
||||
? node
|
||||
: [
|
||||
<span style={{ color: 'blue' }} key="seperator">
|
||||
{keyword}
|
||||
</span>,
|
||||
node,
|
||||
],
|
||||
);
|
||||
}
|
||||
const flattenOptions = flattenTree(originOptions, { changeOnSelect: false });
|
||||
function search(val) {
|
||||
const filtered = [];
|
||||
flattenOptions.forEach(path => {
|
||||
const match = filter(val, path);
|
||||
if (match) {
|
||||
filtered.push(path);
|
||||
}
|
||||
});
|
||||
const result = filtered.map(path => ({
|
||||
__IS_FILTERED_OPTION: true,
|
||||
path,
|
||||
value: path.map(o => o.value),
|
||||
label: path.map((option, index) => {
|
||||
const { label } = option;
|
||||
const node = label.indexOf(val) > -1 ? highlightKeyword(label, val) : label;
|
||||
return index === 0 ? node : [' / ', node];
|
||||
}),
|
||||
disabled: path.some(o => !!o.disabled),
|
||||
isEmptyNode: true,
|
||||
}));
|
||||
return result;
|
||||
}
|
||||
function onChange1(val) {
|
||||
const result = search(val);
|
||||
setOptions1(result);
|
||||
}
|
||||
function onChange2(val) {
|
||||
const result = search(val);
|
||||
setOptions2(result);
|
||||
}
|
||||
function onChange3(val) {
|
||||
const result = search(val);
|
||||
setOptions3(result);
|
||||
}
|
||||
return (
|
||||
<Row gutter={[12, 18]}>
|
||||
<Col span={24}>
|
||||
<Cascader
|
||||
options={options1}
|
||||
onChange={onChange}
|
||||
placeholder="Please select"
|
||||
showSearch={{
|
||||
onChange: onChange1,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Cascader
|
||||
options={options2}
|
||||
onChange={onChange}
|
||||
placeholder="Please select"
|
||||
showSearch={{
|
||||
onChange: onChange2,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Cascader
|
||||
options={options3}
|
||||
onChange={onChange}
|
||||
placeholder="Please select"
|
||||
showSearch={{
|
||||
onChange: onChange3,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
ReactDOM.render(<CustomSearchCascaders />, mountNode);
|
||||
```
|
@ -58,6 +58,7 @@ Cascade selection box.
|
||||
| matchInputWidth | Whether the width of list matches input, ([how it looks](https://github.com/ant-design/ant-design/issues/25779)) | boolean | true | |
|
||||
| render | Used to render filtered options | function(inputValue, path): ReactNode | - | |
|
||||
| sort | Used to sort filtered options | function(a, b, inputValue) | - | |
|
||||
| onChange | A callback function, can be executed when the search input changes | function(value: string) | - | 4.9.0 |
|
||||
|
||||
### Option
|
||||
|
||||
|
@ -64,6 +64,7 @@ export interface ShowSearchType {
|
||||
) => number;
|
||||
matchInputWidth?: boolean;
|
||||
limit?: number | false;
|
||||
onChange?: (val: string) => void;
|
||||
}
|
||||
|
||||
export interface CascaderProps {
|
||||
@ -254,7 +255,11 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
|
||||
if ('popupVisible' in nextProps) {
|
||||
newState.popupVisible = nextProps.popupVisible;
|
||||
}
|
||||
if (nextProps.showSearch && prevProps.options !== nextProps.options) {
|
||||
if (
|
||||
nextProps.showSearch &&
|
||||
(nextProps.showSearch as ShowSearchType).onChange === undefined &&
|
||||
prevProps.options !== nextProps.options
|
||||
) {
|
||||
newState.flattenOptions = flattenTree(nextProps.options, nextProps);
|
||||
}
|
||||
|
||||
@ -278,7 +283,10 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
|
||||
inputValue: '',
|
||||
inputFocused: false,
|
||||
popupVisible: props.popupVisible,
|
||||
flattenOptions: props.showSearch ? flattenTree(props.options, props) : undefined,
|
||||
flattenOptions:
|
||||
props.showSearch && (props.showSearch as ShowSearchType).onChange === undefined
|
||||
? flattenTree(props.options, props)
|
||||
: undefined,
|
||||
prevProps: props,
|
||||
};
|
||||
}
|
||||
@ -370,9 +378,16 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
|
||||
handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { popupVisible } = this.state;
|
||||
const inputValue = e.target.value;
|
||||
const { showSearch } = this.props;
|
||||
if (!popupVisible) {
|
||||
this.handlePopupVisibleChange(true);
|
||||
}
|
||||
if (showSearch) {
|
||||
const { onChange } = showSearch as ShowSearchType;
|
||||
if (onChange) {
|
||||
onChange(inputValue);
|
||||
}
|
||||
}
|
||||
this.setState({ inputValue });
|
||||
};
|
||||
|
||||
@ -564,7 +579,7 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
|
||||
let { options } = props;
|
||||
const names: FilledFieldNamesType = getFilledFieldNames(this.props);
|
||||
if (options && options.length > 0) {
|
||||
if (state.inputValue) {
|
||||
if (state.inputValue && (showSearch as ShowSearchType).onChange === undefined) {
|
||||
options = this.generateFilteredOptions(prefixCls, renderEmpty);
|
||||
}
|
||||
} else {
|
||||
|
@ -61,6 +61,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/UdS8y8xyZ/Cascader.svg
|
||||
| matchInputWidth | 搜索结果列表是否与输入框同宽([效果](https://github.com/ant-design/ant-design/issues/25779)) | boolean | true | |
|
||||
| render | 用于渲染 filter 后的选项 | function(inputValue, path): ReactNode | - | |
|
||||
| sort | 用于排序 filter 后的选项 | function(a, b, inputValue) | - | |
|
||||
| onChange | 文本框值变化时的回调 | function(value: string) | - | 4.9.0 |
|
||||
|
||||
### Option
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user