From ecffde423f3f363d17ab7de230289109b9a0d52d Mon Sep 17 00:00:00 2001
From: luoxiao <luoxiaozero@163.com>
Date: Fri, 6 Oct 2023 16:53:57 +0800
Subject: [PATCH] feat: add MaybeRwSignal and watch

---
 src/lib.rs                   |  2 +-
 src/menu/mod.rs              |  6 ++++-
 src/utils/maybe_rw_signal.rs | 32 +++++++++++++++++++++++++
 src/utils/mod.rs             |  2 ++
 src/utils/signal.rs          | 46 ++++++++++++++++++++++++++++++++++++
 5 files changed, 86 insertions(+), 2 deletions(-)
 create mode 100644 src/utils/maybe_rw_signal.rs
 create mode 100644 src/utils/signal.rs

diff --git a/src/lib.rs b/src/lib.rs
index 13a7480..570e86e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -38,5 +38,5 @@ pub use space::*;
 pub use table::*;
 pub use tabs::*;
 pub use theme::Theme;
-pub use utils::mount_style::mount_style;
+pub use utils::{mount_style::mount_style, signal::SignalWatch};
 pub use wave::*;
diff --git a/src/menu/mod.rs b/src/menu/mod.rs
index 76a5c48..df23d8f 100644
--- a/src/menu/mod.rs
+++ b/src/menu/mod.rs
@@ -2,13 +2,17 @@ mod menu_group;
 mod menu_item;
 mod theme;
 
+use crate::utils::maybe_rw_signal::MaybeRwSignal;
 use leptos::*;
 pub use menu_group::MenuGroup;
 pub use menu_item::*;
 pub use theme::MenuTheme;
 
 #[component]
-pub fn Menu(#[prop(into)] selected: RwSignal<String>, children: Children) -> impl IntoView {
+pub fn Menu(
+    #[prop(optional, into)] selected: MaybeRwSignal<String>,
+    children: Children,
+) -> impl IntoView {
     let menu_injection_key = create_rw_signal(MenuInjectionKey::new(selected.get_untracked()));
     create_effect(move |_| {
         let selected_key = selected.get();
diff --git a/src/utils/maybe_rw_signal.rs b/src/utils/maybe_rw_signal.rs
new file mode 100644
index 0000000..47fb42e
--- /dev/null
+++ b/src/utils/maybe_rw_signal.rs
@@ -0,0 +1,32 @@
+use leptos::RwSignal;
+use std::ops::Deref;
+
+pub struct MaybeRwSignal<T: Default + 'static>(RwSignal<T>);
+
+impl<T: Default> Default for MaybeRwSignal<T> {
+    fn default() -> Self {
+        Self(RwSignal::new(Default::default()))
+    }
+}
+
+impl<T: Default> Clone for MaybeRwSignal<T> {
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+impl<T: Default> Copy for MaybeRwSignal<T> {}
+
+impl<T: Default> Deref for MaybeRwSignal<T> {
+    type Target = RwSignal<T>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<T: Default> From<RwSignal<T>> for MaybeRwSignal<T> {
+    fn from(value: RwSignal<T>) -> Self {
+        Self(value)
+    }
+}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index f21060a..e265b39 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1 +1,3 @@
+pub mod maybe_rw_signal;
 pub mod mount_style;
+pub mod signal;
diff --git a/src/utils/signal.rs b/src/utils/signal.rs
new file mode 100644
index 0000000..80cbeec
--- /dev/null
+++ b/src/utils/signal.rs
@@ -0,0 +1,46 @@
+use leptos::{create_effect, untrack, RwSignal, SignalDispose, SignalWith};
+
+pub trait SignalWatch {
+    type Value;
+
+    fn watch(&self, f: impl Fn(&Self::Value) + 'static) -> Box<dyn FnOnce()>;
+}
+
+impl<T> SignalWatch for RwSignal<T> {
+    type Value = T;
+
+    /// Listens for RwSignal changes and is not executed immediately
+    ///
+    /// ## Usage
+    ///
+    /// ```rust
+    /// use leptos::*;
+    /// use melt_ui::*;
+    ///
+    /// let count = create_rw_signal(0);
+    /// let stop = count.watch(|count| {
+    ///     assert_eq!(count, &1);
+    /// });
+    ///
+    /// count.set(1); // assert_eq!(count, &1);
+    ///
+    /// stop(); // stop watching
+    ///
+    /// count.set(2); // nothing happens
+    /// ```
+    fn watch(&self, f: impl Fn(&Self::Value) + 'static) -> Box<dyn FnOnce()> {
+        let signal = self.clone();
+
+        let effect = create_effect(move |prev| {
+            signal.with(|value| {
+                if prev.is_some() {
+                    untrack(|| f(value));
+                }
+            });
+        });
+
+        Box::new(move || {
+            effect.dispose();
+        })
+    }
+}