use crate::core::{ElementMaybeSignal, Position};
use crate::{
use_mouse_with_options, use_window, UseMouseCoordType, UseMouseEventExtractor,
UseMouseEventExtractorDefault, UseMouseOptions, UseMouseReturn, UseMouseSourceType, UseWindow,
};
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::*;
use std::marker::PhantomData;
/// Reactive mouse position related to an element.
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_mouse_in_element)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos::html::Div;
/// # use leptos_use::{use_mouse_in_element, UseMouseInElementReturn};
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// let target = create_node_ref::
();
/// let UseMouseInElementReturn { x, y, is_outside, .. } = use_mouse_in_element(target);
///
/// view! {
///
///
Hello world
///
/// }
/// # }
/// ```
///
/// ## Server-Side Rendering
///
/// On the server this returns simple Signals with the `initial_value` for `x` and `y`,
/// no-op for `stop`, `is_outside = true` and `0.0` for the rest of the signals.
pub fn use_mouse_in_element
(target: El) -> UseMouseInElementReturn
where
El: Into> + Clone,
T: Into + Clone + 'static,
{
use_mouse_in_element_with_options(target, Default::default())
}
/// Version of [`use_mouse_in_element`] that takes a `UseMouseInElementOptions`. See [`use_mouse_in_element`] for how to use.
pub fn use_mouse_in_element_with_options(
target: El,
options: UseMouseInElementOptions,
) -> UseMouseInElementReturn
where
El: Into> + Clone,
T: Into + Clone + 'static,
OptEl: Into> + Clone,
OptT: Into + Clone + 'static,
OptEx: UseMouseEventExtractor + Clone + 'static,
{
let UseMouseInElementOptions {
coord_type,
target: use_mouse_target,
touch,
reset_on_touch_ends,
initial_value,
handle_outside,
..
} = options;
let UseMouseReturn {
x, y, source_type, ..
} = use_mouse_with_options(
UseMouseOptions::default()
.coord_type(coord_type)
.target(use_mouse_target)
.touch(touch)
.reset_on_touch_ends(reset_on_touch_ends)
.initial_value(initial_value),
);
let (element_x, set_element_x) = create_signal(0.0);
let (element_y, set_element_y) = create_signal(0.0);
let (element_position_x, set_element_position_x) = create_signal(0.0);
let (element_position_y, set_element_position_y) = create_signal(0.0);
let (element_width, set_element_width) = create_signal(0.0);
let (element_height, set_element_height) = create_signal(0.0);
let (is_outside, set_outside) = create_signal(true);
cfg_if! { if #[cfg(feature = "ssr")] {
let stop = || ();
let _ = handle_outside;
let _ = set_element_x;
let _ = set_element_y;
let _ = set_element_position_x;
let _ = set_element_position_y;
let _ = set_element_width;
let _ = set_element_height;
let _ = set_outside;
let _ = target;
} else {
use crate::use_event_listener;
use leptos::ev::mouseleave;
let target = target.into();
let window = window();
let stop = watch(
move || (target.get(), x.get(), y.get()),
move |(el, x, y), _, _| {
if let Some(el) = el {
let el: web_sys::Element = el.clone().into();
let rect = el.get_bounding_client_rect();
let left = rect.left();
let top = rect.top();
let width = rect.width();
let height = rect.height();
set_element_position_x.set(left + window.page_x_offset().unwrap_or_default());
set_element_position_y.set(top + window.page_y_offset().unwrap_or_default());
set_element_height.set(height);
set_element_width.set(width);
let el_x = *x - element_position_x.get_untracked();
let el_y = *y - element_position_y.get_untracked();
set_outside.set(
width == 0.0
|| height == 0.0
|| el_x <= 0.0
|| el_y <= 0.0
|| el_x > width
|| el_y > height,
);
if handle_outside || !is_outside.get_untracked() {
set_element_x.set(el_x);
set_element_y.set(el_y);
}
}
},
false,
);
let _ = use_event_listener(document(), mouseleave, move |_| set_outside.set(true));
}}
UseMouseInElementReturn {
x,
y,
source_type,
element_x: element_x.into(),
element_y: element_y.into(),
element_position_x: element_position_x.into(),
element_position_y: element_position_y.into(),
element_width: element_width.into(),
element_height: element_height.into(),
is_outside: is_outside.into(),
stop,
}
}
/// Options for [`use_mouse_in_element_with_options`].
#[derive(DefaultBuilder)]
pub struct UseMouseInElementOptions
where
El: Clone + Into>,
T: Into + Clone + 'static,
Ex: UseMouseEventExtractor + Clone,
{
/// How to extract the x, y coordinates from mouse events or touches
coord_type: UseMouseCoordType,
/// 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,
/// If `true` updates the `element_x` and `element_y` signals even if the
/// mouse is outside of the element. If `false` it doesn't update them when outside.
/// Defaults to `true`.
handle_outside: bool,
#[builder(skip)]
_marker: PhantomData,
}
impl Default
for UseMouseInElementOptions
{
fn default() -> Self {
Self {
coord_type: UseMouseCoordType::::default(),
target: use_window(),
touch: true,
reset_on_touch_ends: false,
initial_value: Position { x: 0.0, y: 0.0 },
handle_outside: true,
_marker: PhantomData,
}
}
}
/// Return type of [`use_mouse_in_element`].
pub struct UseMouseInElementReturn
where
F: Fn() + Clone,
{
/// X coordinate of the mouse pointer / touch
pub x: Signal,
/// Y coordinate of the mouse pointer / touch
pub y: Signal,
/// Identifies the source of the reported coordinates
pub source_type: Signal,
/// X coordinate of the pointer relative to the left edge of the element
pub element_x: Signal,
/// Y coordinate of the pointer relative to the top edge of the element
pub element_y: Signal,
/// X coordinate of the element relative to the left edge of the document
pub element_position_x: Signal,
/// Y coordinate of the element relative to the top edge of the document
pub element_position_y: Signal,
/// Width of the element
pub element_width: Signal,
/// Height of the element
pub element_height: Signal,
/// `true` if the mouse is outside of the element
pub is_outside: Signal,
/// Stop watching
pub stop: F,
}