added use_debounce_fn

This commit is contained in:
Maccesch 2023-05-26 19:27:42 +01:00
parent 5057bf0765
commit bbe145a42b
18 changed files with 256 additions and 87 deletions

2
.idea/leptos-use.iml generated
View file

@ -11,12 +11,14 @@
<sourceFolder url="file://$MODULE_DIR$/examples/use_event_listener/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/use_throttle_fn/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples/use_debounce_fn/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/examples/use_event_listener/target" />
<excludeFolder url="file://$MODULE_DIR$/target" />
<excludeFolder url="file://$MODULE_DIR$/docs/book/book" />
<excludeFolder url="file://$MODULE_DIR$/examples/use_event_listener/dist" />
<excludeFolder url="file://$MODULE_DIR$/examples/use_throttle_fn/dist" />
<excludeFolder url="file://$MODULE_DIR$/examples/use_throttle_fn/target" />
<excludeFolder url="file://$MODULE_DIR$/examples/use_debounce_fn/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View file

@ -2,4 +2,9 @@
## 0.1.3
- Added `use_scroll`.
#### New Functions
- `use_scroll`
- `use_debounce_fn`
#### Other Changes
- Better and more beautiful demo integration into the guide.

View file

@ -1,6 +1,7 @@
# Summary
[Introduction]()
[Changelog](changelog.md)
# Getting Started
@ -13,4 +14,5 @@
# Utilities
- [use_debounce_fn](utilities/use_debounce_fn.md)
- [use_throttle_fn](utilities/use_throttle_fn.md)

View file

