Преглед на файлове

awesome: arbitrary expressions excepted without braces

Jonathan Kelley преди 3 години
родител
ревизия
4c85bcfdc8
променени са 40 файла, в които са добавени 809 реда и са изтрити 751 реда
  1. 7 1
      README.md
  2. 8 16
      examples/README.md
  3. 4 6
      examples/async.rs
  4. 1 1
      examples/borrowed.rs
  5. 53 26
      examples/calculator.rs
  6. 2 2
      examples/core_reference/iterators.rs
  7. 92 79
      examples/crm.rs
  8. 1 1
      examples/desktop/demo.rs
  9. 0 8
      examples/desktop/kitchensink.rs
  10. 0 1
      examples/desktop/tauri.rs
  11. 7 7
      examples/desktop/todomvc.rs
  12. 30 43
      examples/file_explorer.rs
  13. 15 15
      examples/framework_benchmark.rs
  14. 4 4
      examples/hydration.rs
  15. 20 20
      examples/pattern_model.rs
  16. 11 13
      examples/pattern_reducer.rs
  17. 3 3
      examples/readme.rs
  18. 61 0
      examples/rsx_compile_fail.rs
  19. 54 29
      examples/rsx_usage.rs
  20. 38 38
      examples/tailwind.rs
  21. 3 2
      examples/tasks.rs
  22. 21 18
      examples/todomvc.rs
  23. 9 18
      examples/web_tick.rs
  24. 3 3
      examples/webview_web.rs
  25. 31 0
      examples/xss_safety.rs
  26. 1 0
      packages/core-macro/Cargo.toml
  27. 1 0
      packages/core-macro/src/inlineprops.rs
  28. 3 2
      packages/core-macro/src/lib.rs
  29. 0 64
      packages/core-macro/src/rsx/ambiguous.rs
  30. 0 66
      packages/core-macro/src/rsx/body.rs
  31. 11 86
      packages/core-macro/src/rsx/component.rs
  32. 114 61
      packages/core-macro/src/rsx/element.rs
  33. 0 63
      packages/core-macro/src/rsx/fragment.rs
  34. 74 6
      packages/core-macro/src/rsx/mod.rs
  35. 52 37
      packages/core-macro/src/rsx/node.rs
  36. 2 2
      packages/core/src/nodes.rs
  37. 1 1
      packages/core/tests/vdom_rebuild.rs
  38. 64 0
      packages/hooks/src/usestate.rs
  39. 1 1
      packages/router/src/link.rs
  40. 7 8
      packages/ssr/src/lib.rs

+ 7 - 1
README.md

@@ -79,7 +79,9 @@ If you know React, then you already know Dioxus.
     <tr>
     <tr>
 </table>
 </table>
 
 
-## Examples:
+
+
+## Examples Projects:
 
 
 | File Navigator (Desktop)                                                                                                                                                        | WiFi scanner (Desktop)                                                                                                                                                                 | TodoMVC (All platforms)                                                                                                                                                 | Ecommerce w/ Tailwind (Liveview)                                                                                                                                                     |
 | File Navigator (Desktop)                                                                                                                                                        | WiFi scanner (Desktop)                                                                                                                                                                 | TodoMVC (All platforms)                                                                                                                                                 | Ecommerce w/ Tailwind (Liveview)                                                                                                                                                     |
 | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
 | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -88,6 +90,10 @@ If you know React, then you already know Dioxus.
 
 
 See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
 See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
 
 
+## Running examples locally
+
+All local examples are built for the desktop renderer. This means you can simply clone this repo and call `cargo run --example EXAMPLE_NAME`. To run non-desktop examples, checkout the example projects shown above.
+
 ## Why Dioxus and why Rust?
 ## Why Dioxus and why Rust?
 
 
 TypeScript is a fantastic addition to JavaScript, but it's still fundamentally JavaScript. TS code runs slightly slower, has tons of configuration options, and not every package is properly typed. 
 TypeScript is a fantastic addition to JavaScript, but it's still fundamentally JavaScript. TS code runs slightly slower, has tons of configuration options, and not every package is properly typed. 

+ 8 - 16
examples/README.md

@@ -42,14 +42,6 @@ These examples are not necessarily meant to be run, but rather serve as a refere
 | [Complete rsx reference](./rsx_usage.rs)            | A complete reference for all rsx! usage         | ✅      |
 | [Complete rsx reference](./rsx_usage.rs)            | A complete reference for all rsx! usage         | ✅      |
 | [Event Listeners](./listener.rs)                    | Attach closures to events on elements           | ✅      |
 | [Event Listeners](./listener.rs)                    | Attach closures to events on elements           | ✅      |
 
 
-These web-specific examples must be run with `dioxus-cli` using `dioxus develop --example XYZ`
-
-| Example | What it does |
-| ------- | ------------ |
-| asd     | this does    |
-| asd     | this does    |
-
-
 
 
 ## Show me some examples!
 ## Show me some examples!
 
 
@@ -75,9 +67,9 @@ fn Toggle(cx: Scope<ToggleProps>) -> Element {
   let mut toggled = use_state(&cx, || false);
   let mut toggled = use_state(&cx, || false);
   cx.render(rsx!{
   cx.render(rsx!{
     div {
     div {
-      {&cx.props.children}
+      &cx.props.children
       button { onclick: move |_| toggled.set(true),
       button { onclick: move |_| toggled.set(true),
-        {toggled.and_then(|| "On").or_else(|| "Off")}
+        toggled.and_then(|| "On").or_else(|| "Off")
       }
       }
     }
     }
   })
   })
@@ -112,8 +104,8 @@ fn App(cx: Scope) -> Element {
 
 
   if should_show {
   if should_show {
     cx.render(rsx!( 
     cx.render(rsx!( 
-      {title}
-      ul { {list} } 
+      title,
+      ul { list } 
     ))
     ))
   } else {
   } else {
     None
     None
@@ -169,10 +161,10 @@ enum Route {
 fn App(cx: Scope) -> Element {
 fn App(cx: Scope) -> Element {
   let route = use_router(cx, Route::parse);
   let route = use_router(cx, Route::parse);
   cx.render(rsx!(div {
   cx.render(rsx!(div {
-    {match route {
+    match route {
       Route::Home => rsx!( Home {} ),
       Route::Home => rsx!( Home {} ),
       Route::Post(id) => rsx!( Post { id: id })
       Route::Post(id) => rsx!( Post { id: id })
-    }}
+    }
   }))  
   }))  
 }
 }
 ```
 ```
@@ -187,8 +179,8 @@ fn App(cx: Scope) -> Element {
   
   
   cx.render(rsx!{
   cx.render(rsx!{
     div {
     div {
-      "One doggo coming right up:"
-      {doggo}
+      "One doggo coming right up:",
+      doggo
     }
     }
   })
   })
 }
 }

+ 4 - 6
examples/async.rs

@@ -26,20 +26,18 @@ fn app(cx: Scope) -> Element {
 
 
     rsx!(cx, div {
     rsx!(cx, div {
         h1 {"count is {count}"}
         h1 {"count is {count}"}
-        button {
+        button { onclick: move |_| task.stop(),
             "Stop counting"
             "Stop counting"
-            onclick: move |_| task.stop()
         }
         }
-        button {
+        button { onclick: move |_| task.resume(),
             "Start counting"
             "Start counting"
-            onclick: move |_| task.resume()
         }
         }
         button {
         button {
-            "Switch counting direcion"
             onclick: move |_| {
             onclick: move |_| {
                 *direction.modify() *= -1;
                 *direction.modify() *= -1;
                 task.restart();
                 task.restart();
-            }
+            },
+            "Switch counting direcion"
         }
         }
     })
     })
 }
 }

+ 1 - 1
examples/borrowed.rs

@@ -21,7 +21,7 @@ fn main() {
 }
 }
 
 
 fn App(cx: Scope) -> Element {
 fn App(cx: Scope) -> Element {
-    let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
+    let text = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
 
 
     let first = text.get_mut(0).unwrap();
     let first = text.get_mut(0).unwrap();
 
 

+ 53 - 26
examples/calculator.rs

@@ -24,7 +24,9 @@ fn app(cx: Scope) -> Element {
     let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str());
     let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str());
 
 
     cx.render(rsx!(
     cx.render(rsx!(
-        div { class: "calculator",
+        style { [include_str!("./assets/calculator.css")] }
+        div {
+            class: "calculator",
             onkeydown: move |evt| match evt.key_code {
             onkeydown: move |evt| match evt.key_code {
                 KeyCode::Add => operator.set(Some("+")),
                 KeyCode::Add => operator.set(Some("+")),
                 KeyCode::Subtract => operator.set(Some("-")),
                 KeyCode::Subtract => operator.set(Some("-")),
@@ -46,12 +48,11 @@ fn app(cx: Scope) -> Element {
                     }
                     }
                 }
                 }
                 _ => {}
                 _ => {}
-            }
-            div { class: "calculator-display", {[cur_val.separated_string()]} }
-            div { class: "input-keys"
-                div { class: "function-keys"
+            },
+            div { class: "calculator-display", [cur_val.separated_string()] }
+            div { class: "input-keys",
+                div { class: "function-keys",
                     CalculatorKey {
                     CalculatorKey {
-                        {[if display_value == "0" { "C" } else { "AC" }]}
                         name: "key-clear",
                         name: "key-clear",
                         onclick: move |_| {
                         onclick: move |_| {
                             display_value.set("0".to_string());
                             display_value.set("0".to_string());
@@ -59,10 +60,10 @@ fn app(cx: Scope) -> Element {
                                 operator.set(None);
                                 operator.set(None);
                                 cur_val.set(0.0);
                                 cur_val.set(0.0);
                             }
                             }
-                        }
+                        },
+                        [if display_value == "0" { "C" } else { "AC" }]
                     }
                     }
                     CalculatorKey {
                     CalculatorKey {
-                        "±"
                         name: "key-sign",
                         name: "key-sign",
                         onclick: move |_| {
                         onclick: move |_| {
                             if display_value.starts_with("-") {
                             if display_value.starts_with("-") {
@@ -71,28 +72,53 @@ fn app(cx: Scope) -> Element {
                                 display_value.set(format!("-{}", *display_value))
                                 display_value.set(format!("-{}", *display_value))
                             }
                             }
                         },
                         },
+                        "±"
                     }
                     }
                     CalculatorKey {
                     CalculatorKey {
-                        "%"
-                        onclick: {toggle_percent}
+                        onclick: toggle_percent,
                         name: "key-percent",
                         name: "key-percent",
+                        "%"
                     }
                     }
                 }
                 }
-                div { class: "digit-keys"
-                    CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
-                    CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."), "●" }
-
-                    {(1..9).map(|k| rsx!{
-                        CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
-                    })}
+                div { class: "digit-keys",
+                    CalculatorKey { name: "key-0", onclick: move |_| input_digit(0),
+                        "0"
+                    }
+                    CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."),
+                        "●"
+                    }
+                    (1..9).map(|k| rsx!{
+                        CalculatorKey {
+                            key: "{k}",
+                            name: "key-{k}",
+                            onclick: move |_| input_digit(k),
+                            "{k}"
+                        }
+                    }),
                 }
                 }
-                div { class: "operator-keys"
-                    CalculatorKey { name: "key-divide", onclick: move |_| operator.set(Some("/")) "÷" }
-                    CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some("*")) "×" }
-                    CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some("-")) "−" }
-                    CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some("+")) "+" }
+
+                div { class: "operator-keys",
+                    CalculatorKey {
+                        name: "key-divide",
+                        onclick: move |_| operator.set(Some("/")),
+                        "÷"
+                    }
+                    CalculatorKey {
+                        name: "key-multiply",
+                        onclick: move |_| operator.set(Some("*")),
+                        "×"
+                    }
+                    CalculatorKey {
+                        name: "key-subtract",
+                        onclick: move |_| operator.set(Some("-")),
+                        "−"
+                    }
+                    CalculatorKey {
+                        name: "key-add",
+                        onclick: move |_| operator.set(Some("+")),
+                        "+"
+                    }
                     CalculatorKey {
                     CalculatorKey {
-                        "="
                         name: "key-equals",
                         name: "key-equals",
                         onclick: move |_| {
                         onclick: move |_| {
                             if let Some(op) = operator.as_ref() {
                             if let Some(op) = operator.as_ref() {
@@ -109,6 +135,7 @@ fn app(cx: Scope) -> Element {
                                 operator.set(None);
                                 operator.set(None);
                             }
                             }
                         },
                         },
+                        "="
                     }
                     }
                 }
                 }
             }
             }
