2
0
Fork 0
mirror of https://github.com/leptos-rs/leptos synced 2025-02-03 07:23:26 +00:00

AddAnyAttr working with erase_components ()

* AddAnyAttr working with erase_components

* CI fixes
This commit is contained in:
zakstucke 2025-01-26 17:43:32 +00:00 committed by GitHub
parent b62ae56094
commit 72f960a026
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 717 additions and 275 deletions

8
Cargo.lock generated
View file

@ -865,6 +865,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408"
[[package]]
name = "dyn-clone"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
version = "1.13.0"
@ -3415,8 +3421,10 @@ name = "tachys"
version = "0.1.4"
dependencies = [
"any_spawner",
"async-trait",
"const_str_slice_concat",
"drain_filter_polyfill",
"dyn-clone",
"either_of",
"futures",
"html-escape",

View file

@ -171,7 +171,7 @@ pub(crate) fn component_to_tokens(
let spreads = (!(spreads.is_empty())).then(|| {
quote! {
.add_any_attr((#(#spreads,)*))
.add_any_attr((#(#spreads,)*).into_attr())
}
});

View file

@ -20,6 +20,8 @@ reactive_graph = { workspace = true, optional = true }
reactive_stores = { workspace = true, optional = true }
slotmap = { version = "1.0", optional = true }
oco_ref = { workspace = true, optional = true }
async-trait = "0.1.81"
dyn-clone = "1.0.17"
once_cell = "1.20"
paste = "1.0"
wasm-bindgen = "0.2.97"
@ -198,4 +200,8 @@ skip_feature_sets = [
]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(leptos_debuginfo)'] }
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(leptos_debuginfo)',
'cfg(erase_components)',
] }

View file

@ -1,4 +1,5 @@
use super::{Attribute, NextAttribute};
use dyn_clone::DynClone;
use std::{
any::{Any, TypeId},
fmt::Debug,
@ -6,31 +7,64 @@ use std::{
#[cfg(feature = "ssr")]
use std::{future::Future, pin::Pin};
trait DynAttr: DynClone + Any + Send + 'static {
fn into_any(self: Box<Self>) -> Box<dyn Any>;
#[cfg(feature = "ssr")]
fn as_any_mut(&mut self) -> &mut dyn Any;
}
dyn_clone::clone_trait_object!(DynAttr);
impl<T: Clone> DynAttr for T
where
T: Attribute + 'static,
{
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
#[cfg(feature = "ssr")]
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
/// A type-erased container for any [`Attribute`].
#[derive(Clone)]
pub struct AnyAttribute {
type_id: TypeId,
html_len: usize,
value: Box<dyn Any + Send>,
value: Box<dyn DynAttr>,
#[cfg(feature = "ssr")]
to_html:
fn(Box<dyn Any>, &mut String, &mut String, &mut String, &mut String),
to_html: fn(
Box<dyn DynAttr>,
&mut String,
&mut String,
&mut String,
&mut String,
),
build: fn(
Box<dyn Any>,
Box<dyn DynAttr>,
el: &crate::renderer::types::Element,
) -> AnyAttributeState,
rebuild: fn(TypeId, Box<dyn Any>, &mut AnyAttributeState),
rebuild: fn(TypeId, Box<dyn DynAttr>, &mut AnyAttributeState),
#[cfg(feature = "hydrate")]
hydrate_from_server:
fn(Box<dyn Any>, &crate::renderer::types::Element) -> AnyAttributeState,
hydrate_from_server: fn(
Box<dyn DynAttr>,
&crate::renderer::types::Element,
) -> AnyAttributeState,
#[cfg(feature = "hydrate")]
hydrate_from_template:
fn(Box<dyn Any>, &crate::renderer::types::Element) -> AnyAttributeState,
hydrate_from_template: fn(
Box<dyn DynAttr>,
&crate::renderer::types::Element,
) -> AnyAttributeState,
#[cfg(feature = "ssr")]
#[allow(clippy::type_complexity)]
resolve:
fn(Box<dyn Any>) -> Pin<Box<dyn Future<Output = AnyAttribute> + Send>>,
resolve: fn(
Box<dyn DynAttr>,
) -> Pin<Box<dyn Future<Output = AnyAttribute> + Send>>,
#[cfg(feature = "ssr")]
dry_resolve: fn(&mut Box<dyn Any + Send>),
dry_resolve: fn(&mut Box<dyn DynAttr>),
}
impl Debug for AnyAttribute {
@ -55,145 +89,138 @@ pub trait IntoAnyAttribute {
impl<T> IntoAnyAttribute for T
where
Self: Send,
T: Attribute + 'static,
T::State: 'static,
T: Attribute,
crate::renderer::types::Element: Clone,
{
// inlining allows the compiler to remove the unused functions
// i.e., doesn't ship HTML-generating code that isn't used
#[inline(always)]
fn into_any_attr(self) -> AnyAttribute {
let html_len = self.html_len();
let value = Box::new(self) as Box<dyn Any + Send>;
match value.downcast::<AnyAttribute>() {
let value =
Box::new(self.into_cloneable_owned()) as Box<dyn Any + Send>;
let value = match (value as Box<dyn Any>).downcast::<AnyAttribute>() {
// if it's already an AnyAttribute, we don't need to double-wrap it
Ok(any_attribute) => *any_attribute,
Err(value) => {
#[cfg(feature = "ssr")]
let to_html =
|value: Box<dyn Any>,
buf: &mut String,
class: &mut String,
style: &mut String,
inner_html: &mut String| {
let value = value.downcast::<T>().expect(
"AnyAttribute::to_html could not be downcast",
);
value.to_html(buf, class, style, inner_html);
};
let build =
|value: Box<dyn Any>,
Ok(any_attribute) => return *any_attribute,
Err(value) => value.downcast::<T::CloneableOwned>().unwrap(),
};
#[cfg(feature = "ssr")]
let to_html = |value: Box<dyn DynAttr>,
buf: &mut String,
class: &mut String,
style: &mut String,
inner_html: &mut String| {
let value = value
.into_any()
.downcast::<T::CloneableOwned>()
.expect("AnyAttribute::to_html could not be downcast");
value.to_html(buf, class, style, inner_html);
};
let build = |value: Box<dyn DynAttr>,
el: &crate::renderer::types::Element| {
let value = value
.downcast::<T>()
.expect("AnyAttribute::build couldn't downcast");
let state = Box::new(value.build(el));
let value = value
.into_any()
.downcast::<T::CloneableOwned>()
.expect("AnyAttribute::build couldn't downcast");
let state = Box::new(value.build(el));
AnyAttributeState {
type_id: TypeId::of::<T>(),
state,
el: el.clone(),
}
};
#[cfg(feature = "hydrate")]
let hydrate_from_server =
|value: Box<dyn Any>,
el: &crate::renderer::types::Element| {
let value = value.downcast::<T>().expect(
"AnyAttribute::hydrate_from_server couldn't \
downcast",
);
let state = Box::new(value.hydrate::<true>(el));
AnyAttributeState {
type_id: TypeId::of::<T>(),
state,
el: el.clone(),
}
};
#[cfg(feature = "hydrate")]
let hydrate_from_template =
|value: Box<dyn Any>,
el: &crate::renderer::types::Element| {
let value = value.downcast::<T>().expect(
"AnyAttribute::hydrate_from_server couldn't \
downcast",
);
let state = Box::new(value.hydrate::<true>(el));
AnyAttributeState {
type_id: TypeId::of::<T>(),
state,
el: el.clone(),
}
};
let rebuild =
|new_type_id: TypeId,
value: Box<dyn Any>,
state: &mut AnyAttributeState| {
let value = value.downcast::<T>().expect(
"AnyAttribute::rebuild couldn't downcast value",
);
if new_type_id == state.type_id {
let state = state.state.downcast_mut().expect(
"AnyAttribute::rebuild couldn't downcast state",
);
value.rebuild(state);
} else {
let new = value.into_any_attr().build(&state.el);
*state = new;
}
};
#[cfg(feature = "ssr")]
let dry_resolve = |value: &mut Box<dyn Any + Send>| {
let value = value
.downcast_mut::<T>()
.expect("AnyView::resolve could not be downcast");
value.dry_resolve();
};
#[cfg(feature = "ssr")]
let resolve = |value: Box<dyn Any>| {
let value = value
.downcast::<T>()
.expect("AnyView::resolve could not be downcast");
Box::pin(
async move { value.resolve().await.into_any_attr() },
)
as Pin<Box<dyn Future<Output = AnyAttribute> + Send>>
};
AnyAttribute {
type_id: TypeId::of::<T>(),
html_len,
value,
#[cfg(feature = "ssr")]
to_html,
build,
rebuild,
#[cfg(feature = "hydrate")]
hydrate_from_server,
#[cfg(feature = "hydrate")]
hydrate_from_template,
#[cfg(feature = "ssr")]
resolve,
#[cfg(feature = "ssr")]
dry_resolve,
}
AnyAttributeState {
type_id: TypeId::of::<T::CloneableOwned>(),
state,
el: el.clone(),
}
};
#[cfg(feature = "hydrate")]
let hydrate_from_server =
|value: Box<dyn DynAttr>, el: &crate::renderer::types::Element| {
let value =
value.into_any().downcast::<T::CloneableOwned>().expect(
"AnyAttribute::hydrate_from_server couldn't downcast",
);
let state = Box::new(value.hydrate::<true>(el));
AnyAttributeState {
type_id: TypeId::of::<T::CloneableOwned>(),
state,
el: el.clone(),
}
};
#[cfg(feature = "hydrate")]
let hydrate_from_template =
|value: Box<dyn DynAttr>, el: &crate::renderer::types::Element| {
let value =
value.into_any().downcast::<T::CloneableOwned>().expect(
"AnyAttribute::hydrate_from_server couldn't downcast",
);
let state = Box::new(value.hydrate::<true>(el));
AnyAttributeState {
type_id: TypeId::of::<T::CloneableOwned>(),
state,
el: el.clone(),
}
};
let rebuild = |new_type_id: TypeId,
value: Box<dyn DynAttr>,
state: &mut AnyAttributeState| {
let value = value
.into_any()
.downcast::<T::CloneableOwned>()
.expect("AnyAttribute::rebuild couldn't downcast value");
if new_type_id == state.type_id {
let state = state
.state
.downcast_mut()
.expect("AnyAttribute::rebuild couldn't downcast state");
value.rebuild(state);
} else {
let new = value.into_any_attr().build(&state.el);
*state = new;
}
};
#[cfg(feature = "ssr")]
let dry_resolve = |value: &mut Box<dyn DynAttr>| {
let value = value
.as_any_mut()
.downcast_mut::<T::CloneableOwned>()
.expect("AnyView::resolve could not be downcast");
value.dry_resolve();
};
#[cfg(feature = "ssr")]
let resolve = |value: Box<dyn DynAttr>| {
let value = value
.into_any()
.downcast::<T::CloneableOwned>()
.expect("AnyView::resolve could not be downcast");
Box::pin(async move { value.resolve().await.into_any_attr() })
as Pin<Box<dyn Future<Output = AnyAttribute> + Send>>
};
AnyAttribute {
type_id: TypeId::of::<T::CloneableOwned>(),
html_len: value.html_len(),
value,
#[cfg(feature = "ssr")]
to_html,
build,
rebuild,
#[cfg(feature = "hydrate")]
hydrate_from_server,
#[cfg(feature = "hydrate")]
hydrate_from_template,
#[cfg(feature = "ssr")]
resolve,
#[cfg(feature = "ssr")]
dry_resolve,
}
}
}
impl NextAttribute for AnyAttribute {
type Output<NewAttr: Attribute> = (Self, NewAttr);
type Output<NewAttr: Attribute> = Vec<AnyAttribute>;
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
vec![self, new_attr.into_any_attr()]
}
}
@ -202,8 +229,8 @@ impl Attribute for AnyAttribute {
type AsyncOutput = AnyAttribute;
type State = AnyAttributeState;
type Cloneable = ();
type CloneableOwned = ();
type Cloneable = AnyAttribute;
type CloneableOwned = AnyAttribute;
fn html_len(&self) -> usize {
self.html_len
@ -257,11 +284,11 @@ impl Attribute for AnyAttribute {
}
fn into_cloneable(self) -> Self::Cloneable {
todo!()
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
todo!()
self
}
fn dry_resolve(&mut self) {
@ -288,3 +315,120 @@ impl Attribute for AnyAttribute {
);
}
}
impl NextAttribute for Vec<AnyAttribute> {
type Output<NewAttr: Attribute> = Self;
fn add_any_attr<NewAttr: Attribute>(
mut self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
self.push(new_attr.into_any_attr());
self
}
}
impl Attribute for Vec<AnyAttribute> {
const MIN_LENGTH: usize = 0;
type AsyncOutput = Vec<AnyAttribute>;
type State = Vec<AnyAttributeState>;
type Cloneable = Vec<AnyAttribute>;
type CloneableOwned = Vec<AnyAttribute>;
fn html_len(&self) -> usize {
self.iter().map(|attr| attr.html_len()).sum()
}
#[allow(unused)] // they are used in SSR
fn to_html(
self,
buf: &mut String,
class: &mut String,
style: &mut String,
inner_html: &mut String,
) {
#[cfg(feature = "ssr")]
{
for mut attr in self {
attr.to_html(buf, class, style, inner_html)
}
}
#[cfg(not(feature = "ssr"))]
panic!(
"You are rendering AnyAttribute to HTML without the `ssr` feature \
enabled."
);
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
#[cfg(feature = "hydrate")]
if FROM_SERVER {
self.into_iter()
.map(|attr| attr.hydrate::<true>(el))
.collect()
} else {
self.into_iter()
.map(|attr| attr.hydrate::<false>(el))
.collect()
}
#[cfg(not(feature = "hydrate"))]
{
_ = el;
panic!(
"You are trying to hydrate AnyAttribute without the `hydrate` \
feature enabled."
);
}
}
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
self.into_iter().map(|attr| attr.build(el)).collect()
}
fn rebuild(self, state: &mut Self::State) {
for (attr, state) in self.into_iter().zip(state.iter_mut()) {
attr.rebuild(state)
}
}
fn into_cloneable(self) -> Self::Cloneable {
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self
}
fn dry_resolve(&mut self) {
#[cfg(feature = "ssr")]
{
for attr in self.iter_mut() {
attr.dry_resolve()
}
}
#[cfg(not(feature = "ssr"))]
panic!(
"You are rendering AnyAttribute to HTML without the `ssr` feature \
enabled."
);
}
async fn resolve(self) -> Self::AsyncOutput {
#[cfg(feature = "ssr")]
{
futures::future::join_all(
self.into_iter().map(|attr| attr.resolve()),
)
.await
}
#[cfg(not(feature = "ssr"))]
panic!(
"You are rendering AnyAttribute to HTML without the `ssr` feature \
enabled."
);
}
}

View file

@ -1,6 +1,11 @@
use super::NextAttribute;
use super::{
maybe_next_attr_erasure_macros::next_attr_output_type, NextAttribute,
};
use crate::{
html::attribute::{Attribute, AttributeValue},
html::attribute::{
maybe_next_attr_erasure_macros::next_attr_combine, Attribute,
AttributeValue,
},
view::{add_attr::AddAnyAttr, Position, ToTemplate},
};
use std::{borrow::Cow, sync::Arc};
@ -114,13 +119,13 @@ where
K: CustomAttributeKey,
V: AttributeValue,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}

View file

@ -0,0 +1,27 @@
macro_rules! next_attr_output_type {
($current:ty, $next:ty) => {
#[cfg(not(erase_components))]
type Output<NewAttr: Attribute> = ($current, $next);
#[cfg(erase_components)]
type Output<NewAttr: Attribute> =
Vec<$crate::html::attribute::any_attribute::AnyAttribute>;
};
}
macro_rules! next_attr_combine {
($self:expr, $next_attr:expr) => {{
#[cfg(not(erase_components))]
{
($self, $next_attr)
}
#[cfg(erase_components)]
{
use $crate::html::attribute::any_attribute::IntoAnyAttribute;
vec![$self.into_any_attr(), $next_attr.into_any_attr()]
}
}};
}
pub(crate) use next_attr_combine;
pub(crate) use next_attr_output_type;

View file

@ -7,10 +7,15 @@ pub mod custom;
/// Traits to define global attribute methods on all HTML elements.
pub mod global;
mod key;
pub(crate) mod maybe_next_attr_erasure_macros;
pub(crate) mod panic_on_clone_attribute;
mod value;
use crate::view::{Position, ToTemplate};
pub use key::*;
use maybe_next_attr_erasure_macros::{
next_attr_combine, next_attr_output_type,
};
use std::{fmt::Debug, future::Future};
pub use value::*;
@ -73,6 +78,25 @@ pub trait Attribute: NextAttribute + Send {
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
}
/// A type that can be converted into an attribute.
///
/// Used type-erasing attrs and tuples of attrs to [`Vec<AnyAttribute>`] as early as possible to prevent type explosion.
pub trait IntoAttribute {
/// The type of the attribute.
type Output: Attribute;
/// Converts this into an attribute.
fn into_attr(self) -> Self::Output;
}
impl<T: Attribute> IntoAttribute for T {
type Output = T;
fn into_attr(self) -> Self::Output {
self
}
}
/// Adds another attribute to this one, returning a new attribute.
///
/// This is typically achieved by creating or extending a tuple of attributes.
@ -132,13 +156,27 @@ impl Attribute for () {
}
impl NextAttribute for () {
#[cfg(not(erase_components))]
type Output<NewAttr: Attribute> = (NewAttr,);
#[cfg(erase_components)]
type Output<NewAttr: Attribute> =
Vec<crate::html::attribute::any_attribute::AnyAttribute>;
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(new_attr,)
#[cfg(not(erase_components))]
{
(new_attr,)
}
#[cfg(erase_components)]
{
use crate::html::attribute::any_attribute::IntoAnyAttribute;
vec![new_attr.into_any_attr()]
}
}
}
@ -238,28 +276,28 @@ where
K: AttributeKey,
V: AttributeValue,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}
macro_rules! impl_attr_for_tuples {
($first:ident, $($ty:ident),* $(,)?) => {
impl<$first, $($ty),*> Attribute for ($first, $($ty,)*)
where
$first: Attribute,
$($ty: Attribute),*,
{
($first:ident, $($ty:ident),* $(,)?) => {
#[cfg(not(erase_components))]
impl<$first, $($ty),*> Attribute for ($first, $($ty,)*)
where
$first: Attribute,
$($ty: Attribute),*,
{
const MIN_LENGTH: usize = $first::MIN_LENGTH $(+ $ty::MIN_LENGTH)*;
type AsyncOutput = ($first::AsyncOutput, $($ty::AsyncOutput,)*);
type State = ($first::State, $($ty::State,)*);
type AsyncOutput = ($first::AsyncOutput, $($ty::AsyncOutput,)*);
type State = ($first::State, $($ty::State,)*);
type Cloneable = ($first::Cloneable, $($ty::Cloneable,)*);
type CloneableOwned = ($first::CloneableOwned, $($ty::CloneableOwned,)*);
@ -269,39 +307,39 @@ macro_rules! impl_attr_for_tuples {
$first.html_len() $(+ $ty.html_len())*
}
fn to_html(self, buf: &mut String, class: &mut String, style: &mut String, inner_html: &mut String,) {
fn to_html(self, buf: &mut String, class: &mut String, style: &mut String, inner_html: &mut String,) {
#[allow(non_snake_case)]
let ($first, $($ty,)* ) = self;
$first.to_html(buf, class, style, inner_html);
$($ty.to_html(buf, class, style, inner_html));*
}
let ($first, $($ty,)* ) = self;
$first.to_html(buf, class, style, inner_html);
$($ty.to_html(buf, class, style, inner_html));*
}
fn hydrate<const FROM_SERVER: bool>(self, el: &crate::renderer::types::Element) -> Self::State {
fn hydrate<const FROM_SERVER: bool>(self, el: &crate::renderer::types::Element) -> Self::State {
#[allow(non_snake_case)]
let ($first, $($ty,)* ) = self;
(
$first.hydrate::<FROM_SERVER>(el),
$($ty.hydrate::<FROM_SERVER>(el)),*
)
}
let ($first, $($ty,)* ) = self;
(
$first.hydrate::<FROM_SERVER>(el),
$($ty.hydrate::<FROM_SERVER>(el)),*
)
}
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
#[allow(non_snake_case)]
let ($first, $($ty,)*) = self;
let ($first, $($ty,)*) = self;
(
$first.build(el),
$($ty.build(el)),*
)
}
}
fn rebuild(self, state: &mut Self::State) {
fn rebuild(self, state: &mut Self::State) {
paste::paste! {
let ([<$first:lower>], $([<$ty:lower>],)*) = self;
let ([<view_ $first:lower>], $([<view_ $ty:lower>],)*) = state;
[<$first:lower>].rebuild([<view_ $first:lower>]);
$([<$ty:lower>].rebuild([<view_ $ty:lower>]));*
}
}
let ([<$first:lower>], $([<$ty:lower>],)*) = self;
let ([<view_ $first:lower>], $([<view_ $ty:lower>],)*) = state;
[<$first:lower>].rebuild([<view_ $first:lower>]);
$([<$ty:lower>].rebuild([<view_ $ty:lower>]));*
}
}
fn into_cloneable(self) -> Self::Cloneable {
#[allow(non_snake_case)]
@ -338,10 +376,11 @@ macro_rules! impl_attr_for_tuples {
}
}
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
where
$first: Attribute,
$($ty: Attribute),*,
#[cfg(not(erase_components))]
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
where
$first: Attribute,
$($ty: Attribute),*,
{
type Output<NewAttr: Attribute> = ($first, $($ty,)* NewAttr);
@ -354,22 +393,44 @@ macro_rules! impl_attr_for_tuples {
let ($first, $($ty,)*) = self;
($first, $($ty,)* new_attr)
}
}
};
}
#[cfg(erase_components)]
impl<$first, $($ty),*> IntoAttribute for ($first, $($ty,)*)
where
$first: IntoAttribute,
$($ty: IntoAttribute),*,
{
type Output = Vec<$crate::html::attribute::any_attribute::AnyAttribute>;
fn into_attr(self) -> Self::Output {
use crate::html::attribute::any_attribute::IntoAnyAttribute;
#[allow(non_snake_case)]
let ($first, $($ty,)*) = self;
vec![
$first.into_attr().into_any_attr(),
$($ty.into_attr().into_any_attr(),)*
]
}
}
};
}
macro_rules! impl_attr_for_tuples_truncate_additional {
($first:ident, $($ty:ident),* $(,)?) => {
impl<$first, $($ty),*> Attribute for ($first, $($ty,)*)
where
$first: Attribute,
$($ty: Attribute),*,
($first:ident, $($ty:ident),* $(,)?) => {
#[cfg(not(erase_components))]
impl<$first, $($ty),*> Attribute for ($first, $($ty,)*)
where
$first: Attribute,
$($ty: Attribute),*,
{
{
const MIN_LENGTH: usize = $first::MIN_LENGTH $(+ $ty::MIN_LENGTH)*;
type AsyncOutput = ($first::AsyncOutput, $($ty::AsyncOutput,)*);
type State = ($first::State, $($ty::State,)*);
type AsyncOutput = ($first::AsyncOutput, $($ty::AsyncOutput,)*);
type State = ($first::State, $($ty::State,)*);
type Cloneable = ($first::Cloneable, $($ty::Cloneable,)*);
type CloneableOwned = ($first::CloneableOwned, $($ty::CloneableOwned,)*);
@ -379,21 +440,21 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
$first.html_len() $(+ $ty.html_len())*
}
fn to_html(self, buf: &mut String, class: &mut String, style: &mut String, inner_html: &mut String,) {
fn to_html(self, buf: &mut String, class: &mut String, style: &mut String, inner_html: &mut String,) {
#[allow(non_snake_case)]
let ($first, $($ty,)* ) = self;
$first.to_html(buf, class, style, inner_html);
$($ty.to_html(buf, class, style, inner_html));*
}
}
fn hydrate<const FROM_SERVER: bool>(self, el: &crate::renderer::types::Element) -> Self::State {
fn hydrate<const FROM_SERVER: bool>(self, el: &crate::renderer::types::Element) -> Self::State {
#[allow(non_snake_case)]
let ($first, $($ty,)* ) = self;
(
$first.hydrate::<FROM_SERVER>(el),
$($ty.hydrate::<FROM_SERVER>(el)),*
)
}
}
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
#[allow(non_snake_case)]
@ -402,16 +463,16 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
$first.build(el),
$($ty.build(el)),*
)
}
}
fn rebuild(self, state: &mut Self::State) {
paste::paste! {
let ([<$first:lower>], $([<$ty:lower>],)*) = self;
let ([<view_ $first:lower>], $([<view_ $ty:lower>],)*) = state;
[<$first:lower>].rebuild([<view_ $first:lower>]);
$([<$ty:lower>].rebuild([<view_ $ty:lower>]));*
}
}
fn rebuild(self, state: &mut Self::State) {
paste::paste! {
let ([<$first:lower>], $([<$ty:lower>],)*) = self;
let ([<view_ $first:lower>], $([<view_ $ty:lower>],)*) = state;
[<$first:lower>].rebuild([<view_ $first:lower>]);
$([<$ty:lower>].rebuild([<view_ $ty:lower>]));*
}
}
fn into_cloneable(self) -> Self::Cloneable {
#[allow(non_snake_case)]
@ -448,10 +509,11 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
}
}
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
where
$first: Attribute,
$($ty: Attribute),*,
#[cfg(not(erase_components))]
impl<$first, $($ty),*> NextAttribute for ($first, $($ty,)*)
where
$first: Attribute,
$($ty: Attribute),*,
{
type Output<NewAttr: Attribute> = ($first, $($ty,)*);
@ -463,10 +525,39 @@ macro_rules! impl_attr_for_tuples_truncate_additional {
todo!("adding more than 26 attributes is not supported");
//($first, $($ty,)*)
}
}
};
}
#[cfg(erase_components)]
impl<$first, $($ty),*> IntoAttribute for ($first, $($ty,)*)
where
$first: IntoAttribute,
$($ty: IntoAttribute),*,
{
type Output = $crate::html::attribute::any_attribute::AnyAttribute;
fn into_attr(self) -> Self::Output {
todo!("adding more than 26 attributes is not supported");
//crate::html::attribute::any_attribute::IntoAnyAttribute::into_any_attr(self)
}
}
};
}
#[cfg(erase_components)]
impl<A> IntoAttribute for (A,)
where
A: IntoAttribute,
{
type Output = Vec<crate::html::attribute::any_attribute::AnyAttribute>;
fn into_attr(self) -> Self::Output {
use crate::html::attribute::any_attribute::IntoAnyAttribute;
vec![self.0.into_attr().into_any_attr()]
}
}
#[cfg(not(erase_components))]
impl<A> Attribute for (A,)
where
A: Attribute,
@ -524,17 +615,18 @@ where
}
}
#[cfg(not(erase_components))]
impl<A> NextAttribute for (A,)
where
A: Attribute,
{
type Output<NewAttr: Attribute> = (A, NewAttr);
next_attr_output_type!(A, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self.0, new_attr)
next_attr_combine!(self.0, new_attr)
}
}

