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