Allow on-, class-, prop-, and attr- as equivalent to on:, class:, prop:, and attr: to get around a syn-rsx parsing limitation on mixing colons and dashes in an attribute name

This commit is contained in:
Greg Johnston 2022-11-03 19:57:27 -04:00
parent c8545f47cb
commit 47fad9a042
3 changed files with 92 additions and 14 deletions

View File

@ -81,3 +81,33 @@ fn test_classes() {
);
});
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
#[test]
fn test_dash_prefixes() {
use leptos_dom::*;
use leptos_macro::view;
use leptos_reactive::{create_signal, run_scope};
let colons = run_scope(|cx| {
let (value, set_value) = create_signal(cx, 5);
view! {
cx,
<div class="my big" class:a={move || value() > 10} class:red=true class:car={move || value() > 1} attr:id="id"></div>
}
});
let dashes = run_scope(|cx| {
let (value, set_value) = create_signal(cx, 5);
view! {
cx,
<div class="my big" class-a={move || value() > 10} class-red=true class-car={move || value() > 1} attr-id="id"></div>
}
});
assert_eq!(colons, dashes);
assert_eq!(
dashes,
"<div data-hk=\"0-0\" class=\"my big red car\" id=\"id\"></div>"
);
}

View File

@ -105,7 +105,7 @@ mod server;
/// # });
/// ```
///
/// 5. Event handlers can be added with `on:` attributes
/// 5. Event handlers can be added with `on:` attributes. If the event name contains a dash, you should use `on-` as the prefix instead.
/// ```rust
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
/// # run_scope(|cx| {
@ -124,7 +124,7 @@ mod server;
/// ```
///
/// 6. DOM properties can be set with `prop:` attributes, which take any primitive type or `JsValue` (or a signal
/// that returns a primitive or JsValue).
/// that returns a primitive or JsValue). If your property name contains a dash, you should use `prop-` as the prefix instead.
/// ```rust
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
/// # run_scope(|cx| {
@ -147,6 +147,7 @@ mod server;
/// ```
///
/// 7. Classes can be toggled with `class:` attributes, which take a `bool` (or a signal that returns a `bool`).
/// If your class name contains a dash, you should use `class-` as the prefix instead.
/// ```rust
/// # use leptos_reactive::*; use leptos_dom::*; use leptos_macro::view; use leptos_dom::wasm_bindgen::JsCast;
/// # run_scope(|cx| {

View File

