86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
import { type MutableRefObject, useCallback, useEffect, useState } from "react";
|
|
|
|
function getCalculatedTransitionDuration(
|
|
ref: MutableRefObject<HTMLElement>,
|
|
): 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<HTMLElement | null>,
|
|
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<HTMLElement>)
|
|
: 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 { useAnimatedMount };
|