소스 검색

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.  
 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
 import React from 'react';
 import { Select } from '@douyinfe/semi-ui';
@@ -573,11 +575,12 @@ import { Select } from '@douyinfe/semi-ui';
         </Select>
         <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>
     </>
 );
@@ -1283,8 +1286,9 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | Properties | Instructions | Type | Default |
 | --- | --- | --- | --- |
 | 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 |
+| 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 |
 | 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 |
@@ -1308,7 +1312,7 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | maxHeight | Maximum height of `optionList` in the pop-up layer | string | number | 300 |
 | multiple | Whether allow multiple selection | boolean | false |
 | 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}\]) |  |
 | placeholder | placeholder | ReactNode |  |
 | 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
 import React from 'react';
@@ -625,11 +626,12 @@ import { Select } from '@douyinfe/semi-ui';
             <Select.Option value='xigua'>西瓜视频</Select.Option>
         </Select>
         <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>
     </>
 );
@@ -1298,6 +1300,7 @@ import { Select, Checkbox } from '@douyinfe/semi-ui';
 | allowCreate              | 是否允许用户创建新条目,需配合 filter 使用                                                                                                                                                                | boolean                               | false                             |
 | arrowIcon            | 自定义右侧下拉箭头Icon,当showClear开关打开且当前有选中值时,hover会优先显示clear icon <br/>**v1.15.0 后提供**                                                                                                                                                                 | ReactNode     |                             |
 | autoAdjustOverflow       | 浮层被遮挡时是否自动调整方向(暂时仅支持竖直方向,且插入的父级为 body)                                                                                                            | boolean                               | true                              |
+| autoClearSearchValue     | 选中选项后,是否自动清空搜索关键字,当mutilple、filter都开启时生效<br/>**v2.3.0 后提供**                                                                                          | boolean                               | true                              |
 | autoFocus                | 初始渲染时是否自动 focus                                                                                                                                                                                  | boolean                               | false                             |
 | className                | 类名                                                                                                                                                                                                      | string                                |                                   |
 | 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) {
         const maxLimit = this._adapter.getMaxLimit();
         const selections = this._adapter.getSelections();
-
+        const { autoClearSearchValue } = this.getProps();
         if (selections.has(label)) {
             this._notifyDeselect(value, { value, label, ...rest });
             selections.delete(label);
@@ -420,7 +420,9 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
             // Controlled components, directly notified
             this._notifyChange(selections);
             if (this._isFilterable()) {
-                this.clearInput();
+                if (autoClearSearchValue) {
+                    this.clearInput();
+                }
                 this.focusInput();
             }
         } else {
@@ -431,11 +433,14 @@ export default class SelectFoundation extends BaseFoundation<SelectAdapter> {
             let { options } = this.getStates();
             // Searchable filtering, when selected, resets Input
             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();
-                // 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._notifyChange(selections);

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

@@ -1239,6 +1239,51 @@ describe('Select', () => {
         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 when loading is true, do not response any keyborard event
     // 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',
 };
 
+
+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 = {
     id?: string;
     autoFocus?: boolean;
+    autoClearSearchValue?: boolean;
     arrowIcon?: React.ReactNode;
     defaultValue?: 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 = {
         autoFocus: PropTypes.bool,
+        autoClearSearchValue: PropTypes.bool,
         children: PropTypes.node,
         defaultValue: 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,
         remote: false,
         autoAdjustOverflow: true,
+        autoClearSearchValue: true,
         arrowIcon: <IconChevronDown />
         // Radio selection is different from the default renderSelectedItem for multiple selection, so it is not declared here
         // renderSelectedItem: (optionNode) => optionNode.label,