Skip to main content

tachys/view/
any_view.rs

1#![allow(clippy::type_complexity)]
2#[cfg(feature = "ssr")]
3use super::MarkBranch;
4use super::{
5    add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
6    RenderHtml,
7};
8use crate::{
9    erased::{Erased, ErasedLocal},
10    html::attribute::{
11        any_attribute::{AnyAttribute, AnyAttributeState, IntoAnyAttribute},
12        Attribute,
13    },
14    hydration::Cursor,
15    renderer::Rndr,
16    ssr::StreamBuilder,
17};
18use futures::future::{join, join_all};
19use std::{any::TypeId, fmt::Debug};
20#[cfg(any(feature = "ssr", feature = "hydrate"))]
21use std::{future::Future, pin::Pin};
22
23/// A type-erased view. This can be used if control flow requires that multiple different types of
24/// view must be received, and it is either impossible or too cumbersome to use the `EitherOf___`
25/// enums.
26///
27/// It can also be used to create recursive components, which otherwise cannot return themselves
28/// due to the static typing of the view tree.
29///
30/// Generally speaking, using `AnyView` restricts the amount of information available to the
31/// compiler and should be limited to situations in which it is necessary to preserve the maximum
32/// amount of type information possible.
33pub struct AnyView {
34    type_id: TypeId,
35    value: Erased,
36    build: fn(Erased) -> AnyViewState,
37    rebuild: fn(Erased, &mut AnyViewState),
38    // The fields below are cfg-gated so they will not be included in WASM bundles if not needed.
39    // Ordinarily, the compiler can simply omit this dead code because the methods are not called.
40    // With this type-erased wrapper, however, the compiler is not *always* able to correctly
41    // eliminate that code.
42    #[cfg(feature = "ssr")]
43    html_len: usize,
44    #[cfg(feature = "ssr")]
45    to_html:
46        fn(Erased, &mut String, &mut Position, bool, bool, Vec<AnyAttribute>),
47    #[cfg(feature = "ssr")]
48    to_html_async: fn(
49        Erased,
50        &mut StreamBuilder,
51        &mut Position,
52        bool,
53        bool,
54        Vec<AnyAttribute>,
55    ),
56    #[cfg(feature = "ssr")]
57    to_html_async_ooo: fn(
58        Erased,
59        &mut StreamBuilder,
60        &mut Position,
61        bool,
62        bool,
63        Vec<AnyAttribute>,
64    ),
65    #[cfg(feature = "ssr")]
66    #[allow(clippy::type_complexity)]
67    resolve: fn(Erased) -> Pin<Box<dyn Future<Output = AnyView> + Send>>,
68    #[cfg(feature = "ssr")]
69    dry_resolve: fn(&mut Erased),
70    #[cfg(feature = "hydrate")]
71    #[allow(clippy::type_complexity)]
72    hydrate_from_server: fn(Erased, &Cursor, &PositionState) -> AnyViewState,
73    #[cfg(feature = "hydrate")]
74    #[allow(clippy::type_complexity)]
75    hydrate_async: fn(
76        Erased,
77        &Cursor,
78        &PositionState,
79    ) -> Pin<Box<dyn Future<Output = AnyViewState>>>,
80}
81
82impl AnyView {
83    #[doc(hidden)]
84    pub fn as_type_id(&self) -> TypeId {
85        self.type_id
86    }
87}
88
89impl Debug for AnyView {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.debug_struct("AnyView")
92            .field("type_id", &self.type_id)
93            .finish_non_exhaustive()
94    }
95}
96/// Retained view state for [`AnyView`].
97pub struct AnyViewState {
98    type_id: TypeId,
99    state: ErasedLocal,
100    unmount: fn(&mut ErasedLocal),
101    mount: fn(
102        &mut ErasedLocal,
103        parent: &crate::renderer::types::Element,
104        marker: Option<&crate::renderer::types::Node>,
105    ),
106    insert_before_this: fn(&ErasedLocal, child: &mut dyn Mountable) -> bool,
107    elements: fn(&ErasedLocal) -> Vec<crate::renderer::types::Element>,
108    placeholder: Option<crate::renderer::types::Placeholder>,
109}
110
111impl Debug for AnyViewState {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        f.debug_struct("AnyViewState")
114            .field("type_id", &self.type_id)
115            .field("state", &"")
116            .field("unmount", &self.unmount)
117            .field("mount", &self.mount)
118            .field("insert_before_this", &self.insert_before_this)
119            .finish()
120    }
121}
122
123/// Allows converting some view into [`AnyView`].
124pub trait IntoAny {
125    /// Converts the view into a type-erased [`AnyView`].
126    fn into_any(self) -> AnyView;
127}
128
129/// A more general version of [`IntoAny`] that allows into [`AnyView`],
130/// but also erasing other types that don't implement [`RenderHtml`] like routing.
131pub trait IntoMaybeErased {
132    /// The type of the output.
133    type Output: IntoMaybeErased;
134
135    /// Converts the view into a type-erased view if in erased mode.
136    fn into_maybe_erased(self) -> Self::Output;
137}
138
139impl<T> IntoMaybeErased for T
140where
141    T: RenderHtml,
142{
143    #[cfg(not(erase_components))]
144    type Output = Self;
145
146    #[cfg(erase_components)]
147    type Output = AnyView;
148
149    fn into_maybe_erased(self) -> Self::Output {
150        #[cfg(not(erase_components))]
151        {
152            self
153        }
154        #[cfg(erase_components)]
155        {
156            self.into_owned().into_any()
157        }
158    }
159}
160
161fn mount_any<T>(
162    state: &mut ErasedLocal,
163    parent: &crate::renderer::types::Element,
164    marker: Option<&crate::renderer::types::Node>,
165) where
166    T: Render,
167    T::State: 'static,
168{
169    state.get_mut::<T::State>().mount(parent, marker)
170}
171
172fn unmount_any<T>(state: &mut ErasedLocal)
173where
174    T: Render,
175    T::State: 'static,
176{
177    state.get_mut::<T::State>().unmount();
178}
179
180fn insert_before_this<T>(state: &ErasedLocal, child: &mut dyn Mountable) -> bool
181where
182    T: Render,
183    T::State: 'static,
184{
185    state.get_ref::<T::State>().insert_before_this(child)
186}
187
188fn elements<T>(state: &ErasedLocal) -> Vec<crate::renderer::types::Element>
189where
190    T: Render,
191    T::State: 'static,
192{
193    state.get_ref::<T::State>().elements()
194}
195
196impl<T> IntoAny for T
197where
198    T: Send,
199    T: RenderHtml,
200{
201    fn into_any(self) -> AnyView {
202        #[cfg(feature = "ssr")]
203        fn dry_resolve<T: RenderHtml + 'static>(value: &mut Erased) {
204            value.get_mut::<T>().dry_resolve();
205        }
206
207        #[cfg(feature = "ssr")]
208        fn resolve<T: RenderHtml + 'static>(
209            value: Erased,
210        ) -> Pin<Box<dyn Future<Output = AnyView> + Send>> {
211            use futures::FutureExt;
212
213            async move { value.into_inner::<T>().resolve().await.into_any() }
214                .boxed()
215        }
216
217        #[cfg(feature = "ssr")]
218        fn to_html<T: RenderHtml + 'static>(
219            value: Erased,
220            buf: &mut String,
221            position: &mut Position,
222            escape: bool,
223            mark_branches: bool,
224            extra_attrs: Vec<AnyAttribute>,
225        ) {
226            value.into_inner::<T>().to_html_with_buf(
227                buf,
228                position,
229                escape,
230                mark_branches,
231                extra_attrs,
232            );
233            if !T::EXISTS {
234                buf.push_str("<!--<() />-->");
235            }
236        }
237
238        #[cfg(feature = "ssr")]
239        fn to_html_async<T: RenderHtml + 'static>(
240            value: Erased,
241            buf: &mut StreamBuilder,
242            position: &mut Position,
243            escape: bool,
244            mark_branches: bool,
245            extra_attrs: Vec<AnyAttribute>,
246        ) {
247            value.into_inner::<T>().to_html_async_with_buf::<false>(
248                buf,
249                position,
250                escape,
251                mark_branches,
252                extra_attrs,
253            );
254            if !T::EXISTS {
255                buf.push_sync("<!--<() />-->");
256            }
257        }
258
259        #[cfg(feature = "ssr")]
260        fn to_html_async_ooo<T: RenderHtml + 'static>(
261            value: Erased,
262            buf: &mut StreamBuilder,
263            position: &mut Position,
264            escape: bool,
265            mark_branches: bool,
266            extra_attrs: Vec<AnyAttribute>,
267        ) {
268            value.into_inner::<T>().to_html_async_with_buf::<true>(
269                buf,
270                position,
271                escape,
272                mark_branches,
273                extra_attrs,
274            );
275            if !T::EXISTS {
276                buf.push_sync("<!--<() />-->");
277            }
278        }
279
280        fn build<T: RenderHtml + 'static>(value: Erased) -> AnyViewState {
281            let state = ErasedLocal::new(value.into_inner::<T>().build());
282            let placeholder = (!T::EXISTS).then(Rndr::create_placeholder);
283            AnyViewState {
284                type_id: TypeId::of::<T>(),
285                state,
286                mount: mount_any::<T>,
287                unmount: unmount_any::<T>,
288                insert_before_this: insert_before_this::<T>,
289                elements: elements::<T>,
290                placeholder,
291            }
292        }
293
294        #[cfg(feature = "hydrate")]
295        fn hydrate_from_server<T: RenderHtml + 'static>(
296            value: Erased,
297            cursor: &Cursor,
298            position: &PositionState,
299        ) -> AnyViewState {
300            let state = ErasedLocal::new(
301                value.into_inner::<T>().hydrate::<true>(cursor, position),
302            );
303            let placeholder =
304                (!T::EXISTS).then(|| cursor.next_placeholder(position));
305            AnyViewState {
306                type_id: TypeId::of::<T>(),
307                state,
308                mount: mount_any::<T>,
309                unmount: unmount_any::<T>,
310                insert_before_this: insert_before_this::<T>,
311                elements: elements::<T>,
312                placeholder,
313            }
314        }
315
316        #[cfg(feature = "hydrate")]
317        fn hydrate_async<T: RenderHtml + 'static>(
318            value: Erased,
319            cursor: &Cursor,
320            position: &PositionState,
321        ) -> Pin<Box<dyn Future<Output = AnyViewState>>> {
322            let cursor = cursor.clone();
323            let position = position.clone();
324            Box::pin(async move {
325                let state = ErasedLocal::new(
326                    value
327                        .into_inner::<T>()
328                        .hydrate_async(&cursor, &position)
329                        .await,
330                );
331                let placeholder =
332                    (!T::EXISTS).then(|| cursor.next_placeholder(&position));
333                AnyViewState {
334                    type_id: TypeId::of::<T>(),
335                    state,
336                    mount: mount_any::<T>,
337                    unmount: unmount_any::<T>,
338                    insert_before_this: insert_before_this::<T>,
339                    elements: elements::<T>,
340                    placeholder,
341                }
342            })
343        }
344
345        fn rebuild<T: RenderHtml + 'static>(
346            value: Erased,
347            state: &mut AnyViewState,
348        ) {
349            let state = state.state.get_mut::<<T as Render>::State>();
350            value.into_inner::<T>().rebuild(state);
351        }
352
353        let value = self.into_owned();
354        AnyView {
355            type_id: TypeId::of::<T::Owned>(),
356            build: build::<T::Owned>,
357            rebuild: rebuild::<T::Owned>,
358            #[cfg(feature = "ssr")]
359            resolve: resolve::<T::Owned>,
360            #[cfg(feature = "ssr")]
361            dry_resolve: dry_resolve::<T::Owned>,
362            #[cfg(feature = "ssr")]
363            html_len: value.html_len(),
364            #[cfg(feature = "ssr")]
365            to_html: to_html::<T::Owned>,
366            #[cfg(feature = "ssr")]
367            to_html_async: to_html_async::<T::Owned>,
368            #[cfg(feature = "ssr")]
369            to_html_async_ooo: to_html_async_ooo::<T::Owned>,
370            #[cfg(feature = "hydrate")]
371            hydrate_from_server: hydrate_from_server::<T::Owned>,
372            #[cfg(feature = "hydrate")]
373            hydrate_async: hydrate_async::<T::Owned>,
374            value: Erased::new(value),
375        }
376    }
377}
378
379impl Render for AnyView {
380    type State = AnyViewState;
381
382    fn build(self) -> Self::State {
383        (self.build)(self.value)
384    }
385
386    fn rebuild(self, state: &mut Self::State) {
387        if self.type_id == state.type_id {
388            (self.rebuild)(self.value, state)
389        } else {
390            let mut new = self.build();
391            if let Some(placeholder) = &mut state.placeholder {
392                placeholder.insert_before_this(&mut new);
393                placeholder.unmount();
394            } else {
395                state.insert_before_this(&mut new);
396            }
397            state.unmount();
398            *state = new;
399        }
400    }
401}
402
403impl AddAnyAttr for AnyView {
404    type Output<SomeNewAttr: Attribute> = AnyViewWithAttrs;
405
406    #[allow(unused_variables)]
407    fn add_any_attr<NewAttr: Attribute>(
408        self,
409        attr: NewAttr,
410    ) -> Self::Output<NewAttr>
411    where
412        Self::Output<NewAttr>: RenderHtml,
413    {
414        AnyViewWithAttrs {
415            view: self,
416            attrs: vec![attr.into_cloneable_owned().into_any_attr()],
417        }
418    }
419}
420
421impl RenderHtml for AnyView {
422    type AsyncOutput = Self;
423    type Owned = Self;
424
425    fn dry_resolve(&mut self) {
426        #[cfg(feature = "ssr")]
427        {
428            (self.dry_resolve)(&mut self.value)
429        }
430        #[cfg(not(feature = "ssr"))]
431        panic!(
432            "You are rendering AnyView to HTML without the `ssr` feature \
433             enabled."
434        );
435    }
436
437    async fn resolve(self) -> Self::AsyncOutput {
438        #[cfg(feature = "ssr")]
439        {
440            (self.resolve)(self.value).await
441        }
442        #[cfg(not(feature = "ssr"))]
443        panic!(
444            "You are rendering AnyView to HTML without the `ssr` feature \
445             enabled."
446        );
447    }
448
449    const MIN_LENGTH: usize = 0;
450
451    fn to_html_with_buf(
452        self,
453        buf: &mut String,
454        position: &mut Position,
455        escape: bool,
456        mark_branches: bool,
457        extra_attrs: Vec<AnyAttribute>,
458    ) {
459        #[cfg(feature = "ssr")]
460        {
461            let type_id = if mark_branches && escape {
462                format!("{:?}", self.type_id)
463            } else {
464                Default::default()
465            };
466            if mark_branches && escape {
467                buf.open_branch(&type_id);
468            }
469            (self.to_html)(
470                self.value,
471                buf,
472                position,
473                escape,
474                mark_branches,
475                extra_attrs,
476            );
477            if mark_branches && escape {
478                buf.close_branch(&type_id);
479                if *position == Position::NextChildAfterText {
480                    *position = Position::NextChild;
481                }
482            }
483        }
484        #[cfg(not(feature = "ssr"))]
485        {
486            _ = mark_branches;
487            _ = buf;
488            _ = position;
489            _ = escape;
490            _ = extra_attrs;
491            panic!(
492                "You are rendering AnyView to HTML without the `ssr` feature \
493                 enabled."
494            );
495        }
496    }
497
498    fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
499        self,
500        buf: &mut StreamBuilder,
501        position: &mut Position,
502        escape: bool,
503        mark_branches: bool,
504        extra_attrs: Vec<AnyAttribute>,
505    ) where
506        Self: Sized,
507    {
508        #[cfg(feature = "ssr")]
509        if OUT_OF_ORDER {
510            let type_id = if mark_branches && escape {
511                format!("{:?}", self.type_id)
512            } else {
513                Default::default()
514            };
515            if mark_branches && escape {
516                buf.open_branch(&type_id);
517            }
518            (self.to_html_async_ooo)(
519                self.value,
520                buf,
521                position,
522                escape,
523                mark_branches,
524                extra_attrs,
525            );
526            if mark_branches && escape {
527                buf.close_branch(&type_id);
528                if *position == Position::NextChildAfterText {
529                    *position = Position::NextChild;
530                }
531            }
532        } else {
533            let type_id = if mark_branches && escape {
534                format!("{:?}", self.type_id)
535            } else {
536                Default::default()
537            };
538            if mark_branches && escape {
539                buf.open_branch(&type_id);
540            }
541            (self.to_html_async)(
542                self.value,
543                buf,
544                position,
545                escape,
546                mark_branches,
547                extra_attrs,
548            );
549            if mark_branches && escape {
550                buf.close_branch(&type_id);
551                if *position == Position::NextChildAfterText {
552                    *position = Position::NextChild;
553                }
554            }
555        }
556        #[cfg(not(feature = "ssr"))]
557        {
558            _ = buf;
559            _ = position;
560            _ = escape;
561            _ = mark_branches;
562            _ = extra_attrs;
563            panic!(
564                "You are rendering AnyView to HTML without the `ssr` feature \
565                 enabled."
566            );
567        }
568    }
569
570    fn hydrate<const FROM_SERVER: bool>(
571        self,
572        cursor: &Cursor,
573        position: &PositionState,
574    ) -> Self::State {
575        #[cfg(feature = "hydrate")]
576        {
577            if FROM_SERVER {
578                (self.hydrate_from_server)(self.value, cursor, position)
579            } else {
580                panic!(
581                    "hydrating AnyView from inside a ViewTemplate is not \
582                     supported."
583                );
584            }
585        }
586        #[cfg(not(feature = "hydrate"))]
587        {
588            _ = cursor;
589            _ = position;
590            panic!(
591                "You are trying to hydrate AnyView without the `hydrate` \
592                 feature enabled."
593            );
594        }
595    }
596
597    async fn hydrate_async(
598        self,
599        cursor: &Cursor,
600        position: &PositionState,
601    ) -> Self::State {
602        #[cfg(feature = "hydrate")]
603        {
604            let state =
605                (self.hydrate_async)(self.value, cursor, position).await;
606            state
607        }
608        #[cfg(not(feature = "hydrate"))]
609        {
610            _ = cursor;
611            _ = position;
612            panic!(
613                "You are trying to hydrate AnyView without the `hydrate` \
614                 feature enabled."
615            );
616        }
617    }
618
619    fn html_len(&self) -> usize {
620        #[cfg(feature = "ssr")]
621        {
622            self.html_len
623        }
624        #[cfg(not(feature = "ssr"))]
625        {
626            0
627        }
628    }
629
630    fn into_owned(self) -> Self::Owned {
631        self
632    }
633}
634
635impl Mountable for AnyViewState {
636    fn unmount(&mut self) {
637        (self.unmount)(&mut self.state);
638        if let Some(placeholder) = &mut self.placeholder {
639            placeholder.unmount();
640        }
641    }
642
643    fn mount(
644        &mut self,
645        parent: &crate::renderer::types::Element,
646        marker: Option<&crate::renderer::types::Node>,
647    ) {
648        (self.mount)(&mut self.state, parent, marker);
649        if let Some(placeholder) = &mut self.placeholder {
650            placeholder.mount(parent, marker);
651        }
652    }
653
654    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
655        let before_view = (self.insert_before_this)(&self.state, child);
656        if before_view {
657            return true;
658        }
659
660        if let Some(placeholder) = &self.placeholder {
661            placeholder.insert_before_this(child)
662        } else {
663            false
664        }
665    }
666
667    fn elements(&self) -> Vec<crate::renderer::types::Element> {
668        (self.elements)(&self.state)
669    }
670}
671
672/// wip
673pub struct AnyViewWithAttrs {
674    view: AnyView,
675    attrs: Vec<AnyAttribute>,
676}
677
678impl Render for AnyViewWithAttrs {
679    type State = AnyViewWithAttrsState;
680
681    fn build(self) -> Self::State {
682        let view = self.view.build();
683        let elements = view.elements();
684        let mut attrs = Vec::with_capacity(elements.len() * self.attrs.len());
685        for attr in self.attrs {
686            for el in &elements {
687                attrs.push(attr.clone().build(el))
688            }
689        }
690        AnyViewWithAttrsState { view, attrs }
691    }
692
693    fn rebuild(self, state: &mut Self::State) {
694        self.view.rebuild(&mut state.view);
695
696        // at this point, we have rebuilt the inner view
697        // now we need to update attributes that were spread onto this
698        // this approach is not ideal, but it avoids two edge cases:
699        // 1) merging attributes from two unrelated views (https://github.com/leptos-rs/leptos/issues/4268)
700        // 2) failing to re-create attributes from the same view (https://github.com/leptos-rs/leptos/issues/4512)
701        for element in state.elements() {
702            // first, remove the previous set of attributes
703            self.attrs
704                .clone()
705                .rebuild(&mut (element.clone(), Vec::new()));
706            // then, add the new set of attributes
707            self.attrs.clone().build(&element);
708        }
709    }
710}
711
712impl RenderHtml for AnyViewWithAttrs {
713    type AsyncOutput = Self;
714    type Owned = Self;
715    const MIN_LENGTH: usize = 0;
716
717    fn dry_resolve(&mut self) {
718        self.view.dry_resolve();
719        for attr in &mut self.attrs {
720            attr.dry_resolve();
721        }
722    }
723
724    async fn resolve(self) -> Self::AsyncOutput {
725        let resolve_view = self.view.resolve();
726        let resolve_attrs =
727            join_all(self.attrs.into_iter().map(|attr| attr.resolve()));
728        let (view, attrs) = join(resolve_view, resolve_attrs).await;
729        Self { view, attrs }
730    }
731
732    fn to_html_with_buf(
733        self,
734        buf: &mut String,
735        position: &mut Position,
736        escape: bool,
737        mark_branches: bool,
738        mut extra_attrs: Vec<AnyAttribute>,
739    ) {
740        // `extra_attrs` will be empty here in most cases, but it will have
741        // attributes in it already if this is, itself, receiving additional attrs
742        extra_attrs.extend(self.attrs);
743        self.view.to_html_with_buf(
744            buf,
745            position,
746            escape,
747            mark_branches,
748            extra_attrs,
749        );
750    }
751
752    fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
753        self,
754        buf: &mut StreamBuilder,
755        position: &mut Position,
756        escape: bool,
757        mark_branches: bool,
758        mut extra_attrs: Vec<AnyAttribute>,
759    ) where
760        Self: Sized,
761    {
762        extra_attrs.extend(self.attrs);
763        self.view.to_html_async_with_buf::<OUT_OF_ORDER>(
764            buf,
765            position,
766            escape,
767            mark_branches,
768            extra_attrs,
769        );
770    }
771
772    fn hydrate<const FROM_SERVER: bool>(
773        self,
774        cursor: &Cursor,
775        position: &PositionState,
776    ) -> Self::State {
777        let view = self.view.hydrate::<FROM_SERVER>(cursor, position);
778        let elements = view.elements();
779        let mut attrs = Vec::with_capacity(elements.len() * self.attrs.len());
780        for attr in self.attrs {
781            for el in &elements {
782                attrs.push(attr.clone().hydrate::<FROM_SERVER>(el));
783            }
784        }
785        AnyViewWithAttrsState { view, attrs }
786    }
787
788    async fn hydrate_async(
789        self,
790        cursor: &Cursor,
791        position: &PositionState,
792    ) -> Self::State {
793        let view = self.view.hydrate_async(cursor, position).await;
794        let elements = view.elements();
795        let mut attrs = Vec::with_capacity(elements.len() * self.attrs.len());
796        for attr in self.attrs {
797            for el in &elements {
798                attrs.push(attr.clone().hydrate::<true>(el));
799            }
800        }
801        AnyViewWithAttrsState { view, attrs }
802    }
803
804    fn html_len(&self) -> usize {
805        self.view.html_len()
806            + self.attrs.iter().map(|attr| attr.html_len()).sum::<usize>()
807    }
808
809    fn into_owned(self) -> Self::Owned {
810        self
811    }
812}
813
814impl AddAnyAttr for AnyViewWithAttrs {
815    type Output<SomeNewAttr: Attribute> = AnyViewWithAttrs;
816
817    fn add_any_attr<NewAttr: Attribute>(
818        mut self,
819        attr: NewAttr,
820    ) -> Self::Output<NewAttr>
821    where
822        Self::Output<NewAttr>: RenderHtml,
823    {
824        self.attrs.push(attr.into_cloneable_owned().into_any_attr());
825        self
826    }
827}
828
829/// State for any view with attributes spread onto it.
830pub struct AnyViewWithAttrsState {
831    view: AnyViewState,
832    #[allow(dead_code)] // keeps attribute states alive until dropped
833    attrs: Vec<AnyAttributeState>,
834}
835
836impl Mountable for AnyViewWithAttrsState {
837    fn unmount(&mut self) {
838        self.view.unmount();
839    }
840
841    fn mount(
842        &mut self,
843        parent: &crate::renderer::types::Element,
844        marker: Option<&crate::renderer::types::Node>,
845    ) {
846        self.view.mount(parent, marker)
847    }
848
849    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
850        self.view.insert_before_this(child)
851    }
852
853    fn elements(&self) -> Vec<crate::renderer::types::Element> {
854        self.view.elements()
855    }
856}
857
858/*
859#[cfg(test)]
860mod tests {
861    use super::IntoAny;
862    use crate::{
863        html::element::{p, span},
864        renderer::mock_dom::MockDom,
865        view::{any_view::AnyView, RenderHtml},
866    };
867
868    #[test]
869    fn should_handle_html_creation() {
870        let x = 1;
871        let mut buf = String::new();
872        let view: AnyView<MockDom> = if x == 0 {
873            p((), "foo").into_any()
874        } else {
875            span((), "bar").into_any()
876        };
877        view.to_html(&mut buf, &Default::default());
878        assert_eq!(buf, "<span>bar</span><!>");
879    }
880}
881 */