View file

@ -0,0 +1,88 @@
use super::{Attribute, NextAttribute};
/// When type erasing with `AnyAttribute`, the underling attribute must be cloneable.
///
/// For most this is possible, but for some like `NodeRef` it is not.
///
/// This allows for a panic to be thrown if a non-cloneable attribute is cloned, whilst still seeming like it can be cloned.
pub struct PanicOnCloneAttr<T: Attribute + 'static> {
msg: &'static str,
attr: T,
}
impl<T: Attribute + 'static> PanicOnCloneAttr<T> {
pub(crate) fn new(attr: T, msg: &'static str) -> Self {
Self { msg, attr }
}
}
impl<T: Attribute + 'static> Clone for PanicOnCloneAttr<T> {
fn clone(&self) -> Self {
panic!("{}", self.msg)
}
}
impl<T: Attribute + 'static> NextAttribute for PanicOnCloneAttr<T> {
type Output<NewAttr: Attribute> = <T as NextAttribute>::Output<NewAttr>;
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
self.attr.add_any_attr(new_attr)
}
}
impl<T: Attribute + 'static> Attribute for PanicOnCloneAttr<T> {
const MIN_LENGTH: usize = T::MIN_LENGTH;
type State = T::State;
type AsyncOutput = T::AsyncOutput;
type Cloneable = Self;
type CloneableOwned = Self;
fn html_len(&self) -> usize {
self.attr.html_len()
}
fn to_html(
self,
buf: &mut String,
class: &mut String,
style: &mut String,
inner_html: &mut String,
) {
self.attr.to_html(buf, class, style, inner_html)
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &crate::renderer::types::Element,
) -> Self::State {
self.attr.hydrate::<FROM_SERVER>(el)
}
fn build(self, el: &crate::renderer::types::Element) -> Self::State {
self.attr.build(el)
}
fn rebuild(self, state: &mut Self::State) {
self.attr.rebuild(state)
}
fn into_cloneable(self) -> Self::Cloneable {
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self
}
fn dry_resolve(&mut self) {
self.attr.dry_resolve()
}
async fn resolve(self) -> Self::AsyncOutput {
self.attr.resolve().await
}
}

