mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Merge pull request #1810 from DioxusLabs/jk/disambiguate-exprs-in-rsx
Disambiguate if expressions in rsx by requiring curlies, allow shorthand component/element initialization
This commit is contained in:
commit
f7bf156422
81 changed files with 868 additions and 649 deletions
|
@ -76,7 +76,11 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
"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:?}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
|
|
|
@ -58,13 +58,13 @@ fn app(cx: Scope) -> Element {
|
|||
};
|
||||
|
||||
cx.render(rsx!(
|
||||
style { include_str!("./assets/calculator.css") }
|
||||
style { {include_str!("./assets/calculator.css")} }
|
||||
div { id: "wrapper",
|
||||
div { class: "app",
|
||||
div { class: "calculator",
|
||||
tabindex: "0",
|
||||
onkeydown: handle_key_down_event,
|
||||
div { class: "calculator-display", val.to_string() }
|
||||
div { class: "calculator-display", "{val}" }
|
||||
div { class: "calculator-keypad",
|
||||
div { class: "input-keys",
|
||||
div { class: "function-keys",
|
||||
|
@ -103,14 +103,14 @@ fn app(cx: Scope) -> Element {
|
|||
div { class: "digit-keys",
|
||||
button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" }
|
||||
button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" }
|
||||
(1..10).map(|k| rsx!{
|
||||
for k in 1..10 {
|
||||
button {
|
||||
class: "calculator-key {k}",
|
||||
name: "key-{k}",
|
||||
onclick: move |_| input_digit(k),
|
||||
"{k}"
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
div { class: "operator-keys",
|
||||
|
|
|
@ -32,12 +32,12 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
ul {
|
||||
emails_sent.read().iter().map(|message| cx.render(rsx! {
|
||||
for message in emails_sent.read().iter() {
|
||||
li {
|
||||
h3 { "email" }
|
||||
span {"{message}"}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -15,8 +15,8 @@ fn app(cx: Scope) -> Element {
|
|||
if *running.current() {
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
if let Some(element) = elements.read().get(focused) {
|
||||
element.set_focus(true);
|
||||
if let Some(element) = elements.with(|f| f.get(focused).cloned()) {
|
||||
_ = element.set_focus(true).await;
|
||||
} else {
|
||||
focused = 0;
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ fn ClientList(cx: Scope) -> Element {
|
|||
Link { to: Route::ClientAdd {}, class: "pure-button pure-button-primary", "Add Client" }
|
||||
Link { to: Route::Settings {}, class: "pure-button", "Settings" }
|
||||
|
||||
clients.read().iter().map(|client| rsx! {
|
||||
for client in clients.read().iter() {
|
||||
div {
|
||||
class: "client",
|
||||
style: "margin-bottom: 50px",
|
||||
|
@ -70,7 +70,7 @@ fn ClientList(cx: Scope) -> Element {
|
|||
p { "Name: {client.first_name} {client.last_name}" }
|
||||
p { "Description: {client.description}" }
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -14,9 +14,9 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
render! {
|
||||
(0..count).map(|_| rsx!{
|
||||
for _ in 0..count {
|
||||
drop_child {}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,9 @@ fn app(cx: Scope) -> Element {
|
|||
response.respond(Response::new(include_bytes!("./assets/logo.png").to_vec()));
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
render! {
|
||||
div {
|
||||
img {
|
||||
src: "/logos/logo.png"
|
||||
}
|
||||
img { src: "/logos/logo.png" }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,7 @@ fn DemoC(cx: Scope, x: i32) -> Element {
|
|||
|
||||
result.throw()?;
|
||||
|
||||
cx.render(rsx! {
|
||||
h1 {
|
||||
"{x}"
|
||||
}
|
||||
})
|
||||
render! {
|
||||
h1 { "{x}" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@ fn ChildWithRef(cx: Scope) -> Element {
|
|||
cx.render(rsx! {
|
||||
div {
|
||||
ul {
|
||||
names.read().iter().map(|f| rsx!{
|
||||
li { "hello: {f}" }
|
||||
})
|
||||
for name in names.read().iter() {
|
||||
li { "hello: {name}" }
|
||||
}
|
||||
}
|
||||
button {
|
||||
onclick: move |_| {
|
||||
|
|
|
@ -28,39 +28,36 @@ fn app(cx: Scope) -> Element {
|
|||
link { href:"https://fonts.googleapis.com/icon?family=Material+Icons", rel:"stylesheet", }
|
||||
header {
|
||||
i { class: "material-icons icon-menu", "menu" }
|
||||
h1 { "Files: ", files.read().current() }
|
||||
h1 { "Files: ", {files.read().current()} }
|
||||
span { }
|
||||
i { class: "material-icons", onclick: move |_| files.write().go_up(), "logout" }
|
||||
}
|
||||
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 icon_type = if path_end.contains('.') {
|
||||
"description"
|
||||
} else {
|
||||
"folder"
|
||||
};
|
||||
rsx! (
|
||||
div {
|
||||
class: "folder",
|
||||
key: "{path}",
|
||||
i { class: "material-icons",
|
||||
onclick: move |_| files.write().enter_dir(dir_id),
|
||||
"{icon_type}"
|
||||
if path_end.contains('.') {
|
||||
"description"
|
||||
} else {
|
||||
"folder"
|
||||
}
|
||||
p { class: "cooltip", "0 folders / 0 files" }
|
||||
}
|
||||
h1 { "{path_end}" }
|
||||
}
|
||||
)
|
||||
}),
|
||||
files.read().err.as_ref().map(|err| {
|
||||
rsx! (
|
||||
div {
|
||||
code { "{err}" }
|
||||
button { onclick: move |_| files.write().clear_err(), "x" }
|
||||
}
|
||||
)
|
||||
})
|
||||
})},
|
||||
if let Some(err) = files.read().err.as_ref() {
|
||||
div {
|
||||
code { "{err}" }
|
||||
button { onclick: move |_| files.write().clear_err(), "x" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -66,9 +66,9 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
table {
|
||||
tbody {
|
||||
items.read().iter().enumerate().map(|(id, item)| {
|
||||
let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
|
||||
rsx!(tr { class: "{is_in_danger}",
|
||||
for (id, item) in items.read().iter().enumerate() {
|
||||
tr {
|
||||
class: if (*selected).map(|s| s == id).unwrap_or(false) { "danger" },
|
||||
td { class:"col-md-1" }
|
||||
td { class:"col-md-1", "{item.key}" }
|
||||
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" }
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
|
||||
|
|
|
@ -7,9 +7,9 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! { generic_child {
|
||||
data: 0i32
|
||||
} })
|
||||
render! {
|
||||
generic_child { data: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
|
@ -18,9 +18,7 @@ struct GenericChildProps<T: Display + PartialEq> {
|
|||
}
|
||||
|
||||
fn generic_child<T: Display + PartialEq>(cx: Scope<GenericChildProps<T>>) -> Element {
|
||||
let data = &cx.props.data;
|
||||
|
||||
cx.render(rsx! { div {
|
||||
"{data}"
|
||||
} })
|
||||
render! {
|
||||
div { "{&cx.props.data}" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! (
|
||||
render! {
|
||||
div { "Hello, world!" }
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ const FIELDS: &[(&str, &str)] = &[
|
|||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { margin_left: "30px",
|
||||
select_example(cx),
|
||||
{select_example(cx)},
|
||||
div {
|
||||
// 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)
|
||||
|
@ -114,7 +114,7 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
}
|
||||
|
||||
FIELDS.iter().map(|(field, value)| rsx! {
|
||||
for (field, value) in FIELDS.iter() {
|
||||
div {
|
||||
input {
|
||||
id: "{field}",
|
||||
|
@ -131,7 +131,7 @@ fn app(cx: Scope) -> Element {
|
|||
}
|
||||
br {}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ pub fn LogOut(cx: Scope<ClientProps>) -> Element {
|
|||
}
|
||||
Err(error) => {
|
||||
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) => {
|
||||
log::info! {"Failed to load client: {:?}", error};
|
||||
rsx! {
|
||||
div { format!{"Failed to load client: {:?}", error} }
|
||||
log::info!{"Failed to load client: {:?}", error},
|
||||
div { "Failed to load client: {error:?}" }
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ pub fn AuthHeader(cx: Scope) -> Element {
|
|||
Ok(email) => {
|
||||
rsx! {
|
||||
div {
|
||||
div { email }
|
||||
div { {email} }
|
||||
LogOut { client_id: client_props.client_id, client: client_props.client }
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
|
@ -207,7 +207,7 @@ pub fn AuthHeader(cx: Scope) -> Element {
|
|||
log::info!("Other issue with token");
|
||||
rsx! {
|
||||
div {
|
||||
div { error.to_string() }
|
||||
div { "{error}" }
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ use dioxus::prelude::*;
|
|||
|
||||
#[component]
|
||||
pub fn NotFound(cx: Scope, route: Vec<String>) -> Element {
|
||||
let routes = route.join("");
|
||||
render! {rsx! {div{routes}}}
|
||||
render! {
|
||||
div{
|
||||
{route.join("")}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,15 +35,17 @@ fn main() {
|
|||
dioxus_desktop::launch_cfg(app, cfg);
|
||||
}
|
||||
|
||||
const STYLE: &str = include_str!("./assets/calculator.css");
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let state = use_ref(cx, Calculator::new);
|
||||
|
||||
cx.render(rsx! {
|
||||
style { include_str!("./assets/calculator.css") }
|
||||
style { {STYLE} }
|
||||
div { id: "wrapper",
|
||||
div { class: "app",
|
||||
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: "input-keys",
|
||||
div { class: "function-keys",
|
||||
|
@ -74,14 +76,14 @@ fn app(cx: Scope) -> Element {
|
|||
onclick: move |_| state.write().input_dot(),
|
||||
"●"
|
||||
}
|
||||
(1..10).map(move |k| rsx!{
|
||||
for k in 1..10 {
|
||||
CalculatorKey {
|
||||
key: "{k}",
|
||||
name: "key-{k}",
|
||||
onclick: move |_| state.write().input_digit(k),
|
||||
"{k}"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
div { class: "operator-keys",
|
||||
|
@ -130,7 +132,7 @@ fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
|
|||
button {
|
||||
class: "calculator-key {cx.props.name}",
|
||||
onclick: move |e| cx.props.onclick.call(e),
|
||||
&cx.props.children
|
||||
{&cx.props.children}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ fn app(cx: Scope) -> Element {
|
|||
cx.render(rsx!(
|
||||
div {
|
||||
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),
|
||||
"Pause"
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ impl FromQuery for ManualBlogQuerySegments {
|
|||
fn BlogPost(cx: Scope, query_params: ManualBlogQuerySegments) -> Element {
|
||||
render! {
|
||||
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 {
|
||||
render! {
|
||||
div{"This is your blogpost with a query segment:"}
|
||||
div{format!("name={}&surname={}", name, surname)}
|
||||
div{ "name={name}&surname={surname}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,17 +36,17 @@ fn example(cx: Scope) -> Element {
|
|||
div { id: "asd",
|
||||
"your neighborhood spiderman"
|
||||
|
||||
items.iter().cycle().take(5).map(|f| rsx!{
|
||||
div { "{f.a}" }
|
||||
})
|
||||
for item in items.iter().cycle().take(5) {
|
||||
div { "{item.a}" }
|
||||
}
|
||||
|
||||
things_list.iter().map(|f| rsx!{
|
||||
div { "{f.a}" "{f.b}" }
|
||||
})
|
||||
for thing in things_list.iter() {
|
||||
div { "{thing.a}" "{thing.b}" }
|
||||
}
|
||||
|
||||
mything_read.as_ref().map(|f| rsx! {
|
||||
if let Some(f) = mything_read.as_ref() {
|
||||
div { "{f}" }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
|
|
|
@ -61,7 +61,7 @@ fn App(cx: Scope) -> Element {
|
|||
h1 {"Some text"}
|
||||
h1 {"Some text with {formatting}"}
|
||||
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 {
|
||||
"Multiple"
|
||||
"Text"
|
||||
|
@ -94,10 +94,10 @@ fn App(cx: Scope) -> Element {
|
|||
}
|
||||
|
||||
// Expressions can be used in element position too:
|
||||
rsx!(p { "More templating!" }),
|
||||
{rsx!(p { "More templating!" })},
|
||||
|
||||
// Iterators
|
||||
(0..10).map(|i| rsx!(li { "{i}" })),
|
||||
{(0..10).map(|i| rsx!(li { "{i}" }))},
|
||||
|
||||
// Iterators within expressions
|
||||
{
|
||||
|
@ -117,24 +117,25 @@ fn App(cx: Scope) -> Element {
|
|||
// Conditional rendering
|
||||
// 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
|
||||
true.then(|| rsx!(div {})),
|
||||
{true.then(|| rsx!(div {}))},
|
||||
|
||||
// Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
|
||||
if false {
|
||||
rsx!(h1 {"Top text"})
|
||||
h1 {"Top text"}
|
||||
} else {
|
||||
rsx!(h1 {"Bottom text"})
|
||||
h1 {"Bottom text"}
|
||||
}
|
||||
|
||||
// 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"}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}}
|
||||
|
||||
// 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
|
||||
Fragment {}
|
||||
|
@ -169,13 +170,13 @@ fn App(cx: Scope) -> Element {
|
|||
|
||||
// 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 })
|
||||
}
|
||||
|
||||
// Spreading can also be overridden manually
|
||||
Taller {
|
||||
..TallerProps { a: "ballin!", children: cx.render(rsx!(()) )},
|
||||
..TallerProps { a: "ballin!", children: None },
|
||||
a: "not ballin!"
|
||||
}
|
||||
|
||||
|
@ -204,16 +205,16 @@ fn App(cx: Scope) -> Element {
|
|||
|
||||
// helper functions
|
||||
// Anything that implements IntoVnode can be dropped directly into Rsx
|
||||
helper(cx, "hello world!")
|
||||
{helper(cx, "hello world!")}
|
||||
|
||||
// Strings can be supplied directly
|
||||
String::from("Hello world!")
|
||||
{String::from("Hello world!")}
|
||||
|
||||
// So can format_args
|
||||
format_args!("Hello {}!", "world")
|
||||
{format_args!("Hello {}!", "world")}
|
||||
|
||||
// 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]
|
||||
pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
|
||||
cx.render(rsx! {
|
||||
&cx.props.children
|
||||
{&cx.props.children}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,10 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
button {
|
||||
onclick: move |_| {
|
||||
if let Some(header) = header_element.read().as_ref() {
|
||||
header.scroll_to(ScrollBehavior::Smooth);
|
||||
if let Some(header) = header_element.read().as_ref().cloned() {
|
||||
cx.spawn(async move {
|
||||
let _ = header.scroll_to(ScrollBehavior::Smooth).await;
|
||||
});
|
||||
}
|
||||
},
|
||||
"Scroll to top"
|
||||
|
|
|
@ -51,26 +51,23 @@ pub fn App(cx: Scope) -> Element {
|
|||
|
||||
#[component]
|
||||
fn DataEditor(cx: Scope, id: usize) -> Element {
|
||||
let cool_data = use_shared_state::<CoolData>(cx).unwrap().read();
|
||||
let data = use_shared_state::<CoolData>(cx)?;
|
||||
|
||||
let my_data = &cool_data.view(id).unwrap();
|
||||
|
||||
render!(p {
|
||||
"{my_data}"
|
||||
})
|
||||
render! {
|
||||
p {
|
||||
{data.read().view(id)?}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn DataView(cx: Scope, id: usize) -> Element {
|
||||
let cool_data = use_shared_state::<CoolData>(cx).unwrap();
|
||||
let data = use_shared_state::<CoolData>(cx)?;
|
||||
|
||||
let oninput = |e: FormEvent| cool_data.write().set(*id, e.value());
|
||||
|
||||
let cool_data = cool_data.read();
|
||||
let my_data = &cool_data.view(id).unwrap();
|
||||
|
||||
render!(input {
|
||||
oninput: oninput,
|
||||
value: "{my_data}"
|
||||
})
|
||||
render! {
|
||||
input {
|
||||
oninput: move |e: FormEvent| data.write().set(*id, e.value()),
|
||||
value: data.read().view(id)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
45
examples/shorthand.rs
Normal file
45
examples/shorthand.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let a = 123;
|
||||
let b = 456;
|
||||
let c = 789;
|
||||
let class = "class";
|
||||
let id = "id";
|
||||
|
||||
// todo: i'd like it for children on elements to be inferred as the children of the element
|
||||
// also should shorthands understand references/dereferences?
|
||||
// ie **a, *a, &a, &mut a, etc
|
||||
let children = render! { "Child" };
|
||||
let onclick = move |_| println!("Clicked!");
|
||||
|
||||
render! {
|
||||
div { class, id, {&children} }
|
||||
Component { a, b, c, children, onclick }
|
||||
Component { a, ..ComponentProps { a: 1, b: 2, c: 3, children: None, onclick: Default::default() } }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Component<'a>(
|
||||
cx: Scope<'a>,
|
||||
a: i32,
|
||||
b: i32,
|
||||
c: i32,
|
||||
children: Element<'a>,
|
||||
onclick: EventHandler<'a, ()>,
|
||||
) -> Element {
|
||||
render! {
|
||||
div { "{a}" }
|
||||
div { "{b}" }
|
||||
div { "{c}" }
|
||||
div { {children} }
|
||||
div {
|
||||
onclick: move |_| onclick.call(()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
// We can do boolean operations on the current signal value
|
||||
if count.value() > 5 {
|
||||
rsx!{ h2 { "High five!" } }
|
||||
h2 { "High five!" }
|
||||
}
|
||||
|
||||
// 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
|
||||
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 {
|
||||
rsx! { "No saved values" }
|
||||
"No saved values"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,17 +8,17 @@ fn app(cx: Scope) -> Element {
|
|||
cx.render(rsx!(
|
||||
div {
|
||||
// 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
|
||||
["a", "b", "c", "d", "e", "f"]
|
||||
{["a", "b", "c", "d", "e", "f"]
|
||||
.into_iter()
|
||||
.map(|f| rsx! { "{f}" })
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter(),
|
||||
.into_iter()},
|
||||
|
||||
// Use optionals
|
||||
Some(rsx! { "Some" }),
|
||||
{Some(rsx! { "Some" })},
|
||||
|
||||
// use a for loop where the body itself is RSX
|
||||
for name in 0..10 {
|
||||
|
@ -27,7 +27,7 @@ fn app(cx: Scope) -> Element {
|
|||
|
||||
// Or even use an unterminated conditional
|
||||
if true {
|
||||
rsx!{ "hello world!" }
|
||||
"hello world!"
|
||||
}
|
||||
}
|
||||
))
|
||||
|
|
|
@ -97,7 +97,7 @@ pub fn Die<'a>(cx: Scope<'a, DieProps<'a>>) -> Element {
|
|||
fill: "{fill}",
|
||||
}
|
||||
|
||||
dots
|
||||
{dots}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -52,30 +52,30 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||
TodoHeader { todos: todos }
|
||||
section { class: "main",
|
||||
if !todos.is_empty() {
|
||||
rsx! {
|
||||
input {
|
||||
id: "toggle-all",
|
||||
class: "toggle-all",
|
||||
r#type: "checkbox",
|
||||
onchange: move |_| {
|
||||
let check = active_todo_count != 0;
|
||||
for (_, item) in todos.make_mut().iter_mut() {
|
||||
item.checked = check;
|
||||
}
|
||||
},
|
||||
checked: if active_todo_count == 0 { "true" } else { "false" },
|
||||
}
|
||||
label { r#for: "toggle-all" }
|
||||
input {
|
||||
id: "toggle-all",
|
||||
class: "toggle-all",
|
||||
r#type: "checkbox",
|
||||
onchange: move |_| {
|
||||
let check = active_todo_count != 0;
|
||||
for (_, item) in todos.make_mut().iter_mut() {
|
||||
item.checked = check;
|
||||
}
|
||||
},
|
||||
checked: if active_todo_count == 0 { "true" } else { "false" },
|
||||
}
|
||||
label { r#for: "toggle-all" }
|
||||
}
|
||||
ul { class: "todo-list",
|
||||
filtered_todos.iter().map(|id| rsx!(TodoEntry {
|
||||
key: "{id}",
|
||||
id: *id,
|
||||
todos: todos,
|
||||
}))
|
||||
for id in filtered_todos.iter() {
|
||||
TodoEntry {
|
||||
key: "{id}",
|
||||
id: *id,
|
||||
todos: todos,
|
||||
}
|
||||
}
|
||||
}
|
||||
(!todos.is_empty()).then(|| rsx!(
|
||||
if !todos.is_empty() {
|
||||
ListFooter {
|
||||
active_todo_count: active_todo_count,
|
||||
active_todo_text: active_todo_text,
|
||||
|
@ -83,7 +83,7 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||
todos: todos,
|
||||
filter: filter,
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
PageFooter {}
|
||||
|
@ -172,7 +172,7 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
|||
prevent_default: "onclick"
|
||||
}
|
||||
}
|
||||
is_editing.then(|| rsx!{
|
||||
if **is_editing {
|
||||
input {
|
||||
class: "edit",
|
||||
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",
|
||||
for (state , state_text , url) in [
|
||||
(FilterState::All, "All", "#/"),
|
||||
(FilterState::Active, "Active", "#/active"),
|
||||
(FilterState::Completed, "Completed", "#/completed"),
|
||||
] {
|
||||
(FilterState::All, "All", "#/"),
|
||||
(FilterState::Active, "Active", "#/active"),
|
||||
(FilterState::Completed, "Completed", "#/completed"),
|
||||
] {
|
||||
li {
|
||||
a {
|
||||
href: url,
|
||||
class: selected(state),
|
||||
onclick: move |_| cx.props.filter.set(state),
|
||||
prevent_default: "onclick",
|
||||
state_text
|
||||
{state_text}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if cx.props.show_clear_completed {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
class: "clear-completed",
|
||||
onclick: move |_| cx.props.todos.make_mut().retain(|_, todo| !todo.checked),
|
||||
"Clear completed"
|
||||
}
|
||||
})
|
||||
button {
|
||||
class: "clear-completed",
|
||||
onclick: move |_| cx.props.todos.make_mut().retain(|_, todo| !todo.checked),
|
||||
"Clear completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -182,6 +182,9 @@ impl Writer<'_> {
|
|||
s.source.as_ref().unwrap().to_token_stream()
|
||||
)?;
|
||||
}
|
||||
ContentField::Shorthand(e) => {
|
||||
write!(self.out, "{}", e.to_token_stream())?;
|
||||
}
|
||||
ContentField::OnHandlerRaw(exp) => {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
|
@ -223,6 +226,7 @@ impl Writer<'_> {
|
|||
.iter()
|
||||
.map(|field| match &field.content {
|
||||
ContentField::Formatted(s) => ifmt_to_string(s).len() ,
|
||||
ContentField::Shorthand(e) => e.to_token_stream().to_string().len(),
|
||||
ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
|
||||
let formatted = prettyplease::unparse_expr(exp);
|
||||
let len = if formatted.contains('\n') {
|
||||
|
|
|
@ -237,6 +237,9 @@ impl Writer<'_> {
|
|||
ElementAttrValue::AttrLiteral(value) => {
|
||||
write!(self.out, "{value}", value = ifmt_to_string(value))?;
|
||||
}
|
||||
ElementAttrValue::Shorthand(value) => {
|
||||
write!(self.out, "{value}",)?;
|
||||
}
|
||||
ElementAttrValue::AttrExpr(value) => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
|
|
|
@ -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 quote::ToTokens;
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt::{Result, Write},
|
||||
};
|
||||
use syn::{spanned::Spanned, Expr, ExprIf};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
use crate::ifmt_to_string;
|
||||
|
@ -142,6 +142,7 @@ impl<'a> Writer<'a> {
|
|||
}
|
||||
ElementAttrValue::AttrLiteral(lit) => ifmt_to_string(lit).len(),
|
||||
ElementAttrValue::AttrExpr(expr) => expr.span().line_length(),
|
||||
ElementAttrValue::Shorthand(expr) => expr.span().line_length(),
|
||||
ElementAttrValue::EventTokens(tokens) => {
|
||||
let location = Location::new(tokens.span().start());
|
||||
|
||||
|
@ -231,8 +232,49 @@ impl<'a> Writer<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn write_if_chain(&mut self, ifchain: &ExprIf) -> std::fmt::Result {
|
||||
self.write_raw_expr(ifchain.span())
|
||||
fn write_if_chain(&mut self, ifchain: &IfChain) -> std::fmt::Result {
|
||||
// Recurse in place by setting the next chain
|
||||
let mut branch = Some(ifchain);
|
||||
|
||||
while let Some(chain) = branch {
|
||||
let IfChain {
|
||||
if_token,
|
||||
cond,
|
||||
then_branch,
|
||||
else_if_branch,
|
||||
else_branch,
|
||||
} = chain;
|
||||
|
||||
write!(
|
||||
self.out,
|
||||
"{} {} {{",
|
||||
if_token.to_token_stream(),
|
||||
prettyplease::unparse_expr(cond)
|
||||
)?;
|
||||
|
||||
self.write_body_indented(then_branch)?;
|
||||
|
||||
if let Some(else_if_branch) = else_if_branch {
|
||||
// write the closing bracket and else
|
||||
self.out.tabbed_line()?;
|
||||
write!(self.out, "}} else ")?;
|
||||
|
||||
branch = Some(else_if_branch);
|
||||
} else if let Some(else_branch) = else_branch {
|
||||
self.out.tabbed_line()?;
|
||||
write!(self.out, "}} else {{")?;
|
||||
|
||||
self.write_body_indented(else_branch)?;
|
||||
branch = None;
|
||||
} else {
|
||||
branch = None;
|
||||
}
|
||||
}
|
||||
|
||||
self.out.tabbed_line()?;
|
||||
write!(self.out, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,17 +8,17 @@ rsx! {
|
|||
"hello world"
|
||||
|
||||
// Comments
|
||||
expr1,
|
||||
{expr1},
|
||||
|
||||
// Comments
|
||||
expr2,
|
||||
{expr2},
|
||||
|
||||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
expr3,
|
||||
{expr3},
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
|
|
|
@ -18,7 +18,7 @@ rsx! {
|
|||
to: "{to}",
|
||||
span { class: "inline-block mr-3", icons::icon_0 {} }
|
||||
span { "{name}" }
|
||||
children.is_some().then(|| rsx! {
|
||||
{children.is_some().then(|| rsx! {
|
||||
span {
|
||||
class: "inline-block ml-auto hover:bg-gray-500",
|
||||
onclick: move |evt| {
|
||||
|
@ -27,9 +27,9 @@ rsx! {
|
|||
},
|
||||
icons::icon_8 {}
|
||||
}
|
||||
})
|
||||
})}
|
||||
}
|
||||
div { class: "px-4", is_current.then(|| rsx!{ children }) }
|
||||
div { class: "px-4", {is_current.then(|| rsx!{ children })} }
|
||||
}
|
||||
|
||||
// No nesting
|
||||
|
@ -48,5 +48,5 @@ rsx! {
|
|||
}
|
||||
}
|
||||
|
||||
div { asdbascasdbasd, asbdasbdabsd, asbdabsdbasdbas }
|
||||
div { "asdbascasdbasd", "asbdasbdabsd", {asbdabsdbasdbas} }
|
||||
}
|
||||
|
|
|
@ -8,6 +8,20 @@ rsx! {
|
|||
// Some ifchain
|
||||
if a > 10 {
|
||||
//
|
||||
rsx! { div {} }
|
||||
div {}
|
||||
} else if a > 20 {
|
||||
h1 {}
|
||||
} else if a > 20 {
|
||||
h1 {}
|
||||
} else if a > 20 {
|
||||
h1 {}
|
||||
} else if a > 20 {
|
||||
h1 {}
|
||||
} else if a > 20 {
|
||||
h1 {}
|
||||
} else if a > 20 {
|
||||
h1 {}
|
||||
} else {
|
||||
h3 {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
fn it_works() {
|
||||
cx.render(rsx!(()))
|
||||
cx.render(rsx!({()}))
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ pub fn Explainer<'a>(
|
|||
// pt-5 sm:pt-24 lg:pt-24
|
||||
|
||||
let mut right = rsx! {
|
||||
div { class: "relative w-1/2", flasher }
|
||||
div { class: "relative w-1/2", {flasher} }
|
||||
};
|
||||
|
||||
let align = match invert {
|
||||
|
@ -24,7 +24,7 @@ pub fn Explainer<'a>(
|
|||
h2 { class: "mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold",
|
||||
"{title}"
|
||||
}
|
||||
content
|
||||
{content}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -34,8 +34,8 @@ pub fn Explainer<'a>(
|
|||
|
||||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
|
||||
left,
|
||||
right
|
||||
{left},
|
||||
{right}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ rsx! {
|
|||
section { class: "body-font overflow-hidden dark:bg-ideblack",
|
||||
div { class: "container px-6 mx-auto",
|
||||
div { class: "-my-8 divide-y-2 divide-gray-100",
|
||||
POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })
|
||||
{POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,20 +4,20 @@ rsx! {
|
|||
div {}
|
||||
|
||||
// hi
|
||||
div { abcd, ball, s }
|
||||
div { "abcd", "ball", "s" }
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
div { abcd, ball, s }
|
||||
div { "abcd", "ball", "s" }
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
div {
|
||||
abcd,
|
||||
ball,
|
||||
s,
|
||||
"abcd"
|
||||
"ball"
|
||||
"s"
|
||||
|
||||
//
|
||||
"asdasd"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
fn it_works() {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
span { "Description: ", package.description.as_deref().unwrap_or("❌❌❌❌ missing") }
|
||||
span { "Description: ", {package.description.as_deref().unwrap_or("❌❌❌❌ missing")} }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
fn ItWroks() {
|
||||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
|
||||
left,
|
||||
right
|
||||
{left},
|
||||
{right}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
fn ItWroks() {
|
||||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
fn ItWroks() {
|
||||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
|
||||
left,
|
||||
right
|
||||
{left},
|
||||
{right}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
fn ItWroks() {
|
||||
cx.render(rsx! {
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
|
||||
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -267,10 +267,10 @@ async fn test_auto_fmt() {
|
|||
let test_rsx = r#"
|
||||
//
|
||||
|
||||
rsx! {
|
||||
|
||||
|
||||
div {}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
|
|
|
@ -804,6 +804,15 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoDynNode<'_> for &String {
|
||||
fn into_dyn_node(self, cx: &ScopeState) -> DynamicNode {
|
||||
DynamicNode::Text(VText {
|
||||
value: cx.bump().alloc_str(self),
|
||||
id: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDynNode<'_> for String {
|
||||
fn into_dyn_node(self, cx: &ScopeState) -> DynamicNode {
|
||||
DynamicNode::Text(VText {
|
||||
|
@ -868,7 +877,7 @@ where
|
|||
nodes.extend(self.into_iter().map(|node| node.into_vnode(cx)));
|
||||
|
||||
match nodes.into_bump_slice() {
|
||||
children if children.is_empty() => DynamicNode::default(),
|
||||
[] => DynamicNode::default(),
|
||||
children => DynamicNode::Fragment(children),
|
||||
}
|
||||
}
|
||||
|
@ -898,6 +907,12 @@ impl<'a> IntoAttributeValue<'a> for String {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for &String {
|
||||
fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Text(cx.alloc_str(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for f64 {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Float(self)
|
||||
|
|
|
@ -117,7 +117,7 @@ impl ScopeContext {
|
|||
}
|
||||
|
||||
let mut search_parent = self.parent_id;
|
||||
match with_runtime(|runtime: &crate::runtime::Runtime| {
|
||||
let cur_runtime = with_runtime(|runtime: &crate::runtime::Runtime| {
|
||||
while let Some(parent_id) = search_parent {
|
||||
let parent = runtime.get_context(parent_id).unwrap();
|
||||
tracing::trace!(
|
||||
|
@ -135,9 +135,9 @@ impl ScopeContext {
|
|||
search_parent = parent.parent_id;
|
||||
}
|
||||
None
|
||||
})
|
||||
.flatten()
|
||||
{
|
||||
});
|
||||
|
||||
match cur_runtime.flatten() {
|
||||
Some(ctx) => Some(ctx),
|
||||
None => {
|
||||
tracing::trace!(
|
||||
|
|
|
@ -78,7 +78,7 @@ use std::{
|
|||
/// #[component]
|
||||
/// fn Title<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||
/// cx.render(rsx! {
|
||||
/// div { id: "title", children }
|
||||
/// div { id: "title", {children} }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
|
|
|
@ -84,7 +84,7 @@ fn create() {
|
|||
fn create_list() {
|
||||
let mut dom = VirtualDom::new(|cx| {
|
||||
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 {
|
||||
cx.render(rsx! {
|
||||
h1 {}
|
||||
div { &cx.props.children }
|
||||
div { {&cx.props.children} }
|
||||
p {}
|
||||
})
|
||||
}
|
||||
|
@ -163,10 +163,10 @@ fn anchors() {
|
|||
let mut dom = VirtualDom::new(|cx| {
|
||||
cx.render(rsx! {
|
||||
if true {
|
||||
rsx!( div { "hello" } )
|
||||
div { "hello" }
|
||||
}
|
||||
if false {
|
||||
rsx!( div { "goodbye" } )
|
||||
div { "goodbye" }
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ use dioxus_core::ElementId;
|
|||
#[test]
|
||||
fn empty_fragment_creates_nothing() {
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!(()))
|
||||
cx.render(rsx!({}))
|
||||
}
|
||||
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
|
@ -43,18 +43,18 @@ fn fragments_nested() {
|
|||
cx.render(rsx!(
|
||||
div { "hello" }
|
||||
div { "goodbye" }
|
||||
rsx! {
|
||||
{rsx! {
|
||||
div { "hello" }
|
||||
div { "goodbye" }
|
||||
rsx! {
|
||||
{rsx! {
|
||||
div { "hello" }
|
||||
div { "goodbye" }
|
||||
rsx! {
|
||||
{rsx! {
|
||||
div { "hello" }
|
||||
div { "goodbye" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
))
|
||||
});
|
||||
|
||||
|
@ -79,7 +79,7 @@ fn fragments_across_components() {
|
|||
let world = "world";
|
||||
cx.render(rsx! {
|
||||
"hellO!"
|
||||
world
|
||||
{world}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -94,7 +94,7 @@ fn list_fragments() {
|
|||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
h1 {"hello"}
|
||||
(0..6).map(|f| rsx!( span { "{f}" }))
|
||||
{(0..6).map(|f| rsx!( span { "{f}" }))}
|
||||
))
|
||||
}
|
||||
assert_eq!(
|
||||
|
|
|
@ -11,12 +11,12 @@ use dioxus_core::ElementId;
|
|||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
(0..3).map(|i| rsx! {
|
||||
for i in 0..3 {
|
||||
div {
|
||||
h1 { "hello world! "}
|
||||
p { "{i}" }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ fn nested_passthru_creates() {
|
|||
|
||||
#[component]
|
||||
fn PassThru<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||
cx.render(rsx!(children))
|
||||
cx.render(rsx!({ children }))
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
@ -60,7 +60,7 @@ fn nested_passthru_creates_add() {
|
|||
|
||||
#[component]
|
||||
fn ChildComp<'a>(cx: Scope, children: Element<'a>) -> Element {
|
||||
cx.render(rsx! { children })
|
||||
cx.render(rsx! { {children} })
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
|
|
@ -41,7 +41,7 @@ fn component_swap() {
|
|||
cx.render(rsx! {
|
||||
h1 {
|
||||
"NavBar"
|
||||
(0..3).map(|_| rsx!(nav_link {}))
|
||||
{(0..3).map(|_| rsx!(nav_link {}))}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ fn keyed_diffing_out_of_order() {
|
|||
_ => 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!(),
|
||||
};
|
||||
|
||||
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
|
||||
cx.render(rsx!({ order.iter().map(|i| rsx!(div { key: "{i}" })) }))
|
||||
});
|
||||
|
||||
_ = dom.rebuild();
|
||||
|
@ -85,7 +85,7 @@ fn keyed_diffing_out_of_order_adds_3() {
|
|||
_ => 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();
|
||||
|
@ -111,7 +111,7 @@ fn keyed_diffing_out_of_order_adds_4() {
|
|||
_ => 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();
|
||||
|
@ -137,7 +137,7 @@ fn keyed_diffing_out_of_order_adds_5() {
|
|||
_ => 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();
|
||||
|
@ -162,7 +162,7 @@ fn keyed_diffing_additions() {
|
|||
_ => 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();
|
||||
|
@ -187,7 +187,7 @@ fn keyed_diffing_additions_and_moves_on_ends() {
|
|||
_ => 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();
|
||||
|
@ -216,7 +216,7 @@ fn keyed_diffing_additions_and_moves_in_middle() {
|
|||
_ => 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();
|
||||
|
@ -250,7 +250,7 @@ fn controlled_keyed_diffing_out_of_order() {
|
|||
_ => 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();
|
||||
|
@ -284,7 +284,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
|
|||
_ => 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();
|
||||
|
@ -313,7 +313,7 @@ fn remove_list() {
|
|||
_ => 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();
|
||||
|
@ -338,7 +338,7 @@ fn no_common_keys() {
|
|||
_ => 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();
|
||||
|
|
|
@ -9,9 +9,9 @@ fn list_creates_one_by_one() {
|
|||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
(0..gen).map(|i| rsx! {
|
||||
for i in 0..gen {
|
||||
div { "{i}" }
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -78,9 +78,9 @@ fn removes_one_by_one() {
|
|||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
(0..gen).map(|i| rsx! {
|
||||
for i in 0..gen {
|
||||
div { "{i}" }
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -153,10 +153,10 @@ fn list_shrink_multiroot() {
|
|||
let mut dom = VirtualDom::new(|cx| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
(0..cx.generation()).map(|i| rsx! {
|
||||
for i in 0..cx.generation() {
|
||||
div { "{i}" }
|
||||
div { "{i}" }
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -214,10 +214,10 @@ fn removes_one_by_one_multiroot() {
|
|||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
(0..gen).map(|i| rsx! {
|
||||
{(0..gen).map(|i| rsx! {
|
||||
div { "{i}" }
|
||||
div { "{i}" }
|
||||
})
|
||||
})}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
@ -276,9 +276,9 @@ fn removes_one_by_one_multiroot() {
|
|||
fn two_equal_fragments_are_equal_static() {
|
||||
let mut dom = VirtualDom::new(|cx| {
|
||||
cx.render(rsx! {
|
||||
(0..5).map(|_| rsx! {
|
||||
for _ in 0..5 {
|
||||
div { "hello" }
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -290,9 +290,9 @@ fn two_equal_fragments_are_equal_static() {
|
|||
fn two_equal_fragments_are_equal() {
|
||||
let mut dom = VirtualDom::new(|cx| {
|
||||
cx.render(rsx! {
|
||||
(0..5).map(|i| rsx! {
|
||||
for i in 0..5 {
|
||||
div { "hello {i}" }
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -311,7 +311,9 @@ fn remove_many() {
|
|||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
(0..num).map(|i| rsx! { div { "hello {i}" } })
|
||||
for i in 0..num {
|
||||
div { "hello {i}" }
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
|
|
@ -58,11 +58,11 @@ fn app(cx: Scope) -> Element {
|
|||
*CLICKS.lock().unwrap() += 1;
|
||||
},
|
||||
|
||||
vec![
|
||||
{vec![
|
||||
render! {
|
||||
problematic_child {}
|
||||
}
|
||||
].into_iter()
|
||||
].into_iter()}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ fn events_generate() {
|
|||
"Click me!"
|
||||
}
|
||||
}),
|
||||
_ => cx.render(rsx!(())),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -39,9 +39,9 @@ fn App(cx: Scope) -> Element {
|
|||
}
|
||||
button { onclick: move |_| idx -= 1, "-" }
|
||||
ul {
|
||||
(0..**idx).map(|i| rsx! {
|
||||
{(0..**idx).map(|i| rsx! {
|
||||
ChildExample { i: i, onhover: onhover }
|
||||
})
|
||||
})}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -15,7 +15,7 @@ fn test_memory_leak() {
|
|||
cx.spawn(async {});
|
||||
|
||||
if val == 2 || val == 4 {
|
||||
return cx.render(rsx!(()));
|
||||
return render!({});
|
||||
}
|
||||
|
||||
let name = cx.use_hook(|| String::from("numbers: "));
|
||||
|
@ -73,7 +73,7 @@ fn memo_works_properly() {
|
|||
let val = cx.generation();
|
||||
|
||||
if val == 2 || val == 4 {
|
||||
return cx.render(rsx!(()));
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = cx.use_hook(|| String::from("asd"));
|
||||
|
|
|
@ -25,7 +25,7 @@ async fn it_works() {
|
|||
});
|
||||
});
|
||||
|
||||
cx.render(rsx!(()))
|
||||
None
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
|
|
@ -89,7 +89,7 @@ fn check_html_renders(cx: Scope) -> Element {
|
|||
h1 {
|
||||
"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"
|
||||
}
|
||||
div { width: "80%", height: "50%", flex_direction: "column", events_rendered }
|
||||
div { width: "80%", height: "50%", flex_direction: "column", {events_rendered} }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -27,26 +27,25 @@ fn Button(cx: Scope<ButtonProps>) -> Element {
|
|||
height: "100%",
|
||||
background_color: "{color}",
|
||||
tabindex: "{cx.props.layer}",
|
||||
onkeydown: |e| {
|
||||
onkeydown: move |e| {
|
||||
if let Code::Space = e.inner().code() {
|
||||
toggle.modify(|f| !f);
|
||||
}
|
||||
},
|
||||
onclick: |_| {
|
||||
onclick: move |_| {
|
||||
toggle.modify(|f| !f);
|
||||
},
|
||||
onmouseenter: |_|{
|
||||
onmouseenter: move |_| {
|
||||
hovered.set(true);
|
||||
},
|
||||
onmouseleave: |_|{
|
||||
onmouseleave: move |_|{
|
||||
hovered.set(false);
|
||||
},
|
||||
justify_content: "center",
|
||||
align_items: "center",
|
||||
display: "flex",
|
||||
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",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
|
||||
(1..8).map(|y|
|
||||
rsx!{
|
||||
div{
|
||||
display: "flex",
|
||||
flex_direction: "row",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
(1..8).map(|x|{
|
||||
if (x + y) % 2 == 0{
|
||||
rsx!{
|
||||
div{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background_color: "rgb(100, 100, 100)",
|
||||
}
|
||||
}
|
||||
for y in 1..8 {
|
||||
div {
|
||||
display: "flex",
|
||||
flex_direction: "row",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
for x in 1..8 {
|
||||
if (x + y) % 2 == 0 {
|
||||
div {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background_color: "rgb(100, 100, 100)",
|
||||
}
|
||||
else{
|
||||
let layer = (x + y) % 3;
|
||||
rsx!{
|
||||
Button{
|
||||
color_offset: x * y,
|
||||
layer: layer as u16,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button {
|
||||
color_offset: x * y,
|
||||
layer: ((x + y) % 3) as u16,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,32 +14,25 @@ fn app(cx: Scope) -> Element {
|
|||
width: "100%",
|
||||
height: "100%",
|
||||
flex_direction: "column",
|
||||
(0..=steps).map(|x|
|
||||
{
|
||||
let hue = x as f32*360.0/steps as f32;
|
||||
rsx! {
|
||||
div{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
flex_direction: "row",
|
||||
(0..=steps).map(|y|
|
||||
{
|
||||
let alpha = y as f32*100.0/steps as f32;
|
||||
rsx! {
|
||||
div {
|
||||
left: "{x}px",
|
||||
top: "{y}px",
|
||||
width: "10%",
|
||||
height: "100%",
|
||||
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
|
||||
}
|
||||
}
|
||||
for x in 0..=steps {
|
||||
div { width: "100%", height: "100%", flex_direction: "row",
|
||||
for y in 0..=steps {
|
||||
{
|
||||
let hue = x as f32*360.0/steps as f32;
|
||||
let alpha = y as f32*100.0/steps as f32;
|
||||
rsx! {
|
||||
div {
|
||||
left: "{x}px",
|
||||
top: "{y}px",
|
||||
width: "10%",
|
||||
height: "100%",
|
||||
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@ fn app(cx: Scope) -> Element {
|
|||
ul {
|
||||
flex_direction: "column",
|
||||
padding_left: "3px",
|
||||
(0..10).map(|i| rsx!(
|
||||
for i in 0..10 {
|
||||
"> hello {i}"
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -71,32 +71,28 @@ fn Grid(cx: Scope<GridProps>) -> Element {
|
|||
width: "100%",
|
||||
height: "100%",
|
||||
flex_direction: "column",
|
||||
(0..size).map(|x|
|
||||
{
|
||||
rsx! {
|
||||
div{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
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);
|
||||
rsx! {
|
||||
Box {
|
||||
x: x,
|
||||
y: y,
|
||||
alpha: 100.0,
|
||||
hue: alpha,
|
||||
key: "{key}",
|
||||
}
|
||||
}
|
||||
for x in 0..size {
|
||||
div{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
flex_direction: "row",
|
||||
for y in 0..size {
|
||||
{
|
||||
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
|
||||
let key = format!("{}-{}", x, y);
|
||||
rsx! {
|
||||
Box {
|
||||
x: x,
|
||||
y: y,
|
||||
alpha: 100.0,
|
||||
hue: alpha,
|
||||
key: "{key}",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,12 @@ fn app(cx: Scope) -> Element {
|
|||
cx.render(rsx! (
|
||||
table {
|
||||
tbody {
|
||||
(0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
rsx!( table_row { row_id: f, label: label } )
|
||||
})
|
||||
for f in 0..10_000_usize {
|
||||
table_row {
|
||||
row_id: f,
|
||||
label: Label::new(&mut rng)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
|
|
|
@ -306,15 +306,10 @@ fn persist_removes() {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
render!(
|
||||
div{
|
||||
(0..children).map(|i|{
|
||||
rsx!{
|
||||
p{
|
||||
key: "{i}",
|
||||
"{i}"
|
||||
}
|
||||
}
|
||||
})
|
||||
div {
|
||||
for i in 0..children {
|
||||
p { key: "{i}", "{i}" }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -387,15 +382,10 @@ fn persist_instertions_before() {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
render!(
|
||||
div{
|
||||
(0..children).map(|i|{
|
||||
rsx!{
|
||||
p{
|
||||
key: "{i}",
|
||||
"{i}"
|
||||
}
|
||||
}
|
||||
})
|
||||
div {
|
||||
for i in 0..children {
|
||||
p { key: "{i}", "{i}" }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -446,14 +436,9 @@ fn persist_instertions_after() {
|
|||
};
|
||||
render!(
|
||||
div{
|
||||
(0..children).map(|i|{
|
||||
rsx!{
|
||||
p{
|
||||
key: "{i}",
|
||||
"{i}"
|
||||
}
|
||||
}
|
||||
})
|
||||
for i in 0..children {
|
||||
p { key: "{i}", "{i}" }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
render! {
|
||||
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" }
|
||||
Link {
|
||||
to: Route::Route3 {
|
||||
|
|
|
@ -77,7 +77,7 @@ pub fn GoBackButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
|
|||
disabled: "{disabled}",
|
||||
prevent_default: "onclick",
|
||||
onclick: move |_| router.go_back(),
|
||||
children
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element {
|
|||
disabled: "{disabled}",
|
||||
prevent_default: "onclick",
|
||||
onclick: move |_| router.go_forward(),
|
||||
children
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ impl Debug for LinkProps<'_> {
|
|||
/// new_tab: true,
|
||||
/// rel: "link_rel",
|
||||
/// to: Route::Index {},
|
||||
///
|
||||
///
|
||||
/// "A fully configured link"
|
||||
/// }
|
||||
/// }
|
||||
|
@ -250,7 +250,7 @@ pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element {
|
|||
id: "{id}",
|
||||
rel: "{rel}",
|
||||
target: "{tag_target}",
|
||||
children
|
||||
{children}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,13 @@ impl AttributeType {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn matches_attr_name(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Named(a), Self::Named(b)) => a.attr.name == b.attr.name,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_combine(&self, other: &Self) -> Option<Self> {
|
||||
match (self, other) {
|
||||
(Self::Named(a), Self::Named(b)) => a.try_combine(b).map(Self::Named),
|
||||
|
@ -102,16 +109,25 @@ impl ToTokens for ElementAttrNamed {
|
|||
};
|
||||
|
||||
let attribute = {
|
||||
let value = &self.attr.value;
|
||||
let is_shorthand_event = match &attr.value {
|
||||
ElementAttrValue::Shorthand(s) => s.to_string().starts_with("on"),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
match &attr.value {
|
||||
ElementAttrValue::AttrLiteral(_)
|
||||
| ElementAttrValue::AttrExpr(_)
|
||||
| ElementAttrValue::AttrOptionalExpr { .. } => {
|
||||
| ElementAttrValue::Shorthand(_)
|
||||
| ElementAttrValue::AttrOptionalExpr { .. }
|
||||
if !is_shorthand_event =>
|
||||
{
|
||||
let name = &self.attr.name;
|
||||
let ns = ns(name);
|
||||
let volitile = volitile(name);
|
||||
let attribute = attribute(name);
|
||||
let value = &self.attr.value;
|
||||
let value = quote! { #value };
|
||||
|
||||
quote! {
|
||||
__cx.attr(
|
||||
#attribute,
|
||||
|
@ -123,12 +139,13 @@ impl ToTokens for ElementAttrNamed {
|
|||
}
|
||||
ElementAttrValue::EventTokens(tokens) => match &self.attr.name {
|
||||
ElementAttrName::BuiltIn(name) => {
|
||||
quote! {
|
||||
dioxus_elements::events::#name(__cx, #tokens)
|
||||
}
|
||||
quote! { dioxus_elements::events::#name(__cx, #tokens) }
|
||||
}
|
||||
ElementAttrName::Custom(_) => todo!(),
|
||||
},
|
||||
_ => {
|
||||
quote! { dioxus_elements::events::#value(__cx, #value) }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -144,6 +161,8 @@ pub struct ElementAttr {
|
|||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum ElementAttrValue {
|
||||
/// attribute,
|
||||
Shorthand(Ident),
|
||||
/// attribute: "value"
|
||||
AttrLiteral(IfmtInput),
|
||||
/// attribute: if bool { "value" }
|
||||
|
@ -159,7 +178,7 @@ pub enum ElementAttrValue {
|
|||
|
||||
impl Parse for ElementAttrValue {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
Ok(if input.peek(Token![if]) {
|
||||
let element_attr_value = if input.peek(Token![if]) {
|
||||
let if_expr = input.parse::<ExprIf>()?;
|
||||
if is_if_chain_terminated(&if_expr) {
|
||||
ElementAttrValue::AttrExpr(Expr::If(if_expr))
|
||||
|
@ -180,13 +199,16 @@ impl Parse for ElementAttrValue {
|
|||
} else {
|
||||
let value = input.parse::<Expr>()?;
|
||||
ElementAttrValue::AttrExpr(value)
|
||||
})
|
||||
};
|
||||
|
||||
Ok(element_attr_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ElementAttrValue {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self {
|
||||
ElementAttrValue::Shorthand(i) => tokens.append_all(quote! { #i }),
|
||||
ElementAttrValue::AttrLiteral(lit) => tokens.append_all(quote! { #lit }),
|
||||
ElementAttrValue::AttrOptionalExpr { condition, value } => {
|
||||
tokens.append_all(quote! { if #condition { Some(#value) } else { None } })
|
||||
|
|
|
@ -19,6 +19,7 @@ use syn::{
|
|||
ext::IdentExt,
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
spanned::Spanned,
|
||||
token::Brace,
|
||||
AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result, Token,
|
||||
};
|
||||
|
||||
|
@ -32,8 +33,85 @@ pub struct Component {
|
|||
pub brace: syn::token::Brace,
|
||||
}
|
||||
|
||||
impl Parse for Component {
|
||||
fn parse(stream: ParseStream) -> Result<Self> {
|
||||
let mut name = stream.parse::<syn::Path>()?;
|
||||
Component::validate_component_path(&name)?;
|
||||
|
||||
// extract the path arguments from the path into prop_gen_args
|
||||
let prop_gen_args = normalize_path(&mut name);
|
||||
|
||||
let content: ParseBuffer;
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut fields = Vec::new();
|
||||
let mut children = Vec::new();
|
||||
let mut manual_props = None;
|
||||
|
||||
while !content.is_empty() {
|
||||
// if we splat into a component then we're merging properties
|
||||
if content.peek(Token![..]) {
|
||||
content.parse::<Token![..]>()?;
|
||||
manual_props = Some(content.parse()?);
|
||||
} else if
|
||||
// Named fields
|
||||
(content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]))
|
||||
// shorthand struct initialization
|
||||
// Not a div {}, mod::Component {}, or web-component {}
|
||||
|| (content.peek(Ident)
|
||||
&& !content.peek2(Brace)
|
||||
&& !content.peek2(Token![:])
|
||||
&& !content.peek2(Token![-]))
|
||||
{
|
||||
fields.push(content.parse()?);
|
||||
} else {
|
||||
children.push(content.parse()?);
|
||||
}
|
||||
|
||||
if content.peek(Token![,]) {
|
||||
let _ = content.parse::<Token![,]>();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
prop_gen_args,
|
||||
fields,
|
||||
children,
|
||||
manual_props,
|
||||
brace,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Component {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let Self {
|
||||
name,
|
||||
prop_gen_args,
|
||||
..
|
||||
} = self;
|
||||
|
||||
let builder = self
|
||||
.manual_props
|
||||
.as_ref()
|
||||
.map(|props| self.collect_manual_props(props))
|
||||
.unwrap_or_else(|| self.collect_props());
|
||||
|
||||
let fn_name = self.fn_name();
|
||||
|
||||
tokens.append_all(quote! {
|
||||
__cx.component(
|
||||
#name #prop_gen_args,
|
||||
#builder,
|
||||
#fn_name
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Component {
|
||||
pub fn validate_component_path(path: &syn::Path) -> Result<()> {
|
||||
fn validate_component_path(path: &syn::Path) -> Result<()> {
|
||||
// ensure path segments doesn't have PathArguments, only the last
|
||||
// segment is allowed to have one.
|
||||
if path
|
||||
|
@ -67,134 +145,53 @@ impl Component {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Component {
|
||||
fn parse(stream: ParseStream) -> Result<Self> {
|
||||
let mut name = stream.parse::<syn::Path>()?;
|
||||
Component::validate_component_path(&name)?;
|
||||
|
||||
// extract the path arguments from the path into prop_gen_args
|
||||
let prop_gen_args = name.segments.last_mut().and_then(|seg| {
|
||||
if let PathArguments::AngleBracketed(args) = seg.arguments.clone() {
|
||||
seg.arguments = PathArguments::None;
|
||||
Some(args)
|
||||
} else {
|
||||
None
|
||||
fn collect_manual_props(&self, manual_props: &Expr) -> TokenStream2 {
|
||||
let mut toks = quote! { let mut __manual_props = #manual_props; };
|
||||
for field in &self.fields {
|
||||
if field.name == "key" {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
let ComponentField { name, content } = field;
|
||||
toks.append_all(quote! { __manual_props.#name = #content; });
|
||||
}
|
||||
toks.append_all(quote! { __manual_props });
|
||||
quote! {{ #toks }}
|
||||
}
|
||||
|
||||
let content: ParseBuffer;
|
||||
fn collect_props(&self) -> TokenStream2 {
|
||||
let name = &self.name;
|
||||
|
||||
// if we see a `{` then we have a block
|
||||
// else parse as a function-like call
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut fields = Vec::new();
|
||||
let mut children = Vec::new();
|
||||
let mut manual_props = None;
|
||||
|
||||
while !content.is_empty() {
|
||||
// if we splat into a component then we're merging properties
|
||||
if content.peek(Token![..]) {
|
||||
content.parse::<Token![..]>()?;
|
||||
manual_props = Some(content.parse::<Expr>()?);
|
||||
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
fields.push(content.parse::<ComponentField>()?);
|
||||
} else {
|
||||
children.push(content.parse::<BodyNode>()?);
|
||||
}
|
||||
|
||||
if content.peek(Token![,]) {
|
||||
let _ = content.parse::<Token![,]>();
|
||||
let mut toks = match &self.prop_gen_args {
|
||||
Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) },
|
||||
None => quote! { fc_to_builder(__cx, #name) },
|
||||
};
|
||||
for field in &self.fields {
|
||||
match field.name.to_string().as_str() {
|
||||
"key" => {}
|
||||
_ => toks.append_all(quote! {#field}),
|
||||
}
|
||||
}
|
||||
if !self.children.is_empty() {
|
||||
let renderer: TemplateRenderer = TemplateRenderer {
|
||||
roots: &self.children,
|
||||
location: None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
prop_gen_args,
|
||||
fields,
|
||||
children,
|
||||
manual_props,
|
||||
brace,
|
||||
})
|
||||
toks.append_all(quote! {
|
||||
.children(
|
||||
Some({ #renderer })
|
||||
)
|
||||
});
|
||||
}
|
||||
toks.append_all(quote! {
|
||||
.build()
|
||||
});
|
||||
toks
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Component {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = &self.name;
|
||||
let prop_gen_args = &self.prop_gen_args;
|
||||
|
||||
let builder = match &self.manual_props {
|
||||
Some(manual_props) => {
|
||||
let mut toks = quote! {
|
||||
let mut __manual_props = #manual_props;
|
||||
};
|
||||
for field in &self.fields {
|
||||
if field.name == "key" {
|
||||
// skip keys
|
||||
} else {
|
||||
let name = &field.name;
|
||||
let val = &field.content;
|
||||
toks.append_all(quote! {
|
||||
__manual_props.#name = #val;
|
||||
});
|
||||
}
|
||||
}
|
||||
toks.append_all(quote! {
|
||||
__manual_props
|
||||
});
|
||||
quote! {{
|
||||
#toks
|
||||
}}
|
||||
}
|
||||
None => {
|
||||
let mut toks = match prop_gen_args {
|
||||
Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) },
|
||||
None => quote! { fc_to_builder(__cx, #name) },
|
||||
};
|
||||
for field in &self.fields {
|
||||
match field.name.to_string().as_str() {
|
||||
"key" => {}
|
||||
_ => toks.append_all(quote! {#field}),
|
||||
}
|
||||
}
|
||||
|
||||
if !self.children.is_empty() {
|
||||
let renderer: TemplateRenderer = TemplateRenderer {
|
||||
roots: &self.children,
|
||||
location: None,
|
||||
};
|
||||
|
||||
toks.append_all(quote! {
|
||||
.children(
|
||||
Some({ #renderer })
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
toks.append_all(quote! {
|
||||
.build()
|
||||
});
|
||||
toks
|
||||
}
|
||||
};
|
||||
|
||||
let fn_name = self.name.segments.last().unwrap().ident.to_string();
|
||||
|
||||
let gen_name = match &self.prop_gen_args {
|
||||
Some(gen) => quote! { #name #gen },
|
||||
None => quote! { #name },
|
||||
};
|
||||
|
||||
tokens.append_all(quote! {
|
||||
__cx.component(
|
||||
#gen_name,
|
||||
#builder,
|
||||
#fn_name
|
||||
)
|
||||
})
|
||||
fn fn_name(&self) -> String {
|
||||
self.name.segments.last().unwrap().ident.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,21 +204,50 @@ pub struct ComponentField {
|
|||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
pub enum ContentField {
|
||||
Shorthand(Ident),
|
||||
ManExpr(Expr),
|
||||
Formatted(IfmtInput),
|
||||
OnHandlerRaw(Expr),
|
||||
}
|
||||
|
||||
impl ContentField {
|
||||
fn new_from_name(name: &Ident, input: ParseStream) -> Result<Self> {
|
||||
if name.to_string().starts_with("on") {
|
||||
return Ok(ContentField::OnHandlerRaw(input.parse()?));
|
||||
}
|
||||
|
||||
if *name == "key" {
|
||||
return Ok(ContentField::Formatted(input.parse()?));
|
||||
}
|
||||
|
||||
if input.peek(LitStr) {
|
||||
let forked = input.fork();
|
||||
let t: LitStr = forked.parse()?;
|
||||
|
||||
// the string literal must either be the end of the input or a followed by a comma
|
||||
let res =
|
||||
match (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
|
||||
true => ContentField::Formatted(input.parse()?),
|
||||
false => ContentField::ManExpr(input.parse()?),
|
||||
};
|
||||
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
Ok(ContentField::ManExpr(input.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ContentField {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
match self {
|
||||
ContentField::Shorthand(i) if i.to_string().starts_with("on") => {
|
||||
tokens.append_all(quote! { __cx.event_handler(#i) })
|
||||
}
|
||||
ContentField::Shorthand(i) => tokens.append_all(quote! { #i }),
|
||||
ContentField::ManExpr(e) => e.to_tokens(tokens),
|
||||
ContentField::Formatted(s) => tokens.append_all(quote! {
|
||||
__cx.raw_text(#s)
|
||||
}),
|
||||
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
|
||||
__cx.event_handler(#e)
|
||||
}),
|
||||
ContentField::Formatted(s) => tokens.append_all(quote! { __cx.raw_text(#s) }),
|
||||
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { __cx.event_handler(#e) }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,30 +255,21 @@ impl ToTokens for ContentField {
|
|||
impl Parse for ComponentField {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let name = Ident::parse_any(input)?;
|
||||
input.parse::<Token![:]>()?;
|
||||
|
||||
let content = {
|
||||
if name.to_string().starts_with("on") {
|
||||
ContentField::OnHandlerRaw(input.parse()?)
|
||||
} else if name == "key" {
|
||||
let content = ContentField::Formatted(input.parse()?);
|
||||
return Ok(Self { name, content });
|
||||
} else if input.peek(LitStr) {
|
||||
let forked = input.fork();
|
||||
let t: LitStr = forked.parse()?;
|
||||
// the string literal must either be the end of the input or a followed by a comma
|
||||
if (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
|
||||
ContentField::Formatted(input.parse()?)
|
||||
} else {
|
||||
ContentField::ManExpr(input.parse()?)
|
||||
}
|
||||
} else {
|
||||
ContentField::ManExpr(input.parse()?)
|
||||
}
|
||||
// if the next token is not a colon, then it's a shorthand field
|
||||
if input.parse::<Token![:]>().is_err() {
|
||||
return Ok(Self {
|
||||
content: ContentField::Shorthand(name.clone()),
|
||||
name,
|
||||
});
|
||||
};
|
||||
|
||||
let content = ContentField::new_from_name(&name, input)?;
|
||||
|
||||
if input.peek(LitStr) || input.peek(Ident) {
|
||||
missing_trailing_comma!(content.span());
|
||||
}
|
||||
|
||||
Ok(Self { name, content })
|
||||
}
|
||||
}
|
||||
|
@ -260,9 +277,7 @@ impl Parse for ComponentField {
|
|||
impl ToTokens for ComponentField {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let ComponentField { name, content, .. } = self;
|
||||
tokens.append_all(quote! {
|
||||
.#name(#content)
|
||||
})
|
||||
tokens.append_all(quote! { .#name(#content) })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -281,3 +296,14 @@ fn is_literal_foramtted(lit: &LitStr) -> bool {
|
|||
|
||||
false
|
||||
}
|
||||
|
||||
fn normalize_path(name: &mut syn::Path) -> Option<AngleBracketedGenericArguments> {
|
||||
let seg = name.segments.last_mut()?;
|
||||
match seg.arguments.clone() {
|
||||
PathArguments::AngleBracketed(args) => {
|
||||
seg.arguments = PathArguments::None;
|
||||
Some(args)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use syn::{
|
|||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
token::Brace,
|
||||
Expr, Ident, LitStr, Result, Token,
|
||||
};
|
||||
|
||||
|
@ -59,6 +60,7 @@ impl Parse for Element {
|
|||
}
|
||||
|
||||
// Parse the raw literal fields
|
||||
// "def": 456,
|
||||
if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
let name = content.parse::<LitStr>()?;
|
||||
let ident = name.clone();
|
||||
|
@ -84,6 +86,8 @@ impl Parse for Element {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Parse
|
||||
// abc: 123,
|
||||
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
let name = content.parse::<Ident>()?;
|
||||
|
||||
|
@ -102,22 +106,17 @@ impl Parse for Element {
|
|||
value: ElementAttrValue::EventTokens(content.parse()?),
|
||||
},
|
||||
}));
|
||||
} else if name_str == "key" {
|
||||
key = Some(content.parse()?);
|
||||
} else {
|
||||
match name_str.as_str() {
|
||||
"key" => {
|
||||
key = Some(content.parse()?);
|
||||
}
|
||||
_ => {
|
||||
let value = content.parse::<ElementAttrValue>()?;
|
||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(name),
|
||||
value,
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
let value = content.parse::<ElementAttrValue>()?;
|
||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(name),
|
||||
value,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
if content.is_empty() {
|
||||
|
@ -130,6 +129,47 @@ impl Parse for Element {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Parse shorthand fields
|
||||
if content.peek(Ident)
|
||||
&& !content.peek2(Brace)
|
||||
&& !content.peek2(Token![:])
|
||||
&& !content.peek2(Token![-])
|
||||
{
|
||||
let name = content.parse::<Ident>()?;
|
||||
let name_ = name.clone();
|
||||
|
||||
// If the shorthand field is children, these are actually children!
|
||||
if name == "children" {
|
||||
return Err(syn::Error::new(
|
||||
name.span(),
|
||||
r#"Shorthand element children are not supported.
|
||||
To pass children into elements, wrap them in curly braces.
|
||||
Like so:
|
||||
div {{ {{children}} }}
|
||||
|
||||
"#,
|
||||
));
|
||||
};
|
||||
|
||||
let value = ElementAttrValue::Shorthand(name.clone());
|
||||
attributes.push(attribute::AttributeType::Named(ElementAttrNamed {
|
||||
el_name: el_name.clone(),
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(name),
|
||||
value,
|
||||
},
|
||||
}));
|
||||
|
||||
if content.is_empty() {
|
||||
break;
|
||||
}
|
||||
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
missing_trailing_comma!(name_.span());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -137,31 +177,21 @@ impl Parse for Element {
|
|||
// For example, if there are two `class` attributes, combine them into one
|
||||
let mut merged_attributes: Vec<AttributeType> = Vec::new();
|
||||
for attr in &attributes {
|
||||
if let Some(old_attr_index) = merged_attributes.iter().position(|a| {
|
||||
matches!((a, attr), (
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(old_name),
|
||||
..
|
||||
},
|
||||
..
|
||||
}),
|
||||
AttributeType::Named(ElementAttrNamed {
|
||||
attr: ElementAttr {
|
||||
name: ElementAttrName::BuiltIn(new_name),
|
||||
..
|
||||
},
|
||||
..
|
||||
}),
|
||||
) if old_name == new_name)
|
||||
}) {
|
||||
let attr_index = merged_attributes
|
||||
.iter()
|
||||
.position(|a| a.matches_attr_name(attr));
|
||||
|
||||
if let Some(old_attr_index) = attr_index {
|
||||
let old_attr = &mut merged_attributes[old_attr_index];
|
||||
|
||||
if let Some(combined) = old_attr.try_combine(attr) {
|
||||
*old_attr = combined;
|
||||
}
|
||||
} else {
|
||||
merged_attributes.push(attr.clone());
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
merged_attributes.push(attr.clone());
|
||||
}
|
||||
|
||||
while !content.is_empty() {
|
||||
|
|
|
@ -576,7 +576,7 @@ fn create_template() {
|
|||
p {
|
||||
"hello world"
|
||||
}
|
||||
(0..10).map(|i| rsx!{"{i}"})
|
||||
{(0..10).map(|i| rsx!{"{i}"})}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -668,9 +668,9 @@ fn diff_template() {
|
|||
p {
|
||||
"hello world"
|
||||
}
|
||||
(0..10).map(|i| rsx!{"{i}"}),
|
||||
(0..10).map(|i| rsx!{"{i}"}),
|
||||
(0..11).map(|i| rsx!{"{i}"}),
|
||||
{(0..10).map(|i| rsx!{"{i}"})},
|
||||
{(0..10).map(|i| rsx!{"{i}"})},
|
||||
{(0..11).map(|i| rsx!{"{i}"})},
|
||||
Comp{}
|
||||
}
|
||||
};
|
||||
|
@ -714,9 +714,9 @@ fn diff_template() {
|
|||
"height2": "100px",
|
||||
width: 100,
|
||||
Comp{}
|
||||
(0..11).map(|i| rsx!{"{i}"}),
|
||||
(0..10).map(|i| rsx!{"{i}"}),
|
||||
(0..10).map(|i| rsx!{"{i}"}),
|
||||
{(0..11).map(|i| rsx!{"{i}"})},
|
||||
{(0..10).map(|i| rsx!{"{i}"})},
|
||||
{(0..10).map(|i| rsx!{"{i}"})},
|
||||
p {
|
||||
"hello world"
|
||||
}
|
||||
|
|
|
@ -6,7 +6,8 @@ use syn::{
|
|||
braced,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
token, Expr, ExprIf, LitStr, Pat, Result,
|
||||
token::{self, Brace},
|
||||
Expr, ExprIf, LitStr, Pat, Result,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -15,14 +16,14 @@ Parse
|
|||
-> Component {}
|
||||
-> component()
|
||||
-> "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)]
|
||||
pub enum BodyNode {
|
||||
Element(Element),
|
||||
Component(Component),
|
||||
ForLoop(ForLoop),
|
||||
IfChain(ExprIf),
|
||||
IfChain(IfChain),
|
||||
Text(IfmtInput),
|
||||
RawExpr(Expr),
|
||||
}
|
||||
|
@ -97,7 +98,6 @@ impl Parse for BodyNode {
|
|||
// Input::<InputProps<'_, i32> {}
|
||||
// crate::Input::<InputProps<'_, i32> {}
|
||||
if body_stream.peek(token::Brace) {
|
||||
Component::validate_component_path(&path)?;
|
||||
return Ok(BodyNode::Component(stream.parse()?));
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,27 @@ impl Parse for BodyNode {
|
|||
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 +171,52 @@ impl ToTokens for BodyNode {
|
|||
})
|
||||
}
|
||||
BodyNode::IfChain(chain) => {
|
||||
if is_if_chain_terminated(chain) {
|
||||
tokens.append_all(quote! {
|
||||
{
|
||||
let ___nodes = (#chain).into_dyn_node(__cx);
|
||||
___nodes
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let ExprIf {
|
||||
let mut body = TokenStream2::new();
|
||||
let mut terminated = false;
|
||||
|
||||
let mut elif = Some(chain);
|
||||
|
||||
while let Some(chain) = elif {
|
||||
let IfChain {
|
||||
if_token,
|
||||
cond,
|
||||
then_branch,
|
||||
else_if_branch,
|
||||
else_branch,
|
||||
..
|
||||
} = chain;
|
||||
|
||||
let mut body = TokenStream2::new();
|
||||
let mut renderer: TemplateRenderer = TemplateRenderer {
|
||||
roots: then_branch,
|
||||
location: None,
|
||||
};
|
||||
|
||||
body.append_all(quote! {
|
||||
if #cond {
|
||||
Some(#then_branch)
|
||||
}
|
||||
});
|
||||
body.append_all(quote! { #if_token #cond { Some({#renderer}) } });
|
||||
|
||||
let mut elif = else_branch;
|
||||
|
||||
while let Some((_, ref branch)) = elif {
|
||||
match branch.as_ref() {
|
||||
Expr::If(ref eelif) => {
|
||||
let ExprIf {
|
||||
cond,
|
||||
then_branch,
|
||||
else_branch,
|
||||
..
|
||||
} = eelif;
|
||||
|
||||
body.append_all(quote! {
|
||||
else if #cond {
|
||||
Some(#then_branch)
|
||||
}
|
||||
});
|
||||
|
||||
elif = else_branch;
|
||||
}
|
||||
_ => {
|
||||
body.append_all(quote! {
|
||||
else {
|
||||
#branch
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(next) = else_if_branch {
|
||||
body.append_all(quote! { else });
|
||||
elif = Some(next);
|
||||
} else if let Some(else_branch) = else_branch {
|
||||
renderer.roots = else_branch;
|
||||
body.append_all(quote! { else { Some({#renderer}) } });
|
||||
terminated = true;
|
||||
break;
|
||||
} else {
|
||||
elif = None;
|
||||
}
|
||||
}
|
||||
|
||||
if !terminated {
|
||||
body.append_all(quote! {
|
||||
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 +241,73 @@ impl Parse for ForLoop {
|
|||
let in_token: Token![in] = input.parse()?;
|
||||
let expr: Expr = input.call(Expr::parse_without_eager_brace)?;
|
||||
|
||||
let content;
|
||||
let brace_token = braced!(content in input);
|
||||
|
||||
let mut children = vec![];
|
||||
|
||||
while !content.is_empty() {
|
||||
children.push(content.parse()?);
|
||||
}
|
||||
let (brace_token, body) = parse_buffer_as_braced_children(input)?;
|
||||
|
||||
Ok(Self {
|
||||
for_token,
|
||||
pat,
|
||||
in_token,
|
||||
body: children,
|
||||
expr: Box::new(expr),
|
||||
body,
|
||||
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 {
|
||||
let mut current = chain;
|
||||
loop {
|
||||
|
|
|
@ -242,7 +242,9 @@ fn to_string_works() {
|
|||
div {}
|
||||
div { "nest 2" }
|
||||
"{dyn2}"
|
||||
(0..5).map(|i| rsx! { div { "finalize {i}" } })
|
||||
for i in (0..5) {
|
||||
div { "finalize {i}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ fn text_nodes() {
|
|||
fn app(cx: Scope) -> Element {
|
||||
let dynamic_text = "hello";
|
||||
render! {
|
||||
div { dynamic_text }
|
||||
div { {dynamic_text} }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +124,7 @@ fn components_hydrate() {
|
|||
fn Child2(cx: Scope) -> Element {
|
||||
let dyn_text = "hello";
|
||||
render! {
|
||||
div { dyn_text }
|
||||
div { {dyn_text} }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,11 +159,7 @@ fn components_hydrate() {
|
|||
fn Child4(cx: Scope) -> Element {
|
||||
render! {
|
||||
for _ in 0..2 {
|
||||
render! {
|
||||
render! {
|
||||
"{1}"
|
||||
}
|
||||
}
|
||||
{render! { "{1}" }}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ fn lists() {
|
|||
assert_eq!(
|
||||
dioxus_ssr::render_lazy(rsx! {
|
||||
ul {
|
||||
(0..5).map(|i| rsx! {
|
||||
for i in 0..5 {
|
||||
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>"
|
||||
|
@ -53,9 +53,9 @@ fn components() {
|
|||
assert_eq!(
|
||||
dioxus_ssr::render_lazy(rsx! {
|
||||
div {
|
||||
(0..5).map(|name| rsx! {
|
||||
for name in 0..5 {
|
||||
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>"
|
||||
|
@ -67,7 +67,9 @@ fn fragments() {
|
|||
assert_eq!(
|
||||
dioxus_ssr::render_lazy(rsx! {
|
||||
div {
|
||||
(0..5).map(|_| rsx! (()))
|
||||
for _ in 0..5 {
|
||||
{}
|
||||
}
|
||||
}
|
||||
}),
|
||||
"<div></div>"
|
||||
|
|
|
@ -12,11 +12,11 @@ fn app(cx: Scope) -> Element {
|
|||
"asd"
|
||||
Bapp {}
|
||||
}
|
||||
(0..10).map(|f| rsx!{
|
||||
{(0..10).map(|f| rsx!{
|
||||
div {
|
||||
"thing {f}"
|
||||
}
|
||||
})
|
||||
})}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,11 @@ fn app(cx: Scope) -> Element {
|
|||
start();
|
||||
*count.write() += 1;
|
||||
},
|
||||
// format is needed as {count} does not seemed to work in `if` within content
|
||||
if **started { format!("Current score: {}", count.write()) } else { "Start".to_string() }
|
||||
if **started {
|
||||
"Current score: {count.read()}"
|
||||
} else {
|
||||
"Start"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ fn rehydrates() {
|
|||
},
|
||||
"listener test"
|
||||
}
|
||||
false.then(|| rsx!{ "hello" })
|
||||
{false.then(|| rsx!{ "hello" })}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue