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 styled(self, style: crate::style::Style) -> Self {
67        let css = style.to_css();
68        if css.is_empty() {
69            self
70        } else {
71            self.style(&css)
72        }
73    }
74
75    pub fn attr(mut self, name: &str, value: &str) -> Self {
76        self.attributes.push(Attribute::new(name, value));
77        self
78    }
79
80    pub fn href(mut self, href: &str) -> Self {
81        self.attributes.push(Attribute::new("href", href));
82        self
83    }
84
85    pub fn src(mut self, src: &str) -> Self {
86        self.attributes.push(Attribute::new("src", src));
87        self
88    }
89
90    pub fn alt(mut self, alt: &str) -> Self {
91        self.attributes.push(Attribute::new("alt", alt));
92        self
93    }
94
95    pub fn type_(mut self, type_value: &str) -> Self {
96        self.attributes.push(Attribute::new("type", type_value));
97        self
98    }
99
100    pub fn input_type(self, input_type: crate::types::InputType) -> Self {
101        self.type_(input_type.as_str())
102    }
103
104    pub fn target(mut self, target: crate::types::LinkTarget) -> Self {
105        self.attributes
106            .push(Attribute::new("target", target.as_str()));
107        self
108    }
109
110    pub fn method(mut self, method: crate::types::FormMethod) -> Self {
111        self.attributes
112            .push(Attribute::new("method", method.as_str()));
113        self
114    }
115
116    pub fn name(mut self, name: &str) -> Self {
117        self.attributes.push(Attribute::new("name", name));
118        self
119    }
120
121    pub fn value(mut self, value: &str) -> Self {
122        self.attributes.push(Attribute::new("value", value));
123        self
124    }
125
126    pub fn value_reactive(mut self, value: impl IntoReactiveString) -> Self {
127        self.attributes.push(Attribute::reactive_string(
128            "value",
129            value.into_reactive_string(),
130        ));
131        self
132    }
133
134    pub fn bind_value(
135        self,
136        read: react_rs_core::signal::ReadSignal<String>,
137        write: react_rs_core::signal::WriteSignal<String>,
138    ) -> Self {
139        use crate::reactive::SignalExt;
140        self.value_reactive(read.map(|s| s.clone()))
141            .on_input(move |e| {
142                write.set(e.value().to_string());
143            })
144    }
145
146    pub fn placeholder(mut self, placeholder: &str) -> Self {
147        self.attributes
148            .push(Attribute::new("placeholder", placeholder));
149        self
150    }
151
152    pub fn disabled(mut self, disabled: bool) -> Self {
153        self.attributes
154            .push(Attribute::boolean("disabled", disabled));
155        self
156    }
157
158    pub fn text(mut self, text: impl Into<String>) -> Self {
159        self.children.push(Node::Text(text.into()));
160        self
161    }
162
163    pub fn text_reactive(mut self, text: impl IntoReactiveString) -> Self {
164        self.children
165            .push(Node::ReactiveText(text.into_reactive_string()));
166        self
167    }
168
169    pub fn child(mut self, child: impl IntoNode) -> Self {
170        self.children.push(child.into_node());
171        self
172    }
173
174    pub fn children<I, C>(mut self, children: I) -> Self
175    where
176        I: IntoIterator<Item = C>,
177        C: IntoNode,
178    {
179        for child in children {
180            self.children.push(child.into_node());
181        }
182        self
183    }
184
185    pub fn on_click<F>(mut self, handler: F) -> Self
186    where
187        F: Fn(Event) + 'static,
188    {
189        self.event_handlers
190            .push(EventHandler::new("click", handler));
191        self
192    }
193
194    pub fn on_input<F>(mut self, handler: F) -> Self
195    where
196        F: Fn(Event) + 'static,
197    {
198        self.event_handlers
199            .push(EventHandler::new("input", handler));
200        self
201    }
202
203    pub fn on_submit<F>(mut self, handler: F) -> Self
204    where
205        F: Fn(Event) + 'static,
206    {
207        self.event_handlers
208            .push(EventHandler::new("submit", handler));
209        self
210    }
211
212    pub fn on_change<F>(mut self, handler: F) -> Self
213    where
214        F: Fn(Event) + 'static,
215    {
216        self.event_handlers
217            .push(EventHandler::new("change", handler));
218        self
219    }
220
221    pub fn show_when(self, condition: impl IntoReactiveBool) -> crate::node::Node {
222        crate::node::Node::Conditional(
223            condition.into_reactive_bool(),
224            Box::new(self.into_node()),
225            None,
226        )
227    }
228
229    pub fn show_when_else(
230        self,
231        condition: impl IntoReactiveBool,
232        else_node: impl IntoNode,
233    ) -> crate::node::Node {
234        crate::node::Node::Conditional(
235            condition.into_reactive_bool(),
236            Box::new(self.into_node()),
237            Some(Box::new(else_node.into_node())),
238        )
239    }
240
241    pub fn has_class(&self, class_name: &str) -> bool {
242        self.attributes.iter().any(|attr| {
243            attr.name == "class" && matches!(&attr.value, crate::attributes::AttributeValue::String(v) if v.contains(class_name))
244        })
245    }
246
247    pub fn event_handlers(&self) -> &[EventHandler] {
248        &self.event_handlers
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use crate::html::*;
255    use crate::reactive::SignalExt;
256    use react_rs_core::signal::create_signal;
257
258    #[test]
259    fn test_element_api() {
260        let element = div().class("container").child(h1().text("Hello"));
261
262        assert_eq!(element.tag(), "div");
263        assert!(element.has_class("container"));
264    }
265
266    #[test]
267    fn test_nested_elements() {
268        let view = div()
269            .class("app")
270            .child(nav().class("sidebar").child(ul().children([
271                li().child(a().href("/").text("Home")),
272                li().child(a().href("/about").text("About")),
273            ])))
274            .child(main_el().class("content").child(h1().text("Welcome")));
275
276        assert_eq!(view.tag(), "div");
277        assert!(view.has_class("app"));
278        assert_eq!(view.get_children().len(), 2);
279    }
280
281    #[test]
282    fn test_event_handlers() {
283        let clicked = std::cell::Cell::new(false);
284        let _button = button().text("Click me").on_click(|_| {});
285
286        assert!(!clicked.get());
287    }
288
289    #[test]
290    fn test_reactive_text() {
291        let (count, _set_count) = create_signal(0);
292        let _element = div().text_reactive(count.map(|n| format!("Count: {}", n)));
293    }
294
295    #[test]
296    fn test_reactive_class() {
297        let (active, _set_active) = create_signal(false);
298        let _element = div()
299            .class_reactive(active.map(|a| if *a { "active" } else { "inactive" }.to_string()));
300    }
301}