Disambiguate expressions in rsx by requiring curlies

This commit is contained in:
Jonathan Kelley committed 2024-01-10 19:33:34 -08:00
1 parent 663296ae45
commit d9b84f9f8f
53 files changed
+399 -373

No files matched your search

+5 -1
View File
@@ -76,7 +76,11 @@ fn app(cx: Scope) -> Element {
"Hover, click, type or scroll to see the info down below" "Hover, click, type or scroll to see the info down below"
} }
div { events.read().iter().map(|event| rsx!( div { "{event:?}" } )) } div {
for event in events.read().iter() {
div { "{event:?}" }
}
}
} }
)) ))
} }
+4 -4
View File
@@ -58,13 +58,13 @@ fn app(cx: Scope) -> Element {
}; };
cx.render(rsx!( cx.render(rsx!(
style { include_str!("./assets/calculator.css") } style { {include_str!("./assets/calculator.css")} }
div { id: "wrapper", div { id: "wrapper",
div { class: "app", div { class: "app",
div { class: "calculator", div { class: "calculator",
tabindex: "0", tabindex: "0",
onkeydown: handle_key_down_event, onkeydown: handle_key_down_event,
div { class: "calculator-display", val.to_string() } div { class: "calculator-display", "{val}" }
div { class: "calculator-keypad", div { class: "calculator-keypad",
div { class: "input-keys", div { class: "input-keys",
div { class: "function-keys", div { class: "function-keys",
@@ -103,14 +103,14 @@ fn app(cx: Scope) -> Element {
div { class: "digit-keys", div { class: "digit-keys",
button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" } button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" }
button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" } button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" }
(1..10).map(|k| rsx!{ for k in 1..10 {
button { button {
class: "calculator-key {k}", class: "calculator-key {k}",
name: "key-{k}", name: "key-{k}",
onclick: move |_| input_digit(k), onclick: move |_| input_digit(k),
"{k}" "{k}"
} }
}), }
} }
} }
div { class: "operator-keys", div { class: "operator-keys",
+2 -2
View File
@@ -32,12 +32,12 @@ fn app(cx: Scope) -> Element {
} }
ul { ul {
emails_sent.read().iter().map(|message| cx.render(rsx! { for message in emails_sent.read().iter() {
li { li {
h3 { "email" } h3 { "email" }
span {"{message}"} span {"{message}"}
} }
})) }
} }
} }
}) })
+1 -1
View File
@@ -16,7 +16,7 @@ fn app(cx: Scope) -> Element {
loop { loop {
tokio::time::sleep(std::time::Duration::from_millis(10)).await; tokio::time::sleep(std::time::Duration::from_millis(10)).await;
if let Some(element) = elements.read().get(focused) { if let Some(element) = elements.read().get(focused) {
element.set_focus(true); _ = element.set_focus(true).await;
} else { } else {
focused = 0; focused = 0;
} }
+2 -2
View File
@@ -62,7 +62,7 @@ fn ClientList(cx: Scope) -> Element {
Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" } Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" }
Link { to: Route::Settings {}, class: "pure-button", "Settings" } Link { to: Route::Settings {}, class: "pure-button", "Settings" }
clients.read().iter().map(|client| rsx! { for client in clients.read().iter() {
div { div {
class: "client", class: "client",
style: "margin-bottom: 50px", style: "margin-bottom: 50px",
@@ -70,7 +70,7 @@ fn ClientList(cx: Scope) -> Element {
p { "Name: {client.first_name} {client.last_name}" } p { "Name: {client.first_name} {client.last_name}" }
p { "Description: {client.description}" } p { "Description: {client.description}" }
} }
}) }
}) })
} }
+2 -2
View File
@@ -14,9 +14,9 @@ fn app(cx: Scope) -> Element {
} }
render! { render! {
(0..count).map(|_| rsx!{ {(0..count).map(|_| rsx!{
drop_child {} drop_child {}
}) })}
} }
} }
+3 -3
View File
@@ -39,9 +39,9 @@ fn ChildWithRef(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { div {
ul { ul {
names.read().iter().map(|f| rsx!{ for name in names.read().iter() {
li { "hello: {f}" } li { "hello: {name}" }
}) }
} }
button { button {
onclick: move |_| { onclick: move |_| {
+9 -11
View File
@@ -28,12 +28,12 @@ fn app(cx: Scope) -> Element {
link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", } link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
header { header {
i { class: "material-icons icon-menu", "menu" } i { class: "material-icons icon-menu", "menu" }
h1 { "Files: ", files.read().current() } h1 { "Files: ", {files.read().current()} }
span { } span { }
i { class: "material-icons", onclick: move |_| files.write().go_up(), "logout" } i { class: "material-icons", onclick: move |_| files.write().go_up(), "logout" }
} }
main { main {
files.read().path_names.iter().enumerate().map(|(dir_id, path)| { {files.read().path_names.iter().enumerate().map(|(dir_id, path)| {
let path_end = path.split('/').last().unwrap_or(path.as_str()); let path_end = path.split('/').last().unwrap_or(path.as_str());
let icon_type = if path_end.contains('.') { let icon_type = if path_end.contains('.') {
"description" "description"
@@ -52,15 +52,13 @@ fn app(cx: Scope) -> Element {
h1 { "{path_end}" } h1 { "{path_end}" }
} }
) )
}), })},
files.read().err.as_ref().map(|err| { if let Some(err) = files.read().err.as_ref() {
rsx! ( div {
div { code { "{err}" }
code { "{err}" } button { onclick: move |_| files.write().clear_err(), "x" }
button { onclick: move |_| files.write().clear_err(), "x" } }
} }
)
})
} }
} }
}) })
+6 -5
View File
@@ -66,9 +66,9 @@ fn app(cx: Scope) -> Element {
} }
table { table {
tbody { tbody {
items.read().iter().enumerate().map(|(id, item)| { for (id, item) in items.read().iter().enumerate() {
let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""}; tr {
rsx!(tr { class: "{is_in_danger}", class: if (*selected).map(|s| s == id).unwrap_or(false) { "danger" },
td { class:"col-md-1" } td { class:"col-md-1" }
td { class:"col-md-1", "{item.key}" } td { class:"col-md-1", "{item.key}" }
td { class:"col-md-1", onclick: move |_| selected.set(Some(id)), td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
@@ -80,8 +80,9 @@ fn app(cx: Scope) -> Element {
} }
} }
td { class: "col-md-6" } td { class: "col-md-6" }
}) }
}) }
} }
} }
span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" } span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
+3 -3
View File
@@ -37,7 +37,7 @@ const FIELDS: &[(&str, &str)] = &[
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { margin_left: "30px", div { margin_left: "30px",
select_example(cx), {select_example(cx)},
div { div {
// handling inputs on divs will catch all input events below // handling inputs on divs will catch all input events below
// so the value of our input event will be either huey, dewey, louie, or true/false (because of the checkboxe) // so the value of our input event will be either huey, dewey, louie, or true/false (because of the checkboxe)
@@ -114,7 +114,7 @@ fn app(cx: Scope) -> Element {
} }
} }
FIELDS.iter().map(|(field, value)| rsx! { for (field, value) in FIELDS.iter() {
div { div {
input { input {
id: "{field}", id: "{field}",
@@ -131,7 +131,7 @@ fn app(cx: Scope) -> Element {
} }
br {} br {}
} }
}) }
} }
}) })
} }
@@ -41,7 +41,7 @@ pub fn LogOut(cx: Scope<ClientProps>) -> Element {
} }
Err(error) => { Err(error) => {
rsx! { rsx! {
div { format!{"Failed to load disconnection url: {:?}", error} } div { "Failed to load disconnection url: {error:?}" }
} }
} }
}, },
@@ -143,9 +143,9 @@ pub fn LoadClient(cx: Scope) -> Element {
} }
} }
Err(error) => { Err(error) => {
log::info! {"Failed to load client: {:?}", error};
rsx! { rsx! {
div { format!{"Failed to load client: {:?}", error} } div { "Failed to load client: {error:?}" }
log::info!{"Failed to load client: {:?}", error},
Outlet::<Route> {} Outlet::<Route> {}
} }
} }
@@ -184,7 +184,7 @@ pub fn AuthHeader(cx: Scope) -> Element {
Ok(email) => { Ok(email) => {
rsx! { rsx! {
div { div {
div { email } div { {email} }
LogOut { client_id: client_props.client_id, client: client_props.client } LogOut { client_id: client_props.client_id, client: client_props.client }
Outlet::<Route> {} Outlet::<Route> {}
} }
@@ -207,7 +207,7 @@ pub fn AuthHeader(cx: Scope) -> Element {
log::info!("Other issue with token"); log::info!("Other issue with token");
rsx! { rsx! {
div { div {
div { error.to_string() } div { "{error}" }
Outlet::<Route> {} Outlet::<Route> {}
} }
} }
@@ -2,6 +2,9 @@ use dioxus::prelude::*;
#[component] #[component]
pub fn NotFound(cx: Scope, route: Vec<String>) -> Element { pub fn NotFound(cx: Scope, route: Vec<String>) -> Element {
let routes = route.join(""); render! {
render! {rsx! {div{routes}}} div{
{route.join("")}
}
}
} }
+5 -5
View File
@@ -39,11 +39,11 @@ fn app(cx: Scope) -> Element {
let state = use_ref(cx, Calculator::new); let state = use_ref(cx, Calculator::new);
cx.render(rsx! { cx.render(rsx! {
style { include_str!("./assets/calculator.css") } style { {include_str!("./assets/calculator.css")} }
div { id: "wrapper", div { id: "wrapper",
div { class: "app", div { class: "app",
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt), div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
div { class: "calculator-display", state.read().formatted_display() } div { class: "calculator-display", {state.read().formatted_display()} }
div { class: "calculator-keypad", div { class: "calculator-keypad",
div { class: "input-keys", div { class: "input-keys",
div { class: "function-keys", div { class: "function-keys",
@@ -74,14 +74,14 @@ fn app(cx: Scope) -> Element {
onclick: move |_| state.write().input_dot(), onclick: move |_| state.write().input_dot(),
"●" "●"
} }
(1..10).map(move |k| rsx!{ for k in 1..10 {
CalculatorKey { CalculatorKey {
key: "{k}", key: "{k}",
name: "key-{k}", name: "key-{k}",
onclick: move |_| state.write().input_digit(k), onclick: move |_| state.write().input_digit(k),
"{k}" "{k}"
} }
}) }
} }
} }
div { class: "operator-keys", div { class: "operator-keys",
@@ -130,7 +130,7 @@ fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
button { button {
class: "calculator-key {cx.props.name}", class: "calculator-key {cx.props.name}",
onclick: move |e| cx.props.onclick.call(e), onclick: move |e| cx.props.onclick.call(e),
&cx.props.children {&cx.props.children}
} }
}) })
} }
+1 -1
View File
@@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
cx.render(rsx!( cx.render(rsx!(
div { div {
h1 {"Select an option"} h1 {"Select an option"}
h3 { "The radio is... ", state.is_playing(), "!" } h3 { "The radio is... ", {state.is_playing()}, "!" }
button { onclick: move |_| state.make_mut().reduce(PlayerAction::Pause), button { onclick: move |_| state.make_mut().reduce(PlayerAction::Pause),
"Pause" "Pause"
} }
+2 -2
View File
@@ -66,7 +66,7 @@ impl FromQuery for ManualBlogQuerySegments {
fn BlogPost(cx: Scope, query_params: ManualBlogQuerySegments) -> Element { fn BlogPost(cx: Scope, query_params: ManualBlogQuerySegments) -> Element {
render! { render! {
div{"This is your blogpost with a query segment:"} div{"This is your blogpost with a query segment:"}
div{format!("{:?}", query_params)} div{ "{query_params:?}" }
} }
} }
@@ -74,7 +74,7 @@ fn BlogPost(cx: Scope, query_params: ManualBlogQuerySegments) -> Element {
fn AutomaticBlogPost(cx: Scope, name: String, surname: String) -> Element { fn AutomaticBlogPost(cx: Scope, name: String, surname: String) -> Element {
render! { render! {
div{"This is your blogpost with a query segment:"} div{"This is your blogpost with a query segment:"}
div{format!("name={}&surname={}", name, surname)} div{ "name={name}&surname={surname}" }
} }
} }
+8 -8
View File
@@ -36,17 +36,17 @@ fn example(cx: Scope) -> Element {
div { id: "asd", div { id: "asd",
"your neighborhood spiderman" "your neighborhood spiderman"
items.iter().cycle().take(5).map(|f| rsx!{ for item in items.iter().cycle().take(5) {
div { "{f.a}" } div { "{item.a}" }
}) }
things_list.iter().map(|f| rsx!{ for thing in things_list.iter() {
div { "{f.a}" "{f.b}" } div { "{thing.a}" "{thing.b}" }
}) }
mything_read.as_ref().map(|f| rsx! { if let Some(f) = mything_read.as_ref() {
div { "{f}" } div { "{f}" }
}) }
} }
} }
)) ))
+17 -16
View File
@@ -61,7 +61,7 @@ fn App(cx: Scope) -> Element {
h1 {"Some text"} h1 {"Some text"}
h1 {"Some text with {formatting}"} h1 {"Some text with {formatting}"}
h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"} h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
h1 {"Formatting without interpolation " formatting_tuple.0 "and" formatting_tuple.1 } h1 {"Formatting without interpolation " {formatting_tuple.0} "and" {formatting_tuple.1} }
h2 { h2 {
"Multiple" "Multiple"
"Text" "Text"
@@ -94,10 +94,10 @@ fn App(cx: Scope) -> Element {
} }
// Expressions can be used in element position too: // Expressions can be used in element position too:
rsx!(p { "More templating!" }), {rsx!(p { "More templating!" })},
// Iterators // Iterators
(0..10).map(|i| rsx!(li { "{i}" })), {(0..10).map(|i| rsx!(li { "{i}" }))},
// Iterators within expressions // Iterators within expressions
{ {
@@ -117,24 +117,25 @@ fn App(cx: Scope) -> Element {
// Conditional rendering // Conditional rendering
// Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals. // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
// You can convert a bool condition to rsx! with .then and .or // You can convert a bool condition to rsx! with .then and .or
true.then(|| rsx!(div {})), {true.then(|| rsx!(div {}))},
// Alternatively, you can use the "if" syntax - but both branches must be resolve to Element // Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
if false { if false {
rsx!(h1 {"Top text"}) h1 {"Top text"}
} else { } else {
rsx!(h1 {"Bottom text"}) h1 {"Bottom text"}
} }
// Using optionals for diverging branches // Using optionals for diverging branches
if true { // Note that since this is wrapped in curlies, it's interpreted as an expression
{if true {
Some(rsx!(h1 {"Top text"})) Some(rsx!(h1 {"Top text"}))
} else { } else {
None None
} }}
// returning "None" without a diverging branch is a bit noisy... but rare in practice // returning "None" without a diverging branch is a bit noisy... but rare in practice
None as Option<()>, {None as Option<()>},
// can also just use empty fragments // can also just use empty fragments
Fragment {} Fragment {}
@@ -169,13 +170,13 @@ fn App(cx: Scope) -> Element {
// Can pass in props directly as an expression // Can pass in props directly as an expression
{ {
let props = TallerProps {a: "hello", children: cx.render(rsx!(()))}; let props = TallerProps {a: "hello", children: None };
rsx!(Taller { ..props }) rsx!(Taller { ..props })
} }
// Spreading can also be overridden manually // Spreading can also be overridden manually
Taller { Taller {
..TallerProps { a: "ballin!", children: cx.render(rsx!(()) )}, ..TallerProps { a: "ballin!", children: None },
a: "not ballin!" a: "not ballin!"
} }
@@ -204,16 +205,16 @@ fn App(cx: Scope) -> Element {
// helper functions // helper functions
// Anything that implements IntoVnode can be dropped directly into Rsx // Anything that implements IntoVnode can be dropped directly into Rsx
helper(cx, "hello world!") {helper(cx, "hello world!")}
// Strings can be supplied directly // Strings can be supplied directly
String::from("Hello world!") {String::from("Hello world!")}
// So can format_args // So can format_args
format_args!("Hello {}!", "world") {format_args!("Hello {}!", "world")}
// Or we can shell out to a helper function // Or we can shell out to a helper function
format_dollars(10, 50) {format_dollars(10, 50)}
} }
}) })
} }
@@ -269,7 +270,7 @@ pub struct TallerProps<'a> {
#[component] #[component]
pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element { pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
cx.render(rsx! { cx.render(rsx! {
&cx.props.children {&cx.props.children}
}) })
} }
+1 -1
View File
@@ -23,7 +23,7 @@ fn app(cx: Scope) -> Element {
button { button {
onclick: move |_| { onclick: move |_| {
if let Some(header) = header_element.read().as_ref() { if let Some(header) = header_element.read().as_ref() {
header.scroll_to(ScrollBehavior::Smooth); _ = header.scroll_to(ScrollBehavior::Smooth);
} }
}, },
"Scroll to top" "Scroll to top"
+3 -3
View File
@@ -31,7 +31,7 @@ fn app(cx: Scope) -> Element {
// We can do boolean operations on the current signal value // We can do boolean operations on the current signal value
if count.value() > 5 { if count.value() > 5 {
rsx!{ h2 { "High five!" } } h2 { "High five!" }
} }
// We can cleanly map signals with iterators // We can cleanly map signals with iterators
@@ -41,9 +41,9 @@ fn app(cx: Scope) -> Element {
// We can also use the signal value as a slice // We can also use the signal value as a slice
if let [ref first, .., ref last] = saved_values.read().as_slice() { if let [ref first, .., ref last] = saved_values.read().as_slice() {
rsx! { li { "First and last: {first}, {last}" } } li { "First and last: {first}, {last}" }
} else { } else {
rsx! { "No saved values" } "No saved values"
} }
}) })
} }
+5 -5
View File
@@ -8,17 +8,17 @@ fn app(cx: Scope) -> Element {
cx.render(rsx!( cx.render(rsx!(
div { div {
// Use Map directly to lazily pull elements // Use Map directly to lazily pull elements
(0..10).map(|f| rsx! { "{f}" }), {(0..10).map(|f| rsx! { "{f}" })},
// Collect into an intermediate collection if necessary, and call into_iter // Collect into an intermediate collection if necessary, and call into_iter
["a", "b", "c", "d", "e", "f"] {["a", "b", "c", "d", "e", "f"]
.into_iter() .into_iter()
.map(|f| rsx! { "{f}" }) .map(|f| rsx! { "{f}" })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into_iter(), .into_iter()},
// Use optionals // Use optionals
Some(rsx! { "Some" }), {Some(rsx! { "Some" })},
// use a for loop where the body itself is RSX // use a for loop where the body itself is RSX
for name in 0..10 { for name in 0..10 {
@@ -27,7 +27,7 @@ fn app(cx: Scope) -> Element {
// Or even use an unterminated conditional // Or even use an unterminated conditional
if true { if true {
rsx!{ "hello world!" } "hello world!"
} }
} }
)) ))
+1 -1
View File
@@ -97,7 +97,7 @@ pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
fill: "{fill}", fill: "{fill}",
} }
dots {dots}
} }
}) })
} }
+33 -35
View File
@@ -52,30 +52,30 @@ pub fn app(cx: Scope<()>) -> Element {
TodoHeader { todos: todos } TodoHeader { todos: todos }
section { class: "main", section { class: "main",
if !todos.is_empty() { if !todos.is_empty() {
rsx! { input {
input { id: "toggle-all",
id: "toggle-all", class: "toggle-all",
class: "toggle-all", r#type: "checkbox",
r#type: "checkbox", onchange: move |_| {
onchange: move |_| { let check = active_todo_count != 0;
let check = active_todo_count != 0; for (_, item) in todos.make_mut().iter_mut() {
for (_, item) in todos.make_mut().iter_mut() { item.checked = check;
item.checked = check; }
} },
}, checked: if active_todo_count == 0 { "true" } else { "false" },
checked: if active_todo_count == 0 { "true" } else { "false" },
}
label { r#for: "toggle-all" }
} }
label { r#for: "toggle-all" }
} }
ul { class: "todo-list", ul { class: "todo-list",
filtered_todos.iter().map(|id| rsx!(TodoEntry { for id in filtered_todos.iter() {
key: "{id}", TodoEntry {
id: *id, key: "{id}",
todos: todos, id: *id,
})) todos: todos,
}
}
} }
(!todos.is_empty()).then(|| rsx!( if !todos.is_empty() {
ListFooter { ListFooter {
active_todo_count: active_todo_count, active_todo_count: active_todo_count,
active_todo_text: active_todo_text, active_todo_text: active_todo_text,
@@ -83,7 +83,7 @@ pub fn app(cx: Scope<()>) -> Element {
todos: todos, todos: todos,
filter: filter, filter: filter,
} }
)) }
} }
} }
PageFooter {} PageFooter {}
@@ -172,7 +172,7 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
prevent_default: "onclick" prevent_default: "onclick"
} }
} }
is_editing.then(|| rsx!{ if **is_editing {
input { input {
class: "edit", class: "edit",
value: "{todo.contents}", value: "{todo.contents}",
@@ -186,7 +186,7 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
} }
}, },
} }
}) }
} }
}) })
} }
@@ -220,29 +220,27 @@ pub fn ListFooter<'a>(cx: Scope<'a, ListFooterProps<'a>>) -> Element {
} }
ul { class: "filters", ul { class: "filters",
for (state , state_text , url) in [ for (state , state_text , url) in [
(FilterState::All, "All", "#/"), (FilterState::All, "All", "#/"),
(FilterState::Active, "Active", "#/active"), (FilterState::Active, "Active", "#/active"),
(FilterState::Completed, "Completed", "#/completed"), (FilterState::Completed, "Completed", "#/completed"),
] { ] {
li { li {
a { a {
href: url, href: url,
class: selected(state), class: selected(state),
onclick: move |_| cx.props.filter.set(state), onclick: move |_| cx.props.filter.set(state),
prevent_default: "onclick", prevent_default: "onclick",
state_text {state_text}
} }
} }
} }
} }
if cx.props.show_clear_completed { if cx.props.show_clear_completed {
cx.render(rsx! { button {
button { class: "clear-completed",
class: "clear-completed", onclick: move |_| cx.props.todos.make_mut().retain(|_, todo| !todo.checked),
onclick: move |_| cx.props.todos.make_mut().retain(|_, todo| !todo.checked), "Clear completed"
"Clear completed" }
}
})
} }
} }
}) })
+5 -4
View File
@@ -1,11 +1,11 @@
use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop}; use dioxus_rsx::{AttributeType, BodyNode, ElementAttrValue, ForLoop, IfChain};
use proc_macro2::{LineColumn, Span}; use proc_macro2::{LineColumn, Span};
use quote::ToTokens; use quote::ToTokens;
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
fmt::{Result, Write}, fmt::{Result, Write},
}; };
use syn::{spanned::Spanned, Expr, ExprIf}; use syn::{spanned::Spanned, Expr};
use crate::buffer::Buffer; use crate::buffer::Buffer;
use crate::ifmt_to_string; use crate::ifmt_to_string;
@@ -231,8 +231,9 @@ impl<'a> Writer<'a> {
Ok(()) Ok(())
} }
fn write_if_chain(&mut self, ifchain: &ExprIf) -> std::fmt::Result { fn write_if_chain(&mut self, ifchain: &IfChain) -> std::fmt::Result {
self.write_raw_expr(ifchain.span()) todo!()
// self.write_raw_expr(ifchain.span())
} }
} }
+4 -4
View File
@@ -84,7 +84,7 @@ fn create() {
fn create_list() { fn create_list() {
let mut dom = VirtualDom::new(|cx| { let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! { cx.render(rsx! {
(0..3).map(|f| rsx!( div { "hello" } )) {(0..3).map(|f| rsx!( div { "hello" } ))}
}) })
}); });
@@ -148,7 +148,7 @@ fn create_components() {
fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element { fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! { cx.render(rsx! {
h1 {} h1 {}
div { &cx.props.children } div { {&cx.props.children} }
p {} p {}
}) })
} }
@@ -163,10 +163,10 @@ fn anchors() {
let mut dom = VirtualDom::new(|cx| { let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! { cx.render(rsx! {
if true { if true {
rsx!( div { "hello" } ) div { "hello" }
} }
if false { if false {
rsx!( div { "goodbye" } ) div { "goodbye" }
} }
}) })
}); });
+9 -9
View File
@@ -7,7 +7,7 @@ use dioxus_core::ElementId;
#[test] #[test]
fn empty_fragment_creates_nothing() { fn empty_fragment_creates_nothing() {
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx!(())) cx.render(rsx!({ () }))
} }
let mut vdom = VirtualDom::new(app); let mut vdom = VirtualDom::new(app);
@@ -43,18 +43,18 @@ fn fragments_nested() {
cx.render(rsx!( cx.render(rsx!(
div { "hello" } div { "hello" }
div { "goodbye" } div { "goodbye" }
rsx! { {rsx! {
div { "hello" } div { "hello" }
div { "goodbye" } div { "goodbye" }
rsx! { {rsx! {
div { "hello" } div { "hello" }
div { "goodbye" } div { "goodbye" }
rsx! { {rsx! {
div { "hello" } div { "hello" }
div { "goodbye" } div { "goodbye" }
} }}
} }}
} }}
)) ))
}); });
@@ -79,7 +79,7 @@ fn fragments_across_components() {
let world = "world"; let world = "world";
cx.render(rsx! { cx.render(rsx! {
"hellO!" "hellO!"
world {world}
}) })
} }
@@ -94,7 +94,7 @@ fn list_fragments() {
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx!( cx.render(rsx!(
h1 {"hello"} h1 {"hello"}
(0..6).map(|f| rsx!( span { "{f}" })) {(0..6).map(|f| rsx!( span { "{f}" }))}
)) ))
} }
assert_eq!( assert_eq!(
+2 -2
View File
@@ -11,12 +11,12 @@ use dioxus_core::ElementId;
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
cx.render(rsx! { cx.render(rsx! {
div { div {
(0..3).map(|i| rsx! { for i in 0..3 {
div { div {
h1 { "hello world! "} h1 { "hello world! "}
p { "{i}" } p { "{i}" }
} }
}) }
} }
}) })
} }
+2 -2
View File
@@ -20,7 +20,7 @@ fn nested_passthru_creates() {
#[component] #[component]
fn PassThru<'a>(cx: Scope<'a>, children: Element<'a>) -> Element { fn PassThru<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
cx.render(rsx!(children)) cx.render(rsx!({ children }))
} }
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
@@ -60,7 +60,7 @@ fn nested_passthru_creates_add() {
#[component] #[component]
fn ChildComp<'a>(cx: Scope, children: Element<'a>) -> Element { fn ChildComp<'a>(cx: Scope, children: Element<'a>) -> Element {
cx.render(rsx! { children }) cx.render(rsx! { {children} })
} }
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
+1 -1
View File
@@ -41,7 +41,7 @@ fn component_swap() {
cx.render(rsx! { cx.render(rsx! {
h1 { h1 {
"NavBar" "NavBar"
(0..3).map(|_| rsx!(nav_link {})) {(0..3).map(|_| rsx!(nav_link {}))}
} }
}) })
} }
+12 -12
View File
@@ -17,7 +17,7 @@ fn keyed_diffing_out_of_order() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
{ {
@@ -59,7 +59,7 @@ fn keyed_diffing_out_of_order_adds() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -85,7 +85,7 @@ fn keyed_diffing_out_of_order_adds_3() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -111,7 +111,7 @@ fn keyed_diffing_out_of_order_adds_4() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -137,7 +137,7 @@ fn keyed_diffing_out_of_order_adds_5() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -162,7 +162,7 @@ fn keyed_diffing_additions() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -187,7 +187,7 @@ fn keyed_diffing_additions_and_moves_on_ends() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -216,7 +216,7 @@ fn keyed_diffing_additions_and_moves_in_middle() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -250,7 +250,7 @@ fn controlled_keyed_diffing_out_of_order() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -284,7 +284,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -313,7 +313,7 @@ fn remove_list() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
@@ -338,7 +338,7 @@ fn no_common_keys() {
_ => unreachable!(), _ => unreachable!(),
}; };
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" })))) cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
}); });
_ = dom.rebuild(); _ = dom.rebuild();
+15 -13
View File
@@ -9,9 +9,9 @@ fn list_creates_one_by_one() {
cx.render(rsx! { cx.render(rsx! {
div { div {
(0..gen).map(|i| rsx! { for i in 0..gen {
div { "{i}" } div { "{i}" }
}) }
} }
}) })
}); });
@@ -78,9 +78,9 @@ fn removes_one_by_one() {
cx.render(rsx! { cx.render(rsx! {
div { div {
(0..gen).map(|i| rsx! { for i in 0..gen {
div { "{i}" } div { "{i}" }
}) }
} }
}) })
}); });
@@ -153,10 +153,10 @@ fn list_shrink_multiroot() {
let mut dom = VirtualDom::new(|cx| { let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! { cx.render(rsx! {
div { div {
(0..cx.generation()).map(|i| rsx! { for i in 0..cx.generation() {
div { "{i}" } div { "{i}" }
div { "{i}" } div { "{i}" }
}) }
} }
}) })
}); });
@@ -214,10 +214,10 @@ fn removes_one_by_one_multiroot() {
cx.render(rsx! { cx.render(rsx! {
div { div {
(0..gen).map(|i| rsx! { {(0..gen).map(|i| rsx! {
div { "{i}" } div { "{i}" }
div { "{i}" } div { "{i}" }
}) })}
} }
}) })
}); });
@@ -276,9 +276,9 @@ fn removes_one_by_one_multiroot() {
fn two_equal_fragments_are_equal_static() { fn two_equal_fragments_are_equal_static() {
let mut dom = VirtualDom::new(|cx| { let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! { cx.render(rsx! {
(0..5).map(|_| rsx! { for _ in 0..5 {
div { "hello" } div { "hello" }
}) }
}) })
}); });
@@ -290,9 +290,9 @@ fn two_equal_fragments_are_equal_static() {
fn two_equal_fragments_are_equal() { fn two_equal_fragments_are_equal() {
let mut dom = VirtualDom::new(|cx| { let mut dom = VirtualDom::new(|cx| {
cx.render(rsx! { cx.render(rsx! {
(0..5).map(|i| rsx! { for i in 0..5 {
div { "hello {i}" } div { "hello {i}" }
}) }
}) })
}); });
@@ -311,7 +311,9 @@ fn remove_many() {
}; };
cx.render(rsx! { cx.render(rsx! {
(0..num).map(|i| rsx! { div { "hello {i}" } }) for i in 0..num {
div { "hello {i}" }
}
}) })
}); });
+2 -2
View File
@@ -58,11 +58,11 @@ fn app(cx: Scope) -> Element {
*CLICKS.lock().unwrap() += 1; *CLICKS.lock().unwrap() += 1;
}, },
vec![ {vec![
render! { render! {
problematic_child {} problematic_child {}
} }
].into_iter() ].into_iter()}
} }
} }
} }
+1 -1
View File
@@ -51,7 +51,7 @@ fn events_generate() {
"Click me!" "Click me!"
} }
}), }),
_ => cx.render(rsx!(())), _ => None,
} }
}; };
+2 -2
View File
@@ -39,9 +39,9 @@ fn App(cx: Scope) -> Element {
} }
button { onclick: move |_| idx -= 1, "-" } button { onclick: move |_| idx -= 1, "-" }
ul { ul {
(0..**idx).map(|i| rsx! { {(0..**idx).map(|i| rsx! {
ChildExample { i: i, onhover: onhover } ChildExample { i: i, onhover: onhover }
}) })}
} }
} }
}) })
+2 -2
View File
@@ -15,7 +15,7 @@ fn test_memory_leak() {
cx.spawn(async {}); cx.spawn(async {});
if val == 2 || val == 4 { if val == 2 || val == 4 {
return cx.render(rsx!(())); return None;
} }
let name = cx.use_hook(|| String::from("numbers: ")); let name = cx.use_hook(|| String::from("numbers: "));
@@ -73,7 +73,7 @@ fn memo_works_properly() {
let val = cx.generation(); let val = cx.generation();
if val == 2 || val == 4 { if val == 2 || val == 4 {
return cx.render(rsx!(())); return None;
} }
let name = cx.use_hook(|| String::from("asd")); let name = cx.use_hook(|| String::from("asd"));
+1 -1
View File
@@ -25,7 +25,7 @@ async fn it_works() {
}); });
}); });
cx.render(rsx!(())) None
} }
let mut dom = VirtualDom::new(app); let mut dom = VirtualDom::new(app);
+1 -1
View File
@@ -89,7 +89,7 @@ fn check_html_renders(cx: Scope) -> Element {
h1 { h1 {
"text" "text"
} }
dyn_element {dyn_element}
} }
} }
} }
@@ -71,7 +71,7 @@ fn app(cx: Scope) -> Element {
"Hover, click, type or scroll to see the info down below" "Hover, click, type or scroll to see the info down below"
} }
div { width: "80%", height: "50%", flex_direction: "column", events_rendered } div { width: "80%", height: "50%", flex_direction: "column", {events_rendered} }
} }
}) })
} }
+23 -33
View File
@@ -27,26 +27,25 @@ fn Button(cx: Scope<ButtonProps>) -> Element {
height: "100%", height: "100%",
background_color: "{color}", background_color: "{color}",
tabindex: "{cx.props.layer}", tabindex: "{cx.props.layer}",
onkeydown: |e| { onkeydown: move |e| {
if let Code::Space = e.inner().code() { if let Code::Space = e.inner().code() {
toggle.modify(|f| !f); toggle.modify(|f| !f);
} }
}, },
onclick: |_| { onclick: move |_| {
toggle.modify(|f| !f); toggle.modify(|f| !f);
}, },
onmouseenter: |_|{ onmouseenter: move |_| {
hovered.set(true); hovered.set(true);
}, },
onmouseleave: |_|{ onmouseleave: move |_|{
hovered.set(false); hovered.set(false);
}, },
justify_content: "center", justify_content: "center",
align_items: "center", align_items: "center",
display: "flex", display: "flex",
flex_direction: "column", flex_direction: "column",
p{ "tabindex: {cx.props.layer}" }
p{"tabindex: {cx.props.layer}"}
} }
}) })
} }
@@ -58,37 +57,28 @@ fn app(cx: Scope) -> Element {
flex_direction: "column", flex_direction: "column",
width: "100%", width: "100%",
height: "100%", height: "100%",
for y in 1..8 {
(1..8).map(|y| div {
rsx!{ display: "flex",
div{ flex_direction: "row",
display: "flex", width: "100%",
flex_direction: "row", height: "100%",
width: "100%", for x in 1..8 {
height: "100%", if (x + y) % 2 == 0 {
(1..8).map(|x|{ div {
if (x + y) % 2 == 0{ width: "100%",
rsx!{ height: "100%",
div{ background_color: "rgb(100, 100, 100)",
width: "100%",
height: "100%",
background_color: "rgb(100, 100, 100)",
}
}
} }
else{ } else {
let layer = (x + y) % 3; Button {
rsx!{ color_offset: x * y,
Button{ layer: ((x + y) % 3) as u16,
color_offset: x * y,
layer: layer as u16,
}
}
} }
}) }
} }
} }
) }
} }
}) })
} }
+15 -22
View File
@@ -14,32 +14,25 @@ fn app(cx: Scope) -> Element {
width: "100%", width: "100%",
height: "100%", height: "100%",
flex_direction: "column", flex_direction: "column",
(0..=steps).map(|x| for x in 0..=steps {
{ div { width: "100%", height: "100%", flex_direction: "row",
let hue = x as f32*360.0/steps as f32; for y in 0..=steps {
rsx! { {
div{ let hue = x as f32*360.0/steps as f32;
width: "100%", let alpha = y as f32*100.0/steps as f32;
height: "100%", rsx! {
flex_direction: "row", div {
(0..=steps).map(|y| left: "{x}px",
{ top: "{y}px",
let alpha = y as f32*100.0/steps as f32; width: "10%",
rsx! { height: "100%",
div { background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
left: "{x}px",
top: "{y}px",
width: "10%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
}
}
} }
) }
} }
} }
} }
) }
} }
}) })
} }
+2 -2
View File
@@ -19,9 +19,9 @@ fn app(cx: Scope) -> Element {
ul { ul {
flex_direction: "column", flex_direction: "column",
padding_left: "3px", padding_left: "3px",
(0..10).map(|i| rsx!( for i in 0..10 {
"> hello {i}" "> hello {i}"
)) }
} }
} }
}) })
@@ -71,32 +71,28 @@ fn Grid(cx: Scope<GridProps>) -> Element {
width: "100%", width: "100%",
height: "100%", height: "100%",
flex_direction: "column", flex_direction: "column",
(0..size).map(|x| for x in 0..size {
{ div{
rsx! { width: "100%",
div{ height: "100%",
width: "100%", flex_direction: "row",
height: "100%", for y in 0..size {
flex_direction: "row", {
(0..size).map(|y| let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
{ let key = format!("{}-{}", x, y);
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32; rsx! {
let key = format!("{}-{}", x, y); Box {
rsx! { x: x,
Box { y: y,
x: x, alpha: 100.0,
y: y, hue: alpha,
alpha: 100.0, key: "{key}",
hue: alpha,
key: "{key}",
}
}
} }
) }
} }
} }
} }
) }
} }
} }
} }
+6 -4
View File
@@ -17,10 +17,12 @@ fn app(cx: Scope) -> Element {
cx.render(rsx! ( cx.render(rsx! (
table { table {
tbody { tbody {
(0..10_000_usize).map(|f| { for f in 0..10_000_usize {
let label = Label::new(&mut rng); table_row {
rsx!( table_row { row_id: f, label: label } ) row_id: f,
}) label: Label::new(&mut rng)
}
}
} }
} }
)) ))
@@ -306,15 +306,10 @@ fn persist_removes() {
_ => unreachable!(), _ => unreachable!(),
}; };
render!( render!(
div{ div {
(0..children).map(|i|{ for i in 0..children {
rsx!{ p { key: "{i}", "{i}" }
p{ }
key: "{i}",
"{i}"
}
}
})
} }
) )
} }
@@ -387,15 +382,10 @@ fn persist_instertions_before() {
_ => unreachable!(), _ => unreachable!(),
}; };
render!( render!(
div{ div {
(0..children).map(|i|{ for i in 0..children {
rsx!{ p { key: "{i}", "{i}" }
p{ }
key: "{i}",
"{i}"
}
}
})
} }
) )
} }
@@ -446,14 +436,9 @@ fn persist_instertions_after() {
}; };
render!( render!(
div{ div{
(0..children).map(|i|{ for i in 0..children {
rsx!{ p { key: "{i}", "{i}" }
p{ }
key: "{i}",
"{i}"
}
}
})
} }
) )
} }
+1 -1
View File
@@ -103,7 +103,7 @@ fn Route1(cx: Scope, user_id: usize, dynamic: usize, query: String, extra: Strin
fn Route2(cx: Scope, user_id: usize) -> Element { fn Route2(cx: Scope, user_id: usize) -> Element {
render! { render! {
pre { "Route2{{\n\tuser_id:{user_id}\n}}" } pre { "Route2{{\n\tuser_id:{user_id}\n}}" }
(0..*user_id).map(|i| rsx!{ p { "{i}" } }), {(0..*user_id).map(|i| rsx!{ p { "{i}" } })},
p { "Footer" } p { "Footer" }
Link { Link {
to: Route::Route3 { to: Route::Route3 {
@@ -77,7 +77,7 @@ pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
disabled: "{disabled}", disabled: "{disabled}",
prevent_default: "onclick", prevent_default: "onclick",
onclick: move |_| router.go_back(), onclick: move |_| router.go_back(),
children {children}
} }
} }
} }
@@ -149,7 +149,7 @@ pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
disabled: "{disabled}", disabled: "{disabled}",
prevent_default: "onclick", prevent_default: "onclick",
onclick: move |_| router.go_forward(), onclick: move |_| router.go_forward(),
children {children}
} }
} }
} }
+2 -2
View File
@@ -167,7 +167,7 @@ impl Debug for LinkProps<'_> {
/// new_tab: true, /// new_tab: true,
/// rel: "link_rel", /// rel: "link_rel",
/// to: Route::Index {}, /// to: Route::Index {},
/// ///
/// "A fully configured link" /// "A fully configured link"
/// } /// }
/// } /// }
@@ -250,7 +250,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
id: "{id}", id: "{id}",
rel: "{rel}", rel: "{rel}",
target: "{tag_target}", target: "{tag_target}",
children {children}
} }
} }
} }
+115 -66
View File
@@ -6,7 +6,8 @@ use syn::{
braced, braced,
parse::{Parse, ParseStream}, parse::{Parse, ParseStream},
spanned::Spanned, spanned::Spanned,
token, Expr, ExprIf, LitStr, Pat, Result, token::{self, Brace},
Expr, ExprIf, LitStr, Pat, Result,
}; };
/* /*
@@ -15,14 +16,14 @@ Parse
-> Component {} -> Component {}
-> component() -> component()
-> "text {with_args}" -> "text {with_args}"
-> (0..10).map(|f| rsx!("asd")), // <--- notice the comma - must be a complete expr -> {(0..10).map(|f| rsx!("asd"))} // <--- notice the curly braces
*/ */
#[derive(PartialEq, Eq, Clone, Debug, Hash)] #[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub enum BodyNode { pub enum BodyNode {
Element(Element), Element(Element),
Component(Component), Component(Component),
ForLoop(ForLoop), ForLoop(ForLoop),
IfChain(ExprIf), IfChain(IfChain),
Text(IfmtInput), Text(IfmtInput),
RawExpr(Expr), RawExpr(Expr),
} }
@@ -112,7 +113,27 @@ impl Parse for BodyNode {
return Ok(BodyNode::IfChain(stream.parse()?)); return Ok(BodyNode::IfChain(stream.parse()?));
} }
Ok(BodyNode::RawExpr(stream.parse::<Expr>()?)) // Match statements are special but have no special arm syntax
// we could allow arm syntax if we wanted
//
// ```
// match {
// val => div {}
// other_val => div {}
// }
// ```
if stream.peek(Token![match]) {
return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
}
if stream.peek(token::Brace) {
return Ok(BodyNode::RawExpr(stream.parse::<Expr>()?));
}
Err(syn::Error::new(
stream.span(),
"Expected a valid body node.\nExpressions must be wrapped in curly braces.",
))
} }
} }
@@ -151,71 +172,52 @@ impl ToTokens for BodyNode {
}) })
} }
BodyNode::IfChain(chain) => { BodyNode::IfChain(chain) => {
if is_if_chain_terminated(chain) { let mut body = TokenStream2::new();
tokens.append_all(quote! { let mut terminated = false;
{
let ___nodes = (#chain).into_dyn_node(__cx); let mut elif = Some(chain);
___nodes
} while let Some(chain) = elif {
}); let IfChain {
} else { if_token,
let ExprIf {
cond, cond,
then_branch, then_branch,
else_if_branch,
else_branch, else_branch,
..
} = chain; } = chain;
let mut body = TokenStream2::new(); let mut renderer: TemplateRenderer = TemplateRenderer {
roots: then_branch,
location: None,
};
body.append_all(quote! { body.append_all(quote! { #if_token #cond { Some({#renderer}) } });
if #cond {
Some(#then_branch)
}
});
let mut elif = else_branch; if let Some(next) = else_if_branch {
body.append_all(quote! { else });
while let Some((_, ref branch)) = elif { elif = Some(next);
match branch.as_ref() { } else if let Some(else_branch) = else_branch {
Expr::If(ref eelif) => { renderer.roots = else_branch;
let ExprIf { body.append_all(quote! { else { Some({#renderer}) } });
cond, terminated = true;
then_branch, break;
else_branch, } else {
.. elif = None;
} = eelif;
body.append_all(quote! {
else if #cond {
Some(#then_branch)
}
});
elif = else_branch;
}
_ => {
body.append_all(quote! {
else {
#branch
}
});
break;
}
}
} }
}
if !terminated {
body.append_all(quote! { body.append_all(quote! {
else { None } else { None }
}); });
tokens.append_all(quote! {
{
let ___nodes = (#body).into_dyn_node(__cx);
___nodes
}
});
} }
tokens.append_all(quote! {
{
let ___nodes = (#body).into_dyn_node(__cx);
___nodes
}
});
} }
} }
} }
@@ -240,26 +242,73 @@ impl Parse for ForLoop {
let in_token: Token![in] = input.parse()?; let in_token: Token![in] = input.parse()?;
let expr: Expr = input.call(Expr::parse_without_eager_brace)?; let expr: Expr = input.call(Expr::parse_without_eager_brace)?;
let content; let (brace_token, body) = parse_buffer_as_braced_children(input)?;
let brace_token = braced!(content in input);
let mut children = vec![];
while !content.is_empty() {
children.push(content.parse()?);
}
Ok(Self { Ok(Self {
for_token, for_token,
pat, pat,
in_token, in_token,
body: children, body,
expr: Box::new(expr),
brace_token, brace_token,
expr: Box::new(expr),
}) })
} }
} }
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct IfChain {
pub if_token: Token![if],
pub cond: Box<Expr>,
pub then_branch: Vec<BodyNode>,
pub else_if_branch: Option<Box<IfChain>>,
pub else_branch: Option<Vec<BodyNode>>,
}
impl Parse for IfChain {
fn parse(input: ParseStream) -> Result<Self> {
let if_token: Token![if] = input.parse()?;
// stolen from ExprIf
let cond = Box::new(input.call(Expr::parse_without_eager_brace)?);
let (_, then_branch) = parse_buffer_as_braced_children(input)?;
let mut else_branch = None;
let mut else_if_branch = None;
// if the next token is `else`, set the else branch as the next if chain
if input.peek(Token![else]) {
input.parse::<Token![else]>()?;
if input.peek(Token![if]) {
else_if_branch = Some(Box::new(input.parse::<IfChain>()?));
} else {
let (_, else_branch_nodes) = parse_buffer_as_braced_children(input)?;
else_branch = Some(else_branch_nodes);
}
}
Ok(Self {
cond,
if_token,
then_branch,
else_if_branch,
else_branch,
})
}
}
fn parse_buffer_as_braced_children(
input: &syn::parse::ParseBuffer<'_>,
) -> Result<(Brace, Vec<BodyNode>)> {
let content;
let brace_token = braced!(content in input);
let mut then_branch = vec![];
while !content.is_empty() {
then_branch.push(content.parse()?);
}
Ok((brace_token, then_branch))
}
pub(crate) fn is_if_chain_terminated(chain: &ExprIf) -> bool { pub(crate) fn is_if_chain_terminated(chain: &ExprIf) -> bool {
let mut current = chain; let mut current = chain;
loop { loop {
+3 -1
View File
@@ -242,7 +242,9 @@ fn to_string_works() {
div {} div {}
div { "nest 2" } div { "nest 2" }
"{dyn2}" "{dyn2}"
(0..5).map(|i| rsx! { div { "finalize {i}" } }) for i in (0..5) {
div { "finalize {i}" }
}
} }
} }
} }
+3 -7
View File
@@ -70,7 +70,7 @@ fn text_nodes() {
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let dynamic_text = "hello"; let dynamic_text = "hello";
render! { render! {
div { dynamic_text } div { {dynamic_text} }
} }
} }
@@ -124,7 +124,7 @@ fn components_hydrate() {
fn Child2(cx: Scope) -> Element { fn Child2(cx: Scope) -> Element {
let dyn_text = "hello"; let dyn_text = "hello";
render! { render! {
div { dyn_text } div { {dyn_text} }
} }
} }
@@ -159,11 +159,7 @@ fn components_hydrate() {
fn Child4(cx: Scope) -> Element { fn Child4(cx: Scope) -> Element {
render! { render! {
for _ in 0..2 { for _ in 0..2 {
render! { {render! { "{1}" }}
render! {
"{1}"
}
}
} }
} }
} }
+7 -5
View File
@@ -23,9 +23,9 @@ fn lists() {
assert_eq!( assert_eq!(
dioxus_ssr::render_lazy(rsx! { dioxus_ssr::render_lazy(rsx! {
ul { ul {
(0..5).map(|i| rsx! { for i in 0..5 {
li { "item {i}" } li { "item {i}" }
}) }
} }
}), }),
"<ul><li>item 0</li><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li></ul>" "<ul><li>item 0</li><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li></ul>"
@@ -53,9 +53,9 @@ fn components() {
assert_eq!( assert_eq!(
dioxus_ssr::render_lazy(rsx! { dioxus_ssr::render_lazy(rsx! {
div { div {
(0..5).map(|name| rsx! { for name in 0..5 {
MyComponent { name: name } MyComponent { name: name }
}) }
} }
}), }),
"<div><div>component 0</div><div>component 1</div><div>component 2</div><div>component 3</div><div>component 4</div></div>" "<div><div>component 0</div><div>component 1</div><div>component 2</div><div>component 3</div><div>component 4</div></div>"
@@ -67,7 +67,9 @@ fn fragments() {
assert_eq!( assert_eq!(
dioxus_ssr::render_lazy(rsx! { dioxus_ssr::render_lazy(rsx! {
div { div {
(0..5).map(|_| rsx! (())) for _ in 0..5 {
{()}
}
} }
}), }),
"<div></div>" "<div></div>"
+2 -2
View File
@@ -12,11 +12,11 @@ fn app(cx: Scope) -> Element {
"asd" "asd"
Bapp {} Bapp {}
} }
(0..10).map(|f| rsx!{ {(0..10).map(|f| rsx!{
div { div {
"thing {f}" "thing {f}"
} }
}) })}
}) })
} }
+5 -2
View File
@@ -24,8 +24,11 @@ fn app(cx: Scope) -> Element {
start(); start();
*count.write() += 1; *count.write() += 1;
}, },
// format is needed as {count} does not seemed to work in `if` within content if **started {
if **started { format!("Current score: {}", count.write()) } else { "Start".to_string() } "Current score: {count.read()}"
} else {
"Start"
}
} }
}) })
} }
+1 -1
View File
@@ -43,7 +43,7 @@ fn rehydrates() {
}, },
"listener test" "listener test"
} }
false.then(|| rsx!{ "hello" }) {false.then(|| rsx!{ "hello" })}
} }
}) })
} }