mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-01-23 00:59:22 -05:00
added use_throttle_fn
This commit is contained in:
parent
ed8e2ba465
commit
e539181907
12 changed files with 339 additions and 12 deletions
6
.idea/leptos-use.iml
generated
6
.idea/leptos-use.iml
generated
|
@ -1,5 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="CPP_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="Python" name="Python facet">
|
||||
<configuration sdkName="Python 3.9" />
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||
|
@ -10,5 +15,6 @@
|
|||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
|
||||
</component>
|
||||
</module>
|
|
@ -16,3 +16,4 @@ repository = "https://github.com/Synphonyte/leptos-use"
|
|||
leptos = "0.3"
|
||||
web-sys = "0.3"
|
||||
wasm-bindgen = "0.2"
|
||||
js-sys = "0.3"
|
|
@ -10,3 +10,7 @@
|
|||
# Browser
|
||||
|
||||
- [use_event_listener](browser/use_event_listener.md)
|
||||
|
||||
# Utilities
|
||||
|
||||
- [use_throttle_fn](utilities/use_throttle_fn.md)
|
|
@ -6,8 +6,10 @@ def main():
|
|||
file_name = f"../../../../src/{name}.rs"
|
||||
with open(file_name) as f:
|
||||
in_code_block = False
|
||||
doc_comment_started = False
|
||||
for line in f.readlines():
|
||||
if line.startswith("///"):
|
||||
doc_comment_started = True
|
||||
line = line.strip().replace("/// ", "").replace("///", "")
|
||||
if "```" in line:
|
||||
if not in_code_block:
|
||||
|
@ -15,6 +17,8 @@ def main():
|
|||
in_code_block = not in_code_block
|
||||
|
||||
print(line)
|
||||
elif doc_comment_started:
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
3
docs/book/src/utilities/use_throttle_fn.md
Normal file
3
docs/book/src/utilities/use_throttle_fn.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# use_throttle_fn
|
||||
|
||||
<!-- cmdrun python3 ../extract_doc_comment.py use_throttle_fn -->
|
|
@ -1,6 +1,9 @@
|
|||
pub mod core;
|
||||
pub mod use_event_listener;
|
||||
pub mod use_scroll;
|
||||
pub mod use_throttle_fn;
|
||||
pub mod utils;
|
||||
|
||||
pub use use_event_listener::use_event_listener;
|
||||
pub use use_scroll::use_scroll;
|
||||
pub use use_scroll::*;
|
||||
pub use use_throttle_fn::*;
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
use crate::core::EventTargetMaybeSignal;
|
||||
use leptos::ev::EventDescriptor;
|
||||
use leptos::html::ElementDescriptor;
|
||||
use leptos::*;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
use crate::core::EventTargetMaybeSignal;
|
||||
use crate::use_event_listener;
|
||||
use leptos::*;
|
||||
|
||||
// pub fn use_scroll<El, T, Fx, Fy>(
|
||||
// cx: Scope,
|
||||
// element: El,
|
||||
// options: UseScrollOptions,
|
||||
// ) -> UseScrollReturn<Fx, Fy>
|
||||
// where
|
||||
// (Scope, El): Into<EventTargetMaybeSignal<T>>,
|
||||
// T: Into<web_sys::EventTarget> + Clone + 'static,
|
||||
// {
|
||||
// }
|
||||
|
||||
/// Options for [`use_scroll`].
|
||||
#[derive(Default)]
|
||||
pub struct UseScrollOptions {
|
||||
/// Throttle time in milliseconds for the scroll events. Defaults to 0 (disabled).
|
||||
pub throttle: u32,
|
||||
|
@ -8,18 +22,46 @@ pub struct UseScrollOptions {
|
|||
/// After scrolling ends we wait idle + throttle milliseconds before we consider scrolling to have stopped.
|
||||
/// Defaults to 200.
|
||||
pub idle: u32,
|
||||
|
||||
/// Threshold in pixels when we consider a side to have arrived (`UseScrollReturn::arrived_state`).
|
||||
pub offset: ScrollOffset,
|
||||
|
||||
/// Callback when scrolling is happening.
|
||||
pub on_scroll: Option<Box<dyn Fn()>>,
|
||||
|
||||
/// Callback when scrolling stops (after `idle` + `throttle` milliseconds have passed).
|
||||
pub on_stop: Option<Box<dyn Fn()>>,
|
||||
|
||||
/// Options passed to the `addEventListener("scroll", ...)` call
|
||||
pub event_listener_options: Option<web_sys::AddEventListenerOptions>,
|
||||
|
||||
/// When changing the `x` or `y` signals this specifies the scroll behaviour.
|
||||
/// Can be `Auto` (= not smooth) or `Smooth`. Defaults to `Auto`.
|
||||
pub behavior: ScrollBehavior,
|
||||
}
|
||||
|
||||
pub struct UseScrollReturn {
|
||||
pub x: ReadSignal<f64>,
|
||||
pub setX: WriteSignal<f64>,
|
||||
pub y: ReadSignal<f64>,
|
||||
pub setY: WriteSignal<f64>,
|
||||
pub isScrolling: ReadSignal<bool>,
|
||||
pub arrivedState: ReadSignal<Directions>,
|
||||
pub directions: ReadSignal<Directions>,
|
||||
#[derive(Default)]
|
||||
pub enum ScrollBehavior {
|
||||
#[default]
|
||||
Auto,
|
||||
Smooth,
|
||||
}
|
||||
|
||||
pub struct UseScrollReturn<Fx, Fy>
|
||||
where
|
||||
Fx: FnMut(f64),
|
||||
Fy: FnMut(f64),
|
||||
{
|
||||
pub x: Signal<f64>,
|
||||
pub set_x: Fx,
|
||||
pub y: Signal<f64>,
|
||||
pub set_y: Fy,
|
||||
pub is_scrolling: Signal<bool>,
|
||||
pub arrived_state: Signal<Directions>,
|
||||
pub directions: Signal<Directions>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Directions {
|
||||
pub left: bool,
|
||||
pub right: bool,
|
||||
|
@ -27,4 +69,10 @@ pub struct Directions {
|
|||
pub bottom: bool,
|
||||
}
|
||||
|
||||
pub fn use_scroll() {}
|
||||
#[derive(Default)]
|
||||
pub struct ScrollOffset {
|
||||
pub left: f64,
|
||||
pub top: f64,
|
||||
pub right: f64,
|
||||
pub bottom: f64,
|
||||
}
|
||||
|
|
116
src/use_throttle_fn.rs
Normal file
116
src/use_throttle_fn.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use crate::utils::{create_filter_wrapper, create_filter_wrapper_with_arg, throttle_filter};
|
||||
use leptos::MaybeSignal;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub use crate::utils::ThrottleOptions;
|
||||
|
||||
/// Throttle execution of a function.
|
||||
/// Especially useful for rate limiting execution of handlers on events like resize and scroll.
|
||||
///
|
||||
/// > Throttle is a spring that throws balls: after a ball flies out it needs some time to shrink back, so it cannot throw any more balls unless it's ready.
|
||||
///
|
||||
/// ## Demo
|
||||
///
|
||||
/// TODO
|
||||
///
|
||||
/// ## 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>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// You can provide options when you use [`use_throttle_fn_with_options`].
|
||||
///
|
||||
/// ```
|
||||
/// # use leptos::*;
|
||||
/// # use leptos_use::{ThrottleOptions, use_throttle_fn_with_options};
|
||||
///
|
||||
/// # #[component]
|
||||
/// # fn Demo(cx: Scope) -> impl IntoView {
|
||||
/// let throttled_fn = use_throttle_fn_with_options(
|
||||
/// || {
|
||||
/// // do something, it will be called at most 1 time per second
|
||||
/// },
|
||||
/// 1000.0,
|
||||
/// ThrottleOptions {
|
||||
/// leading: true,
|
||||
/// trailing: true,
|
||||
/// }
|
||||
/// );
|
||||
/// # view! { cx, }
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// If your function that you want to throttle takes an argument there are also the versions
|
||||
/// [`use_throttle_fn_with_args`] and [`use_throttle_fn_with_args_and_options`].
|
||||
///
|
||||
/// ## Recommended Reading
|
||||
///
|
||||
/// - [**Debounce vs Throttle**: Definitive Visual Guide](https://redd.one/blog/debounce-vs-throttle)
|
||||
pub fn use_throttle_fn<F, R>(
|
||||
func: F,
|
||||
ms: impl Into<MaybeSignal<f64>>,
|
||||
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
|
||||
where
|
||||
F: FnMut() -> R + Clone + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
use_throttle_fn_with_options(func, ms, Default::default())
|
||||
}
|
||||
|
||||
/// Version of [`use_throttle_fn`] with throttle options. See the docs for [`use_throttle_fn`] for how to use.
|
||||
pub fn use_throttle_fn_with_options<F, R>(
|
||||
func: F,
|
||||
ms: impl Into<MaybeSignal<f64>>,
|
||||
options: ThrottleOptions,
|
||||
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
|
||||
where
|
||||
F: FnMut() -> R + Clone + 'static,
|
||||
R: 'static,
|
||||
{
|
||||
create_filter_wrapper(throttle_filter(ms, options), func)
|
||||
}
|
||||
|
||||
/// Version of [`use_throttle_fn`] with an argument for the throttled function. See the docs for [`use_throttle_fn`] for how to use.
|
||||
pub fn use_throttle_fn_with_arg<F, Arg, R>(
|
||||
func: F,
|
||||
ms: impl Into<MaybeSignal<f64>>,
|
||||
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
|
||||
where
|
||||
F: FnMut(Arg) -> R + Clone + 'static,
|
||||
Arg: 'static,
|
||||
R: 'static,
|
||||
{
|
||||
use_throttle_fn_with_arg_and_options(func, ms, Default::default())
|
||||
}
|
||||
|
||||
/// Version of [`use_throttle_fn_with_arg`] with throttle options. See the docs for [`use_throttle_fn`] for how to use.
|
||||
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>>>
|
||||
where
|
||||
F: FnMut(Arg) -> R + Clone + 'static,
|
||||
Arg: 'static,
|
||||
R: 'static,
|
||||
{
|
||||
create_filter_wrapper_with_arg(throttle_filter(ms, options), func)
|
||||
}
|
136
src/utils/filters.rs
Normal file
136
src/utils/filters.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
use js_sys::Date;
|
||||
use leptos::leptos_dom::helpers::TimeoutHandle;
|
||||
use leptos::{set_timeout_with_handle, MaybeSignal, Scope, SignalGetUntracked};
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cmp::max;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn create_filter_wrapper<F, R, Filter>(
|
||||
mut filter: Filter,
|
||||
func: F,
|
||||
) -> impl FnMut() -> Rc<RefCell<Option<R>>>
|
||||
where
|
||||
F: FnMut() -> R + Clone + 'static,
|
||||
R: 'static,
|
||||
Filter: FnMut(Box<dyn FnOnce() -> R>) -> Rc<RefCell<Option<R>>>,
|
||||
{
|
||||
move || {
|
||||
let wrapped_func = Box::new(func.clone());
|
||||
filter(wrapped_func)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_filter_wrapper_with_arg<F, Arg, R, Filter>(
|
||||
mut filter: Filter,
|
||||
func: F,
|
||||
) -> impl FnMut(Arg) -> Rc<RefCell<Option<R>>>
|
||||
where
|
||||
F: FnMut(Arg) -> R + Clone + 'static,
|
||||
R: 'static,
|
||||
Arg: 'static,
|
||||
Filter: FnMut(Box<dyn FnOnce() -> R>) -> Rc<RefCell<Option<R>>>,
|
||||
{
|
||||
move |arg: Arg| {
|
||||
let mut func = func.clone();
|
||||
let wrapped_func = Box::new(move || func(arg));
|
||||
filter(wrapped_func)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ThrottleOptions {
|
||||
pub trailing: bool,
|
||||
pub leading: bool,
|
||||
}
|
||||
|
||||
impl Default for ThrottleOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
trailing: true,
|
||||
leading: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn throttle_filter<R>(
|
||||
ms: impl Into<MaybeSignal<f64>>,
|
||||
options: ThrottleOptions,
|
||||
) -> impl FnMut(Box<dyn FnOnce() -> R>) -> Rc<RefCell<Option<R>>>
|
||||
where
|
||||
R: 'static,
|
||||
{
|
||||
let last_exec = Rc::new(Cell::new(0_f64));
|
||||
let timer = Rc::new(Cell::new(None::<TimeoutHandle>));
|
||||
let is_leading = Rc::new(Cell::new(true));
|
||||
let last_value: Rc<RefCell<Option<R>>> = Rc::new(RefCell::new(None));
|
||||
|
||||
let t = Rc::clone(&timer);
|
||||
let clear = move || {
|
||||
if let Some(handle) = t.get() {
|
||||
handle.clear();
|
||||
t.set(None);
|
||||
}
|
||||
};
|
||||
|
||||
let ms = ms.into();
|
||||
|
||||
move |mut _invoke: Box<dyn FnOnce() -> R>| {
|
||||
let duration = ms.get_untracked();
|
||||
let elapsed = Date::now() - last_exec.get();
|
||||
|
||||
let last_val = Rc::clone(&last_value);
|
||||
let mut invoke = move || {
|
||||
let return_value = _invoke();
|
||||
|
||||
let mut val_mut = last_val.borrow_mut();
|
||||
*val_mut = Some(return_value);
|
||||
};
|
||||
|
||||
let clear = clear.clone();
|
||||
clear();
|
||||
|
||||
if duration <= 0.0 {
|
||||
last_exec.set(Date::now());
|
||||
invoke();
|
||||
return Rc::clone(&last_value);
|
||||
}
|
||||
|
||||
if elapsed > duration && (options.leading || !is_leading.get()) {
|
||||
last_exec.set(Date::now());
|
||||
invoke();
|
||||
} else if options.trailing {
|
||||
let last_exec = Rc::clone(&last_exec);
|
||||
let is_leading = Rc::clone(&is_leading);
|
||||
timer.set(
|
||||
set_timeout_with_handle(
|
||||
move || {
|
||||
last_exec.set(Date::now());
|
||||
is_leading.set(true);
|
||||
invoke();
|
||||
clear();
|
||||
},
|
||||
Duration::from_millis(max(0, (duration - elapsed) as u64)),
|
||||
)
|
||||
.ok(),
|
||||
);
|
||||
}
|
||||
|
||||
if !options.leading && timer.get().is_none() {
|
||||
let is_leading = Rc::clone(&is_leading);
|
||||
timer.set(
|
||||
set_timeout_with_handle(
|
||||
move || {
|
||||
is_leading.set(true);
|
||||
},
|
||||
Duration::from_millis(duration as u64),
|
||||
)
|
||||
.ok(),
|
||||
);
|
||||
}
|
||||
|
||||
is_leading.set(false);
|
||||
|
||||
Rc::clone(&last_value)
|
||||
}
|
||||
}
|
3
src/utils/is.rs
Normal file
3
src/utils/is.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub fn noop() -> Box<dyn FnMut()> {
|
||||
Box::new(|| {})
|
||||
}
|
5
src/utils/mod.rs
Normal file
5
src/utils/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod filters;
|
||||
mod is;
|
||||
|
||||
pub use filters::*;
|
||||
pub use is::*;
|
Loading…
Add table
Reference in a new issue