Browse Source

fix: fix the wrong timing of onBlur/onFocus calls in TreeSelect (#1444)

* fix: [TreeSelect] Fix onFocus/onBlur call timing of TreeSelect

* fix: [TreeSelect] Fix the problem that the preventScroll parameter in TreeSelect is not declared and not transparently transmitted

* fix: remove unnecessary code
YyumeiZhang 2 years ago
parent
commit
f3a5d4189c

+ 23 - 0
cypress/integration/treeSelect.spec.js

@@ -61,4 +61,27 @@ describe('treeSelect', () => {
         cy.get('.semi-tagInput-wrapper .semi-tag').should('not.exist');
     });
 
+    it('onblur / onfocus', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=treeselect--on-blur-on-focus&args=&viewMode=story', {
+            onBeforeLoad(win) {
+                cy.stub(win.console, 'log').as('consoleLog');
+            },
+        });
+        // 单选,点击 trigger 触发 onFocus, 点击选项触发 onBlur,
+        cy.get('.semi-tree-select').eq(0).click();
+        cy.get('@consoleLog').should('be.calledWith', 'focus');
+        cy.get('.semi-tree-option').eq(0).click();
+        cy.get('@consoleLog').should('be.calledWith', 'blur');
+        // 单选,点击 trigger 触发 onFocus,再次点击 trigger 收起面板,但不会触发 onBlur, 点击外部触发 onBlur,
+        cy.get('.semi-tree-select').eq(0).click();
+        cy.get('@consoleLog').should('be.calledWith', 'focus');
+        cy.get('.semi-tree-select').eq(0).click();
+        cy.get('.semi-tree-select').eq(1).click();
+        cy.get('@consoleLog').should('be.calledWith', 'blur');
+        // 多选, 点击 trigger 触发 onFocus,在此点击再次点击收起面板,但不会触发 onBlur, 点击外部触发 onBlur,
+        cy.get('@consoleLog').should('be.calledWith', 'focus');
+        cy.get('.semi-tree-select').eq(1).click();
+        cy.get('.semi-tree-select').eq(2).click();
+        cy.get('@consoleLog').should('be.calledWith', 'blur');
+    });
 });

+ 39 - 15
packages/semi-foundation/treeSelect/foundation.ts

@@ -95,6 +95,7 @@ export interface BasicTreeSelectProps extends Pick<BasicTreeProps,
 | 'disableStrictly'
 | 'aria-label'
 | 'checkRelation'
+| 'preventScroll'
 > {
     motion?: Motion;
     mouseEnterDelay?: number;
@@ -163,7 +164,7 @@ export interface BasicTreeSelectInnerData extends Pick<BasicTreeInnerData,
 > {
     inputTriggerFocus: boolean;
     isOpen: boolean;
-    isInput: boolean;
+    // isInput: boolean;
     rePosKey: number;
     dropdownMinWidth: null | number;
     isHovering: boolean;
@@ -191,7 +192,8 @@ export interface TreeSelectAdapter<P = Record<string, any>, S = Record<string, a
     toggleHovering: (bool: boolean) => void;
     notifyLoad: (newLoadedKeys: Set<string>, data: BasicTreeNodeData) => void;
     updateInputFocus: (bool: boolean) => void;
-    updateLoadKeys: (data: BasicTreeNodeData, resolve: (value?: any) => void) => void
+    updateLoadKeys: (data: BasicTreeNodeData, resolve: (value?: any) => void) => void;
+    updateIsFocus: (bool: boolean) => void
 }
 
 // eslint-disable-next-line max-len
@@ -210,6 +212,9 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
         if (isOpen) {
             this.open();
         }
+        if (triggerSearchAutoFocus) {
+            this.handleTriggerFocus(null);
+        }
     }
 
     destroy() {
@@ -424,12 +429,25 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
         }
     }
 
