leptos_animate 0.1.15

Animation utilities for Leptos. FLIP, in/out transitions, and helpers for custom animations.
use std::time;

use leptos::prelude::ReadUntracked;
use reactive_stores::ArcStore;
use web_sys::{self, DomRect};

use super::{Duration, Options};
use crate::{
    animation::{listeners, Animation as AnimationTrait},
    impl_empty_animation_listeners,
    utils::{spawn_animation, Trackable},
};

pub struct Animation {
    element: web_sys::HtmlElement,
    trackable: Trackable,
    options: ArcStore<Options>,
    last_rect: DomRect,
    handle: Option<web_sys::Animation>,
}

fn progress_scale_axis(value: f64, progress: f64) -> f64 {
    (value - 1.0) * (1.0 - progress) + 1.0
}

impl Animation {
    pub fn new(
        element: web_sys::HtmlElement,
        trackable: Trackable,
        options: ArcStore<Options>,
    ) -> Self {
        Self {
            last_rect: element.get_bounding_client_rect(),
            element,
            trackable,
            options,
            handle: None,
        }
    }

    fn compute_scale(&mut self) -> Option<(f64, f64)> {
        let new_rect = self.element.get_bounding_client_rect();
        let new_width = new_rect.width();
        let new_height = new_rect.height();

        if new_width == 0.0 || new_height == 0.0 {
            return None;
        }

        let last_width = self.last_rect.width();
        let last_height = self.last_rect.height();

        let sx = last_width / new_width;
        let sy = last_height / new_height;

        Some((sx, sy))
    }

    fn compute_duration(&self, sx: f64, sy: f64) -> time::Duration {
        match &self.options.read_untracked().duration {
            Duration::Fixed(duration) => *duration,
            Duration::ScaleBased(duration_fn) => duration_fn(sx.abs().max(sy.abs())),
        }
    }

    fn resize(&mut self) {
        let Some((sx, sy)) = self.compute_scale() else {
            return;
        };

        if (sx - 1.0).abs() > f64::EPSILON || (sy - 1.0).abs() > f64::EPSILON {
            let scale = move |t: f64| {
                let sx = progress_scale_axis(sx, t);
                let sy = progress_scale_axis(sy, t);
                format!("{sx} {sy}")
            };

            let duration = self.compute_duration(sx, sy);

            let options = self.options.read_untracked();
            let handle = spawn_animation()
                .element(&self.element)
                .keyframe(|t| vec![("scale".into(), scale(t))])
                .duration(duration)
                .easing(options.easing)
                .delay(options.delay)
                .call();

            self.handle = Some(handle);
        }
    }
}

impl_empty_animation_listeners!(
    Animation;
    BeforeEnter,
    Enter,
    EnterAnimationsFinished,
    Mutation,
    MutationAnimationsFinished,
    ParentMutation,
    ParentMutationAnimationsFinished,
    Cleanup
);

impl listeners::ImmediateEffect for Animation {
    fn listening_for_immediate_effect(&self) -> bool {
        true
    }

    fn on_immediate_effect(&mut self) {
        self.last_rect = self.element.get_bounding_client_rect();
    }
}

impl listeners::Effect for Animation {
    fn listening_for_effect(&self) -> bool {
        true
    }

    fn on_effect(&mut self) {
        self.resize();
    }
}

impl AnimationTrait for Animation {
    fn enabled(&self) -> bool {
        self.options.read_untracked().enabled
    }

    fn track(&self) {
        self.trackable.track();
    }
}