Browse Source

Merge branch 'main' into release

DaiQiangReal 3 years ago
parent
commit
8235f05eb6

+ 1 - 1
content/input/input/index-en-US.md

@@ -457,7 +457,7 @@ Answers to some questions:
 
 ### InputGroup
 
-Common attributes will be set to the child elements of InputGroup, such as disabled, onFocus, etc. If the child sets the same attribute separately, the child has a higher priority.
+Common attributes will be set to the child elements of InputGroup, such as disabled, onFocus, etc. If you set onFocus, onBlur or disabled on the child, it will override the corresponding property value of InputGroup.
 
 
 | Property      | Instructions                                | Type                                                          | Default |

+ 1 - 1
content/input/input/index.md

@@ -465,7 +465,7 @@ import { Input, Typography, Form, TextArea, Button } from '@douyinfe/semi-ui';
 
 ### InputGroup
 
-通用属性将设置到 InputGroup 的子级元素上,例如 disabled、onFocus 等,如果子级单独设置了相同属性,子级的优先级更高
+通用属性将设置到 InputGroup 的子级元素上,例如 disabled、onFocus 等。如果你在子级设置了 onFocus、onBlur 或 disabled,会覆盖掉 InputGroup 对应属性值
 
 
 | 属性          | 说明                           | 类型                                                          | 默认值    |

+ 11 - 30
cypress/integration/cascader.spec.js

@@ -2,39 +2,27 @@ describe('cascader', () => {
     it('clear when single choose', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--show-clear&args=&viewMode=story');
         cy.viewport(1500, 1000);
-
-        cy.get('input').eq(0).click();
-        cy.get('span').contains('Node1').click();
-        cy.get('span').contains('Child Node2').click();
-        cy.get('input').should('have.value', 'Node1 / Child Node2');
-        cy.get('input').eq(0).trigger('mouseover');
-        cy.get('.semi-cascader-clearbtn').click();
-        cy.get('input').should('have.value', '');
-
-        // clear when search
-        cy.get('input').eq(0).click();
-        // wait the panel open
-        cy.wait(100);
+        cy.get('.semi-cascader-selection').eq(0).click();
         cy.get('span').contains('Node1').click();
         cy.get('span').contains('Child Node2').click();
-        cy.get('input').eq(0).type('Node1');
-        cy.get('input').eq(0).trigger('mouseover');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Child Node2').should('exist');
+        cy.get('.semi-cascader-selection').eq(0).trigger('mouseover');
         cy.get('.semi-cascader-clearbtn').click();
-        cy.get('input').should('have.attr', 'placeholder', 'Node1 / Child Node2');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Child Node2').should('not.exist');
     });
 
     it('clear by key press', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--show-clear&args=&viewMode=story');
         cy.viewport(1500, 1000);
 
-        cy.get('input').eq(0).click();
+        cy.get('.semi-cascader-selection').eq(0).click();
         cy.get('span').contains('Node1').click();
         cy.get('span').contains('Child Node2').click();
-        cy.get('input').should('have.value', 'Node1 / Child Node2');
-        cy.get('input').eq(0).trigger('mouseover');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Child Node2').should('exist');
+        cy.get('.semi-cascader-selection').eq(0).trigger('mouseover');
         cy.get(".semi-cascader-clearbtn").focus();
         cy.get('.semi-cascader-clearbtn').type('{enter}');
-        cy.get('input').should('have.value', '');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Child Node2').should('not.exist');;
         cy.get('#root').click('right');
 
         cy.get('.semi-cascader').eq(1).click();
@@ -68,14 +56,7 @@ describe('cascader', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--dynamic-placeholder&args=&viewMode=story');
         cy.get('.semi-cascader-selection-placeholder').contains('Please select');
         cy.get('.semi-button-content').contains('Toggle').click();
-        cy.get('.semi-input-default').should('have.attr', 'placeholder', 'Search something');
-    });
-
-    it('placeholder change', () => {
-        cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--dynamic-placeholder&args=&viewMode=story');
-        cy.get('.semi-cascader-selection-placeholder').contains('Please select');
-        cy.get('.semi-button-content').contains('Toggle').click();
-        cy.get('.semi-input-default').should('have.attr', 'placeholder', 'Search something');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('Search something').should('exist');
     });
 
     it('load data', () => {
@@ -112,7 +93,7 @@ describe('cascader', () => {
     it('not exit default value', () => {
         cy.visit('http://127.0.0.1:6006/iframe.html?id=cascader--default-value-not-exist&args=&viewMode=story');
         
-        cy.get('input').should('have.value', 'yazhou not exist');
+        cy.get('.semi-cascader-search-wrapper span').eq(0).contains('yazhou not exist').should('exist');
     });
    
     it('multiple onChangeWithObject value=undefined', () => {
@@ -121,5 +102,5 @@ describe('cascader', () => {
         cy.get('.semi-cascader').click();
         cy.get('.semi-checkbox').eq(0).click();
         cy.get('.semi-tag-content').contains('亚洲');
-    })
+    });
 });

+ 20 - 0
packages/semi-foundation/cascader/cascader.scss

@@ -175,6 +175,17 @@ $module: #{$prefix}-cascader;
                 height: $height-cascader_selection_tagInput_input_large;
             }
         }
+
+        &-text {
+
+            &-inactive {
+                color: $color-cascader_selection_text_inactive;
+            }
+
+            &-hide {
+                display: none;
+            }
+        }
     }
 
     &-arrow,
