(),
+ );
+
+ if let Some(Some(root)) = root {
+ let root: web_sys::Element = root.clone().into();
+ options.root(Some(&root));
+ }
+
+ let obs = web_sys::IntersectionObserver::new_with_options(
+ closure_js.clone().as_ref().unchecked_ref(),
+ &options,
+ )
+ .expect("failed to create IntersectionObserver");
+
+ for target in targets.iter().flatten() {
+ let target: web_sys::Element = target.clone().into();
+ obs.observe(&target);
+ }
+
+ observer.replace(Some(obs));
+ },
+ WatchOptions::default().immediate(immediate),
+ )
+ };
+
+ let stop = {
+ let cleanup = cleanup.clone();
+
+ move || {
+ cleanup();
+ stop_watch();
+ }
+ };
+
+ on_cleanup(stop.clone());
+
+ let pause = {
+ let cleanup = cleanup.clone();
+
+ move || {
+ cleanup();
+ set_active.set(false);
+ }
+ };
+ }}
UseIntersectionObserverReturn {
is_active: is_active.into(),
diff --git a/src/use_mouse.rs b/src/use_mouse.rs
index b76b84b..5e4e17b 100644
--- a/src/use_mouse.rs
+++ b/src/use_mouse.rs
@@ -1,14 +1,13 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::core::{ElementMaybeSignal, Position};
-use crate::use_event_listener_with_options;
+use crate::{use_event_listener_with_options, UseEventListenerOptions};
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart};
use leptos::*;
use std::marker::PhantomData;
use wasm_bindgen::{JsCast, JsValue};
-use web_sys::AddEventListenerOptions;
/// Reactive mouse position
///
@@ -158,40 +157,39 @@ where
cfg_if! { if #[cfg(not(feature = "ssr"))] {
let target = options.target;
- let mut event_listener_options = AddEventListenerOptions::new();
- event_listener_options.passive(true);
+ let event_listener_options = UseEventListenerOptions::default().passive(true);
let _ = use_event_listener_with_options(
target.clone(),
mousemove,
mouse_handler,
- event_listener_options.clone(),
+ event_listener_options,
);
let _ = use_event_listener_with_options(
target.clone(),
dragover,
drag_handler,
- event_listener_options.clone(),
+ event_listener_options,
);
if options.touch && !matches!(options.coord_type, UseMouseCoordType::Movement) {
let _ = use_event_listener_with_options(
target.clone(),
touchstart,
touch_handler.clone(),
- event_listener_options.clone(),
+ event_listener_options,
);
let _ = use_event_listener_with_options(
target.clone(),
touchmove,
touch_handler,
- event_listener_options.clone(),
+ event_listener_options,
);
if options.reset_on_touch_ends {
let _ = use_event_listener_with_options(
target,
touchend,
move |_| reset(),
- event_listener_options.clone(),
+ event_listener_options,
);
}
}
diff --git a/src/use_mutation_observer.rs b/src/use_mutation_observer.rs
index beb217f..f518000 100644
--- a/src/use_mutation_observer.rs
+++ b/src/use_mutation_observer.rs
@@ -1,10 +1,14 @@
use crate::core::ElementsMaybeSignal;
-use crate::use_supported;
+use cfg_if::cfg_if;
+use default_struct_builder::DefaultBuilder;
use leptos::*;
-use std::cell::RefCell;
-use std::rc::Rc;
use wasm_bindgen::prelude::*;
-use web_sys::MutationObserverInit;
+
+cfg_if! { if #[cfg(not(feature = "ssr"))] {
+ use crate::use_supported;
+ use std::cell::RefCell;
+ use std::rc::Rc;
+}}
/// Reactive [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver).
///
@@ -19,16 +23,13 @@ use web_sys::MutationObserverInit;
/// ```
/// # use leptos::*;
/// # use leptos::html::Pre;
-/// # use leptos_use::use_mutation_observer_with_options;
+/// # use leptos_use::{use_mutation_observer_with_options, UseMutationObserverOptions};
/// #
/// # #[component]
/// # fn Demo() -> impl IntoView {
/// let el = create_node_ref::();
/// let (text, set_text) = create_signal("".to_string());
///
-/// let mut init = web_sys::MutationObserverInit::new();
-/// init.attributes(true);
-///
/// use_mutation_observer_with_options(
/// el,
/// move |mutations, _| {
@@ -36,7 +37,7 @@ use web_sys::MutationObserverInit;
/// set_text.update(|text| *text = format!("{text}\n{:?}", mutation.attribute_name()));
/// }
/// },
-/// init,
+/// UseMutationObserverOptions::default().attributes(true),
/// );
///
/// view! {
@@ -47,7 +48,7 @@ use web_sys::MutationObserverInit;
///
/// ## Server-Side Rendering
///
-/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
+/// On the server this amounts to a no-op.
pub fn use_mutation_observer(
target: El,
callback: F,
@@ -57,84 +58,167 @@ where
T: Into + Clone + 'static,
F: FnMut(Vec, web_sys::MutationObserver) + 'static,
{
- use_mutation_observer_with_options(target, callback, MutationObserverInit::default())
+ use_mutation_observer_with_options(target, callback, UseMutationObserverOptions::default())
}
-/// Version of [`use_mutation_observer`] that takes a `web_sys::MutationObserverInit`. See [`use_mutation_observer`] for how to use.
+/// Version of [`use_mutation_observer`] that takes a `UseMutationObserverOptions`. See [`use_mutation_observer`] for how to use.
+#[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))]
pub fn use_mutation_observer_with_options(
target: El,
mut callback: F,
- options: web_sys::MutationObserverInit,
+ options: UseMutationObserverOptions,
) -> UseMutationObserverReturn
where
El: Into>,
T: Into + Clone + 'static,
F: FnMut(Vec, web_sys::MutationObserver) + 'static,
{
- let closure_js = Closure::::new(
- move |entries: js_sys::Array, observer| {
- callback(
- entries
- .to_vec()
- .into_iter()
- .map(|v| v.unchecked_into::())
- .collect(),
- observer,
- );
- },
- )
- .into_js_value();
-
- let observer: Rc>> = Rc::new(RefCell::new(None));
-
- let is_supported = use_supported(|| JsValue::from("MutationObserver").js_in(&window()));
-
- 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;
- }
+ cfg_if! { if #[cfg(feature = "ssr")] {
+ UseMutationObserverReturn {
+ is_supported: Signal::derive(|| true),
+ stop: || {},
}
- };
-
- let targets = (target).into();
-
- let stop_watch = {
- let cleanup = cleanup.clone();
-
- leptos::watch(
- move || targets.get(),
- move |targets, _, _| {
- cleanup();
-
- if is_supported.get() && !targets.is_empty() {
- let obs = web_sys::MutationObserver::new(closure_js.as_ref().unchecked_ref())
- .expect("failed to create MutationObserver");
-
- 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));
- }
+ } else {
+ let closure_js = Closure::::new(
+ move |entries: js_sys::Array, observer| {
+ callback(
+ entries
+ .to_vec()
+ .into_iter()
+ .map(|v| v.unchecked_into::())
+ .collect(),
+ observer,
+ );
},
- false,
)
- };
+ .into_js_value();
- let stop = move || {
- cleanup();
- stop_watch();
- };
+ let observer: Rc>> = Rc::new(RefCell::new(None));
- on_cleanup(stop.clone());
+ let is_supported = use_supported(|| JsValue::from("MutationObserver").js_in(&window()));
- UseMutationObserverReturn { is_supported, stop }
+ 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;
+ }
+ }
+ };
+
+ let targets = (target).into();
+
+ let stop_watch = {
+ let cleanup = cleanup.clone();
+
+ leptos::watch(
+ move || targets.get(),
+ move |targets, _, _| {
+ cleanup();
+
+ if is_supported.get() && !targets.is_empty() {
+ let obs = web_sys::MutationObserver::new(closure_js.as_ref().unchecked_ref())
+ .expect("failed to create MutationObserver");
+
+ for target in targets.iter().flatten() {
+ let target: web_sys::Element = target.clone().into();
+ let _ = obs.observe_with_options(&target, &options.clone().into());
+ }
+
+ observer.replace(Some(obs));
+ }
+ },
+ false,
+ )
+ };
+
+ let stop = move || {
+ cleanup();
+ stop_watch();
+ };
+
+ on_cleanup(stop.clone());
+
+ UseMutationObserverReturn { is_supported, stop }
+ }}
+}
+
+/// Options for [`use_mutation_observer_with_options`].
+#[derive(DefaultBuilder, Clone, Default)]
+pub struct UseMutationObserverOptions {
+ /// Set to `true` to extend monitoring to the entire subtree of nodes rooted at `target`.
+ /// All of the other properties are then extended to all of the nodes in the subtree
+ /// instead of applying solely to the `target` node. The default value is `false`.
+ subtree: bool,
+
+ /// Set to `true` to monitor the target node (and, if `subtree` is `true`, its descendants)
+ /// for the addition of new child nodes or removal of existing child nodes.
+ /// The default value is `false`.
+ child_list: bool,
+
+ /// Set to `true` to watch for changes to the value of attributes on the node or nodes being
+ /// monitored. The default value is `true` if either of `attribute_filter` or
+ /// `attribute_old_value` is specified, otherwise the default value is `false`.
+ attributes: bool,
+
+ /// An array of specific attribute names to be monitored. If this property isn't included,
+ /// changes to all attributes cause mutation notifications.
+ #[builder(into)]
+ attribute_filter: Option>,
+
+ /// Set to `true` to record the previous value of any attribute that changes when monitoring
+ /// the node or nodes for attribute changes; See
+ /// [Monitoring attribute values](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe#monitoring_attribute_values)
+ /// for an example of watching for attribute changes and recording values.
+ /// The default value is `false`.
+ attribute_old_value: bool,
+
+ /// Set to `true` to monitor the specified target node
+ /// (and, if `subtree` is `true`, its descendants)
+ /// for changes to the character data contained within the node or nodes.
+ /// The default value is `true` if `character_data_old_value` is specified,
+ /// otherwise the default value is `false`.
+ #[builder(into)]
+ character_data: Option,
+
+ /// Set to `true` to record the previous value of a node's text whenever the text changes on
+ /// nodes being monitored. The default value is `false`.
+ character_data_old_value: bool,
+}
+
+impl From for web_sys::MutationObserverInit {
+ fn from(val: UseMutationObserverOptions) -> Self {
+ let UseMutationObserverOptions {
+ subtree,
+ child_list,
+ attributes,
+ attribute_filter,
+ attribute_old_value,
+ character_data,
+ character_data_old_value,
+ } = val;
+
+ let mut init = Self::new();
+
+ init.subtree(subtree)
+ .child_list(child_list)
+ .attributes(attributes)
+ .attribute_old_value(attribute_old_value)
+ .character_data_old_value(character_data_old_value);
+
+ if let Some(attribute_filter) = attribute_filter {
+ let array = js_sys::Array::from_iter(attribute_filter.into_iter().map(JsValue::from));
+ init.attribute_filter(array.unchecked_ref());
+ }
+ if let Some(character_data) = character_data {
+ init.character_data(character_data);
+ }
+
+ init
+ }
}
/// The return value of [`use_mutation_observer`].
diff --git a/src/use_resize_observer.rs b/src/use_resize_observer.rs
index 9dfdaf3..1c61081 100644
--- a/src/use_resize_observer.rs
+++ b/src/use_resize_observer.rs
@@ -1,10 +1,14 @@
use crate::core::ElementsMaybeSignal;
-use crate::use_supported;
+use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::*;
-use std::cell::RefCell;
-use std::rc::Rc;
-use wasm_bindgen::prelude::*;
+
+cfg_if! { if #[cfg(not(feature = "ssr"))] {
+ use crate::use_supported;
+ use std::cell::RefCell;
+ use std::rc::Rc;
+ use wasm_bindgen::prelude::*;
+}}
/// Reports changes to the dimensions of an Element's content or the border-box.
///
@@ -45,7 +49,7 @@ use wasm_bindgen::prelude::*;
///
/// ## Server-Side Rendering
///
-/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
+/// On the server this amounts to a no-op.
///
/// ## See also
///
@@ -63,6 +67,7 @@ where
}
/// Version of [`use_resize_observer`] that takes a `web_sys::ResizeObserverOptions`. See [`use_resize_observer`] for how to use.
+#[cfg_attr(feature = "ssr", allow(unused_variables, unused_mut))]
pub fn use_resize_observer_with_options(
target: El, // TODO : multiple elements?
mut callback: F,
@@ -73,92 +78,95 @@ where
T: Into + Clone + 'static,
F: FnMut(Vec, web_sys::ResizeObserver) + 'static,
{
- let closure_js = Closure::::new(
- move |entries: js_sys::Array, observer| {
- callback(
- entries
- .to_vec()
- .into_iter()
- .map(|v| v.unchecked_into::())
- .collect(),
- observer,
- );
- },
- )
- .into_js_value();
-
- let observer: Rc>> = Rc::new(RefCell::new(None));
-
- let is_supported = use_supported(|| JsValue::from("ResizeObserver").js_in(&window()));
-
- 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;
- }
+ cfg_if! { if #[cfg(feature = "ssr")] {
+ UseResizeObserverReturn {
+ is_supported: Signal::derive(|| true),
+ stop: || {}
}
- };
-
- let targets = (target).into();
-
- let stop_watch = {
- let cleanup = cleanup.clone();
-
- watch(
- move || targets.get(),
- move |targets, _, _| {
- cleanup();
-
- if is_supported.get() && !targets.is_empty() {
- let obs =
- web_sys::ResizeObserver::new(closure_js.clone().as_ref().unchecked_ref())
- .expect("failed to create ResizeObserver");
-
- for target in targets.iter().flatten() {
- let target: web_sys::Element = target.clone().into();
- obs.observe_with_options(&target, &options.clone().into());
- }
-
- observer.replace(Some(obs));
- }
+ } else {
+ let closure_js = Closure::::new(
+ move |entries: js_sys::Array, observer| {
+ callback(
+ entries
+ .to_vec()
+ .into_iter()
+ .map(|v| v.unchecked_into::())
+ .collect(),
+ observer,
+ );
},
- false,
)
- };
+ .into_js_value();
- let stop = move || {
- cleanup();
- stop_watch();
- };
+ let observer: Rc>> = Rc::new(RefCell::new(None));
- on_cleanup(stop.clone());
+ let is_supported = use_supported(|| JsValue::from("ResizeObserver").js_in(&window()));
- UseResizeObserverReturn { is_supported, stop }
+ 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;
+ }
+ }
+ };
+
+ let targets = (target).into();
+
+ let stop_watch = {
+ let cleanup = cleanup.clone();
+
+ watch(
+ move || targets.get(),
+ move |targets, _, _| {
+ cleanup();
+
+ if is_supported.get() && !targets.is_empty() {
+ let obs =
+ web_sys::ResizeObserver::new(closure_js.clone().as_ref().unchecked_ref())
+ .expect("failed to create ResizeObserver");
+
+ for target in targets.iter().flatten() {
+ let target: web_sys::Element = target.clone().into();
+ obs.observe_with_options(&target, &options.clone().into());
+ }
+
+ observer.replace(Some(obs));
+ }
+ },
+ false,
+ )
+ };
+
+ let stop = move || {
+ cleanup();
+ stop_watch();
+ };
+
+ on_cleanup(stop.clone());
+
+ UseResizeObserverReturn { is_supported, stop }
+ }}
}
/// Options for [`use_resize_observer_with_options`].
-#[derive(DefaultBuilder, Clone)]
+#[derive(DefaultBuilder, Clone, Default)]
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,
- }
- }
+ #[builder(into)]
+ pub box_: Option,
}
impl From for web_sys::ResizeObserverOptions {
fn from(val: UseResizeObserverOptions) -> Self {
let mut options = web_sys::ResizeObserverOptions::new();
- options.box_(val.box_);
+ options.box_(
+ val.box_
+ .unwrap_or(web_sys::ResizeObserverBoxOptions::ContentBox),
+ );
options
}
}
diff --git a/src/use_scroll.rs b/src/use_scroll.rs
index 9913fb8..94c2c12 100644
--- a/src/use_scroll.rs
+++ b/src/use_scroll.rs
@@ -1,13 +1,25 @@
use crate::core::ElementMaybeSignal;
-use crate::use_event_listener::use_event_listener_with_options;
-use crate::{use_debounce_fn_with_arg, use_throttle_fn_with_arg_and_options, ThrottleOptions};
+use crate::UseEventListenerOptions;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
-use leptos::ev::scrollend;
use leptos::*;
use std::rc::Rc;
+
+cfg_if! { if #[cfg(not(feature = "ssr"))] {
+use crate::use_event_listener::use_event_listener_with_options;
+use crate::{
+ use_debounce_fn_with_arg, use_throttle_fn_with_arg_and_options, ThrottleOptions,
+};
+use leptos::ev::scrollend;
use wasm_bindgen::JsCast;
+/// We have to check if the scroll amount is close enough to some threshold in order to
+/// more accurately calculate arrivedState. This is because scrollTop/scrollLeft are non-rounded
+/// numbers, while scrollHeight/scrollWidth and clientHeight/clientWidth are rounded.
+/// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
+const ARRIVED_STATE_THRESHOLD_PIXELS: f64 = 1.0;
+}}
+
/// Reactive scroll position and state.
///
/// ## Demo
@@ -172,7 +184,7 @@ where
}
/// Version of [`use_scroll`] with options. See [`use_scroll`] for how to use.
-#[allow(unused_variables)]
+#[cfg_attr(feature = "ssr", allow(unused_variables))]
pub fn use_scroll_with_options(element: El, options: UseScrollOptions) -> UseScrollReturn
where
El: Clone,
@@ -377,7 +389,7 @@ where
target,
ev::scroll,
handler,
- options.event_listener_options.clone().unwrap_or_default(),
+ options.event_listener_options,
);
} else {
let _ = use_event_listener_with_options::<
@@ -389,7 +401,7 @@ where
target,
ev::scroll,
on_scroll_handler,
- options.event_listener_options.clone().unwrap_or_default(),
+ options.event_listener_options,
);
}
@@ -402,7 +414,7 @@ where
target,
scrollend,
on_scroll_end,
- options.event_listener_options.unwrap_or_default(),
+ options.event_listener_options,
);
let measure = Box::new(move || {
@@ -426,15 +438,10 @@ where
}
}
-/// We have to check if the scroll amount is close enough to some threshold in order to
-/// more accurately calculate arrivedState. This is because scrollTop/scrollLeft are non-rounded
-/// numbers, while scrollHeight/scrollWidth and clientHeight/clientWidth are rounded.
-/// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
-const ARRIVED_STATE_THRESHOLD_PIXELS: f64 = 1.0;
-
/// Options for [`use_scroll`].
#[derive(DefaultBuilder)]
/// Options for [`use_scroll_with_options`].
+#[cfg_attr(feature = "ssr", allow(dead_code))]
pub struct UseScrollOptions {
/// Throttle time in milliseconds for the scroll events. Defaults to 0 (disabled).
throttle: f64,
@@ -453,8 +460,7 @@ pub struct UseScrollOptions {
on_stop: Rc,
/// Options passed to the `addEventListener("scroll", ...)` call
- #[builder(into)]
- event_listener_options: Option,
+ event_listener_options: UseEventListenerOptions,
/// When changing the `x` or `y` signals this specifies the scroll behaviour.
/// Can be `Auto` (= not smooth) or `Smooth`. Defaults to `Auto`.
diff --git a/src/use_window.rs b/src/use_window.rs
index 42c31ff..b9dae5a 100644
--- a/src/use_window.rs
+++ b/src/use_window.rs
@@ -1,8 +1,10 @@
use crate::{use_document, UseDocument};
use cfg_if::cfg_if;
-use leptos::*;
use std::ops::Deref;
+#[cfg(not(feature = "ssr"))]
+use leptos::*;
+
/// SSR safe `window()`.
/// This returns just a new-type wrapper around `Option`.
/// Calling this amounts to `None` on the server and `Some(Window)` on the client.
diff --git a/src/use_window_scroll.rs b/src/use_window_scroll.rs
index 8906a38..9b1fbd9 100644
--- a/src/use_window_scroll.rs
+++ b/src/use_window_scroll.rs
@@ -1,10 +1,9 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
-use crate::use_event_listener_with_options;
+use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions};
use cfg_if::cfg_if;
use leptos::ev::scroll;
use leptos::*;
-use web_sys::AddEventListenerOptions;
/// Reactive window scroll.
///
@@ -40,21 +39,17 @@ pub fn use_window_scroll() -> (Signal, Signal) {
let (x, set_x) = create_signal(initial_x);
let (y, set_y) = create_signal(initial_y);
- cfg_if! { if #[cfg(not(feature = "ssr"))] {
- let mut options = AddEventListenerOptions::new();
- options.capture(false);
- options.passive(true);
-
- let _ = use_event_listener_with_options(
- window(),
- scroll,
- move |_| {
- set_x.set(window().scroll_x().unwrap_or_default());
- set_y.set(window().scroll_y().unwrap_or_default());
- },
- options,
- );
- }}
+ let _ = use_event_listener_with_options(
+ use_window(),
+ scroll,
+ move |_| {
+ set_x.set(window().scroll_x().unwrap_or_default());
+ set_y.set(window().scroll_y().unwrap_or_default());
+ },
+ UseEventListenerOptions::default()
+ .capture(false)
+ .passive(true),
+ );
(x.into(), y.into())
}