+    _registerClickOutsideHandler = (e) => {
+        this._adapter.registerClickOutsideHandler(e => {
+            this.handlerTriggerBlur(e);
+            this.close(e);
+        });
+    }
+
     // Scenes that may trigger focus:
     //  1、click selection
     _notifyFocus(e: any) {
         this._adapter.notifyFocus(e);
     }
 
+    handleTriggerFocus(e: any) {
+        this._adapter.updateIsFocus(true);
+        this._notifyFocus(e);
+        this._registerClickOutsideHandler(e);
+    }
+
     // Scenes that may trigger blur
     //  1、clickOutSide
     //  2、click option / press enter, and then select complete(when multiple is false
@@ -438,6 +456,12 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
         this._adapter.notifyBlur(e);
     }
 
+    handlerTriggerBlur(e) {
+        this._adapter.updateIsFocus(false);
+        this._notifyBlur(e);
+        this._adapter.unregisterClickOutsideHandler();
+    }
+
     toggleHoverState(bool: boolean) {
         this._adapter.toggleHovering(bool);
     }
@@ -445,15 +469,10 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
     open() {
         this._adapter.openMenu();
         this._setDropdownWidth();
-        this._adapter.registerClickOutsideHandler(e => {
-            this.close(e);
-        });
     }
 
     close(e: any) {
         this._adapter.closeMenu();
-        this._adapter.unregisterClickOutsideHandler();
-        this._notifyBlur(e);
         if (this.getProp('motionExpand')) {
             this._adapter.updateState({ motionKeys: new Set([]) });
         }
@@ -461,18 +480,22 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
 
     handleClick(e: any) {
         const isDisabled = this._isDisabled();
-        const { isOpen, inputValue } = this.getStates();
+        const { isOpen, inputValue, isFocus } = 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;
+        } else {
+            if (!isFocus) {
+                this.handleTriggerFocus(e);
+            }
+            if (isOpen) {
+                if (searchPosition === 'trigger' && inputValue) {
+                    return;
+                }
+                this.close(e);
+            } else {
+                this.open();
             }
-            this.close(e);
         }
     }
 
@@ -639,6 +662,7 @@ export default class TreeSelectFoundation<P = Record<string, any>, S = Record<st
         }
         if (clickToHide && (this._isSelectToClose() || !data.children)) {
             this.close(e);
+            this.handlerTriggerBlur(e);
         }
     }
 

+ 45 - 1
packages/semi-ui/treeSelect/_story/treeSelect.stories.jsx

@@ -754,6 +754,50 @@ export const OnBlurOnFocus = () => (
       onFocus={(...args) => console.log('focus', args)}
       placeholder="Please select"
     />
+     <div>single, filterTreeNode, searchPosition=dropdown</div>
+    <TreeSelect
+      filterTreeNode
+      style={{ width: 300 }}
+      dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+      treeData={treeData2}
+      onBlur={(...args) => console.log('blur', args)}
+      onFocus={(...args) => console.log('focus', args)}
+      placeholder="Please select"
+    />
+    <div>multiple, filterTreeNode, searchPosition=dropdown</div>
+    <TreeSelect
+      filterTreeNode
+      style={{ width: 300 }}
+      dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+      treeData={treeData2}
+      multiple
+      onBlur={(...args) => console.log('blur', args)}
+      onFocus={(...args) => console.log('focus', args)}
+      placeholder="Please select"
+    />
+    <div>single, filterTreeNode, searchPosition=trigger</div>
+    <TreeSelect
+      searchPosition="trigger"
+      filterTreeNode
+      style={{ width: 300 }}
+      dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+      treeData={treeData2}
+      onBlur={(...args) => console.log('blur', args)}
+      onFocus={(...args) => console.log('focus', args)}
+      placeholder="Please select"
+    />
+    <div>multiple, filterTreeNode, searchPosition=trigger</div>
+    <TreeSelect
+      searchPosition="trigger"
+      filterTreeNode
+      style={{ width: 300 }}
+      dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
+      treeData={treeData2}
+      multiple
+      onBlur={(...args) => console.log('blur', args)}
+      onFocus={(...args) => console.log('focus', args)}
+      placeholder="Please select"
+    />
   </>
 );
 
