import { type MutableRefObject, useCallback, useEffect, useState } from "react"; function getCalculatedTransitionDuration( ref: MutableRefObject, ): number { let transitionDuration: { value: number; unit: string; } | null; if (ref.current.computedStyleMap !== undefined) { transitionDuration = ref.current .computedStyleMap() .get("transition-duration") as { value: number; unit: string }; } else { const style = /(\d+(\.\d+)?)(.+)/.exec( window.getComputedStyle(ref.current).transitionDuration, ); if (!style) return 0; transitionDuration = { value: Number.parseFloat(style[1] ?? "0"), unit: style[3] ?? style[2] ?? "s", }; } return ( transitionDuration.value * ({ s: 1000, ms: 1, }[transitionDuration.unit] ?? 1) ); } /* * isMounted: true isRendered: true isRendered: false isMounted: false * Component Mount Component Appear Component Disappear Component Unmount * v v v v * |-|=================|------------------------|======================|-| */ function useAnimatedMount( visible: boolean, ref: MutableRefObject, callbacks?: { onMount: () => void; onUnmount: () => void }, ) { const [state, setState] = useState<{ isMounted: boolean; isRendered: boolean; }>({ isMounted: visible, isRendered: visible }); const umountCallback = useCallback(() => { setState((p) => ({ ...p, isRendered: false })); const calculatedTransitionDuration = ref.current ? getCalculatedTransitionDuration(ref as MutableRefObject) : 0; setTimeout(() => { setState((p) => ({ ...p, isMounted: false })); callbacks?.onUnmount?.(); }, calculatedTransitionDuration); }, [ref, callbacks]); const mountCallback = useCallback(() => { setState((p) => ({ ...p, isMounted: true })); callbacks?.onMount?.(); requestAnimationFrame(function onMount() { if (!ref.current) return requestAnimationFrame(onMount); setState((p) => ({ ...p, isRendered: true })); }); }, [ref.current, callbacks]); useEffect(() => { console.log(state); if (!visible && state.isRendered) { umountCallback(); } else if (visible && !state.isMounted) { mountCallback(); } }, [state, visible, mountCallback, umountCallback]); return state; } export { getCalculatedTransitionDuration, useAnimatedMount };