@@ -125,9 +152,9 @@ fn CalculatorKey<'a>(
 ) -> Element {
 ) -> Element {
     cx.render(rsx! {
     cx.render(rsx! {
         button {
         button {
-            class: "calculator-key {name}"
-            onclick: {onclick}
-            {children}
+            class: "calculator-key {name}",
+            onclick: onclick,
+            children
         }
         }
     })
     })
 }
 }

+ 2 - 2
examples/core_reference/iterators.rs

@@ -20,11 +20,11 @@ pub static Example: Component = |cx| {
             li { onclick: move |_| example_data.set(f)
             li { onclick: move |_| example_data.set(f)
                 "ID: {f}"
                 "ID: {f}"
                 ul {
                 ul {
-                    {(0..10).map(|k| rsx!{
+                    (0..10).map(|k| rsx!{
                         li {
                         li {
                             "Sub iterator: {f}.{k}"
                             "Sub iterator: {f}.{k}"
                         }
                         }
-                    })}
+                    })
                 }
                 }
             }
             }
         }
         }

+ 92 - 79
examples/crm.rs

@@ -4,7 +4,7 @@ Tiny CRM: A port of the Yew CRM example to Dioxus.
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 
 
 fn main() {
 fn main() {
-    dioxus::web::launch(App);
+    dioxus::desktop::launch(app);
 }
 }
 enum Scene {
 enum Scene {
     ClientsList,
     ClientsList,
@@ -19,88 +19,101 @@ pub struct Client {
     pub description: String,
     pub description: String,
 }
 }
 
 
-static App: Component = |cx| {
-    let mut clients = use_ref(&cx, || vec![] as Vec<Client>);
-    let mut scene = use_state(&cx, || Scene::ClientsList);
+fn app(cx: Scope) -> Element {
+    let clients = use_ref(&cx, || vec![] as Vec<Client>);
 
 
-    let mut firstname = use_state(&cx, || String::new());
-    let mut lastname = use_state(&cx, || String::new());
-    let mut description = use_state(&cx, || String::new());
+    let scene = use_state(&cx, || Scene::ClientsList);
+    let firstname = use_state(&cx, String::new);
+    let lastname = use_state(&cx, String::new);
+    let description = use_state(&cx, String::new);
 
 
-    let scene = match *scene {
-        Scene::ClientsList => {
-            rsx!(cx, div { class: "crm"
-                h2 { "List of clients" margin_bottom: "10px" }
-                div { class: "clients" margin_left: "10px"
-                    {clients.read().iter().map(|client| rsx!(
-                        div { class: "client" style: "margin-bottom: 50px"
-                            p { "First Name: {client.first_name}" }
-                            p { "Last Name: {client.last_name}" }
-                            p {"Description: {client.description}"}
-                        })
-                    )}
-                }
-                button { class: "pure-button pure-button-primary" onclick: move |_| scene.set(Scene::NewClientForm), "Add New" }
-                button { class: "pure-button" onclick: move |_| scene.set(Scene::Settings), "Settings" }
-            })
-        }
-        Scene::NewClientForm => {
-            let add_new = move |_| {
-                clients.write().push(Client {
-                    description: (*description).clone(),
-                    first_name: (*firstname).clone(),
-                    last_name: (*lastname).clone(),
-                });
-                description.set(String::new());
-                firstname.set(String::new());
-                lastname.set(String::new());
-            };
-            rsx!(cx, div { class: "crm"
-                h2 {"Add new client" margin_bottom: "10px" }
-                form { class: "pure-form"
-                    input { class: "new-client firstname" placeholder: "First name" value: "{firstname}"
-                        oninput: move |e| firstname.set(e.value.clone())
+    cx.render(rsx!(
+        body { margin_left: "35%",
+            link {
+                rel: "stylesheet",
+                href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
+                integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
+                crossorigin: "anonymous",
+            }
+
+            h1 {"Dioxus CRM Example"}
+
+            match *scene {
+                Scene::ClientsList => rsx!(
+                    div { class: "crm",
+                        h2 { margin_bottom: "10px", "List of clients" }
+                        div { class: "clients", margin_left: "10px",
+                            clients.read().iter().map(|client| rsx!(
+                                div { class: "client", style: "margin-bottom: 50px",
+                                    p { "First Name: {client.first_name}" }
+                                    p { "Last Name: {client.last_name}" }
+                                    p {"Description: {client.description}"}
+                                })
+                            )
+                        }
+                        button { class: "pure-button pure-button-primary", onclick: move |_| scene.set(Scene::NewClientForm), "Add New" }
+                        button { class: "pure-button", onclick: move |_| scene.set(Scene::Settings), "Settings" }
                     }
                     }
-                    input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}"
-                        oninput: move |e| lastname.set(e.value.clone())
+                ),
+                Scene::NewClientForm => rsx!(
+                    div { class: "crm",
+                        h2 { margin_bottom: "10px", "Add new client" }
+                        form { class: "pure-form",
+                            input {
+                                class: "new-client firstname",
+                                placeholder: "First name",
+                                value: "{firstname}",
+                                oninput: move |e| firstname.set(e.value.clone())
+                            }
+                            input {
+                                class: "new-client lastname",
+                                placeholder: "Last name",
+                                value: "{lastname}",
+                                oninput: move |e| lastname.set(e.value.clone())
+                            }
+                            textarea {
+                                class: "new-client description",
+                                placeholder: "Description",
+                                value: "{description}",
+                                oninput: move |e| description.set(e.value.clone())
+                            }
+                        }
+                        button {
+                            class: "pure-button pure-button-primary",
+                            onclick: move |_| {
+                                clients.write().push(Client {
+                                    description: (*description).clone(),
+                                    first_name: (*firstname).clone(),
+                                    last_name: (*lastname).clone(),
+                                });
+                                description.set(String::new());
+                                firstname.set(String::new());
+                                lastname.set(String::new());
+                            },
+                            "Add New"
+                        }
+                        button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList),
+                            "Go Back"
+                        }
                     }
                     }
-                    textarea { class: "new-client description" placeholder: "Description" value: "{description}"
-                        oninput: move |e| description.set(e.value.clone())
+                ),
+                Scene::Settings => rsx!(
+                    div {
+                        h2 { margin_bottom: "10px", "Settings" }
+                        button {
+                            background: "rgb(202, 60, 60)",
+                            class: "pure-button pure-button-primary",
+                            onclick: move |_| clients.write().clear(),
+                            "Remove all clients"
+                        }
+                        button {
+                            class: "pure-button pure-button-primary",
+                            onclick: move |_| scene.set(Scene::ClientsList),
+                            "Go Back"
+                        }
                     }
                     }
-                }
-                button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" }
-                button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" }
-            })
+                )
+            }
         }
         }
-        Scene::Settings => {
-            rsx!(cx, div {
-                h2 {"Settings" margin_bottom: "10px" }
-                button {
-                    background: "rgb(202, 60, 60)"
-                    class: "pure-button pure-button-primary"
-                    onclick: move |_| clients.write().clear(),
-                    "Remove all clients"
-                }
-                button {
-                    class: "pure-button pure-button-primary"
-                    onclick: move |_| scene.set(Scene::ClientsList),
-                    "Go Back"
-                }
-            })
-        }
-    };
-
-    cx.render(rsx!(
-        body {
-           link {
-               rel: "stylesheet"
-               href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
-               integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
-               crossorigin: "anonymous"
-           }
-           margin_left: "35%"
-           h1 {"Dioxus CRM Example"}
-           {scene}
-       }
     ))
     ))
-};
+}

+ 1 - 1
examples/desktop/demo.rs

@@ -13,6 +13,6 @@ static App: Component = |cx| {
         div {
         div {
             "hello world!"
             "hello world!"
         }
         }
-        {(0..10).map(|f| rsx!( div {"abc {f}"}))}
+        (0..10).map(|f| rsx!( div {"abc {f}"}))
     ))
     ))
 };
 };

+ 0 - 8
examples/desktop/kitchensink.rs

@@ -1,8 +0,0 @@
-use dioxus_core as dioxus;
-use dioxus_core::prelude::*;
-use dioxus_core_macro::*;
-use dioxus_hooks::*;
-
-use dioxus_html as dioxus_elements;
-
-fn main() {}

+ 0 - 1
examples/desktop/tauri.rs

@@ -1 +0,0 @@
-fn main() {}

+ 7 - 7
examples/desktop/todomvc.rs

@@ -98,24 +98,24 @@ pub static App: Component = |cx| {
                         }
                         }
                     }
                     }
                 }
                 }
-                ul { class: "todo-list"
-                    {filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id }))}
+                ul { class: "todo-list",
+                    filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id }))
                 }
                 }
