Skip to main content

euv_ui/hook/use_hover/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`UseHover`].
4impl UseHover {
5    /// Creates a handle with the hover state starting at `false`.
6    ///
7    /// # Returns
8    ///
9    /// - `UseHover` - The fresh handle.
10    pub(crate) fn new() -> Self {
11        Self {
12            target: NodeRef::new(),
13            hovered: Signal::create(false),
14            teardown: Rc::new(RefCell::new(None)),
15        }
16    }
17
18    /// Returns the current hover state as a snapshot.
19    ///
20    /// # Returns
21    ///
22    /// - `bool` - `true` when the pointer is over the watched element.
23    pub fn is_hovered(&self) -> bool {
24        self.get_hovered().get()
25    }
26
27    /// Subscribes to the document's `mouseover` and `mouseout` events.
28    ///
29    /// Moving between two children of the watched element fires
30    /// `mouseout` with a related target that is still inside the
31    /// element, and that case must not clear the hover state — so the
32    /// related target is checked rather than assuming every `mouseout`
33    /// means "left the element".
34    pub(crate) fn attach(&self) {
35        #[cfg(target_arch = "wasm32")]
36        {
37            let Some(document) = browser_document() else {
38                return;
39            };
40            let hovered: Signal<bool> = self.get_hovered();
41            let over_target: NodeRef<Element> = self.get_target().clone();
42            let on_over: Closure<dyn FnMut(Event)> =
43                Closure::wrap(Box::new(move |event: Event| {
44                    if pointer_is_inside(&over_target, &event) {
45                        hovered.set(true);
46                    }
47                }));
48            if document
49                .add_event_listener_with_callback("mouseover", on_over.as_ref().unchecked_ref())
50                .is_err()
51            {
52                return;
53            }
54            let out_target: NodeRef<Element> = self.get_target().clone();
55            let on_out: Closure<dyn FnMut(Event)> = Closure::wrap(Box::new(move |event: Event| {
56                let Some(element) = out_target.get_cloned() else {
57                    return;
58                };
59                let related: Option<Node> = event
60                    .dyn_into::<MouseEvent>()
61                    .ok()
62                    .and_then(|mouse: MouseEvent| mouse.related_target())
63                    .and_then(|related: EventTarget| related.dyn_into::<Node>().ok());
64                if let Some(related) = related
65                    && node_contains(&element, &related)
66                {
67                    return;
68                }
69                hovered.set(false);
70            }));
71            if document
72                .add_event_listener_with_callback("mouseout", on_out.as_ref().unchecked_ref())
73                .is_err()
74            {
75                return;
76            }
77            *self.get_teardown().borrow_mut() = Some(Box::new(move || {
78                let _: Result<(), JsValue> = document.remove_event_listener_with_callback(
79                    "mouseover",
80                    on_over.as_ref().unchecked_ref(),
81                );
82                let _: Result<(), JsValue> = document.remove_event_listener_with_callback(
83                    "mouseout",
84                    on_out.as_ref().unchecked_ref(),
85                );
86            }));
87        }
88    }
89
90    /// Removes the document listeners.
91    pub(crate) fn detach(&self) {
92        let taken: Option<Box<dyn Fn()>> = self.get_teardown().borrow_mut().take();
93        if let Some(teardown) = taken {
94            teardown();
95        }
96    }
97}