diff --git a/leptos_dom/src/html.rs b/leptos_dom/src/html.rs index a3105c8e7e..c0a31fe42d 100644 --- a/leptos_dom/src/html.rs +++ b/leptos_dom/src/html.rs @@ -63,7 +63,9 @@ cfg_if! { use crate::{ ev::EventDescriptor, hydration::HydrationCtx, - macro_helpers::{IntoAttribute, IntoClass, IntoProperty, IntoStyle}, + macro_helpers::{ + Attribute, IntoAttribute, IntoClass, IntoProperty, IntoStyle, + }, Element, Fragment, IntoView, NodeRef, Text, View, }; use leptos_reactive::Oco; @@ -593,8 +595,6 @@ impl HtmlElement { #[cfg(not(all(target_arch = "wasm32", feature = "web")))] { - use crate::macro_helpers::Attribute; - let mut this = self; let mut attr = attr.into_attribute(); @@ -622,6 +622,18 @@ impl HtmlElement { } } + /// Adds multiple attributes to the element + #[track_caller] + pub fn attrs( + mut self, + attrs: impl std::iter::IntoIterator, + ) -> Self { + for (name, value) in attrs { + self = self.attr(name, value); + } + self + } + /// Adds a class to an element. /// /// **Note**: In the builder syntax, this will be overwritten by the `class` diff --git a/leptos_macro/src/view/client_builder.rs b/leptos_macro/src/view/client_builder.rs index b0e559052e..6cd21bd521 100644 --- a/leptos_macro/src/view/client_builder.rs +++ b/leptos_macro/src/view/client_builder.rs @@ -217,6 +217,27 @@ pub(crate) fn element_to_tokens( None } }); + let spread_attrs = node.attributes().iter().filter_map(|node| { + use rstml::node::NodeBlock; + use syn::{Expr, ExprRange, RangeLimits, Stmt}; + + if let NodeAttribute::Block(NodeBlock::ValidBlock(block)) = node { + match block.stmts.first()? { + Stmt::Expr( + Expr::Range(ExprRange { + start: None, + limits: RangeLimits::HalfOpen(_), + end: Some(end), + .. + }), + _, + ) => Some(quote! { .attrs(#[allow(unused_brace)] {#end}) }), + _ => None, + } + } else { + None + } + }); let class_attrs = node.attributes().iter().filter_map(|node| { if let NodeAttribute::Attribute(node) = node { let name = node.key.to_string(); @@ -332,6 +353,7 @@ pub(crate) fn element_to_tokens( #(#ide_helper_close_tag)* #name #(#attrs)* + #(#spread_attrs)* #(#class_attrs)* #(#style_attrs)* #global_class_expr diff --git a/leptos_macro/src/view/server_template.rs b/leptos_macro/src/view/server_template.rs index bc98659793..2c55cabc94 100644 --- a/leptos_macro/src/view/server_template.rs +++ b/leptos_macro/src/view/server_template.rs @@ -265,6 +265,31 @@ fn element_to_tokens_ssr( ); } } + for attr in node.attributes() { + use syn::{Expr, ExprRange, RangeLimits, Stmt}; + + if let NodeAttribute::Block(NodeBlock::ValidBlock(block)) = attr { + if let Some(Stmt::Expr( + Expr::Range(ExprRange { + start: None, + limits: RangeLimits::HalfOpen(_), + end: Some(end), + .. + }), + _, + )) = block.stmts.first() + { + // should basically be the resolved attributes, joined on spaces, placed into + // the template + template.push_str(" {}"); + holes.push(quote! { + {#end}.into_iter().filter_map(|(name, attr)| { + Some(format!("{}={}", name, ::leptos::leptos_dom::ssr::escape_attr(&attr.as_nameless_value_string()?))) + }).collect::>().join(" ") + }); + }; + } + } // insert hydration ID let hydration_id = if is_root {