1use crate::{
6 DragAxis,
7 DragConfig,
8 DragConstraints,
9 };
14
15type MomentumStepCallback = Rc<RefCell<Option<Box<dyn FnMut()>>>>;
17use leptos::prelude::{
18 Children, ClassAttribute, Effect, ElementChild, Get, GetUntracked, NodeRef, NodeRefAttribute,
19 OnAttribute, Set, StyleAttribute,
20};
21use leptos::reactive::signal::signal;
22use leptos::*;
23use leptos_motion_core::*;
24use std::cell::RefCell;
25use std::collections::HashMap;
26use std::rc::Rc;
27use wasm_bindgen::prelude::*;
28use web_sys;
29
30#[derive(Clone)]
32pub enum AnimationTargetOrReactive {
33 Static(AnimationTarget),
35 Reactive(Rc<dyn Fn() -> AnimationTarget>),
37}
38
39impl AnimationTargetOrReactive {
40 pub fn get_target(&self) -> AnimationTarget {
42 match self {
43 AnimationTargetOrReactive::Static(target) => target.clone(),
44 AnimationTargetOrReactive::Reactive(closure) => closure(),
45 }
46 }
47}
48
49#[component]
51pub fn MotionDiv(
52 #[prop(optional)]
54 class: Option<String>,
55 #[prop(optional)]
57 style: Option<String>,
58 #[prop(optional)]
60 node_ref: Option<NodeRef<leptos::html::Div>>,
61 #[prop(optional)]
63 initial: Option<AnimationTarget>,
64 #[prop(optional)]
66 animate: Option<AnimationTarget>,
67 #[prop(optional)]
69 _transition: Option<Transition>,
70 #[prop(optional)]
72 while_hover: Option<AnimationTarget>,
73 #[prop(optional)]
75 while_tap: Option<AnimationTarget>,
76 #[prop(optional)]
78 _layout: Option<bool>,
79 #[prop(optional)]
81 drag: Option<DragConfig>,
82 #[prop(optional)]
84 _drag_constraints: Option<DragConstraints>,
85 children: Children,
87) -> impl IntoView {
88 let (_is_hovered, _set_hovered) = signal(false);
90 let (_is_tapped, _set_tapped) = signal(false);
91 let (current_styles, set_styles) = signal(HashMap::<String, String>::new());
92
93 let (is_dragging, set_dragging) = signal(false);
95 let (drag_position, set_drag_position) = signal((0.0, 0.0));
96 let (drag_velocity, set_drag_velocity) = signal((0.0, 0.0));
97 let (is_animating_momentum, set_animating_momentum) = signal(false);
98
99 let node_ref = node_ref.unwrap_or_else(|| NodeRef::new());
101
102 if let Some(initial_target) = initial {
104 let mut styles = HashMap::new();
105 for (key, value) in initial_target {
106 styles.insert(key, value.to_string_value());
107 }
108 set_styles.set(styles);
109 }
110
111 if let Some(animate_target) = animate {
113 Effect::new(move |_| {
115 let animate_values = animate_target.clone();
116 let mut styles = current_styles.get();
117 for (key, value) in animate_values.iter() {
118 styles.insert(key.clone(), value.to_string_value());
119 }
120 set_styles.set(styles);
121 });
122 }
123
124 if let Some(hover_target) = while_hover {
126 let (hover_signal, _set_hover_signal) = signal(hover_target.clone());
127 Effect::new(move |_| {
128 let is_hovered = _is_hovered.get();
129 let hover_values = hover_signal.get();
130 let mut styles = current_styles.get();
131
132 if is_hovered {
133 for (key, value) in hover_values.iter() {
134 styles.insert(key.clone(), value.to_string_value());
135 }
136 }
137 set_styles.set(styles);
138 });
139 }
140
141 if let Some(tap_target) = while_tap {
143 let (tap_signal, _set_tap_signal) = signal(tap_target.clone());
144 Effect::new(move |_| {
145 let is_tapped = _is_tapped.get();
146 let tap_values = tap_signal.get();
147 let mut styles = current_styles.get();
148
149 if is_tapped {
150 for (key, value) in tap_values.iter() {
151 styles.insert(key.clone(), value.to_string_value());
152 }
153 }
154 set_styles.set(styles);
155 });
156 }
157
158 let style_string = move || {
160 let mut styles = current_styles.get_untracked();
161
162 let (drag_x, drag_y) = drag_position.get_untracked();
164 if drag_x != 0.0 || drag_y != 0.0 {
165 styles.insert(
166 "transform".to_string(),
167 format!("translate({}px, {}px)", drag_x, drag_y),
168 );
169 }
170
171 let mut style_parts = styles
173 .iter()
174 .map(|(key, value)| format!("{}: {}", key, value))
175 .collect::<Vec<_>>();
176
177 if let Some(style_prop) = &style {
179 style_parts.push(style_prop.clone());
180 }
181
182 style_parts.join("; ")
183 };
184
185 let drag_config_clone = drag.clone();
187 let drag_config_mousemove = drag.clone();
188 let drag_config_mouseup = drag.clone();
189
190 Effect::new(move |_| {
192 let _ = current_styles.get();
197 let _ = _is_hovered.get();
198 let _ = _is_tapped.get();
199 let _ = is_dragging.get();
200 let _ = drag_position.get();
201 let _ = drag_velocity.get();
202 let _ = is_animating_momentum.get();
203
204 move || {
206 web_sys::console::log_1(&"๐งน MotionDiv: Cleanup effect triggered".into());
209 }
210 });
211
212 view! {
213 <div
214 node_ref=node_ref
215 class=class
216 style=style_string()
217 on:mousedown=move |_event| {
218 if let Some(_drag_config) = &drag_config_clone {
219 set_dragging.set(true);
220 set_animating_momentum.set(false);
221 }
222 }
223 on:mousemove=move |event| {
224 if let Some(_drag_config) = &drag_config_mousemove && is_dragging.get() {
225 let (current_x, current_y) = drag_position.get();
226 let new_x = current_x + event.movement_x() as f64;
227 let new_y = current_y + event.movement_y() as f64;
228 set_drag_position.set((new_x, new_y));
229
230 let velocity_x = event.movement_x() as f64;
232 let velocity_y = event.movement_y() as f64;
233 set_drag_velocity.set((velocity_x, velocity_y));
234 }
235 }
236 on:mouseup=move |_event| {
237 if let Some(drag_config) = &drag_config_mouseup {
238 set_dragging.set(false);
239
240 if drag_config.momentum.unwrap_or(false) {
242 set_animating_momentum.set(true);
243
244 let start_momentum = move || {
246 let momentum_step: MomentumStepCallback = Rc::new(RefCell::new(None));
248
249 let momentum_step_ref = momentum_step.clone();
250 let set_drag_position_clone = set_drag_position.clone();
251 let set_drag_velocity_clone = set_drag_velocity.clone();
252 let set_animating_momentum_clone = set_animating_momentum.clone();
253 let drag_config_clone = drag_config.clone();
254 let drag_position_clone = drag_position.clone();
255 let drag_velocity_clone = drag_velocity.clone();
256 let is_animating_momentum_clone = is_animating_momentum.clone();
257
258 *momentum_step.borrow_mut() = Some(Box::new(move || {
259 if !is_animating_momentum_clone.get() {
261 return;
262 }
263
264 let (current_x, current_y) = drag_position_clone.get();
265 let (velocity_x, velocity_y) = drag_velocity_clone.get();
266
267 let friction = 0.95;
269 let new_velocity_x = velocity_x * friction;
270 let new_velocity_y = velocity_y * friction;
271
272 let new_x = current_x + new_velocity_x;
274 let new_y = current_y + new_velocity_y;
275
276 let (final_x, final_y) = if let Some(constraints) = &drag_config_clone.constraints {
278 let mut constrained_x = new_x;
279 let mut constrained_y = new_y;
280
281 match drag_config_clone.axis {
283 Some(DragAxis::X) => constrained_y = current_y,
284 Some(DragAxis::Y) => constrained_x = current_x,
285 _ => {} }
287
288 let elastic_factor = drag_config_clone.elastic.unwrap_or(0.0);
290
291 if let Some(left) = constraints.left && constrained_x < left {
292 if elastic_factor > 0.0 {
293 let overshoot = left - constrained_x;
294 constrained_x = left - (overshoot * elastic_factor);
295 } else {
296 constrained_x = left;
297 }
298 }
299 if let Some(right) = constraints.right && constrained_x > right {
300 if elastic_factor > 0.0 {
301 let overshoot = constrained_x - right;
302 constrained_x = right + (overshoot * elastic_factor);
303 } else {
304 constrained_x = right;
305 }
306 }
307 if let Some(top) = constraints.top && constrained_y < top {
308 if elastic_factor > 0.0 {
309 let overshoot = top - constrained_y;
310 constrained_y = top - (overshoot * elastic_factor);
311 } else {
312 constrained_y = top;
313 }
314 }
315 if let Some(bottom) = constraints.bottom && constrained_y > bottom {
316 if elastic_factor > 0.0 {
317 let overshoot = constrained_y - bottom;
318 constrained_y = bottom + (overshoot * elastic_factor);
319 } else {
320 constrained_y = bottom;
321 }
322 }
323
324 (constrained_x, constrained_y)
325 } else {
326 (new_x, new_y)
327 };
328
329 set_drag_position_clone.set((final_x, final_y));
331 set_drag_velocity_clone.set((new_velocity_x, new_velocity_y));
332
333 let velocity_magnitude = (new_velocity_x * new_velocity_x + new_velocity_y * new_velocity_y).sqrt();
335 if velocity_magnitude < 0.1 {
336 set_animating_momentum_clone.set(false);
337 } else {
338 let momentum_step_ref = momentum_step_ref.clone();
340 let _ = web_sys::window()
341 .unwrap()
342 .set_timeout_with_callback_and_timeout_and_arguments_0(
343 &Closure::wrap(Box::new(move || {
344 if let Some(ref mut step) = *momentum_step_ref.borrow_mut() {
346 step();
347 }
348 }) as Box<dyn FnMut()>).as_ref().unchecked_ref(),
349 16 )
351 .unwrap();
352 }
353 }));
354
355 if let Some(ref mut step) = *momentum_step.borrow_mut() {
357 step();
358 }
359 };
360
361 start_momentum();
363 }
364 }
365 }
366 on:mouseenter=move |_event| {
367 _set_hovered.set(true);
368 }
369 on:mouseleave=move |_event| {
370 _set_hovered.set(false);
371 }
372 on:click=move |_event| {
373 _set_tapped.set(true);
374 let set_tapped_clone = _set_tapped.clone();
376 let _ = web_sys::window()
377 .unwrap()
378 .set_timeout_with_callback_and_timeout_and_arguments_0(
379 &Closure::wrap(Box::new(move || {
380 set_tapped_clone.set(false);
381 }) as Box<dyn FnMut()>).as_ref().unchecked_ref(),
382 150 )
384 .unwrap();
385 }
386 >
387 {children()}
388 </div>
389 }
390}
391
392#[component]
394pub fn MotionSpan(
395 #[prop(optional)]
397 class: Option<String>,
398 #[prop(optional)]
400 initial: Option<AnimationTarget>,
401 #[prop(optional)]
403 animate: Option<AnimationTarget>,
404 #[prop(optional)]
406 _transition: Option<Transition>,
407 #[prop(optional)]
409 _while_hover: Option<AnimationTarget>,
410 #[prop(optional)]
412 _while_tap: Option<AnimationTarget>,
413 children: Children,
415) -> impl IntoView {
416 let (_is_hovered, _set_hovered) = signal(false);
418 let (_is_tapped, _set_tapped) = signal(false);
419 let (current_styles, set_styles) = signal(HashMap::<String, String>::new());
420
421 if let Some(initial_target) = initial {
423 let mut styles = HashMap::new();
424 for (key, value) in initial_target {
425 styles.insert(key, value.to_string_value());
426 }
427 set_styles.set(styles);
428 }
429
430 if let Some(animate_target) = animate {
432 let mut styles = current_styles.get();
433 for (key, value) in animate_target {
434 styles.insert(key, value.to_string_value());
435 }
436 set_styles.set(styles);
437 }
438
439 let style_string = move || {
441 let styles = current_styles.get();
442 styles
443 .iter()
444 .map(|(key, value)| format!("{}: {}", key, value))
445 .collect::<Vec<_>>()
446 .join("; ")
447 };
448
449 view! {
450 <span
451 class=class
452 style=style_string()
453 >
454 {children()}
455 </span>
456 }
457}