yew_transition_group/
transition.rs

1use crate::timeout::Timeout;
2use yew::prelude::*;
3
4/// The Props for Transition.
5#[derive(Debug, PartialEq, Properties)]
6pub struct TransitionProps {
7    /// The wrapped Children.
8    pub children: Children,
9    /// in From react-transition-group. Signal for the wrapped element to appear.
10    pub enter: Option<bool>,
11
12    /// The timeout for appear, enter, exit.
13    pub timeout: Timeout,
14
15    /// The Callback to be notified of state changes.
16    pub notification: Callback<TransitionState>,
17}
18
19impl TransitionProps {
20    fn enter(&self) -> bool {
21        self.enter.unwrap_or(false)
22    }
23}
24
25/// The four states that the children components see.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
27pub enum TransitionState {
28    /// The first state.
29    Entering,
30    /// The second state.
31    Entered,
32    /// The third state.
33    Exiting,
34    /// The fourth state.
35    Exited,
36}
37
38#[derive(Debug)]
39pub enum TransitionStateComplete {
40    /// The child component does not exist in the DOM.
41    BeforeEnter,
42    /// The element exists in the DOM.
43    Mounted,
44    /// The four states that will be passed to the wrapped components.
45    TransitionState(TransitionState),
46}
47
48#[derive(Debug)]
49pub struct Tick;
50
51/// A component that wraps other components and handles transitions, making available `TransitionState` to the wrapped children.
52#[derive(Debug)]
53pub struct Transition {
54    state: TransitionStateComplete,
55    saved_enter: Option<bool>,
56}
57
58impl Component for Transition {
59    type Message = Tick;
60
61    type Properties = TransitionProps;
62
63    fn create(_ctx: &Context<Self>) -> Self {
64        Transition {
65            state: TransitionStateComplete::BeforeEnter,
66            saved_enter: Some(false),
67        }
68    }
69
70    fn update(&mut self, ctx: &Context<Self>, _msg: Self::Message) -> bool {
71        self.state = match self.state {
72            TransitionStateComplete::BeforeEnter => TransitionStateComplete::Mounted,
73            TransitionStateComplete::Mounted => {
74                let enter = ctx.props().timeout.enter();
75                ctx.link().send_future(async move {
76                    gloo_timers::future::TimeoutFuture::new(enter).await;
77                    Tick
78                });
79                ctx.props().notification.emit(TransitionState::Entering);
80
81                TransitionStateComplete::TransitionState(TransitionState::Entering)
82            }
83            TransitionStateComplete::TransitionState(state) => {
84                let new_state = match state {
85                    TransitionState::Entering => TransitionState::Entered,
86                    TransitionState::Entered => TransitionState::Exiting,
87                    TransitionState::Exiting => TransitionState::Exited,
88                    TransitionState::Exited => TransitionState::Exited,
89                };
90                ctx.props().notification.emit(new_state);
91
92                TransitionStateComplete::TransitionState(new_state)
93            }
94        };
95        true
96    }
97
98    fn changed(&mut self, ctx: &Context<Self>) -> bool {
99        match (ctx.props().enter(), self.saved_enter) {
100            (true, Some(true)) => true,
101            (true, Some(false)) => {
102                let duration = if ctx.props().timeout.appear() == 0 {
103                    self.state =
104                        TransitionStateComplete::TransitionState(TransitionState::Entering);
105                    ctx.props().notification.emit(TransitionState::Entering);
106                    ctx.props().timeout.enter()
107                } else {
108                    self.state = TransitionStateComplete::Mounted;
109                    ctx.props().timeout.appear()
110                };
111
112                ctx.link().send_future(async move {
113                    gloo_timers::future::TimeoutFuture::new(duration).await;
114                    Tick
115                });
116
117                self.saved_enter = Some(true);
118                true
119            }
120            (false, Some(true)) => {
121                if ctx.props().timeout.exit() != 0 {
122                    self.state = TransitionStateComplete::TransitionState(TransitionState::Exiting);
123
124                    let duration = ctx.props().timeout.exit();
125
126                    ctx.link().send_future(async move {
127                        gloo_timers::future::TimeoutFuture::new(duration).await;
128                        Tick
129                    });
130                } else {
131                    self.state = TransitionStateComplete::TransitionState(TransitionState::Exited);
132                }
133                self.saved_enter = Some(false);
134                true
135            }
136            (false, Some(false)) => true,
137            (true, None) => true,
138            (false, None) => true,
139        }
140    }
141
142    fn view(&self, ctx: &Context<Self>) -> Html {
143        match self.state {
144            TransitionStateComplete::BeforeEnter => html! {<> </>},
145            TransitionStateComplete::Mounted => html! { <>{ ctx.props().children.clone() } </>  },
146            TransitionStateComplete::TransitionState(_) => {
147                html! {
148                    <>{ ctx.props().children.clone() }</>
149
150                }
151            }
152        }
153    }
154}