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