mirror of
https://github.com/leptos-rs/leptos
synced 2024-11-10 14:54:16 +00:00
feat: attr:
and #[prop(attrs)]
syntax for passing attributes down to components (#1628)
This commit is contained in:
parent
8c3e0f23b0
commit
1c2327b2d6
5 changed files with 77 additions and 3 deletions
|
@ -265,6 +265,18 @@ pub trait Props {
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait DynAttrs {
|
||||||
|
fn dyn_attrs(self, _args: Vec<(&'static str, Attribute)>) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynAttrs for () {}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait PropsOrNoPropsBuilder {
|
pub trait PropsOrNoPropsBuilder {
|
||||||
type Builder;
|
type Builder;
|
||||||
|
|
|
@ -332,6 +332,39 @@ impl ToTokens for Model {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let count = props
|
||||||
|
.iter()
|
||||||
|
.filter(
|
||||||
|
|Prop {
|
||||||
|
prop_opts: PropOpt { attrs, .. },
|
||||||
|
..
|
||||||
|
}| *attrs,
|
||||||
|
)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let dyn_attrs_props = props
|
||||||
|
.iter()
|
||||||
|
.filter(
|
||||||
|
|Prop {
|
||||||
|
prop_opts: PropOpt { attrs, .. },
|
||||||
|
..
|
||||||
|
}| *attrs,
|
||||||
|
)
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, Prop { name, .. })| {
|
||||||
|
let ident = &name.ident;
|
||||||
|
if idx < count - 1 {
|
||||||
|
quote! {
|
||||||
|
self.#ident = v.clone().into();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
self.#ident = v.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<TokenStream>();
|
||||||
|
|
||||||
let body = quote! {
|
let body = quote! {
|
||||||
#body
|
#body
|
||||||
#destructure_props
|
#destructure_props
|
||||||
|
@ -461,6 +494,13 @@ impl ToTokens for Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl #impl_generics ::leptos::DynAttrs for #props_name #generics #where_clause {
|
||||||
|
fn dyn_attrs(mut self, v: Vec<(&'static str, ::leptos::Attribute)>) -> Self {
|
||||||
|
#dyn_attrs_props
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#into_view
|
#into_view
|
||||||
|
|
||||||
#docs
|
#docs
|
||||||
|
@ -699,6 +739,7 @@ struct PropOpt {
|
||||||
#[attribute(example = "5 * 10")]
|
#[attribute(example = "5 * 10")]
|
||||||
default: Option<syn::Expr>,
|
default: Option<syn::Expr>,
|
||||||
into: bool,
|
into: bool,
|
||||||
|
attrs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TypedBuilderOpts {
|
struct TypedBuilderOpts {
|
||||||
|
@ -711,7 +752,7 @@ struct TypedBuilderOpts {
|
||||||
impl TypedBuilderOpts {
|
impl TypedBuilderOpts {
|
||||||
fn from_opts(opts: &PropOpt, is_ty_option: bool) -> Self {
|
fn from_opts(opts: &PropOpt, is_ty_option: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
default: opts.optional || opts.optional_no_strip,
|
default: opts.optional || opts.optional_no_strip || opts.attrs,
|
||||||
default_with_value: opts.default.clone(),
|
default_with_value: opts.default.clone(),
|
||||||
strip_option: opts.strip_option || opts.optional && is_ty_option,
|
strip_option: opts.strip_option || opts.optional && is_ty_option,
|
||||||
into: opts.into,
|
into: opts.into,
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub(crate) fn component_to_tokens(
|
||||||
!attr.key.to_string().starts_with("bind:")
|
!attr.key.to_string().starts_with("bind:")
|
||||||
&& !attr.key.to_string().starts_with("clone:")
|
&& !attr.key.to_string().starts_with("clone:")
|
||||||
&& !attr.key.to_string().starts_with("on:")
|
&& !attr.key.to_string().starts_with("on:")
|
||||||
|
&& !attr.key.to_string().starts_with("attr:")
|
||||||
})
|
})
|
||||||
.map(|attr| {
|
.map(|attr| {
|
||||||
let name = &attr.key;
|
let name = &attr.key;
|
||||||
|
@ -70,6 +71,7 @@ pub(crate) fn component_to_tokens(
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let events = attrs
|
let events = attrs
|
||||||
|
.clone()
|
||||||
.filter(|attr| attr.key.to_string().starts_with("on:"))
|
.filter(|attr| attr.key.to_string().starts_with("on:"))
|
||||||
.map(|attr| {
|
.map(|attr| {
|
||||||
let (event_type, handler) = event_from_attribute_node(attr, true);
|
let (event_type, handler) = event_from_attribute_node(attr, true);
|
||||||
|
@ -80,6 +82,24 @@ pub(crate) fn component_to_tokens(
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let dyn_attrs = attrs
|
||||||
|
.filter(|attr| attr.key.to_string().starts_with("attr:"))
|
||||||
|
.filter_map(|attr| {
|
||||||
|
let name = &attr.key.to_string();
|
||||||
|
let name = name.strip_prefix("attr:");
|
||||||
|
let value = attr.value().map(|v| {
|
||||||
|
quote! { #v }
|
||||||
|
})?;
|
||||||
|
Some(quote! { (#name, #value.into_attribute()) })
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let dyn_attrs = if dyn_attrs.is_empty() {
|
||||||
|
quote! {}
|
||||||
|
} else {
|
||||||
|
quote! { .dyn_attrs(vec![#(#dyn_attrs),*]) }
|
||||||
|
};
|
||||||
|
|
||||||
let mut slots = HashMap::new();
|
let mut slots = HashMap::new();
|
||||||
let children = if node.children.is_empty() {
|
let children = if node.children.is_empty() {
|
||||||
quote! {}
|
quote! {}
|
||||||
|
@ -163,6 +183,7 @@ pub(crate) fn component_to_tokens(
|
||||||
#(#slots)*
|
#(#slots)*
|
||||||
#children
|
#children
|
||||||
.build()
|
.build()
|
||||||
|
#dyn_attrs
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ error: return type is incorrect
|
||||||
|
|
|
|
||||||
= help: return signature must be `-> impl IntoView`
|
= help: return signature must be `-> impl IntoView`
|
||||||
|
|
||||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default` and `into`
|
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs`
|
||||||
--> tests/ui/component.rs:10:31
|
--> tests/ui/component.rs:10:31
|
||||||
|
|
|
|
||||||
10 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl IntoView {
|
10 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl IntoView {
|
||||||
|
|
|
@ -6,7 +6,7 @@ error: return type is incorrect
|
||||||
|
|
|
|
||||||
= help: return signature must be `-> impl IntoView`
|
= help: return signature must be `-> impl IntoView`
|
||||||
|
|
||||||
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default` and `into`
|
error: supported fields are `optional`, `optional_no_strip`, `strip_option`, `default`, `into` and `attrs`
|
||||||
--> tests/ui/component_absolute.rs:5:31
|
--> tests/ui/component_absolute.rs:5:31
|
||||||
|
|
|
|
||||||
5 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl ::leptos::IntoView {
|
5 | fn unknown_prop_option(#[prop(hello)] test: bool) -> impl ::leptos::IntoView {
|
||||||
|
|
Loading…
Reference in a new issue