use crate::core::{ElementMaybeSignal, Size}; use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; use leptos::*; use wasm_bindgen::prelude::wasm_bindgen; cfg_if! { if #[cfg(not(feature = "ssr"))] { use crate::{use_resize_observer_with_options, UseResizeObserverOptions}; use crate::{watch_with_options, WatchOptions}; use wasm_bindgen::JsCast; }} /// Reactive size of an HTML element. /// /// Please refer to [ResizeObserver on MDN](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) /// for more details. /// /// ## Demo /// /// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_element_size) /// /// ## Usage /// /// ``` /// # use leptos::{html::Div, *}; /// # use leptos_use::{use_element_size, UseElementSizeReturn}; /// # /// # #[component] /// # fn Demo() -> impl IntoView { /// let el = create_node_ref::
(); /// /// let UseElementSizeReturn { width, height } = use_element_size(el); /// /// view! { ///
/// "Width: " {width} /// "Height: " {height} ///
/// } /// # } /// ``` /// /// ## Server-Side Rendering /// /// On the server the returned signals always contain the value of the `initial_size` option. /// /// ## See also /// /// - [`use_resize_observer`] pub fn use_element_size(target: El) -> UseElementSizeReturn where El: Into> + Clone, T: Into + Clone + 'static, { use_element_size_with_options(target, UseElementSizeOptions::default()) } /// Version of [`use_element_size`] that takes a `UseElementSizeOptions`. See [`use_element_size`] for how to use. #[cfg_attr(feature = "ssr", allow(unused_variables))] pub fn use_element_size_with_options( target: El, options: UseElementSizeOptions, ) -> UseElementSizeReturn where El: Into> + Clone, T: Into + Clone + 'static, { let UseElementSizeOptions { box_, initial_size } = options; let (width, set_width) = create_signal(initial_size.width); let (height, set_height) = create_signal(initial_size.height); #[cfg(not(feature = "ssr"))] { let box_ = box_.unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox); let target = target.into(); let is_svg = { let target = target.clone(); move || { if let Some(target) = target.get_untracked() { target .into() .namespace_uri() .map(|ns| ns.contains("svg")) .unwrap_or(false) } else { false } } }; { let target = target.clone(); let _ = use_resize_observer_with_options::, _, _>( target.clone(), move |entries, _| { let entry = &entries[0]; let box_size = match box_ { web_sys::ResizeObserverBoxOptions::ContentBox => entry.content_box_size(), web_sys::ResizeObserverBoxOptions::BorderBox => entry.border_box_size(), web_sys::ResizeObserverBoxOptions::DevicePixelContentBox => { entry.device_pixel_content_box_size() } _ => unreachable!(), }; if is_svg() { if let Some(target) = target.get() { if let Ok(Some(styles)) = window().get_computed_style(&target.into()) { set_height.set( styles .get_property_value("height") .map(|v| v.parse().unwrap_or_default()) .unwrap_or_default(), ); set_width.set( styles .get_property_value("width") .map(|v| v.parse().unwrap_or_default()) .unwrap_or_default(), ); } } } else if !box_size.is_null() && !box_size.is_undefined() && box_size.length() > 0 { let format_box_size = if box_size.is_array() { box_size.to_vec() } else { vec![box_size.into()] }; set_width.set(format_box_size.iter().fold(0.0, |acc, v| { acc + v.as_ref().clone().unchecked_into::().inline_size() })); set_height.set(format_box_size.iter().fold(0.0, |acc, v| { acc + v.as_ref().clone().unchecked_into::().block_size() })) } else { // fallback set_width.set(entry.content_rect().width()); set_height.set(entry.content_rect().height()) } }, UseResizeObserverOptions::default().box_(box_), ); } let _ = watch_with_options( move || target.get(), move |ele, _, _| { if ele.is_some() { set_width.set(initial_size.width); set_height.set(initial_size.height); } else { set_width.set(0.0); set_height.set(0.0); } }, WatchOptions::default().immediate(false), ); } UseElementSizeReturn { width: width.into(), height: height.into(), } } #[derive(DefaultBuilder, Default)] /// Options for [`use_element_size_with_options`]. pub struct UseElementSizeOptions { /// Initial size returned before any measurements on the `target` are done. Also the value reported /// at first when the `target` is a signal and changes. initial_size: Size, /// The box that is used to determine the dimensions of the target. Defaults to `ContentBox`. #[builder(into)] pub box_: Option, } /// The return value of [`use_element_size`]. pub struct UseElementSizeReturn { /// The width of the element. pub width: Signal, /// The height of the element. pub height: Signal, } #[wasm_bindgen] extern "C" { type BoxSize; #[wasm_bindgen(method, getter = blockSize)] fn block_size(this: &BoxSize) -> f64; #[wasm_bindgen(method, getter = inlineSize)] fn inline_size(this: &BoxSize) -> f64; }