2023-06-08 23:52:14 +01:00
|
|
|
use crate::utils::{create_filter_wrapper, FilterOptions};
|
2023-06-08 01:19:36 +01:00
|
|
|
use crate::{filter_builder_methods, DebounceOptions, ThrottleOptions};
|
|
|
|
use default_struct_builder::DefaultBuilder;
|
2023-05-29 01:52:03 +01:00
|
|
|
use leptos::*;
|
|
|
|
use std::cell::RefCell;
|
2023-06-08 01:19:36 +01:00
|
|
|
use std::rc::Rc;
|
2023-05-29 01:52:03 +01:00
|
|
|
|
|
|
|
/// A version of `create_effect` that listens to any dependency that is accessed inside `deps`.
|
|
|
|
/// Also a stop handler is returned.
|
|
|
|
/// The return value of `deps` is passed into `callback` as an argument together with the previous value
|
|
|
|
/// and the previous value that the `callback` itself returned last time.
|
|
|
|
///
|
2023-06-08 23:52:14 +01:00
|
|
|
/// ## Usage
|
2023-05-29 01:52:03 +01:00
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use std::time::Duration;
|
|
|
|
/// # use leptos::*;
|
|
|
|
/// # use leptos_use::watch;
|
|
|
|
/// #
|
|
|
|
/// # pub fn Demo(cx: Scope) -> impl IntoView {
|
|
|
|
/// let (num, set_num) = create_signal(cx, 0);
|
|
|
|
///
|
|
|
|
/// let stop = watch(
|
|
|
|
/// cx,
|
|
|
|
/// num,
|
|
|
|
/// move |num, _, _| {
|
2023-06-09 23:10:33 +01:00
|
|
|
/// log!("Number {}", num);
|
2023-05-29 01:52:03 +01:00
|
|
|
/// },
|
2023-06-08 23:52:14 +01:00
|
|
|
/// );
|
2023-05-29 01:52:03 +01:00
|
|
|
///
|
2023-06-09 23:10:33 +01:00
|
|
|
/// set_num(1); // > "Number 1"
|
2023-05-29 01:52:03 +01:00
|
|
|
///
|
|
|
|
/// set_timeout_with_handle(move || {
|
|
|
|
/// stop(); // stop watching
|
|
|
|
///
|
2023-06-09 23:10:33 +01:00
|
|
|
/// set_num(2); // (nothing happens)
|
2023-05-29 01:52:03 +01:00
|
|
|
/// }, Duration::from_millis(1000));
|
|
|
|
/// # view! { cx, }
|
|
|
|
/// # }
|
|
|
|
/// ```
|
2023-06-08 01:19:36 +01:00
|
|
|
///
|
|
|
|
/// ## Immediate
|
|
|
|
///
|
2023-06-08 23:52:14 +01:00
|
|
|
/// If `immediate` is true, the `callback` will run immediately.
|
|
|
|
/// If it's `false, the `callback` will run only after
|
2023-06-08 01:19:36 +01:00
|
|
|
/// the first change is detected of any signal that is accessed in `deps`.
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use leptos::*;
|
|
|
|
/// # use leptos_use::{watch_with_options, WatchOptions};
|
|
|
|
/// #
|
|
|
|
/// # pub fn Demo(cx: Scope) -> impl IntoView {
|
|
|
|
/// let (num, set_num) = create_signal(cx, 0);
|
|
|
|
///
|
|
|
|
/// watch_with_options(
|
|
|
|
/// cx,
|
|
|
|
/// num,
|
|
|
|
/// move |num, _, _| {
|
2023-06-09 23:10:33 +01:00
|
|
|
/// log!("Number {}", num);
|
2023-06-08 01:19:36 +01:00
|
|
|
/// },
|
2023-06-08 23:52:14 +01:00
|
|
|
/// WatchOptions::default().immediate(true),
|
2023-06-09 23:10:33 +01:00
|
|
|
/// ); // > "Number 0"
|
2023-06-08 01:19:36 +01:00
|
|
|
///
|
2023-06-09 23:10:33 +01:00
|
|
|
/// set_num(1); // > "Number 1"
|
2023-06-08 01:19:36 +01:00
|
|
|
/// # view! { cx, }
|
|
|
|
/// # }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ## Filters
|
|
|
|
///
|
|
|
|
/// The callback can be throttled or debounced. Please see [`watch_throttled`] and [`watch_debounced`] for details.
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use leptos::*;
|
|
|
|
/// # use leptos_use::{watch_with_options, WatchOptions};
|
|
|
|
/// #
|
|
|
|
/// # pub fn Demo(cx: Scope) -> impl IntoView {
|
|
|
|
/// # let (num, set_num) = create_signal(cx, 0);
|
|
|
|
/// #
|
|
|
|
/// watch_with_options(
|
|
|
|
/// cx,
|
|
|
|
/// num,
|
|
|
|
/// move |num, _, _| {
|
2023-06-09 23:10:33 +01:00
|
|
|
/// log!("Number {}", num);
|
2023-06-08 01:19:36 +01:00
|
|
|
/// },
|
|
|
|
/// WatchOptions::default().throttle(100.0), // there's also `throttle_with_options`
|
|
|
|
/// );
|
|
|
|
/// # view! { cx, }
|
|
|
|
/// # }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use leptos::*;
|
|
|
|
/// # use leptos_use::{watch_with_options, WatchOptions};
|
|
|
|
/// #
|
|
|
|
/// # pub fn Demo(cx: Scope) -> impl IntoView {
|
|
|
|
/// # let (num, set_num) = create_signal(cx, 0);
|
|
|
|
/// #
|
|
|
|
/// watch_with_options(
|
|
|
|
/// cx,
|
|
|
|
/// num,
|
|
|
|
/// move |num, _, _| {
|
|
|
|
/// log!("number {}", num);
|
|
|
|
/// },
|
|
|
|
/// WatchOptions::default().debounce(100.0), // there's also `debounce_with_options`
|
|
|
|
/// );
|
|
|
|
/// # view! { cx, }
|
|
|
|
/// # }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ## See also
|
|
|
|
///
|
|
|
|
/// * [`watch_throttled`]
|
|
|
|
/// * [`watch_debounced`]
|
|
|
|
pub fn watch<W, T, DFn, CFn>(cx: Scope, deps: DFn, callback: CFn) -> 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_with_options(cx, deps, callback, WatchOptions::default())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Version of `watch` that accepts `WatchOptions`. See [`watch`] for how to use.
|
|
|
|
pub fn watch_with_options<W, T, DFn, CFn>(
|
2023-05-29 01:52:03 +01:00
|
|
|
cx: Scope,
|
|
|
|
deps: DFn,
|
|
|
|
callback: CFn,
|
2023-06-08 01:19:36 +01:00
|
|
|
options: WatchOptions,
|
2023-05-29 01:52:03 +01:00
|
|
|
) -> impl Fn() + Clone
|
|
|
|
where
|
|
|
|
DFn: Fn() -> W + 'static,
|
2023-06-08 01:19:36 +01:00
|
|
|
CFn: Fn(&W, Option<&W>, Option<T>) -> T + Clone + 'static,
|
|
|
|
W: Clone + 'static,
|
|
|
|
T: Clone + 'static,
|
2023-05-29 01:52:03 +01:00
|
|
|
{
|
|
|
|
let (is_active, set_active) = create_signal(cx, true);
|
|
|
|
|
2023-06-08 01:19:36 +01:00
|
|
|
let cur_deps_value: Rc<RefCell<Option<W>>> = Rc::new(RefCell::new(None));
|
|
|
|
let prev_deps_value: Rc<RefCell<Option<W>>> = Rc::new(RefCell::new(None));
|
|
|
|
let prev_callback_value: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None));
|
|
|
|
|
|
|
|
let cur_val = Rc::clone(&cur_deps_value);
|
|
|
|
let prev_val = Rc::clone(&prev_deps_value);
|
|
|
|
let prev_cb_val = Rc::clone(&prev_callback_value);
|
|
|
|
let wrapped_callback = move || {
|
|
|
|
callback(
|
|
|
|
cur_val
|
|
|
|
.borrow()
|
|
|
|
.as_ref()
|
|
|
|
.expect("this will not be called before there is deps value"),
|
|
|
|
prev_val.borrow().as_ref(),
|
|
|
|
prev_cb_val.take(),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
2023-06-08 23:52:14 +01:00
|
|
|
let filtered_callback =
|
|
|
|
create_filter_wrapper(options.filter.filter_fn(), wrapped_callback.clone());
|
2023-05-29 01:52:03 +01:00
|
|
|
|
|
|
|
create_effect(cx, move |did_run_before| {
|
|
|
|
if !is_active() {
|
|
|
|
return ();
|
|
|
|
}
|
|
|
|
|
|
|
|
let deps_value = deps();
|
|
|
|
|
2023-06-08 01:19:36 +01:00
|
|
|
if !options.immediate && did_run_before.is_none() {
|
2023-05-29 01:52:03 +01:00
|
|
|
prev_deps_value.replace(Some(deps_value));
|
|
|
|
return ();
|
|
|
|
}
|
|
|
|
|
2023-06-08 01:19:36 +01:00
|
|
|
cur_deps_value.replace(Some(deps_value.clone()));
|
|
|
|
|
2023-06-08 23:52:14 +01:00
|
|
|
let callback_value = if options.immediate && did_run_before.is_none() {
|
|
|
|
Some(wrapped_callback())
|
|
|
|
} else {
|
|
|
|
filtered_callback().take()
|
|
|
|
};
|
2023-06-08 01:19:36 +01:00
|
|
|
|
|
|
|
prev_callback_value.replace(callback_value);
|
2023-05-29 01:52:03 +01:00
|
|
|
|
|
|
|
prev_deps_value.replace(Some(deps_value));
|
|
|
|
|
|
|
|
()
|
|
|
|
});
|
|
|
|
|
|
|
|
move || {
|
|
|
|
set_active(false);
|
|
|
|
}
|
|
|
|
}
|
2023-06-08 01:19:36 +01:00
|
|
|
|
|
|
|
/// Options for `watch_with_options`
|
2023-06-08 23:52:14 +01:00
|
|
|
#[derive(DefaultBuilder, Default)]
|
2023-06-08 01:19:36 +01:00
|
|
|
pub struct WatchOptions {
|
2023-06-08 23:52:14 +01:00
|
|
|
/// If `immediate` is true, the `callback` will run immediately.
|
|
|
|
/// If it's `false, the `callback` will run only after
|
2023-06-08 01:19:36 +01:00
|
|
|
/// the first change is detected of any signal that is accessed in `deps`.
|
|
|
|
immediate: bool,
|
|
|
|
|
|
|
|
filter: FilterOptions,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WatchOptions {
|
|
|
|
filter_builder_methods!(
|
|
|
|
/// the watch callback
|
|
|
|
filter
|
|
|
|
);
|
|
|
|
}
|