1pub mod html;
12
13use std::rc::Rc;
14
15pub mod html_tags {
16 pub use rustolio_web_core_macros::*;
19}
20
21#[derive(Debug, Clone)]
22pub struct Element {
23 pub(crate) inner: InnerElement,
24 pub(crate) attributes: html::Attributes,
25}
26
27#[derive(Debug, Clone)]
28pub(crate) enum InnerElement {
29 HtmlTag {
30 tag: &'static str,
31 children: Elements,
32 namespace: Option<&'static str>,
33 },
34 Text(Rc<str>),
35 Raw {
36 raw: &'static str,
37 supported_type: web_sys::SupportedType,
38 },
39 None,
40}
41
42impl Default for Element {
43 fn default() -> Self {
44 Element::empty()
45 }
46}
47
48impl Element {
49 pub fn html(tag: &'static str, attributes: html::Attributes, children: Elements) -> Self {
50 Self {
51 inner: InnerElement::HtmlTag {
52 tag,
53 children,
54 namespace: None,
55 },
56 attributes,
57 }
58 }
59
60 pub fn svg(tag: &'static str, attributes: html::Attributes, children: Elements) -> Self {
61 Self {
62 inner: InnerElement::HtmlTag {
63 tag,
64 children,
65 namespace: Some("http://www.w3.org/2000/svg"),
66 },
67 attributes,
68 }
69 }
70
71 pub fn text(text: impl ToString) -> Self {
72 Self {
73 inner: InnerElement::Text(text.to_string().into()),
74 attributes: html::Attributes::default(),
75 }
76 }
77
78 fn text_rc(rc: Rc<str>) -> Self {
79 Self {
80 inner: InnerElement::Text(rc),
81 attributes: html::Attributes::default(),
82 }
83 }
84
85 pub fn raw(
86 attributes: html::Attributes,
87 raw: &'static str,
88 supported_type: web_sys::SupportedType,
89 ) -> Self {
90 Self {
91 inner: InnerElement::Raw {
92 raw,
93 supported_type,
94 },
95 attributes,
96 }
97 }
98
99 pub fn option(option: Option<impl Into<Element>>) -> Self {
100 match option {
101 Some(e) => e.into(),
102 None => Element::empty(),
103 }
104 }
105
106 pub fn result(result: crate::Result<impl Into<Element>>) -> Self {
107 crate::error::match_result("Element", result.map(|e| e.into()))
108 }
109
110 pub fn empty() -> Self {
111 Self {
112 inner: InnerElement::None,
113 attributes: html::Attributes::default(),
114 }
115 }
116
117 pub fn is_empty(&self) -> bool {
118 match &self.inner {
119 InnerElement::None => true,
120 InnerElement::Text(s) => s.trim().is_empty(),
121 _ => false,
122 }
123 }
124
125 pub fn is_html(&self) -> bool {
128 !matches!(&self.inner, InnerElement::Text(_))
129 }
130
131 pub fn non_empty(self) -> Option<Self> {
132 if self.is_empty() {
133 return None;
134 }
135 Some(self)
136 }
137
138 pub fn add_attribute(
139 &mut self,
140 name: &'static str,
141 value: impl Into<html::AttributeValue>,
142 ) -> bool {
143 if !self.is_html() {
144 return false;
145 }
146 self.attributes.add_attribute(name, value);
147 true
148 }
149
150 pub fn add_listener(&mut self, listener: &html::Listener) -> bool {
151 if !self.is_html() {
152 return false;
153 }
154 self.attributes.add_listener(listener);
155 true
156 }
157}
158
159pub struct ElementWrapper(Element);
160pub trait IntoElementWrapper {
161 fn into_wrapper(self) -> ElementWrapper;
162}
163impl<T> IntoElementWrapper for T
164where
165 T: Into<Element>,
166{
167 fn into_wrapper(self) -> ElementWrapper {
168 ElementWrapper(self.into())
169 }
170}
171impl From<ElementWrapper> for Element {
172 #[inline]
173 fn from(val: ElementWrapper) -> Self {
174 val.0
175 }
176}
177impl From<ElementWrapper> for Elements {
178 #[inline]
179 fn from(val: ElementWrapper) -> Self {
180 Elements::single(val.0)
181 }
182}
183impl Element {
184 #[inline]
185 pub fn into_wrapper(self) -> ElementWrapper {
186 ElementWrapper(self)
187 }
188}
189
190pub struct StringWrapper(Rc<str>);
191impl From<StringWrapper> for Element {
192 #[inline]
193 fn from(val: StringWrapper) -> Self {
194 Element::text_rc(val.0)
195 }
196}
197impl From<StringWrapper> for Elements {
198 #[inline]
199 fn from(val: StringWrapper) -> Self {
200 Elements::single(Element::text_rc(val.0))
201 }
202}
203pub trait IntoStringWrapper {
204 fn into_wrapper(&self) -> StringWrapper;
205}
206macro_rules! impl_from_tostring {
207 ($($impl_ty:ty)*) => {
208 $(
209 impl IntoStringWrapper for $impl_ty {
210 fn into_wrapper(&self) -> StringWrapper {
211 StringWrapper(self.to_string().into())
212 }
213 }
214 impl From<$impl_ty> for Element {
215 fn from(value: $impl_ty) -> Self {
216 Element::text(value)
217 }
218 }
219 )*
220 };
221}
222impl_from_tostring! { u8 u16 u32 u64 u128 usize }
223impl_from_tostring! { i8 i16 i32 i64 i128 isize }
224impl_from_tostring! { f32 f64 }
225impl_from_tostring! { &str char String &String }
226impl_from_tostring! { bool }
227
228impl IntoStringWrapper for Rc<str> {
229 fn into_wrapper(&self) -> StringWrapper {
230 StringWrapper(self.clone())
231 }
232}
233impl From<Rc<str>> for Element {
234 fn from(value: Rc<str>) -> Self {
235 Element::text_rc(value)
236 }
237}
238
239impl<T> From<Option<T>> for Element
240where
241 T: Into<Element>,
242{
243 #[inline]
244 fn from(val: Option<T>) -> Self {
245 Element::option(val)
246 }
247}
248impl<T> From<crate::Result<T>> for Element
249where
250 T: Into<Element>,
251{
252 #[inline]
253 fn from(val: crate::Result<T>) -> Self {
254 Element::result(val)
255 }
256}
257
258use wasm_bindgen::prelude::*;
259#[wasm_bindgen]
260#[derive(Debug, Clone)]
261pub struct Elements {
262 pub(crate) inner: InnerElements,
263}
264
265#[derive(Clone)]
266pub(crate) enum InnerElements {
267 Element(Rc<Element>),
268 Elements(Rc<[Elements]>),
269 Overlay(Rc<dyn Fn() -> Element>),
270 Dynamic(Rc<dyn Fn() -> Element>),
271}
272
273impl std::fmt::Debug for InnerElements {
274 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275 match self {
276 Self::Element(e) => f.debug_tuple("Element").field(e).finish(),
277 Self::Elements(e) => f.debug_tuple("Elements").field(e).finish(),
278 Self::Overlay(_) => f.debug_tuple("Overlay").field(&"Funtion").finish(),
279 Self::Dynamic(_) => f.debug_tuple("Dynamic").field(&"Function").finish(),
280 }
281 }
282}
283
284impl Default for Elements {
285 fn default() -> Self {
286 Self::empty()
287 }
288}
289
290impl Elements {
291 pub fn single(element: impl Into<Element>) -> Self {
292 Self {
293 inner: InnerElements::Element(Rc::new(element.into())),
294 }
295 }
296
297 pub fn iter(iter: impl IntoIterator<Item = Elements>) -> Self {
298 Self {
299 inner: InnerElements::Elements(iter.into_iter().collect()),
300 }
301 }
302
303 pub fn option(option: Option<impl Into<Elements>>) -> Self {
304 match option {
305 Some(e) => e.into(),
306 None => Elements::empty(),
307 }
308 }
309
310 pub fn result(result: crate::Result<impl Into<Elements>>) -> Self {
311 crate::error::match_result("Element", result.map(|e| e.into()))
312 }
313
314 pub fn dynamic(f: impl Fn() -> Element + 'static) -> Self {
315 Self {
316 inner: InnerElements::Dynamic(Rc::new(f)),
317 }
318 }
319
320 pub fn overlay(f: impl Fn() -> Element + 'static) -> Self {
321 Self {
322 inner: InnerElements::Overlay(Rc::new(f)),
323 }
324 }
325
326 pub fn empty() -> Self {
327 Self {
328 inner: InnerElements::Element(Rc::new(Element::empty())),
329 }
330 }
331
332 pub fn is_empty(&self) -> bool {
333 match &self.inner {
334 InnerElements::Dynamic(_) | InnerElements::Overlay(_) => false,
335 InnerElements::Element(e) => e.is_empty(),
336 InnerElements::Elements(v) => v.is_empty() && v.iter().all(|e| e.is_empty()),
337 }
338 }
339
340 pub fn non_empty(self) -> Option<Self> {
341 if self.is_empty() {
342 return None;
343 }
344 Some(self)
345 }
346}
347
348impl IntoIterator for Elements {
349 type IntoIter = ElementsIter;
350 type Item = Elements;
351
352 fn into_iter(self) -> Self::IntoIter {
353 if let InnerElements::Elements(elements) = self.inner {
354 return ElementsIter {
355 elements: None,
356 elements_iter: elements
357 .iter()
358 .cloned()
359 .collect::<Vec<_>>()
360 .into_iter(),
361 };
362 }
363 ElementsIter {
364 elements: Some(self),
365 elements_iter: Vec::new().into_iter(),
366 }
367 }
368}
369pub struct ElementsIter {
370 elements: Option<Elements>,
371 elements_iter: <Vec<Elements> as IntoIterator>::IntoIter,
372}
373impl Iterator for ElementsIter {
374 type Item = Elements;
375 fn next(&mut self) -> Option<Self::Item> {
376 if let Some(elements) = self.elements_iter.next() {
377 return Some(elements);
378 }
379 let Some(elements) = self.elements.take() else {
380 return None;
381 };
382 if elements.is_empty() {
383 return None;
384 }
385 Some(elements)
386 }
387}
388
389impl From<Element> for Elements {
390 #[inline]
391 fn from(val: Element) -> Self {
392 Elements::single(val)
393 }
394}
395
396pub struct ElementsWrapper(Elements);
397impl From<ElementsWrapper> for Elements {
398 #[inline]
399 fn from(val: ElementsWrapper) -> Self {
400 val.0
401 }
402}
403impl Elements {
404 #[inline]
405 pub fn into_wrapper(self) -> ElementsWrapper {
406 ElementsWrapper(self)
407 }
408}
409pub trait IntoElementsWrapper {
410 fn into_wrapper(self) -> ElementsWrapper;
411}
412impl<T> IntoElementsWrapper for crate::Result<T>
413where
414 T: Into<Elements>,
415{
416 fn into_wrapper(self) -> ElementsWrapper {
417 ElementsWrapper(Elements::result(self))
418 }
419}
420
421pub struct ElementClosureWrapper(Rc<dyn Fn() -> Element>);
423impl From<ElementClosureWrapper> for Elements {
424 #[inline]
425 fn from(val: ElementClosureWrapper) -> Self {
426 Elements {
427 inner: InnerElements::Dynamic(val.0),
428 }
429 }
430}
431pub trait IntoElementClosureWrapper {
432 fn into_wrapper(self) -> ElementClosureWrapper;
433}
434impl<F, E> IntoElementClosureWrapper for F
435where
436 F: Fn() -> E + 'static,
437 E: Into<Element>,
438{
439 #[inline]
440 fn into_wrapper(self) -> ElementClosureWrapper {
441 ElementClosureWrapper(Rc::new(move || self().into()))
442 }
443}
444pub struct ElementsIteratorWrapper(Rc<[Elements]>);
478impl From<ElementsIteratorWrapper> for Elements {
479 #[inline]
480 fn from(val: ElementsIteratorWrapper) -> Self {
481 Elements {
482 inner: InnerElements::Elements(val.0),
483 }
484 }
485}
486pub trait IntoElementIteratorWrapper {
487 #[allow(private_interfaces)]
488 fn into_wrapper(self) -> ElementsIteratorWrapper;
489}
490impl<I, Item> IntoElementIteratorWrapper for I
491where
492 I: Iterator<Item = Item>,
493 Item: Into<Element>,
494{
495 #[allow(private_interfaces)]
496 #[inline]
497 fn into_wrapper(self) -> ElementsIteratorWrapper {
498 ElementsIteratorWrapper(self.map(|i| Elements::single(i)).collect())
499 }
500}
501pub trait IntoElementsIteratorWrapper {
523 fn into_wrapper(self) -> ElementsIteratorWrapper;
524}
525impl<I> IntoElementsIteratorWrapper for I
526where
527 I: Iterator<Item = Elements>,
528{
529 #[inline]
530 fn into_wrapper(self) -> ElementsIteratorWrapper {
531 ElementsIteratorWrapper(self.collect())
532 }
533}
534pub trait RcElementsSliceWrapper {
551 fn into_wrapper(self) -> ElementsIteratorWrapper;
552}
553impl RcElementsSliceWrapper for Rc<[Elements]> {
554 #[inline]
555 fn into_wrapper(self) -> ElementsIteratorWrapper {
556 ElementsIteratorWrapper(self)
557 }
558}
559
560pub trait RcElementSliceWrapper {
562 fn into_wrapper(self) -> ElementsIteratorWrapper;
563}
564impl RcElementSliceWrapper for Rc<[Element]> {
565 #[inline]
566 fn into_wrapper(self) -> ElementsIteratorWrapper {
567 ElementsIteratorWrapper(
568 self.iter()
569 .map(|e| Elements::single(e.clone()))
570 .collect(),
571 )
572 }
573}
574
575impl<T> From<Option<T>> for Elements
593where
594 T: Into<Elements>,
595{
596 #[inline]
597 fn from(val: Option<T>) -> Self {
598 Elements::option(val)
599 }
600}
601impl<T> From<crate::Result<T>> for Elements
602where
603 T: Into<Elements>,
604{
605 #[inline]
606 fn from(val: crate::Result<T>) -> Self {
607 Elements::result(val)
608 }
609}
610
611#[cfg(test)]
612#[allow(unused)]
613mod tests {
614 use crate::prelude::*;
615
616 fn raw_html() {
617 Element::html(
618 "div",
619 html::Attributes::new(),
620 elements![
621 String::new(),
622 Element::html("div", html::Attributes::new(), elements![]),
623 (|| String::new()),
624 [String::new()].into_iter().map(|i| i),
625 (vec![1].into_iter()),
626 ],
627 );
628 }
629
630 fn html() -> Element {
631 HtmlComponentWithOptional::builder().f2(None).f5("").build();
632 div! {
633 a! {},
634 Some(a! {}),
635 None::<Element>,
636 td! {
637 "Test"
638 },
639 span! {
640 p! {
641 "Nested"
642 }
643 },
644 div! {
645 p! {},
646 "Mixed",
647 },
648 Some("tet"),
649 div! {
650 Element::option(Some(a! {})),
651 },
652 div! {
653 "Mixed",
654 p! {},
655 html_fn(),
656 HtmlComponentWithAttributes {
657 f1: String::new(),
658 with:class: "test",
659 with:class: || "test",
660 with:disabled: true,
661 with:selected: || true,
662 on:click: |_: Event| {},
663 },
664 HtmlComponentWithChildren {
665 f1: String::new(),
666 "Test"
667 },
668 HtmlComponentWithChildren {
669 f1: String::new(),
670 html_fn()
671 },
672 HtmlComponentWithAttributesAndChildren {
673 f1: String::new(),
674 },
675 HtmlComponentWithAttributesAndChildren {
676 f1: String::new(),
677 with:class: "Test",
678 "Test"
679 },
680 HtmlComponentWithGenerics::<_, _, 4, _> {
681 f1: &String::from(""),
682 f2: String::new(),
683 f3: String::new()
684 },
685 crate::elements::tests::HtmlComponent {},
686 HtmlComponentWithChild {
687 f1: "",
688 "",
689 },
690 },
691 div! {
692 id: "id",
693 on:click: move |_: Event| {},
694 class: "only attributes",
695 },
696 div! {
697 id: "id",
698 class: "attributes",
699 "and Text"
700 },
701 div! {
702 id: "id",
703 class: "attributes",
704 "and Text"
705 },
706 div! {
707 id: "id",
708 class: "attributes",
709 crate::elements::tests::HtmlComponent {},
710 },
711 elements! {},
712 elements! {
713 "Test"
714 },
715 elements! {
716 div! {}
717 },
718 elements! {
719 [1, 2, 3].into_iter()
720 },
721 elements! {
722 std::iter::once(a! {})
723 },
724 elements! {
725 "Test",
726 html_fn(),
727 div! {},
728 1..4,
729 (1..6).map(|i| div! { i }),
730 1..,
731 [div! {}, a! {}].into_iter(),
732 [
733 Ok(div! {}),
734 Ok(a! {}),
735 Err(crate::Error::new("ERROR")),
736 ].into_iter(),
737 move || a! {},
738 move || Ok(a! {}),
739 move || Err::<Element, _>(crate::Error::new("ERROR")),
740 },
741 }
742 }
743
744 fn html_fn() -> Element {
745 div! {
746 with:class: "test",
747 on:click: |_: Event| {},
748 "Test"
749 }
750 }
751
752 #[component]
753 fn HtmlComponent() -> Element {
754 Element::text("")
755 }
756
757 #[component]
758 fn ResultHtmlComponent() -> crate::Result<Element> {
759 Ok(Element::text(""))
760 }
761
762 #[component]
763 fn HtmlComponentWithAttributes(f1: String, attributes: html::Attributes) -> Element {
764 div! {
765 html_fn()
766 }
767 }
768
769 #[component]
770 fn HtmlComponentWithChildren(f1: String, children: Elements) -> Element {
771 div! {
772 html_fn()
773 }
774 }
775
776 #[component]
777 fn HtmlComponentWithChild(#[prop(into)] f1: String, children: Element) -> Element {
778 div! {
779 html_fn()
780 }
781 }
782
783 #[component]
784 fn HtmlComponentWithAttributesAndChildren(
785 f1: String,
786 attributes: html::Attributes,
787 children: Elements,
788 ) -> Element {
789 div! {
790 html_fn()
791 }
792 }
793
794 #[component]
795 fn HtmlComponentWithGenerics<'a, G: ToString, G1, const N: usize>(
796 f1: &'a G,
797 f2: impl Clone,
798 f3: G1,
799 ) -> Element
800 where
801 G1: Default,
802 {
803 f1.to_string();
804 f2.clone();
805 G1::default();
806 div! {
807 html_fn()
808 }
809 }
810
811 #[component]
812 fn HtmlComponentWithOptional(
813 f1: Option<String>,
814 #[prop(required, into)] f2: Option<String>,
815 #[prop(default)] f3: String,
816 #[prop(default = String::from("default"), into)] f4: String,
817 #[prop(into)] f5: String,
818 ) -> Element {
819 let s = ToStringStruct {
820 f1: String::from("test"),
821 };
822 div! {
823 "",
824 false,
825 || "",
826 || true,
827 s.to_string(),
828 move || s.clone().to_string(),
829 }
830 }
831
832 #[derive(Debug, Clone)]
833 struct ToStringStruct {
834 f1: String,
835 }
836 impl std::fmt::Display for ToStringStruct {
837 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
838 write!(f, "self:?")
839 }
840 }
841}