use crate::core::UseRwSignal;
use default_struct_builder::DefaultBuilder;
use leptos::prelude::*;
use std::rc::Rc;
pub fn sync_signal<T>(
left: impl Into<UseRwSignal<T>>,
right: impl Into<UseRwSignal<T>>,
) -> impl Fn() + Clone
where
T: Clone + Send + Sync + 'static,
{
sync_signal_with_options(left, right, SyncSignalOptions::<T, T>::default())
}
pub fn sync_signal_with_options<L, R>(
left: impl Into<UseRwSignal<L>>,
right: impl Into<UseRwSignal<R>>,
options: SyncSignalOptions<L, R>,
) -> impl Fn() + Clone
where
L: Clone + Send + Sync + 'static,
R: Clone + Send + Sync + 'static,
{
let SyncSignalOptions {
immediate,
direction,
transforms,
} = options;
let (assign_ltr, assign_rtl) = transforms.assigns();
let left = left.into();
let right = right.into();
let mut stop_watch_left = None;
let mut stop_watch_right = None;
let is_sync_update = StoredValue::new(false);
if matches!(direction, SyncDirection::Both | SyncDirection::LeftToRight) {
#[cfg(feature = "ssr")]
{
if immediate {
let assign_ltr = Rc::clone(&assign_ltr);
right.try_update(move |right| assign_ltr(right, &left.get_untracked()));
}
}
stop_watch_left = Some(Effect::watch(
move || left.get(),
move |new_value, _, _| {
if !is_sync_update.get_value() || !matches!(direction, SyncDirection::Both) {
is_sync_update.set_value(true);
right.try_update(|right| {
assign_ltr(right, new_value);
});
} else {
is_sync_update.set_value(false);
}
},
immediate,
));
}
if matches!(direction, SyncDirection::Both | SyncDirection::RightToLeft) {
#[cfg(feature = "ssr")]
{
if immediate && matches!(direction, SyncDirection::RightToLeft) {
let assign_rtl = Rc::clone(&assign_rtl);
left.try_update(move |left| assign_rtl(left, &right.get_untracked()));
}
}
stop_watch_right = Some(Effect::watch(
move || right.get(),
move |new_value, _, _| {
if !is_sync_update.get_value() || !matches!(direction, SyncDirection::Both) {
is_sync_update.set_value(true);
left.try_update(|left| assign_rtl(left, new_value));
} else {
is_sync_update.set_value(false);
}
},
immediate,
));
}
move || {
if let Some(stop_watch_left) = &stop_watch_left {
stop_watch_left.stop();
}
if let Some(stop_watch_right) = &stop_watch_right {
stop_watch_right.stop();
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum SyncDirection {
LeftToRight,
RightToLeft,
#[default]
Both,
}
pub type AssignFn<T, S> = Rc<dyn Fn(&mut T, &S)>;
pub enum SyncTransforms<L, R> {
Transforms {
ltr: Rc<dyn Fn(&L) -> R>,
rtl: Rc<dyn Fn(&R) -> L>,
},
Assigns {
ltr: AssignFn<R, L>,
rtl: AssignFn<L, R>,
},
}
impl<T> Default for SyncTransforms<T, T>
where
T: Clone,
{
fn default() -> Self {
Self::Assigns {
ltr: Rc::new(|right, left| *right = left.clone()),
rtl: Rc::new(|left, right| *left = right.clone()),
}
}
}
impl<L, R> SyncTransforms<L, R>
where
L: 'static,
R: 'static,
{
pub fn assigns(&self) -> (AssignFn<R, L>, AssignFn<L, R>) {
match self {
SyncTransforms::Transforms { ltr, rtl } => {
let ltr = Rc::clone(ltr);
let rtl = Rc::clone(rtl);
(
Rc::new(move |right, left| *right = ltr(left)),
Rc::new(move |left, right| *left = rtl(right)),
)
}
SyncTransforms::Assigns { ltr, rtl } => (Rc::clone(ltr), Rc::clone(rtl)),
}
}
}
#[derive(DefaultBuilder)]
pub struct SyncSignalOptions<L, R> {
immediate: bool,
direction: SyncDirection,
#[builder(skip)]
transforms: SyncTransforms<L, R>,
}
impl<L, R> SyncSignalOptions<L, R> {
pub fn with_transforms(
transform_ltr: impl Fn(&L) -> R + 'static,
transform_rtl: impl Fn(&R) -> L + 'static,
) -> Self {
Self {
immediate: true,
direction: SyncDirection::Both,
transforms: SyncTransforms::Transforms {
ltr: Rc::new(transform_ltr),
rtl: Rc::new(transform_rtl),
},
}
}
pub fn with_assigns(
assign_ltr: impl Fn(&mut R, &L) + 'static,
assign_rtl: impl Fn(&mut L, &R) + 'static,
) -> Self {
Self {
immediate: true,
direction: SyncDirection::Both,
transforms: SyncTransforms::Assigns {
ltr: Rc::new(assign_ltr),
rtl: Rc::new(assign_rtl),
},
}
}
}
impl<T> Default for SyncSignalOptions<T, T>
where
T: Clone,
{
fn default() -> Self {
Self {
immediate: true,
direction: Default::default(),
transforms: Default::default(),
}
}
}