dioxus_resize_observer/
lib.rs

1use dioxus::prelude::*;
2use dioxus_use_mounted::UseMounted;
3use dioxus_web::WebEventExt;
4use js_sys::Array;
5use std::rc::Rc;
6use wasm_bindgen::closure::Closure;
7use wasm_bindgen::JsCast;
8use web_sys::DomRectReadOnly;
9use web_sys::ResizeObserver;
10use web_sys::ResizeObserverEntry;
11
12pub type Rect = DomRectReadOnly;
13
14/// Hook to get an element's size, updating on changes.
15///
16/// ## Examples
17/// ```
18/// use dioxus::prelude::*;
19/// use dioxus_resize_observer::use_size;
20/// use dioxus_use_mounted::use_mounted;
21///
22/// fn app() -> Element {
23///     let mounted = use_mounted();
24///     let size = use_size(mounted);
25///
26///     rsx!(div {
27///         onmounted: move |event| mounted.onmounted(event),
28///         "Size: {size.width()} x {size.height()}"
29///     })
30/// }
31/// ```
32pub fn use_size(mounted: UseMounted) -> Rect {
33    let resize = use_resize(mounted);
34    let resize_ref = resize.read();
35    resize_ref
36        .clone()
37        .unwrap_or_else(|| DomRectReadOnly::new().unwrap())
38}
39
40/// Hook to get an element's resize events as a signal.
41pub fn use_resize(mounted: UseMounted) -> Signal<Option<Rect>> {
42    let mut state_ref: Signal<Option<State>> = use_signal(|| None);
43    let mut size_ref = use_signal(|| None);
44
45    use_effect(move || {
46        if let Some(mounted) = mounted.signal.read().clone() {
47            // Unmount the previous element, if it exists
48            maybe_unobserve(state_ref);
49
50            // Create a new resize observer with an entry event handler.
51            let on_resize = Closure::<dyn FnMut(Array)>::new(move |entries: Array| {
52                let entry = entries.at(0);
53                let entry: ResizeObserverEntry = entry.dyn_into().unwrap();
54                size_ref.set(Some(entry.content_rect()));
55            });
56            let resize_observer = ResizeObserver::new(on_resize.as_ref().unchecked_ref()).unwrap();
57
58            // Observe the raw element with the resize observer.
59            let raw_elem = get_raw_element(&mounted);
60            resize_observer.observe(&raw_elem);
61
62            // Update the current state.
63            state_ref.set(Some(State {
64                resize_observer,
65                mounted,
66                _on_resize: on_resize,
67            }));
68        } else {
69            // Unmount the current element, if it exists
70            maybe_unobserve(state_ref);
71        }
72    });
73
74    size_ref
75}
76
77/// State of the hook.
78struct State {
79    /// JS resize observer.
80    resize_observer: ResizeObserver,
81
82    /// Currently mounted element data.
83    mounted: Rc<MountedData>,
84
85    /// Current closure handling resize observer events.
86    _on_resize: Closure<dyn FnMut(Array)>,
87}
88
89/// Utility to get the raw element from its mounted data.
90fn get_raw_element(mounted: &MountedData) -> web_sys::Element {
91    mounted.try_as_web_event().unwrap()
92}
93
94/// Attempt to unobserve an element, if it exists.
95fn maybe_unobserve(mut state_ref: Signal<Option<State>>) {
96    if let Some(state) = state_ref.write().take() {
97        let raw_elem = get_raw_element(&state.mounted);
98        state.resize_observer.unobserve(&raw_elem);
99    }
100}