1use crate::*;
2
3impl Attribute {
5 pub fn as_str(&self) -> String {
7 match self {
8 Attribute::AccessKey => "accesskey".to_string(),
9 Attribute::Action => "action".to_string(),
10 Attribute::Alt => "alt".to_string(),
11 Attribute::AriaLabel => "aria-label".to_string(),
12 Attribute::AutoComplete => "autocomplete".to_string(),
13 Attribute::AutoFocus => "autofocus".to_string(),
14 Attribute::Checked => "checked".to_string(),
15 Attribute::Class => "class".to_string(),
16 Attribute::Cols => "cols".to_string(),
17 Attribute::ContentEditable => "contenteditable".to_string(),
18 Attribute::Data(name) => format!("data-{}", name),
19 Attribute::Dir => "dir".to_string(),
20 Attribute::Disabled => "disabled".to_string(),
21 Attribute::Draggable => "draggable".to_string(),
22 Attribute::EncType => "enctype".to_string(),
23 Attribute::For => "for".to_string(),
24 Attribute::Form => "form".to_string(),
25 Attribute::Height => "height".to_string(),
26 Attribute::Hidden => "hidden".to_string(),
27 Attribute::Href => "href".to_string(),
28 Attribute::Id => "id".to_string(),
29 Attribute::Lang => "lang".to_string(),
30 Attribute::Max => "max".to_string(),
31 Attribute::MaxLength => "maxlength".to_string(),
32 Attribute::Method => "method".to_string(),
33 Attribute::Min => "min".to_string(),
34 Attribute::MinLength => "minlength".to_string(),
35 Attribute::Multiple => "multiple".to_string(),
36 Attribute::Name => "name".to_string(),
37 Attribute::Pattern => "pattern".to_string(),
38 Attribute::Placeholder => "placeholder".to_string(),
39 Attribute::ReadOnly => "readonly".to_string(),
40 Attribute::Required => "required".to_string(),
41 Attribute::Rows => "rows".to_string(),
42 Attribute::Selected => "selected".to_string(),
43 Attribute::Size => "size".to_string(),
44 Attribute::SpellCheck => "spellcheck".to_string(),
45 Attribute::Src => "src".to_string(),
46 Attribute::Step => "step".to_string(),
47 Attribute::Style => "style".to_string(),
48 Attribute::TabIndex => "tabindex".to_string(),
49 Attribute::Target => "target".to_string(),
50 Attribute::Title => "title".to_string(),
51 Attribute::Type => "type".to_string(),
52 Attribute::Value => "value".to_string(),
53 Attribute::Width => "width".to_string(),
54 Attribute::Other(name) => name.clone(),
55 }
56 }
57}
58
59impl Clone for DynamicNode {
61 fn clone(&self) -> Self {
62 DynamicNode {
63 render_fn: Rc::clone(&self.render_fn),
64 hook_context: self.hook_context,
65 }
66 }
67}
68
69impl AsNode for VirtualNode {
71 fn as_node(&self) -> Option<VirtualNode> {
72 Some(self.clone())
73 }
74}
75
76impl AsNode for &VirtualNode {
78 fn as_node(&self) -> Option<VirtualNode> {
79 Some((*self).clone())
80 }
81}
82
83impl AsNode for String {
85 fn as_node(&self) -> Option<VirtualNode> {
86 Some(VirtualNode::Text(TextNode::new(self.clone(), None)))
87 }
88}
89
90impl AsNode for &str {
92 fn as_node(&self) -> Option<VirtualNode> {
93 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
94 }
95}
96
97impl AsNode for i32 {
99 fn as_node(&self) -> Option<VirtualNode> {
100 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
101 }
102}
103
104impl AsNode for i64 {
106 fn as_node(&self) -> Option<VirtualNode> {
107 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
108 }
109}
110
111impl AsNode for usize {
113 fn as_node(&self) -> Option<VirtualNode> {
114 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
115 }
116}
117
118impl AsNode for f32 {
120 fn as_node(&self) -> Option<VirtualNode> {
121 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
122 }
123}
124
125impl AsNode for f64 {
127 fn as_node(&self) -> Option<VirtualNode> {
128 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
129 }
130}
131
132impl AsNode for bool {
134 fn as_node(&self) -> Option<VirtualNode> {
135 Some(VirtualNode::Text(TextNode::new(self.to_string(), None)))
136 }
137}
138
139impl<T> AsNode for Signal<T>
141where
142 T: Clone + PartialEq + std::fmt::Display + 'static,
143{
144 fn as_node(&self) -> Option<VirtualNode> {
145 Some(self.as_reactive_text())
146 }
147}
148
149impl IntoNode for VirtualNode {
151 fn into_node(self) -> VirtualNode {
152 self
153 }
154}
155
156impl<F> IntoNode for F
161where
162 F: FnMut() -> VirtualNode + 'static,
163{
164 fn into_node(self) -> VirtualNode {
165 VirtualNode::Dynamic(DynamicNode {
166 render_fn: Rc::new(RefCell::new(self)),
167 hook_context: crate::reactive::create_hook_context(),
168 })
169 }
170}
171
172impl IntoNode for String {
174 fn into_node(self) -> VirtualNode {
175 VirtualNode::Text(TextNode::new(self, None))
176 }
177}
178
179impl IntoNode for &str {
181 fn into_node(self) -> VirtualNode {
182 VirtualNode::Text(TextNode::new(self.to_string(), None))
183 }
184}
185
186impl IntoNode for i32 {
188 fn into_node(self) -> VirtualNode {
189 VirtualNode::Text(TextNode::new(self.to_string(), None))
190 }
191}
192
193impl IntoNode for usize {
195 fn into_node(self) -> VirtualNode {
196 VirtualNode::Text(TextNode::new(self.to_string(), None))
197 }
198}
199
200impl IntoNode for bool {
202 fn into_node(self) -> VirtualNode {
203 VirtualNode::Text(TextNode::new(self.to_string(), None))
204 }
205}
206
207impl<T> IntoNode for Signal<T>
209where
210 T: Clone + PartialEq + std::fmt::Display + 'static,
211{
212 fn into_node(self) -> VirtualNode {
213 self.as_reactive_text()
214 }
215}
216
217impl VirtualNode {
219 pub fn get_element_node(tag_name: &str) -> Self {
221 VirtualNode::Element {
222 tag: Tag::Element(tag_name.to_string()),
223 attributes: Vec::new(),
224 children: Vec::new(),
225 key: None,
226 }
227 }
228
229 pub fn get_text_node(content: &str) -> Self {
231 VirtualNode::Text(TextNode::new(content.to_string(), None))
232 }
233
234 pub fn with_attribute(mut self, name: &str, value: AttributeValue) -> Self {
236 if let VirtualNode::Element {
237 ref mut attributes, ..
238 } = self
239 {
240 attributes.push(AttributeEntry::new(name.to_string(), value));
241 }
242 self
243 }
244
245 pub fn with_child(mut self, child: VirtualNode) -> Self {
247 if let VirtualNode::Element {
248 ref mut children, ..
249 } = self
250 {
251 children.push(child);
252 }
253 self
254 }
255
256 pub fn is_component(&self) -> bool {
258 matches!(
259 self,
260 VirtualNode::Element {
261 tag: Tag::Component(_),
262 ..
263 }
264 )
265 }
266
267 pub fn tag_name(&self) -> Option<String> {
269 match self {
270 VirtualNode::Element { tag, .. } => match tag {
271 Tag::Element(name) => Some(name.clone()),
272 Tag::Component(name) => Some(name.clone()),
273 },
274 _ => None,
275 }
276 }
277
278 pub fn try_get_prop(&self, name: &Attribute) -> Option<String> {
280 let name_str: String = name.as_str();
281 if let VirtualNode::Element { attributes, .. } = self {
282 for attr in attributes {
283 if attr.get_name() == &name_str {
284 match attr.get_value() {
285 AttributeValue::Text(value) => return Some(value.clone()),
286 AttributeValue::Signal(signal) => return Some(signal.get()),
287 _ => {}
288 }
289 }
290 }
291 }
292 None
293 }
294
295 pub fn get_children(&self) -> Vec<VirtualNode> {
297 if let VirtualNode::Element { children, .. } = self {
298 children.clone()
299 } else {
300 Vec::new()
301 }
302 }
303
304 pub fn try_get_text(&self) -> Option<String> {
306 match self {
307 VirtualNode::Text(text_node) => Some(text_node.get_content().clone()),
308 VirtualNode::Element { children, .. } => {
309 children.first().and_then(VirtualNode::try_get_text)
310 }
311 _ => None,
312 }
313 }
314
315 pub fn try_get_event(
317 &self,
318 name: &NativeEventName,
319 ) -> Option<crate::event::NativeEventHandler> {
320 let name_str: String = name.as_str();
321 if let VirtualNode::Element { attributes, .. } = self {
322 for attr in attributes {
323 if attr.get_name() == &name_str
324 && let AttributeValue::Event(handler) = attr.get_value()
325 {
326 return Some(handler.clone());
327 }
328 }
329 }
330 None
331 }
332
333 pub fn try_get_callback(&self, name: &str) -> Option<crate::event::NativeEventHandler> {
335 if let VirtualNode::Element { attributes, .. } = self {
336 for attr in attributes {
337 if attr.get_name() == name
338 && let AttributeValue::Event(handler) = attr.get_value()
339 {
340 return Some(handler.clone());
341 }
342 }
343 }
344 None
345 }
346}
347
348impl<T> AsReactiveText for Signal<T>
350where
351 T: Clone + PartialEq + std::fmt::Display + 'static,
352{
353 fn as_reactive_text(&self) -> VirtualNode {
354 let signal: Signal<T> = *self;
355 let initial: String = signal.get().to_string();
356 let string_signal: Signal<String> = {
357 let boxed: Box<SignalInner<String>> = Box::new(SignalInner::new(initial.clone()));
358 Signal::from_inner(Box::leak(boxed) as *mut SignalInner<String>)
359 };
360 let source_signal: Signal<T> = *self;
361 let string_signal_clone: Signal<String> = string_signal;
362 source_signal.subscribe({
363 let source_signal: Signal<T> = source_signal;
364 move || {
365 let new_value: String = source_signal.get().to_string();
366 string_signal_clone.set(new_value);
367 }
368 });
369 VirtualNode::Text(TextNode::new(initial, Some(string_signal)))
370 }
371}
372
373impl Style {
375 pub fn property<N, V>(mut self, name: N, value: V) -> Self
380 where
381 N: AsRef<str>,
382 V: AsRef<str>,
383 {
384 self.get_mut_properties().push(StyleProperty::new(
385 name.as_ref().replace('_', "-"),
386 value.as_ref().to_string(),
387 ));
388 self
389 }
390
391 pub fn to_css_string(&self) -> String {
393 self.get_properties()
394 .iter()
395 .map(|p| format!("{}: {};", p.get_name(), p.get_value()))
396 .collect::<Vec<String>>()
397 .join(" ")
398 }
399}
400
401impl Default for Style {
403 fn default() -> Self {
404 Self::new(Vec::new())
405 }
406}
407
408impl StyleProperty {
410 pub fn new(name: String, value: String) -> Self {
412 let mut prop: StyleProperty = StyleProperty::default();
413 prop.set_name(name);
414 prop.set_value(value);
415 prop
416 }
417}
418
419impl CssClass {
421 pub fn new(name: String, style: String) -> Self {
423 let mut css_class: CssClass = CssClass::default();
424 css_class.set_name(name);
425 css_class.set_style(style);
426 css_class
427 }
428
429 pub fn inject_style(&self) {
436 #[cfg(target_arch = "wasm32")]
437 {
438 let style_id: &str = "euv-css-injected";
439 let document: web_sys::Document = web_sys::window()
440 .expect("no global window exists")
441 .document()
442 .expect("no document exists");
443 let style_element: web_sys::HtmlStyleElement = match document
444 .get_element_by_id(style_id)
445 {
446 Some(el) => el.dyn_into::<web_sys::HtmlStyleElement>().unwrap(),
447 None => {
448 let el: web_sys::HtmlStyleElement = document
449 .create_element("style")
450 .unwrap()
451 .dyn_into::<web_sys::HtmlStyleElement>()
452 .unwrap();
453 el.set_id(style_id);
454 let keyframes: &str = "@keyframes euv-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }";
455 el.set_inner_text(keyframes);
456 document.head().unwrap().append_child(&el).unwrap();
457 el
458 }
459 };
460 let existing_css: String = style_element.inner_text();
461 let class_rule: String = format!(".{} {{ {} }}", self.get_name(), self.get_style());
462 if !existing_css.contains(&class_rule) {
463 let new_css: String = if existing_css.is_empty() {
464 class_rule
465 } else {
466 format!("{}\n{}", existing_css, class_rule)
467 };
468 style_element.set_inner_text(&new_css);
469 }
470 }
471 }
472}