From 422fe9f43b67e22bfced47f5bdeb53f169b43246 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 4 Oct 2024 13:13:23 -0400 Subject: [PATCH 1/5] feat: add `IntoRender` for rendering arbitrary types --- leptos_macro/src/view/mod.rs | 4 +++- tachys/src/html/element/mod.rs | 16 +++++++++------- tachys/src/lib.rs | 4 ++-- tachys/src/view/mod.rs | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/leptos_macro/src/view/mod.rs b/leptos_macro/src/view/mod.rs index 045484f4d4..fc4e90cd6f 100644 --- a/leptos_macro/src/view/mod.rs +++ b/leptos_macro/src/view/mod.rs @@ -548,7 +548,9 @@ fn node_to_tokens( view_marker, disable_inert_html, ), - Node::Block(block) => Some(quote! { #block }), + Node::Block(block) => { + Some(quote! { ::leptos::prelude::IntoRender::into_render(#block) }) + } Node::Text(text) => Some(text_to_tokens(&text.value)), Node::RawText(raw) => { let text = raw.to_string_best(); diff --git a/tachys/src/html/element/mod.rs b/tachys/src/html/element/mod.rs index 49b52363ae..388eb3805b 100644 --- a/tachys/src/html/element/mod.rs +++ b/tachys/src/html/element/mod.rs @@ -4,8 +4,8 @@ use crate::{ renderer::{CastFrom, Rndr}, ssr::StreamBuilder, view::{ - add_attr::AddAnyAttr, Mountable, Position, PositionState, Render, - RenderHtml, ToTemplate, + add_attr::AddAnyAttr, IntoRender, Mountable, Position, PositionState, + Render, RenderHtml, ToTemplate, }, }; use const_str_slice_concat::{ @@ -65,11 +65,13 @@ impl ElementChild for HtmlElement where E: ElementWithChildren, Ch: Render + NextTuple, - ::Output: Render, + ::Output: Render, - NewChild: Render, + NewChild: IntoRender, + NewChild::Output: Render, { - type Output = HtmlElement::Output>; + type Output = + HtmlElement::Output>; fn child(self, child: NewChild) -> Self::Output { let HtmlElement { @@ -82,7 +84,7 @@ where tag, attributes, - children: children.next_tuple(child), + children: children.next_tuple(child.into_render()), } } } @@ -116,7 +118,7 @@ where /// Adds a child to the element. pub trait ElementChild where - NewChild: Render, + NewChild: IntoRender, { /// The type of the element, with the child added. type Output; diff --git a/tachys/src/lib.rs b/tachys/src/lib.rs index f9dbf83625..a14acbbcae 100644 --- a/tachys/src/lib.rs +++ b/tachys/src/lib.rs @@ -26,8 +26,8 @@ pub mod prelude { }, renderer::{dom::Dom, Renderer}, view::{ - add_attr::AddAnyAttr, any_view::IntoAny, Mountable, Render, - RenderHtml, + add_attr::AddAnyAttr, any_view::IntoAny, IntoRender, Mountable, + Render, RenderHtml, }, }; } diff --git a/tachys/src/view/mod.rs b/tachys/src/view/mod.rs index 9ea6c7b490..74f901ade3 100644 --- a/tachys/src/view/mod.rs +++ b/tachys/src/view/mod.rs @@ -432,3 +432,20 @@ pub enum Position { /// This is the last child of its parent. LastChild, } + +pub trait IntoRender { + type Output; + + fn into_render(self) -> Self::Output; +} + +impl IntoRender for T +where + T: Render, +{ + type Output = Self; + + fn into_render(self) -> Self::Output { + self + } +} From a4ed0cbe5b57a563c3b034ce8806bb6dab91d3d2 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 4 Oct 2024 13:24:39 -0400 Subject: [PATCH 2/5] feat: add `IntoAttributeValue` for rendering arbitrary attribute values --- leptos_macro/src/view/mod.rs | 12 +++++++++--- tachys/src/html/attribute/value.rs | 20 ++++++++++++++++++++ tachys/src/lib.rs | 1 + 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/leptos_macro/src/view/mod.rs b/leptos_macro/src/view/mod.rs index fc4e90cd6f..8e32856036 100644 --- a/leptos_macro/src/view/mod.rs +++ b/leptos_macro/src/view/mod.rs @@ -1365,14 +1365,20 @@ fn attribute_value(attr: &KeyedAttribute) -> TokenStream { } } - quote! { - {#expr} + if matches!(expr, Expr::Lit(_)) { + quote! { + #expr + } + } else { + quote! { + ::leptos::prelude::IntoAttributeValue::into_attribute_value(#expr) + } } } // any value in braces: expand as-is to give proper r-a support KVAttributeValue::InvalidBraced(block) => { quote! { - #block + ::leptos::prelude::IntoAttributeValue::into_attribute_value(#block) } } }, diff --git a/tachys/src/html/attribute/value.rs b/tachys/src/html/attribute/value.rs index 851bdbf12e..999bb3c6d8 100644 --- a/tachys/src/html/attribute/value.rs +++ b/tachys/src/html/attribute/value.rs @@ -11,6 +11,26 @@ use std::{ sync::Arc, }; +/// Declares that can be converted into some other type, which is a valid attribute value. +pub trait IntoAttributeValue { + /// The attribute value into which this type can be converted. + type Output; + + /// Consumes this value, transforming it into an attribute value. + fn into_attribute_value(self) -> Self::Output; +} + +impl IntoAttributeValue for T +where + T: AttributeValue, +{ + type Output = Self; + + fn into_attribute_value(self) -> Self::Output { + self + } +} + /// A possible value for an HTML attribute. pub trait AttributeValue: Send { /// The state that should be retained between building and rebuilding. diff --git a/tachys/src/lib.rs b/tachys/src/lib.rs index a14acbbcae..d8553a8694 100644 --- a/tachys/src/lib.rs +++ b/tachys/src/lib.rs @@ -19,6 +19,7 @@ pub mod prelude { OnAttribute, OnTargetAttribute, PropAttribute, StyleAttribute, }, + IntoAttributeValue, }, directive::DirectiveAttribute, element::{ElementChild, ElementExt, InnerHtmlAttribute}, From 5e8e93001d1dd7f2268ac3cf216f49768b502145 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 4 Oct 2024 13:25:57 -0400 Subject: [PATCH 3/5] docs: `IntoRender` and `IntoAttributeValue` --- tachys/src/html/attribute/value.rs | 2 +- tachys/src/view/mod.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tachys/src/html/attribute/value.rs b/tachys/src/html/attribute/value.rs index 999bb3c6d8..347ef6b47d 100644 --- a/tachys/src/html/attribute/value.rs +++ b/tachys/src/html/attribute/value.rs @@ -11,7 +11,7 @@ use std::{ sync::Arc, }; -/// Declares that can be converted into some other type, which is a valid attribute value. +/// Declares that this type can be converted into some other type, which is a valid attribute value. pub trait IntoAttributeValue { /// The attribute value into which this type can be converted. type Output; diff --git a/tachys/src/view/mod.rs b/tachys/src/view/mod.rs index 74f901ade3..61651d1e71 100644 --- a/tachys/src/view/mod.rs +++ b/tachys/src/view/mod.rs @@ -433,9 +433,12 @@ pub enum Position { LastChild, } +/// Declares that this type can be converted into some other type, which can be renderered. pub trait IntoRender { + /// The renderable type into which this type can be converted. type Output; + /// Consumes this value, transforming it into the renderable type. fn into_render(self) -> Self::Output; } From b39985d9b8a19a1c10123b8e4d1425fc74c38bcc Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 4 Oct 2024 13:38:09 -0400 Subject: [PATCH 4/5] fix: only use `IntoAttributeValue` for parts of view that are actually attribute values --- leptos_macro/src/view/mod.rs | 40 ++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/leptos_macro/src/view/mod.rs b/leptos_macro/src/view/mod.rs index 8e32856036..902f652dac 100644 --- a/leptos_macro/src/view/mod.rs +++ b/leptos_macro/src/view/mod.rs @@ -880,7 +880,7 @@ fn attribute_to_tokens( NodeName::Path(path) => path.path.get_ident(), _ => unreachable!(), }; - let value = attribute_value(node); + let value = attribute_value(node, false); quote! { .#node_ref(#value) } @@ -930,13 +930,13 @@ fn attribute_to_tokens( // we don't provide statically-checked methods for SVG attributes || (tag_type == TagType::Svg && name != "inner_html") { - let value = attribute_value(node); + let value = attribute_value(node, true); quote! { .attr(#name, #value) } } else { let key = attribute_name(&node.key); - let value = attribute_value(node); + let value = attribute_value(node, true); // special case of global_class and class attribute if &node.key.to_string() == "class" @@ -973,11 +973,11 @@ pub(crate) fn attribute_absolute( let id = &parts[0]; match id { NodeNameFragment::Ident(id) => { - let value = attribute_value(node); // ignore `let:` and `clone:` if id == "let" || id == "clone" { None } else if id == "attr" { + let value = attribute_value(node, true); let key = &parts[1]; let key_name = key.to_string(); if key_name == "class" || key_name == "style" { @@ -985,6 +985,7 @@ pub(crate) fn attribute_absolute( quote! { ::leptos::tachys::html::#key::#key(#value) }, ) } else if key_name == "aria" { + let value = attribute_value(node, true); let mut parts_iter = parts.iter(); parts_iter.next(); let fn_name = parts_iter.map(|p| p.to_string()).collect::>().join("_"); @@ -998,6 +999,7 @@ pub(crate) fn attribute_absolute( ) } } else if id == "use" { + let value = attribute_value(node, false); let key = &parts[1]; let param = if let Some(value) = node.value() { quote!(#value) @@ -1013,6 +1015,7 @@ pub(crate) fn attribute_absolute( }, ) } else if id == "style" || id == "class" { + let value = attribute_value(node, false); let key = &node.key.to_string(); let key = key .replacen("style:", "", 1) @@ -1021,12 +1024,14 @@ pub(crate) fn attribute_absolute( quote! { ::leptos::tachys::html::#id::#id((#key, #value)) }, ) } else if id == "prop" { + let value = attribute_value(node, false); let key = &node.key.to_string(); let key = key.replacen("prop:", "", 1); Some( quote! { ::leptos::tachys::html::property::#id(#key, #value) }, ) } else if id == "on" { + let value = attribute_value(node, false); let key = &node.key.to_string(); let key = key.replacen("on:", "", 1); let (on, ty, handler) = @@ -1077,7 +1082,7 @@ pub(crate) fn two_way_binding_to_tokens( name: &str, node: &KeyedAttribute, ) -> TokenStream { - let value = attribute_value(node); + let value = attribute_value(node, false); let ident = format_ident!("{}", name.to_case(UpperCamel), span = node.key.span()); @@ -1102,7 +1107,7 @@ pub(crate) fn event_type_and_handler( name: &str, node: &KeyedAttribute, ) -> (TokenStream, TokenStream, TokenStream) { - let handler = attribute_value(node); + let handler = attribute_value(node, false); let (event_type, is_custom, is_force_undelegated, is_targeted) = parse_event_name(name); @@ -1159,7 +1164,7 @@ fn class_to_tokens( class: TokenStream, class_name: Option<&str>, ) -> TokenStream { - let value = attribute_value(node); + let value = attribute_value(node, false); if let Some(class_name) = class_name { quote! { .#class((#class_name, #value)) @@ -1176,7 +1181,7 @@ fn style_to_tokens( style: TokenStream, style_name: Option<&str>, ) -> TokenStream { - let value = attribute_value(node); + let value = attribute_value(node, false); if let Some(style_name) = style_name { quote! { .#style((#style_name, #value)) @@ -1193,7 +1198,7 @@ fn prop_to_tokens( prop: TokenStream, key: &str, ) -> TokenStream { - let value = attribute_value(node); + let value = attribute_value(node, false); quote! { .#prop(#key, #value) } @@ -1350,7 +1355,10 @@ fn attribute_name(name: &NodeName) -> TokenStream { } } -fn attribute_value(attr: &KeyedAttribute) -> TokenStream { +fn attribute_value( + attr: &KeyedAttribute, + is_attribute_proper: bool, +) -> TokenStream { match attr.possible_value.to_value() { None => quote! { true }, Some(value) => match &value.value { @@ -1365,7 +1373,7 @@ fn attribute_value(attr: &KeyedAttribute) -> TokenStream { } } - if matches!(expr, Expr::Lit(_)) { + if matches!(expr, Expr::Lit(_)) || !is_attribute_proper { quote! { #expr } @@ -1377,8 +1385,14 @@ fn attribute_value(attr: &KeyedAttribute) -> TokenStream { } // any value in braces: expand as-is to give proper r-a support KVAttributeValue::InvalidBraced(block) => { - quote! { - ::leptos::prelude::IntoAttributeValue::into_attribute_value(#block) + if is_attribute_proper { + quote! { + ::leptos::prelude::IntoAttributeValue::into_attribute_value(#block) + } + } else { + quote! { + #block + } } } }, From ab9de1b8c0f797b6d731f02368fd8f03fff4fcc4 Mon Sep 17 00:00:00 2001 From: Greg Johnston Date: Fri, 4 Oct 2024 13:56:38 -0400 Subject: [PATCH 5/5] chore: remove unused variable --- leptos_macro/src/view/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/leptos_macro/src/view/mod.rs b/leptos_macro/src/view/mod.rs index 902f652dac..d4d1783055 100644 --- a/leptos_macro/src/view/mod.rs +++ b/leptos_macro/src/view/mod.rs @@ -985,7 +985,7 @@ pub(crate) fn attribute_absolute( quote! { ::leptos::tachys::html::#key::#key(#value) }, ) } else if key_name == "aria" { - let value = attribute_value(node, true); + let value = attribute_value(node, true); let mut parts_iter = parts.iter(); parts_iter.next(); let fn_name = parts_iter.map(|p| p.to_string()).collect::>().join("_"); @@ -999,7 +999,6 @@ pub(crate) fn attribute_absolute( ) } } else if id == "use" { - let value = attribute_value(node, false); let key = &parts[1]; let param = if let Some(value) = node.value() { quote!(#value) @@ -1015,7 +1014,7 @@ pub(crate) fn attribute_absolute( }, ) } else if id == "style" || id == "class" { - let value = attribute_value(node, false); + let value = attribute_value(node, false); let key = &node.key.to_string(); let key = key .replacen("style:", "", 1) @@ -1024,14 +1023,13 @@ pub(crate) fn attribute_absolute( quote! { ::leptos::tachys::html::#id::#id((#key, #value)) }, ) } else if id == "prop" { - let value = attribute_value(node, false); + let value = attribute_value(node, false); let key = &node.key.to_string(); let key = key.replacen("prop:", "", 1); Some( quote! { ::leptos::tachys::html::property::#id(#key, #value) }, ) } else if id == "on" { - let value = attribute_value(node, false); let key = &node.key.to_string(); let key = key.replacen("on:", "", 1); let (on, ty, handler) =