UNPKG

@wordpress/components

Version:
106 lines (100 loc) 4.45 kB
/** * WordPress dependencies */ import { useState, useEffect, useLayoutEffect, useCallback } from '@wordpress/element'; import { useReducedMotion } from '@wordpress/compose'; import { isRTL as isRTLFn } from '@wordpress/i18n'; /** * Internal dependencies */ import * as styles from '../styles'; // Possible values: // - 'INITIAL': the initial state // - 'ANIMATING_IN': start enter animation // - 'IN': enter animation has ended // - 'ANIMATING_OUT': start exit animation // - 'OUT': the exit animation has ended // Allow an extra 20% of the total animation duration to account for potential // event loop delays. const ANIMATION_TIMEOUT_MARGIN = 1.2; const isEnterAnimation = (animationDirection, animationStatus, animationName) => animationStatus === 'ANIMATING_IN' && animationName === styles.ANIMATION_END_NAMES[animationDirection].in; const isExitAnimation = (animationDirection, animationStatus, animationName) => animationStatus === 'ANIMATING_OUT' && animationName === styles.ANIMATION_END_NAMES[animationDirection].out; export function useScreenAnimatePresence({ isMatch, skipAnimation, isBack, onAnimationEnd }) { const isRTL = isRTLFn(); const prefersReducedMotion = useReducedMotion(); const [animationStatus, setAnimationStatus] = useState('INITIAL'); // Start enter and exit animations when the screen is selected or deselected. // The animation status is set to `IN` or `OUT` immediately if the animation // should be skipped. const becameSelected = animationStatus !== 'ANIMATING_IN' && animationStatus !== 'IN' && isMatch; const becameUnselected = animationStatus !== 'ANIMATING_OUT' && animationStatus !== 'OUT' && !isMatch; useLayoutEffect(() => { if (becameSelected) { setAnimationStatus(skipAnimation || prefersReducedMotion ? 'IN' : 'ANIMATING_IN'); } else if (becameUnselected) { setAnimationStatus(skipAnimation || prefersReducedMotion ? 'OUT' : 'ANIMATING_OUT'); } }, [becameSelected, becameUnselected, skipAnimation, prefersReducedMotion]); // Animation attributes (derived state). const animationDirection = isRTL && isBack || !isRTL && !isBack ? 'end' : 'start'; const isAnimatingIn = animationStatus === 'ANIMATING_IN'; const isAnimatingOut = animationStatus === 'ANIMATING_OUT'; let animationType; if (isAnimatingIn) { animationType = 'in'; } else if (isAnimatingOut) { animationType = 'out'; } const onScreenAnimationEnd = useCallback(e => { onAnimationEnd?.(e); if (isExitAnimation(animationDirection, animationStatus, e.animationName)) { // When the exit animation ends on an unselected screen, set the // status to 'OUT' to remove the screen contents from the DOM. setAnimationStatus('OUT'); } else if (isEnterAnimation(animationDirection, animationStatus, e.animationName)) { // When the enter animation ends on a selected screen, set the // status to 'IN' to ensure the screen is rendered in the DOM. setAnimationStatus('IN'); } }, [onAnimationEnd, animationStatus, animationDirection]); // Fallback timeout to ensure that the logic is applied even if the // `animationend` event is not triggered. useEffect(() => { let animationTimeout; if (isAnimatingOut) { animationTimeout = window.setTimeout(() => { setAnimationStatus('OUT'); animationTimeout = undefined; }, styles.TOTAL_ANIMATION_DURATION.OUT * ANIMATION_TIMEOUT_MARGIN); } else if (isAnimatingIn) { animationTimeout = window.setTimeout(() => { setAnimationStatus('IN'); animationTimeout = undefined; }, styles.TOTAL_ANIMATION_DURATION.IN * ANIMATION_TIMEOUT_MARGIN); } return () => { if (animationTimeout) { window.clearTimeout(animationTimeout); animationTimeout = undefined; } }; }, [isAnimatingOut, isAnimatingIn]); return { animationStyles: styles.navigatorScreenAnimation, // Render the screen's contents in the DOM not only when the screen is // selected, but also while it is animating out. shouldRenderScreen: isMatch || animationStatus === 'IN' || animationStatus === 'ANIMATING_OUT', screenProps: { onAnimationEnd: onScreenAnimationEnd, 'data-animation-direction': animationDirection, 'data-animation-type': animationType, 'data-skip-animation': skipAnimation || undefined } }; } //# sourceMappingURL=use-screen-animate-presence.js.map