-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathuseTransitionAnimation.ts
117 lines (102 loc) · 3.12 KB
/
useTransitionAnimation.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { useContext, useEffect } from "react";
import {
EXITED,
EXITING,
NOT_EXIT,
TransitionNotifierContext,
TransitionState,
TransitionStateContext,
} from "../components/TransitionGroup";
import { AnimationHandle, useAnimation } from "./useAnimation";
import { getKeys, noop } from "../../core/utils";
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
import type {
TypedKeyframeEffectOptions,
GetKeyframeFunction,
TypedKeyframe,
} from "../../core";
import { useStatic } from "./useStatic";
import { useLatestRef } from "./useLatestRef";
export interface TransitionAnimationHandle {
(ref: Element | null): void;
}
export interface TransitionAnimationOptions
extends TypedKeyframeEffectOptions {}
export type TransitionAnimationDefinition = [
keyframe: TypedKeyframe | TypedKeyframe[] | GetKeyframeFunction,
options?: TransitionAnimationOptions
];
/**
*
* A hook to compose multiple {@link useAnimation} and plays them when element enter/update/exits.
* This hook must be used under {@link TransitionGroup} component.
*/
export const useTransitionAnimation = (keyframes: {
enter?: TransitionAnimationDefinition;
update?: TransitionAnimationDefinition;
exit?: TransitionAnimationDefinition;
}): TransitionAnimationHandle => {
const keys = getKeys(keyframes);
const animations = keys.reduce((acc, k) => {
const def = keyframes[k];
if (!def) return acc;
acc[k] = useAnimation(def[0], def[1]);
return acc;
}, {} as { [key in TransitionState]: AnimationHandle | undefined });
const animationsRef = useLatestRef(animations);
const [animation, cleanup] = useStatic(
(): [TransitionAnimationHandle, () => void] => {
const forAllHandle = (fn: (handle: AnimationHandle) => void) => {
getKeys(animationsRef.current).forEach((name) =>
fn(animationsRef.current[name]!)
);
};
const externalHandle: TransitionAnimationHandle = (
ref: Element | null
) => {
forAllHandle((h) => {
h(ref);
});
};
return [
externalHandle,
() => {
forAllHandle((handle) => {
handle.cancel();
});
},
];
}
);
useEffect(() => cleanup, []);
const currentState = useContext(TransitionStateContext);
const notify = useContext(TransitionNotifierContext);
useIsomorphicLayoutEffect(() => {
// Decide if the parent should animate children on exit or not
// State must change like enter (-> update) -> exit so it's ok to use ref
if (keys.includes("exit")) {
notify(EXITING);
} else {
notify(NOT_EXIT);
}
}, keys);
useIsomorphicLayoutEffect(() => {
if (currentState !== "update") return;
animationsRef.current[currentState]?.play();
});
useIsomorphicLayoutEffect(() => {
if (currentState === "update") return;
animationsRef.current[currentState]
?.play()
.waitFor("finish")
.then(() => {
if (currentState === "exit") {
notify(EXITED);
}
})
.catch(
noop // ignore uncaught promise error
);
}, [currentState]);
return animation;
};