1#[cfg(any(debug_assertions, leptos_debuginfo))]
2use crate::hydration::set_currently_hydrating;
3#[cfg(erase_components)]
4use crate::view::any_view::AnyView;
5use crate::{
6 html::attribute::Attribute,
7 hydration::{failed_to_cast_element, Cursor},
8 renderer::{CastFrom, Rndr},
9 ssr::StreamBuilder,
10 view::{
11 add_attr::AddAnyAttr, IntoRender, Mountable, Position, PositionState,
12 Render, RenderHtml, ToTemplate,
13 },
14};
15use const_str_slice_concat::{
16 const_concat, const_concat_with_prefix, str_from_buffer,
17};
18use futures::future::join;
19use std::ops::Deref;
20
21mod custom;
22mod element_ext;
23mod elements;
24mod inner_html;
25use super::attribute::{
26 any_attribute::AnyAttribute, escape_attr, NextAttribute,
27};
28pub use custom::*;
29pub use element_ext::*;
30pub use elements::*;
31pub use inner_html::*;
32#[cfg(any(debug_assertions, leptos_debuginfo))]
33use std::panic::Location;
34
35#[derive(Debug, PartialEq, Eq)]
37pub struct HtmlElement<E, At, Ch> {
38 #[cfg(any(debug_assertions, leptos_debuginfo))]
39 pub(crate) defined_at: &'static Location<'static>,
40 pub(crate) tag: E,
41 pub(crate) attributes: At,
42 pub(crate) children: Ch,
43}
44
45impl<E: Clone, At: Clone, Ch: Clone> Clone for HtmlElement<E, At, Ch> {
46 fn clone(&self) -> Self {
47 HtmlElement {
48 #[cfg(any(debug_assertions, leptos_debuginfo))]
49 defined_at: self.defined_at,
50 tag: self.tag.clone(),
51 attributes: self.attributes.clone(),
52 children: self.children.clone(),
53 }
54 }
55}
56
57impl<E: Copy, At: Copy, Ch: Copy> Copy for HtmlElement<E, At, Ch> {}
58
59#[cfg(not(erase_components))]
75impl<E, At, Ch, NewChild> ElementChild<NewChild> for HtmlElement<E, At, Ch>
76where
77 E: ElementWithChildren,
78 Ch: RenderHtml + next_tuple::NextTuple,
79 <Ch as next_tuple::NextTuple>::Output<NewChild::Output>: Render,
80
81 NewChild: IntoRender,
82 NewChild::Output: RenderHtml,
83{
84 type Output = HtmlElement<
85 E,
86 At,
87 <Ch as next_tuple::NextTuple>::Output<NewChild::Output>,
88 >;
89
90 fn child(self, child: NewChild) -> Self::Output {
91 HtmlElement {
92 #[cfg(any(debug_assertions, leptos_debuginfo))]
93 defined_at: self.defined_at,
94 tag: self.tag,
95 attributes: self.attributes,
96 children: self.children.next_tuple(child.into_render()),
97 }
98 }
99}
100
101#[cfg(erase_components)]
102impl<E, At, Ch, NewChild> ElementChild<NewChild> for HtmlElement<E, At, Ch>
103where
104 E: ElementWithChildren,
105 Ch: RenderHtml + NextChildren,
106
107 NewChild: IntoRender,
108 NewChild::Output: RenderHtml,
109{
110 type Output =
111 HtmlElement<E, At, crate::view::iterators::StaticVec<AnyView>>;
112
113 fn child(self, child: NewChild) -> Self::Output {
114 use crate::view::any_view::IntoAny;
115
116 HtmlElement {
117 #[cfg(any(debug_assertions, leptos_debuginfo))]
118 defined_at: self.defined_at,
119 tag: self.tag,
120 attributes: self.attributes,
121 children: self
122 .children
123 .next_children(child.into_render().into_any()),
124 }
125 }
126}
127
128#[cfg(erase_components)]
129trait NextChildren {
130 fn next_children(
131 self,
132 child: AnyView,
133 ) -> crate::view::iterators::StaticVec<AnyView>;
134}
135
136#[cfg(erase_components)]
137mod erased_tuples {
138 use super::*;
139 use crate::view::{any_view::IntoAny, iterators::StaticVec};
140
141 impl NextChildren for StaticVec<AnyView> {
142 fn next_children(mut self, child: AnyView) -> StaticVec<AnyView> {
143 self.0.push(child);
144 self
145 }
146 }
147
148 impl NextChildren for () {
149 fn next_children(self, child: AnyView) -> StaticVec<AnyView> {
150 vec![child].into()
151 }
152 }
153
154 impl<T: RenderHtml> NextChildren for (T,) {
155 fn next_children(self, child: AnyView) -> StaticVec<AnyView> {
156 vec![self.0.into_owned().into_any(), child].into()
157 }
158 }
159
160 macro_rules! impl_next_children_tuples {
161 ($($ty:ident),*) => {
162 impl<$($ty: RenderHtml),*> NextChildren for ($($ty,)*)
163 {
164 fn next_children(
165 self, child: AnyView,
166 ) -> StaticVec<AnyView> {
167 #[allow(non_snake_case)]
168 let ($($ty,)*) = self;
169 vec![$($ty.into_owned().into_any(),)* child].into()
170 }
171 }
172 };
173 }
174
175 impl_next_children_tuples!(AA, BB);
176 impl_next_children_tuples!(AA, BB, CC);
177 impl_next_children_tuples!(AA, BB, CC, DD);
178 impl_next_children_tuples!(AA, BB, CC, DD, EE);
179 impl_next_children_tuples!(AA, BB, CC, DD, EE, FF);
180 impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG);
181 impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH);
182 impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II);
183 impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II, JJ);
184 impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK);
185 impl_next_children_tuples!(AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL);
186 impl_next_children_tuples!(
187 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM
188 );
189 impl_next_children_tuples!(
190 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN
191 );
192 impl_next_children_tuples!(
193 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO
194 );
195 impl_next_children_tuples!(
196 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP
197 );
198 impl_next_children_tuples!(
199 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ
200 );
201 impl_next_children_tuples!(
202 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR
203 );
204 impl_next_children_tuples!(
205 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
206 SS
207 );
208 impl_next_children_tuples!(
209 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
210 SS, TT
211 );
212 impl_next_children_tuples!(
213 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
214 SS, TT, UU
215 );
216 impl_next_children_tuples!(
217 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
218 SS, TT, UU, VV
219 );
220 impl_next_children_tuples!(
221 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
222 SS, TT, UU, VV, WW
223 );
224 impl_next_children_tuples!(
225 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
226 SS, TT, UU, VV, WW, XX
227 );
228 impl_next_children_tuples!(
229 AA, BB, CC, DD, EE, FF, GG, HH, II, JJ, KK, LL, MM, NN, OO, PP, QQ, RR,
230 SS, TT, UU, VV, WW, XX, YY
231 );
232}
233
234impl<E, At, Ch> AddAnyAttr for HtmlElement<E, At, Ch>
235where
236 E: ElementType + Send,
237 At: Attribute + Send,
238 Ch: RenderHtml + Send,
239{
240 type Output<SomeNewAttr: Attribute> =
241 HtmlElement<E, <At as NextAttribute>::Output<SomeNewAttr>, Ch>;
242
243 fn add_any_attr<NewAttr: Attribute>(
244 self,
245 attr: NewAttr,
246 ) -> Self::Output<NewAttr> {
247 let HtmlElement {
248 #[cfg(any(debug_assertions, leptos_debuginfo))]
249 defined_at,
250 tag,
251 attributes,
252 children,
253 } = self;
254 HtmlElement {
255 #[cfg(any(debug_assertions, leptos_debuginfo))]
256 defined_at,
257 tag,
258 attributes: attributes.add_any_attr(attr),
259 children,
260 }
261 }
262}
263
264pub trait ElementChild<NewChild>
266where
267 NewChild: IntoRender,
268{
269 type Output;
271
272 fn child(self, child: NewChild) -> Self::Output;
274}
275
276pub trait ElementType: Send + 'static {
278 type Output;
280
281 const TAG: &'static str;
283 const SELF_CLOSING: bool;
285 const ESCAPE_CHILDREN: bool;
289 const NAMESPACE: Option<&'static str>;
291
292 fn tag(&self) -> &str;
294}
295
296pub trait HasElementType {
298 type ElementType;
300}
301
302pub(crate) trait ElementWithChildren {}
303
304impl<E, At, Ch> HasElementType for HtmlElement<E, At, Ch>
305where
306 E: ElementType,
307{
308 type ElementType = E::Output;
309}
310
311impl<E, At, Ch> Render for HtmlElement<E, At, Ch>
312where
313 E: ElementType,
314 At: Attribute,
315 Ch: Render,
316{
317 type State = ElementState<At::State, Ch::State>;
318
319 fn rebuild(self, state: &mut Self::State) {
320 if E::TAG.is_empty() {
324 let new_tag = self.tag.tag();
326
327 let old_tag = state.el.tag_name();
330 if new_tag != old_tag {
331 let mut new_state = self.build();
332 state.insert_before_this(&mut new_state);
333 state.unmount();
334 *state = new_state;
335 return;
336 }
337 }
338
339 let ElementState {
341 attrs, children, ..
342 } = state;
343 self.attributes.rebuild(attrs);
344 if let Some(children) = children {
345 self.children.rebuild(children);
346 }
347 }
348
349 fn build(self) -> Self::State {
350 let el = Rndr::create_element(self.tag.tag(), E::NAMESPACE);
351
352 let attrs = self.attributes.build(&el);
353
354 let children = if E::SELF_CLOSING {
355 None
356 } else {
357 let mut children = self.children.build();
358 children.mount(&el, None);
359 Some(children)
360 };
361
362 ElementState {
363 el,
364 attrs,
365 children,
366 }
367 }
368}
369
370impl<E, At, Ch> RenderHtml for HtmlElement<E, At, Ch>
371where
372 E: ElementType + Send,
373 At: Attribute + Send,
374 Ch: RenderHtml + Send,
375{
376 type AsyncOutput = HtmlElement<E, At::AsyncOutput, Ch::AsyncOutput>;
377 type Owned = HtmlElement<E, At::CloneableOwned, Ch::Owned>;
378
379 const MIN_LENGTH: usize = if E::SELF_CLOSING {
380 3 + E::TAG.len()
382 + At::MIN_LENGTH
383 } else {
384 2 + E::TAG.len()
386 + At::MIN_LENGTH
387 + Ch::MIN_LENGTH
388 + 3 + E::TAG.len()
390 };
391
392 fn dry_resolve(&mut self) {
393 self.attributes.dry_resolve();
394 self.children.dry_resolve();
395 }
396
397 async fn resolve(self) -> Self::AsyncOutput {
398 let (attributes, children) =
399 join(self.attributes.resolve(), self.children.resolve()).await;
400 HtmlElement {
401 #[cfg(any(debug_assertions, leptos_debuginfo))]
402 defined_at: self.defined_at,
403 tag: self.tag,
404 attributes,
405 children,
406 }
407 }
408
409 fn html_len(&self) -> usize {
410 if E::SELF_CLOSING {
411 3 + E::TAG.len()
413 + self.attributes.html_len()
414 } else {
415 2 + E::TAG.len()
417 + self.attributes.html_len()
418 + self.children.html_len()
419 + 3 + E::TAG.len()
421 }
422 }
423
424 fn to_html_with_buf(
425 self,
426 buf: &mut String,
427 position: &mut Position,
428 _escape: bool,
429 mark_branches: bool,
430 extra_attributes: Vec<AnyAttribute>,
431 ) {
432 buf.push('<');
434 buf.push_str(self.tag.tag());
435
436 let inner_html =
437 attributes_to_html((self.attributes, extra_attributes), buf);
438
439 buf.push('>');
440
441 if !E::SELF_CLOSING {
442 if !inner_html.is_empty() {
443 buf.push_str(&inner_html);
444 } else if Ch::EXISTS {
445 *position = Position::FirstChild;
447 self.children.to_html_with_buf(
448 buf,
449 position,
450 E::ESCAPE_CHILDREN,
451 mark_branches,
452 vec![],
453 );
454 }
455
456 buf.push_str("</");
458 buf.push_str(self.tag.tag());
459 buf.push('>');
460 }
461 *position = Position::NextChild;
462 }
463
464 fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
465 self,
466 buffer: &mut StreamBuilder,
467 position: &mut Position,
468 _escape: bool,
469 mark_branches: bool,
470 extra_attributes: Vec<AnyAttribute>,
471 ) where
472 Self: Sized,
473 {
474 let mut buf = String::with_capacity(Self::MIN_LENGTH);
475 buf.push('<');
477 buf.push_str(self.tag.tag());
478
479 let inner_html =
480 attributes_to_html((self.attributes, extra_attributes), &mut buf);
481
482 buf.push('>');
483 buffer.push_sync(&buf);
484
485 if !E::SELF_CLOSING {
486 *position = Position::FirstChild;
488 if !inner_html.is_empty() {
489 buffer.push_sync(&inner_html);
490 } else if Ch::EXISTS {
491 self.children.to_html_async_with_buf::<OUT_OF_ORDER>(
492 buffer,
493 position,
494 E::ESCAPE_CHILDREN,
495 mark_branches,
496 vec![],
497 );
498 }
499
500 let mut buf = String::with_capacity(3 + E::TAG.len());
502 buf.push_str("</");
503 buf.push_str(self.tag.tag());
504 buf.push('>');
505 buffer.push_sync(&buf);
506 }
507 *position = Position::NextChild;
508 }
509
510 fn hydrate<const FROM_SERVER: bool>(
511 self,
512 cursor: &Cursor,
513 position: &PositionState,
514 ) -> Self::State {
515 if E::TAG.is_empty() && !FROM_SERVER {
518 panic!("Custom elements are not supported in ViewTemplate.");
519 }
520
521 fn inner_1(
523 cursor: &Cursor,
524 position: &PositionState,
525 tag_name: &str,
526 #[cfg(any(debug_assertions, leptos_debuginfo))]
527 defined_at: &'static std::panic::Location<'static>,
528 ) -> crate::renderer::types::Element {
529 #[cfg(any(debug_assertions, leptos_debuginfo))]
530 {
531 set_currently_hydrating(Some(defined_at));
532 }
533
534 let curr_position = position.get();
535 if curr_position == Position::FirstChild {
536 cursor.child();
537 } else if curr_position != Position::Current {
538 cursor.sibling();
539 }
540 crate::renderer::types::Element::cast_from(cursor.current())
541 .unwrap_or_else(|| {
542 failed_to_cast_element(tag_name, cursor.current())
543 })
544 }
545 let el = inner_1(
546 cursor,
547 position,
548 E::TAG,
549 #[cfg(any(debug_assertions, leptos_debuginfo))]
550 self.defined_at,
551 );
552
553 let attrs = self.attributes.hydrate::<FROM_SERVER>(&el);
554
555 let children = if !Ch::EXISTS || !E::ESCAPE_CHILDREN {
557 None
558 } else {
559 position.set(Position::FirstChild);
560 Some(self.children.hydrate::<FROM_SERVER>(cursor, position))
561 };
562
563 fn inner_2(
565 cursor: &Cursor,
566 position: &PositionState,
567 el: &crate::renderer::types::Element,
568 ) {
569 cursor.set(
571 <crate::renderer::types::Element as AsRef<
572 crate::renderer::types::Node,
573 >>::as_ref(el)
574 .clone(),
575 );
576 position.set(Position::NextChild);
577 }
578 inner_2(cursor, position, &el);
579
580 ElementState {
581 el,
582 attrs,
583 children,
584 }
585 }
586
587 async fn hydrate_async(
588 self,
589 cursor: &Cursor,
590 position: &PositionState,
591 ) -> Self::State {
592 fn inner_1(
594 cursor: &Cursor,
595 position: &PositionState,
596 tag_name: &str,
597 #[cfg(any(debug_assertions, leptos_debuginfo))]
598 defined_at: &'static std::panic::Location<'static>,
599 ) -> crate::renderer::types::Element {
600 #[cfg(any(debug_assertions, leptos_debuginfo))]
601 {
602 set_currently_hydrating(Some(defined_at));
603 }
604
605 let curr_position = position.get();
606 if curr_position == Position::FirstChild {
607 cursor.child();
608 } else if curr_position != Position::Current {
609 cursor.sibling();
610 }
611 crate::renderer::types::Element::cast_from(cursor.current())
612 .unwrap_or_else(|| {
613 failed_to_cast_element(tag_name, cursor.current())
614 })
615 }
616 let el = inner_1(
617 cursor,
618 position,
619 E::TAG,
620 #[cfg(any(debug_assertions, leptos_debuginfo))]
621 self.defined_at,
622 );
623
624 let attrs = self.attributes.hydrate::<true>(&el);
625
626 let children = if !Ch::EXISTS || !E::ESCAPE_CHILDREN {
628 None
629 } else {
630 position.set(Position::FirstChild);
631 Some(self.children.hydrate_async(cursor, position).await)
632 };
633
634 fn inner_2(
636 cursor: &Cursor,
637 position: &PositionState,
638 el: &crate::renderer::types::Element,
639 ) {
640 cursor.set(
642 <crate::renderer::types::Element as AsRef<
643 crate::renderer::types::Node,
644 >>::as_ref(el)
645 .clone(),
646 );
647 position.set(Position::NextChild);
648 }
649 inner_2(cursor, position, &el);
650
651 ElementState {
652 el,
653 attrs,
654 children,
655 }
656 }
657
658 fn into_owned(self) -> Self::Owned {
659 HtmlElement {
660 #[cfg(any(debug_assertions, leptos_debuginfo))]
661 defined_at: self.defined_at,
662 tag: self.tag,
663 attributes: self.attributes.into_cloneable_owned(),
664 children: self.children.into_owned(),
665 }
666 }
667}
668
669pub fn attributes_to_html<At>(attr: At, buf: &mut String) -> String
671where
672 At: Attribute,
673{
674 let mut class = String::new();
684 let mut style = String::new();
685 let mut inner_html = String::new();
686
687 attr.to_html(buf, &mut class, &mut style, &mut inner_html);
689
690 if !class.is_empty() {
691 buf.push(' ');
692 buf.push_str("class=\"");
693 buf.push_str(&escape_attr(class.trim_start().trim_end()));
694 buf.push('"');
695 }
696 if !style.is_empty() {
697 buf.push(' ');
698 buf.push_str("style=\"");
699 buf.push_str(&escape_attr(style.trim_start().trim_end()));
700 buf.push('"');
701 }
702
703 inner_html
704}
705
706pub struct ElementState<At, Ch> {
708 pub(crate) el: crate::renderer::types::Element,
709 pub(crate) attrs: At,
710 pub(crate) children: Option<Ch>,
711}
712
713impl<At, Ch> Deref for ElementState<At, Ch> {
714 type Target = crate::renderer::types::Element;
715
716 fn deref(&self) -> &Self::Target {
717 &self.el
718 }
719}
720
721impl<At, Ch> Mountable for ElementState<At, Ch> {
722 fn unmount(&mut self) {
723 Rndr::remove(&self.el);
724 }
725
726 fn mount(
727 &mut self,
728 parent: &crate::renderer::types::Element,
729 marker: Option<&crate::renderer::types::Node>,
730 ) {
731 Rndr::insert_node(parent, &self.el, marker);
732 }
733
734 fn try_mount(
735 &mut self,
736 parent: &crate::renderer::types::Element,
737 marker: Option<&crate::renderer::types::Node>,
738 ) -> bool {
739 Rndr::try_insert_node(parent, &self.el, marker)
740 }
741
742 fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
743 fn inner(
745 element: &crate::renderer::types::Element,
746 child: &mut dyn Mountable,
747 ) -> bool {
748 if let Some(parent) = Rndr::get_parent(element)
749 .and_then(crate::renderer::types::Element::cast_from)
750 {
751 child.mount(&parent, Some(element));
752 true
753 } else {
754 false
755 }
756 }
757 inner(&self.el, child)
758 }
759
760 fn elements(&self) -> Vec<crate::renderer::types::Element> {
761 fn inner(
763 element: &crate::renderer::types::Element,
764 ) -> Vec<crate::renderer::types::Element> {
765 vec![element.clone()]
766 }
767 inner(&self.el)
768 }
769}
770
771impl<E, At, Ch> ToTemplate for HtmlElement<E, At, Ch>
772where
773 E: ElementType,
774 At: Attribute + ToTemplate,
775 Ch: Render + ToTemplate,
776{
777 const TEMPLATE: &'static str = str_from_buffer(&const_concat(&[
778 "<",
779 E::TAG,
780 At::TEMPLATE,
781 str_from_buffer(&const_concat_with_prefix(
782 &[At::CLASS],
783 " class=\"",
784 "\"",
785 )),
786 str_from_buffer(&const_concat_with_prefix(
787 &[At::STYLE],
788 " style=\"",
789 "\"",
790 )),
791 ">",
792 Ch::TEMPLATE,
793 "</",
794 E::TAG,
795 ">",
796 ]));
797
798 #[allow(unused)] fn to_template(
800 buf: &mut String,
801 class: &mut String,
802 style: &mut String,
803 inner_html: &mut String,
804 position: &mut Position,
805 ) {
806 if !E::TAG.is_empty() {
808 let mut class = String::new();
810 let mut style = String::new();
811 let mut inner_html = String::new();
812
813 buf.push('<');
814 buf.push_str(E::TAG);
815 <At as ToTemplate>::to_template_attribute(
816 buf,
817 &mut class,
818 &mut style,
819 &mut inner_html,
820 position,
821 );
822
823 if !class.is_empty() {
824 buf.push(' ');
825 buf.push_str("class=\"");
826 buf.push_str(class.trim_start().trim_end());
827 buf.push('"');
828 }
829 if !style.is_empty() {
830 buf.push(' ');
831 buf.push_str("style=\"");
832 buf.push_str(style.trim_start().trim_end());
833 buf.push('"');
834 }
835 buf.push('>');
836
837 *position = Position::FirstChild;
839 class.clear();
840 style.clear();
841 inner_html.clear();
842 Ch::to_template(
843 buf,
844 &mut class,
845 &mut style,
846 &mut inner_html,
847 position,
848 );
849
850 buf.push_str("</");
852 buf.push_str(E::TAG);
853 buf.push('>');
854 *position = Position::NextChild;
855 }
856 }
857}
858