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::components::merge_style_invariant_last;
10use crate::core::{platform, Point, Rect};
11
12#[derive(Debug, Clone, Copy, PartialEq, Default)]
15enum FlipPhase {
16 #[default]
18 Rest,
19 Invert(Point),
21}
22
23fn invert_style(d: Point) -> String {
26 format!(
27 "transform: translate({}px, {}px); transition: none;",
28 d.x, d.y
29 )
30}
31
32fn rest_style(duration: f64, easing: &str) -> String {
36 format!("transform: none; transition: transform {duration}ms {easing};")
37}
38
39#[component]
42pub fn FlipItem(
43 epoch: usize,
45 #[props(default = 200.0)]
47 duration: f64,
48 #[props(default = "ease".to_string())]
50 easing: String,
51 #[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
52 children: Element,
53) -> Element {
54 let mounted = use_signal(|| None::<Rc<MountedData>>);
55 let prev = use_signal(|| None::<Rect>);
56 let mut phase = use_signal(FlipPhase::default);
57 let mut measurement_generation = use_signal(|| 0u64);
58
59 use_effect(use_reactive!(|epoch, duration, easing| {
64 let _ = epoch;
65 let Some(m) = mounted.peek().clone() else {
66 return;
67 };
68 measurement_generation += 1;
69 let generation = *measurement_generation.peek();
70 let mut prev = prev;
71 spawn(async move {
72 if let Ok(r) = m.get_client_rect().await {
73 if *measurement_generation.peek() != generation {
74 return;
75 }
76 let now = Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height);
77 if let Some(old) = *prev.peek() {
78 let d = Point::new(old.x - now.x, old.y - now.y);
79 if d.x != 0.0 || d.y != 0.0 {
80 let handed_off = platform::flip_transform(
81 &m,
82 &invert_style(d),
83 &rest_style(duration, &easing),
84 );
85 if !handed_off {
86 phase.set(FlipPhase::Invert(d));
87 }
88 }
89 }
90 prev.set(Some(now));
91 }
92 });
93 }));
94
95 use_effect(move || {
100 if matches!(phase(), FlipPhase::Invert(_)) {
101 phase.set(FlipPhase::Rest);
102 }
103 });
104
105 let functional_style = match phase() {
106 FlipPhase::Invert(d) => invert_style(d),
107 FlipPhase::Rest => rest_style(duration, &easing),
108 };
109 let mut attributes = attributes;
110 crate::core::components::protect_attributes(&mut attributes, &["data-dnd-motion", "onmounted"]);
111 let style = merge_style_invariant_last(
112 &mut attributes,
113 &functional_style,
114 &["transform", "transition"],
115 );
116 let reduced_motion_css = use_reduced_motion_css();
118
119 rsx! {
120 {reduced_motion_css}
121 div {
122 style: "{style}",
123 "data-dnd-motion": true,
124 onmounted: move |evt: Event<MountedData>| {
125 let mut mounted = mounted;
126 mounted.set(Some(evt.data()));
127 },
128 ..attributes,
129 {children}
130 }
131 }
132}