leptos_use/
use_element_size.rs1use 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
14pub fn use_element_size<El, M>(target: El) -> UseElementSizeReturn
54where
55 El: IntoElementMaybeSignal<web_sys::Element, M>,
56{
57 use_element_size_with_options(target, UseElementSizeOptions::default())
58}
59
60#[cfg_attr(feature = "ssr", allow(unused_variables))]
62pub fn use_element_size_with_options<El, M>(
63 target: El,
64 options: UseElementSizeOptions,
65) -> UseElementSizeReturn
66where
67 El: IntoElementMaybeSignal<web_sys::Element, M>,
68{
69 let UseElementSizeOptions { box_, initial_size } = options;
70
71 let (width, set_width) = signal(initial_size.width);
72 let (height, set_height) = signal(initial_size.height);
73
74 #[cfg(not(feature = "ssr"))]
75 {
76 let box_ = box_.unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox);
77
78 let target = target.into_element_maybe_signal();
79
80 let is_svg = move || {
81 if let Some(target) = target.get_untracked() {
82 target
83 .namespace_uri()
84 .map(|ns| ns.contains("svg"))
85 .unwrap_or(false)
86 } else {
87 false
88 }
89 };
90
91 let _ = use_resize_observer_with_options(
92 target,
93 move |entries, _| {
94 let entry = &entries[0];
95
96 let box_size = match box_ {
97 web_sys::ResizeObserverBoxOptions::ContentBox => entry.content_box_size(),
98 web_sys::ResizeObserverBoxOptions::BorderBox => entry.border_box_size(),
99 web_sys::ResizeObserverBoxOptions::DevicePixelContentBox => {
100 entry.device_pixel_content_box_size()
101 }
102 _ => unreachable!(),
103 };
104
105 if is_svg() {
106 if let Some(target) = target.get()
107 && let Ok(Some(styles)) = window().get_computed_style(&target)
108 {
109 set_height.set(
110 styles
111 .get_property_value("height")
112 .map(|v| v.parse().unwrap_or_default())
113 .unwrap_or_default(),
114 );
115 set_width.set(
116 styles
117 .get_property_value("width")
118 .map(|v| v.parse().unwrap_or_default())
119 .unwrap_or_default(),
120 );
121 }
122 } else if !box_size.is_null() && !box_size.is_undefined() && box_size.length() > 0 {
123 let format_box_size = if box_size.is_array() {
124 box_size.to_vec()
125 } else {
126 vec![box_size.into()]
127 };
128
129 set_width.set(format_box_size.iter().fold(0.0, |acc, v| {
130 acc + v
131 .as_ref()
132 .clone()
133 .unchecked_into::<web_sys::ResizeObserverSize>()
134 .inline_size()
135 }));
136 set_height.set(format_box_size.iter().fold(0.0, |acc, v| {
137 acc + v
138 .as_ref()
139 .clone()
140 .unchecked_into::<web_sys::ResizeObserverSize>()
141 .block_size()
142 }))
143 } else {
144 set_width.set(entry.content_rect().width());
146 set_height.set(entry.content_rect().height())
147 }
148 },
149 UseResizeObserverOptions::default().box_(box_),
150 );
151
152 let _ = watch_with_options(
153 move || target.get(),
154 move |ele, _, _| {
155 if ele.is_some() {
156 set_width.set(initial_size.width);
157 set_height.set(initial_size.height);
158 } else {
159 set_width.set(0.0);
160 set_height.set(0.0);
161 }
162 },
163 WatchOptions::default().immediate(false),
164 );
165 }
166
167 UseElementSizeReturn {
168 width: width.into(),
169 height: height.into(),
170 }
171}
172
173#[derive(DefaultBuilder, Default)]
174pub struct UseElementSizeOptions {
176 initial_size: Size,
179
180 #[builder(into)]
182 pub box_: Option<web_sys::ResizeObserverBoxOptions>,
183}
184
185pub struct UseElementSizeReturn {
187 pub width: Signal<f64>,
189 pub height: Signal<f64>,
191}