diff --git a/examples/use_event_listener/src/main.rs b/examples/use_event_listener/src/main.rs index fb24e5d..2af1d59 100644 --- a/examples/use_event_listener/src/main.rs +++ b/examples/use_event_listener/src/main.rs @@ -1,17 +1,24 @@ -use leptos::ev::click; +use leptos::ev::{click, keydown}; +use leptos::html::AnyElement; use leptos::*; use leptos_use::use_event_listener; use web_sys::HtmlDivElement; #[component] fn Demo(cx: Scope) -> impl IntoView { + let _ = use_event_listener(cx, window(), keydown, |evt| { + log!("window keydown: '{}'", evt.key()); + }); + let element = create_node_ref(cx); let _ = use_event_listener(cx, element, click, |evt| { log!( - "click from element {:?}", - event_target::(&evt) + "click from element '{:?}'", + event_target::(&evt).inner_text() ); + evt.stop_propagation(); + evt.prevent_default(); }); let (cond, set_cond) = create_signal(cx, true); @@ -29,9 +36,19 @@ fn Demo(cx: Scope) -> impl IntoView {

"Condition false [click me]" } + fallback=move |cx| view! { cx, + + "Condition" + " false " + "[click me]" + + } > -
"Condition true [click me]"
+ + "Condition " + "true" + " [click me]" +
} } diff --git a/src/core/event_target_maybe_signal.rs b/src/core/event_target_maybe_signal.rs index 8ef20e3..7a15f7a 100644 --- a/src/core/event_target_maybe_signal.rs +++ b/src/core/event_target_maybe_signal.rs @@ -2,6 +2,13 @@ use leptos::html::ElementDescriptor; use leptos::*; use std::ops::Deref; +/// Used as an argument type to make it easily possible to pass either +/// * a `web_sys` element that implements `EventTarget`, +/// * an `Option` where `T` is the web_sys element, +/// * a `Signal` where `T` is the web_sys element, +/// * a `Signal>` where `T` is the web_sys element, +/// * a `NodeRef` +/// into a function. Used for example in [`use_event_listener`]. pub enum EventTargetMaybeSignal where T: Into + Clone + 'static, @@ -10,6 +17,103 @@ where Dynamic(Signal>), } +impl Default for EventTargetMaybeSignal +where + T: Into + Clone + 'static, +{ + fn default() -> Self { + Self::Static(None) + } +} + +impl Clone for EventTargetMaybeSignal +where + T: Into + Clone + 'static, +{ + fn clone(&self) -> Self { + match self { + Self::Static(t) => Self::Static(t.clone()), + Self::Dynamic(s) => Self::Dynamic(*s), + } + } +} + +impl SignalGet> for EventTargetMaybeSignal +where + T: Into + Clone + 'static, +{ + fn get(&self) -> Option { + match self { + Self::Static(t) => t.clone(), + Self::Dynamic(s) => s.get(), + } + } + + fn try_get(&self) -> Option> { + match self { + Self::Static(t) => Some(t.clone()), + Self::Dynamic(s) => s.try_get(), + } + } +} + +impl SignalWith> for EventTargetMaybeSignal +where + T: Into + Clone + 'static, +{ + fn with(&self, f: impl FnOnce(&Option) -> O) -> O { + match self { + Self::Static(t) => f(t), + Self::Dynamic(s) => s.with(f), + } + } + + fn try_with(&self, f: impl FnOnce(&Option) -> O) -> Option { + match self { + Self::Static(t) => Some(f(t)), + Self::Dynamic(s) => s.try_with(f), + } + } +} + +impl SignalWithUntracked> for EventTargetMaybeSignal +where + T: Into + Clone + 'static, +{ + fn with_untracked(&self, f: impl FnOnce(&Option) -> O) -> O { + match self { + Self::Static(t) => f(t), + Self::Dynamic(s) => s.with_untracked(f), + } + } + + fn try_with_untracked(&self, f: impl FnOnce(&Option) -> O) -> Option { + match self { + Self::Static(t) => Some(f(t)), + Self::Dynamic(s) => s.try_with_untracked(f), + } + } +} + +impl SignalGetUntracked> for EventTargetMaybeSignal +where + T: Into + Clone + 'static, +{ + fn get_untracked(&self) -> Option { + match self { + Self::Static(t) => t.clone(), + Self::Dynamic(s) => s.get_untracked(), + } + } + + fn try_get_untracked(&self) -> Option> { + match self { + Self::Static(t) => Some(t.clone()), + Self::Dynamic(s) => s.try_get_untracked(), + } + } +} + impl From<(Scope, T)> for EventTargetMaybeSignal where T: Into + Clone + 'static, diff --git a/src/use_event_listener.rs b/src/use_event_listener.rs index e5918a8..196d832 100644 --- a/src/use_event_listener.rs +++ b/src/use_event_listener.rs @@ -28,7 +28,7 @@ use wasm_bindgen::JsCast; /// } /// ``` /// -/// You can also pass a [NodeRef](leptos::NodeRef) as the event target, `use_event_listener` will unregister the previous event and register +/// You can also pass a [`leptos::NodeRef`] as the event target, `use_event_listener` will unregister the previous event and register /// the new one when you change the target. /// /// ``` @@ -79,7 +79,7 @@ use wasm_bindgen::JsCast; /// /// Note if your components also run in SSR (Server Side Rendering), you might get errors /// because DOM APIs like document and window are not available outside of the browser. -/// To avoid that you can put the logic inside a [`create_effect`](leptos::create_effect) hook +/// To avoid that you can put the logic inside a [`leptos::create_effect`] hook /// which only runs client side. #[allow(unused_must_use)] pub fn use_event_listener( @@ -106,72 +106,48 @@ where let event_name = event.name(); - match (cx, target).into() { - EventTargetMaybeSignal::Static(element) => { - if let Some(element) = element { - let element = element.into(); - _ = element.add_event_listener_with_callback( - &event_name, - closure_js.as_ref().unchecked_ref(), - ); + let signal = (cx, target).into(); - let cleanup_fn = move || { - cleanup(&element); - }; - on_cleanup(cx, cleanup_fn.clone()); + let element = signal.get_untracked(); - Box::new(cleanup_fn) - } else { - Box::new(|| {}) - } + let cleanup_prev_element = if let Some(element) = element { + let element = element.into(); + + _ = element + .add_event_listener_with_callback(&event_name, closure_js.as_ref().unchecked_ref()); + + let clean = cleanup.clone(); + Rc::new(RefCell::new(Box::new(move || { + clean(&element); + }) as Box)) + } else { + Rc::new(RefCell::new(Box::new(move || {}) as Box)) + }; + + let cleanup_prev_el = Rc::clone(&cleanup_prev_element); + let closure = closure_js.clone(); + create_effect(cx, move |_| { + cleanup_prev_el.borrow()(); + + let element = signal.get(); + + if let Some(element) = element { + let element = element.into(); + + _ = element + .add_event_listener_with_callback(&event_name, closure.as_ref().unchecked_ref()); + + let clean = cleanup.clone(); + cleanup_prev_el.replace(Box::new(move || { + clean(&element); + }) as Box); + } else { + cleanup_prev_el.replace(Box::new(move || {}) as Box); } - EventTargetMaybeSignal::Dynamic(signal) => { - let element = signal.get_untracked(); + }); - let cleanup_prev_element = if let Some(element) = element { - let element = element.into(); + let cleanup_fn = move || cleanup_prev_element.borrow()(); + on_cleanup(cx, cleanup_fn.clone()); - _ = element.add_event_listener_with_callback( - &event_name, - closure_js.as_ref().unchecked_ref(), - ); - - let clean = cleanup.clone(); - Rc::new(RefCell::new(Box::new(move || { - clean(&element); - }) as Box)) - } else { - Rc::new(RefCell::new(Box::new(move || {}) as Box)) - }; - - let cleanup_prev_el = Rc::clone(&cleanup_prev_element); - let closure = closure_js.clone(); - create_effect(cx, move |_| { - cleanup_prev_el.borrow()(); - - let element = signal(); - - if let Some(element) = element { - let element = element.into(); - - _ = element.add_event_listener_with_callback( - &event_name, - closure.as_ref().unchecked_ref(), - ); - - let clean = cleanup.clone(); - cleanup_prev_el.replace(Box::new(move || { - clean(&element); - }) as Box); - } else { - cleanup_prev_el.replace(Box::new(move || {}) as Box); - } - }); - - let cleanup_fn = move || cleanup_prev_element.borrow()(); - on_cleanup(cx, cleanup_fn.clone()); - - Box::new(cleanup_fn) - } - } + Box::new(cleanup_fn) }