Skip to main content

react_rs_elements/
element.rs

1use crate::attributes::Attribute;
2use crate::events::{Event, EventHandler};
3use crate::node::{IntoNode, Node};
4use crate::reactive::{IntoReactiveBool, IntoReactiveString};
5
6pub struct Element {
7    tag: &'static str,
8    attributes: Vec<Attribute>,
9    children: Vec<Node>,
10    event_handlers: Vec<EventHandler>,
11}
12
13impl Element {
14    pub fn new(tag: &'static str) -> Self {
15        Self {
16            tag,
17            attributes: Vec::new(),
18            children: Vec::new(),
19            event_handlers: Vec::new(),
20        }
21    }
22
23    pub fn tag(&self) -> &'static str {
24        self.tag
25    }
26
27    pub fn attributes(&self) -> &[Attribute] {
28        &self.attributes
29    }
30
31    pub fn get_children(&self) -> &[Node] {
32        &self.children
33    }
34
35    pub fn class(mut self, class: &str) -> Self {
36        self.attributes.push(Attribute::new("class", class));
37        self
38    }
39
40    pub fn class_reactive(mut self, class: impl IntoReactiveString) -> Self {
41        self.attributes.push(Attribute::reactive_string(
42            "class",
43            class.into_reactive_string(),
44        ));
45        self
46    }
47
48    pub fn visible_reactive(mut self, visible: impl IntoReactiveBool) -> Self {
49        self.attributes.push(Attribute::reactive_bool(
50            "data-visible",
51            visible.into_reactive_bool(),
52        ));
53        self
54    }
55
56    pub fn id(mut self, id: &str) -> Self {
57        self.attributes.push(Attribute::new("id", id));
58        self
59    }
60
61    pub fn style(mut self, style: &str) -> Self {
62        self.attributes.push(Attribute::new("style", style));
63        self
64    }
65
66    pub fn attr(mut self, name: &str, value: &str) -> Self {
67        self.attributes.push(Attribute::new(name, value));
68        self
69    }
70
71    pub fn href(mut self, href: &str) -> Self {
72        self.attributes.push(Attribute::new("href", href));
73        self
74    }
75
76    pub fn src(mut self, src: &str) -> Self {
77        self.attributes.push(Attribute::new("src", src));
78        self
79    }
80
81    pub fn alt(mut self, alt: &str) -> Self {
82        self.attributes.push(Attribute::new("alt", alt));
83        self
84    }
85
86    pub fn type_(mut self, type_value: &str) -> Self {
87        self.attributes.push(Attribute::new("type", type_value));
88        self
89    }
90
91    pub fn name(mut self, name: &str) -> Self {
92        self.attributes.push(Attribute::new("name", name));
93        self
94    }
95
96    pub fn value(mut self, value: &str) -> Self {
97        self.attributes.push(Attribute::new("value", value));
98        self
99    }
100
101    pub fn placeholder(mut self, placeholder: &str) -> Self {
102        self.attributes
103            .push(Attribute::new("placeholder", placeholder));
104        self
105    }
106
107    pub fn disabled(mut self, disabled: bool) -> Self {
108        self.attributes
109            .push(Attribute::boolean("disabled", disabled));
110        self
111    }
112
113    pub fn text(mut self, text: impl Into<String>) -> Self {
114        self.children.push(Node::Text(text.into()));
115        self
116    }
117
118    pub fn text_reactive(mut self, text: impl IntoReactiveString) -> Self {
119        self.children
120            .push(Node::ReactiveText(text.into_reactive_string()));
121        self
122    }
123
124    pub fn child(mut self, child: impl IntoNode) -> Self {
125        self.children.push(child.into_node());
126        self
127    }
128
129    pub fn children<I, C>(mut self, children: I) -> Self
130    where
131        I: IntoIterator<Item = C>,
132        C: IntoNode,
133    {
134        for child in children {
135            self.children.push(child.into_node());
136        }
137        self
138    }
139
140    pub fn on_click<F>(mut self, handler: F) -> Self
141    where
142        F: Fn(Event) + 'static,
143    {
144        self.event_handlers
145            .push(EventHandler::new("click", handler));
146        self
147    }
148
149    pub fn on_input<F>(mut self, handler: F) -> Self
150    where
151        F: Fn(Event) + 'static,
152    {
153        self.event_handlers
154            .push(EventHandler::new("input", handler));
155        self
156    }
157
158    pub fn on_submit<F>(mut self, handler: F) -> Self
159    where
160        F: Fn(Event) + 'static,
161    {
162        self.event_handlers
163            .push(EventHandler::new("submit", handler));
164        self
165    }
166
167    pub fn on_change<F>(mut self, handler: F) -> Self
168    where
169        F: Fn(Event) + 'static,
170    {
171        self.event_handlers
172            .push(EventHandler::new("change", handler));
173        self
174    }
175
176    pub fn has_class(&self, class_name: &str) -> bool {
177        self.attributes.iter().any(|attr| {
178            attr.name == "class" && matches!(&attr.value, crate::attributes::AttributeValue::String(v) if v.contains(class_name))
179        })
180    }
181
182    pub fn event_handlers(&self) -> &[EventHandler] {
183        &self.event_handlers
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use crate::html::*;
190    use crate::reactive::SignalExt;
191    use react_rs_core::signal::create_signal;
192
193    #[test]
194    fn test_element_api() {
195        let element = div().class("container").child(h1().text("Hello"));
196
197        assert_eq!(element.tag(), "div");
198        assert!(element.has_class("container"));
199    }
200
201    #[test]
202    fn test_nested_elements() {
203        let view = div()
204            .class("app")
205            .child(nav().class("sidebar").child(ul().children([
206                li().child(a().href("/").text("Home")),
207                li().child(a().href("/about").text("About")),
208            ])))
209            .child(main_el().class("content").child(h1().text("Welcome")));
210
211        assert_eq!(view.tag(), "div");
212        assert!(view.has_class("app"));
213        assert_eq!(view.get_children().len(), 2);
214    }
215
216    #[test]
217    fn test_event_handlers() {
218        let clicked = std::cell::Cell::new(false);
219        let _button = button().text("Click me").on_click(|_| {});
220
221        assert!(!clicked.get());
222    }
223
224    #[test]
225    fn test_reactive_text() {
226        let (count, _set_count) = create_signal(0);
227        let _element = div().text_reactive(count.map(|n| format!("Count: {}", n)));
228    }
229
230    #[test]
231    fn test_reactive_class() {
232        let (active, _set_active) = create_signal(false);
233        let _element = div()
234            .class_reactive(active.map(|a| if *a { "active" } else { "inactive" }.to_string()));
235    }
236}