@@ -294,8 +305,17 @@ $module: #{$prefix}-cascader;
     .#{$module}-selection {
         .#{$module}-search-wrapper {
             width: 100%;
+            height: $height-cascader_selection_wrapper;
+            display: flex;
+            align-items: center;
+            position: relative;
 
             .#{$prefix}-input-wrapper {
+                position: absolute;
+                top: 0;
+                left: 0;
+                border: none;
+                background-color: transparent;
                 height: 100%;
                 width: 100%;
                 border: $color-cascader_input-border-default;

+ 20 - 0
packages/semi-foundation/cascader/foundation.ts

@@ -184,6 +184,7 @@ export interface BasicCascaderInnerData {
     isFocus?: boolean;
     isInput?: boolean;
     disabledKeys?: Set<string>;
+    showInput?: boolean;
 }
 
 export interface CascaderAdapter extends DefaultAdapter<BasicCascaderProps, BasicCascaderInnerData> {
@@ -209,6 +210,8 @@ export interface CascaderAdapter extends DefaultAdapter<BasicCascaderProps, Basi
     notifyOnLoad: (newLoadedKeys: Set<string>, data: BasicCascaderData) => void;
     notifyListScroll: (e: any, panel: BasicScrollPanelProps) => void;
     notifyOnExceed: (data: BasicEntity[]) => void;
+    toggleInputShow: (show: boolean, cb: () => void) => void;
+    updateFocusState: (focus: boolean) => void,
 }
 
 // eslint-disable-next-line max-len
@@ -485,9 +488,11 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
 
     open() {
         const filterable = this._isFilterable();
+        const { multiple } = this.getProps();
         this._adapter.openMenu();
         if (filterable) {
             this._clearInput();
+            !multiple && this.toggle2SearchInput(true);
         }
         if (this._isControlledComponent()) {
             this.reCalcActiveKeys();
@@ -525,10 +530,25 @@ export default class CascaderFoundation extends BaseFoundation<CascaderAdapter,
                 inputValue = this.renderDisplayText([...selectedKeys][0]);
             }
             this._adapter.updateStates({ inputValue });
+            !multiple && this.toggle2SearchInput(false);
+            !multiple && this._adapter.updateFocusState(false);
         }
         this._notifyBlur(e);
     }
 
