#![doc = include_str!("../docs/api/animation.md")]
use std::rc::Rc;
use dioxus::html::MountedData;
use dioxus::prelude::*;
use crate::a11y::use_reduced_motion_css;
use crate::core::components::merge_style_invariant_last;
use crate::core::{platform, Point, Rect};
#[derive(Debug, Clone, Copy, PartialEq, Default)]
enum FlipPhase {
#[default]
Rest,
Invert(Point),
}
fn invert_style(d: Point) -> String {
format!(
"transform: translate({}px, {}px); transition: none;",
d.x, d.y
)
}
fn rest_style(duration: f64, easing: &str) -> String {
format!("transform: none; transition: transform {duration}ms {easing};")
}
#[component]
pub fn FlipItem(
epoch: usize,
#[props(default = 200.0)]
duration: f64,
#[props(default = "ease".to_string())]
easing: String,
#[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let mounted = use_signal(|| None::<Rc<MountedData>>);
let prev = use_signal(|| None::<Rect>);
let mut phase = use_signal(FlipPhase::default);
let mut measurement_generation = use_signal(|| 0u64);
use_effect(use_reactive!(|epoch, duration, easing| {
let _ = epoch;
let Some(m) = mounted.peek().clone() else {
return;
};
measurement_generation += 1;
let generation = *measurement_generation.peek();
let mut prev = prev;
spawn(async move {
if let Ok(r) = m.get_client_rect().await {
if *measurement_generation.peek() != generation {
return;
}
let now = Rect::new(r.origin.x, r.origin.y, r.size.width, r.size.height);
if let Some(old) = *prev.peek() {
let d = Point::new(old.x - now.x, old.y - now.y);
if d.x != 0.0 || d.y != 0.0 {
let handed_off = platform::flip_transform(
&m,
&invert_style(d),
&rest_style(duration, &easing),
);
if !handed_off {
phase.set(FlipPhase::Invert(d));
}
}
}
prev.set(Some(now));
}
});
}));
use_effect(move || {
if matches!(phase(), FlipPhase::Invert(_)) {
phase.set(FlipPhase::Rest);
}
});
let functional_style = match phase() {
FlipPhase::Invert(d) => invert_style(d),
FlipPhase::Rest => rest_style(duration, &easing),
};
let mut attributes = attributes;
crate::core::components::protect_attributes(&mut attributes, &["data-dnd-motion", "onmounted"]);
let style = merge_style_invariant_last(
&mut attributes,
&functional_style,
&["transform", "transition"],
);
let reduced_motion_css = use_reduced_motion_css();
rsx! {
{reduced_motion_css}
div {
style: "{style}",
"data-dnd-motion": true,
onmounted: move |evt: Event<MountedData>| {
let mut mounted = mounted;
mounted.set(Some(evt.data()));
},
..attributes,
{children}
}
}
}