@ -214,8 +214,14 @@ fn element_to_tokens(
.iter()
.filter_map(|node| {
node.name_as_string().and_then(|name| {
if name.starts_with("class:") {
let name = name.replacen("class:", "", 1);
if name.starts_with("class:") || name.starts_with("class-") {
let name = if name.starts_with("class:") {
name.replacen("class:", "", 1)
} else if name.starts_with("class-") {
name.replacen("class-", "", 1)
} else {
name
};
let value = node.value.as_ref().expect("class: attributes need values");
let span = node.name_span().expect("missing span for class name");
Some(quote_spanned! {
@ -400,6 +406,13 @@ fn attr_to_tokens(
} else {
name
};
let name = if name.starts_with("attr:") {
name.replacen("attr:", "", 1)
} else if name.starts_with("attr-") {
name.replacen("attr-", "", 1)
} else {
name
};
let value = match &node.value {
Some(expr) => match expr {
syn::Expr::Lit(expr_lit) => {
@ -456,16 +469,20 @@ fn attr_to_tokens(
}
}
// Event Handlers
else if name.starts_with("on:") {
else if name.starts_with("on:") || name.starts_with("on-") {
let handler = node
.value
.as_ref()
.expect("event listener attributes need a value");
if mode != Mode::Ssr {
let event_name = name.replacen("on:", "", 1);
let name = if name.starts_with("on:") {
name.replacen("on:", "", 1)
} else {
name.replacen("on-", "", 1)
};
expressions.push(quote_spanned! {
span => add_event_listener(#el_id.unchecked_ref(), #event_name, #handler);
span => add_event_listener(#el_id.unchecked_ref(), #name, #handler);
});
} else {
// this is here to avoid warnings about unused signals
@ -476,10 +493,9 @@ fn attr_to_tokens(
}
}
// Properties
else if name.starts_with("prop:") {
else if name.starts_with("prop:") || name.starts_with("prop-") {
// can't set properties in SSR
if mode != Mode::Ssr {
let name = name.replacen("prop:", "", 1);
if mode != Mode::Ssr {
let value = node.value.as_ref().expect("prop: blocks need values");
expressions.push(quote_spanned! {
span => leptos_dom::property(#cx, #el_id.unchecked_ref(), #name, #value.into_property(#cx))
@ -487,11 +503,10 @@ fn attr_to_tokens(
}
}
// Classes
else if name.starts_with("class:") {
else if name.starts_with("class:") || name.starts_with("class-") {
if mode == Mode::Ssr {
// handled separately because they need to be merged
} else {
let name = name.replacen("class:", "", 1);
let value = node.value.as_ref().expect("class: attributes need values");
expressions.push(quote_spanned! {
span => leptos_dom::class(#cx, #el_id.unchecked_ref(), #name, #value.into_class(#cx))
@ -875,9 +890,13 @@ fn create_component(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
let props = node.attributes.iter().filter_map(|attr| {
let attr_name = attr.name_as_string().unwrap_or_default();
if attr_name.starts_with("on:")
|| attr_name.starts_with("on-")
|| attr_name.starts_with("prop:")
|| attr_name.starts_with("prop-")
|| attr_name.starts_with("class:")
|| attr_name.starts_with("class-")
|| attr_name.starts_with("attr:")
|| attr_name.starts_with("attr-")
{
None
} else {
@ -896,12 +915,23 @@ fn create_component(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
let mut other_attrs = node.attributes.iter().filter_map(|attr| {
let attr_name = attr.name_as_string().unwrap_or_default();
// Event Listeners
if let Some(event_name) = attr_name.strip_prefix("on:") {
let span = attr.name_span().unwrap();
let handler = attr
.value
.as_ref()
.expect("event listener attributes need a value");
.expect("on: event listener attributes need a value");
Some(quote_spanned! {
span => add_event_listener(#component_name.unchecked_ref(), #event_name, #handler)
})
}
else if let Some(event_name) = attr_name.strip_prefix("on-") {
let span = attr.name_span().unwrap();
let handler = attr
.value
.as_ref()
.expect("on- event listener attributes need a value");
Some(quote_spanned! {
span => add_event_listener(#component_name.unchecked_ref(), #event_name, #handler)
})
@ -913,6 +943,12 @@ fn create_component(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
span => leptos_dom::property(#cx, #component_name.unchecked_ref(), #name, #value.into_property(#cx))
})
}
else if let Some(name) = attr_name.strip_prefix("prop-") {
let value = attr.value.as_ref().expect("prop- attributes need values");
Some(quote_spanned! {
span => leptos_dom::property(#cx, #component_name.unchecked_ref(), #name, #value.into_property(#cx))
})
}
// Classes
else if let Some(name) = attr_name.strip_prefix("class:") {
let value = attr.value.as_ref().expect("class: attributes need values");
@ -920,10 +956,21 @@ fn create_component(cx: &Ident, node: &Node, mode: Mode) -> TokenStream {
span => leptos_dom::class(#cx, #component_name.unchecked_ref(), #name, #value.into_class(#cx))
})
}
else if let Some(name) = attr_name.strip_prefix("class-") {
let value = attr.value.as_ref().expect("class: attributes need values");
Some(quote_spanned! {
span => leptos_dom::class(#cx, #component_name.unchecked_ref(), #name, #value.into_class(#cx))
})
}
// Attributes
else if let Some(name) = attr_name.strip_prefix("attr:") {
let value = attr.value.as_ref().expect("attr: attributes need values");
let name = name.replace('_', "-");
Some(quote_spanned! {
span => leptos_dom::attribute(#cx, #component_name.unchecked_ref(), #name, #value.into_attribute(#cx))
})
}
else if let Some(name) = attr_name.strip_prefix("attr-") {
let value = attr.value.as_ref().expect("attr- attributes need values");
Some(quote_spanned! {
span => leptos_dom::attribute(#cx, #component_name.unchecked_ref(), #name, #value.into_attribute(#cx))
})