feat: attr: and #[prop(attrs)] syntax for passing attributes down to components (#1628)

This commit is contained in:
Village 2023-09-10 15:19:53 -04:00 committed by GitHub
parent 8c3e0f23b0
commit 1c2327b2d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 3 deletions

View file

@ -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;

View file

@ -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,

View file

@ -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
) )
}; };

View file

@ -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 {

View file

@ -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 {