View file

@ -1,5 +1,9 @@
use super::attribute::{Attribute, NextAttribute};
use super::attribute::{
maybe_next_attr_erasure_macros::next_attr_output_type, Attribute,
NextAttribute,
};
use crate::{
html::attribute::maybe_next_attr_erasure_macros::next_attr_combine,
renderer::Rndr,
view::{Position, ToTemplate},
};
@ -99,13 +103,13 @@ impl<C> NextAttribute for Class<C>
where
C: IntoClass,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}

View file

@ -1,5 +1,9 @@
use super::attribute::{Attribute, NextAttribute};
use super::attribute::{
maybe_next_attr_erasure_macros::next_attr_output_type, Attribute,
NextAttribute,
};
use crate::{
html::attribute::maybe_next_attr_erasure_macros::next_attr_combine,
prelude::AddAnyAttr,
view::{Position, ToTemplate},
};
@ -164,13 +168,13 @@ where
P: Clone + 'static,
T: 'static,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}

View file

@ -21,7 +21,7 @@ where
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Custom<E>(E);
impl<E> ElementType for Custom<E>
impl<E: 'static> ElementType for Custom<E>
where
E: AsRef<str> + Send,
{

View file

@ -1,11 +1,10 @@
use crate::{
html::{
attribute::{Attr, Attribute, AttributeValue},
attribute::{Attr, Attribute, AttributeValue, NextAttribute},
element::{ElementType, ElementWithChildren, HtmlElement},
},
view::Render,
};
use next_tuple::NextTuple;
use std::fmt::Debug;
macro_rules! html_element_inner {
@ -48,13 +47,13 @@ macro_rules! html_element_inner {
#[doc = concat!("The [`", stringify!($attr), "`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/", stringify!($tag), "#", stringify!($attr) ,") attribute on `<", stringify!($tag), ">`.")]
pub fn $attr<V>(self, value: V) -> HtmlElement <
$struct_name,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
<At as NextAttribute>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
Ch
>
where
V: AttributeValue,
At: NextTuple,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
At: NextAttribute,
<At as NextAttribute>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
{
let HtmlElement {
#[cfg(any(debug_assertions, leptos_debuginfo))]
@ -68,7 +67,7 @@ macro_rules! html_element_inner {
defined_at,
tag,
children,
attributes: attributes.next_tuple($crate::html::attribute::$attr(value)),
attributes: attributes.add_any_attr($crate::html::attribute::$attr(value)),
}
}
)*
@ -153,13 +152,13 @@ macro_rules! html_self_closing_elements {
#[doc = concat!("The [`", stringify!($attr), "`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/", stringify!($tag), "#", stringify!($attr) ,") attribute on `<", stringify!($tag), ">`.")]
pub fn $attr<V>(self, value: V) -> HtmlElement<
[<$tag:camel>],
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
<At as NextAttribute>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
(),
>
where
V: AttributeValue,
At: NextTuple,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
At: NextAttribute,
<At as NextAttribute>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
{
let HtmlElement {
#[cfg(any(debug_assertions, leptos_debuginfo))]
@ -173,7 +172,7 @@ macro_rules! html_self_closing_elements {
defined_at,
tag,
children,
attributes: attributes.next_tuple($crate::html::attribute::$attr(value)),
attributes: attributes.add_any_attr($crate::html::attribute::$attr(value)),
}
}
)*

View file

@ -1,6 +1,11 @@
use super::{ElementWithChildren, HtmlElement};
use crate::{
html::attribute::{Attribute, NextAttribute},
html::attribute::{
maybe_next_attr_erasure_macros::{
next_attr_combine, next_attr_output_type,
},
Attribute, NextAttribute,
},
renderer::Rndr,
view::add_attr::AddAnyAttr,
};
@ -106,13 +111,13 @@ impl<T> NextAttribute for InnerHtml<T>
where
T: InnerHtmlValue,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}

View file

@ -141,7 +141,7 @@ where
}
/// An HTML element.
pub trait ElementType: Send {
pub trait ElementType: Send + 'static {
/// The underlying native widget type that this represents.
type Output;

View file

@ -1,5 +1,7 @@
use crate::{
html::attribute::Attribute,
html::attribute::{
maybe_next_attr_erasure_macros::next_attr_combine, Attribute,
},
renderer::{CastFrom, RemoveEventHandler, Rndr},
view::{Position, ToTemplate},
};
@ -302,13 +304,13 @@ where
E::EventType: From<crate::renderer::types::Event>,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}
@ -671,7 +673,12 @@ generate_event_types! {
}
// Export `web_sys` event types
use super::{attribute::NextAttribute, element::HasElementType};
use super::{
attribute::{
maybe_next_attr_erasure_macros::next_attr_output_type, NextAttribute,
},
element::HasElementType,
};
#[doc(no_inline)]
pub use web_sys::{
AnimationEvent, BeforeUnloadEvent, CompositionEvent, CustomEvent,

View file

@ -1,14 +1,22 @@
use super::{
attribute::{Attribute, NextAttribute},
attribute::{
maybe_next_attr_erasure_macros::next_attr_output_type,
panic_on_clone_attribute::PanicOnCloneAttr, Attribute, NextAttribute,
},
element::ElementType,
};
use crate::{
html::element::HtmlElement, prelude::Render, view::add_attr::AddAnyAttr,
html::{
attribute::maybe_next_attr_erasure_macros::next_attr_combine,
element::HtmlElement,
},
prelude::Render,
view::add_attr::AddAnyAttr,
};
use std::marker::PhantomData;
/// Describes a container that can be used to hold a reference to an HTML element.
pub trait NodeRefContainer<E>: Send + Clone
pub trait NodeRefContainer<E>: Send + Clone + 'static
where
E: ElementType,
{
@ -57,8 +65,8 @@ where
const MIN_LENGTH: usize = 0;
type AsyncOutput = Self;
type State = crate::renderer::types::Element;
type Cloneable = ();
type CloneableOwned = ();
type Cloneable = PanicOnCloneAttr<Self>;
type CloneableOwned = PanicOnCloneAttr<Self>;
#[inline(always)]
fn html_len(&self) -> usize {
@ -92,11 +100,17 @@ where
}
fn into_cloneable(self) -> Self::Cloneable {
panic!("node_ref should not be spread across multiple elements.");
PanicOnCloneAttr::new(
self,
"node_ref should not be spread across multiple elements.",
)
}
fn into_cloneable_owned(self) -> Self::Cloneable {
panic!("node_ref should not be spread across multiple elements.");
PanicOnCloneAttr::new(
self,
"node_ref should not be spread across multiple elements.",
)
}
fn dry_resolve(&mut self) {}
@ -113,13 +127,13 @@ where
crate::renderer::types::Element: PartialEq,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}

View file

@ -1,5 +1,9 @@
use super::attribute::{Attribute, NextAttribute};
use super::attribute::{
maybe_next_attr_erasure_macros::next_attr_output_type, Attribute,
NextAttribute,
};
use crate::{
html::attribute::maybe_next_attr_erasure_macros::next_attr_combine,
renderer::Rndr,
view::{Position, ToTemplate},
};
@ -127,13 +131,13 @@ where
K: AsRef<str> + Send,
P: IntoProperty,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}

View file

@ -1,7 +1,11 @@
use super::attribute::{Attribute, NextAttribute};
use super::attribute::{
maybe_next_attr_erasure_macros::next_attr_output_type, Attribute,
NextAttribute,
};
#[cfg(feature = "nightly")]
use crate::view::static_types::Static;
use crate::{
html::attribute::maybe_next_attr_erasure_macros::next_attr_combine,
renderer::Rndr,
view::{Position, ToTemplate},
};
@ -102,13 +106,13 @@ impl<S> NextAttribute for Style<S>
where
S: IntoStyle,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}

View file

@ -21,7 +21,7 @@ pub mod prelude {
OnAttribute, OnTargetAttribute, PropAttribute,
StyleAttribute,
},
IntoAttributeValue,
IntoAttribute, IntoAttributeValue,
},
directive::DirectiveAttribute,
element::{ElementChild, ElementExt, InnerHtmlAttribute},

