mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-11-30 11:08:45 +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>
|
</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`] = `
|
exports[`renders ./components/cascader/demo/custom-trigger.md correctly 1`] = `
|
||||||
<span>
|
<span>
|
||||||
Unselect
|
Unselect
|
||||||
|
@ -542,4 +542,11 @@ describe('Cascader', () => {
|
|||||||
.simulate('click');
|
.simulate('click');
|
||||||
expect(onChange).toHaveBeenCalledWith(['Zhejiang', 'Hangzhou', 'West Lake'], expect.anything());
|
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 | |
|
| 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 | - | |
|
| render | Used to render filtered options | function(inputValue, path): ReactNode | - | |
|
||||||
| sort | Used to sort filtered options | function(a, b, inputValue) | - | |
|
| 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
|
### Option
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ export interface ShowSearchType {
|
|||||||
) => number;
|
) => number;
|
||||||
matchInputWidth?: boolean;
|
matchInputWidth?: boolean;
|
||||||
limit?: number | false;
|
limit?: number | false;
|
||||||
|
onChange?: (val: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CascaderProps {
|
export interface CascaderProps {
|
||||||
@ -254,7 +255,11 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
|
|||||||
if ('popupVisible' in nextProps) {
|
if ('popupVisible' in nextProps) {
|
||||||
newState.popupVisible = nextProps.popupVisible;
|
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);
|
newState.flattenOptions = flattenTree(nextProps.options, nextProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +283,10 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
|
|||||||
inputValue: '',
|
inputValue: '',
|
||||||
inputFocused: false,
|
inputFocused: false,
|
||||||
popupVisible: props.popupVisible,
|
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,
|
prevProps: props,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -370,9 +378,16 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
|
|||||||
handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const { popupVisible } = this.state;
|
const { popupVisible } = this.state;
|
||||||
const inputValue = e.target.value;
|
const inputValue = e.target.value;
|
||||||
|
const { showSearch } = this.props;
|
||||||
if (!popupVisible) {
|
if (!popupVisible) {
|
||||||
this.handlePopupVisibleChange(true);
|
this.handlePopupVisibleChange(true);
|
||||||
}
|
}
|
||||||
|
if (showSearch) {
|
||||||
|
const { onChange } = showSearch as ShowSearchType;
|
||||||
|
if (onChange) {
|
||||||
|
onChange(inputValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.setState({ inputValue });
|
this.setState({ inputValue });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -564,7 +579,7 @@ class Cascader extends React.Component<CascaderProps, CascaderState> {
|
|||||||
let { options } = props;
|
let { options } = props;
|
||||||
const names: FilledFieldNamesType = getFilledFieldNames(this.props);
|
const names: FilledFieldNamesType = getFilledFieldNames(this.props);
|
||||||
if (options && options.length > 0) {
|
if (options && options.length > 0) {
|
||||||
if (state.inputValue) {
|
if (state.inputValue && (showSearch as ShowSearchType).onChange === undefined) {
|
||||||
options = this.generateFilteredOptions(prefixCls, renderEmpty);
|
options = this.generateFilteredOptions(prefixCls, renderEmpty);
|
||||||
}
|
}
|
||||||
} else {
|
} 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 | |
|
| matchInputWidth | 搜索结果列表是否与输入框同宽([效果](https://github.com/ant-design/ant-design/issues/25779)) | boolean | true | |
|
||||||
| render | 用于渲染 filter 后的选项 | function(inputValue, path): ReactNode | - | |
|
| render | 用于渲染 filter 后的选项 | function(inputValue, path): ReactNode | - | |
|
||||||
| sort | 用于排序 filter 后的选项 | function(a, b, inputValue) | - | |
|
| sort | 用于排序 filter 后的选项 | function(a, b, inputValue) | - | |
|
||||||
|
| onChange | 文本框值变化时的回调 | function(value: string) | - | 4.9.0 |
|
||||||
|
|
||||||
### Option
|
### Option
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user