use crate::core::{ElementMaybeSignal, MaybeRwSignal, PointerType, Position};
use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions, UseWindow};
use default_struct_builder::DefaultBuilder;
use leptos::ev::{pointerdown, pointermove, pointerup};
use leptos::*;
use std::marker::PhantomData;
use std::rc::Rc;
use wasm_bindgen::JsCast;
use web_sys::PointerEvent;
pub fn use_draggable<El, T>(target: El) -> UseDraggableReturn
where
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
T: Into<web_sys::EventTarget> + Clone + 'static,
{
use_draggable_with_options::<
El,
T,
UseWindow,
web_sys::Window,
web_sys::EventTarget,
web_sys::EventTarget,
>(target, UseDraggableOptions::default())
}
pub fn use_draggable_with_options<El, T, DragEl, DragT, HandleEl, HandleT>(
target: El,
options: UseDraggableOptions<DragEl, DragT, HandleEl, HandleT>,
) -> UseDraggableReturn
where
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
T: Into<web_sys::EventTarget> + Clone + 'static,
DragEl: Clone,
DragEl: Into<ElementMaybeSignal<DragT, web_sys::EventTarget>>,
DragT: Into<web_sys::EventTarget> + Clone + 'static,
HandleEl: Into<ElementMaybeSignal<HandleT, web_sys::EventTarget>>,
HandleT: Into<web_sys::EventTarget> + Clone + 'static,
{
let UseDraggableOptions {
exact,
prevent_default,
stop_propagation,
dragging_element,
handle,
pointer_types,
initial_value,
on_start,
on_move,
on_end,
..
} = options;
let target = target.into();
let dragging_handle = if let Some(handle) = handle {
let handle = (handle).into();
Signal::derive(move || handle.get().map(|handle| handle.into()))
} else {
let target = target.clone();
Signal::derive(move || target.get().map(|target| target.into()))
};
let (position, set_position) = initial_value.into_signal();
let (start_position, set_start_position) = create_signal(None::<Position>);
let filter_event = move |event: &PointerEvent| {
let ty = event.pointer_type();
pointer_types.iter().any(|p| p.to_string() == ty)
};
let handle_event = move |event: PointerEvent| {
if prevent_default.get_untracked() {
event.prevent_default();
}
if stop_propagation.get_untracked() {
event.stop_propagation();
}
};
let on_pointer_down = {
let filter_event = filter_event.clone();
move |event: PointerEvent| {
if !filter_event(&event) {
return;
}
if let Some(target) = target.get_untracked() {
let target: web_sys::Element = target.into().unchecked_into();
if exact.get_untracked() && event_target::<web_sys::Element>(&event) != target {
return;
}
let rect = target.get_bounding_client_rect();
let position = Position {
x: event.client_x() as f64 - rect.left(),
y: event.client_y() as f64 - rect.top(),
};
#[cfg(debug_assertions)]
let prev = SpecialNonReactiveZone::enter();
if !on_start(UseDraggableCallbackArgs {
position,
event: event.clone(),
}) {
#[cfg(debug_assertions)]
SpecialNonReactiveZone::exit(prev);
return;
}
#[cfg(debug_assertions)]
SpecialNonReactiveZone::exit(prev);
set_start_position.set(Some(position));
handle_event(event);
}
}
};
let on_pointer_move = {
let filter_event = filter_event.clone();
move |event: PointerEvent| {
if !filter_event(&event) {
return;
}
if let Some(start_position) = start_position.get_untracked() {
let position = Position {
x: event.client_x() as f64 - start_position.x,
y: event.client_y() as f64 - start_position.y,
};
set_position.set(position);
#[cfg(debug_assertions)]
let prev = SpecialNonReactiveZone::enter();
on_move(UseDraggableCallbackArgs {
position,
event: event.clone(),
});
#[cfg(debug_assertions)]
SpecialNonReactiveZone::exit(prev);
handle_event(event);
}
}
};
let on_pointer_up = move |event: PointerEvent| {
if !filter_event(&event) {
return;
}
if start_position.get_untracked().is_none() {
return;
}
set_start_position.set(None);
#[cfg(debug_assertions)]
let prev = SpecialNonReactiveZone::enter();
on_end(UseDraggableCallbackArgs {
position: position.get_untracked(),
event: event.clone(),
});
#[cfg(debug_assertions)]
SpecialNonReactiveZone::exit(prev);
handle_event(event);
};
let listener_options = UseEventListenerOptions::default().capture(true);
let _ = use_event_listener_with_options(
dragging_handle,
pointerdown,
on_pointer_down,
listener_options,
);
let _ = use_event_listener_with_options(
dragging_element.clone(),
pointermove,
on_pointer_move,
listener_options,
);
let _ = use_event_listener_with_options(
dragging_element,
pointerup,
on_pointer_up,
listener_options,
);
UseDraggableReturn {
x: Signal::derive(move || position.get().x),
y: Signal::derive(move || position.get().y),
position,
set_position,
is_dragging: Signal::derive(move || start_position.get().is_some()),
style: Signal::derive(move || {
let position = position.get();
format!("left: {}px; top: {}px;", position.x, position.y)
}),
}
}
#[derive(DefaultBuilder)]
pub struct UseDraggableOptions<DragEl, DragT, HandleEl, HandleT>
where
DragEl: Into<ElementMaybeSignal<DragT, web_sys::EventTarget>>,
DragT: Into<web_sys::EventTarget> + Clone + 'static,
HandleEl: Into<ElementMaybeSignal<HandleT, web_sys::EventTarget>>,
HandleT: Into<web_sys::EventTarget> + Clone + 'static,
{
#[builder(into)]
exact: MaybeSignal<bool>,
#[builder(into)]
prevent_default: MaybeSignal<bool>,
#[builder(into)]
stop_propagation: MaybeSignal<bool>,
dragging_element: DragEl,
handle: Option<HandleEl>,
pointer_types: Vec<PointerType>,
#[builder(into)]
initial_value: MaybeRwSignal<Position>,
on_start: Rc<dyn Fn(UseDraggableCallbackArgs) -> bool>,
on_move: Rc<dyn Fn(UseDraggableCallbackArgs)>,
on_end: Rc<dyn Fn(UseDraggableCallbackArgs)>,
#[builder(skip)]
_marker1: PhantomData<DragT>,
#[builder(skip)]
_marker2: PhantomData<HandleT>,
}
impl Default
for UseDraggableOptions<UseWindow, web_sys::Window, web_sys::EventTarget, web_sys::EventTarget>
{
fn default() -> Self {
Self {
exact: MaybeSignal::default(),
prevent_default: MaybeSignal::default(),
stop_propagation: MaybeSignal::default(),
dragging_element: use_window(),
handle: None,
pointer_types: vec![PointerType::Mouse, PointerType::Touch, PointerType::Pen],
initial_value: MaybeRwSignal::default(),
on_start: Rc::new(|_| true),
on_move: Rc::new(|_| {}),
on_end: Rc::new(|_| {}),
_marker1: PhantomData,
_marker2: PhantomData,
}
}
}
pub struct UseDraggableCallbackArgs {
pub position: Position,
pub event: PointerEvent,
}
pub struct UseDraggableReturn {
pub x: Signal<f64>,
pub y: Signal<f64>,
pub position: Signal<Position>,
pub set_position: WriteSignal<Position>,
pub is_dragging: Signal<bool>,
pub style: Signal<String>,
}