+ "You can spread any valid attribute, including a tuple of attributes, with the {..attr} syntax"
+
+
+
- // Overwriting an event handler, here on:click, will result in a panic in debug builds. In release builds, the initial handler is kept.
- // If spreading is used, prefer manually merging event handlers in the binding list instead.
- //
- // "with overwritten click handler"
- //
+
+
+
+ "The .. is not required to spread; you can pass any valid attribute in a block by itself."
+
+
+
+
+
+
+
+
}
+ // TODO check below
+ // Overwriting an event handler, here on:click, will result in a panic in debug builds. In release builds, the initial handler is kept.
+ // If spreading is used, prefer manually merging event handlers in the binding list instead.
+ //
+ // "with overwritten click handler"
+ //
}
diff --git a/examples/spread/src/main.rs b/examples/spread/src/main.rs
index afde734c8..9bd4aadf1 100644
--- a/examples/spread/src/main.rs
+++ b/examples/spread/src/main.rs
@@ -4,9 +4,5 @@ use spread::SpreadingExample;
pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
- mount_to_body(|| {
- view! {
-
- }
- })
+ mount::mount_to_body(SpreadingExample)
}
diff --git a/leptos/src/error_boundary.rs b/leptos/src/error_boundary.rs
index 7984326cb..7e521e5ff 100644
--- a/leptos/src/error_boundary.rs
+++ b/leptos/src/error_boundary.rs
@@ -10,10 +10,14 @@ use reactive_graph::{
use rustc_hash::FxHashMap;
use std::{marker::PhantomData, sync::Arc};
use tachys::{
+ html::attribute::Attribute,
hydration::Cursor,
renderer::{CastFrom, Renderer},
ssr::StreamBuilder,
- view::{Mountable, Position, PositionState, Render, RenderHtml},
+ view::{
+ add_attr::AddAnyAttr, Mountable, Position, PositionState, Render,
+ RenderHtml,
+ },
};
use throw_error::{Error, ErrorHook, ErrorId};
@@ -193,6 +197,41 @@ where
}
}
+impl
AddAnyAttr for ErrorBoundaryView
+where
+ Chil: RenderHtml,
+ Fal: RenderHtml + Send,
+ Rndr: Renderer,
+{
+ type Output> =
+ ErrorBoundaryView, Fal, Rndr>;
+
+ fn add_any_attr>(
+ self,
+ attr: NewAttr,
+ ) -> Self::Output
+ where
+ Self::Output: RenderHtml,
+ {
+ let ErrorBoundaryView {
+ boundary_id,
+ errors_empty,
+ children,
+ fallback,
+ errors,
+ rndr,
+ } = self;
+ ErrorBoundaryView {
+ boundary_id,
+ errors_empty,
+ children: children.add_any_attr(attr),
+ fallback,
+ errors,
+ rndr,
+ }
+ }
+}
+
impl RenderHtml for ErrorBoundaryView
where
Chil: RenderHtml,
diff --git a/leptos/src/for_loop.rs b/leptos/src/for_loop.rs
index c02ac35f2..049eca1f7 100644
--- a/leptos/src/for_loop.rs
+++ b/leptos/src/for_loop.rs
@@ -55,7 +55,7 @@ pub fn For(
) -> impl IntoView
where
IF: Fn() -> I + Send + 'static,
- I: IntoIterator- + Send,
+ I: IntoIterator
- + Send + 'static,
EF: Fn(T) -> N + Send + Clone + 'static,
N: IntoView + 'static,
KF: Fn(&T) -> K + Send + Clone + 'static,
diff --git a/leptos/src/lib.rs b/leptos/src/lib.rs
index 1fd994b26..ea8317d17 100644
--- a/leptos/src/lib.rs
+++ b/leptos/src/lib.rs
@@ -249,6 +249,8 @@ pub mod context {
pub use leptos_server as server;
/// HTML element types.
pub use tachys::html::element as html;
+/// HTML attribute types.
+pub use tachys::html::attribute as attr;
/// HTML event types.
#[doc(no_inline)]
pub use tachys::html::event as ev;
diff --git a/leptos/src/suspense_component.rs b/leptos/src/suspense_component.rs
index a9db10034..a49286206 100644
--- a/leptos/src/suspense_component.rs
+++ b/leptos/src/suspense_component.rs
@@ -17,15 +17,18 @@ use std::{
cell::RefCell,
fmt::Debug,
future::{ready, Future, Ready},
+ pin::Pin,
rc::Rc,
};
use tachys::{
either::Either,
+ html::attribute::Attribute,
hydration::Cursor,
reactive_graph::RenderEffectState,
renderer::{dom::Dom, Renderer},
ssr::StreamBuilder,
view::{
+ add_attr::AddAnyAttr,
any_view::AnyView,
either::{EitherKeepAlive, EitherKeepAliveState},
iterators::OptionState,
@@ -67,8 +70,8 @@ pub(crate) struct SuspenseBoundary {
impl Render
for SuspenseBoundary
where
- Fal: Render + 'static,
- Chil: Render + 'static,
+ Fal: Render + Send + 'static,
+ Chil: Render + Send + 'static,
Rndr: Renderer + 'static,
{
type State =
@@ -100,6 +103,40 @@ where
fn rebuild(self, _state: &mut Self::State) {}
}
+impl AddAnyAttr
+ for SuspenseBoundary
+where
+ Fal: RenderHtml + Send + 'static,
+ Chil: RenderHtml + Send + 'static,
+ Rndr: Renderer + 'static,
+{
+ type Output> = SuspenseBoundary<
+ TRANSITION,
+ Fal,
+ Chil::Output,
+ >;
+
+ fn add_any_attr>(
+ self,
+ attr: NewAttr,
+ ) -> Self::Output
+ where
+ Self::Output: RenderHtml,
+ {
+ let attr = attr.into_cloneable_owned();
+ let SuspenseBoundary {
+ none_pending,
+ fallback,
+ children,
+ } = self;
+ SuspenseBoundary {
+ none_pending,
+ fallback,
+ children: children.add_any_attr(attr),
+ }
+ }
+}
+
impl RenderHtml
for SuspenseBoundary
where
@@ -341,6 +378,39 @@ where
}
}
+impl AddAnyAttr for Suspend
+where
+ Fut: Future + Send + 'static,
+ Fut::Output: AddAnyAttr,
+ Rndr: Renderer + 'static,
+{
+ type Output> = Suspend<
+ Pin<
+ Box<
+ dyn Future<
+ Output = >::Output<
+ SomeNewAttr::CloneableOwned,
+ >,
+ > + Send,
+ >,
+ >,
+ >;
+
+ fn add_any_attr>(
+ self,
+ attr: NewAttr,
+ ) -> Self::Output
+ where
+ Self::Output: RenderHtml,
+ {
+ let attr = attr.into_cloneable_owned();
+ Suspend(Box::pin(async move {
+ let this = self.0.await;
+ this.add_any_attr(attr)
+ }))
+ }
+}
+
impl RenderHtml for Suspend
where
Fut: Future + Send + 'static,
diff --git a/leptos_macro/src/view/component_builder.rs b/leptos_macro/src/view/component_builder.rs
index b6fcda331..d8571994b 100644
--- a/leptos_macro/src/view/component_builder.rs
+++ b/leptos_macro/src/view/component_builder.rs
@@ -102,6 +102,37 @@ pub(crate) fn component_to_tokens(
})
.collect::>();
+ let spreads = node.attributes().iter().filter_map(|attr| {
+ use rstml::node::NodeBlock;
+ use syn::{Expr, ExprRange, RangeLimits, Stmt};
+
+ if let NodeAttribute::Block(block) = attr {
+ let dotted = if let NodeBlock::ValidBlock(block) = block {
+ match block.stmts.first() {
+ Some(Stmt::Expr(
+ Expr::Range(ExprRange {
+ start: None,
+ limits: RangeLimits::HalfOpen(_),
+ end: Some(end),
+ ..
+ }),
+ _,
+ )) => Some(quote! { .add_any_attr(#end) }),
+ _ => None,
+ }
+ } else {
+ None
+ };
+ Some(dotted.unwrap_or_else(|| {
+ quote! {
+ .add_any_attr(#[allow(unused_braces)] { #node })
+ }
+ }))
+ } else {
+ None
+ }
+ });
+
/*let directives = attrs
.clone()
.filter_map(|attr| {
@@ -244,6 +275,7 @@ pub(crate) fn component_to_tokens(
#name_ref,
props
)
+ #(#spreads)*
}
};
diff --git a/leptos_macro/src/view/mod.rs b/leptos_macro/src/view/mod.rs
index 80c2a32b4..53976de26 100644
--- a/leptos_macro/src/view/mod.rs
+++ b/leptos_macro/src/view/mod.rs
@@ -6,9 +6,13 @@ use leptos_hot_reload::parsing::is_component_node;
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use proc_macro_error::abort;
use quote::{quote, quote_spanned, ToTokens};
-use rstml::node::{KeyedAttribute, Node, NodeAttribute, NodeElement, NodeName};
+use rstml::node::{
+ KeyedAttribute, Node, NodeAttribute, NodeBlock, NodeElement, NodeName,
+};
use std::collections::HashMap;
-use syn::{spanned::Spanned, Expr, ExprPath, Lit, LitStr};
+use syn::{
+ spanned::Spanned, Expr, ExprPath, ExprRange, Lit, LitStr, RangeLimits, Stmt,
+};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum TagType {
@@ -300,7 +304,29 @@ fn attribute_to_tokens(
global_class: Option<&TokenTree>,
) -> TokenStream {
match node {
- NodeAttribute::Block(_) => todo!(),
+ NodeAttribute::Block(node) => {
+ let dotted = if let NodeBlock::ValidBlock(block) = node {
+ match block.stmts.first() {
+ Some(Stmt::Expr(
+ Expr::Range(ExprRange {
+ start: None,
+ limits: RangeLimits::HalfOpen(_),
+ end: Some(end),
+ ..
+ }),
+ _,
+ )) => Some(quote! { .add_any_attr(#end) }),
+ _ => None,
+ }
+ } else {
+ None
+ };
+ dotted.unwrap_or_else(|| {
+ quote! {
+ .add_any_attr(#[allow(unused_braces)] { #node })
+ }
+ })
+ }
NodeAttribute::Attribute(node) => {
let name = node.key.to_string();
if name == "node_ref" {
diff --git a/tachys/src/lib.rs b/tachys/src/lib.rs
index d9aab3995..3c708ee65 100644
--- a/tachys/src/lib.rs
+++ b/tachys/src/lib.rs
@@ -17,7 +17,7 @@ pub mod prelude {
node_ref::NodeRefAttribute,
},
renderer::{dom::Dom, Renderer},
- view::{Mountable, Render, RenderHtml},
+ view::{add_attr::AddAnyAttr, Mountable, Render, RenderHtml},
};
}
diff --git a/tachys/src/oco.rs b/tachys/src/oco.rs
index cd5a57b49..b2b2e97e8 100644
--- a/tachys/src/oco.rs
+++ b/tachys/src/oco.rs
@@ -109,6 +109,7 @@ where
{
type State = (R::Element, Oco<'static, str>);
type Cloneable = Self;
+ type CloneableOwned = Self;
fn html_len(&self) -> usize {
self.as_str().len()
@@ -151,6 +152,12 @@ where
self.upgrade_inplace();
self
}
+
+ fn into_cloneable_owned(mut self) -> Self::CloneableOwned {
+ // ensure it's reference-counted
+ self.upgrade_inplace();
+ self
+ }
}
impl IntoClass for Oco<'static, str>
@@ -159,6 +166,7 @@ where
{
type State = (R::Element, Self);
type Cloneable = Self;
+ type CloneableOwned = Self;
fn html_len(&self) -> usize {
self.as_str().len()
@@ -193,4 +201,10 @@ where
self.upgrade_inplace();
self
}
+
+ fn into_cloneable_owned(mut self) -> Self::CloneableOwned {
+ // ensure it's reference-counted
+ self.upgrade_inplace();
+ self
+ }
}
diff --git a/tachys/src/reactive_graph/class.rs b/tachys/src/reactive_graph/class.rs
index 660b51149..05abe89c0 100644
--- a/tachys/src/reactive_graph/class.rs
+++ b/tachys/src/reactive_graph/class.rs
@@ -376,6 +376,8 @@ mod stable {
R: DomRenderer,
{
type State = RenderEffectState;
+ type Cloneable = Self;
+ type CloneableOwned = Self;
fn html_len(&self) -> usize {
0
@@ -400,6 +402,14 @@ mod stable {
fn rebuild(self, _state: &mut Self::State) {
// TODO rebuild here?
}
+
+ fn into_cloneable(self) -> Self::Cloneable {
+ self
+ }
+
+ fn into_cloneable_owned(self) -> Self::CloneableOwned {
+ self
+ }
}
impl IntoClass for (&'static str, $sig)
@@ -407,6 +417,8 @@ mod stable {
R: DomRenderer,
{
type State = RenderEffectState<(R::ClassList, bool)>;
+ type Cloneable = Self;
+ type CloneableOwned = Self;
fn html_len(&self) -> usize {
self.0.len()
@@ -437,6 +449,14 @@ mod stable {
fn rebuild(self, _state: &mut Self::State) {
// TODO rebuild here?
}
+
+ fn into_cloneable(self) -> Self::Cloneable {
+ self
+ }
+
+ fn into_cloneable_owned(self) -> Self::CloneableOwned {
+ self
+ }
}
};
}
@@ -450,6 +470,8 @@ mod stable {
R: DomRenderer,
{
type State = RenderEffectState;
+ type Cloneable = Self;
+ type CloneableOwned = Self;
fn html_len(&self) -> usize {
0
@@ -474,6 +496,14 @@ mod stable {
fn rebuild(self, _state: &mut Self::State) {
// TODO rebuild here?
}
+
+ fn into_cloneable(self) -> Self::Cloneable {
+ self
+ }
+
+ fn into_cloneable_owned(self) -> Self::CloneableOwned {
+ self
+ }
}
impl IntoClass for (&'static str, $sig)
@@ -481,6 +511,8 @@ mod stable {
R: DomRenderer,
{
type State = RenderEffectState<(R::ClassList, bool)>;
+ type Cloneable = Self;
+ type CloneableOwned = Self;
fn html_len(&self) -> usize {
self.0.len()
@@ -511,6 +543,14 @@ mod stable {
fn rebuild(self, _state: &mut Self::State) {
// TODO rebuild here?
}
+
+ fn into_cloneable(self) -> Self::Cloneable {
+ self
+ }
+
+ fn into_cloneable_owned(self) -> Self::CloneableOwned {
+ self
+ }
}
};
}
diff --git a/tachys/src/reactive_graph/inner_html.rs b/tachys/src/reactive_graph/inner_html.rs
index 6a55b2925..6a604a3ac 100644
--- a/tachys/src/reactive_graph/inner_html.rs
+++ b/tachys/src/reactive_graph/inner_html.rs
@@ -90,6 +90,8 @@ mod stable {
R: DomRenderer,
{
type State = RenderEffect;
+ type Cloneable = Self;
+ type CloneableOwned = Self;
fn html_len(&self) -> usize {
0
@@ -114,6 +116,14 @@ mod stable {
}
fn rebuild(self, _state: &mut Self::State) {}
+
+ fn into_cloneable(self) -> Self::Cloneable {
+ self
+ }
+
+ fn into_cloneable_owned(self) -> Self::CloneableOwned {
+ self
+ }
}
};
}
@@ -127,6 +137,8 @@ mod stable {
R: DomRenderer,
{
type State = RenderEffect;
+ type Cloneable = Self;
+ type CloneableOwned = Self;
fn html_len(&self) -> usize {
0
@@ -151,6 +163,14 @@ mod stable {
}
fn rebuild(self, _state: &mut Self::State) {}
+
+ fn into_cloneable(self) -> Self::Cloneable {
+ self
+ }
+
+ fn into_cloneable_owned(self) -> Self::CloneableOwned {
+ self
+ }
}
};
}
diff --git a/tachys/src/reactive_graph/mod.rs b/tachys/src/reactive_graph/mod.rs
index cc6019219..bc983b72d 100644
--- a/tachys/src/reactive_graph/mod.rs
+++ b/tachys/src/reactive_graph/mod.rs
@@ -147,7 +147,7 @@ where
impl RenderHtml for F
where
F: ReactiveFunction