leptos-use/src/use_resize_observer.rs

168 lines
5.2 KiB
Rust
Raw Normal View History

2023-05-29 01:52:03 +01:00
use crate::core::ElementMaybeSignal;
2023-05-31 05:56:32 +01:00
use crate::{use_supported, watch};
2023-06-05 00:02:13 +01:00
use default_struct_builder::DefaultBuilder;
2023-05-29 01:52:03 +01:00
use leptos::*;
use std::cell::RefCell;
use std::rc::Rc;
2023-05-31 05:56:32 +01:00
use wasm_bindgen::prelude::*;
2023-05-29 01:52:03 +01:00
2023-05-31 05:56:32 +01:00
/// Reports changes to the dimensions of an Element's content or the border-box.
///
/// > 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_resize_observer)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos_use::use_resize_observer;
/// #
/// # #[component]
/// # fn Demo(cx: Scope) -> impl IntoView {
/// let el = create_node_ref(cx);
/// let (text, set_text) = create_signal(cx, "".to_string());
2023-05-31 05:56:32 +01:00
///
/// use_resize_observer(
/// cx,
/// el,
/// move |entries, observer| {
/// let rect = entries[0].content_rect();
/// set_text(format!("width: {}\nheight: {}", rect.width(), rect.height()));
/// },
/// );
///
/// view! { cx,
/// <div node_ref=el>{ text }</div>
/// }
2023-05-31 05:56:32 +01:00
/// # }
/// ```
2023-06-05 00:02:13 +01:00
/// ## See also
///
/// - [`use_element_size`]
2023-05-31 05:56:32 +01:00
pub fn use_resize_observer<El, T, F>(
cx: Scope,
target: El, // TODO : multiple elements?
callback: F,
2023-06-02 13:38:01 +01:00
) -> UseResizeObserverReturn
2023-05-31 05:56:32 +01:00
where
(Scope, El): Into<ElementMaybeSignal<T, web_sys::Element>>,
T: Into<web_sys::Element> + Clone + 'static,
F: FnMut(Vec<web_sys::ResizeObserverEntry>, web_sys::ResizeObserver) + Clone + 'static,
{
2023-06-05 00:02:13 +01:00
use_resize_observer_with_options(cx, target, callback, UseResizeObserverOptions::default())
2023-05-31 05:56:32 +01:00
}
2023-06-05 00:02:13 +01:00
/// Version of [`use_resize_observer`] that takes a `web_sys::ResizeObserverOptions`. See [`use_resize_observer`] for how to use.
2023-05-29 01:52:03 +01:00
pub fn use_resize_observer_with_options<El, T, F>(
cx: Scope,
target: El, // TODO : multiple elements?
callback: F,
2023-06-05 00:02:13 +01:00
options: UseResizeObserverOptions,
2023-06-02 13:38:01 +01:00
) -> UseResizeObserverReturn
2023-05-31 05:56:32 +01:00
where
2023-05-29 01:52:03 +01:00
(Scope, El): Into<ElementMaybeSignal<T, web_sys::Element>>,
T: Into<web_sys::Element> + Clone + 'static,
2023-05-31 05:56:32 +01:00
F: FnMut(Vec<web_sys::ResizeObserverEntry>, web_sys::ResizeObserver) + Clone + 'static,
2023-05-29 01:52:03 +01:00
{
let observer: Rc<RefCell<Option<web_sys::ResizeObserver>>> = Rc::new(RefCell::new(None));
let is_supported = use_supported(cx, || JsValue::from("ResizeObserver").js_in(&window()));
let obs = Rc::clone(&observer);
let cleanup = move || {
2023-05-31 05:56:32 +01:00
let mut observer = obs.borrow_mut();
if let Some(o) = observer.as_ref() {
2023-05-29 01:52:03 +01:00
o.disconnect();
*observer = None;
}
};
2023-05-31 05:56:32 +01:00
let target = (cx, target).into();
2023-05-29 01:52:03 +01:00
let clean = cleanup.clone();
2023-05-31 05:56:32 +01:00
let stop_watch = watch(
cx,
move || target.get(),
move |target, _, _| {
clean();
if is_supported() {
if let Some(target) = target {
let mut callback = callback.clone();
let closure = Closure::<dyn FnMut(js_sys::Array, web_sys::ResizeObserver)>::new(
move |entries: js_sys::Array, observer| {
callback(
entries
.to_vec()
.into_iter()
.map(|v| v.unchecked_into::<web_sys::ResizeObserverEntry>())
.collect(),
observer,
);
},
);
let obs = web_sys::ResizeObserver::new(closure.as_ref().unchecked_ref())
.expect("failed to create ResizeObserver");
closure.forget();
let target: web_sys::Element = target.clone().into();
2023-06-05 00:02:13 +01:00
obs.observe_with_options(&target, &options.clone().into());
2023-05-31 05:56:32 +01:00
observer.replace(Some(obs));
}
}
},
);
2023-05-29 01:52:03 +01:00
let stop = move || {
cleanup();
2023-05-31 05:56:32 +01:00
stop_watch();
2023-05-29 01:52:03 +01:00
};
2023-05-31 05:56:32 +01:00
on_cleanup(cx, stop.clone());
2023-06-02 13:38:01 +01:00
UseResizeObserverReturn {
2023-05-31 05:56:32 +01:00
is_supported,
stop: Box::new(stop),
2023-06-02 13:38:01 +01:00
}
2023-05-31 05:56:32 +01:00
}
2023-06-05 00:02:13 +01:00
#[derive(DefaultBuilder, Clone)]
/// Options for [`use_resize_observer_with_options`].
pub struct UseResizeObserverOptions {
/// The box that is used to determine the dimensions of the target. Defaults to `ContentBox`.
pub box_: web_sys::ResizeObserverBoxOptions,
}
impl Default for UseResizeObserverOptions {
fn default() -> Self {
Self {
box_: web_sys::ResizeObserverBoxOptions::ContentBox,
}
}
}
2023-06-10 19:43:51 +01:00
impl From<UseResizeObserverOptions> for web_sys::ResizeObserverOptions {
fn from(val: UseResizeObserverOptions) -> Self {
2023-06-05 00:02:13 +01:00
let mut options = web_sys::ResizeObserverOptions::new();
2023-06-10 19:43:51 +01:00
options.box_(val.box_);
2023-06-05 00:02:13 +01:00
options
}
}
2023-05-31 05:56:32 +01:00
/// The return value of [`use_resize_observer`].
2023-06-02 13:38:01 +01:00
pub struct UseResizeObserverReturn {
2023-05-31 05:56:32 +01:00
/// Whether the browser supports the ResizeObserver API
pub is_supported: Signal<bool>,
/// A function to stop and detach the ResizeObserver
pub stop: Box<dyn Fn()>,
2023-05-29 01:52:03 +01:00
}