@ -0,0 +1 @@
{{#include ../../../CHANGELOG.md}}

View file

@ -0,0 +1,3 @@
# use_debounce_fn
<!-- cmdrun python3 ../extract_doc_comment.py use_debounce_fn -->

View file

@ -0,0 +1,16 @@
[package]
name = "use_debounce_fn"
version = "0.1.0"
edition = "2021"
[dependencies]
leptos = "0.3"
console_error_panic_hook = "0.1"
console_log = "1"
log = "0.4"
leptos-use = { path = "../.." }
web-sys = "0.3"
[dev-dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.0"

View file

@ -0,0 +1,16 @@
A simple example for `use_debounce_fn`.
If you don't have it installed already, install [Trunk](https://trunkrs.dev/)
as well as the nightly toolchain for Rust and the wasm32-unknown-unknown target:
```bash
cargo install trunk
rustup toolchain install nightly
rustup target add wasm32-unknown-unknown
```
To run the demo:
```bash
trunk serve --open
```

View file

@ -0,0 +1,2 @@
[build]
public_url = "./demo/"

View file

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html>
<head></head>
<body></body>
</html>

View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View file

@ -0,0 +1,40 @@
use leptos::*;
use leptos_use::use_debounce_fn_with_options;
use leptos_use::utils::{demo_or_body, DebounceOptions};
#[component]
fn Demo(cx: Scope) -> impl IntoView {
let (click_count, set_click_count) = create_signal(cx, 0);
let (debounced_count, set_debounced_count) = create_signal(cx, 0);
let debounced_fn = use_debounce_fn_with_options(
move || set_debounced_count(debounced_count() + 1),
1000.0,
DebounceOptions {
max_wait: Some(5000.0),
},
);
view! { cx,
<button
on:click=move |_| {
set_click_count(click_count() + 1);
debounced_fn();
}
>
"Smash me!"
</button>
<div class="note">"Delay is set to 1000ms and max_wait is set to 5000ms for this demo."</div>
<p>"Button clicked: " { click_count }</p>
<p>"Event handler called: " { debounced_count }</p>
}
}
fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
mount_to(demo_or_body(), |cx| {
view! { cx, <Demo /> }
})
}

View file

@ -7,8 +7,7 @@ fn Demo(cx: Scope) -> impl IntoView {
let (click_count, set_click_count) = create_signal(cx, 0);
let (throttled_count, set_throttled_count) = create_signal(cx, 0);
let mut throttled_fn =
use_throttle_fn(move || set_throttled_count(throttled_count() + 1), 1000.0);
let throttled_fn = use_throttle_fn(move || set_throttled_count(throttled_count() + 1), 1000.0);
view! { cx,
<button

View file

@ -3,40 +3,108 @@ use crate::utils::{
};
use leptos::MaybeSignal;
pub fn use_debounce_fn<F>(func: F, ms: impl Into<MaybeSignal<f64>>) -> impl FnMut()
/// Debounce execution of a function.
///
/// > Debounce is an overloaded waiter: If you keep asking him your requests will be ignored until you stop and give him some time to think about your latest inquiry.
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/master/examples/use_debounce_fn)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos::ev::resize;
/// # use leptos_use::use_debounce_fn;
/// #
/// # #[component]
/// # fn Demo(cx: Scope) -> impl IntoView {
/// let mut debounced_fn = use_debounce_fn(
/// || {
/// // do something
/// },
/// 1000.0,
/// );
///
/// window_event_listener(resize, move |_| debounced_fn());
/// # view! { cx, }
/// # }
/// ```
///
/// You can also pass options to [`use_debounce_fn_with_options`] with a maximum wait time, similar to
/// [lodash debounce](https://lodash.com/docs/#debounce).
///
/// ```
/// # use leptos::*;
/// # use leptos::ev::resize;
/// # use leptos_use::use_debounce_fn_with_options;
/// # use leptos_use::utils::DebounceOptions;
/// #
/// # #[component]
/// # fn Demo(cx: Scope) -> impl IntoView {
/// let mut debounced_fn = use_debounce_fn_with_options(
/// || {
/// // do something
/// },
/// 1000.0,
/// DebounceOptions {
/// max_wait: Some(5000.0),
/// }
/// );
///
/// window_event_listener(resize, move |_| debounced_fn());
/// # view! { cx, }
/// # }
/// ```
///
/// Currently there is no way to use a function with a return value. Please open an issue if you need this.
///
/// If you want to throttle a function that takes an argument there are also the versions
/// [`use_debounce_fn_with_args`] and [`use_debounce_fn_with_args_and_options`].
///
/// ## Recommended Reading
///
/// - [**Debounce vs Throttle**: Definitive Visual Guide](https://redd.one/blog/debounce-vs-throttle)
pub fn use_debounce_fn<F>(func: F, ms: impl Into<MaybeSignal<f64>>) -> impl Fn()
where
F: FnOnce() + Clone + 'static,
{
use_debounce_fn_with_options(func, ms, Default::default())
use_debounce_fn_with_options(func, ms, DebounceOptions::<Option<f64>>::default())
}
pub fn use_debounce_fn_with_options<F>(
/// Version of [`use_debounce_fn`] with debounce options. See the docs for [`use_debounce_fn`] for how to use.
pub fn use_debounce_fn_with_options<F, W>(
func: F,
ms: impl Into<MaybeSignal<f64>>,
options: DebounceOptions,
) -> impl FnMut()
options: DebounceOptions<W>,
) -> impl Fn()
where
F: FnOnce() + Clone + 'static,
W: Into<MaybeSignal<Option<f64>>>,
{
create_filter_wrapper(debounce_filter(ms, options), func)
}
pub fn use_debounce_fn_with_arg<F, Arg>(func: F, ms: impl Into<MaybeSignal<f64>>) -> impl FnMut(Arg)
/// Version of [`use_debounce_fn`] with an argument for the debounced function. See the docs for [`use_debounce_fn`] for how to use.
pub fn use_debounce_fn_with_arg<F, Arg>(func: F, ms: impl Into<MaybeSignal<f64>>) -> impl Fn(Arg)
where
F: FnOnce(Arg) + Clone + 'static,
Arg: Clone + 'static,
{
use_debounce_fn_with_arg_and_options(func, ms, Default::default())
use_debounce_fn_with_arg_and_options(func, ms, DebounceOptions::<Option<f64>>::default())
}
pub fn use_debounce_fn_with_arg_and_options<F, Arg>(
/// Version of [`use_debounce_fn_with_arg`] with debounce options. See the docs for [`use_debounce_fn`] for how to use.
pub fn use_debounce_fn_with_arg_and_options<F, Arg, W>(
func: F,
ms: impl Into<MaybeSignal<f64>>,
options: DebounceOptions,
) -> impl FnMut(Arg)
options: DebounceOptions<W>,
) -> impl Fn(Arg)
where
F: FnOnce(Arg) + Clone + 'static,
Arg: Clone + 'static,
W: Into<MaybeSignal<Option<f64>>>,
{
create_filter_wrapper_with_arg(debounce_filter(ms, options), func)
}

View file

@ -13,47 +13,46 @@ use wasm_bindgen::JsCast;
/// ## Usage
///
/// ```
/// use leptos::*;
/// use leptos::ev::visibilitychange;
/// use leptos_use::use_event_listener;
///
/// #[component]
/// fn Demo(cx: Scope) -> impl IntoView {
/// use_event_listener(cx, document(), visibilitychange, |evt| {
/// log!("{:?}", evt);
/// });
///
/// view! { cx, }
/// }
/// # use leptos::*;
/// # use leptos::ev::visibilitychange;
/// # use leptos_use::use_event_listener;
/// #
/// # #[component]
/// # fn Demo(cx: Scope) -> impl IntoView {
/// use_event_listener(cx, document(), visibilitychange, |evt| {
/// log!("{:?}", evt);
/// });
/// # view! { cx, }
/// # }
/// ```
///
/// You can also pass a [`NodeRef`](https://docs.rs/leptos/latest/leptos/struct.NodeRef.html) as the event target, [`use_event_listener`] will unregister the previous event and register
/// the new one when you change the target.
///
/// ```
/// use leptos::*;
/// use leptos::ev::click;
/// use leptos_use::use_event_listener;
/// # use leptos::*;
/// # use leptos::ev::click;
/// # use leptos_use::use_event_listener;
/// #
/// # #[component]
/// # fn Demo(cx: Scope) -> impl IntoView {
/// let element = create_node_ref(cx);
///
/// #[component]
/// fn Demo(cx: Scope) -> impl IntoView {
/// let element = create_node_ref(cx);
/// use_event_listener(cx, element, click, |evt| {
/// log!("click from element {:?}", event_target::<web_sys::HtmlDivElement>(&evt));
/// });
///
/// use_event_listener(cx, element, click, |evt| {
/// log!("click from element {:?}", event_target::<web_sys::HtmlDivElement>(&evt));
/// });
/// let (cond, set_cond) = create_signal(cx, true);
///
/// let (cond, set_cond) = create_signal(cx, true);
///
/// view! { cx,
/// <Show
/// when=move || cond()
/// fallback=move |cx| view! { cx, <div node_ref=element>"Condition false"</div> }
/// >
/// <div node_ref=element>"Condition true"</div>
/// </Show>
/// }
/// view! { cx,
/// <Show
/// when=move || cond()
/// fallback=move |cx| view! { cx, <div node_ref=element>"Condition false"</div> }
/// >
/// <div node_ref=element>"Condition true"</div>
/// </Show>
/// }
/// # }
/// ```
///
/// You can also call the returned to unregister the listener.

View file

@ -19,23 +19,23 @@ pub use crate::utils::ThrottleOptions;
/// ## Usage
///
/// ```
/// use leptos::*;
/// use leptos_use::use_throttle_fn;
///
/// #[component]
/// fn Demo(cx: Scope) -> impl IntoView {
/// let mut throttled_fn = use_throttle_fn(
/// || {
/// // do something, it will be called at most 1 time per second
/// },
/// 1000.0,
/// );
/// view! { cx,
/// <button on:click=move |_| { throttled_fn(); }>
/// "Smash me!"
/// </button>
/// }
/// # use leptos::*;
/// # use leptos_use::use_throttle_fn;
/// #
/// # #[component]
/// # fn Demo(cx: Scope) -> impl IntoView {
/// let mut throttled_fn = use_throttle_fn(
/// || {
/// // do something, it will be called at most 1 time per second
/// },
/// 1000.0,
/// );
/// view! { cx,
/// <button on:click=move |_| { throttled_fn(); }>
/// "Smash me!"
/// </button>
/// }
/// # }
/// ```
///
/// You can provide options when you use [`use_throttle_fn_with_options`].
@ -68,7 +68,7 @@ pub use crate::utils::ThrottleOptions;
pub fn use_throttle_fn<F, R>(
func: F,
ms: impl Into<MaybeSignal<f64>>,
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
) -> impl Fn() -> Rc<RefCell<Option<R>>>
where
F: FnOnce() -> R + Clone + 'static,
R: 'static,
@ -81,7 +81,7 @@ pub fn use_throttle_fn_with_options<F, R>(
func: F,
ms: impl Into<MaybeSignal<f64>>,
options: ThrottleOptions,
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
) -> impl Fn() -> Rc<RefCell<Option<R>>>
where
F: FnOnce() -> R + Clone + 'static,
R: 'static,
@ -93,7 +93,7 @@ where
pub fn use_throttle_fn_with_arg<F, Arg, R>(
func: F,
ms: impl Into<MaybeSignal<f64>>,
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>>
where
F: FnOnce(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,
@ -107,7 +107,7 @@ pub fn use_throttle_fn_with_arg_and_options<F, Arg, R>(
func: F,
ms: impl Into<MaybeSignal<f64>>,
options: ThrottleOptions,
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>>
where
F: FnOnce(Arg) -> R + Clone + 'static,
Arg: Clone + 'static,

View file

@ -5,17 +5,28 @@ use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::time::Duration;
#[derive(Default)]
pub struct DebounceOptions {
pub struct DebounceOptions<W>
where
W: Into<MaybeSignal<Option<f64>>>,
{
/// The maximum time allowed to be delayed before it's invoked.
/// In milliseconds.
max_wait: MaybeSignal<Option<f64>>,
pub max_wait: W,
}
pub fn debounce_filter(
impl Default for DebounceOptions<Option<f64>> {
fn default() -> Self {
Self { max_wait: None }
}
}
pub fn debounce_filter<W>(
ms: impl Into<MaybeSignal<f64>>,
options: DebounceOptions,
) -> impl FnMut(Box<dyn CloneableFnWithReturn<()>>) -> Rc<RefCell<Option<()>>> {
options: DebounceOptions<W>,
) -> impl Fn(Box<dyn CloneableFnWithReturn<()>>) -> Rc<RefCell<Option<()>>>
where
W: Into<MaybeSignal<Option<f64>>>,
{
let timer = Rc::new(Cell::new(None::<TimeoutHandle>));
let max_timer = Rc::new(Cell::new(None::<TimeoutHandle>));
@ -27,10 +38,11 @@ pub fn debounce_filter(
};
let ms = ms.into();
let max_wait_signal = options.max_wait.into();
move |invoke: Box<dyn CloneableFnWithReturn<()>>| {
let duration = ms.get_untracked();
let max_duration = options.max_wait.get_untracked();
let max_duration = max_wait_signal.get_untracked();
// TODO : return value like throttle_filter?

View file

@ -8,55 +8,52 @@ use crate::utils::CloneableFnWithReturn;
use std::cell::RefCell;
use std::rc::Rc;
pub fn create_filter_wrapper<F, Filter>(mut filter: Filter, func: F) -> impl FnMut()
pub fn create_filter_wrapper<F, Filter>(filter: Filter, func: F) -> impl Fn()
where
F: FnOnce() + Clone + 'static,
Filter: FnMut(Box<dyn CloneableFnWithReturn<()>>) -> Rc<RefCell<Option<()>>>,
Filter: Fn(Box<dyn CloneableFnWithReturn<()>>) -> Rc<RefCell<Option<()>>>,
{
move || {
filter(Box::new(func.clone()));
}
}
pub fn create_filter_wrapper_with_arg<F, Arg, Filter>(
mut filter: Filter,
func: F,
) -> impl FnMut(Arg)
pub fn create_filter_wrapper_with_arg<F, Arg, Filter>(filter: Filter, func: F) -> impl Fn(Arg)
where
F: FnOnce(Arg) + Clone + 'static,
Arg: Clone + 'static,
Filter: FnMut(Box<dyn CloneableFnWithReturn<()>>) -> Rc<RefCell<Option<()>>>,
Filter: Fn(Box<dyn CloneableFnWithReturn<()>>) -> Rc<RefCell<Option<()>>>,
{
move |arg: Arg| {
let mut func = func.clone();
let func = func.clone();
filter(Box::new(move || func(arg)));
}
}
pub fn create_filter_wrapper_with_return<F, R, Filter>(
mut filter: Filter,
filter: Filter,
func: F,
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
) -> impl Fn() -> Rc<RefCell<Option<R>>>
where
F: FnOnce() -> R + Clone + 'static,
R: 'static,
Filter: FnMut(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>>,
Filter: Fn(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>>,
{
move || filter(Box::new(func.clone()))
}
pub fn create_filter_wrapper_with_return_and_arg<F, Arg, R, Filter>(
mut filter: Filter,
filter: Filter,
func: F,
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
) -> impl Fn(Arg) -> Rc<RefCell<Option<R>>>
where
F: FnOnce(Arg) -> R + Clone + 'static,
R: 'static,
Arg: Clone + 'static,
Filter: FnMut(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>>,
Filter: Fn(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>>,
{
move |arg: Arg| {
let mut func = func.clone();
let func = func.clone();
filter(Box::new(move || func(arg)))
}
}

View file

@ -25,7 +25,7 @@ impl Default for ThrottleOptions {
pub fn throttle_filter<R>(
ms: impl Into<MaybeSignal<f64>>,
options: ThrottleOptions,
) -> impl FnMut(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>>
) -> impl Fn(Box<dyn CloneableFnWithReturn<R>>) -> Rc<RefCell<Option<R>>>
where
R: 'static,
{