react_rs_elements/
node.rs1use 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}