use leptos::prelude::*;
use orbital_motion::{MotionCurve, MotionDuration};
#[component]
pub fn PathDrawMotion(
#[prop(into)]
d: Signal<String>,
#[prop(into)]
stroke: Signal<String>,
#[prop(into, default = "orb-line-stroke".into())]
class: String,
#[prop(default = false)]
skip_animation: bool,
#[prop(default = MotionDuration::Normal)]
duration: MotionDuration,
#[prop(default = MotionCurve::DecelerateMax)]
curve: MotionCurve,
#[prop(into)]
draw_key: Signal<String>,
#[prop(default = 2.0)]
stroke_width: f64,
) -> impl IntoView {
let drawn = RwSignal::new(skip_animation);
let path_length = Memo::new(move |_| estimate_path_length(&d.get()));
Effect::new(move |_| {
let _ = draw_key.get();
if skip_animation {
drawn.set(true);
return;
}
drawn.set(false);
schedule_draw_complete(move || drawn.set(true));
});
let dash_style = move || {
let len = path_length.get().max(1.0);
let offset = if drawn.get() { 0.0 } else { len };
format!(
"stroke-dasharray: {len}; stroke-dashoffset: {offset}; transition: stroke-dashoffset {} {}; fill: none; stroke-width: {stroke_width}; stroke: {};",
duration.ms(),
curve.css_var(),
stroke.get(),
)
};
view! {
<path
class=class
d=move || d.get()
style=dash_style
fill="none"
/>
}
}
fn schedule_draw_complete(f: impl FnOnce() + 'static) {
#[cfg(feature = "hydrate")]
{
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
if let Some(win) = web_sys::window() {
let cb = Closure::once(f);
let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(
cb.as_ref().unchecked_ref(),
16,
);
cb.forget();
}
}
#[cfg(not(feature = "hydrate"))]
let _ = f;
#[cfg(not(feature = "hydrate"))]
f();
}
fn estimate_path_length(d: &str) -> f64 {
let nums: Vec<f64> = d
.split(|c: char| !c.is_ascii_digit() && c != '.' && c != '-')
.filter_map(|s| s.parse().ok())
.collect();
if nums.len() < 4 {
return 100.0;
}
let mut len = 0.0;
for w in nums.windows(2) {
len += (w[1] - w[0]).abs();
}
len.max(50.0)
}