leptos_use/
use_element_size.rs

1use crate::core::IntoElementMaybeSignal;
2use crate::core::Size;
3use cfg_if::cfg_if;
4use default_struct_builder::DefaultBuilder;
5use leptos::prelude::*;
6use leptos::reactive::wrappers::read::Signal;
7
8cfg_if! { if #[cfg(not(feature = "ssr"))] {
9    use crate::{use_resize_observer_with_options, UseResizeObserverOptions};
10    use crate::{watch_with_options, WatchOptions};
11    use wasm_bindgen::JsCast;
12}}
13
14/// Reactive size of an HTML element.
15///
16/// Please refer to [ResizeObserver on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
17/// for more details.
18///
19/// ## Demo
20///
21/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_element_size)
22///
23/// ## Usage
24///
25/// ```
26/// # use leptos::{html::Div, prelude::*};
27/// # use leptos_use::{use_element_size, UseElementSizeReturn};
28/// #
29/// # #[component]
30/// # fn Demo() -> impl IntoView {
31/// let el = NodeRef::<Div>::new();
32///
33/// let UseElementSizeReturn { width, height } = use_element_size(el);
34///
35/// view! {
36///     <div node_ref=el>
37///         "Width: " {width}
38///         "Height: " {height}
39///     </div>
40/// }
41/// # }
42/// ```
43///
44/// ## Server-Side Rendering
45///
46/// On the server the returned signals always contain the value of the `initial_size` option.
47///
48/// ## See also
49///
50/// - [`fn@crate::use_resize_observer`]
51pub fn use_element_size<El, M>(target: El) -> UseElementSizeReturn
52where
53    El: IntoElementMaybeSignal<web_sys::Element, M>,
54{
55    use_element_size_with_options(target, UseElementSizeOptions::default())
56}
57
58/// Version of [`use_element_size`] that takes a `UseElementSizeOptions`. See [`use_element_size`] for how to use.
59#[cfg_attr(feature = "ssr", allow(unused_variables))]
60pub fn use_element_size_with_options<El, M>(
61    target: El,
62    options: UseElementSizeOptions,
63) -> UseElementSizeReturn
64where
65    El: IntoElementMaybeSignal<web_sys::Element, M>,
66{
67    let UseElementSizeOptions { box_, initial_size } = options;
68
69    let (width, set_width) = signal(initial_size.width);
70    let (height, set_height) = signal(initial_size.height);
71
72    #[cfg(not(feature = "ssr"))]
73    {
74        let box_ = box_.unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox);
75
76        let target = target.into_element_maybe_signal();
77
78        let is_svg = move || {
79            if let Some(target) = target.get_untracked() {
80                target
81                    .namespace_uri()
82                    .map(|ns| ns.contains("svg"))
83                    .unwrap_or(false)
84            } else {
85                false
86            }
87        };
88
89        let _ = use_resize_observer_with_options(
90            target,
91            move |entries, _| {
92                let entry = &entries[0];
93
94                let box_size = match box_ {
95                    web_sys::ResizeObserverBoxOptions::ContentBox => entry.content_box_size(),
96                    web_sys::ResizeObserverBoxOptions::BorderBox => entry.border_box_size(),
97                    web_sys::ResizeObserverBoxOptions::DevicePixelContentBox => {
98                        entry.device_pixel_content_box_size()
99                    }
100                    _ => unreachable!(),
101                };
102
103                if is_svg() {
104                    if let Some(target) = target.get() {
105                        if let Ok(Some(styles)) = window().get_computed_style(&target) {
106                            set_height.set(
107                                styles
108                                    .get_property_value("height")
109                                    .map(|v| v.parse().unwrap_or_default())
110                                    .unwrap_or_default(),
111                            );
112                            set_width.set(
113                                styles
114                                    .get_property_value("width")
115                                    .map(|v| v.parse().unwrap_or_default())
116                                    .unwrap_or_default(),
117                            );
118                        }
119                    }
120                } else if !box_size.is_null() && !box_size.is_undefined() && box_size.length() > 0 {
121                    let format_box_size = if box_size.is_array() {
122                        box_size.to_vec()
123                    } else {
124                        vec![box_size.into()]
125                    };
126
127                    set_width.set(format_box_size.iter().fold(0.0, |acc, v| {
128                        acc + v
129                            .as_ref()
130                            .clone()
131                            .unchecked_into::<web_sys::ResizeObserverSize>()
132                            .inline_size()
133                    }));
134                    set_height.set(format_box_size.iter().fold(0.0, |acc, v| {
135                        acc + v
136                            .as_ref()
137                            .clone()
138                            .unchecked_into::<web_sys::ResizeObserverSize>()
139                            .block_size()
140                    }))
141                } else {
142                    // fallback
143                    set_width.set(entry.content_rect().width());
144                    set_height.set(entry.content_rect().height())
145                }
146            },
147            UseResizeObserverOptions::default().box_(box_),
148        );
149
150        let _ = watch_with_options(
151            move || target.get(),
152            move |ele, _, _| {
153                if ele.is_some() {
154                    set_width.set(initial_size.width);
155                    set_height.set(initial_size.height);
156                } else {
157                    set_width.set(0.0);
158                    set_height.set(0.0);
159                }
160            },
161            WatchOptions::default().immediate(false),
162        );
163    }
164
165    UseElementSizeReturn {
166        width: width.into(),
167        height: height.into(),
168    }
169}
170
171#[derive(DefaultBuilder, Default)]
172/// Options for [`use_element_size_with_options`].
173pub struct UseElementSizeOptions {
174    /// Initial size returned before any measurements on the `target` are done. Also the value reported
175    /// at first when the `target` is a signal and changes.
176    initial_size: Size,
177
178    /// The box that is used to determine the dimensions of the target. Defaults to `ContentBox`.
179    #[builder(into)]
180    pub box_: Option<web_sys::ResizeObserverBoxOptions>,
181}
182
183/// The return value of [`use_element_size`].
184pub struct UseElementSizeReturn {
185    /// The width of the element.
186    pub width: Signal<f64>,
187    /// The height of the element.
188    pub height: Signal<f64>,
189}