diff --git a/packages/router/src/history/mod.rs b/packages/router/src/history/mod.rs index f692159a4..e9668b979 100644 --- a/packages/router/src/history/mod.rs +++ b/packages/router/src/history/mod.rs @@ -88,15 +88,18 @@ pub trait HistoryProvider { /// # use dioxus::prelude::*; /// # #[component] /// # fn Index(cx: Scope) -> Element { todo!() } + /// # fn Other(cx: Scope) -> Element { todo!() } /// #[derive(Clone, Routable, Debug, PartialEq)] /// enum Route { /// #[route("/")] /// Index {}, + /// #[route("/other")] + /// Other {}, /// } /// let mut history = MemoryHistory::::default(); /// assert_eq!(history.can_go_back(), false); /// - /// history.push(Route::Index {}); + /// history.push(Route::Other {}); /// assert_eq!(history.can_go_back(), true); /// ``` #[must_use] diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 8f5892f38..42cca27fb 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -35,7 +35,7 @@ use crate::utils::use_router_internal::use_router_internal; /// /// #[component] /// fn Index(cx: Scope) -> Element { -/// let path = use_route(&cx).unwrap(); +/// let path: Route = use_route(&cx).unwrap(); /// render! { /// h2 { "Current Path" } /// p { "{path}" } diff --git a/packages/router/src/routable.rs b/packages/router/src/routable.rs index fcf30464e..b1bb6d915 100644 --- a/packages/router/src/routable.rs +++ b/packages/router/src/routable.rs @@ -226,9 +226,9 @@ pub trait Routable: FromStr + Display + Clone + 'static { /// /// #[derive(Routable, Clone, PartialEq, Debug)] /// enum Route { - /// #[route("/")] + /// #[route("/home")] /// Home {}, - /// #[route("/about")] + /// #[route("/home/about")] /// About {}, /// } /// diff --git a/packages/ssr/src/cache.rs b/packages/ssr/src/cache.rs index a67fd5f22..bcd6ba71f 100644 --- a/packages/ssr/src/cache.rs +++ b/packages/ssr/src/cache.rs @@ -1,6 +1,8 @@ use dioxus_core::prelude::*; use std::fmt::Write; +use crate::renderer::{str_truthy, BOOL_ATTRS}; + #[derive(Debug)] pub struct StringCache { pub segments: Vec, @@ -86,6 +88,10 @@ impl StringCache { inner_html = Some(value); } else if let Some("style") = namespace { styles.push((name, value)); + } else if BOOL_ATTRS.contains(name) { + if str_truthy(value) { + write!(chain, " {name}=\"{value}\"",)?; + } } else { write!(chain, " {name}=\"{value}\"")?; } diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index 154fad641..98afef8b7 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -1,5 +1,7 @@ use super::cache::Segment; use crate::cache::StringCache; + +use dioxus_core::Attribute; use dioxus_core::{prelude::*, AttributeValue, DynamicNode, RenderReturn}; use std::collections::HashMap; use std::fmt::Write; @@ -83,18 +85,13 @@ impl Renderer { inner_html = Some(attr); } else if attr.namespace == Some("style") { accumulated_dynamic_styles.push(attr); + } else if BOOL_ATTRS.contains(&attr.name) { + if truthy(&attr.value) { + write!(buf, " {}=", attr.name)?; + write_value(buf, &attr.value)?; + } } else { - match attr.value { - AttributeValue::Text(value) => { - write!(buf, " {}=\"{}\"", attr.name, value)? - } - AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, - AttributeValue::Int(value) => write!(buf, " {}={}", attr.name, value)?, - AttributeValue::Float(value) => { - write!(buf, " {}={}", attr.name, value)? - } - _ => {} - }; + write_attribute(buf, attr)?; } } Segment::Node(idx) => match &template.dynamic_nodes[*idx] { @@ -153,17 +150,9 @@ impl Renderer { write!(buf, " style=\"")?; } for attr in &accumulated_dynamic_styles { - match attr.value { - AttributeValue::Text(value) => { - write!(buf, "{}:{};", attr.name, value)? - } - AttributeValue::Bool(value) => { - write!(buf, "{}:{};", attr.name, value)? - } - AttributeValue::Float(f) => write!(buf, "{}:{};", attr.name, f)?, - AttributeValue::Int(i) => write!(buf, "{}:{};", attr.name, i)?, - _ => {} - }; + write!(buf, "{}:", attr.name)?; + write_value_unquoted(buf, &attr.value)?; + write!(buf, ";")?; } if !*inside_style_tag { write!(buf, "\"")?; @@ -248,3 +237,81 @@ fn to_string_works() { assert_eq!(out, "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
</diiiiiiiiv>
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
"); } + +pub(crate) const BOOL_ATTRS: &[&str] = &[ + "allowfullscreen", + "allowpaymentrequest", + "async", + "autofocus", + "autoplay", + "checked", + "controls", + "default", + "defer", + "disabled", + "formnovalidate", + "hidden", + "ismap", + "itemscope", + "loop", + "multiple", + "muted", + "nomodule", + "novalidate", + "open", + "playsinline", + "readonly", + "required", + "reversed", + "selected", + "truespeed", + "webkitdirectory", +]; + +pub(crate) fn str_truthy(value: &str) -> bool { + !value.is_empty() && value != "0" && value.to_lowercase() != "false" +} + +pub(crate) fn truthy(value: &AttributeValue) -> bool { + match value { + AttributeValue::Text(value) => str_truthy(value), + AttributeValue::Bool(value) => *value, + AttributeValue::Int(value) => *value != 0, + AttributeValue::Float(value) => *value != 0.0, + _ => false, + } +} + +pub(crate) fn write_attribute(buf: &mut impl Write, attr: &Attribute) -> std::fmt::Result { + let name = &attr.name; + match attr.value { + AttributeValue::Text(value) => write!(buf, " {name}=\"{value}\""), + AttributeValue::Bool(value) => write!(buf, " {name}={value}"), + AttributeValue::Int(value) => write!(buf, " {name}={value}"), + AttributeValue::Float(value) => write!(buf, " {name}={value}"), + _ => Ok(()), + } +} + +pub(crate) fn write_value(buf: &mut impl Write, value: &AttributeValue) -> std::fmt::Result { + match value { + AttributeValue::Text(value) => write!(buf, "\"{}\"", value), + AttributeValue::Bool(value) => write!(buf, "{}", value), + AttributeValue::Int(value) => write!(buf, "{}", value), + AttributeValue::Float(value) => write!(buf, "{}", value), + _ => Ok(()), + } +} + +pub(crate) fn write_value_unquoted( + buf: &mut impl Write, + value: &AttributeValue, +) -> std::fmt::Result { + match value { + AttributeValue::Text(value) => write!(buf, "{}", value), + AttributeValue::Bool(value) => write!(buf, "{}", value), + AttributeValue::Int(value) => write!(buf, "{}", value), + AttributeValue::Float(value) => write!(buf, "{}", value), + _ => Ok(()), + } +} diff --git a/packages/ssr/tests/bool_attr.rs b/packages/ssr/tests/bool_attr.rs new file mode 100644 index 000000000..778e90dcc --- /dev/null +++ b/packages/ssr/tests/bool_attr.rs @@ -0,0 +1,37 @@ +use dioxus::prelude::*; + +#[test] +fn static_boolean_attributs() { + fn app(cx: Scope) -> Element { + render! { + div { hidden: "false" } + div { hidden: "true" } + } + } + + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + + assert_eq!( + dioxus_ssr::render(&dom), + r#"
"# + ); +} + +#[test] +fn dynamic_boolean_attributs() { + fn app(cx: Scope) -> Element { + render! { + div { hidden: false } + div { hidden: true } + } + } + + let mut dom = VirtualDom::new(app); + _ = dom.rebuild(); + + assert_eq!( + dioxus_ssr::render(&dom), + r#"
"# + ); +}