use crate::core::IntoElementMaybeSignal;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::ev::EventDescriptor;
cfg_if! { if #[cfg(not(feature = "ssr"))] {
use crate::{watch_with_options, WatchOptions, sendwrap_fn};
use leptos::prelude::*;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
}}
pub fn use_event_listener<Ev, El, M, F>(
target: El,
event: Ev,
handler: F,
) -> impl Fn() + Clone + Send + Sync
where
Ev: EventDescriptor + 'static,
El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
F: FnMut(<Ev as EventDescriptor>::EventType) + 'static,
{
use_event_listener_with_options(target, event, handler, UseEventListenerOptions::default())
}
#[cfg_attr(feature = "ssr", allow(unused_variables))]
#[allow(unused_mut)]
pub fn use_event_listener_with_options<Ev, El, M, F>(
target: El,
event: Ev,
mut handler: F,
options: UseEventListenerOptions,
) -> impl Fn() + Clone + Send + Sync
where
Ev: EventDescriptor + 'static,
El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
F: FnMut(<Ev as EventDescriptor>::EventType) + 'static,
{
#[cfg(feature = "ssr")]
{
|| {}
}
#[cfg(not(feature = "ssr"))]
{
use send_wrapper::SendWrapper;
let event_name = event.name();
let closure_js = Closure::wrap(Box::new(move |e| {
#[cfg(debug_assertions)]
let _z = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
handler(e);
}) as Box<dyn FnMut(_)>)
.into_js_value();
let cleanup_fn = {
let closure_js = closure_js.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(
&event_name,
closure_js.as_ref().unchecked_ref(),
options.unchecked_ref(),
);
}
};
let event_name = event.name();
let signal = target.into_element_maybe_signal();
let prev_element = Rc::new(RefCell::new(None::<web_sys::EventTarget>));
let cleanup_prev_element = {
let prev_element = prev_element.clone();
move || {
if let Some(element) = prev_element.take() {
cleanup_fn(&element);
}
}
};
let stop_watch = {
let cleanup_prev_element = cleanup_prev_element.clone();
watch_with_options(
move || signal.get(),
move |element, _, _| {
cleanup_prev_element();
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(),
&options,
);
}
},
WatchOptions::default().immediate(true),
)
};
let stop = sendwrap_fn!(move || {
stop_watch();
cleanup_prev_element();
});
on_cleanup({
let stop = SendWrapper::new(stop.clone());
#[allow(clippy::redundant_closure)]
move || stop()
});
stop
}
}
#[derive(DefaultBuilder, Default, Copy, Clone)]
#[cfg_attr(feature = "ssr", allow(dead_code))]
pub struct UseEventListenerOptions {
capture: bool,
once: bool,
#[builder(into)]
passive: Option<bool>,
}
impl UseEventListenerOptions {
#[cfg_attr(feature = "ssr", allow(dead_code))]
fn as_add_event_listener_options(&self) -> web_sys::AddEventListenerOptions {
let UseEventListenerOptions {
capture,
once,
passive,
} = self;
let options = web_sys::AddEventListenerOptions::new();
options.set_capture(*capture);
options.set_once(*once);
if let Some(passive) = passive {
options.set_passive(*passive);
}
options
}
}