leptos_use/
use_resize_observer.rs1use crate::core::IntoElementsMaybeSignal;
2use cfg_if::cfg_if;
3use default_struct_builder::DefaultBuilder;
4use leptos::reactive::wrappers::read::Signal;
5
6cfg_if! { if #[cfg(not(feature = "ssr"))] {
7 use crate::{sendwrap_fn, use_supported};
8 use std::cell::RefCell;
9 use std::rc::Rc;
10 use wasm_bindgen::prelude::*;
11 use leptos::prelude::*;
12}}
13
14pub fn use_resize_observer<Els, M, F>(
63 target: Els,
64 callback: F,
65) -> UseResizeObserverReturn<impl Fn() + Clone + Send + Sync>
66where
67 Els: IntoElementsMaybeSignal<web_sys::Element, M>,
68 F: FnMut(Vec<web_sys::ResizeObserverEntry>, web_sys::ResizeObserver) + 'static,
69{
70 use_resize_observer_with_options(target, callback, UseResizeObserverOptions::default())
71}
72
73#[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))]
75pub fn use_resize_observer_with_options<Els, M, F>(
76 target: Els,
77 mut callback: F,
78 options: UseResizeObserverOptions,
79) -> UseResizeObserverReturn<impl Fn() + Clone + Send + Sync>
80where
81 Els: IntoElementsMaybeSignal<web_sys::Element, M>,
82 F: FnMut(Vec<web_sys::ResizeObserverEntry>, web_sys::ResizeObserver) + 'static,
83{
84 #[cfg(feature = "ssr")]
85 {
86 UseResizeObserverReturn {
87 is_supported: Signal::derive(|| true),
88 stop: || {},
89 }
90 }
91
92 #[cfg(not(feature = "ssr"))]
93 {
94 use crate::js;
95
96 let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::ResizeObserver)>::new(
97 move |entries: js_sys::Array, observer| {
98 #[cfg(debug_assertions)]
99 let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
100
101 callback(
102 entries
103 .to_vec()
104 .into_iter()
105 .map(|v| v.unchecked_into::<web_sys::ResizeObserverEntry>())
106 .collect(),
107 observer,
108 );
109 },
110 )
111 .into_js_value();
112
113 let observer: Rc<RefCell<Option<web_sys::ResizeObserver>>> = Rc::new(RefCell::new(None));
114
115 let is_supported = use_supported(|| js!("ResizeObserver" in &window()));
116
117 let cleanup = {
118 let observer = Rc::clone(&observer);
119
120 move || {
121 let mut observer = observer.borrow_mut();
122 if let Some(o) = observer.as_ref() {
123 o.disconnect();
124 *observer = None;
125 }
126 }
127 };
128
129 let targets = target.into_elements_maybe_signal();
130
131 let stop_watch = {
132 let cleanup = cleanup.clone();
133
134 let stop = Effect::watch(
135 move || targets.get(),
136 move |targets, _, _| {
137 cleanup();
138
139 if is_supported.get_untracked() && !targets.is_empty() {
140 let obs = web_sys::ResizeObserver::new(
141 closure_js.clone().as_ref().unchecked_ref(),
142 )
143 .expect("failed to create ResizeObserver");
144
145 for target in targets.iter().flatten() {
146 let target = target.clone();
147 obs.observe_with_options(&target, &options.clone().into());
148 }
149 observer.replace(Some(obs));
150 }
151 },
152 true,
153 );
154
155 move || stop.stop()
156 };
157
158 let stop = sendwrap_fn!(move || {
159 cleanup();
160 stop_watch();
161 });
162
163 on_cleanup({
164 let stop = stop.clone();
165 #[allow(clippy::redundant_closure)]
166 move || stop()
167 });
168
169 UseResizeObserverReturn { is_supported, stop }
170 }
171}
172
173#[derive(DefaultBuilder, Clone, Default)]
175pub struct UseResizeObserverOptions {
176 #[builder(into)]
178 pub box_: Option<web_sys::ResizeObserverBoxOptions>,
179}
180
181impl From<UseResizeObserverOptions> for web_sys::ResizeObserverOptions {
182 fn from(val: UseResizeObserverOptions) -> Self {
183 let options = web_sys::ResizeObserverOptions::new();
184 options.set_box(
185 val.box_
186 .unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox),
187 );
188 options
189 }
190}
191
192pub struct UseResizeObserverReturn<F: Fn() + Clone + Send + Sync> {
194 pub is_supported: Signal<bool>,
196 pub stop: F,
198}