Browse Source

fix: tabs dynamic tabs disable motion by med

代强 3 years ago
parent
commit
6a25bb3903

+ 4 - 4
packages/semi-ui/_cssAnimation/index.tsx

@@ -19,7 +19,7 @@ interface AnimationProps {
         isAnimating: boolean
         isAnimating: boolean
     }) => ReactNode;
     }) => ReactNode;
     animationState: "enter" | "leave";
     animationState: "enter" | "leave";
-    onAnimationEnd?: () => void;
+    onAnimationEnd?: (stoppedByAnother:boolean) => void;
     onAnimationStart?: () => void;
     onAnimationStart?: () => void;
     motion?: boolean;
     motion?: boolean;
     replayKey?: string
     replayKey?: string
@@ -56,7 +56,7 @@ class CSSAnimation extends React.Component<AnimationProps, AnimationState> {
         // so when there is no animation , it is logically (and only logically) regarded as an animation with a duration of 0.
         // so when there is no animation , it is logically (and only logically) regarded as an animation with a duration of 0.
         this.props.onAnimationStart?.();
         this.props.onAnimationStart?.();
         if (!this.props.motion){
         if (!this.props.motion){
-            this.props.onAnimationEnd?.();
+            this.props.onAnimationEnd?.(false);
         }
         }
     }
     }
 
 
@@ -73,7 +73,7 @@ class CSSAnimation extends React.Component<AnimationProps, AnimationState> {
             }, () => {
             }, () => {
                 this.props.onAnimationStart?.();
                 this.props.onAnimationStart?.();
                 if (!this.props.motion) {
                 if (!this.props.motion) {
-                    this.props.onAnimationEnd?.();
+                    this.props.onAnimationEnd?.(this.state.isAnimating);
                 }
                 }
             });
             });
         }
         }
@@ -92,7 +92,7 @@ class CSSAnimation extends React.Component<AnimationProps, AnimationState> {
             extraStyle: {},
             extraStyle: {},
             isAnimating: false
             isAnimating: false
         }, () => {
         }, () => {
-            this.props.onAnimationEnd?.();
+            this.props.onAnimationEnd?.(false);
         });
         });
     }
     }
 
 

+ 9 - 20
packages/semi-ui/tabs/TabPane.tsx

@@ -4,8 +4,7 @@ import cls from 'classnames';
 import { cssClasses } from '@douyinfe/semi-foundation/tabs/constants';
 import { cssClasses } from '@douyinfe/semi-foundation/tabs/constants';
 import getDataAttr from '@douyinfe/semi-foundation/utils/getDataAttr';
 import getDataAttr from '@douyinfe/semi-foundation/utils/getDataAttr';
 import TabsContext from './tabs-context';
 import TabsContext from './tabs-context';
