diff --git a/leptos/tests/ssr.rs b/leptos/tests/ssr.rs index b974d47d2..d2e85b87a 100644 --- a/leptos/tests/ssr.rs +++ b/leptos/tests/ssr.rs @@ -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, +
10} class:red=true class:car={move || value() > 1} attr:id="id">
+ } + }); + + let dashes = run_scope(|cx| { + let (value, set_value) = create_signal(cx, 5); + view! { + cx, +
10} class-red=true class-car={move || value() > 1} attr-id="id">
+ } + }); + + assert_eq!(colons, dashes); + assert_eq!( + dashes, + "
" + ); +} diff --git a/leptos_macro/src/lib.rs b/leptos_macro/src/lib.rs index c726e1928..84c38a83f 100644 --- a/leptos_macro/src/lib.rs +++ b/leptos_macro/src/lib.rs @@ -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| { diff --git a/leptos_macro/src/view.rs b/leptos_macro/src/view.rs index 9a68b569a..261820a95 100644 --- a/leptos_macro/src/view.rs +++ b/leptos_macro/src/view.rs @@ -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)) })