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