2023-07-14 22:43:19 +01:00
|
|
|
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
|
|
|
|
|
2023-06-02 13:38:01 +01:00
|
|
|
use crate::core::{ElementMaybeSignal, Position};
|
2023-10-21 15:21:11 -05:00
|
|
|
use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions, UseWindow};
|
2023-07-14 22:43:19 +01:00
|
|
|
use cfg_if::cfg_if;
|
2023-06-02 13:38:01 +01:00
|
|
|
use default_struct_builder::DefaultBuilder;
|
|
|
|
use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart};
|
|
|
|
use leptos::*;
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
use wasm_bindgen::{JsCast, JsValue};
|
|
|
|
|
|
|
|
/// Reactive mouse position
|
|
|
|
///
|
|
|
|
/// ## Demo
|
|
|
|
///
|
|
|
|
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_mouse)
|
|
|
|
///
|
|
|
|
/// ## Basic Usage
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use leptos::*;
|
|
|
|
/// # use leptos_use::{use_mouse, UseMouseReturn};
|
|
|
|
/// #
|
|
|
|
/// # #[component]
|
2023-07-27 18:06:36 +01:00
|
|
|
/// # fn Demo() -> impl IntoView {
|
2023-06-02 13:38:01 +01:00
|
|
|
/// let UseMouseReturn {
|
|
|
|
/// x, y, source_type, ..
|
2023-07-27 18:06:36 +01:00
|
|
|
/// } = use_mouse();
|
|
|
|
/// # view! { }
|
2023-06-02 13:38:01 +01:00
|
|
|
/// # }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Touch is enabled by default. To only detect mouse changes, set `touch` to `false`.
|
|
|
|
/// The `dragover` event is used to track mouse position while dragging.
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use leptos::*;
|
|
|
|
/// # use leptos_use::{use_mouse_with_options, UseMouseOptions, UseMouseReturn};
|
|
|
|
/// #
|
|
|
|
/// # #[component]
|
2023-07-27 18:06:36 +01:00
|
|
|
/// # fn Demo() -> impl IntoView {
|
2023-06-02 13:38:01 +01:00
|
|
|
/// let UseMouseReturn {
|
|
|
|
/// x, y, ..
|
|
|
|
/// } = use_mouse_with_options(
|
|
|
|
/// UseMouseOptions::default().touch(false)
|
|
|
|
/// );
|
2023-07-27 18:06:36 +01:00
|
|
|
/// # view! { }
|
2023-06-02 13:38:01 +01:00
|
|
|
/// # }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ## Custom Extractor
|
|
|
|
///
|
|
|
|
/// It's also possible to provide a custom extractor to get the position from the events.
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use leptos::*;
|
2023-07-03 15:16:22 +01:00
|
|
|
/// # use leptos::html::Div;
|
2023-06-02 13:38:01 +01:00
|
|
|
/// use web_sys::MouseEvent;
|
|
|
|
/// use leptos_use::{use_mouse_with_options, UseMouseOptions, UseMouseReturn, UseMouseEventExtractor, UseMouseCoordType};
|
|
|
|
///
|
|
|
|
/// #[derive(Clone)]
|
|
|
|
/// struct MyExtractor;
|
|
|
|
///
|
|
|
|
/// impl UseMouseEventExtractor for MyExtractor {
|
|
|
|
/// fn extract_mouse_coords(&self, event: &MouseEvent) -> Option<(f64, f64)> {
|
|
|
|
/// Some((event.offset_x() as f64, event.offset_y() as f64))
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// // don't implement fn extract_touch_coords to ignore touch events
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// #[component]
|
2023-07-27 18:06:36 +01:00
|
|
|
/// fn Demo() -> impl IntoView {
|
|
|
|
/// let element = create_node_ref::<Div>();
|
2023-06-02 13:38:01 +01:00
|
|
|
///
|
|
|
|
/// let UseMouseReturn {
|
|
|
|
/// x, y, source_type, ..
|
|
|
|
/// } = use_mouse_with_options(
|
|
|
|
/// UseMouseOptions::default()
|
|
|
|
/// .target(element)
|
|
|
|
/// .coord_type(UseMouseCoordType::Custom(MyExtractor))
|
|
|
|
/// );
|
2023-07-27 18:06:36 +01:00
|
|
|
/// view! { <div node_ref=element></div> }
|
2023-06-02 13:38:01 +01:00
|
|
|
/// }
|
|
|
|
/// ```
|
2023-07-14 22:43:19 +01:00
|
|
|
///
|
|
|
|
/// ## Server-Side Rendering
|
|
|
|
///
|
|
|
|
/// On the server this returns simple `Signal`s with the `initial_value`s.
|
2023-07-27 18:06:36 +01:00
|
|
|
pub fn use_mouse() -> UseMouseReturn {
|
|
|
|
use_mouse_with_options(Default::default())
|
2023-06-02 13:38:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Variant of [`use_mouse`] that accepts options. Please see [`use_mouse`] for how to use.
|
2023-07-27 18:06:36 +01:00
|
|
|
pub fn use_mouse_with_options<El, T, Ex>(options: UseMouseOptions<El, T, Ex>) -> UseMouseReturn
|
2023-06-02 13:38:01 +01:00
|
|
|
where
|
2024-01-03 22:52:59 +00:00
|
|
|
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>> + Clone,
|
2023-06-02 13:38:01 +01:00
|
|
|
T: Into<web_sys::EventTarget> + Clone + 'static,
|
|
|
|
Ex: UseMouseEventExtractor + Clone + 'static,
|
|
|
|
{
|
2023-07-27 18:06:36 +01:00
|
|
|
let (x, set_x) = create_signal(options.initial_value.x);
|
|
|
|
let (y, set_y) = create_signal(options.initial_value.y);
|
|
|
|
let (source_type, set_source_type) = create_signal(UseMouseSourceType::Unset);
|
2023-06-02 13:38:01 +01:00
|
|
|
|
2023-06-13 17:48:32 +01:00
|
|
|
let mouse_handler = {
|
|
|
|
let coord_type = options.coord_type.clone();
|
2023-06-02 13:38:01 +01:00
|
|
|
|
2023-06-13 17:48:32 +01:00
|
|
|
move |event: web_sys::MouseEvent| {
|
|
|
|
let result = coord_type.extract_mouse_coords(&event);
|
|
|
|
|
|
|
|
if let Some((x, y)) = result {
|
2023-06-21 13:09:00 +02:00
|
|
|
set_x.set(x);
|
|
|
|
set_y.set(y);
|
|
|
|
set_source_type.set(UseMouseSourceType::Mouse);
|
2023-06-13 17:48:32 +01:00
|
|
|
}
|
2023-06-02 13:38:01 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-06-13 17:48:32 +01:00
|
|
|
let drag_handler = {
|
|
|
|
let mouse_handler = mouse_handler.clone();
|
|
|
|
|
|
|
|
move |event: web_sys::DragEvent| {
|
|
|
|
let js_value: &JsValue = event.as_ref();
|
|
|
|
mouse_handler(js_value.clone().unchecked_into::<web_sys::MouseEvent>());
|
|
|
|
}
|
2023-06-02 13:38:01 +01:00
|
|
|
};
|
|
|
|
|
2023-06-13 17:48:32 +01:00
|
|
|
let touch_handler = {
|
|
|
|
let coord_type = options.coord_type.clone();
|
2023-06-13 17:50:39 +01:00
|
|
|
|
2023-06-13 17:48:32 +01:00
|
|
|
move |event: web_sys::TouchEvent| {
|
|
|
|
let touches = event.touches();
|
|
|
|
if touches.length() > 0 {
|
|
|
|
let result = coord_type.extract_touch_coords(
|
|
|
|
&touches
|
|
|
|
.get(0)
|
|
|
|
.expect("Just checked that there's at least on touch"),
|
|
|
|
);
|
2023-06-02 13:38:01 +01:00
|
|
|
|
2023-06-13 17:48:32 +01:00
|
|
|
if let Some((x, y)) = result {
|
2023-06-21 13:09:00 +02:00
|
|
|
set_x.set(x);
|
|
|
|
set_y.set(y);
|
|
|
|
set_source_type.set(UseMouseSourceType::Touch);
|
2023-06-13 17:48:32 +01:00
|
|
|
}
|
2023-06-02 13:38:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let initial_value = options.initial_value;
|
|
|
|
let reset = move || {
|
2023-06-21 13:09:00 +02:00
|
|
|
set_x.set(initial_value.x);
|
|
|
|
set_y.set(initial_value.y);
|
2023-06-02 13:38:01 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
// TODO : event filters?
|
|
|
|
|
2023-07-14 22:43:19 +01:00
|
|
|
cfg_if! { if #[cfg(not(feature = "ssr"))] {
|
|
|
|
let target = options.target;
|
2023-09-13 19:39:56 +01:00
|
|
|
let event_listener_options = UseEventListenerOptions::default().passive(true);
|
2023-07-14 22:43:19 +01:00
|
|
|
|
2023-06-02 13:38:01 +01:00
|
|
|
let _ = use_event_listener_with_options(
|
2023-07-27 18:06:36 +01:00
|
|
|
target.clone(),
|
2023-07-14 22:43:19 +01:00
|
|
|
mousemove,
|
|
|
|
mouse_handler,
|
2023-09-13 19:39:56 +01:00
|
|
|
event_listener_options,
|
2023-06-02 13:38:01 +01:00
|
|
|
);
|
|
|
|
let _ = use_event_listener_with_options(
|
2023-07-27 18:06:36 +01:00
|
|
|
target.clone(),
|
2023-07-14 22:43:19 +01:00
|
|
|
dragover,
|
|
|
|
drag_handler,
|
2023-09-13 19:39:56 +01:00
|
|
|
event_listener_options,
|
2023-06-02 13:38:01 +01:00
|
|
|
);
|
2023-07-14 22:43:19 +01:00
|
|
|
if options.touch && !matches!(options.coord_type, UseMouseCoordType::Movement) {
|
|
|
|
let _ = use_event_listener_with_options(
|
2023-07-27 18:06:36 +01:00
|
|
|
target.clone(),
|
2023-07-14 22:43:19 +01:00
|
|
|
touchstart,
|
|
|
|
touch_handler.clone(),
|
2023-09-13 19:39:56 +01:00
|
|
|
event_listener_options,
|
2023-07-14 22:43:19 +01:00
|
|
|
);
|
2023-06-02 13:38:01 +01:00
|
|
|
let _ = use_event_listener_with_options(
|
2023-07-27 18:06:36 +01:00
|
|
|
target.clone(),
|
2023-07-14 22:43:19 +01:00
|
|
|
touchmove,
|
|
|
|
touch_handler,
|
2023-09-13 19:39:56 +01:00
|
|
|
event_listener_options,
|
2023-06-02 13:38:01 +01:00
|
|
|
);
|
2023-07-14 22:43:19 +01:00
|
|
|
if options.reset_on_touch_ends {
|
|
|
|
let _ = use_event_listener_with_options(
|
2023-07-27 18:06:36 +01:00
|
|
|
target,
|
2023-07-14 22:43:19 +01:00
|
|
|
touchend,
|
|
|
|
move |_| reset(),
|
2023-09-13 19:39:56 +01:00
|
|
|
event_listener_options,
|
2023-07-14 22:43:19 +01:00
|
|
|
);
|
|
|
|
}
|
2023-06-02 13:38:01 +01:00
|
|
|
}
|
2023-07-14 22:43:19 +01:00
|
|
|
}}
|
2023-06-02 13:38:01 +01:00
|
|
|
|
|
|
|
UseMouseReturn {
|
2024-01-03 22:52:59 +00:00
|
|
|
x: x.into(),
|
|
|
|
y: y.into(),
|
2023-06-02 13:38:01 +01:00
|
|
|
set_x,
|
|
|
|
set_y,
|
2024-01-03 22:52:59 +00:00
|
|
|
source_type: source_type.into(),
|
2023-06-02 13:38:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(DefaultBuilder)]
|
2023-06-05 00:02:13 +01:00
|
|
|
/// Options for [`use_mouse_with_options`].
|
2023-06-02 13:38:01 +01:00
|
|
|
pub struct UseMouseOptions<El, T, Ex>
|
|
|
|
where
|
2023-10-21 15:21:11 -05:00
|
|
|
El: Clone + Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
|
2023-06-02 13:38:01 +01:00
|
|
|
T: Into<web_sys::EventTarget> + Clone + 'static,
|
|
|
|
Ex: UseMouseEventExtractor + Clone,
|
|
|
|
{
|
|
|
|
/// How to extract the x, y coordinates from mouse events or touches
|
|
|
|
coord_type: UseMouseCoordType<Ex>,
|
|
|
|
|
|
|
|
/// Listen events on `target` element. Defaults to `window`
|
|
|
|
target: El,
|
|
|
|
|
|
|
|
/// Listen to `touchmove` events. Defaults to `true`.
|
|
|
|
touch: bool,
|
|
|
|
|
|
|
|
/// Reset to initial value when `touchend` event fired. Defaults to `false`
|
|
|
|
reset_on_touch_ends: bool,
|
|
|
|
|
|
|
|
/// Initial values. Defaults to `{x: 0.0, y: 0.0}`.
|
|
|
|
initial_value: Position,
|
|
|
|
|
|
|
|
#[builder(skip)]
|
|
|
|
_marker: PhantomData<T>,
|
|
|
|
}
|
|
|
|
|
2023-10-21 15:21:11 -05:00
|
|
|
impl Default for UseMouseOptions<UseWindow, web_sys::Window, UseMouseEventExtractorDefault> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
coord_type: UseMouseCoordType::<UseMouseEventExtractorDefault>::default(),
|
|
|
|
target: use_window(),
|
|
|
|
touch: true,
|
|
|
|
reset_on_touch_ends: false,
|
|
|
|
initial_value: Position { x: 0.0, y: 0.0 },
|
|
|
|
_marker: PhantomData,
|
2023-06-02 13:38:01 +01:00
|
|
|
}
|
|
|
|
}
|
2023-10-21 15:21:11 -05:00
|
|
|
}
|
2023-06-02 13:38:01 +01:00
|
|
|
|
|
|
|
/// Defines how to get the coordinates from the event.
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub enum UseMouseCoordType<E: UseMouseEventExtractor + Clone> {
|
|
|
|
Page,
|
|
|
|
Client,
|
|
|
|
Screen,
|
|
|
|
Movement,
|
|
|
|
Custom(E),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for UseMouseCoordType<UseMouseEventExtractorDefault> {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::Page
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Trait to implement if you want to specify a custom extractor
|
|
|
|
#[allow(unused_variables)]
|
|
|
|
pub trait UseMouseEventExtractor {
|
|
|
|
/// Return the coordinates from mouse events (`Some(x, y)`) or `None`
|
|
|
|
fn extract_mouse_coords(&self, event: &web_sys::MouseEvent) -> Option<(f64, f64)> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the coordinates from touches (`Some(x, y)`) or `None`
|
|
|
|
fn extract_touch_coords(&self, touch: &web_sys::Touch) -> Option<(f64, f64)> {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<E: UseMouseEventExtractor + Clone> UseMouseEventExtractor for UseMouseCoordType<E> {
|
|
|
|
fn extract_mouse_coords(&self, event: &web_sys::MouseEvent) -> Option<(f64, f64)> {
|
|
|
|
match self {
|
|
|
|
UseMouseCoordType::Page => Some((event.page_x() as f64, event.page_y() as f64)),
|
|
|
|
UseMouseCoordType::Client => Some((event.client_x() as f64, event.client_y() as f64)),
|
|
|
|
UseMouseCoordType::Screen => Some((event.screen_x() as f64, event.client_y() as f64)),
|
|
|
|
UseMouseCoordType::Movement => {
|
|
|
|
Some((event.movement_x() as f64, event.movement_y() as f64))
|
|
|
|
}
|
|
|
|
UseMouseCoordType::Custom(ref extractor) => extractor.extract_mouse_coords(event),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn extract_touch_coords(&self, touch: &web_sys::Touch) -> Option<(f64, f64)> {
|
|
|
|
match self {
|
|
|
|
UseMouseCoordType::Page => Some((touch.page_x() as f64, touch.page_y() as f64)),
|
|
|
|
UseMouseCoordType::Client => Some((touch.client_x() as f64, touch.client_y() as f64)),
|
|
|
|
UseMouseCoordType::Screen => Some((touch.screen_x() as f64, touch.client_y() as f64)),
|
|
|
|
UseMouseCoordType::Movement => None,
|
|
|
|
UseMouseCoordType::Custom(ref extractor) => extractor.extract_touch_coords(touch),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct UseMouseEventExtractorDefault;
|
|
|
|
|
|
|
|
impl UseMouseEventExtractor for UseMouseEventExtractorDefault {}
|
|
|
|
|
|
|
|
/// Return type of [`use_mouse`].
|
|
|
|
pub struct UseMouseReturn {
|
|
|
|
/// X coordinate of the mouse pointer / touch
|
2024-01-03 22:52:59 +00:00
|
|
|
pub x: Signal<f64>,
|
2023-06-02 13:38:01 +01:00
|
|
|
/// Y coordinate of the mouse pointer / touch
|
2024-01-03 22:52:59 +00:00
|
|
|
pub y: Signal<f64>,
|
2023-06-02 13:38:01 +01:00
|
|
|
/// Sets the value of `x`. This does not actually move the mouse cursor.
|
|
|
|
pub set_x: WriteSignal<f64>,
|
|
|
|
/// Sets the value of `y`. This does not actually move the mouse cursor.
|
|
|
|
pub set_y: WriteSignal<f64>,
|
|
|
|
/// Identifies the source of the reported coordinates
|
2024-01-03 22:52:59 +00:00
|
|
|
pub source_type: Signal<UseMouseSourceType>,
|
2023-06-02 13:38:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Identifies the source of the reported coordinates
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum UseMouseSourceType {
|
|
|
|
/// coordinates come from mouse movement
|
|
|
|
Mouse,
|
|
|
|
/// coordinates come from touch
|
|
|
|
Touch,
|
|
|
|
/// Initially before any event has been recorded the source type is unset
|
|
|
|
Unset,
|
|
|
|
}
|