浏览代码

feat: select supports autoClearSearchValue (#509)

* feat: select supported autoClearSearchValue
* fix: add autoClearSearchValue proptypes
pointhalo 3 年之前
父节点
当前提交
9909b421c2

+ 11 - 7
content/input/select/index-en-US.md

@@ -559,6 +559,8 @@ class Link extends React.Component {
 You can turn on the search capability by setting `filter` to true.  
 You can turn on the search capability by setting `filter` to true.  
 The default search strategy will include comparison of the input value with the label value of option
 The default search strategy will include comparison of the input value with the label value of option
 
 
+By default, the search keywords will be cleared automatically after multiple selection is selected. If you want to keep it, you can turn off the default behavior by setting `autoClearSearchValue` to false (provided after v2.3)
+
 ```jsx live=true
 ```jsx live=true
 import React from 'react';
 import React from 'react';
 import { Select } from '@douyinfe/semi-ui';
 import { Select } from '@douyinfe/semi-ui';
@@ -573,11 +575,12 @@ import { Select } from '@douyinfe/semi-ui';
         </Select>
         </Select>
         <br />
         <br />
         <br />
         <br />
-        <Select filter multiple style={{ width: 350 }} placeholder="Searchable Multiple Select">
-            <Select.Option value="app1">Semi</Select.Option>
-            <Select.Option value="app2">Hotsoon</Select.Option>
-            <Select.Option value="app3">Pipixia</Select.Option>
-            <Select.Option value="app4">BuzzVideo</Select.Option>
+        <Select filter multiple style={{ width: 350 }} placeholder="Searchable Multiple Select" autoClearSearchValue={false}>
+            <Select.Option value='semi-0'>Semi-0</Select.Option>
+            <Select.Option value='semi-1'>Semi-1</Select.Option>
+            <Select.Option value='semi-2'>Semi-2</Select.Option>
+            <Select.Option value='semi-3'>Semi-3</Select.Option>
+            <Select.Option value='semi-4'>Semi-4</Select.Option>
         </Select>
         </Select>
     </>
     </>
 );
 );
@@ -1283,8 +1286,9 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | Properties | Instructions | Type | Default |
 | Properties | Instructions | Type | Default |
 | --- | --- | --- | --- |
 | --- | --- | --- | --- |
 | allowCreate | Whether to allow the user to create new entries. Needs to be used with `filter` | boolean | false |
 | allowCreate | Whether to allow the user to create new entries. Needs to be used with `filter` | boolean | false |
-| arrowIcon | Customize the right drop-down arrow Icon, when the showClear switch is turned on and there is currently a selected value, hover will give priority to the clear icon <br/>**since v1.15.0** | ReactNode |  |
+| arrowIcon | Customize the right drop-down arrow Icon, when the showClear switch is turned on and there is currently a selected value, hover will give priority to the clear icon <br/>**supported after v1.15.0** | ReactNode |  |
 | autoAdjustOverflow | Whether the pop-up layer automatically adjusts the direction when it is obscured (only vertical direction is supported for the time being, and the inserted parent is body) | boolean | true |
 | autoAdjustOverflow | Whether the pop-up layer automatically adjusts the direction when it is obscured (only vertical direction is supported for the time being, and the inserted parent is body) | boolean | true |
+| autoClearSearchValue     |  After selecting the option, whether to automatically clear the search keywords, it will take effect when mutilple and filter are both enabled.<br/>**supported after v2.3.0** | boolean                      | true                                |
 | autoFocus | Whether automatically focus when component mount | boolean | false |
 | autoFocus | Whether automatically focus when component mount | boolean | false |
 | className | The CSS class name of the wrapper element | string |  |
 | className | The CSS class name of the wrapper element | string |  |
 | clickToHide | When expanded, click on the selection box to automatically put away the drop-down list | boolean | false |
 | clickToHide | When expanded, click on the selection box to automatically put away the drop-down list | boolean | false |
@@ -1308,7 +1312,7 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | maxHeight | Maximum height of `optionList` in the pop-up layer | string | number | 300 |
 | maxHeight | Maximum height of `optionList` in the pop-up layer | string | number | 300 |
 | multiple | Whether allow multiple selection | boolean | false |
 | multiple | Whether allow multiple selection | boolean | false |
 | outerBottomSlot | Rendered at the bottom of the pop-up layer, custom slot level with optionList | ReactNode |  |
 | outerBottomSlot | Rendered at the bottom of the pop-up layer, custom slot level with optionList | ReactNode |  |