@@ -2041,4 +2085,4 @@ class ValueTypeIsNumber extends React.Component {
   }
 }
 
-export const valueIsNumber = () => <ValueTypeIsNumber />
+export const valueIsNumber = () => <ValueTypeIsNumber />

+ 17 - 8
packages/semi-ui/treeSelect/index.tsx

@@ -159,11 +159,12 @@ export type OverrideCommonState =
 export interface TreeSelectState extends Omit<BasicTreeSelectInnerData, OverrideCommonState | 'prevProps'>, Pick<TreeState, OverrideCommonState> {
     inputTriggerFocus: boolean;
     isOpen: boolean;
-    isInput: boolean;
+    // isInput: boolean;
     rePosKey: number;
     dropdownMinWidth: null | number;
     isHovering: boolean;
-    prevProps: TreeSelectProps
+    prevProps: TreeSelectProps;
+    isFocus: boolean
 }
 
 const prefixcls = cssClasses.PREFIX;
@@ -261,6 +262,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         'aria-label': PropTypes.string,
         showRestTagsPopover: PropTypes.bool,
         restTagsPopoverProps: PropTypes.object,
+        preventScroll: PropTypes.bool,
     };
 
     static defaultProps: Partial<TreeSelectProps> = {
@@ -310,7 +312,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
         this.state = {
             inputTriggerFocus: false,
             isOpen: false,
-            isInput: false,
+            isFocus: false,
+            // isInput: false,
             rePosKey: key,
             dropdownMinWidth: null,
             inputValue: '',
@@ -639,7 +642,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             updateInputFocus: bool => { 
                 if (bool) {
                     if (this.inputRef && this.inputRef.current) {
-                        (this.inputRef.current as any).focus();
+                        const { preventScroll } = this.props;
+                        (this.inputRef.current as any).focus({ preventScroll });
                     }
                     if (this.tagInputRef && this.tagInputRef.current) {
                         this.tagInputRef.current.focus();
@@ -652,7 +656,10 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
                         this.tagInputRef.current.blur();
                     }
                 }
-            } // eslint-disable-line
+            },
+            updateIsFocus: bool => {
+                this.setState({ isFocus: bool });
+            } 
         };
     }
 
@@ -960,7 +967,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             searchPosition,
             triggerRender,
         } = this.props;
-        const { isOpen, isInput, inputValue, selectedKeys, checkedKeys, keyEntities } = this.state;
+        const { inputValue, selectedKeys, checkedKeys, keyEntities, isFocus } = this.state;
         const filterable = Boolean(filterTreeNode);
         const useCustomTrigger = typeof triggerRender === 'function';
         const mouseEvent = showClear ?
@@ -977,7 +984,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             cls(
                 prefixcls,
                 {
-                    [`${prefixcls}-focus`]: isOpen && !isInput,
+                    [`${prefixcls}-focus`]: isFocus,
                     [`${prefixcls}-disabled`]: disabled,
                     [`${prefixcls}-single`]: !multiple,
                     [`${prefixcls}-multiple`]: multiple,
@@ -1122,7 +1129,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
             showRestTagsPopover, 
             restTagsPopoverProps,
             searchPosition,
-            filterTreeNode
+            filterTreeNode,
+            preventScroll
         } = this.props;
         const {
             keyEntities,
@@ -1161,6 +1169,7 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
                 renderTagItem={(itemKey, index) => this.renderTagItem(itemKey, index)}
                 onRemove={itemKey => this.removeTag(itemKey)}
                 expandRestTagsOnClick={false}
+                preventScroll={preventScroll}
             />
         );
     };