From c9abd005323f9f07846436f3a9dfd64c077643e5 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Mon, 10 Feb 2025 18:23:01 +0000 Subject: [PATCH] RenderHtml::into_owned --- leptos/src/attribute_interceptor.rs | 17 ++++++-- leptos/src/error_boundary.rs | 5 +++ leptos/src/into_view.rs | 9 +++++ leptos/src/suspense_component.rs | 10 +++++ leptos_server/src/lib.rs | 5 +++ meta/src/body.rs | 7 ++++ meta/src/html.rs | 7 ++++ meta/src/lib.rs | 12 ++++++ meta/src/title.rs | 5 +++ router/src/flat_router.rs | 9 ++++- router/src/nested_router.rs | 13 +++++-- tachys/src/html/element/mod.rs | 11 ++++++ tachys/src/html/islands.rs | 17 ++++++++ tachys/src/html/mod.rs | 10 +++++ tachys/src/oco.rs | 5 +++ tachys/src/reactive_graph/mod.rs | 15 +++++++ tachys/src/reactive_graph/owned.rs | 8 ++++ tachys/src/reactive_graph/suspense.rs | 5 +++ tachys/src/view/any_view.rs | 56 ++++++++++++++++----------- tachys/src/view/either.rs | 26 +++++++++++++ tachys/src/view/error_boundary.rs | 8 ++++ tachys/src/view/iterators.rs | 21 ++++++++++ tachys/src/view/keyed.rs | 13 +++++-- tachys/src/view/mod.rs | 6 +++ tachys/src/view/primitives.rs | 5 +++ tachys/src/view/static_types.rs | 5 +++ tachys/src/view/strings.rs | 20 ++++++++++ tachys/src/view/template.rs | 5 +++ tachys/src/view/tuples.rs | 18 +++++++++ 29 files changed, 317 insertions(+), 36 deletions(-) diff --git a/leptos/src/attribute_interceptor.rs b/leptos/src/attribute_interceptor.rs index 5133754266..22698a4950 100644 --- a/leptos/src/attribute_interceptor.rs +++ b/leptos/src/attribute_interceptor.rs @@ -43,7 +43,7 @@ pub fn AttributeInterceptor( ) -> impl IntoView where Chil: Fn(AnyAttribute) -> T + Send + Sync + 'static, - T: IntoView, + T: IntoView + 'static, { AttributeInterceptorInner::new(children) } @@ -86,7 +86,7 @@ impl Render for AttributeInterceptorInner { } } -impl AddAnyAttr for AttributeInterceptorInner +impl AddAnyAttr for AttributeInterceptorInner where A: Attribute, { @@ -114,8 +114,11 @@ where } } -impl RenderHtml for AttributeInterceptorInner { +impl RenderHtml + for AttributeInterceptorInner +{ type AsyncOutput = T::AsyncOutput; + type Owned = AttributeInterceptorInner; const MIN_LENGTH: usize = T::MIN_LENGTH; @@ -153,4 +156,12 @@ impl RenderHtml for AttributeInterceptorInner { ) -> Self::State { self.children.hydrate::(cursor, position) } + + fn into_owned(self) -> Self::Owned { + AttributeInterceptorInner { + children_builder: self.children_builder, + children: self.children, + attributes: self.attributes.into_cloneable_owned(), + } + } } diff --git a/leptos/src/error_boundary.rs b/leptos/src/error_boundary.rs index 774fe29b4b..8b690547ea 100644 --- a/leptos/src/error_boundary.rs +++ b/leptos/src/error_boundary.rs @@ -276,6 +276,7 @@ where Fal: RenderHtml + Send + 'static, { type AsyncOutput = ErrorBoundaryView; + type Owned = Self; const MIN_LENGTH: usize = Chil::MIN_LENGTH; @@ -437,6 +438,10 @@ where }, ) } + + fn into_owned(self) -> Self::Owned { + self + } } #[derive(Debug)] diff --git a/leptos/src/into_view.rs b/leptos/src/into_view.rs index fda32e67a2..9ffaa512f0 100644 --- a/leptos/src/into_view.rs +++ b/leptos/src/into_view.rs @@ -87,6 +87,7 @@ impl Render for View { impl RenderHtml for View { type AsyncOutput = T::AsyncOutput; + type Owned = View; const MIN_LENGTH: usize = ::MIN_LENGTH; @@ -165,6 +166,14 @@ impl RenderHtml for View { ) -> Self::State { self.inner.hydrate::(cursor, position) } + + fn into_owned(self) -> Self::Owned { + View { + inner: self.inner.into_owned(), + #[cfg(debug_assertions)] + view_marker: self.view_marker, + } + } } impl ToTemplate for View { diff --git a/leptos/src/suspense_component.rs b/leptos/src/suspense_component.rs index e54fb48193..58ab8a2e06 100644 --- a/leptos/src/suspense_component.rs +++ b/leptos/src/suspense_component.rs @@ -247,6 +247,7 @@ where // i.e., if this is the child of another Suspense during SSR, don't wait for it: it will handle // itself type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = Chil::MIN_LENGTH; @@ -473,6 +474,10 @@ where } }) } + + fn into_owned(self) -> Self::Owned { + self + } } /// A wrapper that prevents [`Suspense`] from waiting for any resource reads that happen inside @@ -525,6 +530,7 @@ where T: RenderHtml + 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = T::MIN_LENGTH; @@ -577,4 +583,8 @@ where ) -> Self::State { (self.0)().hydrate::(cursor, position) } + + fn into_owned(self) -> Self::Owned { + self + } } diff --git a/leptos_server/src/lib.rs b/leptos_server/src/lib.rs index 8583e9462a..bc580cc511 100644 --- a/leptos_server/src/lib.rs +++ b/leptos_server/src/lib.rs @@ -135,6 +135,7 @@ mod view_implementations { Ser: Send + 'static, { type AsyncOutput = Option; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -191,5 +192,9 @@ mod view_implementations { (move || Suspend::new(async move { self.await })) .hydrate::(cursor, position) } + + fn into_owned(self) -> Self::Owned { + self + } } } diff --git a/meta/src/body.rs b/meta/src/body.rs index 2a80fc8670..8e687c7e4d 100644 --- a/meta/src/body.rs +++ b/meta/src/body.rs @@ -103,6 +103,7 @@ where At: Attribute, { type AsyncOutput = BodyView; + type Owned = BodyView; const MIN_LENGTH: usize = At::MIN_LENGTH; @@ -146,6 +147,12 @@ where BodyViewState { attributes } } + + fn into_owned(self) -> Self::Owned { + BodyView { + attributes: self.attributes.into_cloneable_owned(), + } + } } impl Mountable for BodyViewState diff --git a/meta/src/html.rs b/meta/src/html.rs index edda1ad7cf..66892f3d20 100644 --- a/meta/src/html.rs +++ b/meta/src/html.rs @@ -103,6 +103,7 @@ where At: Attribute, { type AsyncOutput = HtmlView; + type Owned = HtmlView; const MIN_LENGTH: usize = At::MIN_LENGTH; @@ -149,6 +150,12 @@ where HtmlViewState { attributes } } + + fn into_owned(self) -> Self::Owned { + HtmlView { + attributes: self.attributes.into_cloneable_owned(), + } + } } impl Mountable for HtmlViewState diff --git a/meta/src/lib.rs b/meta/src/lib.rs index 524ab8e07b..2cd7bcf1e1 100644 --- a/meta/src/lib.rs +++ b/meta/src/lib.rs @@ -430,6 +430,7 @@ where Ch: RenderHtml + Send, { type AsyncOutput = Self; + type Owned = RegisteredMetaTag; const MIN_LENGTH: usize = 0; @@ -470,6 +471,12 @@ where ); RegisteredMetaTagState { state } } + + fn into_owned(self) -> Self::Owned { + RegisteredMetaTag { + el: self.el.map(|inner| inner.into_owned()), + } + } } impl Mountable for RegisteredMetaTagState @@ -547,6 +554,7 @@ impl AddAnyAttr for MetaTagsView { impl RenderHtml for MetaTagsView { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -573,6 +581,10 @@ impl RenderHtml for MetaTagsView { _position: &PositionState, ) -> Self::State { } + + fn into_owned(self) -> Self::Owned { + self + } } pub(crate) trait OrDefaultNonce { diff --git a/meta/src/title.rs b/meta/src/title.rs index 37ef49a5bc..39149513f2 100644 --- a/meta/src/title.rs +++ b/meta/src/title.rs @@ -234,6 +234,7 @@ impl AddAnyAttr for TitleView { impl RenderHtml for TitleView { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -283,6 +284,10 @@ impl RenderHtml for TitleView { }); TitleViewState { effect } } + + fn into_owned(self) -> Self::Owned { + self + } } impl Mountable for TitleViewState { diff --git a/router/src/flat_router.rs b/router/src/flat_router.rs index 1fac582e49..57d553fd73 100644 --- a/router/src/flat_router.rs +++ b/router/src/flat_router.rs @@ -348,7 +348,7 @@ impl AddAnyAttr for FlatRoutesView where Loc: LocationProvider + Send, Defs: MatchNestedRoutes + Send + 'static, - FalFn: FnOnce() -> Fal + Send, + FalFn: FnOnce() -> Fal + Send + 'static, Fal: RenderHtml + 'static, { type Output = @@ -421,10 +421,11 @@ impl RenderHtml for FlatRoutesView where Loc: LocationProvider + Send, Defs: MatchNestedRoutes + Send + 'static, - FalFn: FnOnce() -> Fal + Send, + FalFn: FnOnce() -> Fal + Send + 'static, Fal: RenderHtml + 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = as RenderHtml>::MIN_LENGTH; @@ -618,4 +619,8 @@ where } } } + + fn into_owned(self) -> Self::Owned { + self + } } diff --git a/router/src/nested_router.rs b/router/src/nested_router.rs index 2bc0679bb6..c04685d704 100644 --- a/router/src/nested_router.rs +++ b/router/src/nested_router.rs @@ -228,8 +228,8 @@ where impl AddAnyAttr for NestedRoutesView where Loc: LocationProvider + Send, - Defs: MatchNestedRoutes + Send, - FalFn: FnOnce() -> Fal + Send, + Defs: MatchNestedRoutes + Send + 'static, + FalFn: FnOnce() -> Fal + Send + 'static, Fal: RenderHtml + 'static, { type Output = @@ -249,11 +249,12 @@ where impl RenderHtml for NestedRoutesView where Loc: LocationProvider + Send, - Defs: MatchNestedRoutes + Send, - FalFn: FnOnce() -> Fal + Send, + Defs: MatchNestedRoutes + Send + 'static, + FalFn: FnOnce() -> Fal + Send + 'static, Fal: RenderHtml + 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; // TODO @@ -465,6 +466,10 @@ where view, } } + + fn into_owned(self) -> Self::Owned { + self + } } type OutletViewFn = Box Suspend + Send>; diff --git a/tachys/src/html/element/mod.rs b/tachys/src/html/element/mod.rs index 82dab71c75..06bb6d1cda 100644 --- a/tachys/src/html/element/mod.rs +++ b/tachys/src/html/element/mod.rs @@ -222,6 +222,7 @@ where Ch: RenderHtml + Send, { type AsyncOutput = HtmlElement; + type Owned = HtmlElement; const MIN_LENGTH: usize = if E::SELF_CLOSING { 3 // < ... /> @@ -406,6 +407,16 @@ where children, } } + + fn into_owned(self) -> Self::Owned { + HtmlElement { + #[cfg(any(debug_assertions, leptos_debuginfo))] + defined_at: self.defined_at, + tag: self.tag, + attributes: self.attributes.into_cloneable_owned(), + children: self.children.into_owned(), + } + } } /// Renders an [`Attribute`] (which can be one or more HTML attributes) into an HTML buffer. diff --git a/tachys/src/html/islands.rs b/tachys/src/html/islands.rs index e8590a9603..ba52f12a46 100644 --- a/tachys/src/html/islands.rs +++ b/tachys/src/html/islands.rs @@ -100,6 +100,7 @@ where View: RenderHtml, { type AsyncOutput = Island; + type Owned = Island; const MIN_LENGTH: usize = ISLAND_TAG.len() * 2 + "<>".len() @@ -187,6 +188,14 @@ where self.view.hydrate::(cursor, position) } + + fn into_owned(self) -> Self::Owned { + Island { + component: self.component, + props_json: self.props_json, + view: self.view.into_owned(), + } + } } /// The children that will be projected into an [`Island`]. @@ -267,6 +276,7 @@ where View: RenderHtml, { type AsyncOutput = IslandChildren; + type Owned = IslandChildren; const MIN_LENGTH: usize = ISLAND_CHILDREN_TAG.len() * 2 + "<>".len() @@ -372,4 +382,11 @@ where ); } } + + fn into_owned(self) -> Self::Owned { + IslandChildren { + view: self.view.into_owned(), + on_hydrate: self.on_hydrate, + } + } } diff --git a/tachys/src/html/mod.rs b/tachys/src/html/mod.rs index c4e8cba1fd..ea41a3b783 100644 --- a/tachys/src/html/mod.rs +++ b/tachys/src/html/mod.rs @@ -53,6 +53,7 @@ no_attrs!(Doctype); impl RenderHtml for Doctype { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = "".len(); @@ -81,6 +82,10 @@ impl RenderHtml for Doctype { _position: &PositionState, ) -> Self::State { } + + fn into_owned(self) -> Self::Owned { + self + } } /// An element that contains no interactivity, and whose contents can be known at compile time. @@ -155,6 +160,7 @@ impl AddAnyAttr for InertElement { impl RenderHtml for InertElement { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -196,4 +202,8 @@ impl RenderHtml for InertElement { position.set(Position::NextChild); InertElementState(self.html, el) } + + fn into_owned(self) -> Self::Owned { + self + } } diff --git a/tachys/src/oco.rs b/tachys/src/oco.rs index 1d43a4967e..4ad4f7066e 100644 --- a/tachys/src/oco.rs +++ b/tachys/src/oco.rs @@ -39,6 +39,7 @@ no_attrs!(Oco<'static, str>); impl RenderHtml for Oco<'static, str> { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -77,6 +78,10 @@ impl RenderHtml for Oco<'static, str> { ); OcoStrState { node, str: self } } + + fn into_owned(self) -> ::Owned { + self + } } impl ToTemplate for Oco<'static, str> { diff --git a/tachys/src/reactive_graph/mod.rs b/tachys/src/reactive_graph/mod.rs index 7274f4d61d..89fcbdf1cb 100644 --- a/tachys/src/reactive_graph/mod.rs +++ b/tachys/src/reactive_graph/mod.rs @@ -135,6 +135,7 @@ where V::State: 'static, { type AsyncOutput = V::AsyncOutput; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -210,6 +211,10 @@ where }) .into() } + + fn into_owned(self) -> Self::Owned { + self + } } impl AddAnyAttr for F @@ -605,6 +610,7 @@ mod stable { V::State: 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -668,6 +674,10 @@ mod stable { (move || self.get()) .hydrate::(cursor, position) } + + fn into_owned(self) -> Self::Owned { + self + } } impl AttributeValue for $sig @@ -788,6 +798,7 @@ mod stable { V::State: 'static, { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -851,6 +862,10 @@ mod stable { (move || self.get()) .hydrate::(cursor, position) } + + fn into_owned(self) -> Self::Owned { + self + } } #[allow(deprecated)] diff --git a/tachys/src/reactive_graph/owned.rs b/tachys/src/reactive_graph/owned.rs index 14446407ea..307db24255 100644 --- a/tachys/src/reactive_graph/owned.rs +++ b/tachys/src/reactive_graph/owned.rs @@ -92,6 +92,7 @@ where { // TODO type AsyncOutput = OwnedView; + type Owned = OwnedView; const MIN_LENGTH: usize = T::MIN_LENGTH; @@ -163,6 +164,13 @@ where fn dry_resolve(&mut self) { self.owner.with(|| self.view.dry_resolve()); } + + fn into_owned(self) -> Self::Owned { + OwnedView { + owner: self.owner, + view: self.view.into_owned(), + } + } } impl Mountable for OwnedViewState diff --git a/tachys/src/reactive_graph/suspense.rs b/tachys/src/reactive_graph/suspense.rs index d6846e8a89..6a3750b941 100644 --- a/tachys/src/reactive_graph/suspense.rs +++ b/tachys/src/reactive_graph/suspense.rs @@ -288,6 +288,7 @@ where T: RenderHtml + Sized + 'static, { type AsyncOutput = Option; + type Owned = Self; const MIN_LENGTH: usize = T::MIN_LENGTH; @@ -472,4 +473,8 @@ where as Pin + Send>>; } } + + fn into_owned(self) -> Self::Owned { + self + } } diff --git a/tachys/src/view/any_view.rs b/tachys/src/view/any_view.rs index 8f5ab9775a..951575be7a 100644 --- a/tachys/src/view/any_view.rs +++ b/tachys/src/view/any_view.rs @@ -162,14 +162,14 @@ where impl IntoAny for T where T: Send, - T: RenderHtml + 'static, + T: RenderHtml, T::State: 'static, { fn into_any(self) -> AnyView { #[cfg(feature = "ssr")] let html_len = self.html_len(); - let value = Box::new(self) as Box; + let value = Box::new(self.into_owned()) as Box; match value.downcast::() { // if it's already an AnyView, we don't need to double-wrap it @@ -178,7 +178,7 @@ where #[cfg(feature = "ssr")] let dry_resolve = |value: &mut Box| { let value = value - .downcast_mut::() + .downcast_mut::() .expect("AnyView::resolve could not be downcast"); value.dry_resolve(); }; @@ -186,7 +186,7 @@ where #[cfg(feature = "ssr")] let resolve = |value: Box| { let value = value - .downcast::() + .downcast::() .expect("AnyView::resolve could not be downcast"); Box::pin(async move { value.resolve().await.into_any() }) as Pin + Send>> @@ -200,10 +200,10 @@ where mark_branches: bool, extra_attrs: Vec| { let type_id = mark_branches - .then(|| format!("{:?}", TypeId::of::())) + .then(|| format!("{:?}", TypeId::of::())) .unwrap_or_default(); let value = value - .downcast::() + .downcast::() .expect("AnyView::to_html could not be downcast"); if mark_branches { buf.open_branch(&type_id); @@ -228,10 +228,10 @@ where mark_branches: bool, extra_attrs: Vec| { let type_id = mark_branches - .then(|| format!("{:?}", TypeId::of::())) + .then(|| format!("{:?}", TypeId::of::())) .unwrap_or_default(); let value = value - .downcast::() + .downcast::() .expect("AnyView::to_html could not be downcast"); if mark_branches { buf.open_branch(&type_id); @@ -256,7 +256,7 @@ where mark_branches: bool, extra_attrs: Vec| { let value = value - .downcast::() + .downcast::() .expect("AnyView::to_html could not be downcast"); value.to_html_async_with_buf::( buf, @@ -268,17 +268,17 @@ where }; let build = |value: Box| { let value = value - .downcast::() + .downcast::() .expect("AnyView::build couldn't downcast"); let state = Box::new(value.build()); AnyViewState { - type_id: TypeId::of::(), + type_id: TypeId::of::(), state, - mount: mount_any::, - unmount: unmount_any::, - insert_before_this: insert_before_this::, - elements: elements::, + mount: mount_any::, + unmount: unmount_any::, + insert_before_this: insert_before_this::, + elements: elements::, } }; #[cfg(feature = "hydrate")] @@ -286,19 +286,19 @@ where |value: Box, cursor: &Cursor, position: &PositionState| { - let value = value.downcast::().expect( + let value = value.downcast::().expect( "AnyView::hydrate_from_server couldn't downcast", ); let state = Box::new(value.hydrate::(cursor, position)); AnyViewState { - type_id: TypeId::of::(), + type_id: TypeId::of::(), state, - mount: mount_any::, - unmount: unmount_any::, - insert_before_this: insert_before_this::, - elements: elements::, + mount: mount_any::, + unmount: unmount_any::, + insert_before_this: insert_before_this::, + elements: elements::, } }; @@ -307,7 +307,7 @@ where value: Box, state: &mut AnyViewState| { let value = value - .downcast::() + .downcast::() .expect("AnyView::rebuild couldn't downcast value"); if new_type_id == state.type_id { let state = state.state.downcast_mut().expect( @@ -323,7 +323,7 @@ where }; AnyView { - type_id: TypeId::of::(), + type_id: TypeId::of::(), value, build, rebuild, @@ -379,6 +379,7 @@ impl AddAnyAttr for AnyView { impl RenderHtml for AnyView { type AsyncOutput = Self; + type Owned = Self; fn dry_resolve(&mut self) { #[cfg(feature = "ssr")] @@ -516,6 +517,10 @@ impl RenderHtml for AnyView { 0 } } + + fn into_owned(self) -> Self::Owned { + self + } } impl Mountable for AnyViewState { @@ -569,6 +574,7 @@ impl Render for AnyViewWithAttrs { impl RenderHtml for AnyViewWithAttrs { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; fn dry_resolve(&mut self) { @@ -646,6 +652,10 @@ impl RenderHtml for AnyViewWithAttrs { self.view.html_len() + self.attrs.iter().map(|attr| attr.html_len()).sum::() } + + fn into_owned(self) -> Self::Owned { + self + } } impl AddAnyAttr for AnyViewWithAttrs { diff --git a/tachys/src/view/either.rs b/tachys/src/view/either.rs index 19b4d02178..36c482c41a 100644 --- a/tachys/src/view/either.rs +++ b/tachys/src/view/either.rs @@ -129,6 +129,7 @@ where B: RenderHtml, { type AsyncOutput = Either; + type Owned = Either; fn dry_resolve(&mut self) { match self { @@ -254,6 +255,13 @@ where } } } + + fn into_owned(self) -> Self::Owned { + match self { + Either::Left(left) => Either::Left(left.into_owned()), + Either::Right(right) => Either::Right(right.into_owned()), + } + } } /// Stores each value in the view state, overwriting it only if `Some(_)` is provided. @@ -358,6 +366,7 @@ where B: RenderHtml, { type AsyncOutput = EitherKeepAlive; + type Owned = EitherKeepAlive; const MIN_LENGTH: usize = 0; @@ -477,6 +486,14 @@ where EitherKeepAliveState { showing_b, a, b } } + + fn into_owned(self) -> Self::Owned { + EitherKeepAlive { + a: self.a.map(|a| a.into_owned()), + b: self.b.map(|b| b.into_owned()), + show_b: self.show_b, + } + } } impl Mountable for EitherKeepAliveState @@ -653,6 +670,7 @@ macro_rules! tuples { { type AsyncOutput = []<$($ty::AsyncOutput,)*>; + type Owned = []<$($ty::Owned,)*>; const MIN_LENGTH: usize = max_usize(&[$($ty ::MIN_LENGTH,)*]); @@ -735,6 +753,14 @@ macro_rules! tuples { Self::State { state } } + + fn into_owned(self) -> Self::Owned { + match self { + $([]::$ty(this) => { + []::$ty(this.into_owned()) + })* + } + } } } } diff --git a/tachys/src/view/error_boundary.rs b/tachys/src/view/error_boundary.rs index 1bd238af7d..27fd1c484b 100644 --- a/tachys/src/view/error_boundary.rs +++ b/tachys/src/view/error_boundary.rs @@ -136,6 +136,7 @@ where E: Into + Send + 'static, { type AsyncOutput = Result; + type Owned = Result; const MIN_LENGTH: usize = T::MIN_LENGTH; @@ -228,4 +229,11 @@ where }; ResultState { state, error, hook } } + + fn into_owned(self) -> Self::Owned { + match self { + Ok(view) => Ok(view.into_owned()), + Err(e) => Err(e), + } + } } diff --git a/tachys/src/view/iterators.rs b/tachys/src/view/iterators.rs index 66d71c8163..49a8e0fbd8 100644 --- a/tachys/src/view/iterators.rs +++ b/tachys/src/view/iterators.rs @@ -60,6 +60,7 @@ where T: RenderHtml, { type AsyncOutput = Option; + type Owned = Option; const MIN_LENGTH: usize = T::MIN_LENGTH; @@ -139,6 +140,10 @@ where } .hydrate::(cursor, position) } + + fn into_owned(self) -> Self::Owned { + self.map(RenderHtml::into_owned) + } } impl Render for Vec @@ -273,6 +278,7 @@ where T: RenderHtml, { type AsyncOutput = Vec; + type Owned = Vec; const MIN_LENGTH: usize = 0; @@ -370,6 +376,12 @@ where VecState { states, marker } } + + fn into_owned(self) -> Self::Owned { + self.into_iter() + .map(RenderHtml::into_owned) + .collect::>() + } } impl Render for [T; N] @@ -459,6 +471,7 @@ where T: RenderHtml, { type AsyncOutput = [T::AsyncOutput; N]; + type Owned = [T::Owned; N]; const MIN_LENGTH: usize = 0; @@ -530,4 +543,12 @@ where self.map(|child| child.hydrate::(cursor, position)); ArrayState { states } } + + fn into_owned(self) -> Self::Owned { + self.into_iter() + .map(RenderHtml::into_owned) + .collect::>() + .try_into() + .unwrap_or_else(|_| unreachable!()) + } } diff --git a/tachys/src/view/keyed.rs b/tachys/src/view/keyed.rs index 702042b859..82e380fc71 100644 --- a/tachys/src/view/keyed.rs +++ b/tachys/src/view/keyed.rs @@ -131,9 +131,9 @@ where impl AddAnyAttr for Keyed where - I: IntoIterator + Send, + I: IntoIterator + Send + 'static, K: Eq + Hash + 'static, - KF: Fn(&T) -> K + Send, + KF: Fn(&T) -> K + Send + 'static, V: RenderHtml, V: 'static, VF: Fn(usize, T) -> (VFS, V) + Send + 'static, @@ -184,15 +184,16 @@ where impl RenderHtml for Keyed where - I: IntoIterator + Send, + I: IntoIterator + Send + 'static, K: Eq + Hash + 'static, - KF: Fn(&T) -> K + Send, + KF: Fn(&T) -> K + Send + 'static, V: RenderHtml + 'static, VF: Fn(usize, T) -> (VFS, V) + Send + 'static, VFS: Fn(usize) + 'static, T: 'static, { type AsyncOutput = Vec; // TODO + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -292,6 +293,10 @@ where rendered_items, } } + + fn into_owned(self) -> Self::Owned { + self + } } impl Mountable for KeyedState diff --git a/tachys/src/view/mod.rs b/tachys/src/view/mod.rs index 1984d50838..3c24bdee92 100644 --- a/tachys/src/view/mod.rs +++ b/tachys/src/view/mod.rs @@ -99,6 +99,9 @@ where /// The type of the view after waiting for all asynchronous data to load. type AsyncOutput: RenderHtml; + /// An equivalent value that is `'static`. + type Owned: RenderHtml + 'static; + /// The minimum length of HTML created when this view is rendered. const MIN_LENGTH: usize; @@ -298,6 +301,9 @@ where let position = PositionState::new(position); self.hydrate::(&cursor, &position) } + + /// Convert into the equivalent value that is `'static`. + fn into_owned(self) -> Self::Owned; } /// Allows a type to be mounted to the DOM. diff --git a/tachys/src/view/primitives.rs b/tachys/src/view/primitives.rs index a96f1a7f61..f6f4a22c93 100644 --- a/tachys/src/view/primitives.rs +++ b/tachys/src/view/primitives.rs @@ -70,6 +70,7 @@ macro_rules! render_primitive { impl RenderHtml for $child_type { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -115,6 +116,10 @@ macro_rules! render_primitive { [<$child_type:camel State>](node, self) } + + fn into_owned(self) -> Self::Owned { + self + } } impl<'a> ToTemplate for $child_type { diff --git a/tachys/src/view/static_types.rs b/tachys/src/view/static_types.rs index 750ad53d57..755148171e 100644 --- a/tachys/src/view/static_types.rs +++ b/tachys/src/view/static_types.rs @@ -159,6 +159,7 @@ where impl RenderHtml for Static { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = V.len(); @@ -221,6 +222,10 @@ impl RenderHtml for Static { Some(node) } + + fn into_owned(self) -> Self::Owned { + self + } } impl AddAnyAttr for Static { diff --git a/tachys/src/view/strings.rs b/tachys/src/view/strings.rs index 62ae18498e..3dcf31661f 100644 --- a/tachys/src/view/strings.rs +++ b/tachys/src/view/strings.rs @@ -39,6 +39,7 @@ impl<'a> Render for &'a str { impl RenderHtml for &str { type AsyncOutput = Self; + type Owned = String; const MIN_LENGTH: usize = 0; @@ -104,6 +105,10 @@ impl RenderHtml for &str { StrState { node, str: self } } + + fn into_owned(self) -> Self::Owned { + self.to_string() + } } impl ToTemplate for &str { @@ -172,6 +177,7 @@ impl Render for String { impl RenderHtml for String { const MIN_LENGTH: usize = 0; type AsyncOutput = Self; + type Owned = Self; fn dry_resolve(&mut self) {} @@ -210,6 +216,10 @@ impl RenderHtml for String { self.as_str().hydrate::(cursor, position); StringState { node, str: self } } + + fn into_owned(self) -> Self::Owned { + self + } } impl ToTemplate for String { @@ -371,6 +381,7 @@ impl Render for Arc { impl RenderHtml for Arc { type AsyncOutput = Self; + type Owned = Self; const MIN_LENGTH: usize = 0; @@ -412,6 +423,10 @@ impl RenderHtml for Arc { this.hydrate::(cursor, position); ArcStrState { node, str: self } } + + fn into_owned(self) -> Self::Owned { + self + } } impl ToTemplate for Arc { @@ -477,6 +492,7 @@ impl<'a> Render for Cow<'a, str> { impl RenderHtml for Cow<'_, str> { type AsyncOutput = Self; + type Owned = String; const MIN_LENGTH: usize = 0; @@ -518,6 +534,10 @@ impl RenderHtml for Cow<'_, str> { this.hydrate::(cursor, position); CowStrState { node, str: self } } + + fn into_owned(self) -> ::Owned { + self.into_owned() + } } impl ToTemplate for Cow<'_, str> { diff --git a/tachys/src/view/template.rs b/tachys/src/view/template.rs index acc7b0080e..4df169d174 100644 --- a/tachys/src/view/template.rs +++ b/tachys/src/view/template.rs @@ -76,6 +76,7 @@ where V::State: Mountable, { type AsyncOutput = V::AsyncOutput; + type Owned = V::Owned; const MIN_LENGTH: usize = V::MIN_LENGTH; @@ -111,6 +112,10 @@ where async fn resolve(self) -> Self::AsyncOutput { self.view.resolve().await } + + fn into_owned(self) -> Self::Owned { + self.view.into_owned() + } } impl ToTemplate for ViewTemplate diff --git a/tachys/src/view/tuples.rs b/tachys/src/view/tuples.rs index f8e5591c21..e299987321 100644 --- a/tachys/src/view/tuples.rs +++ b/tachys/src/view/tuples.rs @@ -23,6 +23,7 @@ impl Render for () { impl RenderHtml for () { type AsyncOutput = (); + type Owned = (); const MIN_LENGTH: usize = 3; const EXISTS: bool = false; @@ -52,6 +53,8 @@ impl RenderHtml for () { async fn resolve(self) -> Self::AsyncOutput {} fn dry_resolve(&mut self) {} + + fn into_owned(self) -> Self::Owned {} } impl AddAnyAttr for () { @@ -117,6 +120,7 @@ where A: RenderHtml, { type AsyncOutput = (A::AsyncOutput,); + type Owned = (A::Owned,); const MIN_LENGTH: usize = A::MIN_LENGTH; @@ -175,6 +179,10 @@ where fn dry_resolve(&mut self) { self.0.dry_resolve(); } + + fn into_owned(self) -> Self::Owned { + (self.0.into_owned(),) + } } impl ToTemplate for (A,) { @@ -247,6 +255,7 @@ macro_rules! impl_view_for_tuples { { type AsyncOutput = ($first::AsyncOutput, $($ty::AsyncOutput,)*); + type Owned = ($first::Owned, $($ty::Owned,)*); const MIN_LENGTH: usize = $first::MIN_LENGTH $(+ $ty::MIN_LENGTH)*; @@ -311,6 +320,15 @@ macro_rules! impl_view_for_tuples { $first.dry_resolve(); $($ty.dry_resolve());* } + + fn into_owned(self) -> Self::Owned { + #[allow(non_snake_case)] + let ($first, $($ty,)*) = self; + ( + $first.into_owned(), + $($ty.into_owned()),* + ) + } } impl<$first, $($ty),*> ToTemplate for ($first, $($ty,)*)