-import { TabContextValue } from './interface';
-import { PlainTab, TabPaneProps } from './interface';
+import { PlainTab, TabContextValue, TabPaneProps } from './interface';
 import CSSAnimation from "../_cssAnimation";
 import CSSAnimation from "../_cssAnimation";
 
 
 class TabPane extends PureComponent<TabPaneProps> {
 class TabPane extends PureComponent<TabPaneProps> {
@@ -23,32 +22,19 @@ class TabPane extends PureComponent<TabPaneProps> {
         closable: PropTypes.bool
         closable: PropTypes.bool
     };
     };
 
 
-    lastActiveKey: string = null;
 
 
     ref = createRef<HTMLDivElement>();
     ref = createRef<HTMLDivElement>();
     _active: boolean;
     _active: boolean;
     context: TabContextValue;
     context: TabContextValue;
-    rendered: boolean = true;
 
 
-    componentDidMount(): void {
-        this.lastActiveKey = this.context.activeKey;
-    }
-
-    componentDidUpdate(prevProps: Readonly<TabPaneProps>, prevState: any, snapshot?: any) {
-        // CATION: be careful of the tabPane state update
-        // Animation only play while it is not first rendering process.
-        // Update states multiple times in TabPane will cause children unwanted re-rendering and unwanted animation.
-        this.rendered = false; 
-    }
 
 
     // get direction from current item key to activeKey
     // get direction from current item key to activeKey
-    getDirection = (activeKey: string, itemKey: string, panes: Array<PlainTab>): boolean => {
+    getDirection = (activeKey: string, itemKey: string, panes: Array<PlainTab>, lastActiveKey: string): boolean => {
         if (itemKey !== null && activeKey !== null && Array.isArray(panes) && panes.length) {
         if (itemKey !== null && activeKey !== null && Array.isArray(panes) && panes.length) {
             const activeIndex = panes.findIndex(pane => pane.itemKey === activeKey);
             const activeIndex = panes.findIndex(pane => pane.itemKey === activeKey);
             const itemIndex = panes.findIndex(pane => pane.itemKey === itemKey);
             const itemIndex = panes.findIndex(pane => pane.itemKey === itemKey);
-            const lastActiveIndex = panes.findIndex(pane => pane.itemKey === this.lastActiveKey);
+            const lastActiveIndex = panes.findIndex(pane => pane.itemKey === lastActiveKey);
 
 
-            this.lastActiveKey = activeKey;
 
 
             if (activeIndex === itemIndex) {
             if (activeIndex === itemIndex) {
                 return lastActiveIndex > activeIndex;
                 return lastActiveIndex > activeIndex;
@@ -70,7 +56,7 @@ class TabPane extends PureComponent<TabPaneProps> {
     };
     };
 
 
     render(): ReactNode {
     render(): ReactNode {
-        const { tabPaneMotion: motion, tabPosition } = this.context;
+        const { tabPaneMotion: motion, tabPosition, prevActiveKey } = this.context;
         const { className, style, children, itemKey, ...restProps } = this.props;
         const { className, style, children, itemKey, ...restProps } = this.props;
         const active = this.context.activeKey === itemKey;
         const active = this.context.activeKey === itemKey;
         const classNames = cls(className, {
         const classNames = cls(className, {
@@ -80,7 +66,7 @@ class TabPane extends PureComponent<TabPaneProps> {
         });
         });
         const shouldRender = this.shouldRender();
         const shouldRender = this.shouldRender();
         const startClassName = (() => {
         const startClassName = (() => {
-            const direction = this.getDirection(this.context.activeKey, itemKey, this.context.panes);
+            const direction = this.getDirection(this.context.activeKey, itemKey, this.context.panes, prevActiveKey);
             if (tabPosition === 'top') {
             if (tabPosition === 'top') {
                 if (direction) {
                 if (direction) {
                     return cssClasses.TABS_PANE_ANIMATE_RIGHT_SHOW;
                     return cssClasses.TABS_PANE_ANIMATE_RIGHT_SHOW;
@@ -95,6 +81,9 @@ class TabPane extends PureComponent<TabPaneProps> {
                 }
                 }
             }
             }
         })();
         })();
+
+        const isActivatedBecauseOtherTabPaneRemoved = !this.context.panes.find(tabPane => tabPane.itemKey === prevActiveKey);
+        const hasMotion = motion && active && !isActivatedBecauseOtherTabPaneRemoved && !this.context.forceDisableMotion;
         return (
         return (
             <div
             <div
                 ref={this.ref}
                 ref={this.ref}
@@ -109,7 +98,7 @@ class TabPane extends PureComponent<TabPaneProps> {
                 x-semi-prop="children"
                 x-semi-prop="children"
             >
             >
 
 
-                <CSSAnimation motion={motion && active && !this.rendered} animationState={active ? "enter" : "leave"}
+                <CSSAnimation motion={hasMotion} animationState={active ? "enter" : "leave"}
                     startClassName={startClassName}>
                     startClassName={startClassName}>
                     {
                     {
                         ({ animationClassName, animationEventsNeedBind }) => {
                         ({ animationClassName, animationEventsNeedBind }) => {

+ 28 - 11
packages/semi-ui/tabs/index.tsx

@@ -1,19 +1,18 @@
-
-import React, { createRef, MouseEvent, ReactElement, ReactNode, RefCallback, RefObject, isValidElement } from 'react';
+import React, { createRef, isValidElement, MouseEvent, ReactElement, ReactNode, RefCallback, RefObject } from 'react';
 import cls from 'classnames';
 import cls from 'classnames';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/tabs/constants';
 import { cssClasses, strings } from '@douyinfe/semi-foundation/tabs/constants';
 import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined';
 import isNullOrUndefined from '@douyinfe/semi-foundation/utils/isNullOrUndefined';
 import getDataAttr from '@douyinfe/semi-foundation/utils/getDataAttr';
 import getDataAttr from '@douyinfe/semi-foundation/utils/getDataAttr';
 import TabsFoundation, { TabsAdapter } from '@douyinfe/semi-foundation/tabs/foundation';
 import TabsFoundation, { TabsAdapter } from '@douyinfe/semi-foundation/tabs/foundation';
-import { isEqual, pick, omit } from 'lodash';
+import { isEqual, pick } from 'lodash';
 import BaseComponent from '../_base/baseComponent';
 import BaseComponent from '../_base/baseComponent';
 import '@douyinfe/semi-foundation/tabs/tabs.scss';
 import '@douyinfe/semi-foundation/tabs/tabs.scss';
 
 
 import TabBar from './TabBar';
 import TabBar from './TabBar';
 import TabPane from './TabPane';
 import TabPane from './TabPane';
 import TabsContext from './tabs-context';
 import TabsContext from './tabs-context';
-import { TabsProps, PlainTab, TabBarProps } from './interface';
+import { PlainTab, TabBarProps, TabsProps } from './interface';
 
 
 const panePickKeys = ['className', 'style', 'disabled', 'itemKey', 'tab', 'icon'];
 const panePickKeys = ['className', 'style', 'disabled', 'itemKey', 'tab', 'icon'];
 
 
@@ -21,7 +20,9 @@ export * from './interface';
 
 
 export interface TabsState {
 export interface TabsState {
     activeKey: string;
     activeKey: string;
-    panes: Array<PlainTab>
+    panes: Array<PlainTab>;
+    prevActiveKey: string | null;
+    forceDisableMotion: boolean
 }
 }
 
 
 class Tabs extends BaseComponent<TabsProps, TabsState> {
 class Tabs extends BaseComponent<TabsProps, TabsState> {
@@ -71,12 +72,12 @@ class Tabs extends BaseComponent<TabsProps, TabsState> {
 
 
     constructor(props: TabsProps) {
     constructor(props: TabsProps) {
         super(props);
         super(props);
-
         this.foundation = new TabsFoundation(this.adapter);
         this.foundation = new TabsFoundation(this.adapter);
-
         this.state = {
         this.state = {
             activeKey: this.foundation.getDefaultActiveKey(),
             activeKey: this.foundation.getDefaultActiveKey(),
             panes: [],
             panes: [],
+            prevActiveKey: null,
+            forceDisableMotion: false
         };
         };
         this.contentRef = createRef();
         this.contentRef = createRef();
         this.contentHeight = 'auto';
         this.contentHeight = 'auto';
@@ -118,14 +119,14 @@ class Tabs extends BaseComponent<TabsProps, TabsState> {
                         return undefined;
                         return undefined;
                     });
                     });
                 }
                 }
-                if (panes.findIndex(p => p.itemKey === activeKey) === -1){
-                    if (panes.length>0){
+                if (panes.findIndex(p => p.itemKey === activeKey) === -1) {
+                    if (panes.length > 0) {
                         this.setState({ activeKey: panes[0].itemKey });
                         this.setState({ activeKey: panes[0].itemKey });
                     } else {
                     } else {
                         this.setState({ activeKey: '' });
                         this.setState({ activeKey: '' });
                     }
                     }
                 }
                 }
-                
+
             },
             },
             notifyTabClick: (activeKey: string, event: MouseEvent<HTMLDivElement>): void => {
             notifyTabClick: (activeKey: string, event: MouseEvent<HTMLDivElement>): void => {
                 this.props.onTabClick(activeKey, event);
                 this.props.onTabClick(activeKey, event);
@@ -156,13 +157,14 @@ class Tabs extends BaseComponent<TabsProps, TabsState> {
     static getDerivedStateFromProps(props: TabsProps, state: TabsState): Partial<TabsState> {
     static getDerivedStateFromProps(props: TabsProps, state: TabsState): Partial<TabsState> {
         const states: Partial<TabsState> = {};
         const states: Partial<TabsState> = {};
         if (!isNullOrUndefined(props.activeKey) && props.activeKey !== state.activeKey) {
         if (!isNullOrUndefined(props.activeKey) && props.activeKey !== state.activeKey) {
+            state.prevActiveKey = state.activeKey;
             states.activeKey = props.activeKey;
             states.activeKey = props.activeKey;
         }
         }
         return states;
         return states;
     }
     }
 
 
 
 
-    componentDidUpdate(prevProps: TabsProps): void {
+    componentDidUpdate(prevProps: TabsProps, prevState: TabsState): void {
         // Panes state acts on tab bar, no need to compare TabPane children
         // Panes state acts on tab bar, no need to compare TabPane children
         const prevChildrenProps = React.Children.toArray(prevProps.children).map((child) =>
         const prevChildrenProps = React.Children.toArray(prevProps.children).map((child) =>
             pick(isValidElement(child) ? child.props : null, panePickKeys)
             pick(isValidElement(child) ? child.props : null, panePickKeys)
@@ -177,6 +179,19 @@ class Tabs extends BaseComponent<TabsProps, TabsState> {
             this.foundation.handleTabListChange();
             this.foundation.handleTabListChange();
         }
         }
 
 
+        if (prevState.activeKey !== this.state.activeKey && prevState.activeKey !== this.state.prevActiveKey) {
+            this.setState({ prevActiveKey: prevState.activeKey });
+        }
+
+        if (prevProps.activeKey !== this.props.activeKey) {
+            const newAddedPanelItemKey = (() => {
+                const prevItemKeys = new Set(prevChildrenProps.map(p => p.itemKey));
+                return nowChildrenProps.map(p => p.itemKey).filter(itemKey => !prevItemKeys.has(itemKey));
+            })();
+            this.setState({ forceDisableMotion: newAddedPanelItemKey.includes(this.props.activeKey) });
+        }
+
+
         // children变化,tabList方式使用时,啥也不用做
         // children变化,tabList方式使用时,啥也不用做
         // children变化,非tabList方式使用,需要重新取activeKey。TabPane可能是异步更新的,若不重新取,未设activeKey时,第一个不会自动激活
         // children变化,非tabList方式使用,需要重新取activeKey。TabPane可能是异步更新的,若不重新取,未设activeKey时,第一个不会自动激活
         // children changed: do nothing in tabList case
         // children changed: do nothing in tabList case
@@ -285,6 +300,8 @@ class Tabs extends BaseComponent<TabsProps, TabsState> {
                         panes,
                         panes,
                         tabPaneMotion,
                         tabPaneMotion,
                         tabPosition,
                         tabPosition,
+                        prevActiveKey: this.state.prevActiveKey,
+                        forceDisableMotion: this.state.forceDisableMotion
                     }}
                     }}
                 >
                 >
                     <div
                     <div

+ 3 - 1
packages/semi-ui/tabs/interface.ts

@@ -83,5 +83,7 @@ export interface TabContextValue {
     lazyRender?: boolean;
     lazyRender?: boolean;
     panes?: Array<PlainTab>;
     panes?: Array<PlainTab>;
     tabPaneMotion?: boolean;
     tabPaneMotion?: boolean;
-    tabPosition?: TabPosition
+    tabPosition?: TabPosition;
+    prevActiveKey: string|null;
+    forceDisableMotion: boolean
 }
 }