-                {(!todos.read().is_empty()).then(|| rsx!(
-                    footer { class: "footer"
+                (!todos.read().is_empty()).then(|| rsx!(
+                    footer { class: "footer",
                         span { class: "todo-count" strong {"{items_left} "} span {"{item_text} left"} }
                         span { class: "todo-count" strong {"{items_left} "} span {"{item_text} left"} }
                         ul { class: "filters"
                         ul { class: "filters"
                             li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
                             li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
                             li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
                             li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
                             li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
                             li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
                         }
                         }
-                        {(show_clear_completed).then(|| rsx!(
+                        (show_clear_completed).then(|| rsx!(
                             button { class: "clear-completed", onclick: move |_| clear_completed(),
                             button { class: "clear-completed", onclick: move |_| clear_completed(),
                                 "Clear completed"
                                 "Clear completed"
                             }
                             }
-                        ))}
+                        ))
                     }
                     }
-                ))}
+                ))
             }
             }
         }
         }
         footer { class: "info"
         footer { class: "info"

+ 30 - 43
examples/file_explorer.rs

@@ -4,50 +4,45 @@
 //! This is a fun little desktop application that lets you explore the file system.
 //! This is a fun little desktop application that lets you explore the file system.
 //!
 //!
 //! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do.
 //! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do.
+//!
+//! It also uses `use_ref` to maintain a model, rather than `use_state`. That way,
+//! we dont need to clutter our code with `read` commands.
 
 
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 
 
 fn main() {
 fn main() {
-    dioxus::desktop::launch_cfg(App, |c| {
+    dioxus::desktop::launch_cfg(app, |c| {
         c.with_window(|w| {
         c.with_window(|w| {
-            w.with_resizable(true).with_inner_size(
-                dioxus::desktop::wry::application::dpi::LogicalSize::new(400.0, 800.0),
-            )
+            w.with_resizable(true)
+                .with_inner_size(dioxus::desktop::tao::dpi::LogicalSize::new(400.0, 800.0))
         })
         })
     });
     });
 }
 }
 
 
-static App: Component = |cx| {
-    let file_manager = use_ref(&cx, Files::new);
-    let files = file_manager.read();
-
-    let file_list = files.path_names.iter().enumerate().map(|(dir_id, path)| {
-        rsx! (
-            li { a {"{path}", onclick: move |_| file_manager.write().enter_dir(dir_id), href: "#"} }
-        )
-    });
-
-    let err_disp = files.err.as_ref().map(|err| {
-        rsx! (
-            div {
-                code {"{err}"}
-                button {"x", onclick: move |_| file_manager.write().clear_err() }
-            }
-        )
-    });
-
-    let current_dir = files.current();
+fn app(cx: Scope) -> Element {
+    let files = use_ref(&cx, Files::new);
 
 
     cx.render(rsx!(
     cx.render(rsx!(
-        div {
-            h1 {"Files: "}
-            h3 {"Cur dir: {current_dir}"}
-            button { "go up", onclick: move |_| file_manager.write().go_up() }
-            ol { {file_list} }
-            {err_disp}
+        h1 { "Files: " }
+        h3 { "Cur dir: " [files.read().current()] }
+        button { onclick: move |_| files.write().go_up(), "go up" }
+        ol {
+            files.read().path_names.iter().enumerate().map(|(dir_id, path)| rsx!(
+                li { key: "{path}",
+                    a { href: "#", onclick: move |_| files.write().enter_dir(dir_id),
+                        "{path}",
+                    }
+                }
+            ))
         }
         }
+        files.read().err.as_ref().map(|err| rsx!(
+            div {
+                code { "{err}" }
+                button { onclick: move |_| files.write().clear_err(), "x" }
+            }
+        ))
     ))
     ))
-};
+}
 
 
 struct Files {
 struct Files {
     path_stack: Vec<String>,
     path_stack: Vec<String>,
@@ -69,29 +64,21 @@ impl Files {
     }
     }
 
 
     fn reload_path_list(&mut self) {
     fn reload_path_list(&mut self) {
-        let cur_path = self.path_stack.last().unwrap();
-        log::info!("Reloading path list for {:?}", cur_path);
-        let paths = match std::fs::read_dir(cur_path) {
+        let paths = match std::fs::read_dir(self.path_stack.last().unwrap()) {
             Ok(e) => e,
             Ok(e) => e,
             Err(err) => {
             Err(err) => {
-                let err = format!("An error occured: {:?}", err);
-                self.err = Some(err);
+                self.err = Some(format!("An error occured: {:?}", err));
                 self.path_stack.pop();
                 self.path_stack.pop();
                 return;
                 return;
             }
             }
         };
         };
-        let collected = paths.collect::<Vec<_>>();
-        log::info!("Path list reloaded {:#?}", collected);
 
 
         // clear the current state
         // clear the current state
         self.clear_err();
         self.clear_err();
         self.path_names.clear();
         self.path_names.clear();
 
 
-        for path in collected {
-            self.path_names
-                .push(path.unwrap().path().display().to_string());
-        }
-        log::info!("path namees are {:#?}", self.path_names);
+        self.path_names
+            .extend(paths.map(|path| path.unwrap().path().display().to_string()));
     }
     }
 
 
     fn go_up(&mut self) {
     fn go_up(&mut self) {

+ 15 - 15
examples/framework_benchmark.rs

@@ -2,8 +2,8 @@ use dioxus::prelude::*;
 use rand::prelude::*;
 use rand::prelude::*;
 
 
 fn main() {
 fn main() {
-    dioxus::web::launch(App);
-    // dioxus::desktop::launch(App);
+    // dioxus::web::launch(App);
+    dioxus::desktop::launch(App);
 }
 }
 
 
 #[derive(Clone, PartialEq)]
 #[derive(Clone, PartialEq)]
@@ -31,16 +31,16 @@ impl Label {
 }
 }
 
 
 static App: Component = |cx| {
 static App: Component = |cx| {
-    let mut items = use_ref(&cx, || vec![]);
-    let mut selected = use_state(&cx, || None);
+    let items = use_ref(&cx, || vec![]);
+    let selected = use_state(&cx, || None);
 
 
     cx.render(rsx! {
     cx.render(rsx! {
-        div { class: "container"
-            div { class: "jumbotron"
-                div { class: "row"
+        div { class: "container",
+            div { class: "jumbotron",
+                div { class: "row",
                     div { class: "col-md-6", h1 { "Dioxus" } }
                     div { class: "col-md-6", h1 { "Dioxus" } }
-                    div { class: "col-md-6"
-                        div { class: "row"
+                    div { class: "col-md-6",
+                        div { class: "row",
                             ActionButton { name: "Create 1,000 rows", id: "run",
                             ActionButton { name: "Create 1,000 rows", id: "run",
                                 onclick: move || items.set(Label::new_list(1_000)),
                                 onclick: move || items.set(Label::new_list(1_000)),
                             }
                             }
@@ -67,15 +67,15 @@ static App: Component = |cx| {
                 tbody {
                 tbody {
                     {items.read().iter().enumerate().map(|(id, item)| {
                     {items.read().iter().enumerate().map(|(id, item)| {
                         let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
                         let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
-                        rsx!(tr { class: "{is_in_danger}"
+                        rsx!(tr { class: "{is_in_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)),
-                                a { class: "lbl", {item.labels} }
+                                a { class: "lbl", item.labels }
                             }
                             }
-                            td { class: "col-md-1"
+                            td { class: "col-md-1",
                                 a { class: "remove", onclick: move |_| { items.write().remove(id); },
                                 a { class: "remove", onclick: move |_| { items.write().remove(id); },
-                                    span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+                                    span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
                                 }
                                 }
                             }
                             }
                             td { class: "col-md-6" }
                             td { class: "col-md-6" }
@@ -83,7 +83,7 @@ static App: Component = |cx| {
                     })}
                     })}
                 }
                 }
              }
              }
-            // span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" }
+            span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
         }
         }
     })
     })
 };
 };
@@ -96,7 +96,7 @@ struct ActionButtonProps<'a> {
 }
 }
 
 
 fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
 fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
-    rsx!(cx, div { class: "col-sm-6 smallpad"
+    rsx!(cx, div { class: "col-sm-6 smallpad",
         button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}",  onclick: move |_| (cx.props.onclick)(),
         button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}",  onclick: move |_| (cx.props.onclick)(),
             "{cx.props.name}"
             "{cx.props.name}"
         }
         }

+ 4 - 4
examples/hydration.rs

@@ -16,18 +16,18 @@ fn main() {
     let vdom = VirtualDom::new(App);
     let vdom = VirtualDom::new(App);
     let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
     let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
 
 
-    dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
+    // dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
 }
 }
 
 
 static App: Component = |cx| {
 static App: Component = |cx| {
-    let mut val = use_state(&cx, || 0);
+    let val = use_state(&cx, || 0);
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {
-            h1 {"hello world. Count: {val}"}
+            h1 { "hello world. Count: {val}" }
             button {
             button {
+                onclick: move |_| *val.modify() += 1,
                 "click to increment"
                 "click to increment"
-                onclick: move |_| val += 1
             }
             }
         }
         }
     })
     })

+ 20 - 20
examples/pattern_model.rs

@@ -24,42 +24,42 @@ use dioxus::prelude::*;
 const STYLE: &str = include_str!("./assets/calculator.css");
 const STYLE: &str = include_str!("./assets/calculator.css");
 fn main() {
 fn main() {
     env_logger::init();
     env_logger::init();
-    dioxus::desktop::launch_cfg(App, |cfg| {
-        cfg.with_window(|w| {
-            w.with_title("Calculator Demo")
-                .with_resizable(false)
-                .with_inner_size(LogicalSize::new(320.0, 530.0))
-        })
-    });
+    // dioxus::desktop::launch_cfg(App, |cfg| {
+    //     cfg.with_window(|w| {
+    //         w.with_title("Calculator Demo")
+    //             .with_resizable(false)
+    //             .with_inner_size(LogicalSize::new(320.0, 530.0))
+    //     })
+    // });
 }
 }
 
 
-static App: Component = |cx| {
+fn app(cx: Scope) -> Element {
     let state = use_ref(&cx, || Calculator::new());
     let state = use_ref(&cx, || Calculator::new());
 
 
     let clear_display = state.read().display_value.eq("0");
     let clear_display = state.read().display_value.eq("0");
     let clear_text = if clear_display { "C" } else { "AC" };
     let clear_text = if clear_display { "C" } else { "AC" };
     let formatted = state.read().formatted_display();
     let formatted = state.read().formatted_display();
 
 
-    rsx!(cx, div { id: "wrapper"
+    rsx!(cx, div { id: "wrapper",
         div { class: "app", style { "{STYLE}" }
         div { class: "app", style { "{STYLE}" }
             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", "{formatted}"}
                 div { class: "calculator-display", "{formatted}"}
-                div { class: "calculator-keypad"
-                    div { class: "input-keys"
-                        div { class: "function-keys"
+                div { class: "calculator-keypad",
+                    div { class: "input-keys",
+                        div { class: "function-keys",
                             CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" }
                             CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" }
                             CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"}
                             CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"}
                             CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"}
                             CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"}
                         }
                         }
-                        div { class: "digit-keys"
+                        div { class: "digit-keys",
                             CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
                             CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
                             CalculatorKey { name: "key-dot", onclick: move |_|  state.write().input_dot(), "●" }
                             CalculatorKey { name: "key-dot", onclick: move |_|  state.write().input_dot(), "●" }
-                            {(1..10).map(move |k| rsx!{
+                            (1..10).map(move |k| rsx!{
                                 CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_|  state.write().input_digit(k), "{k}" }
                                 CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_|  state.write().input_digit(k), "{k}" }
-                            })}
+                            })
                         }
                         }
                     }
                     }
-                    div { class: "operator-keys"
+                    div { class: "operator-keys",
                         CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" }
                         CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" }
                         CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" }
                         CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" }
                         CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "−" }
                         CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "−" }
@@ -70,7 +70,7 @@ static App: Component = |cx| {
             }
             }
         }
         }
     })
     })
-};
+}
 
 
 #[derive(Props)]
 #[derive(Props)]
 struct CalculatorKeyProps<'a> {
 struct CalculatorKeyProps<'a> {
@@ -82,9 +82,9 @@ struct CalculatorKeyProps<'a> {
 fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
 fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
     cx.render(rsx! {
     cx.render(rsx! {
         button {
         button {
-            class: "calculator-key {cx.props.name}"
-            onclick: move |e| (cx.props.onclick)(e)
-            {&cx.props.children}
+            class: "calculator-key {cx.props.name}",
+            onclick: move |e| (cx.props.onclick)(e),
+            &cx.props.children
         }
         }
     })
     })
 }
 }

+ 11 - 13
examples/pattern_reducer.rs

@@ -14,20 +14,18 @@ fn main() {
 pub static App: Component = |cx| {
 pub static App: Component = |cx| {
     let state = use_state(&cx, PlayerState::new);
     let state = use_state(&cx, PlayerState::new);
 
 
-    let is_playing = state.is_playing();
-
-    rsx!(cx, div {
-        h1 {"Select an option"}
-        h3 {"The radio is... {is_playing}!"}
-        button {
-            "Pause"
-            onclick: move |_| state.modify().reduce(PlayerAction::Pause)
-        }
-        button {
-            "Play"
-            onclick: move |_| state.modify().reduce(PlayerAction::Play)
+    cx.render(rsx!(
+        div {
+            h1 {"Select an option"}
+            h3 { "The radio is... " [state.is_playing()], "!" }
+            button { onclick: move |_| state.modify().reduce(PlayerAction::Pause),
+                "Pause"
+            }
+            button { onclick: move |_| state.modify().reduce(PlayerAction::Play),
+                "Play"
+            }
         }
         }
-    })
+    ))
 };
 };
 
 
 enum PlayerAction {
 enum PlayerAction {

+ 3 - 3
examples/readme.rs

@@ -9,13 +9,13 @@ fn main() {
 }
 }
 
 
 static App: Component = |cx| {
 static App: Component = |cx| {
-    let count = use_state(&cx, || 0);
+    let mut count = use_state(&cx, || 0);
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {
             h1 { "High-Five counter: {count}" }
             h1 { "High-Five counter: {count}" }
-            button { onclick: move |_| *count.modify() += 1, "Up high!" }
-            button { onclick: move |_| *count.modify() -= 1, "Down low!" }
+            button { onclick: move |_| count += 1, "Up high!" }
+            button { onclick: move |_| count -= 1, "Down low!" }
         }
         }
     })
     })
 };
 };

+ 61 - 0
examples/rsx_compile_fail.rs

@@ -0,0 +1,61 @@
+use dioxus::prelude::*;
+
+fn main() {
+    let mut vdom = VirtualDom::new(example);
+    vdom.rebuild();
+
+    let out = dioxus::ssr::render_vdom_cfg(&vdom, |c| c.newline(true).indent(true));
+    println!("{}", out);
+}
+
+fn example(cx: Scope) -> Element {
+    let items = use_state(&cx, || {
+        vec![Thing {
+            a: "asd".to_string(),
+            b: 10,
+        }]
+    });
+
+    let things = use_ref(&cx, || {
+        vec![Thing {
+            a: "asd".to_string(),
+            b: 10,
+        }]
+    });
+    let things_list = things.read();
+
+    let mything = use_ref(&cx, || Some(String::from("asd")));
+    let mything_read = mything.read();
+
+    cx.render(rsx!(
+        div {
+            div {
+                id: "asd",
+                "your neighborhood spiderman"
+
+                items.iter().cycle().take(5).map(|f| rsx!{
+                    div {
+                        "{f.a}"
+                    }
+                })
+
+                things_list.iter().map(|f| rsx!{
+                    div {
+                        "{f.a}"
+                    }
+                })
+
+                mything_read.as_ref().map(|f| rsx!{
+                    div {
+                       "{f}"
+                    }
+                })
+            }
+        }
+    ))
+}
+
+struct Thing {
+    a: String,
+    b: u32,
+}

+ 54 - 29
examples/rsx_usage.rs

@@ -60,6 +60,7 @@ pub static EXAMPLE: Component = |cx| {
             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] }
             h2 {
             h2 {
                 "Multiple"
                 "Multiple"
                 "Text"
                 "Text"
@@ -72,7 +73,7 @@ pub static EXAMPLE: Component = |cx| {
                 h3 {"elements"}
                 h3 {"elements"}
             }
             }
             div {
             div {
-                class: "my special div"
+                class: "my special div",
                 h1 {"Headers and attributes!"}
                 h1 {"Headers and attributes!"}
             }
             }
             div {
             div {
@@ -88,42 +89,51 @@ pub static EXAMPLE: Component = |cx| {
             }
             }
 
 
             // Expressions can be used in element position too:
             // Expressions can be used in element position too:
-            {rsx!(p { "More templating!" })}
-            // {html!(<p>"Even HTML templating!!"</p>)}
+            rsx!(p { "More templating!" }),
 
 
             // Iterators
             // Iterators
-            {(0..10).map(|i| rsx!(li { "{i}" }))}
-            {{
+            (0..10).map(|i| rsx!(li { "{i}" })),
+
+            // Iterators within expressions
+            {
                 let data = std::collections::HashMap::<&'static str, &'static str>::new();
                 let data = std::collections::HashMap::<&'static str, &'static str>::new();
                 // Iterators *should* have keys when you can provide them.
                 // Iterators *should* have keys when you can provide them.
                 // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
                 // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
                 // Using an "ID" associated with your data is a good idea.
                 // Using an "ID" associated with your data is a good idea.
-                data.into_iter().map(|(k, v)| rsx!(li { key: "{k}" "{v}" }))
-            }}
+                data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
+            }
 
 
             // Matching
             // Matching
-            {match true {
+            match true {
                 true => rsx!( h1 {"Top text"}),
                 true => rsx!( h1 {"Top text"}),
                 false => rsx!( h1 {"Bottom text"})
                 false => rsx!( h1 {"Bottom text"})
-            }}
+            }
 
 
             // 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
+            if false {
+                rsx!(h1 {"Top text"})
+            } else {
+                rsx!(h1 {"Bottom text"})
+            }
 
 
-            // True conditions need to be rendered (same reasons as matching)
-            {if true {
-                rsx!(cx, h1 {"Top text"})
+            // Using optionals for diverging branches
+            if true {
+                Some(rsx!(h1 {"Top text"}))
             } else {
             } else {
-                rsx!(cx, h1 {"Bottom text"})
-            }}
+                None
+            }
+
 
 
-            // returning "None" is a bit noisy... but rare in practice
-            {None as Option<()>}
+            // returning "None" without a diverging branch is a bit noisy... but rare in practice
+            None as Option<()>,
 
 
             // Use the Dioxus type-alias for less noise
             // Use the Dioxus type-alias for less noise
-            {NONE_ELEMENT}
+            NONE_ELEMENT,
 
 
             // can also just use empty fragments
             // can also just use empty fragments
             Fragment {}
             Fragment {}
@@ -137,9 +147,8 @@ pub static EXAMPLE: Component = |cx| {
                 Fragment {
                 Fragment {
                     "D"
                     "D"
                     Fragment {
                     Fragment {
-                        "heavily nested fragments is an antipattern"
-                        "they cause Dioxus to do unnecessary work"
-                        "don't use them carelessly if you can help it"
+                        "E"
+                        "F"
                     }
                     }
                 }
                 }
             }
             }
@@ -158,22 +167,29 @@ pub static EXAMPLE: Component = |cx| {
             Taller { a: "asd" }
             Taller { a: "asd" }
 
 
             // Can pass in props directly as an expression
             // Can pass in props directly as an expression
-            {{
+            {
                 let props = TallerProps {a: "hello", children: Default::default()};
                 let props = TallerProps {a: "hello", children: Default::default()};
                 rsx!(Taller { ..props })
                 rsx!(Taller { ..props })
-            }}
+            }
 
 
             // Spreading can also be overridden manually
             // Spreading can also be overridden manually
             Taller {
             Taller {
-                ..TallerProps { a: "ballin!", children: Default::default() }
+                ..TallerProps { a: "ballin!", children: Default::default() },
                 a: "not ballin!"
                 a: "not ballin!"
             }
             }
 
 
             // Can take children too!
             // Can take children too!
             Taller { a: "asd", div {"hello world!"} }
             Taller { a: "asd", div {"hello world!"} }
 
 
+            // Components can be used with the `call` syntax
+            // This component's props are defined *inline* with the `inline_props` macro
+            with_inline(
+                text: "using functionc all syntax"
+            )
+
             // helper functions
             // helper functions
-            {helper(&cx, "hello world!")}
+            // Single values must be wrapped in braces or `Some` to satisfy `IntoIterator`
+            [helper(&cx, "hello world!")]
         }
         }
     })
     })
 };
 };
