react-component-transition
Version:
Easy animations between react component transitions.
120 lines (119 loc) • 6.37 kB
JavaScript
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";