1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::Element;
use yew::prelude::*;

use super::use_raf_state;
use crate::web_sys_ext::{ResizeObserver, ResizeObserverEntry};

#[derive(PartialEq, Default, Clone)]
pub struct UseMeasureState {
    pub x: f64,
    pub y: f64,
    pub width: f64,
    pub height: f64,
    pub top: f64,
    pub left: f64,
    pub bottom: f64,
    pub right: f64,
}

/// A sensor hook that tracks an HTML element's dimensions using the `ResizeObserver` API.
///
/// # Example
///
/// ```rust
/// # use yew::prelude::*;
/// #
/// use yew_hooks::prelude::*;
///
/// #[function_component(UseMeasure)]
/// fn measure() -> Html {
///     let node =  use_node_ref();
///     let state = use_measure(node.clone());
///     
///     html! {
///         <div ref={node}>
///             <b>{ " X: " }</b>
///             { state.x }
///             <b>{ " Y: " }</b>
///             { state.y }
///             <b>{ " Width: " }</b>
///             { state.width }
///             <b>{ " Height: " }</b>
///             { state.height }
///             <b>{ " Top: " }</b>
///             { state.top }
///             <b>{ " Left: " }</b>
///             { state.left }
///             <b>{ " Bottom: " }</b>
///             { state.bottom }
///             <b>{ " Right: " }</b>
///             { state.right }
///         </div>
///     }
/// }
/// ```
#[hook]
pub fn use_measure(node: NodeRef) -> UseMeasureState {
    let state = use_raf_state(UseMeasureState::default);

    {
        let state = state.clone();
        use_effect_with(node, move |node| {
            let closure = Closure::wrap(Box::new(move |entries: Vec<ResizeObserverEntry>| {
                for entry in &entries {
                    let rect = entry.content_rect();
                    state.set(UseMeasureState {
                        x: rect.x(),
                        y: rect.y(),
                        width: rect.width(),
                        height: rect.height(),
                        top: rect.top(),
                        left: rect.left(),
                        bottom: rect.bottom(),
                        right: rect.right(),
                    });
                }
            }) as Box<dyn Fn(Vec<ResizeObserverEntry>)>);

            let observer = ResizeObserver::new(closure.as_ref().unchecked_ref()).unwrap_throw();
            // Forget the closure to keep it alive
            closure.forget();

            if let Some(element) = &node.cast::<Element>() {
                observer.observe(element);
            }

            move || observer.disconnect()
        });
    }

    (*state).clone()
}