@@ -187,6 +203,7 @@ mod baller {
     #[derive(Props, PartialEq)]
     #[derive(Props, PartialEq)]
     pub struct BallerProps {}
     pub struct BallerProps {}
 
 
+    #[allow(non_snake_case)]
     /// This component totally balls
     /// This component totally balls
     pub fn Baller(_: Scope<BallerProps>) -> Element {
     pub fn Baller(_: Scope<BallerProps>) -> Element {
         todo!()
         todo!()
@@ -195,12 +212,20 @@ mod baller {
 
 
 #[derive(Props)]
 #[derive(Props)]
 pub struct TallerProps<'a> {
 pub struct TallerProps<'a> {
+    /// Fields are documented and accessible in rsx!
     a: &'static str,
     a: &'static str,
     children: Element<'a>,
     children: Element<'a>,
 }
 }
 
 
-/// This component is taller than most :)
-pub fn Taller<'a>(_: Scope<'a, TallerProps<'a>>) -> Element {
-    let b = true;
-    todo!()
+/// Documention for this component is visible within the rsx macro
+#[allow(non_snake_case)]
+pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
+    cx.render(rsx! {
+        &cx.props.children
+    })
+}
+
+#[inline_props]
+fn with_inline<'a>(cx: Scope<'a>, text: &'a str) -> Element {
+    rsx!(cx, p { "{text}" })
 }
 }

+ 38 - 38
examples/tailwind.rs

