improved docs and example

This commit is contained in:
Maccesch 2023-05-14 23:50:11 +01:00
parent fa4455d7c9
commit fc4198897c
3 changed files with 167 additions and 70 deletions

View file

@ -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::<HtmlDivElement>(&evt)
"click from element '{:?}'",
event_target::<web_sys::HtmlElement>(&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 {
</p>
<Show
when=move || cond()
fallback=move |cx| view! { cx, <div node_ref=element>"Condition false [click me]"</div> }
fallback=move |cx| view! { cx,
<a node_ref=element href="#">
"Condition"
<b>" false "</b>
"[click me]"
</a>
}
>
<div node_ref=element>"Condition true [click me]"</div>
<a node_ref=element href="#">
"Condition "
<b>"true"</b>
" [click me]"
</a>
</Show>
}
}

View file

@ -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<T>` where `T` is the web_sys element,
/// * a `Signal<T>` where `T` is the web_sys element,
/// * a `Signal<Option<T>>` where `T` is the web_sys element,
/// * a `NodeRef`
/// into a function. Used for example in [`use_event_listener`].
pub enum EventTargetMaybeSignal<T>
where
T: Into<web_sys::EventTarget> + Clone + 'static,
@ -10,6 +17,103 @@ where
Dynamic(Signal<Option<T>>),
}
impl<T> Default for EventTargetMaybeSignal<T>
where
T: Into<web_sys::EventTarget> + Clone + 'static,
{
fn default() -> Self {
Self::Static(None)
}
}
impl<T> Clone for EventTargetMaybeSignal<T>
where
T: Into<web_sys::EventTarget> + Clone + 'static,
{
fn clone(&self) -> Self {
match self {
Self::Static(t) => Self::Static(t.clone()),
Self::Dynamic(s) => Self::Dynamic(*s),
}
}
}
impl<T> SignalGet<Option<T>> for EventTargetMaybeSignal<T>
where
T: Into<web_sys::EventTarget> + Clone + 'static,
{
fn get(&self) -> Option<T> {
match self {
Self::Static(t) => t.clone(),
Self::Dynamic(s) => s.get(),
}
}
fn try_get(&self) -> Option<Option<T>> {
match self {
Self::Static(t) => Some(t.clone()),
Self::Dynamic(s) => s.try_get(),
}
}
}
impl<T> SignalWith<Option<T>> for EventTargetMaybeSignal<T>
where
T: Into<web_sys::EventTarget> + Clone + 'static,
{
fn with<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> O {
match self {
Self::Static(t) => f(t),
Self::Dynamic(s) => s.with(f),
}
}
fn try_with<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> Option<O> {
match self {
Self::Static(t) => Some(f(t)),
Self::Dynamic(s) => s.try_with(f),
}
}
}
impl<T> SignalWithUntracked<Option<T>> for EventTargetMaybeSignal<T>
where
T: Into<web_sys::EventTarget> + Clone + 'static,
{
fn with_untracked<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> O {
match self {
Self::Static(t) => f(t),
Self::Dynamic(s) => s.with_untracked(f),
}
}
fn try_with_untracked<O>(&self, f: impl FnOnce(&Option<T>) -> O) -> Option<O> {
match self {
Self::Static(t) => Some(f(t)),
Self::Dynamic(s) => s.try_with_untracked(f),
}
}
}
impl<T> SignalGetUntracked<Option<T>> for EventTargetMaybeSignal<T>
where
T: Into<web_sys::EventTarget> + Clone + 'static,
{
fn get_untracked(&self) -> Option<T> {
match self {
Self::Static(t) => t.clone(),
Self::Dynamic(s) => s.get_untracked(),
}
}
fn try_get_untracked(&self) -> Option<Option<T>> {
match self {
Self::Static(t) => Some(t.clone()),
Self::Dynamic(s) => s.try_get_untracked(),
}
}
}
impl<T> From<(Scope, T)> for EventTargetMaybeSignal<T>
where
T: Into<web_sys::EventTarget> + Clone + 'static,

View file

@ -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<Ev, El, T, F>(
@ -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<dyn Fn()>))
} else {
Rc::new(RefCell::new(Box::new(move || {}) as Box<dyn Fn()>))
};
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<dyn Fn()>);
} else {
cleanup_prev_el.replace(Box::new(move || {}) as Box<dyn Fn()>);
}
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<dyn Fn()>))
} else {
Rc::new(RefCell::new(Box::new(move || {}) as Box<dyn Fn()>))
};
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<dyn Fn()>);
} else {
cleanup_prev_el.replace(Box::new(move || {}) as Box<dyn Fn()>);
}
});
let cleanup_fn = move || cleanup_prev_element.borrow()();
on_cleanup(cx, cleanup_fn.clone());
Box::new(cleanup_fn)
}
}
Box::new(cleanup_fn)
}