react_rs_elements/
reactive.rs1use std::rc::Rc;
2
3use react_rs_core::signal::ReadSignal;
4
5pub trait SignalExt<T> {
6 fn map<U, F>(&self, f: F) -> MappedSignal<T, U, F>
7 where
8 F: Fn(&T) -> U + 'static;
9}
10
11impl<T: Clone + 'static> SignalExt<T> for ReadSignal<T> {
12 fn map<U, F>(&self, f: F) -> MappedSignal<T, U, F>
13 where
14 F: Fn(&T) -> U + 'static,
15 {
16 MappedSignal {
17 signal: self.clone(),
18 mapper: f,
19 }
20 }
21}
22
23pub struct MappedSignal<T, U, F>
24where
25 F: Fn(&T) -> U,
26{
27 signal: ReadSignal<T>,
28 mapper: F,
29}
30
31impl<T: Clone, U, F> MappedSignal<T, U, F>
32where
33 F: Fn(&T) -> U,
34{
35 pub fn get(&self) -> U {
36 self.signal.with(|v| (self.mapper)(v))
37 }
38}
39
40pub enum ReactiveValue<T> {
41 Static(T),
42 Dynamic(Rc<dyn Fn() -> T>),
43}
44
45impl<T: Clone> Clone for ReactiveValue<T> {
46 fn clone(&self) -> Self {
47 match self {
48 ReactiveValue::Static(v) => ReactiveValue::Static(v.clone()),
49 ReactiveValue::Dynamic(v) => ReactiveValue::Dynamic(v.clone()),
50 }
51 }
52}
53
54impl<T: Clone> ReactiveValue<T> {
55 pub fn get(&self) -> T {
56 match self {
57 ReactiveValue::Static(v) => v.clone(),
58 ReactiveValue::Dynamic(f) => f(),
59 }
60 }
61}
62
63pub trait IntoReactiveString {
64 fn into_reactive_string(self) -> ReactiveValue<String>;
65}
66
67impl IntoReactiveString for &str {
68 fn into_reactive_string(self) -> ReactiveValue<String> {
69 ReactiveValue::Static(self.to_string())
70 }
71}
72
73impl IntoReactiveString for String {
74 fn into_reactive_string(self) -> ReactiveValue<String> {
75 ReactiveValue::Static(self)
76 }
77}
78
79impl<T, F> IntoReactiveString for MappedSignal<T, String, F>
80where
81 T: Clone + 'static,
82 F: Fn(&T) -> String + 'static,
83{
84 fn into_reactive_string(self) -> ReactiveValue<String> {
85 ReactiveValue::Dynamic(Rc::new(move || self.get()))
86 }
87}
88
89pub trait IntoReactiveBool {
90 fn into_reactive_bool(self) -> ReactiveValue<bool>;
91}
92
93impl IntoReactiveBool for bool {
94 fn into_reactive_bool(self) -> ReactiveValue<bool> {
95 ReactiveValue::Static(self)
96 }
97}
98
99impl<T, F> IntoReactiveBool for MappedSignal<T, bool, F>
100where
101 T: Clone + 'static,
102 F: Fn(&T) -> bool + 'static,
103{
104 fn into_reactive_bool(self) -> ReactiveValue<bool> {
105 ReactiveValue::Dynamic(Rc::new(move || self.get()))
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use react_rs_core::signal::create_signal;
113
114 #[test]
115 fn test_signal_map() {
116 let (count, set_count) = create_signal(5);
117 let doubled = count.map(|n| n * 2);
118
119 assert_eq!(doubled.get(), 10);
120
121 set_count.set(10);
122 assert_eq!(doubled.get(), 20);
123 }
124
125 #[test]
126 fn test_mapped_signal_to_string() {
127 let (count, set_count) = create_signal(0);
128 let text = count.map(|n| format!("Count: {}", n));
129
130 assert_eq!(text.get(), "Count: 0");
131
132 set_count.set(42);
133 assert_eq!(text.get(), "Count: 42");
134 }
135
136 #[test]
137 fn test_reactive_value_static() {
138 let value: ReactiveValue<String> = "hello".into_reactive_string();
139 assert_eq!(value.get(), "hello");
140 }
141
142 #[test]
143 fn test_reactive_value_dynamic() {
144 let (count, set_count) = create_signal(0);
145 let text = count.map(|n| format!("{}", n));
146 let reactive: ReactiveValue<String> = text.into_reactive_string();
147
148 assert_eq!(reactive.get(), "0");
149
150 set_count.set(5);
151 assert_eq!(reactive.get(), "5");
152 }
153}