leptos-use/src/use_element_size.rs

217 lines
7.2 KiB
Rust
Raw Normal View History

2023-06-05 00:02:13 +01:00
use crate::core::{ElementMaybeSignal, Size};
use cfg_if::cfg_if;
2023-06-05 00:02:13 +01:00
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;
}}
2023-06-05 00:02:13 +01:00
/// Reactive size of an HTML element.
///
/// > This function requires `--cfg=web_sys_unstable_apis` to be activated as
/// [described in the wasm-bindgen guide](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html).
///
/// 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::*;
/// # use leptos_use::{use_element_size, UseElementSizeReturn};
/// #
/// # #[component]
2023-07-27 18:06:36 +01:00
/// # fn Demo() -> impl IntoView {
/// let el = create_node_ref();
2023-06-05 00:02:13 +01:00
///
2023-07-27 18:06:36 +01:00
/// let UseElementSizeReturn { width, height } = use_element_size(el);
2023-06-05 00:02:13 +01:00
///
2023-07-27 18:06:36 +01:00
/// view! {
2023-06-05 00:02:13 +01:00
/// <div node_ref=el>
/// "Width: " {width}
/// "Height: " {height}
/// </div>
/// }
/// # }
/// ```
///
2023-07-14 22:43:19 +01:00
/// ## Server-Side Rendering
///
/// On the server the returned signals always contain the value of the `initial_size` option.
2023-07-14 22:43:19 +01:00
///
2023-06-05 00:02:13 +01:00
/// ## See also
///
/// - [`use_resize_observer`]
2023-07-27 18:06:36 +01:00
pub fn use_element_size<El, T>(target: El) -> UseElementSizeReturn
2023-06-05 00:02:13 +01:00
where
El: Clone,
2023-07-27 18:06:36 +01:00
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
2023-06-05 00:02:13 +01:00
T: Into<web_sys::Element> + Clone + 'static,
{
2023-07-27 18:06:36 +01:00
use_element_size_with_options(target, UseElementSizeOptions::default())
2023-06-05 00:02:13 +01:00
}
/// Version of [`use_element_size`] that takes a `UseElementSizeOptions`. See [`use_element_size`] for how to use.
#[cfg_attr(feature = "ssr", allow(unused_variables))]
2023-06-05 00:02:13 +01:00
pub fn use_element_size_with_options<El, T>(
target: El,
options: UseElementSizeOptions,
) -> UseElementSizeReturn
where
El: Clone,
2023-07-27 18:06:36 +01:00
El: Into<ElementMaybeSignal<T, web_sys::Element>>,
2023-06-05 00:02:13 +01:00
T: Into<web_sys::Element> + 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_if! { if #[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
}
2023-06-13 17:48:32 +01:00
}
};
2023-06-05 00:02:13 +01:00
{
let target = target.clone();
2023-06-05 00:02:13 +01:00
let _ = use_resize_observer_with_options::<ElementMaybeSignal<T, web_sys::Element>, _, _>(
target.clone(),
move |entries, _| {
let entry = &entries[0];
2023-06-05 00:02:13 +01:00
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!(),
};
2023-06-05 00:02:13 +01:00
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(),
);
}
2023-06-13 17:48:32 +01:00
}
} 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::<BoxSize>().inline_size()
}));
set_height.set(format_box_size.iter().fold(0.0, |acc, v| {
acc + v.as_ref().clone().unchecked_into::<BoxSize>().block_size()
}))
2023-06-13 17:48:32 +01:00
} else {
// fallback
set_width.set(entry.content_rect().width());
set_height.set(entry.content_rect().height())
}
},
UseResizeObserverOptions::default().box_(box_),
);
}
2023-06-13 17:48:32 +01:00
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);
2023-06-13 17:48:32 +01:00
} else {
set_width.set(0.0);
set_height.set(0.0);
2023-06-13 17:48:32 +01:00
}
},
WatchOptions::default().immediate(false),
2023-06-13 17:48:32 +01:00
);
}}
2023-06-05 00:02:13 +01:00
UseElementSizeReturn {
width: width.into(),
height: height.into(),
}
}
#[derive(DefaultBuilder)]
/// 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<web_sys::ResizeObserverBoxOptions>,
2023-06-05 00:02:13 +01:00
}
impl Default for UseElementSizeOptions {
fn default() -> Self {
Self {
initial_size: Size::default(),
box_: None,
2023-06-05 00:02:13 +01:00
}
}
}
/// The return value of [`use_element_size`].
pub struct UseElementSizeReturn {
/// The width of the element.
pub width: Signal<f64>,
/// The height of the element.
pub height: Signal<f64>,
}
#[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;
}