1#![doc = include_str!("../docs/api/animation.md")]
2
3use std::rc::Rc;
4
5use dioxus::html::MountedData;
6use dioxus::prelude::*;
7
8use crate::a11y::use_reduced_motion_css;
9use crate::core::{platform, Point, Rect};
10
11#[derive(Debug, Clone, Copy, PartialEq, Default)]
14enum FlipPhase {
15 #[default]
17 Rest,
18 Invert(Point),
20}
21
22fn invert_style(d: Point) -> String {
25 format!(
26 "transform: translate({}px, {}px); transition: none;",
27 d.x, d.y
28 )
29}
30
31fn rest_style(duration: f64, easing: &str) -> String {
35 format!("transform: none; transition: transform {duration}ms {easing};")
36}
37
38#[component]
41pub fn FlipItem(
42 epoch: usize,
44 #[props(default = 200.0)]
46 duration: f64,
47 #[props(default = "ease".to_string())]
49 easing: String,
50 #[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
51 children: Element,
52) -> Element {
53 let mounted = use_signal(|| None::<Rc<MountedData>>);
54 let prev = use_signal(|| None::<Rect>);
55 let mut phase = use_signal(FlipPhase::default);
56
57 use_effect(use_reactive!(|epoch, duration, easing| {
62 let _ = epoch;
63 let Some(m) = mounted.peek().clone() else {
64 return;
65 };
66 let mut prev = prev;
67 spawn(async move {
68 if let Ok(r) = m.get_client_rect().await {
69 let now = Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height);
70 if let Some(old) = *prev.peek() {
71 let d = Point::new(old.x - now.x, old.y - now.y);
72 if d.x != 0.0 || d.y != 0.0 {
73 let handed_off = platform::flip_transform(
74 &m,
75 &invert_style(d),
76 &rest_style(duration, &easing),
77 );
78 if !handed_off {
79 phase.set(FlipPhase::Invert(d));
80 }
81 }
82 }
83 prev.set(Some(now));
84 }
85 });
86 }));
87
88 use_effect(move || {
93 if matches!(phase(), FlipPhase::Invert(_)) {
94 phase.set(FlipPhase::Rest);
95 }
96 });
97
98 let style = match phase() {
99 FlipPhase::Invert(d) => invert_style(d),
100 FlipPhase::Rest => rest_style(duration, &easing),
101 };
102 let reduced_motion_css = use_reduced_motion_css();
104
105 rsx! {
106 {reduced_motion_css}
107 div {
108 style: "{style}",
109 "data-dnd-motion": true,
110 onmounted: move |evt: Event<MountedData>| {
111 let mut mounted = mounted;
112 mounted.set(Some(evt.data()));
113 },
114 ..attributes,
115 {children}
116 }
117 }
118}