Skip to main content

react_rs_elements/
node.rs

1use crate::head::Head;
2use crate::reactive::ReactiveValue;
3use crate::suspense::{ErrorBoundaryData, SuspenseData};
4use crate::Element;
5use std::rc::Rc;
6
7pub enum Node {
8    Element(Element),
9    Text(String),
10    ReactiveText(ReactiveValue<String>),
11    Fragment(Vec<Node>),
12    Conditional(ReactiveValue<bool>, Box<Node>, Option<Box<Node>>),
13    ReactiveList(Rc<dyn Fn() -> Vec<Node>>),
14    KeyedList(Rc<dyn Fn() -> Vec<(String, Node)>>),
15    Head(Head),
16    Suspense(SuspenseData),
17    ErrorBoundary(ErrorBoundaryData),
18}
19
20pub trait IntoNode {
21    fn into_node(self) -> Node;
22}
23
24impl IntoNode for Element {
25    fn into_node(self) -> Node {
26        Node::Element(self)
27    }
28}
29
30impl IntoNode for String {
31    fn into_node(self) -> Node {
32        Node::Text(self)
33    }
34}
35
36impl IntoNode for &str {
37    fn into_node(self) -> Node {
38        Node::Text(self.to_string())
39    }
40}
41
42impl<T: IntoNode> IntoNode for Vec<T> {
43    fn into_node(self) -> Node {
44        Node::Fragment(self.into_iter().map(|n| n.into_node()).collect())
45    }
46}
47
48impl IntoNode for Node {
49    fn into_node(self) -> Node {
50        self
51    }
52}
53
54impl IntoNode for Head {
55    fn into_node(self) -> Node {
56        Node::Head(self)
57    }
58}
59
60pub fn each<T, F>(items: react_rs_core::signal::ReadSignal<Vec<T>>, render: F) -> Node
61where
62    T: Clone + 'static,
63    F: Fn(&T, usize) -> Node + 'static,
64{
65    Node::ReactiveList(Rc::new(move || {
66        items.with(|list| {
67            list.iter()
68                .enumerate()
69                .map(|(i, item)| render(item, i))
70                .collect()
71        })
72    }))
73}
74
75pub fn each_keyed<T, K, F>(
76    items: react_rs_core::signal::ReadSignal<Vec<T>>,
77    key_fn: impl Fn(&T) -> K + 'static,
78    render: F,
79) -> Node
80where
81    T: Clone + 'static,
82    K: ToString + 'static,
83    F: Fn(&T, usize) -> Node + 'static,
84{
85    Node::KeyedList(Rc::new(move || {
86        items.with(|list| {
87            list.iter()
88                .enumerate()
89                .map(|(i, item)| (key_fn(item).to_string(), render(item, i)))
90                .collect()
91        })
92    }))
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::html;
99    use react_rs_core::create_signal;
100
101    #[test]
102    fn test_conditional_creates_node() {
103        let node = html::div().text("test").show_when(true);
104        assert!(matches!(node, Node::Conditional(_, _, None)));
105    }
106
107    #[test]
108    fn test_conditional_else_creates_node() {
109        let node = html::div()
110            .text("yes")
111            .show_when_else(false, html::span().text("no"));
112        assert!(matches!(node, Node::Conditional(_, _, Some(_))));
113    }
114
115    #[test]
116    fn test_each_creates_reactive_list() {
117        let (items, _) = create_signal(vec![1, 2, 3]);
118        let node = each(items, |item, _| {
119            html::li().text(item.to_string()).into_node()
120        });
121        assert!(matches!(node, Node::ReactiveList(_)));
122    }
123}