mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-02-08 21:33:09 -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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="CPP_MODULE" version="4">
|
<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">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||||
|
@ -10,5 +15,6 @@
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
|
@ -16,3 +16,4 @@ repository = "https://github.com/Synphonyte/leptos-use"
|
||||||
leptos = "0.3"
|
leptos = "0.3"
|
||||||
web-sys = "0.3"
|
web-sys = "0.3"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
|
js-sys = "0.3"
|
|
@ -10,3 +10,7 @@
|
||||||
# Browser
|
# Browser
|
||||||
|
|
||||||
- [use_event_listener](browser/use_event_listener.md)
|
- [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"
|
file_name = f"../../../../src/{name}.rs"
|
||||||
with open(file_name) as f:
|
with open(file_name) as f:
|
||||||
in_code_block = False
|
in_code_block = False
|
||||||
|
doc_comment_started = False
|
||||||
for line in f.readlines():
|
for line in f.readlines():
|
||||||
if line.startswith("///"):
|
if line.startswith("///"):
|
||||||
|
doc_comment_started = True
|
||||||
line = line.strip().replace("/// ", "").replace("///", "")
|
line = line.strip().replace("/// ", "").replace("///", "")
|
||||||
if "```" in line:
|
if "```" in line:
|
||||||
if not in_code_block:
|
if not in_code_block:
|
||||||
|
@ -15,6 +17,8 @@ def main():
|
||||||
in_code_block = not in_code_block
|
in_code_block = not in_code_block
|
||||||
|
|
||||||
print(line)
|
print(line)
|
||||||
|
elif doc_comment_started:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
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 core;
|
||||||
pub mod use_event_listener;
|
pub mod use_event_listener;
|
||||||
pub mod use_scroll;
|
pub mod use_scroll;
|
||||||
|
pub mod use_throttle_fn;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
pub use use_event_listener::use_event_listener;
|
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 crate::core::EventTargetMaybeSignal;
|
||||||
use leptos::ev::EventDescriptor;
|
use leptos::ev::EventDescriptor;
|
||||||
use leptos::html::ElementDescriptor;
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ops::Deref;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use wasm_bindgen::closure::Closure;
|
use wasm_bindgen::closure::Closure;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
|
|
|
@ -1,6 +1,20 @@
|
||||||
|
use crate::core::EventTargetMaybeSignal;
|
||||||
use crate::use_event_listener;
|
use crate::use_event_listener;
|
||||||
use leptos::*;
|
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 {
|
pub struct UseScrollOptions {
|
||||||
/// Throttle time in milliseconds for the scroll events. Defaults to 0 (disabled).
|
/// Throttle time in milliseconds for the scroll events. Defaults to 0 (disabled).
|
||||||
pub throttle: u32,
|
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.
|
/// After scrolling ends we wait idle + throttle milliseconds before we consider scrolling to have stopped.
|
||||||
/// Defaults to 200.
|
/// Defaults to 200.
|
||||||
pub idle: u32,
|
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 {
|
#[derive(Default)]
|
||||||
pub x: ReadSignal<f64>,
|
pub enum ScrollBehavior {
|
||||||
pub setX: WriteSignal<f64>,
|
#[default]
|
||||||
pub y: ReadSignal<f64>,
|
Auto,
|
||||||
pub setY: WriteSignal<f64>,
|
Smooth,
|
||||||
pub isScrolling: ReadSignal<bool>,
|
|
||||||
pub arrivedState: ReadSignal<Directions>,
|
|
||||||
pub directions: ReadSignal<Directions>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 struct Directions {
|
||||||
pub left: bool,
|
pub left: bool,
|
||||||
pub right: bool,
|
pub right: bool,
|
||||||
|
@ -27,4 +69,10 @@ pub struct Directions {
|
||||||
pub bottom: bool,
|
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