+    toggle2SearchInput(isShow: boolean) {
+        if (isShow) {
+            this._adapter.toggleInputShow(isShow, () => this.focusInput());
+        } else {
+            this._adapter.toggleInputShow(isShow, () => undefined);
+        }
+    }
+
+    focusInput() {
+        this._adapter.focusInput();
+        this._adapter.updateFocusState(true);
+    }
+
     getMergedMotion = () => {
         const { motion } = this.getProps();
         const { isSearching } = this.getStates();

+ 2 - 0
packages/semi-foundation/cascader/variables.scss

@@ -54,6 +54,7 @@ $spacing-cascader_clearBtn-marginRight: 12px; // 级联选择触发器清空按
 
 $color-cascader_selection_n-text-default: var(--semi-color-text-0); // 超出 maxTagCount 后,+n 的文字默认颜色
 $color-cascader_selection_n-text-disabled: var(--semi-color-disabled-text); // 超出 maxTagCount 后,+n 的文字disabled颜色
+$color-cascader_selection_text_inactive: var(--semi-color-text-2); // 级联选择单选inpu输入框和text并存时,text颜色
 $color-cascader_selection-text-default: var(--semi-color-text-0); // 级联选择选中项文字颜色
 $color-cascader_placeholder-text-default: var(--semi-color-text-2); // 级联选择未选中项文字颜色
 $color-cascader-icon-default: var(--semi-color-text-2); // 级联选择图标颜色 - 默认
@@ -94,6 +95,7 @@ $height-cascader_option_list: 180px; // 级联选择菜单高度
 $height-cascader_selection_tagInput_input_small: 22px;
 $height-cascader_selection_tagInput_input_default: 30px;
 $height-cascader_selection_tagInput_input_large: 38px;
+$height-cascader_selection_wrapper: 30px;
 
 $spacing-cascader_text-marginX: $spacing-base-tight; // 级联选择 prefix/suffix 文字水平内间距
 $spacing-cascader_icon-marginX: $spacing-tight; // 级联选择 prefix/suffix 图标水平内间距

+ 5 - 1
packages/semi-foundation/treeSelect/foundation.ts

@@ -429,13 +429,17 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
 
     handleClick(e: any) {
         const isDisabled = this._isDisabled();
-        const { isOpen } = this.getStates();
+        const { isOpen, inputValue } = this.getStates();
+        const { searchPosition } = this.getProps();
         if (isDisabled) {
             return;
         } else if (!isOpen) {
             this.open();
             this._notifyFocus(e);
         } else if (isOpen) {
+            if (searchPosition === 'trigger' && inputValue) {
+                return;
+            }
             this.close(e);
         }
     }

+ 0 - 1
packages/semi-ui/autoComplete/index.tsx

@@ -169,7 +169,6 @@ class AutoComplete<T extends AutoCompleteItems> extends BaseComponent<AutoComple
         position: 'bottomLeft' as const,
         data: [] as [],
         showClear: false,
-        disabled: false,
         size: 'default' as const,
         onFocus: noop,
         onSearch: noop,

+ 13 - 10
packages/semi-ui/cascader/__test__/cascader.test.js

@@ -427,10 +427,10 @@ describe('Cascader', () => {
         });
         expect(
             cascaderWithSingleFilter
-            .find(`.${BASE_CLASS_PREFIX}-cascader-search-wrapper .${BASE_CLASS_PREFIX}-input`)
+            .find(`.${BASE_CLASS_PREFIX}-cascader-search-wrapper span`)
             .getDOMNode()
-            .getAttribute('value')
-        ).toEqual('亚洲 / 中国');
+            .textContent
+            ).toEqual('亚洲 / 中国');
         cascaderWithSingleFilter.unmount();
 
         const cascaderWithSingleControlled = render({
@@ -550,10 +550,10 @@ describe('Cascader', () => {
         expect(searchWrapper.exists()).toEqual(true);
         expect(
             searchWrapper
-                .find('input')
-                .instance()
-                .getAttribute('placeholder')
-        ).toEqual('placeholder');
+                .find('span')
+                .getDOMNode()
+                .textContent
+            ).toEqual('placeholder');
     });
 
     it('onSearch', () => {
@@ -563,10 +563,11 @@ describe('Cascader', () => {
             filterTreeNode: true,
             onSearch: spyOnSearch,
         });
-        const searchWrapper = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-search-wrapper`);
         let searchValue = '${BASE_CLASS_PREFIX}';
         let event = { target: { value: searchValue } };
-        searchWrapper.find('input').simulate('change', event);
+        cascader.simulate('click');
+        const input = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection input`);
+        input.simulate('change', event);
         expect(spyOnSearch.calledOnce).toBe(true);
         expect(spyOnSearch.calledWithMatch(searchValue)).toBe(true);
     });
