leptos_use/
use_mouse_in_element.rs

1use crate::core::{IntoElementMaybeSignal, Position};
2use crate::{
3    UseMouseCoordType, UseMouseEventExtractor, UseMouseOptions, UseMouseReturn, UseMouseSourceType,
4    UseWindow, use_mouse_with_options, use_window,
5};
6use default_struct_builder::DefaultBuilder;
7use leptos::prelude::*;
8use std::convert::Infallible;
9use std::marker::PhantomData;
10
11/// Reactive mouse position related to an element.
12///
13/// ## Demo
14///
15/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_mouse_in_element)
16///
17/// ## Usage
18///
19/// ```
20/// # use leptos::prelude::*;
21/// # use leptos::html::Div;
22/// # use leptos_use::{use_mouse_in_element, UseMouseInElementReturn};
23/// #
24/// # #[component]
25/// # fn Demo() -> impl IntoView {
26/// let target = NodeRef::<Div>::new();
27/// let UseMouseInElementReturn { x, y, is_outside, .. } = use_mouse_in_element(target);
28///
29/// view! {
30///     <div node_ref=target>
31///         <h1>Hello world</h1>
32///     </div>
33/// }
34/// # }
35/// ```
36///
37/// ## SendWrapped Return
38///
39/// The returned closure `stop` is a sendwrapped function. It can
40/// only be called from the same thread that called `use_mouse_in_element`.
41///
42/// ## Server-Side Rendering
43///
44/// > Make sure you follow the [instructions in Server-Side Rendering](https://leptos-use.rs/server_side_rendering.html).
45///
46/// On the server this returns simple Signals with the `initial_value` for `x` and `y`,
47/// no-op for `stop`, `is_outside = true` and `0.0` for the rest of the signals.
48pub fn use_mouse_in_element<El, M>(
49    target: El,
50) -> UseMouseInElementReturn<impl Fn() + Clone + Send + Sync>
51where
52    El: IntoElementMaybeSignal<web_sys::Element, M>,
53{
54    use_mouse_in_element_with_options(target, Default::default())
55}
56
57/// Version of [`use_mouse_in_element`] that takes a `UseMouseInElementOptions`. See [`use_mouse_in_element`] for how to use.
58pub fn use_mouse_in_element_with_options<El, M, OptEl, OptM, OptEx>(
59    target: El,
60    options: UseMouseInElementOptions<OptEl, OptM, OptEx>,
61) -> UseMouseInElementReturn<impl Fn() + Clone + Send + Sync>
62where
63    El: IntoElementMaybeSignal<web_sys::Element, M>,
64    OptEl: IntoElementMaybeSignal<web_sys::EventTarget, OptM>,
65    OptEx: UseMouseEventExtractor + Clone + 'static,
66{
67    let UseMouseInElementOptions {
68        coord_type,
69        target: use_mouse_target,
70        touch,
71        reset_on_touch_ends,
72        initial_value,
73        handle_outside,
74        ..
75    } = options;
76
77    let UseMouseReturn {
78        x, y, source_type, ..
79    } = use_mouse_with_options(
80        UseMouseOptions::default()
81            .coord_type(coord_type)
82            .target(use_mouse_target)
83            .touch(touch)
84            .reset_on_touch_ends(reset_on_touch_ends)
85            .initial_value(initial_value),
86    );
87
88    let (element_x, set_element_x) = signal(0.0);
89    let (element_y, set_element_y) = signal(0.0);
90    let (element_position_x, set_element_position_x) = signal(0.0);
91    let (element_position_y, set_element_position_y) = signal(0.0);
92    let (element_width, set_element_width) = signal(0.0);
93    let (element_height, set_element_height) = signal(0.0);
94    let (is_outside, set_outside) = signal(true);
95
96    let stop;
97
98    #[cfg(feature = "ssr")]
99    {
100        stop = || ();
101
102        let _ = handle_outside;
103
104        let _ = set_element_x;
105        let _ = set_element_y;
106        let _ = set_element_position_x;
107        let _ = set_element_position_y;
108        let _ = set_element_width;
109        let _ = set_element_height;
110        let _ = set_outside;
111        let _ = target;
112    }
113
114    #[cfg(not(feature = "ssr"))]
115    {
116        use crate::{sendwrap_fn, use_event_listener};
117        use leptos::ev::mouseleave;
118
119        let target = target.into_element_maybe_signal();
120        let window = window();
121
122        let effect = Effect::watch(
123            move || (target.get(), x.get(), y.get()),
124            move |(el, x, y), _, _| {
125                if let Some(el) = el {
126                    let el = el.clone();
127                    let rect = el.get_bounding_client_rect();
128                    let left = rect.left();
129                    let top = rect.top();
130                    let width = rect.width();
131                    let height = rect.height();
132
133                    set_element_position_x.set(left + window.page_x_offset().unwrap_or_default());
134                    set_element_position_y.set(top + window.page_y_offset().unwrap_or_default());
135
136                    set_element_height.set(height);
137                    set_element_width.set(width);
138
139                    let el_x = *x - element_position_x.get_untracked();
140                    let el_y = *y - element_position_y.get_untracked();
141
142                    set_outside.set(
143                        width == 0.0
144                            || height == 0.0
145                            || el_x <= 0.0
146                            || el_y <= 0.0
147                            || el_x > width
148                            || el_y > height,
149                    );
150
151                    if handle_outside || !is_outside.get_untracked() {
152                        set_element_x.set(el_x);
153                        set_element_y.set(el_y);
154                    }
155                }
156            },
157            false,
158        );
159
160        stop = sendwrap_fn!(move || effect.stop());
161
162        let _ = use_event_listener(document(), mouseleave, move |_| set_outside.set(true));
163    }
164
165    UseMouseInElementReturn {
166        x,
167        y,
168        source_type,
169        element_x: element_x.into(),
170        element_y: element_y.into(),
171        element_position_x: element_position_x.into(),
172        element_position_y: element_position_y.into(),
173        element_width: element_width.into(),
174        element_height: element_height.into(),
175        is_outside: is_outside.into(),
176        stop,
177    }
178}
179
180/// Options for [`use_mouse_in_element_with_options`].
181#[derive(DefaultBuilder)]
182pub struct UseMouseInElementOptions<El, M, Ex>
183where
184    El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
185    Ex: UseMouseEventExtractor + Clone,
186{
187    /// How to extract the x, y coordinates from mouse events or touches
188    coord_type: UseMouseCoordType<Ex>,
189
190    /// Listen events on `target` element. Defaults to `window`
191    target: El,
192
193    /// Listen to `touchmove` events. Defaults to `true`.
194    touch: bool,
195
196    /// Reset to initial value when `touchend` event fired. Defaults to `false`
197    reset_on_touch_ends: bool,
198
199    /// Initial values. Defaults to `{x: 0.0, y: 0.0}`.
200    initial_value: Position,
201
202    /// If `true` updates the `element_x` and `element_y` signals even if the
203    /// mouse is outside of the element. If `false` it doesn't update them when outside.
204    /// Defaults to `true`.
205    handle_outside: bool,
206
207    #[builder(skip)]
208    _marker: PhantomData<M>,
209}
210
211impl<M> Default for UseMouseInElementOptions<UseWindow, M, Infallible>
212where
213    UseWindow: IntoElementMaybeSignal<web_sys::EventTarget, M>,
214{
215    fn default() -> Self {
216        Self {
217            coord_type: UseMouseCoordType::default(),
218            target: use_window(),
219            touch: true,
220            reset_on_touch_ends: false,
221            initial_value: Position { x: 0.0, y: 0.0 },
222            handle_outside: true,
223            _marker: PhantomData,
224        }
225    }
226}
227
228/// Return type of [`use_mouse_in_element`].
229pub struct UseMouseInElementReturn<F>
230where
231    F: Fn() + Clone + Send + Sync,
232{
233    /// X coordinate of the mouse pointer / touch
234    pub x: Signal<f64>,
235
236    /// Y coordinate of the mouse pointer / touch
237    pub y: Signal<f64>,
238
239    /// Identifies the source of the reported coordinates
240    pub source_type: Signal<UseMouseSourceType>,
241
242    /// X coordinate of the pointer relative to the left edge of the element
243    pub element_x: Signal<f64>,
244
245    /// Y coordinate of the pointer relative to the top edge of the element
246    pub element_y: Signal<f64>,
247
248    /// X coordinate of the element relative to the left edge of the document
249    pub element_position_x: Signal<f64>,
250
251    /// Y coordinate of the element relative to the top edge of the document
252    pub element_position_y: Signal<f64>,
253
254    /// Width of the element
255    pub element_width: Signal<f64>,
256
257    /// Height of the element
258    pub element_height: Signal<f64>,
259
260    /// `true` if the mouse is outside of the element
261    pub is_outside: Signal<bool>,
262
263    /// Stop watching
264    pub stop: F,
265}