From cff73d1f088ea68d4c8f340304d18696f8b14b56 Mon Sep 17 00:00:00 2001 From: GSBL <2602140596@qq.com> Date: Mon, 30 Nov 2020 13:21:15 +0800 Subject: [PATCH] feat(cascader): support onSearch callback (#28011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 Co-authored-by: 偏右 --- .../__tests__/__snapshots__/demo.test.js.snap | 128 ++++++++++++++ components/cascader/__tests__/index.test.js | 7 + components/cascader/demo/custom-search.md | 166 ++++++++++++++++++ components/cascader/index.en-US.md | 1 + components/cascader/index.tsx | 21 ++- components/cascader/index.zh-CN.md | 1 + 6 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 components/cascader/demo/custom-search.md diff --git a/components/cascader/__tests__/__snapshots__/demo.test.js.snap b/components/cascader/__tests__/__snapshots__/demo.test.js.snap index e03a8e0158..ff3950e9eb 100644 --- a/components/cascader/__tests__/__snapshots__/demo.test.js.snap +++ b/components/cascader/__tests__/__snapshots__/demo.test.js.snap @@ -190,6 +190,134 @@ exports[`renders ./components/cascader/demo/custom-render.md correctly 1`] = ` `; +exports[`renders ./components/cascader/demo/custom-search.md correctly 1`] = ` +
+
+ + + + + + + +
+
+ + + + + + + +
+
+ + + + + + + +
+
+`; + exports[`renders ./components/cascader/demo/custom-trigger.md correctly 1`] = ` Unselect  diff --git a/components/cascader/__tests__/index.test.js b/components/cascader/__tests__/index.test.js index 4d9bc17fff..f091aa59f2 100644 --- a/components/cascader/__tests__/index.test.js +++ b/components/cascader/__tests__/index.test.js @@ -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(); + wrapper.find('input').simulate('change', { target: { value: '123' } }); + expect(onChange).toHaveBeenCalledWith('123'); + }); }); diff --git a/components/cascader/demo/custom-search.md b/components/cascader/demo/custom-search.md new file mode 100644 index 0000000000..b68e4de57a --- /dev/null +++ b/components/cascader/demo/custom-search.md @@ -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 + : [ + + {keyword} + , + 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 ( + + + + + + + + + + + + ); +}; +ReactDOM.render(, mountNode); +``` diff --git a/components/cascader/index.en-US.md b/components/cascader/index.en-US.md index 5b3de3ab27..0c76dd87fb 100644 --- a/components/cascader/index.en-US.md +++ b/components/cascader/index.en-US.md @@ -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 diff --git a/components/cascader/index.tsx b/components/cascader/index.tsx index b7037d26a5..7ec89d001a 100644 --- a/components/cascader/index.tsx +++ b/components/cascader/index.tsx @@ -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 { 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 { 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 { handleInputChange = (e: React.ChangeEvent) => { 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 { 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 { diff --git a/components/cascader/index.zh-CN.md b/components/cascader/index.zh-CN.md index 97c18c5c7c..2a09254731 100644 --- a/components/cascader/index.zh-CN.md +++ b/components/cascader/index.zh-CN.md @@ -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