View file

@ -1,11 +1,10 @@
use crate::{
html::{
attribute::{Attr, Attribute, AttributeValue},
attribute::{Attr, Attribute, AttributeValue, NextAttribute},
element::{ElementType, ElementWithChildren, HtmlElement},
},
view::Render,
};
use next_tuple::NextTuple;
use std::fmt::Debug;
macro_rules! mathml_global {
@ -14,13 +13,13 @@ macro_rules! mathml_global {
/// A MathML attribute.
pub fn $attr<V>(self, value: V) -> HtmlElement <
[<$tag:camel>],
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
<At as NextAttribute>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
Ch
>
where
V: AttributeValue,
At: NextTuple,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
At: NextAttribute,
<At as NextAttribute>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
{
let HtmlElement {
#[cfg(any(debug_assertions, leptos_debuginfo))]
@ -34,7 +33,7 @@ macro_rules! mathml_global {
defined_at,
tag,
children,
attributes: attributes.next_tuple($crate::html::attribute::$attr(value)),
attributes: attributes.add_any_attr($crate::html::attribute::$attr(value)),
}
}
}
@ -84,13 +83,13 @@ macro_rules! mathml_elements {
/// A MathML attribute.
pub fn $attr<V>(self, value: V) -> HtmlElement <
[<$tag:camel>],
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
<At as NextAttribute>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
Ch
>
where
V: AttributeValue,
At: NextTuple,
<At as NextTuple>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
At: NextAttribute,
<At as NextAttribute>::Output<Attr<$crate::html::attribute::[<$attr:camel>], V>>: Attribute,
{
let HtmlElement {
#[cfg(any(debug_assertions, leptos_debuginfo))]
@ -104,7 +103,7 @@ macro_rules! mathml_elements {
defined_at,
tag,
children,
attributes: attributes.next_tuple($crate::html::attribute::$attr(value)),
attributes: attributes.add_any_attr($crate::html::attribute::$attr(value)),
}
}
)*

View file

@ -1,7 +1,12 @@
use crate::{
dom::{event_target_checked, event_target_value},
html::{
attribute::{Attribute, AttributeKey, AttributeValue, NextAttribute},
attribute::{
maybe_next_attr_erasure_macros::{
next_attr_combine, next_attr_output_type,
},
Attribute, AttributeKey, AttributeValue, NextAttribute,
},
event::{change, input, on},
property::{prop, IntoProperty},
},
@ -276,13 +281,13 @@ where
W: Update<Value = T> + Clone + Send + 'static,
Element: ChangeEvent + GetValue<T>,
{
type Output<NewAttr: Attribute> = (Self, NewAttr);
next_attr_output_type!(Self, NewAttr);
fn add_any_attr<NewAttr: Attribute>(
self,
new_attr: NewAttr,
) -> Self::Output<NewAttr> {
(self, new_attr)
next_attr_combine!(self, new_attr)
}
}

View file

@ -40,13 +40,13 @@ macro_rules! svg_elements {
$(
pub fn $attr<V>(self, value: V) -> HtmlElement <
[<$tag:camel>],
<At as NextTuple<Attr<$crate::html::attribute::[<$attr:camel>], V>>>::Output,
<At as $crate::html::attribute::NextAttribute<Attr<$crate::html::attribute::[<$attr:camel>], V>>>::Output,
Ch
>
where
V: AttributeValue,
At: NextTuple<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
<At as NextTuple<Attr<$crate::html::attribute::[<$attr:camel>], V>>>::Output: Attribute,
At: $crate::html::attribute::NextAttribute<Attr<$crate::html::attribute::[<$attr:camel>], V>>,
<At as $crate::html::attribute::NextAttribute<Attr<$crate::html::attribute::[<$attr:camel>], V>>>::Output: Attribute,
{
let HtmlElement { tag, children, attributes,
#[cfg(any(debug_assertions, leptos_debuginfo))]
@ -56,7 +56,7 @@ macro_rules! svg_elements {
tag,
children,
attributes: attributes.next_tuple($crate::html::attribute::$attr(value)),
attributes: attributes.add_any_attr($crate::html::attribute::$attr(value)),
#[cfg(any(debug_assertions, leptos_debuginfo))]
defined_at
}

View file

@ -29,6 +29,12 @@ pub struct AnyView {
value: Box<dyn Any + Send>,
build: fn(Box<dyn Any>) -> AnyViewState,
rebuild: fn(TypeId, Box<dyn Any>, &mut AnyViewState),
// Without erasure, tuples of attrs created by default cause too much type explosion to enable.
#[cfg(erase_components)]
add_any_attr: fn(
Box<dyn Any>,
crate::html::attribute::any_attribute::AnyAttribute,
) -> AnyView,
// The fields below are cfg-gated so they will not be included in WASM bundles if not needed.
// Ordinarily, the compiler can simply omit this dead code because the methods are not called.
// With this type-erased wrapper, however, the compiler is not *always* able to correctly
@ -128,9 +134,6 @@ where
T: RenderHtml + 'static,
T::State: 'static,
{
// inlining allows the compiler to remove the unused functions
// i.e., doesn't ship HTML-generating code that isn't used
#[inline(always)]
fn into_any(self) -> AnyView {
#[cfg(feature = "ssr")]
let html_len = self.html_len();
@ -282,11 +285,23 @@ where
}
};
// Without erasure, tuples of attrs created by default cause too much type explosion to enable.
#[cfg(erase_components)]
let add_any_attr = |value: Box<dyn Any>, attr: crate::html::attribute::any_attribute::AnyAttribute| {
let value = value
.downcast::<T>()
.expect("AnyView::add_any_attr could not be downcast");
value.add_any_attr(attr).into_any()
};
AnyView {
type_id: TypeId::of::<T>(),
value,
build,
rebuild,
// Without erasure, tuples of attrs created by default cause too much type explosion to enable.
#[cfg(erase_components)]
add_any_attr,
#[cfg(feature = "ssr")]
resolve,
#[cfg(feature = "ssr")]
@ -322,14 +337,26 @@ impl Render for AnyView {
impl AddAnyAttr for AnyView {
type Output<SomeNewAttr: Attribute> = Self;
#[allow(unused_variables)]
fn add_any_attr<NewAttr: Attribute>(
self,
_attr: NewAttr,
attr: NewAttr,
) -> Self::Output<NewAttr>
where
Self::Output<NewAttr>: RenderHtml,
{
self
// Without erasure, tuples of attrs created by default cause too much type explosion to enable.
#[cfg(erase_components)]
{
use crate::html::attribute::any_attribute::IntoAnyAttribute;
let attr = attr.into_cloneable_owned();
(self.add_any_attr)(self.value, attr.into_any_attr())
}
#[cfg(not(erase_components))]
{
self
}
}
}