mirror of
https://github.com/leptos-rs/leptos
synced 2025-02-03 07:23:26 +00:00
AddAnyAttr working with erase_components (#3518)
* AddAnyAttr working with erase_components * CI fixes
This commit is contained in:
parent
b62ae56094
commit
72f960a026
23 changed files with 717 additions and 275 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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)',
|
||||
] }
|
||||
|
||||
|
|
|
@ -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."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
27
tachys/src/html/attribute/maybe_next_attr_erasure_macros.rs
Normal file
27
tachys/src/html/attribute/maybe_next_attr_erasure_macros.rs
Normal 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;
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
88
tachys/src/html/attribute/panic_on_clone_attribute.rs
Normal file
88
tachys/src/html/attribute/panic_on_clone_attribute.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ pub mod prelude {
|
|||
OnAttribute, OnTargetAttribute, PropAttribute,
|
||||
StyleAttribute,
|
||||
},
|
||||
IntoAttributeValue,
|
||||
IntoAttribute, IntoAttributeValue,
|
||||
},
|
||||
directive::DirectiveAttribute,
|
||||
element::{ElementChild, ElementExt, InnerHtmlAttribute},
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue