Skip to main content

euv_ui/hook/use_click_outside/
impl.rs

1use super::*;
2
3/// Inherent implementation of [`UseClickOutside`].
4impl UseClickOutside {
5    /// Creates a handle with the trigger starting at `false`.
6    ///
7    /// # Returns
8    ///
9    /// - `UseClickOutside` - The fresh handle.
10    pub(crate) fn new() -> Self {
11        Self {
12            target: NodeRef::new(),
13            triggered: Signal::create(false),
14            teardown: Rc::new(RefCell::new(None)),
15        }
16    }
17
18    /// Returns the current trigger state as a snapshot.
19    ///
20    /// # Returns
21    ///
22    /// - `bool` - `true` when the last press landed outside.
23    pub fn is_triggered(&self) -> bool {
24        self.get_triggered().get()
25    }
26
27    /// Clears the trigger, so the next outside press is a new event.
28    pub fn reset(&self) {
29        self.get_triggered().set(false);
30    }
31
32    /// Subscribes to the document's `mousedown` and `touchstart`
33    /// events.
34    ///
35    /// A press inside the watched element is ignored. A press outside
36    /// raises the trigger.
37    pub(crate) fn attach(&self) {
38        #[cfg(target_arch = "wasm32")]
39        {
40            let Some(document) = browser_document() else {
41                return;
42            };
43            let triggered: Signal<bool> = self.get_triggered();
44            let press_target: NodeRef<Element> = self.get_target().clone();
45            let on_press: Closure<dyn FnMut(Event)> =
46                Closure::wrap(Box::new(move |event: Event| {
47                    if press_is_outside(&press_target, &event) {
48                        triggered.set(true);
49                    }
50                }));
51            if document
52                .add_event_listener_with_callback(
53                    EVENT_MOUSEDOWN,
54                    on_press.as_ref().unchecked_ref(),
55                )
56                .is_err()
57            {
58                return;
59            }
60            let touch_target: NodeRef<Element> = self.get_target().clone();
61            let on_touch: Closure<dyn FnMut(Event)> =
62                Closure::wrap(Box::new(move |event: Event| {
63                    if press_is_outside(&touch_target, &event) {
64                        triggered.set(true);
65                    }
66                }));
67            if document
68                .add_event_listener_with_callback(
69                    EVENT_TOUCHSTART,
70                    on_touch.as_ref().unchecked_ref(),
71                )
72                .is_err()
73            {
74                return;
75            }
76            *self.get_teardown().borrow_mut() = Some(Box::new(move || {
77                let _: Result<(), JsValue> = document.remove_event_listener_with_callback(
78                    EVENT_MOUSEDOWN,
79                    on_press.as_ref().unchecked_ref(),
80                );
81                let _: Result<(), JsValue> = document.remove_event_listener_with_callback(
82                    EVENT_TOUCHSTART,
83                    on_touch.as_ref().unchecked_ref(),
84                );
85            }));
86        }
87    }
88
89    /// Removes the document listeners.
90    pub(crate) fn detach(&self) {
91        let taken: Option<Box<dyn Fn()>> = self.get_teardown().borrow_mut().take();
92        if let Some(teardown) = taken {
93            teardown();
94        }
95    }
96}