leptos_transition_group 0.2.0

Used to create transitions and animations based on state changes
Documentation
use leptos::prelude::{on_cleanup, window, SetValue, StoredValue, UpdateValue};
use web_sys::wasm_bindgen::{closure::Closure, JsCast, JsValue, UnwrapThrowExt};

#[derive(Clone)]
pub struct NextFrame(StoredValue<Option<AnimationFrameRequestHandle>>);

impl Default for NextFrame {
    fn default() -> Self {
        Self(StoredValue::new(None))
    }
}

impl Copy for NextFrame {}

impl NextFrame {
    pub fn new() -> Self {
        let next_frame = NextFrame::default();

        on_cleanup(move || {
            next_frame.cancel();
        });

        next_frame
    }

    pub fn run(&self, cb: impl FnOnce() + 'static) {
        self.cancel();

        let next_frame_hadnle = self.0.clone();
        let handle = request_animation_frame_with_handle(move || {
            let handle = request_animation_frame_with_handle(cb);
            next_frame_hadnle.set_value(Some(handle));
        });
        self.0.set_value(Some(handle));
    }

    pub fn cancel(&self) {
        self.0.update_value(|value| {
            if let Some(value) = value.take() {
                value.cancel();
            }
        });
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct AnimationFrameRequestHandle(i32);

impl AnimationFrameRequestHandle {
    pub fn cancel(&self) {
        let _ = window().cancel_animation_frame(self.0);
    }
}

fn request_animation_frame_with_handle(cb: impl FnOnce() + 'static) -> AnimationFrameRequestHandle {
    #[inline(never)]
    fn raf(cb: JsValue) -> Result<AnimationFrameRequestHandle, JsValue> {
        window()
            .request_animation_frame(cb.as_ref().unchecked_ref())
            .map(AnimationFrameRequestHandle)
    }

    raf(closure_once(cb)).expect_throw("request_animation_frame_with_handle")
}

fn closure_once(cb: impl FnOnce() + 'static) -> JsValue {
    let mut wrapped_cb: Option<Box<dyn FnOnce()>> = Some(Box::new(cb));
    let closure = Closure::new(move || {
        if let Some(cb) = wrapped_cb.take() {
            cb()
        }
    });
    closure.into_js_value()
}