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