@@ -1228,8 +1229,10 @@ describe('Cascader', () => {
             separator: ' > ',
             defaultOpen: true,
         });
+        const span = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection span`);
+        expect(span.getDOMNode().textContent).toEqual('亚洲 > 中国 > 北京'); 
+        cascader.simulate('click');
         const input = cascader.find(`.${BASE_CLASS_PREFIX}-cascader-selection input`);
-        expect(input.props().placeholder).toEqual('亚洲 > 中国 > 北京'); 
         const event = { target: { value: '中国' } };
         input.simulate('change', event);
         expect(

+ 22 - 0
packages/semi-ui/cascader/_story/cascader.stories.js

@@ -396,6 +396,28 @@ Searchable.parameters = {
   chromatic: { disableSnapshot: false },
 }
 
+export const filterTreeNodeAndDisplayRender = () => {
+  return (
+    <>
+      <div>
+          filterTreeNode=true,配合displayRender 使用,回显到input的内容也是符合预期
+      </div>
+      <Cascader
+        filterTreeNode
+        style={{ width: 300 }}
+        treeData={treeData4}
+        placeholder="自定义回填时显示数据的格式"
+        defaultValue={['zhejiang', 'ningbo', 'jiangbei']}
+        displayRender={(item) => {
+          console.log('item', item);
+          return <div>
+              {'已选择:' + item.join(' -> ')}
+          </div>;}}
+      />
+    </>
+  );
+};
+
 export const Disabled = () => {
   return (
     <div>

+ 27 - 11
packages/semi-ui/cascader/index.tsx

@@ -181,7 +181,6 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         showClear: false,
         autoClearSearchValue: true,
         changeOnSelect: false,
-        disabled: false,
         disableStrictly: false,
         autoMergeValue: true,
         multiple: false,
@@ -249,6 +248,7 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             loadingKeys: new Set(),
             /* Mark whether this rendering has triggered asynchronous loading of data */
             loading: false,
+            showInput: false,
         };
         this.options = {};
         this.isEmpty = false;
@@ -365,6 +365,14 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
             },
             notifyOnExceed: data => this.props.onExceed(data),
             notifyClear: () => this.props.onClear(),
+            toggleInputShow: (showInput: boolean, cb: (...args: any) => void) => {
+                this.setState({ showInput }, () => {
+                    cb();
+                });
+            },
+            updateFocusState: (isFocus: boolean) => {
+                this.setState({ isFocus });
+            },
         };
     }
 
@@ -544,20 +552,28 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
     renderInput() {
         const { size, disabled } = this.props;
         const inputcls = cls(`${prefixcls}-input`);
-        const { inputValue, inputPlaceHolder } = this.state;
+        const { inputValue, inputPlaceHolder, showInput } = this.state;
         const inputProps = {
             disabled,
             value: inputValue,
             className: inputcls,
             onChange: this.handleInputChange,
-            placeholder: inputPlaceHolder,
         };
         const wrappercls = cls({
             [`${prefixcls}-search-wrapper`]: true,
         });
+
+        const displayText = this.renderDisplayText();
+        const spanCls = cls({
+            [`${prefixcls}-selection-placeholder`]: !displayText,
+            [`${prefixcls}-selection-text-hide`]: showInput && inputValue,
+            [`${prefixcls}-selection-text-inactive`]: showInput && !inputValue,
+        });
+
         return (
             <div className={wrappercls}>
-                <Input ref={this.inputRef as any} size={size} {...inputProps} />
+                <span className={spanCls}>{displayText ? displayText : inputPlaceHolder}</span>
+                {showInput && <Input ref={this.inputRef as any} size={size} {...inputProps} />}
             </div>
         );
     }
@@ -868,9 +884,9 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
         const { isOpen, isFocus, isInput, checkedKeys } = this.state;
         const filterable = Boolean(filterTreeNode);
         const useCustomTrigger = typeof triggerRender === 'function';
-        const classNames = useCustomTrigger
-            ? cls(className)
-            : cls(prefixcls, className, {
+        const classNames = useCustomTrigger ?
+            cls(className) :
+            cls(prefixcls, className, {
                 [`${prefixcls}-focus`]: isFocus || (isOpen && !isInput),
                 [`${prefixcls}-disabled`]: disabled,
                 [`${prefixcls}-single`]: true,
@@ -882,12 +898,12 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                 [`${prefixcls}-with-prefix`]: prefix || insetLabel,
                 [`${prefixcls}-with-suffix`]: suffix,
             });
-        const mouseEvent = showClear
-            ? {
+        const mouseEvent = showClear ?
+            {
                 onMouseEnter: () => this.handleMouseOver(),
                 onMouseLeave: () => this.handleMouseLeave(),
-            }
-            : {};
+            } :
+            {};
         const sectionCls = cls(`${prefixcls}-selection`, {
             [`${prefixcls}-selection-multiple`]: multiple && !isEmpty(checkedKeys),
         });

+ 0 - 1
packages/semi-ui/datePicker/datePicker.tsx

@@ -139,7 +139,6 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
         type: 'date',
         size: 'default',
         density: 'default',
-        disabled: false,
         multiple: false,
         defaultOpen: false,
         disabledHours: noop,

+ 31 - 0
packages/semi-ui/input/__test__/input.test.js

@@ -3,6 +3,7 @@ import Icon from '../../icons/index';
 import { BASE_CLASS_PREFIX } from '../../../semi-foundation/base/constants';
 import GraphemeSplitter from 'grapheme-splitter';
 import { isString, isFunction } from 'lodash';
+import { InputGroup, InputNumber } from '../../index';
 
 function getValueLength(str) {
   if (isString(str)) {
@@ -251,4 +252,34 @@ describe('Input', () => {
       expect(truncateValue(value, length, fc)).toBe(result);
     }
   })
+
+  it('input group', () => {
+    const groupFocus = sinon.spy(() => {
+      console.log('group focus');
+    });
+    const groupBlur = sinon.spy(() => {
+      console.log('group focus');
+    });
+    const inputFocus = sinon.spy(() => {
+      console.log('input focus');
+    });
+    const inputBlur = sinon.spy(() => {
+      console.log('input blur');
+    });
+    const inputGroup = mount(
+      <InputGroup disabled={true} onFocus={groupFocus} onBlur={groupBlur}>
+          <Input disabled={false} onFocus={inputFocus} onBlur={inputBlur} placeholder="Name" style={{ width: 100 }} />
+          <InputNumber placeholder="Score" style={{ width: 140 }} />
+      </InputGroup>
+    );
+
+    inputGroup.find('input').at(0).simulate('focus');
+    expect(inputFocus.called).toBe(true);
+    expect(groupFocus.called).toBe(true);
+    inputGroup.find('input').at(0).simulate('blur');
+    expect(inputBlur.called).toBe(true);
+    expect(groupBlur.called).toBe(true);
+    expect(inputGroup.find('input').at(0).instance().disabled).toBe(false);
+    expect(inputGroup.find('input').at(1).instance().disabled).toBe(true);
+  })
 });

+ 24 - 1
packages/semi-ui/input/_story/input.stories.js

@@ -22,7 +22,8 @@ import {
   Switch,
   Form,
   Space,
-  Radio
+  Radio,
+  InputNumber
 } from '../../index';
 import './input.scss';
 import RTLWrapper from '../../configProvider/_story/RTLDirection/RTLWrapper';
@@ -936,3 +937,25 @@ export const InputA11y = () => {
   );
 }
 InputA11y.storyName = "input a11y";
+
+export const FixInputGroup = () => {
+  const groupFocus = () => {
+    console.log('group focus');
+  }
+  const groupBlur = () => {
+    console.log('group blur');
+  }
+  const inputFocus = () => {
+    console.log('input focus');
+  }
+  const inputBlur = () => {
+    console.log('input blur');
+  }
+
+  return (
+    <InputGroup disabled={true} onFocus={groupFocus} onBlur={groupBlur}>
+        <Input disabled={false} onFocus={inputFocus} onBlur={inputBlur} placeholder="Name" style={{ width: 100 }} />
+        <InputNumber placeholder="Score" style={{ width: 140 }} />
+    </InputGroup>
+  );
+}

+ 0 - 1
packages/semi-ui/input/index.tsx

@@ -126,7 +126,6 @@ class Input extends BaseComponent<InputProps, InputState> {
         addonAfter: '',
         prefix: '',
         suffix: '',
-        disabled: false,
         readonly: false,
         type: 'text',
         showClear: false,

+ 7 - 6
packages/semi-ui/input/inputGroup.tsx

@@ -7,7 +7,7 @@ import BaseComponent from '../_base/baseComponent';
 import Label, { LabelProps } from '../form/label';
 
 import { noop } from '@douyinfe/semi-foundation/utils/function';
-import { isFunction } from 'lodash';
+import { get, isFunction } from 'lodash';
 
 const prefixCls = cssClasses.PREFIX;
 const sizeSet = strings.SIZE;
@@ -84,7 +84,7 @@ export default class inputGroup extends BaseComponent<InputGroupProps, InputGrou
     }
 
     render() {
-        const { size, style, className, children, label, onBlur: groupOnBlur, onFocus: groupOnFocus, ...rest } = this.props;
+        const { size, style, className, children, label, onBlur: groupOnBlur, onFocus: groupOnFocus, disabled: groupDisabled, ...rest } = this.props;
         const groupCls = cls(
             `${prefixCls}-group`,
             {
@@ -96,10 +96,11 @@ export default class inputGroup extends BaseComponent<InputGroupProps, InputGrou
         if (children) {
             inner = (Array.isArray(children) ? children : [children]).map((item, index) => {
                 if (item) {
-                    const { onBlur: itemOnBlur, onFocus: itemOnFocus } = (item as any).props;
-                    const onBlur = isFunction(itemOnBlur) ? itemOnBlur : groupOnBlur;
-                    const onFocus = isFunction(itemOnFocus) ? itemOnFocus : groupOnFocus;
-                    return React.cloneElement(item as any, { key: index, size, onBlur, onFocus, ...rest });
+                    const { onBlur: itemOnBlur, onFocus: itemOnFocus, disabled: itemDisabled } = (item as any).props;
+                    const onBlur = isFunction(itemOnBlur) && get(itemOnBlur, 'name') !== 'noop'  ? itemOnBlur : groupOnBlur;
+                    const onFocus = isFunction(itemOnFocus) && get(itemOnFocus, 'name') !== 'noop' ? itemOnFocus : groupOnFocus;
+                    const disabled = typeof itemDisabled === 'boolean' ? itemDisabled : groupDisabled;
+                    return React.cloneElement(item as any, { key: index, ...rest, size, onBlur, onFocus, disabled });
                 }
                 return null;
             });

+ 0 - 1
packages/semi-ui/inputNumber/index.tsx

@@ -99,7 +99,6 @@ class InputNumber extends BaseComponent<InputNumberProps, InputNumberState> {
     };
 
     static defaultProps: InputNumberProps = {
-        disabled: false,
         forwardedRef: noop,
         innerButtons: false,
         keepFocus: false,

+ 4 - 1
packages/semi-ui/treeSelect/index.tsx

@@ -265,7 +265,6 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         motionExpand: true,
         expandAll: false,
         zIndex: popoverNumbers.DEFAULT_Z_INDEX,
-        disabled: false,
         disableStrictly: false,
         multiple: false,
         filterTreeNode: false,
@@ -864,6 +863,10 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
     };
 
     search = (value: string) => {
+        const { isOpen } = this.state;
+        if (!isOpen) {
+            this.foundation.open();
+        }
         this.foundation.handleInputChange(value);
     };