leptos_use/utils/filters/
throttle.rs1#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
2
3use crate::core::now;
4use cfg_if::cfg_if;
5use default_struct_builder::DefaultBuilder;
6use leptos::leptos_dom::helpers::TimeoutHandle;
7use leptos::prelude::*;
8use std::cmp::max;
9use std::sync::{atomic::AtomicBool, Arc, Mutex};
10use std::time::Duration;
11
12#[derive(Copy, Clone, DefaultBuilder)]
13pub struct ThrottleOptions {
14 pub trailing: bool,
16 pub leading: bool,
18}
19
20impl Default for ThrottleOptions {
21 fn default() -> Self {
22 Self {
23 trailing: true,
24 leading: true,
25 }
26 }
27}
28
29pub fn throttle_filter<R>(
30 ms: impl Into<Signal<f64>>,
31 options: ThrottleOptions,
32) -> impl Fn(Arc<dyn Fn() -> R>) -> Arc<Mutex<Option<R>>> + Clone
33where
34 R: 'static,
35{
36 let last_exec = Arc::new(Mutex::new(0_f64));
37 let timer = Arc::new(Mutex::new(None::<TimeoutHandle>));
38 let is_leading = Arc::new(AtomicBool::new(true));
39 let last_return_value: Arc<Mutex<Option<R>>> = Arc::new(Mutex::new(None));
40
41 let t = Arc::clone(&timer);
42 let clear = move || {
43 let mut t = t.lock().unwrap();
44 if let Some(handle) = *t {
45 handle.clear();
46 *t = None;
47 }
48 };
49
50 on_cleanup(clear.clone());
51
52 let ms = ms.into();
53
54 move |mut _invoke: Arc<dyn Fn() -> R>| {
55 let duration = ms.get_untracked();
56 let elapsed = now() - *last_exec.lock().unwrap();
57
58 let last_return_val = Arc::clone(&last_return_value);
59 let invoke = move || {
60 #[cfg(debug_assertions)]
61 let zone = leptos::reactive::diagnostics::SpecialNonReactiveZone::enter();
62
63 let return_value = _invoke();
64
65 #[cfg(debug_assertions)]
66 drop(zone);
67
68 let mut val_mut = last_return_val.lock().unwrap();
69 *val_mut = Some(return_value);
70 };
71
72 let clear = clear.clone();
73 clear();
74
75 if duration <= 0.0 {
76 *last_exec.lock().unwrap() = now();
77 invoke();
78 return Arc::clone(&last_return_value);
79 }
80
81 if elapsed > duration
82 && (options.leading || !is_leading.load(std::sync::atomic::Ordering::Relaxed))
83 {
84 *last_exec.lock().unwrap() = now();
85 invoke();
86 } else if options.trailing {
87 cfg_if! { if #[cfg(not(feature = "ssr"))] {
88 let last_exec = Arc::clone(&last_exec);
89 let is_leading = Arc::clone(&is_leading);
90 *timer.lock().unwrap() =
91 set_timeout_with_handle(
92 move || {
93 *last_exec.lock().unwrap() = now();
94 is_leading.store(true, std::sync::atomic::Ordering::Relaxed);
95 invoke();
96 clear();
97 },
98 Duration::from_millis(max(0, (duration - elapsed) as u64)),
99 )
100 .ok();
101 }}
102 }
103
104 cfg_if! { if #[cfg(not(feature = "ssr"))] {
105 let mut timer = timer.lock().unwrap();
106
107 if !options.leading && timer.is_none() {
108 let is_leading = Arc::clone(&is_leading);
109 *timer = set_timeout_with_handle(
110 move || {
111 is_leading.store(true, std::sync::atomic::Ordering::Relaxed);
112 },
113 Duration::from_millis(duration as u64),
114 )
115 .ok();
116 }
117 }}
118
119 is_leading.store(false, std::sync::atomic::Ordering::Relaxed);
120
121 Arc::clone(&last_return_value)
122 }
123}