diff --git a/CHANGELOG.md b/CHANGELOG.md index 43f0a19..68dcaa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Please check the release notes of Leptos 0.5 for how to upgrade. - `watch` is now deprecated in favor of `leptos::watch` and will be removed in a future release. `watch_with_options` will continue to exist. +- `use_event_listener_with_options` now takes a `UseEventListenerOptions` instead of a `web_sys::AddEventListenerOptions`. - `use_websocket`: - takes now a `&str` instead of a `String` as its `url` parameter. - The `ready_state` return type is now renamed to `ConnectionReadyState` instead of `UseWebSocketReadyState`. @@ -38,6 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Callbacks in options don't require to be cloneable anymore - Callback in `use_raf_fn` doesn't require to be cloneable anymore - `use_scroll` is now callable on the server +- `use_event_listener` can now be called safely on the server. + ### Fixes 🍕 diff --git a/src/core/element_maybe_signal.rs b/src/core/element_maybe_signal.rs index c368297..9d972a7 100644 --- a/src/core/element_maybe_signal.rs +++ b/src/core/element_maybe_signal.rs @@ -1,3 +1,4 @@ +use crate::{UseDocument, UseWindow}; use leptos::html::ElementDescriptor; use leptos::*; use std::marker::PhantomData; @@ -153,6 +154,22 @@ where } } +macro_rules! impl_from_deref_option { + ($ty:ty, $ty2:ty) => { + impl From<$ty> for ElementMaybeSignal<$ty2, E> + where + E: From<$ty2> + 'static, + { + fn from(value: $ty) -> Self { + Self::Static((*value).clone()) + } + } + }; +} + +impl_from_deref_option!(UseWindow, web_sys::Window); +impl_from_deref_option!(UseDocument, web_sys::Document); + // From string (selector) /////////////////////////////////////////////////////////////// impl<'a, E> From<&'a str> for ElementMaybeSignal diff --git a/src/core/elements_maybe_signal.rs b/src/core/elements_maybe_signal.rs index 645d377..8f5eff3 100644 --- a/src/core/elements_maybe_signal.rs +++ b/src/core/elements_maybe_signal.rs @@ -1,4 +1,5 @@ use crate::core::ElementMaybeSignal; +use crate::{UseDocument, UseWindow}; use leptos::html::ElementDescriptor; use leptos::*; use std::marker::PhantomData; @@ -154,6 +155,22 @@ where } } +macro_rules! impl_from_deref_option { + ($ty:ty, $ty2:ty) => { + impl From<$ty> for ElementsMaybeSignal<$ty2, E> + where + E: From<$ty2> + 'static, + { + fn from(value: $ty) -> Self { + Self::Static(vec![(*value).clone()]) + } + } + }; +} + +impl_from_deref_option!(UseWindow, web_sys::Window); +impl_from_deref_option!(UseDocument, web_sys::Document); + // From string (selector) /////////////////////////////////////////////////////////////// impl<'a, E> From<&'a str> for ElementsMaybeSignal diff --git a/src/on_click_outside.rs b/src/on_click_outside.rs index 705c764..6db5c63 100644 --- a/src/on_click_outside.rs +++ b/src/on_click_outside.rs @@ -1,6 +1,6 @@ use crate::core::{ElementMaybeSignal, ElementsMaybeSignal}; use crate::utils::IS_IOS; -use crate::{use_event_listener, use_event_listener_with_options}; +use crate::{use_event_listener, use_event_listener_with_options, UseEventListenerOptions}; use default_struct_builder::DefaultBuilder; use leptos::ev::{blur, click, pointerdown}; use leptos::*; @@ -9,7 +9,6 @@ use std::rc::Rc; use std::sync::RwLock; use std::time::Duration; use wasm_bindgen::JsCast; -use web_sys::AddEventListenerOptions; static IOS_WORKAROUND: RwLock = RwLock::new(false); @@ -151,14 +150,13 @@ where let remove_click_listener = { let mut listener = listener.clone(); - let mut options = AddEventListenerOptions::default(); - options.passive(true).capture(capture); - use_event_listener_with_options::<_, web_sys::Window, _, _>( window(), click, move |event| listener(event.into()), - options, + UseEventListenerOptions::default() + .passive(true) + .capture(capture), ) }; @@ -166,9 +164,6 @@ where let target = target.clone(); let should_listen = Rc::clone(&should_listen); - let mut options = AddEventListenerOptions::default(); - options.passive(true); - use_event_listener_with_options::<_, web_sys::Window, _, _>( window(), pointerdown, @@ -180,7 +175,7 @@ where ); } }, - options, + UseEventListenerOptions::default().passive(true), ) }; diff --git a/src/use_document.rs b/src/use_document.rs index aa84862..bcbe4b0 100644 --- a/src/use_document.rs +++ b/src/use_document.rs @@ -1,5 +1,4 @@ use cfg_if::cfg_if; -use default_struct_builder::DefaultBuilder; use leptos::*; use std::ops::Deref; @@ -34,6 +33,7 @@ pub fn use_document() -> UseDocument { } /// Return type of [`use_document`]. +#[derive(Debug, Clone, PartialEq, Eq)] pub struct UseDocument(Option); impl Deref for UseDocument { diff --git a/src/use_draggable.rs b/src/use_draggable.rs index c882949..b293626 100644 --- a/src/use_draggable.rs +++ b/src/use_draggable.rs @@ -1,5 +1,5 @@ use crate::core::{ElementMaybeSignal, MaybeRwSignal, PointerType, Position}; -use crate::use_event_listener_with_options; +use crate::{use_event_listener_with_options, UseEventListenerOptions}; use default_struct_builder::DefaultBuilder; use leptos::ev::{pointerdown, pointermove, pointerup}; use leptos::*; @@ -183,20 +183,19 @@ where handle_event(event); }; - let mut listener_options = web_sys::AddEventListenerOptions::new(); - listener_options.capture(true); + let listener_options = UseEventListenerOptions::default().capture(true); let _ = use_event_listener_with_options( dragging_handle, pointerdown, on_pointer_down, - listener_options.clone(), + listener_options, ); let _ = use_event_listener_with_options( dragging_element.clone(), pointermove, on_pointer_move, - listener_options.clone(), + listener_options, ); let _ = use_event_listener_with_options( dragging_element, diff --git a/src/use_element_hover.rs b/src/use_element_hover.rs index 3b8be30..6d3e2ce 100644 --- a/src/use_element_hover.rs +++ b/src/use_element_hover.rs @@ -1,11 +1,10 @@ use crate::core::ElementMaybeSignal; -use crate::use_event_listener_with_options; +use crate::{use_event_listener_with_options, UseEventListenerOptions}; use default_struct_builder::DefaultBuilder; use leptos::ev::{mouseenter, mouseleave}; use leptos::leptos_dom::helpers::TimeoutHandle; use leptos::*; use std::time::Duration; -use web_sys::AddEventListenerOptions; /// Reactive element's hover state. /// @@ -81,14 +80,13 @@ where } }; - let mut listener_options = AddEventListenerOptions::new(); - listener_options.passive(true); + let mut listener_options = UseEventListenerOptions::default().passive(true); let _ = use_event_listener_with_options( el.clone(), mouseenter, move |_| toggle(true), - listener_options.clone(), + listener_options, ); let _ = diff --git a/src/use_event_listener.rs b/src/use_event_listener.rs index 580703b..e595895 100644 --- a/src/use_event_listener.rs +++ b/src/use_event_listener.rs @@ -1,5 +1,6 @@ use crate::core::ElementMaybeSignal; use crate::{watch_with_options, WatchOptions}; +use default_struct_builder::DefaultBuilder; use leptos::ev::EventDescriptor; use leptos::*; use std::cell::RefCell; @@ -17,11 +18,11 @@ use wasm_bindgen::JsCast; /// # use leptos::*; /// # use leptos::ev::visibilitychange; /// # use leptos::logging::log; -/// # use leptos_use::use_event_listener; +/// # use leptos_use::{use_document, use_event_listener}; /// # /// # #[component] /// # fn Demo() -> impl IntoView { -/// use_event_listener(document(), visibilitychange, |evt| { +/// use_event_listener(use_document(), visibilitychange, |evt| { /// log!("{:?}", evt); /// }); /// # view! { } @@ -81,7 +82,7 @@ use wasm_bindgen::JsCast; /// /// ## 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 noop. pub fn use_event_listener(target: El, event: Ev, handler: F) -> impl Fn() + Clone where Ev: EventDescriptor + 'static, @@ -89,12 +90,7 @@ where T: Into + Clone + 'static, F: FnMut(::EventType) + 'static, { - use_event_listener_with_options( - target, - event, - handler, - web_sys::AddEventListenerOptions::new(), - ) + use_event_listener_with_options(target, event, handler, UseEventListenerOptions::default()) } /// Version of [`use_event_listener`] that takes `web_sys::AddEventListenerOptions`. See the docs for [`use_event_listener`] for how to use. @@ -102,7 +98,7 @@ pub fn use_event_listener_with_options( target: El, event: Ev, handler: F, - options: web_sys::AddEventListenerOptions, + options: UseEventListenerOptions, ) -> impl Fn() + Clone where Ev: EventDescriptor + 'static, @@ -115,7 +111,7 @@ where let cleanup_fn = { let closure_js = closure_js.clone(); - let options = options.clone(); + let options = options.as_add_event_listener_options(); move |element: &web_sys::EventTarget| { let _ = element.remove_event_listener_with_callback_and_event_listener_options( @@ -152,6 +148,8 @@ where prev_element.replace(element.clone()); if let Some(element) = element { + let options = options.as_add_event_listener_options(); + _ = element.add_event_listener_with_callback_and_add_event_listener_options( &event_name, closure_js.as_ref().unchecked_ref(), @@ -172,3 +170,52 @@ where stop } + +/// Options for [`use_event_listener_with_options`]. +#[derive(DefaultBuilder, Default, Copy, Clone)] +pub struct UseEventListenerOptions { + /// A boolean value indicating that events of this type will be dispatched to + /// the registered `listener` before being dispatched to any `EventTarget` + /// beneath it in the DOM tree. If not specified, defaults to `false`. + capture: bool, + + /// A boolean value indicating that the `listener` should be invoked at most + /// once after being added. If `true`, the `listener` would be automatically + /// removed when invoked. If not specified, defaults to `false`. + once: bool, + + /// A boolean value that, if `true`, indicates that the function specified by + /// `listener` will never call + /// [`preventDefault()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault "preventDefault()"). + /// If a passive listener does call `preventDefault()`, the user agent will do + /// nothing other than generate a console warning. If not specified, + /// defaults to `false` – except that in browsers other than Safari, + /// defaults to `true` for the + /// [`wheel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event "wheel"), + /// [`mousewheel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/mousewheel_event "mousewheel"), + /// [`touchstart`](https://developer.mozilla.org/en-US/docs/Web/API/Element/touchstart_event "touchstart") and + /// [`touchmove`](https://developer.mozilla.org/en-US/docs/Web/API/Element/touchmove_event "touchmove") + /// events. See [Improving scrolling performance with passive listeners](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners) + /// to learn more. + #[builder(into)] + passive: Option, +} + +impl UseEventListenerOptions { + fn as_add_event_listener_options(&self) -> web_sys::AddEventListenerOptions { + let UseEventListenerOptions { + capture, + once, + passive, + } = self; + + let mut options = web_sys::AddEventListenerOptions::new(); + options.capture(*capture); + options.once(*once); + if let Some(passive) = passive { + options.passive(*passive); + } + + options + } +} diff --git a/src/use_window.rs b/src/use_window.rs index 58d9428..42c31ff 100644 --- a/src/use_window.rs +++ b/src/use_window.rs @@ -1,6 +1,5 @@ use crate::{use_document, UseDocument}; use cfg_if::cfg_if; -use default_struct_builder::DefaultBuilder; use leptos::*; use std::ops::Deref; @@ -36,6 +35,7 @@ pub fn use_window() -> UseWindow { } /// Return type of [`use_window`]. +#[derive(Debug, Clone, PartialEq, Eq)] pub struct UseWindow(Option); impl Deref for UseWindow {