use std::cell::RefCell;
use std::rc::Rc;
use serde::de::DeserializeOwned;
use crate::reactive::{effect, track, EffectId, ScopeId};
use crate::scope::{current_scope_id, Scope};
pub fn watch<T, S, C>(source: S, cb: C) -> EffectId
where
T: Clone + PartialEq + 'static,
S: Fn() -> T + 'static,
C: Fn(&T, Option<&T>) + 'static,
{
let prev: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None));
let prev_w = prev.clone();
effect(move || {
let next = source();
let last = prev_w.borrow().clone();
if last.as_ref() != Some(&next) {
cb(&next, last.as_ref());
*prev_w.borrow_mut() = Some(next);
}
})
}
pub fn watch_field<V, C>(field: &'static str, cb: C)
where
V: Clone + PartialEq + Default + DeserializeOwned + 'static,
C: Fn(&V, Option<&V>) + 'static,
{
let scope_id =
current_scope_id().expect("watch_field called outside a handler / lifecycle context");
watch_scope_field(scope_id, field, cb);
}
pub fn watch_scope_field<V, C>(scope_id: ScopeId, field: &'static str, cb: C)
where
V: Clone + PartialEq + Default + DeserializeOwned + 'static,
C: Fn(&V, Option<&V>) + 'static,
{
crate::tick::next(move || {
watch_scope_field_now(scope_id, field, cb);
});
}
fn read_scope_field<V>(scope_id: ScopeId, field: &'static str) -> V
where
V: Clone + PartialEq + Default + DeserializeOwned + 'static,
{
track(scope_id, field);
let Some(scope) = Scope::find(scope_id) else {
return V::default();
};
let v = scope.state.borrow().get(field);
serde_wasm_bindgen::from_value::<V>(v).unwrap_or_default()
}
#[doc(hidden)]
pub fn watch_scope_field_now<V, C>(scope_id: ScopeId, field: &'static str, cb: C) -> EffectId
where
V: Clone + PartialEq + Default + DeserializeOwned + 'static,
C: Fn(&V, Option<&V>) + 'static,
{
watch(move || read_scope_field::<V>(scope_id, field), cb)
}
pub fn watch_scoped<T, S, C>(source: S, cb: C)
where
T: Clone + PartialEq + 'static,
S: Fn() -> T + 'static,
C: Fn(&T, Option<&T>) + 'static,
{
use std::cell::Cell;
use std::rc::Rc;
let pending: Rc<Cell<Option<EffectId>>> = Rc::new(Cell::new(None));
let pending_for_install = pending.clone();
crate::tick::next(move || {
let id = watch(source, cb);
pending_for_install.set(Some(id));
});
crate::events::on_scope_unmount(move || {
if let Some(id) = pending.take() {
crate::reactive::release(id);
}
});
}
pub fn watch_field_scoped<V, C>(field: &'static str, cb: C)
where
V: Clone + PartialEq + Default + DeserializeOwned + 'static,
C: Fn(&V, Option<&V>) + 'static,
{
let scope_id = current_scope_id()
.expect("watch_field_scoped called outside a handler / lifecycle context");
watch_scope_field_scoped(scope_id, field, cb);
}
pub fn watch_scope_field_scoped<V, C>(scope_id: ScopeId, field: &'static str, cb: C)
where
V: Clone + PartialEq + Default + DeserializeOwned + 'static,
C: Fn(&V, Option<&V>) + 'static,
{
use std::cell::Cell;
use std::rc::Rc;
let pending: Rc<Cell<Option<EffectId>>> = Rc::new(Cell::new(None));
let pending_for_install = pending.clone();
crate::tick::next(move || {
let id = watch_scope_field_now(scope_id, field, cb);
pending_for_install.set(Some(id));
});
crate::events::on_scope_unmount(move || {
if let Some(id) = pending.take() {
crate::reactive::release(id);
}
});
}