UNPKG

react-component-transition

Version:
120 lines (119 loc) 6.37 kB
import { __assign } from "tslib"; import React, { useRef, useEffect, useState, useCallback, useLayoutEffect } from "react"; import { flushSync } from "react-dom"; import classnames from "classnames"; import { TransitionState } from "./animation-hooks/types"; import { useContainerRectangle, useExitAnimation, useContainerAnimation, useEnterAnimation, } from "./animation-hooks"; import { defaultTransitionDuration, defaultTransitionEasing } from "../animations/defaults"; export var Transition = function (_a) { var _b = _a.animateContainer, animateContainer = _b === void 0 ? false : _b, _c = _a.animateContainerDuration, animateContainerDuration = _c === void 0 ? defaultTransitionDuration : _c, _d = _a.animateContainerEasing, animateContainerEasing = _d === void 0 ? defaultTransitionEasing : _d, animateOnMount = _a.animateOnMount, children = _a.children, className = _a.className, classNameEnter = _a.classNameEnter, classNameExit = _a.classNameExit, disabled = _a.disabled, enterAnimation = _a.enterAnimation, exitAnimation = _a.exitAnimation, inViewRef = _a.inViewRef, inViewEnabled = _a.inViewEnabled, lazy = _a.lazy, onEnterFinished = _a.onEnterFinished, onExitFinished = _a.onExitFinished, style = _a.style; var _e = useState(children && !lazy && animateOnMount && !animateContainer ? TransitionState.ContainerRect : null), transitionState = _e[0], setTransitionState = _e[1]; var prevChildren = useRef(animateOnMount && animateContainer ? null : children); var containerRef = useRef(null); var unmounted = useRef(false); var hasChildrenChanged = didChildrenChanged(prevChildren.current, children); if (!hasChildrenChanged && !transitionState) { prevChildren.current = children; } var updatedState = function (state) { if (!unmounted.current) { setTransitionState(state); } }; useEffect(function () { return function () { unmounted.current = true; }; }, []); useLayoutEffect(function () { if (inViewEnabled && animateOnMount && !animateContainer) { updatedState(TransitionState.ContainerRect); } }, [inViewEnabled]); // start exit transition if children changed useEffect(function () { if (!hasChildrenChanged) { return; } if (!transitionState) { updatedState(TransitionState.Exit); } }); var animationHooks = { children: children, getElement: function () { return containerRef.current; }, prevChildren: prevChildren.current, transitionState: transitionState, disabled: disabled, onFinish: null, }; var _f = useContainerRectangle(__assign(__assign({}, animationHooks), { onFinish: function () { return updatedState(TransitionState.Container); } })), nextClientRect = _f.nextClientRect, prevClientRect = _f.prevClientRect; var exitFinishedHandler = function () { onExitFinished && onExitFinished(); }; useExitAnimation(__assign(__assign({}, animationHooks), { prevClientRect: prevClientRect, settings: exitAnimation, onFinish: function () { var hadPrevChildren = !!prevChildren.current; prevChildren.current = children; if (hadPrevChildren && !animateContainer) { exitFinishedHandler(); } // If flushSync is done when there are no children, then we get a warning because we are still in react lifecycle // So, if there are no children it means that there was no exit animation therefore we are yet in useEffect of useExitAnimation if (hadPrevChildren && !disabled) { // need to rerender the component after setting state to avoid animation blinking on animation exit flushSync(function () { updatedState(TransitionState.ContainerRect); }); } else { updatedState(TransitionState.ContainerRect); } } })); useContainerAnimation(__assign(__assign({}, animationHooks), { prevClientRect: prevClientRect, nextClientRect: nextClientRect, animateContainer: animateContainer, animateContainerDuration: animateContainerDuration, animateContainerEasing: animateContainerEasing, onFinish: function () { if (!prevChildren.current && animateContainer) { exitFinishedHandler(); } updatedState(TransitionState.Enter); } })); useEnterAnimation(__assign(__assign({}, animationHooks), { nextClientRect: nextClientRect, settings: enterAnimation, onFinish: function () { if (prevChildren.current) { onEnterFinished && onEnterFinished(); } updatedState(null); } })); var shouldRenderPrevChildren = hasChildrenChanged || transitionState === TransitionState.Exit; var hideContent = (lazy && !inViewEnabled) || transitionState === TransitionState.ContainerRect || transitionState === TransitionState.Container; var setRefs = useCallback(function (element) { containerRef.current = element; inViewRef && inViewRef(element); }, [inViewRef]); if (!lazy && !hasChildrenChanged && !transitionState && !children) { return null; } return (React.createElement("div", { ref: setRefs, className: classnames(className, transitionState === TransitionState.Enter && classNameEnter, transitionState === TransitionState.Exit && classNameExit) || null, style: __assign(__assign({}, style), { opacity: hideContent ? 0 : null }) }, shouldRenderPrevChildren ? prevChildren.current : children)); }; var didChildrenChanged = function (prevChildren, children) { var prevChildrenElement = prevChildren; var childrenElement = children; if (!prevChildren && !children) { return false; } if (!prevChildren && children) { return true; } if (prevChildren && !children) { return true; } if (prevChildrenElement.key === childrenElement.key && prevChildrenElement.type === childrenElement.type) { return false; } return true; }; Transition.displayName = "Transition";