leptos_use/utils/filters/
throttle.rs

1#![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    /// Invoke on the trailing edge of the timeout. Defaults to `true`.
15    pub trailing: bool,
16    /// Invoke on the leading edge of the timeout (=immediately). Defaults to `true`.
17    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}