瀏覽代碼

fix: [Autocomplete] Fix the problem that when the AutoComplete panel is open, the option panel cannot be closed by clicking outside (#1684)

YyumeiZhang 2 年之前
父節點
當前提交
7a40f6ca9e

+ 8 - 0
cypress/e2e/autoComplete.spec.js

@@ -61,4 +61,12 @@ describe('autoComplete', () => {
         cy.get('input').should('have.value', '[email protected]');
     });
 
+    it.only('click outer side handler', () => {
+        cy.visit('http://127.0.0.1:6006/iframe.html?id=autocomplete--basic-usage&args=&viewMode=story');
+        cy.get('input').type('123');
+        cy.get('.semi-portal').contains('[email protected]');
+        cy.get('body').click();
+        cy.get('.semi-portal').should('not.exist');
+    });
+
 });

+ 6 - 4
packages/semi-foundation/autoComplete/foundation.ts

@@ -39,7 +39,9 @@ export interface AutoCompleteAdapter<P = Record<string, any>, S = Record<string,
     notifyFocus: (event?: any) => void;
     notifyBlur: (event?: any) => void;
     rePositionDropdown: () => void;
-    persistEvent: (event: any) => void
+    persistEvent: (event: any) => void;
+    registerClickOutsideHandler: (cb: (e: any) => void) => void;
+    unregisterClickOutsideHandler: () => void
 }
 
 class AutoCompleteFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<AutoCompleteAdapter<P, S>, P, S> {
@@ -79,7 +81,7 @@ class AutoCompleteFoundation<P = Record<string, any>, S = Record<string, any>> e
     }
 
     destroy(): void {
-        // this._adapter.unregisterClickOutsideHandler();
+        this._adapter.unregisterClickOutsideHandler();
         // this.unBindKeyBoardEvent();
     }
 
@@ -114,7 +116,7 @@ class AutoCompleteFoundation<P = Record<string, any>, S = Record<string, any>> e
         this.isPanelOpen = true;
         this._adapter.toggleListVisible(true);
         this._setDropdownWidth();
-        // this._adapter.registerClickOutsideHandler(e => this.closeDropdown(e));
+        this._adapter.registerClickOutsideHandler(e => this.closeDropdown(e));
         this._adapter.notifyDropdownVisibleChange(true);
         this._modifyFocusIndexOnPanelOpen();
     }
@@ -122,7 +124,7 @@ class AutoCompleteFoundation<P = Record<string, any>, S = Record<string, any>> e
     closeDropdown(e?: any): void {
         this.isPanelOpen = false;
         this._adapter.toggleListVisible(false);
-        // this._adapter.unregisterClickOutsideHandler();
+        this._adapter.unregisterClickOutsideHandler();
         this._adapter.notifyDropdownVisibleChange(false);
         // After closing the panel, you can still open the panel by pressing the enter key
         // this.unBindKeyBoardEvent();

+ 26 - 2
packages/semi-ui/autoComplete/index.tsx

@@ -18,6 +18,7 @@ import Trigger from '../trigger';
 import Option from './option';
 import warning from '@douyinfe/semi-foundation/utils/warning';
 import '@douyinfe/semi-foundation/autoComplete/autoComplete.scss';
+import ReactDOM from 'react-dom';
 
 const prefixCls = cssClasses.PREFIX;
 const sizeSet = strings.SIZE;
@@ -197,7 +198,7 @@ class AutoComplete<T extends AutoCompleteItems> extends BaseComponent<AutoComple
     triggerRef: React.RefObject<HTMLDivElement> | null;
     optionsRef: React.RefObject<HTMLDivElement> | null;
 
-    private clickOutsideHandler: () => void | null;
+    private clickOutsideHandler: (e: Event) => void | null;
 
     constructor(props: AutoCompleteProps<T>) {
         super(props);
@@ -295,7 +296,30 @@ class AutoComplete<T extends AutoCompleteItems> extends BaseComponent<AutoComple
                 let { rePosKey } = this.state;
                 rePosKey = rePosKey + 1;
                 this.setState({ rePosKey });
-            }
+            },
+            registerClickOutsideHandler: cb => {
+                const clickOutsideHandler = (e: Event) => {
+                    const optionInstance = this.optionsRef && this.optionsRef.current;
+                    const triggerDom = this.triggerRef && this.triggerRef.current;
+                    const optionsDom = ReactDOM.findDOMNode(optionInstance);
+                    const target = e.target as Element;
+                    if (
+                        optionsDom &&
+                        (!optionsDom.contains(target) || !optionsDom.contains(target.parentNode)) &&
+                        triggerDom &&
+                        !triggerDom.contains(target)
+                    ) {
+                        cb(e);
+                    }
+                };
+                this.clickOutsideHandler = clickOutsideHandler;
+                document.addEventListener('mousedown', clickOutsideHandler, false);
+            },
+            unregisterClickOutsideHandler: () => {
+                if (this.clickOutsideHandler) {
+                    document.removeEventListener('mousedown', this.clickOutsideHandler, false);
+                }
+            },
         };
     }