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, }