dioxus_transition/
components.rs

1use dioxus::prelude::*;
2use wasm_bindgen::JsValue;
3
4use crate::utils;
5
6/// This is a dioxus component similar to Vue's `<Transition>` component.
7///
8/// It allows you to animate elements when they are added or removed from the DOM.
9#[component]
10pub fn Transition(
11    /// The tree of elements to conduct the animation on.
12    children: Element,
13
14    /// The id of the element within the tree to animate.
15    id: ReadSignal<String>,
16
17    /// The kind of animation to conduct, you can either use one of the built-in transitions
18    /// or create your own.
19    ///
20    /// Built-in transitions are enabled and injected by default, you can opt out by setting the
21    /// `default-features = false` in your `Cargo.toml`.
22    kind: ReadSignal<String>,
23
24    /// The duration of the animation in milliseconds.
25    duration: u32,
26
27    /// Whether to ignore the fade-in part of the animation on the first run. Useful if the animated
28    /// element is present by default.
29    #[props(default)]
30    ignore_first: bool,
31) -> Element {
32    #[cfg(all(feature = "builtins", not(feature = "ssr")))]
33    utils::int::inject_default_stylesheet();
34
35    let mut backup_element: Signal<Option<Element>> = use_signal(|| None);
36    let mut slot_element: Signal<Option<Element>> = use_signal(|| None);
37    let mut first_run = use_signal(|| true);
38
39    let root_element = use_callback(move |_: ()| {
40        let id = id();
41        element_by_id!(&id)
42    });
43
44    let backup_slot_element = use_callback(move |_: ()| {
45        let slot_element = slot_element();
46        backup_element.set(slot_element);
47    });
48
49    let wipe_backup_element = use_callback(move |_: ()| {
50        backup_element.set(None);
51    });
52
53    let record_the_run = use_callback(move |_: ()| {
54        let is_it_first_run = first_run();
55
56        if !is_it_first_run {
57            return;
58        }
59
60        first_run.set(false);
61    });
62
63    let hidden_class = use_memo(move || format!("{kind}-transition-hidden"));
64    let run_class = use_memo(move || format!("{kind}-transition-activating"));
65    let duration_ms = use_memo(move || format!("{duration}ms"));
66
67    let mut activate = use_future(move || async move {
68        if let Some(element) = root_element(()) {
69            let should_be_hidden = !first_run() || !ignore_first;
70
71            if should_be_hidden {
72                let hidden_class = hidden_class();
73                element.class_list().add_1(&hidden_class)?;
74            }
75        }
76
77        utils::dom::request_animation_frame().await;
78
79        let Some(element) = root_element(()) else {
80            return Ok(());
81        };
82
83        let run_class = run_class();
84        let duration_ms = duration_ms();
85
86        animate!(element => using &run_class => for &duration_ms => forwards);
87        wait_for!(duration).await;
88
89        backup_slot_element(());
90        record_the_run(());
91
92        Ok::<(), JsValue>(())
93    });
94
95    let mut deactivate = use_future(move || async move {
96        utils::dom::request_animation_frame().await;
97
98        let Some(element) = root_element(()) else {
99            return Ok(());
100        };
101
102        let run_class = run_class();
103        let duration_ms = duration_ms();
104
105        animate!(element => using &run_class => for &duration_ms => backwards);
106        wait_for!(duration).await;
107
108        wipe_backup_element(());
109
110        Ok::<(), JsValue>(())
111    });
112
113    let when_children_change = use_reactive((&children,), move |(children,)| {
114        let maybe_is_slot_placeholder = {
115            let children = children.clone();
116            utils::dioxus::maybe_is_element_placeholder(children)
117        };
118
119        let Some(is_slot_placeholder) = maybe_is_slot_placeholder else {
120            return;
121        };
122
123        if is_slot_placeholder {
124            deactivate.restart();
125        } else {
126            let indeed_children = Some(children.clone());
127
128            slot_element.set(indeed_children);
129            activate.restart();
130        }
131    });
132
133    use_effect(when_children_change);
134
135    rsx! {
136        Fragment {
137            { backup_element().unwrap_or(children) }
138        }
139    }
140}