mirror of
https://github.com/chenasraf/leptos.git
synced 2026-05-17 17:48:10 +00:00
feat: add a debounce helper for event listeners (#691)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
//! A variety of DOM utility functions.
|
||||
|
||||
use crate::{is_server, window};
|
||||
use leptos_reactive::{on_cleanup, Scope};
|
||||
use std::time::Duration;
|
||||
use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};
|
||||
|
||||
@@ -166,6 +167,18 @@ pub fn request_idle_callback_with_handle(
|
||||
.map(IdleCallbackHandle)
|
||||
}
|
||||
|
||||
/// Handle that is generated by [set_timeout_with_handle] and can be used to clear the timeout.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TimeoutHandle(i32);
|
||||
|
||||
impl TimeoutHandle {
|
||||
/// Cancels the timeout to which this refers.
|
||||
/// See [`clearTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout)
|
||||
pub fn clear(&self) {
|
||||
window().clear_timeout_with_handle(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the given function after the given duration of time has passed.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
#[cfg_attr(
|
||||
@@ -173,6 +186,19 @@ pub fn request_idle_callback_with_handle(
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
||||
_ = set_timeout_with_handle(cb, duration);
|
||||
}
|
||||
|
||||
/// Executes the given function after the given duration of time has passed, returning a cancelable handle.
|
||||
/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
|
||||
#[cfg_attr(
|
||||
debug_assertions,
|
||||
instrument(level = "trace", skip_all, fields(duration = ?duration))
|
||||
)]
|
||||
pub fn set_timeout_with_handle(
|
||||
cb: impl FnOnce() + 'static,
|
||||
duration: Duration,
|
||||
) -> Result<TimeoutHandle, JsValue> {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
@@ -184,10 +210,83 @@ pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
|
||||
}
|
||||
|
||||
let cb = Closure::once_into_js(Box::new(cb) as Box<dyn FnOnce()>);
|
||||
_ = window().set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
cb.as_ref().unchecked_ref(),
|
||||
duration.as_millis().try_into().unwrap_throw(),
|
||||
);
|
||||
window()
|
||||
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
cb.as_ref().unchecked_ref(),
|
||||
duration.as_millis().try_into().unwrap_throw(),
|
||||
)
|
||||
.map(TimeoutHandle)
|
||||
}
|
||||
|
||||
/// "Debounce" a callback function. This will cause it to wait for a period of `delay`
|
||||
/// after it is called. If it is called again during that period, it will wait
|
||||
/// `delay` before running, and so on. This can be used, for example, to wrap event
|
||||
/// listeners to prevent them from firing constantly as you type.
|
||||
///
|
||||
/// ```
|
||||
/// use leptos::{leptos_dom::helpers::debounce, *};
|
||||
///
|
||||
/// #[component]
|
||||
/// fn DebouncedButton(cx: Scope) -> impl IntoView {
|
||||
/// let delay = std::time::Duration::from_millis(250);
|
||||
/// let on_click = debounce(cx, delay, move |_| {
|
||||
/// log!("...so many clicks!");
|
||||
/// });
|
||||
///
|
||||
/// view! { cx,
|
||||
/// <button on:click=on_click>"Click me"</button>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn debounce<T: 'static>(
|
||||
cx: Scope,
|
||||
delay: Duration,
|
||||
mut cb: impl FnMut(T) + 'static,
|
||||
) -> impl FnMut(T) {
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
let span = ::tracing::Span::current();
|
||||
let cb = move |value| {
|
||||
let _guard = span.enter();
|
||||
cb(value);
|
||||
};
|
||||
}
|
||||
}
|
||||
let cb = Rc::new(RefCell::new(cb));
|
||||
|
||||
let timer = Rc::new(Cell::new(None::<TimeoutHandle>));
|
||||
|
||||
on_cleanup(cx, {
|
||||
let timer = Rc::clone(&timer);
|
||||
move || {
|
||||
if let Some(timer) = timer.take() {
|
||||
timer.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
move |arg| {
|
||||
if let Some(timer) = timer.take() {
|
||||
timer.clear();
|
||||
}
|
||||
let handle = set_timeout_with_handle(
|
||||
{
|
||||
let cb = Rc::clone(&cb);
|
||||
move || {
|
||||
cb.borrow_mut()(arg);
|
||||
}
|
||||
},
|
||||
delay,
|
||||
);
|
||||
if let Ok(handle) = handle {
|
||||
timer.set(Some(handle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle that is generated by [set_interval] and can be used to clear the interval.
|
||||
|
||||
Reference in New Issue
Block a user