Skip to main content

react_rs_elements/
reactive.rs

1use 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}