-| outerTopSlot | Rendered at the top of the pop-up layer, custom slot level with optionList <br/>** supported after v1.6.0 ** |
+| outerTopSlot | Rendered at the top of the pop-up layer, custom slot level with optionList <br/>**supported after v1.6.0** |
 | optionList | You can pass Option through this property, make sure that each element in the array has `label`, `value` properties | Array (\[{value, label}\]) |  |
 | optionList | You can pass Option through this property, make sure that each element in the array has `label`, `value` properties | Array (\[{value, label}\]) |  |
 | placeholder | placeholder | ReactNode |  |
 | placeholder | placeholder | ReactNode |  |
 | position | Pop-up layer position, refer to [Popover·API reference·position](/en-US/show/popover#API%20Reference) | string | 'bottomLeft' |
 | position | Pop-up layer position, refer to [Popover·API reference·position](/en-US/show/popover#API%20Reference) | string | 'bottomLeft' |

+ 9 - 6
content/input/select/index.md

@@ -610,7 +610,8 @@ class Link extends React.Component {
 
 
 ### 开启搜索
 ### 开启搜索
 
 
-将 `filter` 置为 true,开启搜索能力。默认搜索策略将为 input 输入值与 option 的 label 值进行 include 对比
+将 `filter` 置为 true,开启搜索能力。默认搜索策略将为 input 输入值与 option 的 label 值进行 include 对比  
+默认情况下,多选选中后会自动清空搜索关键字。若你希望保留,可以通过 autoClearSearchValue 设为 false 关闭默认行为(v2.3后提供)
 
 
 ```jsx live=true hideInDSM
 ```jsx live=true hideInDSM
 import React from 'react';
 import React from 'react';
@@ -625,11 +626,12 @@ import { Select } from '@douyinfe/semi-ui';
             <Select.Option value='xigua'>西瓜视频</Select.Option>
             <Select.Option value='xigua'>西瓜视频</Select.Option>
         </Select>
         </Select>
         <br/><br/>
         <br/><br/>
-        <Select filter multiple style={{ width: 300 }} placeholder='带搜索功能的多选'>
-            <Select.Option value='abc'>抖音</Select.Option>
-            <Select.Option value='hotsoon'>火山</Select.Option>
-            <Select.Option value='jianying'>剪映</Select.Option>
-            <Select.Option value='xigua'>西瓜视频</Select.Option>
+        <Select filter multiple style={{ width: 300 }} placeholder='带搜索功能的多选' autoClearSearchValue={false}>
+            <Select.Option value='semi-0'>Semi-0</Select.Option>
+            <Select.Option value='semi-1'>Semi-1</Select.Option>
+            <Select.Option value='semi-2'>Semi-2</Select.Option>
+            <Select.Option value='semi-3'>Semi-3</Select.Option>
+            <Select.Option value='semi-4'>Semi-4</Select.Option>
         </Select>
         </Select>
     </>
     </>
 );
 );
@@ -1298,6 +1300,7 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | allowCreate              | 是否允许用户创建新条目,需配合 filter 使用                                                                                                                                                                | boolean                               | false                             |
 | allowCreate              | 是否允许用户创建新条目,需配合 filter 使用                                                                                                                                                                | boolean                               | false                             |
 | arrowIcon            | 自定义右侧下拉箭头Icon,当showClear开关打开且当前有选中值时,hover会优先显示clear icon <br/>**v1.15.0 后提供**                                                                                                                                                                 | ReactNode     |                             |
 | arrowIcon            | 自定义右侧下拉箭头Icon,当showClear开关打开且当前有选中值时,hover会优先显示clear icon <br/>**v1.15.0 后提供**                                                                                                                                                                 | ReactNode     |                             |
 | autoAdjustOverflow       | 浮层被遮挡时是否自动调整方向(暂时仅支持竖直方向,且插入的父级为 body)                                                                                                            | boolean                               | true                              |
 | autoAdjustOverflow       | 浮层被遮挡时是否自动调整方向(暂时仅支持竖直方向,且插入的父级为 body)                                                                                                            | boolean                               | true                              |
+| autoClearSearchValue     | 选中选项后,是否自动清空搜索关键字,当mutilple、filter都开启时生效<br/>**v2.3.0 后提供**                                                                                          | boolean                               | true                              |
 | autoFocus                | 初始渲染时是否自动 focus                                                                                                                                                                                  | boolean                               | false                             |
 | autoFocus                | 初始渲染时是否自动 focus                                                                                                                                                                                  | boolean                               | false                             |
 | className                | 类名                                                                                                                                                                                                      | string                                |                                   |
 | className                | 类名                                                                                                                                                                                                      | string                                |                                   |
 | clickToHide              | 已展开时,点击选择框是否自动收起下拉列表                                                                                                                                          | boolean                               | false                             |
 | clickToHide              | 已展开时,点击选择框是否自动收起下拉列表                                                                                                                                          | boolean                               | false                             |

+ 11 - 6
packages/semi-foundation/select/foundation.ts

@@ -405,7 +405,7 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
     _handleMultipleSelect({ value, label, ...rest }: BasicOptionProps, event: MouseEvent | KeyboardEvent) {
     _handleMultipleSelect({ value, label, ...rest }: BasicOptionProps, event: MouseEvent | KeyboardEvent) {
         const maxLimit = this._adapter.getMaxLimit();
         const maxLimit = this._adapter.getMaxLimit();
         const selections = this._adapter.getSelections();
         const selections = this._adapter.getSelections();
-
+        const { autoClearSearchValue } = this.getProps();
         if (selections.has(label)) {
         if (selections.has(label)) {
             this._notifyDeselect(value, { value, label, ...rest });
             this._notifyDeselect(value, { value, label, ...rest });
             selections.delete(label);
             selections.delete(label);
@@ -420,7 +420,9 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
             // Controlled components, directly notified
             // Controlled components, directly notified
             this._notifyChange(selections);
             this._notifyChange(selections);
             if (this._isFilterable()) {
             if (this._isFilterable()) {
-                this.clearInput();
+                if (autoClearSearchValue) {
+                    this.clearInput();
+                }
                 this.focusInput();
                 this.focusInput();
             }
             }
         } else {
         } else {
@@ -431,11 +433,14 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
             let { options } = this.getStates();
             let { options } = this.getStates();
             // Searchable filtering, when selected, resets Input
             // Searchable filtering, when selected, resets Input
             if (this._isFilterable()) {
             if (this._isFilterable()) {
-                this.clearInput();
+                // When filter active,if autoClearSearchValue is true,reset input after select
+                if (autoClearSearchValue) {
+                    this.clearInput();
+                    // At the same time, the filtering of options is also cleared, in order to show all candidates
+                    const sugInput = '';
+                    options = this._filterOption(options, sugInput);
+                }
                 this.focusInput();
                 this.focusInput();
-                // At the same time, the filtering of options is also cleared, in order to show all candidates
-                const sugInput = '';
-                options = this._filterOption(options, sugInput);
             }
             }
             this.updateOptionsActiveStatus(selections, options);
             this.updateOptionsActiveStatus(selections, options);
             this._notifyChange(selections);
             this._notifyChange(selections);

+ 45 - 0
packages/semi-ui/select/__test__/select.test.js

@@ -1239,6 +1239,51 @@ describe('Select', () => {
         expect(spyOnChange.calledWith(['hotsoon']));
         expect(spyOnChange.calledWith(['hotsoon']));
     });
     });
 
 
+    it('autoClearSearchValue', () => {
+        // default usage
+        let optionList = Array.from({ length: 100 }, (v, i) => ({ label: `option-${i}`, value: i }));
+
+        let props = {
+            multiple: true,
+            optionList: optionList,
+            defaultOpen: true,
+            filter: true,
+        };
+        let select = getSelect(props);
+        select.find(`.${BASE_CLASS_PREFIX}-select`).simulate('click', {});
+        let keyword = 'option';
+        let event = { target: { value: keyword } };
+        select.find('input').simulate('change', event);
+
+        let options = select.find(`.${BASE_CLASS_PREFIX}-select-option-list`).children();
+        const nativeEvent = { nativeEvent: { stopImmediatePropagation: noop } };
+        options.at(0).simulate('click', nativeEvent);
+        let inputValue = select.find('input').getDOMNode().value;
+        expect(inputValue).toEqual('');
+    });
+
+    it('autoClearSearchValue = false', () => {
+        let optionList = Array.from({ length: 100 }, (v, i) => ({ label: `option-${i}`, value: i }));
+
+        let props = {
+            multiple: true,
+            optionList: optionList,
+            defaultOpen: true,
+            autoClearSearchValue: false,
+            filter: true,
+        };
+        let select = getSelect(props);
+        select.find(`.${BASE_CLASS_PREFIX}-select`).simulate('click', {});
+        let keyword = 'option';
+        let event = { target: { value: keyword } };
+        select.find('input').simulate('change', event);
+
+        let options = select.find(`.${BASE_CLASS_PREFIX}-select-option-list`).children();
+        const nativeEvent = { nativeEvent: { stopImmediatePropagation: noop } };
+        options.at(0).simulate('click', nativeEvent);
+        let inputValue = select.find('input').getDOMNode().value;
+        expect(inputValue).toEqual(keyword);
+    });
     // TODO ref selectAll \deselectAll when onChangeWithObject is true
     // TODO ref selectAll \deselectAll when onChangeWithObject is true
     // TODO when loading is true, do not response any keyborard event
     // TODO when loading is true, do not response any keyborard event
     // TODO can't remove tag when option is diabled
     // TODO can't remove tag when option is diabled

+ 29 - 0
packages/semi-ui/select/_story/select.stories.js

@@ -2875,3 +2875,32 @@ SelectInputPropsDemo.story = {
   name: 'inputProps',
   name: 'inputProps',
 };
 };
 
 
+
+export const AutoClearSearchValue = () => {
+    const [val, setVal] = useState(['semi1']);
+    const optionList = [
+        { label: 'semi1', value: 'semi1' },
+        { label: 'semi2', value: 'semi2' },
+        { label: 'semi3', value: 'semi3' },
+        { label: 'semi4', value: 'semi4' },
+        { label: 'semi5', value: 'semi5' },
+        { label: 'semi6', value: 'semi6' },
+    ];
+
+    return (
+        <>
+            <h4>Controlled mode + multiple</h4>
+            <Select style={{ width: 400 }} multiple optionList={optionList} filter value={val} onChange={value => setVal(value)} autoClearSearchValue={false}></Select>
+            <br />
+            <br />
+            <h4>Uncontrolled mode + multiple</h4>
+            <Select style={{ width: 400 }} multiple optionList={optionList} filter autoClearSearchValue={false}></Select>
+            <h4>Uncontrolled mode + multiple + defaultValue</h4>
+            <Select style={{ width: 400 }}  multiple optionList={optionList} filter defaultValue={['semi2']} autoClearSearchValue={false}></Select>
+        </>
+    )
+}
+
+SelectInputPropsDemo.story = {
+  name: 'AutoClearSearchValue',
+};

+ 3 - 0
packages/semi-ui/select/index.tsx

@@ -87,6 +87,7 @@ export type RenderSelectedItemFn = RenderSingleSelectedItemFn | RenderMultipleSe
 export type SelectProps = {
 export type SelectProps = {
     id?: string;
     id?: string;
     autoFocus?: boolean;
     autoFocus?: boolean;
+    autoClearSearchValue?: boolean;
     arrowIcon?: React.ReactNode;
     arrowIcon?: React.ReactNode;
     defaultValue?: string | number | any[] | Record<string, any>;
     defaultValue?: string | number | any[] | Record<string, any>;
     value?: string | number | any[] | Record<string, any>;
     value?: string | number | any[] | Record<string, any>;
@@ -181,6 +182,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
 
 
     static propTypes = {
     static propTypes = {
         autoFocus: PropTypes.bool,
         autoFocus: PropTypes.bool,
+        autoClearSearchValue: PropTypes.bool,
         children: PropTypes.node,
         children: PropTypes.node,
         defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]),
         defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]),
         value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]),
         value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]),
@@ -292,6 +294,7 @@ class Select extends BaseComponent<SelectProps, SelectState> {
         showClear: false,
         showClear: false,
         remote: false,
         remote: false,
         autoAdjustOverflow: true,
         autoAdjustOverflow: true,
+        autoClearSearchValue: true,
         arrowIcon: <IconChevronDown />
         arrowIcon: <IconChevronDown />
         // Radio selection is different from the default renderSelectedItem for multiple selection, so it is not declared here
         // Radio selection is different from the default renderSelectedItem for multiple selection, so it is not declared here
         // renderSelectedItem: (optionNode) => optionNode.label,
         // renderSelectedItem: (optionNode) => optionNode.label,