1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | import {flushSync} from 'react-dom';
|
14 | import {RefObject, useCallback, useState} from 'react';
|
15 | import {useLayoutEffect} from './useLayoutEffect';
|
16 |
|
17 | export function useEnterAnimation(ref: RefObject<HTMLElement | null>, isReady: boolean = true) {
|
18 | let [isEntering, setEntering] = useState(true);
|
19 | let isAnimationReady = isEntering && isReady;
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | useLayoutEffect(() => {
|
28 | if (isAnimationReady && ref.current && 'getAnimations' in ref.current) {
|
29 | for (let animation of ref.current.getAnimations()) {
|
30 | if (animation instanceof CSSTransition) {
|
31 | animation.cancel();
|
32 | }
|
33 | }
|
34 | }
|
35 | }, [ref, isAnimationReady]);
|
36 |
|
37 | useAnimation(ref, isAnimationReady, useCallback(() => setEntering(false), []));
|
38 | return isAnimationReady;
|
39 | }
|
40 |
|
41 | export function useExitAnimation(ref: RefObject<HTMLElement | null>, isOpen: boolean) {
|
42 | let [exitState, setExitState] = useState<'closed' | 'open' | 'exiting'>(isOpen ? 'open' : 'closed');
|
43 |
|
44 | switch (exitState) {
|
45 | case 'open':
|
46 |
|
47 | if (!isOpen) {
|
48 | setExitState('exiting');
|
49 | }
|
50 | break;
|
51 | case 'closed':
|
52 | case 'exiting':
|
53 |
|
54 |
|
55 | if (isOpen) {
|
56 | setExitState('open');
|
57 | }
|
58 | break;
|
59 | }
|
60 |
|
61 | let isExiting = exitState === 'exiting';
|
62 | useAnimation(
|
63 | ref,
|
64 | isExiting,
|
65 | useCallback(() => {
|
66 |
|
67 | setExitState(state => state === 'exiting' ? 'closed' : state);
|
68 | }, [])
|
69 | );
|
70 |
|
71 | return isExiting;
|
72 | }
|
73 |
|
74 | function useAnimation(ref: RefObject<HTMLElement | null>, isActive: boolean, onEnd: () => void) {
|
75 | useLayoutEffect(() => {
|
76 | if (isActive && ref.current) {
|
77 | if (!('getAnimations' in ref.current)) {
|
78 |
|
79 | onEnd();
|
80 | return;
|
81 | }
|
82 |
|
83 | let animations = ref.current.getAnimations();
|
84 | if (animations.length === 0) {
|
85 | onEnd();
|
86 | return;
|
87 | }
|
88 |
|
89 | let canceled = false;
|
90 | Promise.all(animations.map(a => a.finished)).then(() => {
|
91 | if (!canceled) {
|
92 | flushSync(() => {
|
93 | onEnd();
|
94 | });
|
95 | }
|
96 | }).catch(() => {});
|
97 |
|
98 | return () => {
|
99 | canceled = true;
|
100 | };
|
101 | }
|
102 | }, [ref, isActive, onEnd]);
|
103 | }
|