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
93
94
95
96
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::use_measure;
///
/// #[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>
///     }
/// }
/// ```
pub fn use_measure(node: NodeRef) -> UseMeasureState {
    let state = use_raf_state(UseMeasureState::default);

    {
        let state = state.clone();
        use_effect_with_deps(
            move |node| {
                let closure = Closure::wrap(Box::new(move |entries: Vec<ResizeObserverEntry>| {
                    for entry in entries.iter() {
                        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()
            },
            node,
        );
    }

    (*state).clone()
}