diff --git a/packages/signals/README.md b/packages/signals/README.md index d1e772445..9a6f9e17c 100644 --- a/packages/signals/README.md +++ b/packages/signals/README.md @@ -106,7 +106,7 @@ use dioxus_signals::*; #[component] fn App(cx: Scope) -> Element { let signal = use_signal(cx, || 0); - let doubled = use_selector(cx, || signal * 2); + let doubled = use_selector(cx, move || signal * 2); render! { button { @@ -114,7 +114,7 @@ fn App(cx: Scope) -> Element { "Increase" } Child { - signal: signal + signal: doubled } } } diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 1f8bc3297..72cba6a2d 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -12,3 +12,5 @@ pub(crate) mod signal; pub use signal::*; mod dependency; pub use dependency::*; +mod map; +pub use map::*; diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs new file mode 100644 index 000000000..e83dabe7b --- /dev/null +++ b/packages/signals/src/map.rs @@ -0,0 +1,91 @@ +use crate::Signal; +use dioxus_core::ScopeId; +use std::cell::Ref; +use std::fmt::Debug; +use std::fmt::Display; + +/// A signal that can only be read from. +pub struct SignalMap { + inner: Signal, + mapping: fn(&T) -> &U, +} + +impl SignalMap { + /// Create a new read-only signal. + pub fn new(signal: Signal, mapping: fn(&T) -> &U) -> Self { + Self { + inner: signal, + mapping, + } + } + + /// Get the scope that the signal was created in. + pub fn origin_scope(&self) -> ScopeId { + self.inner.origin_scope() + } + + /// Get the current value of the signal. This will subscribe the current scope to the signal. + pub fn read(&self) -> Ref { + Ref::map(self.inner.read(), |v| (self.mapping)(v)) + } + + /// Run a closure with a reference to the signal's value. + pub fn with(&self, f: impl FnOnce(&U) -> O) -> O { + self.inner.with(|v| f((self.mapping)(v))) + } +} + +impl SignalMap { + /// Get the current value of the signal. This will subscribe the current scope to the signal. + pub fn value(&self) -> U { + self.read().clone() + } +} + +impl PartialEq for SignalMap { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl std::clone::Clone for SignalMap { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for SignalMap {} + +impl Display for SignalMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.with(|v| Display::fmt(v, f)) + } +} + +impl Debug for SignalMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.with(|v| Debug::fmt(v, f)) + } +} + +impl SignalMap> { + /// Read a value from the inner vector. + pub fn get(&self, index: usize) -> Option> { + Ref::filter_map(self.read(), |v| v.get(index)).ok() + } +} + +impl SignalMap> { + /// Unwraps the inner value and clones it. + pub fn unwrap(&self) -> U + where + T: Clone, + { + self.with(|v| v.clone()).unwrap() + } + + /// Attemps to read the inner value of the Option. + pub fn as_ref(&self) -> Option> { + Ref::filter_map(self.read(), |v| v.as_ref()).ok() + } +} diff --git a/packages/signals/tests/map.rs b/packages/signals/tests/map.rs new file mode 100644 index 000000000..449e5e599 --- /dev/null +++ b/packages/signals/tests/map.rs @@ -0,0 +1,32 @@ +#![allow(unused, non_upper_case_globals, non_snake_case)] + +use dioxus::prelude::*; +use dioxus_core::ElementId; +use dioxus_signals::*; + +#[test] +fn create_signals_global() { + let mut dom = VirtualDom::new(|cx| { + render! { + for _ in 0..10 { + Child {} + } + } + }); + + fn Child(cx: Scope) -> Element { + let signal = create_without_cx(); + let mapped = SignalMap::new(signal, |v| v.as_bytes()); + + render! { + "{signal:?}" + "{mapped:?}" + } + } + + let _edits = dom.rebuild().santize(); + + fn create_without_cx() -> Signal { + Signal::new("hello world".to_string()) + } +}