import type { RefObject } from 'react'; import { useCallback, useLayoutEffect, useState } from 'react'; import ReactDOM from 'react-dom'; export function useEnterAnimation(ref: RefObject, isReady = true) { const [isEntering, setEntering] = useState(true); const onAnimationEnd = useCallback(() => { setEntering(false); }, []); useAnimation(ref, isEntering && isReady, onAnimationEnd); return isEntering && isReady; } export function useExitAnimation(ref: RefObject, isOpen: boolean) { const [stateIsExiting, setExiting] = useState(false); let isExiting = stateIsExiting; const [exitState, setExitState] = useState('idle'); if (!isOpen && ref.current && exitState === 'idle') { isExiting = true; setExiting(true); setExitState('exiting'); } if (!ref.current && exitState === 'exited') { setExitState('idle'); } const onAnimationEnd = useCallback(() => { setExitState('exited'); setExiting(false); }, []); useAnimation(ref, isExiting, onAnimationEnd); return isExiting; } function useAnimation(ref: RefObject, isActive: boolean, onEnd: () => void) { useLayoutEffect(() => { if (isActive && ref.current) { const computedStyle = window.getComputedStyle(ref.current); if (computedStyle.animationName && computedStyle.animationName !== 'none') { const onAnimationEnd = (e: AnimationEvent) => { if (e.target === ref.current) { element.removeEventListener('animationend', onAnimationEnd); ReactDOM.flushSync(() => { onEnd(); }); } }; const element = ref.current; element.addEventListener('animationend', onAnimationEnd); return () => { element.removeEventListener('animationend', onAnimationEnd); }; } else { onEnd(); } } }, [ref, isActive, onEnd]); }