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> Clone for ReactiveValue<T> {
47 fn clone(&self) -> Self {
48 match self {
49 ReactiveValue::Static(v) => ReactiveValue::Static(v.clone()),
50 ReactiveValue::Dynamic(v) => ReactiveValue::Dynamic(v.clone()),
51 }
52 }
53}
54
55impl<T: Clone> ReactiveValue<T> {
56 pub fn get(&self) -> T {
57 match self {
58 ReactiveValue::Static(v) => v.clone(),
59 ReactiveValue::Dynamic(v) => v.borrow().clone(),
60 }
61 }
62}
63
64pub trait IntoReactiveString {
65 fn into_reactive_string(self) -> ReactiveValue<String>;
66}
67
68impl IntoReactiveString for &str {
69 fn into_reactive_string(self) -> ReactiveValue<String> {
70 ReactiveValue::Static(self.to_string())
71 }
72}
73
74impl IntoReactiveString for String {
75 fn into_reactive_string(self) -> ReactiveValue<String> {
76 ReactiveValue::Static(self)
77 }
78}
79
80impl<T, F> IntoReactiveString for MappedSignal<T, String, F>
81where
82 T: Clone + 'static,
83 F: Fn(&T) -> String + 'static,
84{
85 fn into_reactive_string(self) -> ReactiveValue<String> {
86 use react_rs_core::effect::create_effect;
87
88 let value = Rc::new(RefCell::new(self.get()));
89 let value_clone = value.clone();
90
91 create_effect(move || {
92 let new_value = self.get();
93 *value_clone.borrow_mut() = new_value;
94 });
95
96 ReactiveValue::Dynamic(value)
97 }
98}
99
100pub trait IntoReactiveBool {
101 fn into_reactive_bool(self) -> ReactiveValue<bool>;
102}
103
104impl IntoReactiveBool for bool {
105 fn into_reactive_bool(self) -> ReactiveValue<bool> {
106 ReactiveValue::Static(self)
107 }
108}
109
110impl<T, F> IntoReactiveBool for MappedSignal<T, bool, F>
111where
112 T: Clone + 'static,
113 F: Fn(&T) -> bool + 'static,
114{
115 fn into_reactive_bool(self) -> ReactiveValue<bool> {
116 use react_rs_core::effect::create_effect;
117
118 let value = Rc::new(RefCell::new(self.get()));
119 let value_clone = value.clone();
120
121 create_effect(move || {
122 let new_value = self.get();
123 *value_clone.borrow_mut() = new_value;
124 });
125
126 ReactiveValue::Dynamic(value)
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use react_rs_core::signal::create_signal;
134
135 #[test]
136 fn test_signal_map() {
137 let (count, set_count) = create_signal(5);
138 let doubled = count.map(|n| n * 2);
139
140 assert_eq!(doubled.get(), 10);
141
142 set_count.set(10);
143 assert_eq!(doubled.get(), 20);
144 }
145
146 #[test]
147 fn test_mapped_signal_to_string() {
148 let (count, set_count) = create_signal(0);
149 let text = count.map(|n| format!("Count: {}", n));
150
151 assert_eq!(text.get(), "Count: 0");
152
153 set_count.set(42);
154 assert_eq!(text.get(), "Count: 42");
155 }
156
157 #[test]
158 fn test_reactive_value_static() {
159 let value: ReactiveValue<String> = "hello".into_reactive_string();
160 assert_eq!(value.get(), "hello");
161 }
162
163 #[test]
164 fn test_reactive_value_dynamic() {
165 let (count, set_count) = create_signal(0);
166 let text = count.map(|n| format!("{}", n));
167 let reactive: ReactiveValue<String> = text.into_reactive_string();
168
169 assert_eq!(reactive.get(), "0");
170
171 set_count.set(5);
172 assert_eq!(reactive.get(), "5");
173 }
174}