Browse Source

fix: (tooltip) adjustOverflow not include spacing & auto just cause splash screen

pointhalo 3 years ago
parent
commit
bd532cf837

+ 5 - 5
packages/semi-foundation/tooltip/foundation.ts

@@ -270,7 +270,6 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
         if (trigger === 'custom') {
             // eslint-disable-next-line
             this._adapter.registerClickOutsideHandler(() => {});
-            this._togglePortalVisible(true);
         }
 
         /**
@@ -571,6 +570,7 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
     // place the dom correctly
     adjustPosIfNeed(position: Position | string, style: Record<string, any>, triggerRect: DOMRect, wrapperRect: DOMRect, containerRect: PopupContainerDOMRect) {
         const { innerWidth, innerHeight } = window;
+        const { spacing } = this.getProps();
 
         if (wrapperRect.width > 0 && wrapperRect.height > 0) {
             // let clientLeft = left + translateX * wrapperRect.width - containerRect.scrollLeft;
@@ -600,10 +600,10 @@ export default class Tooltip<P = Record<string, any>, S = Record<string, any>> e
 
             // The wrapperR ect.top|bottom equivalent cannot be directly used here for comparison, which is easy to cause jitter
 
-            const shouldReverseTop = clientTop < wrapperRect.height && restClientBottom > wrapperRect.height;
-            const shouldReverseLeft = clientLeft < wrapperRect.width && restClientRight > wrapperRect.width;
-            const sholdReverseBottom = restClientBottom < wrapperRect.height && clientTop > wrapperRect.height;
-            const shouldReverseRight = restClientRight < wrapperRect.width && clientLeft > wrapperRect.width;
+            const shouldReverseTop = clientTop < wrapperRect.height + spacing && restClientBottom > wrapperRect.height + spacing;
+            const shouldReverseLeft = clientLeft < wrapperRect.width + spacing && restClientRight > wrapperRect.width + spacing;
+            const sholdReverseBottom = restClientBottom < wrapperRect.height + spacing && clientTop > wrapperRect.height + spacing;
+            const shouldReverseRight = restClientRight < wrapperRect.width + spacing && clientLeft > wrapperRect.width + spacing;
 
             const shouldReverseTopSide = restClientTop < wrapperRect.height && clientBottom > wrapperRect.height;
             const shouldReverseBottomSide = clientBottom < wrapperRect.height && restClientTop > wrapperRect.height;

+ 45 - 1
packages/semi-ui/tooltip/_story/tooltip.stories.js

@@ -1,7 +1,7 @@
 import React, { useState, useMemo } from 'react';
 import Tooltip from '../index';
 import './story.scss';
-import { Tag, Icon, IconButton, Switch, Checkbox, Radio, Button, Select } from '@douyinfe/semi-ui';
+import { Tag, Icon, IconButton, Switch, Checkbox, Radio, Button, Select, InputNumber } from '@douyinfe/semi-ui';
 
 import InTableDemo from './InTable';
 import EdgeDemo from './Edge';
@@ -687,4 +687,48 @@ export const OnClickOutSideDemo = () => {
 }
 OnClickOutSideDemo.story = {
   name: 'OnClickOutSide',
+};
+
+export const AutoAdjustWithSpacing = () => {
+    const [height, setHeight] = useState(84);
+    const [key, setKey] = useState(1);
+    const initSpacing = 8;
+    const [spacing, setSpacing] = useState(initSpacing);
+
+    const change = (height, hasSpace) => {
+        setHeight(height);
+        hasSpace ? setSpacing(initSpacing) : setSpacing(0);
+        setKey(Math.random());
+    };
+
+    return (
+        <div className="demo1">
+            <div>
+                <Tooltip
+                    motion={false}
+                    rePosKey={key}
+                    // spacing={spacing}
+                    content={
+                        <article style={{ boxSizing: 'border-box', height: height }}>
+                            <p>hi bytedance, + padding 20</p>
+                            <p>hi bytedance</p>
+                        </article>
+                    }
+                    position="top"
+                    trigger="custom"
+                    visible={true}
+                >
+                    <Tag>demo</Tag>
+                </Tooltip>
+            </div>
+            <div style={{ marginTop: 200 }}>
+                <Switch onChange={hasSpace => change(height, hasSpace)} checked={spacing === initSpacing ? true : false}></Switch>
+                <InputNumber onChange={height => change(Number(height))} value={height} style={{ width: 200 }} />
+            </div>
+        </div>
+    )
+};
+
+AutoAdjustWithSpacing.story = {
+  name: 'AutoAdjustWithSpacing',
 };

+ 20 - 23
packages/semi-ui/tooltip/index.tsx

@@ -77,6 +77,7 @@ interface TooltipState {
     isInsert: boolean;
     placement: Position;
     transitionStyle: Record<string, any>;
+    isPositionUpdated: boolean;
 }
 
 const prefix = cssClasses.PREFIX;
@@ -167,6 +168,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             isInsert: false,
             placement: props.position || 'top',
             transitionStyle: {},
+            isPositionUpdated: false,
         };
         this.foundation = new TooltipFoundation(this.adapter);
         this.eventManager = new Event();
@@ -197,18 +199,15 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
                         containerStyle: { ...this.state.containerStyle, ...containerStyle },
                     },
                     () => {
-                        /**
-                         * Dangerous: remove setTimeout from here fix #1301
-                         * setTimeout may emit portalInserted event after hiding portal
-                         * Hiding portal will remove portalInserted event listener(normal process)
-                         * then portal can't hide because _togglePortalVisible(false) will found isVisible=false and nowVisible=false(bug here)
-                         */
-                        this.eventManager.emit('portalInserted');
+                        setTimeout(() => {
+                            // waiting child component mounted
+                            this.eventManager.emit('portalInserted');
+                        }, 0);
                     }
                 );
             },
             removePortal: () => {
-                this.setState({ isInsert: false });
+                this.setState({ isInsert: false, isPositionUpdated: false });
             },
             getEventName: () => ({
                 mouseEnter: 'onMouseEnter',
@@ -275,7 +274,11 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
             getDocumentElementBounding: () => document.documentElement.getBoundingClientRect(),
             setPosition: ({ position, ...style }: { position: Position }) => {
                 this.setState(
-                    { containerStyle: { ...this.state.containerStyle, ...style }, placement: position },
+                    { 
+                        containerStyle: { ...this.state.containerStyle, ...style },
+                        placement: position,
+                        isPositionUpdated: true
+                    },
                     () => {
                         this.eventManager.emit('positionUpdated');
                     }
@@ -422,16 +425,10 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         return false;
     };
 
-    willEnter = () => {
-        this.foundation.calcPosition();
-        /**
-         * Dangerous: remove setState in motion fix #1379
-         * because togglePortalVisible callback function will use visible state to notifyVisibleChange
-         * if visible state is old value, then notifyVisibleChange function will not be called
-         * we should ensure that after calling togglePortalVisible, callback function can get right visible value
-         */
-        // this.setState({ visible: true });
-    };
+    // willEnter = () => {
+    // this.foundation.calcPosition();
+    // this.setState({ visible: true });
+    // };
 
     didLeave = () => {
         this.adapter.unregisterClickOutsideHandler();
@@ -489,7 +486,7 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
     };
 
     renderPortal = () => {
-        const { containerStyle = {}, visible, portalEventSet, placement, transitionState } = this.state;
+        const { containerStyle = {}, visible, portalEventSet, placement, transitionState, isPositionUpdated } = this.state;
         const { prefixCls, content, showArrow, style, motion, zIndex } = this.props;
         const { className: propClassName } = this.props;
         const direction = this.context.direction;
@@ -502,15 +499,15 @@ export default class Tooltip extends BaseComponent<TooltipProps, TooltipState> {
         const icon = this.renderIcon();
         const portalInnerStyle = omit(containerStyle, motion ? ['transformOrigin'] : undefined);
         const transformOrigin = get(containerStyle, 'transformOrigin');
-        const inner = motion ? (
-            <TooltipTransition position={placement} willEnter={this.willEnter} didLeave={this.didLeave} motion={motion}>
+        const inner = motion && isPositionUpdated ? (
+            <TooltipTransition position={placement} didLeave={this.didLeave} motion={motion}>
                 {
                     transitionState === 'enter' ?
                         ({ animateCls, animateStyle, animateEvents }) => (
                             <div
                                 className={classNames(className, animateCls)}
                                 style={{
-                                    visibility: 'visible',
+                                    // visibility: 'visible',
                                     ...animateStyle,
                                     transformOrigin,
                                     ...style,