浏览代码

fix: fix click outside handler in webcomponent (#2084)

* fix: fix the inaccurate judgment of clickOutSide in webComponent

* fix: fix trigger relate

* fix: fix the inaccurate judgment of clickOutSide in webComponent

---------

Co-authored-by: zhangyumei.0319 <[email protected]>
changlin0_0 1 年之前
父节点
当前提交
575577f1f3

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

@@ -305,11 +305,13 @@ class AutoComplete<T extends AutoCompleteItems> extends BaseComponent<AutoComple
                     const triggerDom = this.triggerRef && this.triggerRef.current;
                     const optionsDom = ReactDOM.findDOMNode(optionInstance);
                     const target = e.target as Element;
+                    const path = e.composedPath && e.composedPath() || [target];
                     if (
                         optionsDom &&
                         (!optionsDom.contains(target) || !optionsDom.contains(target.parentNode)) &&
                         triggerDom &&
-                        !triggerDom.contains(target)
+                        !triggerDom.contains(target) &&
+                        !(path.includes(triggerDom) || path.includes(optionsDom))
                     ) {
                         cb(e);
                     }

+ 3 - 1
packages/semi-ui/calendar/monthCalendar.tsx

@@ -87,7 +87,9 @@ export default class monthCalendar extends BaseComponent<MonthCalendarProps, Mon
                 const clickOutsideHandler = (e: MouseEvent) => {
                     const cardInstance = this.cardRef && this.cardRef.get(key);
                     const cardDom = ReactDOM.findDOMNode(cardInstance);
-                    if (cardDom && !cardDom.contains(e.target as any)) {
+                    const target = e.target as Element;
+                    const path = e.composedPath && e.composedPath() || [target];
+                    if (cardDom && !cardDom.contains(target) && !path.includes(cardDom)) {
                         cb();
                     }
                 };

+ 3 - 1
packages/semi-ui/cascader/index.tsx

@@ -313,11 +313,13 @@ class Cascader extends BaseComponent<CascaderProps, CascaderState> {
                     const triggerDom = this.triggerRef && this.triggerRef.current;
                     const optionsDom = ReactDOM.findDOMNode(optionInstance);
                     const target = e.target as Element;
+                    const path = e.composedPath && e.composedPath() || [target];
                     if (
                         optionsDom &&
                         (!optionsDom.contains(target) || !optionsDom.contains(target.parentNode)) &&
                         triggerDom &&
-                        !triggerDom.contains(target)
+                        !triggerDom.contains(target) &&
+                        !(path.includes(triggerDom) || path.includes(optionsDom))
                     ) {
                         cb(e);
                     }

+ 9 - 9
packages/semi-ui/datePicker/datePicker.tsx

@@ -258,15 +258,15 @@ export default class DatePicker extends BaseComponent<DatePickerProps, DatePicke
                 this.clickOutSideHandler = e => {
                     const triggerEl = this.triggerElRef && this.triggerElRef.current;
                     const panelEl = this.panelRef && this.panelRef.current;
-                    const isInTrigger = triggerEl && triggerEl.contains(e.target as Node);
-                    const isInPanel = panelEl && panelEl.contains(e.target as Node);
-                    const clickOutSide = !isInTrigger && !isInPanel && this._mounted;
-                    if (this.adapter.needConfirm()) {
-                        clickOutSide && this.props.onClickOutSide();
-                        return;
-                    } else {
-                        if (clickOutSide) {
-                            this.props.onClickOutSide();
+                    const target = e.target as Element;
+                    const path = e.composedPath && e.composedPath() || [target];
+                    if (
+                        !(triggerEl && triggerEl.contains(target)) &&
+                        !(panelEl && panelEl.contains(target)) &&
+                        !(path.includes(triggerEl) || path.includes(panelEl))
+                    ) {
+                        this.props.onClickOutSide();
+                        if (!this.adapter.needConfirm()) {
                             this.foundation.closePanel(e);
                         }
                     }

+ 7 - 4
packages/semi-ui/select/index.tsx

@@ -453,10 +453,13 @@ class Select extends BaseComponent<SelectProps, SelectState> {
                     const optionInstance = this.optionsRef && this.optionsRef.current;
                     const triggerDom = (this.triggerRef && this.triggerRef.current) as Element;
                     const optionsDom = ReactDOM.findDOMNode(optionInstance as ReactInstance);
-                    // let isInPanel = optionsDom && optionsDom.contains(e.target);
-                    // let isInTrigger = triggerDom && triggerDom.contains(e.target);
-                    if (optionsDom && !optionsDom.contains(e.target as Node) &&
-                        triggerDom && !triggerDom.contains(e.target as Node)) {
+                    const target = e.target as Element;
+                    const path = (e as any).composedPath && (e as any).composedPath() || [target];
+
+                    if (!(optionsDom && optionsDom.contains(target)) &&
+                        !(triggerDom && triggerDom.contains(target)) &&
+                        !(path.includes(triggerDom) || path.includes(optionsDom))
+                    ) {
                         cb(e);
                     }
                 };

+ 2 - 1
packages/semi-ui/tagInput/index.tsx

@@ -253,7 +253,8 @@ class TagInput extends BaseComponent<TagInputProps, TagInputState> {
                 const clickOutsideHandler = (e: Event) => {
                     const tagInputDom = this.tagInputRef && this.tagInputRef.current;
                     const target = e.target as Element;
-                    if (tagInputDom && !tagInputDom.contains(target)) {
+                    const path = e.composedPath && e.composedPath() || [target];
+                    if (tagInputDom && !tagInputDom.contains(target) && !path.includes(tagInputDom)) {
                         cb(e);
                     }
                 };

+ 13 - 11
packages/semi-ui/timePicker/TimePicker.tsx

@@ -213,6 +213,7 @@ export default class TimePicker extends BaseComponent<TimePickerProps, TimePicke
     foundation: TimePickerFoundation;
     timePickerRef: React.MutableRefObject<HTMLDivElement>;
     savePanelRef: React.RefObject<HTMLDivElement>;
+    useCustomTrigger: boolean;
 
     clickOutSideHandler: (e: MouseEvent) => void;
 
@@ -236,6 +237,7 @@ export default class TimePicker extends BaseComponent<TimePickerProps, TimePicke
         this.foundation = new TimePickerFoundation(this.adapter);
         this.timePickerRef = React.createRef();
         this.savePanelRef = React.createRef();
+        this.useCustomTrigger = typeof this.props.triggerRender === 'function';
     }
 
     get adapter(): TimePickerAdapter<TimePickerProps, TimePickerState> {
@@ -250,14 +252,15 @@ export default class TimePicker extends BaseComponent<TimePickerProps, TimePicke
                 }
                 this.clickOutSideHandler = e => {
                     const panel = this.savePanelRef && this.savePanelRef.current;
-                    const isInPanel = e.target && panel && panel.contains(e.target as Node);
-                    const isInTimepicker =
-                        this.timePickerRef &&
-                        this.timePickerRef.current &&
-                        this.timePickerRef.current.contains(e.target as Node);
-                    if (!isInTimepicker && !isInPanel) {
-                        const clickedOutside = true;
-                        this.foundation.handlePanelClose(clickedOutside, e);
+                    const trigger = this.timePickerRef && this.timePickerRef.current;
+                    const target = e.target as Element;
+                    const path = e.composedPath && e.composedPath() || [target];
+
+                    if (!(panel && panel.contains(target)) &&
+                        !(trigger && trigger.contains(target)) &&
+                        !(path.includes(trigger) || path.includes(panel))
+                    ) {
+                        this.foundation.handlePanelClose(true, e);
                     }
                 };
                 document.addEventListener('mousedown', this.clickOutSideHandler);
@@ -478,7 +481,6 @@ export default class TimePicker extends BaseComponent<TimePickerProps, TimePicke
         } = this.props;
         const format = this.foundation.getDefaultFormatIfNeed();
         const position = this.foundation.getPosition();
-        const useCustomTrigger = typeof triggerRender === 'function';
 
         const { open, inputValue, invalid, value } = this.state;
         const popupClassName = this.getPopupClassName();
@@ -514,7 +516,7 @@ export default class TimePicker extends BaseComponent<TimePickerProps, TimePicke
 
         const outerProps = {} as { onClick: () => void };
 
-        if (useCustomTrigger) {
+        if (this.useCustomTrigger) {
             outerProps.onClick = this.openPanel;
         }
 
@@ -540,7 +542,7 @@ export default class TimePicker extends BaseComponent<TimePickerProps, TimePicke
                     autoAdjustOverflow={autoAdjustOverflow}
                     stopPropagation={stopPropagation}
                 >
-                    {useCustomTrigger ? (
+                    {this.useCustomTrigger ? (
                         <Trigger
                             triggerRender={triggerRender}
                             disabled={disabled}

+ 7 - 2
packages/semi-ui/tooltip/index.tsx

@@ -349,9 +349,14 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
                     let popupEl = this.containerEl && this.containerEl.current;
                     el = ReactDOM.findDOMNode(el as React.ReactInstance);
                     popupEl = ReactDOM.findDOMNode(popupEl as React.ReactInstance) as HTMLDivElement;
+                    const target = e.target as Element;
+                    const path = (e as any).composedPath && (e as any).composedPath() || [target];
+                    const isClickTriggerToHide = this.props.clickTriggerToHide ? el && (el as any).contains(target) || path.includes(el) : false;
                     if (
-                        (el && !(el as any).contains(e.target) && popupEl && !(popupEl as any).contains(e.target)) ||
-                        (this.props.clickTriggerToHide && el && (el as any).contains(e.target))
+                        el && !(el as any).contains(target) && 
+                        popupEl && !(popupEl as any).contains(target) && 
+                        !(path.includes(popupEl) || path.includes(el)) ||
+                        isClickTriggerToHide
                     ) {
                         this.props.onClickOutSide(e);
                         cb();

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

@@ -1,4 +1,5 @@
 import React, { useState, useMemo, useRef, useCallback, useEffect } from 'react';
+import ReactDOM from 'react-dom';
 import { Icon, Input, Button, Form, Popover, Tag, Typography, CheckboxGroup, TagInput, Switch, Tree } from '../../index';
 import TreeSelect from '../index';
 import { flattenDeep } from 'lodash';
@@ -2684,4 +2685,24 @@ export const Issue1542 = () => {
       />
     </>  
   );
-};
+};
+
+class WebComponentWrapper extends HTMLElement {
+  constructor() {
+      super();
+      this.attachShadow({ mode: 'open' });
+  }
+
+  connectedCallback() {
+      ReactDOM.render(<_TreeSelect />, this.shadowRoot);
+  }
+}
+
+customElements.define('my-web-component', WebComponentWrapper);
+
+export const WebCompTestOutside = () => {
+
+  return (
+    <my-web-component></my-web-component>
+  );
+};

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

@@ -638,6 +638,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
                     const triggerDom = this.triggerRef && this.triggerRef.current;
                     const optionsDom = ReactDOM.findDOMNode(optionInstance);
                     const target = e.target as Element;
+                    const path = e.composedPath && e.composedPath() || [target];
+
                     if (
                         optionsDom &&
                         (
@@ -645,7 +647,8 @@ class TreeSelect extends BaseComponent<TreeSelectProps, TreeSelectState> {
                             !optionsDom.contains(target.parentNode)
                         ) &&
                         triggerDom &&
-                        !triggerDom.contains(target)
+                        !triggerDom.contains(target) &&
+                        !(path.includes(triggerDom) || path.includes(optionsDom))
                     ) {
                         cb(e);
                     }