1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use crate::{watch_with_options, WatchOptions};
use leptos::*;

/// Pausable [`watch`].
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/watch_pausable)
///
/// ## Usage
///
/// ```
/// # use leptos::*;
/// # use leptos::logging::log;
/// # use leptos_use::{watch_pausable, WatchPausableReturn};
/// #
/// # pub fn Demo() -> impl IntoView {
/// let (source, set_source) = create_signal("foo".to_string());
///
/// let WatchPausableReturn {
///     stop,
///     pause,
///     resume,
///     ..
/// } = watch_pausable(
///     move || source.get(),
///     |v, _, _| {
///         log!("Changed to {}", v);
///     },
/// );
///
/// set_source.set("bar".to_string()); // > "Changed to bar"
///
/// pause();
///
/// set_source.set("foobar".to_string()); // (nothing happens)
///
/// resume();
///
/// set_source.set("hello".to_string()); // > "Changed to hello"
/// #    view! { }
/// # }
/// ```
///
/// There's also [`watch_pausable_with_options`] which takes the same options as [`watch`].
///
/// ## Server-Side Rendering
///
/// On the server this works just fine except if you throttle or debounce in which case the callback
/// will never be called except if you set `immediate` to `true` in which case the callback will be
/// called exactly once.
///
/// ## See also
///
/// * [`watch`]
pub fn watch_pausable<W, T, DFn, CFn>(
    deps: DFn,
    callback: CFn,
) -> WatchPausableReturn<impl Fn() + Clone, impl Fn() + Clone, impl Fn() + Clone>
where
    DFn: Fn() -> W + 'static,
    CFn: Fn(&W, Option<&W>, Option<T>) -> T + Clone + 'static,
    W: Clone + 'static,
    T: Clone + 'static,
{
    watch_pausable_with_options(deps, callback, WatchOptions::default())
}

/// Version of `watch_pausable` that accepts `WatchOptions`. See [`watch_pausable`] for how to use.
pub fn watch_pausable_with_options<W, T, DFn, CFn>(
    deps: DFn,
    callback: CFn,
    options: WatchOptions,
) -> WatchPausableReturn<impl Fn() + Clone, impl Fn() + Clone, impl Fn() + Clone>
where
    DFn: Fn() -> W + 'static,
    CFn: Fn(&W, Option<&W>, Option<T>) -> T + Clone + 'static,
    W: Clone + 'static,
    T: Clone + 'static,
{
    let (is_active, set_active) = create_signal(true);

    let pausable_callback = move |val: &W, prev_val: Option<&W>, prev_ret: Option<Option<T>>| {
        if is_active.get_untracked() {
            Some(callback(val, prev_val, prev_ret.unwrap_or(None)))
        } else {
            None
        }
    };

    let stop = watch_with_options(deps, pausable_callback, options);

    let pause = move || {
        set_active.set(false);
    };

    let resume = move || {
        set_active.set(true);
    };

    WatchPausableReturn {
        stop,
        pause,
        resume,
        is_active: is_active.into(),
    }
}

/// Return type of [`watch_pausable`]
pub struct WatchPausableReturn<StopFn, PauseFn, ResumeFn>
where
    StopFn: Fn() + Clone,
    PauseFn: Fn() + Clone,
    ResumeFn: Fn() + Clone,
{
    /// Stops the watcher
    pub stop: StopFn,

    /// Pauses the watcher
    pub pause: PauseFn,

    /// Resumes the watcher
    pub resume: ResumeFn,

    /// Whether the watcher is active (not paused). This doesn't reflect if the watcher has been stopped
    pub is_active: Signal<bool>,
}