leptos-use/src/use_mutation_observer.rs

147 lines
4.4 KiB
Rust
Raw Normal View History

2023-06-13 00:31:38 +01:00
use crate::core::ElementsMaybeSignal;
2023-07-24 21:16:59 +02:00
use crate::use_supported;
2023-06-13 00:31:38 +01:00
use leptos::*;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::MutationObserverInit;
/// Reactive [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
///
/// Watch for changes being made to the DOM tree.
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_mutation_observer)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
2023-07-03 15:16:22 +01:00
/// # use leptos::html::Pre;
2023-06-13 00:31:38 +01:00
/// # use leptos_use::use_mutation_observer_with_options;
/// #
/// # #[component]
2023-07-27 18:06:36 +01:00
/// # fn Demo() -> impl IntoView {
/// let el = create_node_ref::<Pre>();
/// let (text, set_text) = create_signal("".to_string());
2023-06-13 00:31:38 +01:00
///
/// let mut init = web_sys::MutationObserverInit::new();
/// init.attributes(true);
///
/// use_mutation_observer_with_options(
/// el,
/// move |mutations, _| {
/// if let Some(mutation) = mutations.first() {
/// set_text.update(|text| *text = format!("{text}\n{:?}", mutation.attribute_name()));
/// }
/// },
/// init,
/// );
///
2023-07-27 18:06:36 +01:00
/// view! {
2023-06-13 00:31:38 +01:00
/// <pre node_ref=el>{ text }</pre>
/// }
/// # }
/// ```
2023-07-14 22:43:19 +01:00
///
/// ## Server-Side Rendering
///
/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
2023-06-13 00:31:38 +01:00
pub fn use_mutation_observer<El, T, F>(
target: El,
callback: F,
) -> UseMutationObserverReturn<impl Fn() + Clone>
where
2023-07-27 18:06:36 +01:00
El: Into<ElementsMaybeSignal<T, web_sys::Element>>,
2023-06-13 00:31:38 +01:00
T: Into<web_sys::Element> + Clone + 'static,
F: FnMut(Vec<web_sys::MutationRecord>, web_sys::MutationObserver) + 'static,
{
2023-07-27 18:06:36 +01:00
use_mutation_observer_with_options(target, callback, MutationObserverInit::default())
2023-06-13 00:31:38 +01:00
}
/// Version of [`use_mutation_observer`] that takes a `web_sys::MutationObserverInit`. See [`use_mutation_observer`] for how to use.
pub fn use_mutation_observer_with_options<El, T, F>(
target: El,
mut callback: F,
options: web_sys::MutationObserverInit,
) -> UseMutationObserverReturn<impl Fn() + Clone>
where
2023-07-27 18:06:36 +01:00
El: Into<ElementsMaybeSignal<T, web_sys::Element>>,
2023-06-13 00:31:38 +01:00
T: Into<web_sys::Element> + Clone + 'static,
F: FnMut(Vec<web_sys::MutationRecord>, web_sys::MutationObserver) + 'static,
{
let closure_js = Closure::<dyn FnMut(js_sys::Array, web_sys::MutationObserver)>::new(
move |entries: js_sys::Array, observer| {
callback(
entries
.to_vec()
.into_iter()
.map(|v| v.unchecked_into::<web_sys::MutationRecord>())
.collect(),
observer,
);
},
)
.into_js_value();
let observer: Rc<RefCell<Option<web_sys::MutationObserver>>> = Rc::new(RefCell::new(None));
2023-07-27 18:06:36 +01:00
let is_supported = use_supported(|| JsValue::from("MutationObserver").js_in(&window()));
2023-06-13 00:31:38 +01:00
2023-06-13 17:48:32 +01:00
let cleanup = {
let observer = Rc::clone(&observer);
move || {
let mut observer = observer.borrow_mut();
if let Some(o) = observer.as_ref() {
o.disconnect();
*observer = None;
}
2023-06-13 00:31:38 +01:00
}
};
2023-07-27 18:06:36 +01:00
let targets = (target).into();
2023-06-13 00:31:38 +01:00
2023-06-13 17:48:32 +01:00
let stop_watch = {
let cleanup = cleanup.clone();
2023-06-13 00:31:38 +01:00
2023-07-24 21:16:59 +02:00
leptos::watch(
2023-06-13 17:48:32 +01:00
move || targets.get(),
move |targets, _, _| {
cleanup();
2023-06-13 00:31:38 +01:00
if is_supported.get() && !targets.is_empty() {
2023-06-13 17:48:32 +01:00
let obs = web_sys::MutationObserver::new(closure_js.as_ref().unchecked_ref())
.expect("failed to create MutationObserver");
2023-06-13 00:31:38 +01:00
2023-06-13 17:48:32 +01:00
for target in targets.iter().flatten() {
let target: web_sys::Element = target.clone().into();
let _ = obs.observe_with_options(&target, &options.clone());
}
observer.replace(Some(obs));
}
},
2023-07-24 21:16:59 +02:00
false,
2023-06-13 17:48:32 +01:00
)
};
2023-06-13 00:31:38 +01:00
let stop = move || {
cleanup();
stop_watch();
};
2023-07-27 18:06:36 +01:00
on_cleanup(stop.clone());
2023-06-13 00:31:38 +01:00
UseMutationObserverReturn { is_supported, stop }
}
/// The return value of [`use_mutation_observer`].
pub struct UseMutationObserverReturn<F: Fn() + Clone> {
/// Whether the browser supports the MutationObserver API
pub is_supported: Signal<bool>,
/// A function to stop and detach the MutationObserver
pub stop: F,
}