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 {