@@ -16,9 +16,9 @@ const STYLE: &str = "body {overflow:hidden;}";
 
 
 pub static App: Component = |cx| {
 pub static App: Component = |cx| {
     cx.render(rsx!(
     cx.render(rsx!(
-        div { class: "overflow-hidden"
+        div { class: "overflow-hidden",
         style { "{STYLE}" }
         style { "{STYLE}" }
-            link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" }
+            link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
             Header {}
             Header {}
             Entry {}
             Entry {}
             Hero {}
             Hero {}
@@ -33,20 +33,20 @@ pub static App: Component = |cx| {
 pub static Header: Component = |cx| {
 pub static Header: Component = |cx| {
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {
-            header { class: "text-gray-400 bg-gray-900 body-font"
-                div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center"
-                    a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0"
+            header { class: "text-gray-400 bg-gray-900 body-font",
+                div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
+                    a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
                         StacksIcon {}
                         StacksIcon {}
-                        span { class: "ml-3 text-xl" "Hello Dioxus!"}
+                        span { class: "ml-3 text-xl", "Hello Dioxus!"}
                     }
                     }
-                    nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center"
-                        a { class: "mr-5 hover:text-white" "First Link"}
-                        a { class: "mr-5 hover:text-white" "Second Link"}
-                        a { class: "mr-5 hover:text-white" "Third Link"}
-                        a { class: "mr-5 hover:text-white" "Fourth Link"}
+                    nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center",
+                        a { class: "mr-5 hover:text-white", "First Link"}
+                        a { class: "mr-5 hover:text-white", "Second Link"}
+                        a { class: "mr-5 hover:text-white", "Third Link"}
+                        a { class: "mr-5 hover:text-white", "Fourth Link"}
                     }
                     }
                     button {
                     button {
-                        class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0"
+                        class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
                         "Button"
                         "Button"
                         RightArrowIcon {}
                         RightArrowIcon {}
                     }
                     }
@@ -59,34 +59,34 @@ pub static Header: Component = |cx| {
 pub static Hero: Component = |cx| {
 pub static Hero: Component = |cx| {
     //
     //
     cx.render(rsx! {
     cx.render(rsx! {
-        section{ class: "text-gray-400 bg-gray-900 body-font"
-            div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center"
-                div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center"
-                    h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white"
+        section{ class: "text-gray-400 bg-gray-900 body-font",
+            div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center",
+                div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center",
+                    h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white",
                         br { class: "hidden lg:inline-block" }
                         br { class: "hidden lg:inline-block" }
                         "Dioxus Sneak Peek"
                         "Dioxus Sneak Peek"
                     }
                     }
                     p {
                     p {
-                        class: "mb-8 leading-relaxed"
+                        class: "mb-8 leading-relaxed",
 
 
                         "Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web
                         "Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web
                         technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and
                         technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and
                         on mobile and embedded platforms."
                         on mobile and embedded platforms."
 
 
                     }
                     }
-                    div { class: "flex justify-center"
+                    div { class: "flex justify-center",
                         button {
                         button {
-                            class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg"
+                            class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg",
                             "Learn more"
                             "Learn more"
                         }
                         }
                         button {
                         button {
-                            class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg" 
+                            class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg",
                             "Build an app"
                             "Build an app"
                         }
                         }
                     }
                     }
                 }
                 }
-                div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6"
-                    img { class: "object-cover object-center rounded" alt: "hero" src: "https://i.imgur.com/oK6BLtw.png" 
+                div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6",
+                    img { class: "object-cover object-center rounded", alt: "hero", src: "https://i.imgur.com/oK6BLtw.png",
                     referrerpolicy:"no-referrer"
                     referrerpolicy:"no-referrer"
                 }
                 }
                 }
                 }
@@ -97,8 +97,8 @@ pub static Hero: Component = |cx| {
 pub static Entry: Component = |cx| {
 pub static Entry: Component = |cx| {
     //
     //
     cx.render(rsx! {
     cx.render(rsx! {
-        section{ class: "text-gray-400 bg-gray-900 body-font"
-            div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center"
+        section{ class: "text-gray-400 bg-gray-900 body-font",
+            div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center",
                 textarea {
                 textarea {
 
 
                 }
                 }
@@ -111,13 +111,13 @@ pub static StacksIcon: Component = |cx| {
     cx.render(rsx!(
     cx.render(rsx!(
         svg {
         svg {
             // xmlns: "http://www.w3.org/2000/svg"
             // xmlns: "http://www.w3.org/2000/svg"
-            fill: "none"
-            stroke: "currentColor"
-            stroke_linecap: "round"
-            stroke_linejoin: "round"
-            stroke_width: "2"
-            class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full"
-            view_box: "0 0 24 24"
+            fill: "none",
+            stroke: "currentColor",
+            stroke_linecap: "round",
+            stroke_linejoin: "round",
+            stroke_width: "2",
+            class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full",
+            view_box: "0 0 24 24",
             path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"}
             path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"}
         }
         }
     ))
     ))
@@ -125,13 +125,13 @@ pub static StacksIcon: Component = |cx| {
 pub static RightArrowIcon: Component = |cx| {
 pub static RightArrowIcon: Component = |cx| {
     cx.render(rsx!(
     cx.render(rsx!(
         svg {
         svg {
-            fill: "none"
-            stroke: "currentColor"
-            stroke_linecap: "round"
-            stroke_linejoin: "round"
-            stroke_width: "2"
-            class: "w-4 h-4 ml-1"
-            view_box: "0 0 24 24"
+            fill: "none",
+            stroke: "currentColor",
+            stroke_linecap: "round",
+            stroke_linejoin: "round",
+            stroke_width: "2",
+            class: "w-4 h-4 ml-1",
+            view_box: "0 0 24 24",
             path { d: "M5 12h14M12 5l7 7-7 7"}
             path { d: "M5 12h14M12 5l7 7-7 7"}
         }
         }
     ))
     ))

+ 3 - 2
examples/tasks.rs

@@ -15,8 +15,9 @@ fn app(cx: Scope) -> Element {
     use_future(&cx, || {
     use_future(&cx, || {
         for_async![count];
         for_async![count];
         async move {
         async move {
-            while let _ = tokio::time::sleep(Duration::from_millis(1000)).await {
-                *count.modify() += 1;
+            loop {
+                tokio::time::sleep(Duration::from_millis(1000)).await;
+                count += 1;
             }
             }
         }
         }
     });
     });

+ 21 - 18
examples/todomvc.rs

@@ -29,7 +29,7 @@ const App: Component = |cx| {
 
 
     let todolist = todos
     let todolist = todos
         .iter()
         .iter()
-        .filter(|(id, item)| match *filter {
+        .filter(|(_id, item)| match *filter {
             FilterState::All => true,
             FilterState::All => true,
             FilterState::Active => !item.checked,
             FilterState::Active => !item.checked,
             FilterState::Completed => item.checked,
             FilterState::Completed => item.checked,
@@ -48,34 +48,37 @@ const App: Component = |cx| {
         _ => "items",
         _ => "items",
     };
     };
 
 
-    rsx!(cx, div { id: "app"
+    rsx!(cx, div { id: "app",
         style {"{STYLE}"}
         style {"{STYLE}"}
         div {
         div {
-            header { class: "header"
+            header { class: "header",
                 h1 {"todos"}
                 h1 {"todos"}
                 input {
                 input {
-                    class: "new-todo"
-                    placeholder: "What needs to be done?"
-                    value: "{draft}"
-                    oninput: move |evt| draft.set(evt.value.clone())
+                    class: "new-todo",
+                    placeholder: "What needs to be done?",
+                    value: "{draft}",
+                    oninput: move |evt| draft.set(evt.value.clone()),
                 }
                 }
             }
             }
-            {todolist}
-            {(!todos.is_empty()).then(|| rsx!(
+            todolist,
+            (!todos.is_empty()).then(|| rsx!(
                 footer {
                 footer {
-                    span { strong {"{items_left}"} span {"{item_text} left"} }
-                    ul { class: "filters"
+                    span {
+                        strong {"{items_left}"}
+                        span {"{item_text} left"}
+                    }
+                    ul { class: "filters",
                         li { class: "All", a { href: "", onclick: move |_| filter.set(FilterState::All), "All" }}
                         li { class: "All", a { href: "", onclick: move |_| filter.set(FilterState::All), "All" }}
                         li { class: "Active", a { href: "active", onclick: move |_| filter.set(FilterState::Active), "Active" }}
                         li { class: "Active", a { href: "active", onclick: move |_| filter.set(FilterState::Active), "Active" }}
                         li { class: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
                         li { class: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
                     }
                     }
                 }
                 }
-            ))}
+            ))
         }
         }
-        footer { class: "info"
+        footer { class: "info",
             p {"Double-click to edit a todo"}
             p {"Double-click to edit a todo"}
-            p { "Created by ", a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }}
-            p { "Part of ", a { "TodoMVC", href: "http://todomvc.com" }}
+            p { "Created by ", a {  href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
+            p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
         }
         }
     })
     })
 };
 };
@@ -93,13 +96,13 @@ pub fn TodoEntry(cx: Scope<TodoEntryProps>) -> Element {
     rsx!(cx, li {
     rsx!(cx, li {
         "{todo.id}"
         "{todo.id}"
         input {
         input {
-            class: "toggle"
-            r#type: "checkbox"
+            class: "toggle",
+            r#type: "checkbox",
             "{todo.checked}"
             "{todo.checked}"
         }
         }
        {is_editing.then(|| rsx!{
        {is_editing.then(|| rsx!{
             input {
             input {
-                value: "{contents}"
+                value: "{contents}",
                 oninput: move |evt| contents.set(evt.value.clone())
                 oninput: move |evt| contents.set(evt.value.clone())
             }
             }
         })}
         })}

+ 9 - 18
examples/web_tick.rs

@@ -13,28 +13,19 @@
 use dioxus::prelude::*;
 use dioxus::prelude::*;
 
 
 fn main() {
 fn main() {
-    #[cfg(target_arch = "wasm32")]
-    intern_strings();
-
-    dioxus::web::launch(App);
+    dioxus::desktop::launch(App);
 }
 }
 
 
 static App: Component = |cx| {
 static App: Component = |cx| {
     let mut rng = SmallRng::from_entropy();
     let mut rng = SmallRng::from_entropy();
-    let rows = (0..1_000).map(|f| {
-        let label = Label::new(&mut rng);
-        rsx! {
-            Row {
-                row_id: f,
-                label: label
-            }
-        }
-    });
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         table {
         table {
             tbody {
             tbody {
-                {rows}
+                (0..1_000).map(|f| {
+                    let label = Label::new(&mut rng);
+                    rsx! (Row { row_id: f, label: label })
+                })
             }
             }
         }
         }
     })
     })
@@ -50,12 +41,12 @@ fn Row(cx: Scope<RowProps>) -> Element {
     cx.render(rsx! {
     cx.render(rsx! {
         tr {
         tr {
             td { class:"col-md-1", "{cx.props.row_id}" }
             td { class:"col-md-1", "{cx.props.row_id}" }
-            td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+            td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
                 a { class: "lbl", "{adj}" "{col}" "{noun}" }
                 a { class: "lbl", "{adj}" "{col}" "{noun}" }
             }
             }
-            td { class: "col-md-1"
-                a { class: "remove", onclick: move |_| {/* remove */}
-                    span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+            td { class: "col-md-1",
+                a { class: "remove", onclick: move |_| {/* remove */},
+                    span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
                 }
                 }
             }
             }
             td { class: "col-md-6" }
             td { class: "col-md-6" }

+ 3 - 3
examples/webview_web.rs

@@ -16,13 +16,13 @@ fn main() {
 }
 }
 
 
 fn app(cx: Scope) -> Element {
 fn app(cx: Scope) -> Element {
-    let count = use_state(&cx, || 0);
+    let mut count = use_state(&cx, || 0);
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         div {
         div {
             h1 { "Hifive counter: {count}" }
             h1 { "Hifive counter: {count}" }
-            button { onclick: move |_| *count.modify() += 1, "Up high!" }
-            button { onclick: move |_| *count.modify() -= 1, "Down low!" }
+            button { onclick: move |_| count += 1, "Up high!" }
+            button { onclick: move |_| count -= 1, "Down low!" }
         }
         }
     })
     })
 }
 }

+ 31 - 0
examples/xss_safety.rs

@@ -0,0 +1,31 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let contents = use_state(&cx, || String::from("<script>alert(123)</script>"));
+
+    cx.render(rsx! {
+        div {
+            "hello world!"
+
+            h1 {
+                "{contents}"
+            }
+            h3 {
+                [contents.as_str()]
+            }
+
+            input {
+                value: "{contents}",
+                oninput: move |e| {
+                    contents.set(e.value.clone());
+                    eprintln!("asd");
+                },
+                "type": "text",
+            }
+        }
+    })
+}

+ 1 - 0
packages/core-macro/Cargo.toml

@@ -16,6 +16,7 @@ proc-macro = true
 
 
 [dependencies]
 [dependencies]
 once_cell = "1.8"
 once_cell = "1.8"
+proc-macro-error = "1.0.4"
 proc-macro2 = { version = "1.0.6" }
 proc-macro2 = { version = "1.0.6" }
 quote = "1.0"
 quote = "1.0"
 syn = { version = "1.0.11", features = ["full", "extra-traits"] }
 syn = { version = "1.0.11", features = ["full", "extra-traits"] }

+ 1 - 0
packages/core-macro/src/inlineprops.rs

@@ -99,6 +99,7 @@ impl ToTokens for InlinePropsBody {
         };
         };
 
 
         out_tokens.append_all(quote! {
         out_tokens.append_all(quote! {
+            #[allow(non_camel_case)]
             #modifiers
             #modifiers
             #vis struct #struct_name #generics {
             #vis struct #struct_name #generics {
                 #(#fields),*
                 #(#fields),*

+ 3 - 2
packages/core-macro/src/lib.rs

@@ -177,11 +177,12 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 ///     todo!()
 ///     todo!()
 /// }
 /// }
 /// ```
 /// ```
+#[proc_macro_error::proc_macro_error]
 #[proc_macro]
 #[proc_macro]
 pub fn rsx(s: TokenStream) -> TokenStream {
 pub fn rsx(s: TokenStream) -> TokenStream {
     match syn::parse::<rsx::CallBody>(s) {
     match syn::parse::<rsx::CallBody>(s) {
-        Err(e) => e.to_compile_error().into(),
-        Ok(s) => s.to_token_stream().into(),
+        Err(err) => err.to_compile_error().into(),
+        Ok(stream) => stream.to_token_stream().into(),
     }
     }
 }
 }
 
 

+ 0 - 64
packages/core-macro/src/rsx/ambiguous.rs

@@ -1,64 +0,0 @@
-//! Parse anything that has a pattern of < Ident, Bracket >
-//! ========================================================
-//!
-//! Whenever a `name {}` pattern emerges, we need to parse it into an element, a component, or a fragment.
-//! This feature must support:
-//! - Namepsaced/pathed components
-//! - Differentiating between built-in and custom elements
-
-use super::*;
-
-use proc_macro2::TokenStream as TokenStream2;
-use quote::ToTokens;
-use syn::{
-    parse::{Parse, ParseStream},
-    Error, Ident, Result, Token,
-};
-
-#[allow(clippy::large_enum_variant)]
-pub enum AmbiguousElement {
-    Element(Element),
-    Component(Component),
-}
-
-impl Parse for AmbiguousElement {
-    fn parse(input: ParseStream) -> Result<Self> {
-        // Try to parse as an absolute path and immediately defer to the componetn
-        if input.peek(Token![::]) {
-            return input.parse::<Component>().map(AmbiguousElement::Component);
-        }
-
-        // If not an absolute path, then parse the ident and check if it's a valid tag
-        if let Ok(pat) = input.fork().parse::<syn::Path>() {
-            if pat.segments.len() > 1 {
-                return input.parse::<Component>().map(AmbiguousElement::Component);
-            }
-        }
-
-        use syn::ext::IdentExt;
-        if let Ok(name) = input.fork().call(Ident::parse_any) {
-            let name_str = name.to_string();
-
-            let first_char = name_str.chars().next().unwrap();
-            if first_char.is_ascii_uppercase() {
-                input.parse::<Component>().map(AmbiguousElement::Component)
-            } else {
-                if input.peek2(syn::token::Paren) {
-                    input.parse::<Component>().map(AmbiguousElement::Component)
-                } else {
-                    input.parse::<Element>().map(AmbiguousElement::Element)
-                }
-            }
-        } else {
-            Err(Error::new(input.span(), "Not a valid Html tag"))
-        }
-    }
-}
-impl ToTokens for AmbiguousElement {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        match self {
-            AmbiguousElement::Element(el) => el.to_tokens(tokens),
-            AmbiguousElement::Component(comp) => comp.to_tokens(tokens),
-        }
-    }
-}

+ 0 - 66
packages/core-macro/src/rsx/body.rs

@@ -1,66 +0,0 @@
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::{
-    parse::{Parse, ParseStream},
-    Ident, Result, Token,
-};
-
-use super::*;
-
-pub struct CallBody {
-    custom_context: Option<Ident>,
-    roots: Vec<BodyNode>,
-}
-
-/// The custom rusty variant of parsing rsx!
-impl Parse for CallBody {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let custom_context = try_parse_custom_context(input)?;
-        let (_, roots, _) = BodyConfig::new_call_body().parse_component_body(input)?;
-        Ok(Self {
-            custom_context,
-            roots,
-        })
-    }
-}
-
-fn try_parse_custom_context(input: ParseStream) -> Result<Option<Ident>> {
-    let res = if input.peek(Ident) && input.peek2(Token![,]) {
-        let name = input.parse::<Ident>()?;
-        input.parse::<Token![,]>()?;
-        Some(name)
-    } else {
-        None
-    };
-    Ok(res)
-}
-
-/// Serialize the same way, regardless of flavor
-impl ToTokens for CallBody {
-    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        let inner = if self.roots.len() == 1 {
-            let inner = &self.roots[0];
-            quote! {#inner}
-        } else {
-            let childs = &self.roots;
-            quote! { __cx.fragment_root([ #(#childs),* ]) }
-        };
-
-        match &self.custom_context {
-            // The `in cx` pattern allows directly rendering
-            Some(ident) => out_tokens.append_all(quote! {
-                #ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
-                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
-                    #inner
-                }))
-            }),
-            // Otherwise we just build the LazyNode wrapper
-            None => out_tokens.append_all(quote! {
-                LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
-                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
-                    #inner
-                })
-            }),
-        };
-    }
-}

+ 11 - 86
packages/core-macro/src/rsx/component.rs

@@ -19,11 +19,10 @@ use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
 use syn::{
     ext::IdentExt,
     ext::IdentExt,
     parse::{Parse, ParseBuffer, ParseStream},
     parse::{Parse, ParseBuffer, ParseStream},
-    token, Error, Expr, ExprClosure, Ident, Result, Token,
+    token, Expr, ExprClosure, Ident, Result, Token,
 };
 };
 
 
 pub struct Component {
 pub struct Component {
-    // accept any path-like argument
     name: syn::Path,
     name: syn::Path,
     body: Vec<ComponentField>,
     body: Vec<ComponentField>,
     children: Vec<BodyNode>,
     children: Vec<BodyNode>,
@@ -32,12 +31,8 @@ pub struct Component {
 
 
 impl Parse for Component {
 impl Parse for Component {
     fn parse(stream: ParseStream) -> Result<Self> {
     fn parse(stream: ParseStream) -> Result<Self> {
-        // let name = s.parse::<syn::ExprPath>()?;
-        // todo: look into somehow getting the crate/super/etc
-
         let name = syn::Path::parse_mod_style(stream)?;
         let name = syn::Path::parse_mod_style(stream)?;
 
 
-        // parse the guts
         let content: ParseBuffer;
         let content: ParseBuffer;
 
 
         // if we see a `{` then we have a block
         // if we see a `{` then we have a block
@@ -48,91 +43,32 @@ impl Parse for Component {
             syn::parenthesized!(content in stream);
             syn::parenthesized!(content in stream);
         }
         }
 
 
-        let cfg: BodyConfig = BodyConfig {
-            allow_children: true,
-            allow_fields: true,
-            allow_manual_props: true,
-        };
-
-        let (body, children, manual_props) = cfg.parse_component_body(&content)?;
-
-        Ok(Self {
-            name,
-            body,
-            children,
-            manual_props,
-        })
-    }
-}
-
-pub struct BodyConfig {
-    pub allow_fields: bool,
-    pub allow_children: bool,
-    pub allow_manual_props: bool,
-}
-
-impl BodyConfig {
-    /// The configuration to parse the root
-    pub fn new_call_body() -> Self {
-        Self {
-            allow_children: true,
-            allow_fields: false,
-            allow_manual_props: false,
-        }
-    }
-}
-
-impl BodyConfig {
-    // todo: unify this body parsing for both elements and components
-    // both are style rather ad-hoc, though components are currently more configured
-    pub fn parse_component_body(
-        &self,
-        content: &ParseBuffer,
-    ) -> Result<(Vec<ComponentField>, Vec<BodyNode>, Option<Expr>)> {
         let mut body = Vec::new();
         let mut body = Vec::new();
         let mut children = Vec::new();
         let mut children = Vec::new();
         let mut manual_props = None;
         let mut manual_props = None;
 
 
-        'parsing: loop {
-            // [1] Break if empty
-            if content.is_empty() {
-                break 'parsing;
-            }
-
+        while !content.is_empty() {
+            // if we splat into a component then we're merging properties
             if content.peek(Token![..]) {
             if content.peek(Token![..]) {
-                if !self.allow_manual_props {
-                    return Err(Error::new(
-                        content.span(),
-                        "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
-                    ));
-                }
                 content.parse::<Token![..]>()?;
                 content.parse::<Token![..]>()?;
                 manual_props = Some(content.parse::<Expr>()?);
                 manual_props = Some(content.parse::<Expr>()?);
             } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
             } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-                if !self.allow_fields {
-                    return Err(Error::new(
-                        content.span(),
-                        "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
-                    ));
-                }
                 body.push(content.parse::<ComponentField>()?);
                 body.push(content.parse::<ComponentField>()?);
             } else {
             } else {
-                if !self.allow_children {
-                    return Err(Error::new(
-                        content.span(),
-                        "This item is not allowed to accept children.",
-                    ));
-                }
                 children.push(content.parse::<BodyNode>()?);
                 children.push(content.parse::<BodyNode>()?);
             }
             }
 
 
-            // consume comma if it exists
-            // we don't actually care if there *are* commas between attrs
             if content.peek(Token![,]) {
             if content.peek(Token![,]) {
                 let _ = content.parse::<Token![,]>();
                 let _ = content.parse::<Token![,]>();
             }
             }
         }
         }
-        Ok((body, children, manual_props))
+
+        Ok(Self {
+            name,
+            body,
+            children,
+            manual_props,
+        })
     }
     }
 }
 }
 
 
@@ -219,9 +155,7 @@ pub struct ComponentField {
 
 
 enum ContentField {
 enum ContentField {
     ManExpr(Expr),
     ManExpr(Expr),
-    OnHandler(ExprClosure),
 
 
-    // A handler was provided in {} tokens
     OnHandlerRaw(Expr),
     OnHandlerRaw(Expr),
 }
 }
 
 
@@ -229,9 +163,6 @@ impl ToTokens for ContentField {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match self {
         match self {
             ContentField::ManExpr(e) => e.to_tokens(tokens),
             ContentField::ManExpr(e) => e.to_tokens(tokens),
-            ContentField::OnHandler(e) => tokens.append_all(quote! {
-                __cx.bump().alloc(#e)
-            }),
             ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
             ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
                 __cx.bump().alloc(#e)
                 __cx.bump().alloc(#e)
             }),
             }),
@@ -246,13 +177,7 @@ impl Parse for ComponentField {
 
 
         let name_str = name.to_string();
         let name_str = name.to_string();
         let content = if name_str.starts_with("on") {
         let content = if name_str.starts_with("on") {
-            if input.peek(token::Brace) {
-                let content;
-                syn::braced!(content in input);
-                ContentField::OnHandlerRaw(content.parse()?)
-            } else {
-                ContentField::OnHandler(input.parse()?)
-            }
+            ContentField::OnHandlerRaw(input.parse()?)
         } else {
         } else {
             ContentField::ManExpr(input.parse::<Expr>()?)
             ContentField::ManExpr(input.parse::<Expr>()?)
         };
         };

+ 114 - 61
packages/core-macro/src/rsx/element.rs

@@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
 use quote::{quote, ToTokens, TokenStreamExt};
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
 use syn::{
     parse::{Parse, ParseBuffer, ParseStream},
     parse::{Parse, ParseBuffer, ParseStream},
-    token, Expr, ExprClosure, Ident, LitStr, Result, Token,
+    Expr, Ident, LitStr, Result, Token,
 };
 };
 
 
 // =======================================
 // =======================================
@@ -33,45 +33,69 @@ impl Parse for Element {
         let mut key = None;
         let mut key = None;
         let mut _el_ref = None;
         let mut _el_ref = None;
 
 
-        // todo: more descriptive error handling
-        while !content.is_empty() {
+        // parse fields with commas
+        // break when we don't get this pattern anymore
+        // start parsing bodynodes
+        // "def": 456,
+        // abc: 123,
+        loop {
+            // Parse the raw literal fields
+            if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+                let name = content.parse::<LitStr>()?;
+                let ident = name.clone();
+
+                content.parse::<Token![:]>()?;
+
+                if content.peek(LitStr) {
+                    let value = content.parse::<LitStr>()?;
+                    attributes.push(ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr::CustomAttrText { name, value },
+                    });
+                } else {
+                    let value = content.parse::<Expr>()?;
+                    attributes.push(ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr::CustomAttrExpression { name, value },
+                    });
+                }
+
+                if content.is_empty() {
+                    break;
+                }
+
+                // todo: add a message saying you need to include commas between fields
+                if content.parse::<Token![,]>().is_err() {
+                    proc_macro_error::emit_error!(
+                        ident,
+                        "This attribute is misisng a trailing comma"
+                    )
+                }
+                continue;
+            }
+
             if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
             if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
                 let name = content.parse::<Ident>()?;
                 let name = content.parse::<Ident>()?;
+                let ident = name.clone();
+
                 let name_str = name.to_string();
                 let name_str = name.to_string();
                 content.parse::<Token![:]>()?;
                 content.parse::<Token![:]>()?;
 
 
                 if name_str.starts_with("on") {
                 if name_str.starts_with("on") {
-                    if content.peek(token::Brace) {
-                        let mycontent;
-                        syn::braced!(mycontent in content);
-
-                        listeners.push(ElementAttrNamed {
-                            el_name: el_name.clone(),
-                            attr: ElementAttr::EventTokens {
-                                name,
-                                tokens: mycontent.parse()?,
-                            },
-                        });
-                    } else {
-                        listeners.push(ElementAttrNamed {
-                            el_name: el_name.clone(),
-                            attr: ElementAttr::EventClosure {
-                                name,
-                                closure: content.parse()?,
-                            },
-                        });
-                    };
+                    listeners.push(ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr::EventTokens {
+                            name,
+                            tokens: content.parse()?,
+                        },
+                    });
                 } else {
                 } else {
                     match name_str.as_str() {
                     match name_str.as_str() {
                         "key" => {
                         "key" => {
                             key = Some(content.parse()?);
                             key = Some(content.parse()?);
                         }
                         }
-                        "classes" => {
-                            todo!("custom class list not supported")
-                        }
-                        "namespace" => {
-                            todo!("custom namespace not supported")
-                        }
+                        "classes" => todo!("custom class list not supported yet"),
+                        // "namespace" => todo!("custom namespace not supported yet"),
                         "node_ref" => {
                         "node_ref" => {
                             _el_ref = Some(content.parse::<Expr>()?);
                             _el_ref = Some(content.parse::<Expr>()?);
                         }
                         }
@@ -96,27 +120,56 @@ impl Parse for Element {
                         }
                         }
                     }
                     }
                 }
                 }
-            } else if content.peek(LitStr) && content.peek2(Token![:]) {
-                let name = content.parse::<LitStr>()?;
-                content.parse::<Token![:]>()?;
 
 
-                if content.peek(LitStr) {
-                    let value = content.parse::<LitStr>()?;
-                    attributes.push(ElementAttrNamed {
-                        el_name: el_name.clone(),
-                        attr: ElementAttr::CustomAttrText { name, value },
-                    });
-                } else {
-                    let value = content.parse::<Expr>()?;
-                    attributes.push(ElementAttrNamed {
-                        el_name: el_name.clone(),
-                        attr: ElementAttr::CustomAttrExpression { name, value },
-                    });
+                if content.is_empty() {
+                    break;
                 }
                 }
-            } else {
-                children.push(content.parse::<BodyNode>()?);
+
+                // todo: add a message saying you need to include commas between fields
+                if content.parse::<Token![,]>().is_err() {
+                    proc_macro_error::emit_error!(
+                        ident,
+                        "This attribute is misisng a trailing comma"
+                    )
+                }
+                continue;
             }
             }
 
 
+            break;
+        }
+
+        while !content.is_empty() {
+            if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
+                let ident = content.parse::<LitStr>().unwrap();
+                let name = ident.value();
+                proc_macro_error::emit_error!(
+                    ident, "This attribute `{}` is in the wrong place.", name;
+                    help =
+"All attribute fields must be placed above children elements.
+
+                div {
+                   attr: \"...\",  <---- attribute is above children
+                   div { }       <---- children are below attributes
+                }";
+                )
+            }
+
+            if (content.peek(Ident) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
+                let ident = content.parse::<Ident>().unwrap();
+                let name = ident.to_string();
+                proc_macro_error::emit_error!(
+                    ident, "This attribute `{}` is in the wrong place.", name;
+                    help =
+"All attribute fields must be placed above children elements.
+
+                div {
+                   attr: \"...\",  <---- attribute is above children
+                   div { }       <---- children are below attributes
+                }";
+                )
+            }
+
+            children.push(content.parse::<BodyNode>()?);
             // consume comma if it exists
             // consume comma if it exists
             // we don't actually care if there *are* commas after elements/text
             // we don't actually care if there *are* commas after elements/text
             if content.peek(Token![,]) {
             if content.peek(Token![,]) {
@@ -138,7 +191,7 @@ impl Parse for Element {
 impl ToTokens for Element {
 impl ToTokens for Element {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = &self.name;
         let name = &self.name;
-        let childs = &self.children;
+        let children = &self.children;
 
 
         let listeners = &self.listeners;
         let listeners = &self.listeners;
         let attr = &self.attributes;
         let attr = &self.attributes;
@@ -153,7 +206,7 @@ impl ToTokens for Element {
                 dioxus_elements::#name,
                 dioxus_elements::#name,
                 [ #(#listeners),* ],
                 [ #(#listeners),* ],
                 [ #(#attr),* ],
                 [ #(#attr),* ],
-                [ #(#childs),* ],
+                [ #(#children),* ],
                 #key,
                 #key,
             )
             )
         });
         });
@@ -173,8 +226,8 @@ enum ElementAttr {
     // "attribute": true,
     // "attribute": true,
     CustomAttrExpression { name: LitStr, value: Expr },
     CustomAttrExpression { name: LitStr, value: Expr },
 
 
-    // onclick: move |_| {}
-    EventClosure { name: Ident, closure: ExprClosure },
+    // // onclick: move |_| {}
+    // EventClosure { name: Ident, closure: ExprClosure },
 
 
     // onclick: {}
     // onclick: {}
     EventTokens { name: Ident, tokens: Expr },
     EventTokens { name: Ident, tokens: Expr },
@@ -189,7 +242,7 @@ impl ToTokens for ElementAttrNamed {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let ElementAttrNamed { el_name, attr } = self;
         let ElementAttrNamed { el_name, attr } = self;
 
 
-        let toks = match attr {
+        tokens.append_all(match attr {
             ElementAttr::AttrText { name, value } => {
             ElementAttr::AttrText { name, value } => {
                 quote! {
                 quote! {
                     dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
                     dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
@@ -200,26 +253,26 @@ impl ToTokens for ElementAttrNamed {
                     dioxus_elements::#el_name.#name(__cx, #value)
                     dioxus_elements::#el_name.#name(__cx, #value)
                 }
                 }
             }
             }
-
             ElementAttr::CustomAttrText { name, value } => {
             ElementAttr::CustomAttrText { name, value } => {
-                quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
+                quote! {
+                    __cx.attr( #name, format_args_f!(#value), None, false )
+                }
             }
             }
             ElementAttr::CustomAttrExpression { name, value } => {
             ElementAttr::CustomAttrExpression { name, value } => {
-                quote! { __cx.attr( #name, format_args_f!(#value), None, false ) }
-            }
-
-            ElementAttr::EventClosure { name, closure } => {
                 quote! {
                 quote! {
-                    dioxus_elements::on::#name(__cx, #closure)
+                    __cx.attr( #name, format_args_f!(#value), None, false )
                 }
                 }
             }
             }
+            // ElementAttr::EventClosure { name, closure } => {
+            //     quote! {
+            //         dioxus_elements::on::#name(__cx, #closure)
+            //     }
+            // }
             ElementAttr::EventTokens { name, tokens } => {
             ElementAttr::EventTokens { name, tokens } => {
                 quote! {
                 quote! {
                     dioxus_elements::on::#name(__cx, #tokens)
                     dioxus_elements::on::#name(__cx, #tokens)
                 }
                 }
             }
             }
-        };
-
-        tokens.append_all(toks);
+        });
     }
     }
 }
 }

+ 0 - 63
packages/core-macro/src/rsx/fragment.rs

@@ -1,63 +0,0 @@
-//! Parse `Fragments` into the Fragment VNode
-//! ==========================================
-//!
-//! This parsing path emerges from [`AmbiguousElement`] which supports validation of the Fragment format.
-//! We can be reasonably sure that whatever enters this parsing path is in the right format.
-//! This feature must support:
-//! - [x] Optional commas
-//! - [ ] Children
-//! - [ ] Keys
-
-use super::AmbiguousElement;
-use syn::parse::ParseBuffer;
-use {
-    proc_macro2::TokenStream as TokenStream2,
-    quote::{quote, ToTokens, TokenStreamExt},
-    syn::{
-        parse::{Parse, ParseStream},
-        Ident, Result, Token,
-    },
-};
-
-pub struct Fragment {
-    children: Vec<AmbiguousElement>,
-}
-
-impl Parse for Fragment {
-    fn parse(input: ParseStream) -> Result<Self> {
-        input.parse::<Ident>()?;
-
-        let children = Vec::new();
-
-        // parse the guts
-        let content: ParseBuffer;
-        syn::braced!(content in input);
-        while !content.is_empty() {
-            content.parse::<AmbiguousElement>()?;
-
-            if content.peek(Token![,]) {
-                let _ = content.parse::<Token![,]>();
-            }
-        }
-        Ok(Self { children })
-    }
-}
-
-impl ToTokens for Fragment {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let childs = &self.children;
-        let children = quote! {
-            ChildrenList::new(__cx)
-                #( .add_child(#childs) )*
-                .finish()
-        };
-        tokens.append_all(quote! {
-            // #key_token,
-            dioxus::builder::vfragment(
-                __cx,
-                None,
-                #children
-            )
-        })
-    }
-}

+ 74 - 6
packages/core-macro/src/rsx/mod.rs

@@ -11,17 +11,85 @@
 //!
 //!
 //! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
 //! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
 
 
-mod ambiguous;
-mod body;
 mod component;
 mod component;
 mod element;
 mod element;
-mod fragment;
 mod node;
 mod node;
 
 
 // Re-export the namespaces into each other
 // Re-export the namespaces into each other
-pub use ambiguous::*;
-pub use body::*;
 pub use component::*;
 pub use component::*;
 pub use element::*;
 pub use element::*;
-pub use fragment::*;
 pub use node::*;
 pub use node::*;
+
+// imports
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{
+    parse::{Parse, ParseStream},
+    Ident, Result, Token,
+};
+
+pub struct CallBody {
+    custom_context: Option<Ident>,
+    roots: Vec<BodyNode>,
+}
+
+impl Parse for CallBody {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let custom_context = if input.peek(Ident) && input.peek2(Token![,]) {
+            let name = input.parse::<Ident>()?;
+            input.parse::<Token![,]>()?;
+
+            Some(name)
+        } else {
+            None
+        };
+
+        let mut roots = Vec::new();
+
+        while !input.is_empty() {
+            let node = input.parse::<BodyNode>()?;
+
+            if input.peek(Token![,]) {
+                let _ = input.parse::<Token![,]>();
+            }
+
+            roots.push(node);
+        }
+
+        Ok(Self {
+            custom_context,
+            roots,
+        })
+    }
+}
+
+/// Serialize the same way, regardless of flavor
+impl ToTokens for CallBody {
+    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
+        let inner = if self.roots.len() == 1 {
+            let inner = &self.roots[0];
+            quote! { #inner }
+        } else {
+            let childs = &self.roots;
+            quote! { __cx.fragment_root([ #(#childs),* ]) }
+        };
+
+        match &self.custom_context {
+            // The `in cx` pattern allows directly rendering
+            Some(ident) => out_tokens.append_all(quote! {
+                #ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
+                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
+                    #inner
+                }))
+            }),
+
+            // Otherwise we just build the LazyNode wrapper
+            None => out_tokens.append_all(quote! {
+                LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
+                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
+                    #inner
+                })
+            }),
+        };
+    }
+}

+ 52 - 37
packages/core-macro/src/rsx/node.rs

@@ -4,34 +4,67 @@ use proc_macro2::TokenStream as TokenStream2;
 use quote::{quote, ToTokens, TokenStreamExt};
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
 use syn::{
     parse::{Parse, ParseStream},
     parse::{Parse, ParseStream},
-    token, Expr, LitStr, Result,
+    token, Expr, LitStr, Result, Token,
 };
 };
 
 
-// ==============================================
-// Parse any div {} as a VElement
-// ==============================================
+/*
+Parse
+-> div {}
+-> Component {}
+-> component()
+-> "text {with_args}"
+-> (0..10).map(|f| rsx!("asd")),  // <--- notice the comma - must be a complete expr
+*/
 pub enum BodyNode {
 pub enum BodyNode {
-    Element(AmbiguousElement),
-    Text(TextNode),
+    Element(Element),
+    Component(Component),
+    Text(LitStr),
     RawExpr(Expr),
     RawExpr(Expr),
 }
 }
 
 
 impl Parse for BodyNode {
 impl Parse for BodyNode {
     fn parse(stream: ParseStream) -> Result<Self> {
     fn parse(stream: ParseStream) -> Result<Self> {
-        // Supposedly this approach is discouraged due to inability to return proper errors
-        // TODO: Rework this to provide more informative errors
+        if stream.peek(LitStr) {
+            return Ok(BodyNode::Text(stream.parse()?));
+        }
 
 
-        if stream.peek(token::Brace) {
-            let content;
-            syn::braced!(content in stream);
-            return Ok(BodyNode::RawExpr(content.parse::<Expr>()?));
+        // div {} -> el
+        // Div {} -> comp
+        if stream.peek(syn::Ident) && stream.peek2(token::Brace) {
+            if stream
+                .fork()
+                .parse::<Ident>()?
+                .to_string()
+                .chars()
+                .next()
+                .unwrap()
+                .is_ascii_uppercase()
+            {
+                return Ok(BodyNode::Component(stream.parse()?));
+            } else {
+                return Ok(BodyNode::Element(stream.parse::<Element>()?));
+            }
         }
         }
 
 
-        if stream.peek(LitStr) {
-            return Ok(BodyNode::Text(stream.parse::<TextNode>()?));
+        // component() -> comp
+        // ::component {} -> comp
+        // ::component () -> comp
+        if (stream.peek(syn::Ident) && stream.peek2(token::Paren))
+            || (stream.peek(Token![::]))
+            || (stream.peek(Token![:]) && stream.peek2(Token![:]))
+        {
+            return Ok(BodyNode::Component(stream.parse::<Component>()?));
         }
         }
 
 
-        Ok(BodyNode::Element(stream.parse::<AmbiguousElement>()?))
+        // crate::component{} -> comp
+        // crate::component() -> comp
+        if let Ok(pat) = stream.fork().parse::<syn::Path>() {
+            if pat.segments.len() > 1 {
+                return Ok(BodyNode::Component(stream.parse::<Component>()?));
+            }
+        }
+
+        Ok(BodyNode::RawExpr(stream.parse::<Expr>()?))
     }
     }
 }
 }
 
 
@@ -39,31 +72,13 @@ impl ToTokens for BodyNode {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match &self {
         match &self {
             BodyNode::Element(el) => el.to_tokens(tokens),
             BodyNode::Element(el) => el.to_tokens(tokens),
-            BodyNode::Text(txt) => txt.to_tokens(tokens),
+            BodyNode::Component(comp) => comp.to_tokens(tokens),
+            BodyNode::Text(txt) => tokens.append_all(quote! {
+                __cx.text(format_args_f!(#txt))
+            }),
             BodyNode::RawExpr(exp) => tokens.append_all(quote! {
             BodyNode::RawExpr(exp) => tokens.append_all(quote! {
                  __cx.fragment_from_iter(#exp)
                  __cx.fragment_from_iter(#exp)
             }),
             }),
         }
         }
     }
     }
 }
 }
-
-// =======================================
-// Parse just plain text
-// =======================================
-pub struct TextNode(LitStr);
-
-impl Parse for TextNode {
-    fn parse(s: ParseStream) -> Result<Self> {
-        Ok(Self(s.parse()?))
-    }
-}
-
-impl ToTokens for TextNode {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        // todo: use heuristics to see if we can promote to &static str
-        let token_stream = &self.0.to_token_stream();
-        tokens.append_all(quote! {
-            __cx.text(format_args_f!(#token_stream))
-        });
-    }
-}

+ 2 - 2
packages/core/src/nodes.rs

@@ -734,9 +734,9 @@ impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
     }
     }
 }
 }
 
 
-impl IntoVNode<'_> for &'static str {
+impl<'b> IntoVNode<'_> for &'b str {
     fn into_vnode(self, cx: NodeFactory) -> VNode {
     fn into_vnode(self, cx: NodeFactory) -> VNode {
-        cx.static_text(self)
+        cx.text(format_args!("{}", self))
     }
     }
 }
 }
 
 

+ 1 - 1
packages/core/tests/vdom_rebuild.rs

@@ -44,7 +44,7 @@ fn lists_work() {
     static App: Component = |cx| {
     static App: Component = |cx| {
         cx.render(rsx!(
         cx.render(rsx!(
             h1 {"hello"}
             h1 {"hello"}
-            {(0..6).map(|f| rsx!(span{ "{f}" }))}
+            (0..6).map(|f| rsx!(span{ "{f}" }))
         ))
         ))
     };
     };
     let mut vdom = VirtualDom::new(App);
     let mut vdom = VirtualDom::new(App);

+ 64 - 0
packages/hooks/src/usestate.rs

@@ -215,3 +215,67 @@ impl<'a, O, T: std::ops::Not<Output = O> + Copy> std::ops::Not for UseState<'a,
         !*self.get()
         !*self.get()
     }
     }
 }
 }
+
+/*
+
+Convenience methods for UseState.
+
+Note!
+
+This is not comprehensive.
+This is *just* meant to make common operations easier.
+*/
+
+use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
+
+impl<'a, T: Copy + Add<T, Output = T>> Add<T> for UseState<'a, T> {
+    type Output = T;
+
+    fn add(self, rhs: T) -> Self::Output {
+        self.inner.current_val.add(rhs)
+    }
+}
+impl<'a, T: Copy + Add<T, Output = T>> AddAssign<T> for UseState<'a, T> {
+    fn add_assign(&mut self, rhs: T) {
+        self.set(self.inner.current_val.add(rhs));
+    }
+}
+impl<'a, T: Copy + Sub<T, Output = T>> Sub<T> for UseState<'a, T> {
+    type Output = T;
+
+    fn sub(self, rhs: T) -> Self::Output {
+        self.inner.current_val.sub(rhs)
+    }
+}
+impl<'a, T: Copy + Sub<T, Output = T>> SubAssign<T> for UseState<'a, T> {
+    fn sub_assign(&mut self, rhs: T) {
+        self.set(self.inner.current_val.sub(rhs));
+    }
+}
+
+/// MUL
+impl<'a, T: Copy + Mul<T, Output = T>> Mul<T> for UseState<'a, T> {
+    type Output = T;
+
+    fn mul(self, rhs: T) -> Self::Output {
+        self.inner.current_val.mul(rhs)
+    }
+}
+impl<'a, T: Copy + Mul<T, Output = T>> MulAssign<T> for UseState<'a, T> {
+    fn mul_assign(&mut self, rhs: T) {
+        self.set(self.inner.current_val.mul(rhs));
+    }
+}
+/// DIV
+impl<'a, T: Copy + Div<T, Output = T>> Div<T> for UseState<'a, T> {
+    type Output = T;
+
+    fn div(self, rhs: T) -> Self::Output {
+        self.inner.current_val.div(rhs)
+    }
+}
+impl<'a, T: Copy + Div<T, Output = T>> DivAssign<T> for UseState<'a, T> {
+    fn div_assign(&mut self, rhs: T) {
+        self.set(self.inner.current_val.div(rhs));
+    }
+}

+ 1 - 1
packages/router/src/link.rs

@@ -41,8 +41,8 @@ pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
         a {
         a {
             href: "#",
             href: "#",
             class: format_args!("{}", cx.props.class.unwrap_or("")),
             class: format_args!("{}", cx.props.class.unwrap_or("")),
-            {&cx.props.children}
             onclick: move |_| service.push_route(cx.props.to.clone()),
             onclick: move |_| service.push_route(cx.props.to.clone()),
+            {&cx.props.children}
         }
         }
     })
     })
 }
 }

+ 7 - 8
packages/ssr/src/lib.rs

@@ -309,19 +309,18 @@ mod tests {
 
 
     static SLIGHTLY_MORE_COMPLEX: Component = |cx| {
     static SLIGHTLY_MORE_COMPLEX: Component = |cx| {
         cx.render(rsx! {
         cx.render(rsx! {
-            div {
-                title: "About W3Schools"
-                {(0..20).map(|f| rsx!{
+            div { title: "About W3Schools",
+                (0..20).map(|f| rsx!{
                     div {
                     div {
-                        title: "About W3Schools"
-                        style: "color:blue;text-align:center"
-                        class: "About W3Schools"
+                        title: "About W3Schools",
+                        style: "color:blue;text-align:center",
+                        class: "About W3Schools",
                         p {
                         p {
-                            title: "About W3Schools"
+                            title: "About W3Schools",
                             "Hello world!: {f}"
                             "Hello world!: {f}"
                         }
                         }
                     }
                     }
-                })}
+                })
             }
             }
         })
         })
     };
     };