Merge pull request #232 from gbj/custom-events-in-macro
`leptos_macro` improvements to `class:`, `prop:`, `on:`, `:undelegate…
This commit is contained in:
commit
3c080e0564
|
@ -85,11 +85,9 @@ macro_rules! generate_event_types {
|
|||
),* $(,)?} => {
|
||||
|
||||
$(
|
||||
#[doc = "The "]
|
||||
#[doc = stringify!($event)]
|
||||
#[doc = " event."]
|
||||
#[doc = concat!("The `", stringify!($event), "` event, which receives [", stringify!($web_sys_event), "](web_sys::", stringify!($web_sys_event), ") as its argument.")]
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct $event;
|
||||
|
||||
impl EventDescriptor for $event {
|
||||
|
|
|
@ -12,6 +12,7 @@ proc-macro = true
|
|||
|
||||
[dependencies]
|
||||
cfg-if = "1"
|
||||
doc-comment = "0.3"
|
||||
itertools = "0.10"
|
||||
pad-adapter = "0.1"
|
||||
prettyplease = "0.1"
|
||||
|
|
|
@ -252,7 +252,7 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
/// When you use the component somewhere else, the names of its arguments are the names
|
||||
/// of the properties you use in the [view](mod@view) macro.
|
||||
///
|
||||
/// Every component function should have the return type `-> impl [IntoView](leptos_dom::IntoView)`.
|
||||
/// Every component function should have the return type `-> impl IntoView`.
|
||||
///
|
||||
/// You can add Rust doc comments to component function arguments and the macro will use them to
|
||||
/// generate documentation for the component.
|
||||
|
@ -386,6 +386,51 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Customizing Properties
|
||||
/// You can use the `#[prop]` attribute on individual component properties (function arguments) to
|
||||
/// customize the types that component property can receive. You can use the following attributes:
|
||||
/// * `#[prop(into)]`: This will call `.into()` on any value passed into the component prop. (For example,
|
||||
/// you could apply `#[prop(into)]` to a prop that takes [Signal](leptos_reactive::Signal), which would
|
||||
/// allow users to pass a [ReadSignal](leptos_reactive::ReadSignal) or [RwSignal](leptos_reactive::RwSignal)
|
||||
/// and automatically convert it.)
|
||||
/// * `#[prop(optional)]`: If the user does not specify this property when they use the component,
|
||||
/// it will be set to its default value. If the property type is `Option<T>`, values should be passed
|
||||
/// as `name=T` and will be received as `Some(T)`.
|
||||
/// * `#[prop(optional_no_strip)]`: The same as `optional`, but requires values to be passed as `None` or
|
||||
/// `Some(T)` explicitly. This means that the optional property can be omitted (and be `None`), or explicitly
|
||||
/// specified as either `None` or `Some(T)`.
|
||||
/// ```rust
|
||||
/// # use leptos::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn MyComponent(
|
||||
/// cx: Scope,
|
||||
/// #[prop(into)]
|
||||
/// name: String,
|
||||
/// #[prop(optional)]
|
||||
/// optional_value: Option<i32>,
|
||||
/// #[prop(optional_no_strip)]
|
||||
/// optional_no_strip: Option<i32>
|
||||
/// ) -> impl IntoView {
|
||||
/// // whatever UI you need
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn App(cx: Scope) -> impl IntoView {
|
||||
/// view! { cx,
|
||||
/// <MyComponent
|
||||
/// name="Greg" // automatically converted to String with `.into()`
|
||||
/// optional_value=42 // received as `Some(42)`
|
||||
/// optional_no_strip=Some(42) // received as `Some(42)`
|
||||
/// />
|
||||
/// <MyComponent
|
||||
/// name="Bob" // automatically converted to String with `.into()`
|
||||
/// // optional values can both be omitted, and received as `None`
|
||||
/// />
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_error::proc_macro_error]
|
||||
#[proc_macro_attribute]
|
||||
pub fn component(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
||||
|
|
|
@ -333,6 +333,7 @@ fn attribute_to_tokens_ssr(
|
|||
.expect("event listener attributes need a value")
|
||||
.as_ref();
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let (name, is_force_undelegated) = parse_event(name);
|
||||
|
||||
let event_type = TYPED_EVENTS
|
||||
|
@ -642,22 +643,74 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
.find(|e| **e == name)
|
||||
.copied()
|
||||
.unwrap_or("Custom");
|
||||
let is_custom = event_type == "Custom";
|
||||
let event_type = event_type
|
||||
.parse::<TokenStream>()
|
||||
.expect("couldn't parse event name");
|
||||
|
||||
// the `on:event_name` is 1 single name. Ideally, we
|
||||
// would point `on:` to `.on`, but I don't know how to
|
||||
// get the "subspan" of a span, so this is good enough
|
||||
let event_type = if is_custom {
|
||||
quote! { Custom::new(#name) }
|
||||
} else {
|
||||
event_type
|
||||
};
|
||||
|
||||
let event_name_ident = match &node.key {
|
||||
NodeName::Punctuated(parts) => {
|
||||
if parts.len() >= 2 {
|
||||
Some(&parts[1])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let undelegated_ident = match &node.key {
|
||||
NodeName::Punctuated(parts) => parts.last().and_then(|last| {
|
||||
if last == "undelegated" {
|
||||
Some(last)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let on = match &node.key {
|
||||
NodeName::Punctuated(parts) => &parts[0],
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let on = {
|
||||
let span = on.span();
|
||||
quote_spanned! {
|
||||
span => .on
|
||||
}
|
||||
};
|
||||
let event_type = if is_custom {
|
||||
event_type
|
||||
} else if let Some(ev_name) = event_name_ident {
|
||||
let span = ev_name.span();
|
||||
quote_spanned! {
|
||||
span => #ev_name
|
||||
}
|
||||
} else {
|
||||
event_type
|
||||
};
|
||||
|
||||
let event_type = if is_force_undelegated {
|
||||
quote! { ::leptos::ev::undelegated(::leptos::ev::#event_type) }
|
||||
let undelegated = if let Some(undelegated) = undelegated_ident {
|
||||
let span = undelegated.span();
|
||||
quote_spanned! {
|
||||
span => #undelegated
|
||||
}
|
||||
} else {
|
||||
quote! { undelegated }
|
||||
};
|
||||
quote! { ::leptos::ev::#undelegated(::leptos::ev::#event_type) }
|
||||
} else {
|
||||
quote! { ::leptos::ev::#event_type }
|
||||
};
|
||||
|
||||
quote! {
|
||||
.on(#event_type, #handler)
|
||||
#on(#event_type, #handler)
|
||||
}
|
||||
} else if let Some(name) = name.strip_prefix("prop:") {
|
||||
let value = node
|
||||
|
@ -665,8 +718,18 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
.as_ref()
|
||||
.expect("prop: attributes need a value")
|
||||
.as_ref();
|
||||
let prop = match &node.key {
|
||||
NodeName::Punctuated(parts) => &parts[0],
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let prop = {
|
||||
let span = prop.span();
|
||||
quote_spanned! {
|
||||
span => .prop
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
.prop(#name, (#cx, #[allow(unused_braces)] #value))
|
||||
#prop(#name, (#cx, #[allow(unused_braces)] #value))
|
||||
}
|
||||
} else if let Some(name) = name.strip_prefix("class:") {
|
||||
let value = node
|
||||
|
@ -674,8 +737,18 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
.as_ref()
|
||||
.expect("class: attributes need a value")
|
||||
.as_ref();
|
||||
let class = match &node.key {
|
||||
NodeName::Punctuated(parts) => &parts[0],
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let class = {
|
||||
let span = class.span();
|
||||
quote_spanned! {
|
||||
span => .class
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
.class(#name, (#cx, #[allow(unused_braces)] #value))
|
||||
#class(#name, (#cx, #[allow(unused_braces)] #value))
|
||||
}
|
||||
} else {
|
||||
let name = name.replacen("attr:", "", 1);
|
||||
|
@ -687,8 +760,22 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
}
|
||||
None => quote_spanned! { span => "" },
|
||||
};
|
||||
let attr = match &node.key {
|
||||
NodeName::Punctuated(parts) => Some(&parts[0]),
|
||||
_ => None,
|
||||
};
|
||||
let attr = if let Some(attr) = attr {
|
||||
let span = attr.span();
|
||||
quote_spanned! {
|
||||
span => .attr
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
.attr(#name, (#cx, #value))
|
||||
.attr
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
#attr(#name, (#cx, #value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue