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 try_get_signal_prop(&self, name: &Attribute) -> Option<Signal<String>> {
300 let name_str: String = name.as_str();
301 if let VirtualNode::Element { attributes, .. } = self {
302 for attr in attributes {
303 if attr.get_name() == &name_str
304 && let AttributeValue::Signal(signal) = attr.get_value()
305 {
306 return Some(*signal);
307 }
308 }
309 }
310 None
311 }
312
313 pub fn get_children(&self) -> Vec<VirtualNode> {
315 if let VirtualNode::Element { children, .. } = self {
316 children.clone()
317 } else {
318 Vec::new()
319 }
320 }
321
322 pub fn try_get_text(&self) -> Option<String> {
324 match self {
325 VirtualNode::Text(text_node) => Some(text_node.get_content().clone()),
326 VirtualNode::Element { children, .. } => {
327 children.first().and_then(VirtualNode::try_get_text)
328 }
329 _ => None,
330 }
331 }
332
333 pub fn try_get_event(
335 &self,
336 name: &NativeEventName,
337 ) -> Option<crate::event::NativeEventHandler> {
338 let name_str: String = name.as_str();
339 if let VirtualNode::Element { attributes, .. } = self {
340 for attr in attributes {
341 if attr.get_name() == &name_str
342 && let AttributeValue::Event(handler) = attr.get_value()
343 {
344 return Some(handler.clone());
345 }
346 }
347 }
348 None
349 }
350
351 pub fn try_get_callback(&self, name: &str) -> Option<crate::event::NativeEventHandler> {
353 if let VirtualNode::Element { attributes, .. } = self {
354 for attr in attributes {
355 if attr.get_name() == name
356 && let AttributeValue::Event(handler) = attr.get_value()
357 {
358 return Some(handler.clone());
359 }
360 }
361 }
362 None
363 }
364}
365
366impl<T> AsReactiveText for Signal<T>
368where
369 T: Clone + PartialEq + std::fmt::Display + 'static,
370{
371 fn as_reactive_text(&self) -> VirtualNode {
372 let signal: Signal<T> = *self;
373 let initial: String = signal.get().to_string();
374 let string_signal: Signal<String> = {
375 let boxed: Box<SignalInner<String>> = Box::new(SignalInner::new(initial.clone()));
376 Signal::from_inner(Box::leak(boxed) as *mut SignalInner<String>)
377 };
378 let source_signal: Signal<T> = *self;
379 let string_signal_clone: Signal<String> = string_signal;
380 source_signal.subscribe({
381 let source_signal: Signal<T> = source_signal;
382 move || {
383 let new_value: String = source_signal.get().to_string();
384 string_signal_clone.set(new_value);
385 }
386 });
387 VirtualNode::Text(TextNode::new(initial, Some(string_signal)))
388 }
389}
390
391impl Style {
393 pub fn property<N, V>(mut self, name: N, value: V) -> Self
398 where
399 N: AsRef<str>,
400 V: AsRef<str>,
401 {
402 self.get_mut_properties().push(StyleProperty::new(
403 name.as_ref().replace('_', "-"),
404 value.as_ref().to_string(),
405 ));
406 self
407 }
408
409 pub fn to_css_string(&self) -> String {
411 self.get_properties()
412 .iter()
413 .map(|p| format!("{}: {};", p.get_name(), p.get_value()))
414 .collect::<Vec<String>>()
415 .join(" ")
416 }
417}
418
419impl Default for Style {
421 fn default() -> Self {
422 Self::new(Vec::new())
423 }
424}
425
426impl StyleProperty {
428 pub fn new(name: String, value: String) -> Self {
430 let mut prop: StyleProperty = StyleProperty::default();
431 prop.set_name(name);
432 prop.set_value(value);
433 prop
434 }
435}
436
437impl CssClass {
439 pub fn new(name: String, style: String) -> Self {
441 let mut css_class: CssClass = CssClass::default();
442 css_class.set_name(name);
443 css_class.set_style(style);
444 css_class
445 }
446
447 pub fn inject_style(&self) {
454 #[cfg(target_arch = "wasm32")]
455 {
456 let style_id: &str = "euv-css-injected";
457 let document: web_sys::Document = web_sys::window()
458 .expect("no global window exists")
459 .document()
460 .expect("no document exists");
461 let style_element: web_sys::HtmlStyleElement = match document
462 .get_element_by_id(style_id)
463 {
464 Some(el) => el.dyn_into::<web_sys::HtmlStyleElement>().unwrap(),
465 None => {
466 let el: web_sys::HtmlStyleElement = document
467 .create_element("style")
468 .unwrap()
469 .dyn_into::<web_sys::HtmlStyleElement>()
470 .unwrap();
471 el.set_id(style_id);
472 let keyframes: &str = "@keyframes euv-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes euv-fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes euv-scale-in { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } } @keyframes euv-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.2); } } @keyframes euv-slide-up { from { transform: translateY(100%); } to { transform: translateY(0); } }";
473 let global: &str = "html, body, #app { height: 100%; margin: 0; padding: 0; overflow: hidden; }";
474 el.set_inner_text(&format!("{} {}", global, keyframes));
475 document.head().unwrap().append_child(&el).unwrap();
476 el
477 }
478 };
479 let existing_css: String = style_element.inner_text();
480 let class_rule: String = format!(".{} {{ {} }}", self.get_name(), self.get_style());
481 if !existing_css.contains(&class_rule) {
482 let new_css: String = if existing_css.is_empty() {
483 class_rule
484 } else {
485 format!("{}\n{}", existing_css, class_rule)
486 };
487 style_element.set_inner_text(&new_css);
488 }
489 }
490 }
491}