Browse Source

wip: docs and router

Jonathan Kelley 3 years ago
parent
commit
a5f05d73ac
50 changed files with 1263 additions and 234 deletions
  1. 4 0
      Cargo.toml
  2. 2 2
      docs/guide/src/concepts/10-concurrent-mode.md
  3. 3 3
      docs/guide/src/concepts/11-arena-memo.md
  4. 3 3
      docs/guide/src/concepts/12-signals.md
  5. 1 1
      docs/guide/src/concepts/conditional_rendering.md
  6. 1 1
      docs/guide/src/hello_world.md
  7. 1 1
      examples/async.rs
  8. 3 3
      examples/calculator.rs
  9. 1 1
      examples/core/alternative.rs
  10. 1 1
      examples/core/syntax.rs
  11. 1 1
      examples/crm.rs
  12. 1 1
      examples/desktop/crm.rs
  13. 1 1
      examples/desktop/todomvc.rs
  14. 87 93
      examples/framework_benchmark.rs
  15. 1 1
      examples/pattern_reducer.rs
  16. 2 2
      examples/web/basic.rs
  17. 1 1
      examples/web/crm2.rs
  18. 1 1
      notes/Parity.md
  19. 5 5
      notes/SOLVEDPROBLEMS.md
  20. 1 1
      packages/core-macro/src/lib.rs
  21. 1 1
      packages/core-macro/src/rsxtemplate.rs
  22. 9 2
      packages/core/Cargo.toml
  23. 7 12
      packages/core/benches/jsframework.rs
  24. 120 0
      packages/core/examples/rows.rs
  25. 412 0
      packages/core/flamegraph.svg
  26. 61 12
      packages/core/src/diff.rs
  27. 1 1
      packages/core/src/scope.rs
  28. 8 8
      packages/core/src/scopearena.rs
  29. 1 1
      packages/core/src/virtual_dom.rs
  30. 7 7
      packages/desktop/README.md
  31. 2 3
      packages/desktop/src/index.html
  32. 6 0
      packages/desktop/src/lib.rs
  33. 1 1
      packages/hooks/src/usecollection.rs
  34. 5 0
      packages/hooks/src/useref.rs
  35. 1 1
      packages/hooks/src/usestate.rs
  36. 18 3
      packages/html/src/elements.rs
  37. 6 0
      packages/router/Cargo.toml
  38. 40 0
      packages/router/README.md
  39. 45 0
      packages/router/examples/simple.rs
  40. 13 23
      packages/router/src/lib.rs
  41. 1 1
      packages/ssr/README.md
  42. 1 1
      packages/ssr/src/lib.rs
  43. 20 2
      packages/web/Cargo.toml
  44. 243 0
      packages/web/examples/js_bench.rs
  45. 46 0
      packages/web/examples/simple.rs
  46. 5 0
      packages/web/src/cache.rs
  47. 1 1
      packages/web/src/cfg.rs
  48. 45 23
      packages/web/src/dom.rs
  49. 9 6
      packages/web/src/lib.rs
  50. 7 2
      src/lib.rs

+ 4 - 0
Cargo.toml

@@ -91,3 +91,7 @@ path = "./examples/webview.rs"
 required-features = ["desktop"]
 required-features = ["desktop"]
 name = "tailwind"
 name = "tailwind"
 path = "./examples/tailwind.rs"
 path = "./examples/tailwind.rs"
+
+
+[patch.crates-io]
+wasm-bindgen = { path = "../Tinkering/wasm-bindgen" }

+ 2 - 2
docs/guide/src/concepts/10-concurrent-mode.md

@@ -50,8 +50,8 @@ async fn ExampleLoader(cx: Context<()>) -> Vnode {
     This API stores the result on the Context object, so the loaded data is taken as reference.
     This API stores the result on the Context object, so the loaded data is taken as reference.
     */
     */
     let name: &Result<SomeStructure> = use_fetch_data("http://example.com/json", ())
     let name: &Result<SomeStructure> = use_fetch_data("http://example.com/json", ())
-                                        .place_holder(|(cx, props)|rsx!{<div> "loading..." </div>})
-                                        .delayed_place_holder(1000, |(cx, props)|rsx!{ <div> "still loading..." </div>})
+                                        .place_holder(|cx, props|rsx!{<div> "loading..." </div>})
+                                        .delayed_place_holder(1000, |cx, props|rsx!{ <div> "still loading..." </div>})
                                         .await;
                                         .await;
 
 
     match name {
     match name {

+ 3 - 3
docs/guide/src/concepts/11-arena-memo.md

@@ -21,9 +21,9 @@ fn test() -> DomTree {
     }
     }
 }
 }
 
 
-static TestComponent: FC<()> = |(cx, props)|html!{<div>"Hello world"</div>};
+static TestComponent: FC<()> = |cx, props|html!{<div>"Hello world"</div>};
 
 
-static TestComponent: FC<()> = |(cx, props)|{
+static TestComponent: FC<()> = |cx, props|{
     let g = "BLAH";
     let g = "BLAH";
     html! {
     html! {
         <div> "Hello world" </div>
         <div> "Hello world" </div>
@@ -31,7 +31,7 @@ static TestComponent: FC<()> = |(cx, props)|{
 };
 };
 
 
 #[functional_component]
 #[functional_component]
-static TestComponent: FC<{ name: String }> = |(cx, props)|html! { <div> "Hello {name}" </div> };
+static TestComponent: FC<{ name: String }> = |cx, props|html! { <div> "Hello {name}" </div> };
 ```
 ```
 
 
 ## Why this behavior?
 ## Why this behavior?

+ 3 - 3
docs/guide/src/concepts/12-signals.md

@@ -96,7 +96,7 @@ Sometimes you want a signal to propagate across your app, either through far-awa
 
 
 ```rust
 ```rust
 const TITLE: Atom<String> = || "".to_string();
 const TITLE: Atom<String> = || "".to_string();
-const Provider: FC<()> = |(cx, props)|{
+const Provider: FC<()> = |cx, props|{
     let title = use_signal(&cx, &TITLE);
     let title = use_signal(&cx, &TITLE);
     rsx!(cx, input { value: title })
     rsx!(cx, input { value: title })
 };
 };
@@ -105,7 +105,7 @@ const Provider: FC<()> = |(cx, props)|{
 If we use the `TITLE` atom in another component, we can cause updates to flow between components without calling render or diffing either component trees:
 If we use the `TITLE` atom in another component, we can cause updates to flow between components without calling render or diffing either component trees:
 
 
 ```rust
 ```rust
-const Receiver: FC<()> = |(cx, props)|{
+const Receiver: FC<()> = |cx, props|{
     let title = use_signal(&cx, &TITLE);
     let title = use_signal(&cx, &TITLE);
     log::info!("This will only be called once!");
     log::info!("This will only be called once!");
     rsx!(cx,
     rsx!(cx,
@@ -132,7 +132,7 @@ Dioxus automatically understands how to use your signals when mixed with iterato
 
 
 ```rust
 ```rust
 const DICT: AtomFamily<String, String> = |_| {};
 const DICT: AtomFamily<String, String> = |_| {};
-const List: FC<()> = |(cx, props)|{
+const List: FC<()> = |cx, props|{
     let dict = use_signal(&cx, &DICT);
     let dict = use_signal(&cx, &DICT);
     cx.render(rsx!(
     cx.render(rsx!(
         ul {
         ul {

+ 1 - 1
docs/guide/src/concepts/conditional_rendering.md

@@ -83,7 +83,7 @@ fn App((cx, props): Scope<()>) -> Element {
 
 
 This syntax even enables us to write a one-line component:
 This syntax even enables us to write a one-line component:
 ```rust
 ```rust
-static App: Fc<()> = |(cx, props)| rsx!(cx, "hello world!");
+static App: Fc<()> = |cx, props| rsx!(cx, "hello world!");
 ```
 ```
 
 
 Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`:
 Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`:

+ 1 - 1
docs/guide/src/hello_world.md

@@ -143,7 +143,7 @@ fn App<'a>(cx: Component<'a, ()>) -> Element<'a> {
 
 
 Writing `fn App((cx, props): Component<()>) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
 Writing `fn App((cx, props): Component<()>) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
 ```rust
 ```rust
-static App: Fc<()> = |(cx, props)| {
+static App: Fc<()> = |cx, props| {
     cx.render(rsx! {
     cx.render(rsx! {
         div { "Hello, world!" }
         div { "Hello, world!" }
     })
     })

+ 1 - 1
examples/async.rs

@@ -11,7 +11,7 @@ async fn main() {
     dioxus::desktop::launch(App, |c| c);
     dioxus::desktop::launch(App, |c| c);
 }
 }
 
 
-pub static App: FC<()> = |(cx, _)| {
+pub static App: FC<()> = |cx, _| {
     let count = use_state(cx, || 0);
     let count = use_state(cx, || 0);
     let mut direction = use_state(cx, || 1);
     let mut direction = use_state(cx, || 1);
 
 

+ 3 - 3
examples/calculator.rs

@@ -10,7 +10,7 @@ fn main() {
     dioxus::desktop::launch(APP, |cfg| cfg);
     dioxus::desktop::launch(APP, |cfg| cfg);
 }
 }
 
 
-const APP: FC<()> = |(cx, _)| {
+const APP: FC<()> = |cx, _| {
     let cur_val = use_state(cx, || 0.0_f64);
     let cur_val = use_state(cx, || 0.0_f64);
     let operator = use_state(cx, || None as Option<&'static str>);
     let operator = use_state(cx, || None as Option<&'static str>);
     let display_value = use_state(cx, || String::from(""));
     let display_value = use_state(cx, || String::from(""));
@@ -114,10 +114,10 @@ const APP: FC<()> = |(cx, _)| {
 struct CalculatorKeyProps<'a> {
 struct CalculatorKeyProps<'a> {
     name: &'static str,
     name: &'static str,
     onclick: &'a dyn Fn(MouseEvent),
     onclick: &'a dyn Fn(MouseEvent),
-    children: ScopeChildren<'a>,
+    children: Element,
 }
 }
 
 
-fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps<'a>>) -> Element {
+fn CalculatorKey<'a>(cx: Context, props: &CalculatorKeyProps) -> Element {
     rsx!(cx, button {
     rsx!(cx, button {
         class: "calculator-key {props.name}"
         class: "calculator-key {props.name}"
         onclick: {props.onclick}
         onclick: {props.onclick}

+ 1 - 1
examples/core/alternative.rs

@@ -9,7 +9,7 @@ fn main() {
     println!("{}", dom);
     println!("{}", dom);
 }
 }
 
 
-pub static EXAMPLE: FC<()> = |(cx, _)| {
+pub static EXAMPLE: FC<()> = |cx, _| {
     let list = (0..10).map(|_f| {
     let list = (0..10).map(|_f| {
         rsx! {
         rsx! {
             "{_f}"
             "{_f}"

+ 1 - 1
examples/core/syntax.rs

@@ -31,7 +31,7 @@ fn html_usage() {
     // let p = rsx!(div { {f} });
     // let p = rsx!(div { {f} });
 }
 }
 
 
-static App2: FC<()> = |(cx, _)| cx.render(rsx!("hello world!"));
+static App2: FC<()> = |cx, _| cx.render(rsx!("hello world!"));
 
 
 static App: FC<()> = |cx, props| {
 static App: FC<()> = |cx, props| {
     let name = cx.use_state(|| 0);
     let name = cx.use_state(|| 0);

+ 1 - 1
examples/crm.rs

@@ -19,7 +19,7 @@ pub struct Client {
     pub description: String,
     pub description: String,
 }
 }
 
 
-static App: FC<()> = |(cx, _)| {
+static App: FC<()> = |cx, _| {
     let mut clients = use_ref(cx, || vec![] as Vec<Client>);
     let mut clients = use_ref(cx, || vec![] as Vec<Client>);
     let mut scene = use_state(cx, || Scene::ClientsList);
     let mut scene = use_state(cx, || Scene::ClientsList);
 
 

+ 1 - 1
examples/desktop/crm.rs

@@ -21,7 +21,7 @@ pub struct Client {
     pub description: String,
     pub description: String,
 }
 }
 
 
-static App: FC<()> = |(cx, _)| {
+static App: FC<()> = |cx, _| {
     let mut scene = use_state(cx, || Scene::ClientsList);
     let mut scene = use_state(cx, || Scene::ClientsList);
     let clients = use_ref(cx, || vec![] as Vec<Client>);
     let clients = use_ref(cx, || vec![] as Vec<Client>);
 
 

+ 1 - 1
examples/desktop/todomvc.rs

@@ -32,7 +32,7 @@ pub struct TodoItem {
 }
 }
 pub type Todos = HashMap<u32, TodoItem>;
 pub type Todos = HashMap<u32, TodoItem>;
 
 
-pub static App: FC<()> = |(cx, _)| {
+pub static App: FC<()> = |cx, _| {
     // Share our TodoList to the todos themselves
     // Share our TodoList to the todos themselves
     use_provide_state(cx, Todos::new);
     use_provide_state(cx, Todos::new);
 
 

+ 87 - 93
examples/framework_benchmark.rs

@@ -1,50 +1,38 @@
-use dioxus::{events::MouseEvent, prelude::*};
-use fxhash::FxBuildHasher;
-use std::rc::Rc;
+use dioxus::prelude::*;
+use rand::prelude::*;
 
 
 fn main() {
 fn main() {
-    dioxus::desktop::launch(App, |c| c);
+    dioxus::web::launch(App, |c| c);
+    // dioxus::desktop::launch(App, |c| c);
 }
 }
 
 
-// We use a special immutable hashmap to make hashmap operations efficient
-type RowList = im_rc::HashMap<usize, Rc<str>, FxBuildHasher>;
-
-static App: FC<()> = |(cx, _props)| {
-    let mut items = use_state(cx, || RowList::default());
-
-    let create_rendered_rows = move |from, num| move |_| items.set(create_row_list(from, num));
-
-    let mut append_1_000_rows =
-        move |_| items.set(create_row_list(items.len(), 1000).union((*items).clone()));
-
-    let update_every_10th_row = move |_| {
-        let mut new_items = (*items).clone();
-        let mut small_rng = SmallRng::from_entropy();
-        new_items.iter_mut().step_by(10).for_each(|(_, val)| {
-            *val = create_new_row_label(&mut String::with_capacity(30), &mut small_rng)
-        });
-        items.set(new_items);
-    };
-    let clear_rows = move |_| items.set(RowList::default());
+#[derive(Clone, PartialEq)]
+struct Label {
+    key: usize,
+    labels: [&'static str; 3],
+}
 
 
-    let swap_rows = move |_| {
-        // this looks a bit ugly because we're using a hashmap instead of a vec
-        if items.len() > 998 {
-            let mut new_items = (*items).clone();
-            let a = new_items.get(&0).unwrap().clone();
-            *new_items.get_mut(&0).unwrap() = new_items.get(&998).unwrap().clone();
-            *new_items.get_mut(&998).unwrap() = a;
-            items.set(new_items);
+impl Label {
+    fn new_list(num: usize) -> Vec<Self> {
+        let mut rng = SmallRng::from_entropy();
+        let mut labels = Vec::with_capacity(num);
+        for _ in 0..num {
+            labels.push(Label {
+                key: 0,
+                labels: [
+                    ADJECTIVES.choose(&mut rng).unwrap(),
+                    COLOURS.choose(&mut rng).unwrap(),
+                    NOUNS.choose(&mut rng).unwrap(),
+                ],
+            });
         }
         }
-    };
+        labels
+    }
+}
 
 
-    let rows = items.iter().map(|(key, value)| {
-        rsx!(Row {
-            key: "{key}",
-            row_id: *key as usize,
-            label: value.clone(),
-        })
-    });
+static App: FC<()> = |cx, _props| {
+    let mut items = use_ref(cx, || vec![]);
+    let mut selected = use_state(cx, || None);
 
 
     cx.render(rsx! {
     cx.render(rsx! {
         div { class: "container"
         div { class: "container"
@@ -53,22 +41,49 @@ static App: FC<()> = |(cx, _props)| {
                     div { class: "col-md-6", h1 { "Dioxus" } }
                     div { class: "col-md-6", h1 { "Dioxus" } }
                     div { class: "col-md-6"
                     div { class: "col-md-6"
                         div { class: "row"
                         div { class: "row"
-                            ActionButton { name: "Create 1,000 rows", id: "run", onclick: {create_rendered_rows(0, 1_000)} }
-                            ActionButton { name: "Create 10,000 rows", id: "runlots", onclick: {create_rendered_rows(0, 10_000)} }
-                            ActionButton { name: "Append 1,000 rows", id: "add", onclick: {append_1_000_rows} }
-                            ActionButton { name: "Update every 10th row", id: "update", onclick: {update_every_10th_row} }
-                            ActionButton { name: "Clear", id: "clear", onclick: {clear_rows} }
-                            ActionButton { name: "Swap rows", id: "swaprows", onclick: {swap_rows} }
+                            ActionButton { name: "Create 1,000 rows", id: "run",
+                                onclick: move || items.set(Label::new_list(1_000)),
+                            }
+                            ActionButton { name: "Create 10,000 rows", id: "runlots",
+                                onclick: move || items.set(Label::new_list(10_000)),
+                            }
+                            ActionButton { name: "Append 1,000 rows", id: "add",
+                                onclick: move || items.write().extend(Label::new_list(1_000)),
+                            }
+                            ActionButton { name: "Update every 10th row", id: "update",
+                                onclick: move || items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"),
+                            }
+                            ActionButton { name: "Clear", id: "clear",
+                                onclick: move || items.write().clear(),
+                            }
+                            ActionButton { name: "Swap rows", id: "swaprows",
+                                onclick: move || items.write().swap(0, 998),
+                            }
                         }
                         }
                     }
                     }
                 }
                 }
             }
             }
             table {
             table {
                 tbody {
                 tbody {
-                    {rows}
+                    {items.read().iter().enumerate().map(|(id, item)| {
+                        let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
+                        rsx!(tr { class: "{is_in_danger}"
+                            td { class:"col-md-1" }
+                            td { class:"col-md-1", "{item.key}" }
+                            td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
+                                a { class: "lbl", {item.labels} }
+                            }
+                            td { class: "col-md-1"
+                                a { class: "remove", onclick: move |_| { items.write().remove(id); },
+                                    span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+                                }
+                            }
+                            td { class: "col-md-6" }
+                        })
+                    })}
                 }
                 }
              }
              }
-            span {}
+            // span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" }
         }
         }
     })
     })
 };
 };
@@ -77,59 +92,17 @@ static App: FC<()> = |(cx, _props)| {
 struct ActionButtonProps<'a> {
 struct ActionButtonProps<'a> {
     name: &'static str,
     name: &'static str,
     id: &'static str,
     id: &'static str,
-    onclick: &'a dyn Fn(MouseEvent),
+    onclick: &'a dyn Fn(),
 }
 }
 
 
-fn ActionButton<'a>((cx, props): Scope<'a, ActionButtonProps>) -> Element<'a> {
+fn ActionButton(cx: Context, props: &ActionButtonProps) -> 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: "{props.id}",  onclick: {props.onclick},
+        button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}",  onclick: move |_| (props.onclick)(),
             "{props.name}"
             "{props.name}"
         }
         }
     })
     })
 }
 }
 
 
-#[derive(PartialEq, Props)]
-struct RowProps {
-    row_id: usize,
-    label: Rc<str>,
-}
-fn Row((cx, props): Scope<RowProps>) -> Element {
-    rsx!(cx, tr {
-        td { class:"col-md-1", "{props.row_id}" }
-        td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
-            a { class: "lbl", "{props.label}" }
-        }
-        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" }
-    })
-}
-
-use rand::prelude::*;
-fn create_new_row_label(label: &mut String, rng: &mut SmallRng) -> Rc<str> {
-    label.push_str(ADJECTIVES.choose(rng).unwrap());
-    label.push(' ');
-    label.push_str(COLOURS.choose(rng).unwrap());
-    label.push(' ');
-    label.push_str(NOUNS.choose(rng).unwrap());
-    Rc::from(label.as_ref())
-}
-
-fn create_row_list(from: usize, num: usize) -> RowList {
-    let mut small_rng = SmallRng::from_entropy();
-    let mut buf = String::with_capacity(35);
-    (from..num + from)
-        .map(|f| {
-            let o = (f, create_new_row_label(&mut buf, &mut small_rng));
-            buf.clear();
-            o
-        })
-        .collect::<RowList>()
-}
-
 static ADJECTIVES: &[&str] = &[
 static ADJECTIVES: &[&str] = &[
     "pretty",
     "pretty",
     "large",
     "large",
@@ -167,3 +140,24 @@ static NOUNS: &[&str] = &[
     "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
     "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
     "pizza", "mouse", "keyboard",
     "pizza", "mouse", "keyboard",
 ];
 ];
+
+// #[derive(PartialEq, Props)]
+// struct RowProps<'a> {
+//     row_id: usize,
+//     label: &'a Label,
+// }
+
+// fn Row(cx: Context, props: &RowProps) -> Element {
+//     rsx!(cx, tr {
+//         td { class:"col-md-1", "{props.row_id}" }
+//         td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+//             a { class: "lbl", {props.label.labels} }
+//         }
+//         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" }
+//     })
+// }

+ 1 - 1
examples/pattern_reducer.rs

@@ -11,7 +11,7 @@ fn main() {
     dioxus::desktop::launch(App, |c| c);
     dioxus::desktop::launch(App, |c| c);
 }
 }
 
 
-pub static App: FC<()> = |(cx, _)| {
+pub static App: FC<()> = |cx, _| {
     let state = use_state(cx, PlayerState::new);
     let state = use_state(cx, PlayerState::new);
 
 
     let is_playing = state.is_playing();
     let is_playing = state.is_playing();

+ 2 - 2
examples/web/basic.rs

@@ -14,7 +14,7 @@ fn main() {
     dioxus_web::launch(APP, |c| c)
     dioxus_web::launch(APP, |c| c)
 }
 }
 
 
-static APP: FC<()> = |(cx, _)| {
+static APP: FC<()> = |cx, _| {
     let mut count = use_state(cx, || 3);
     let mut count = use_state(cx, || 3);
     let content = use_state(cx, || String::from("h1"));
     let content = use_state(cx, || String::from("h1"));
     let text_content = use_state(cx, || String::from("Hello, world!"));
     let text_content = use_state(cx, || String::from("Hello, world!"));
@@ -86,4 +86,4 @@ fn render_list(cx: Context, count: usize) -> Element {
     rsx!(cx, ul { {items} })
     rsx!(cx, ul { {items} })
 }
 }
 
 
-static CHILD: FC<()> = |(cx, _)| rsx!(cx, div {"hello child"});
+static CHILD: FC<()> = |cx, _| rsx!(cx, div {"hello child"});

+ 1 - 1
examples/web/crm2.rs

@@ -28,7 +28,7 @@ pub struct Client {
     pub description: String,
     pub description: String,
 }
 }
 
 
-static App: FC<()> = |(cx, _)| {
+static App: FC<()> = |cx, _| {
     let scene = use_state(cx, || Scene::ClientsList);
     let scene = use_state(cx, || Scene::ClientsList);
     let clients = use_ref(cx, || vec![] as Vec<Client>);
     let clients = use_ref(cx, || vec![] as Vec<Client>);
 
 

+ 1 - 1
notes/Parity.md

@@ -12,7 +12,7 @@ https://github.com/rustwasm/gloo
 For example, resize observer would function like this:
 For example, resize observer would function like this:
 
 
 ```rust
 ```rust
-pub static Example: FC<()> = |(cx, props)|{
+pub static Example: FC<()> = |cx, props|{
     let observer = use_resize_observer();
     let observer = use_resize_observer();
 
 
     cx.render(rsx!(
     cx.render(rsx!(

+ 5 - 5
notes/SOLVEDPROBLEMS.md

@@ -153,13 +153,13 @@ Notice that LiveComponent receivers (the client-side interpretation of a LiveCom
 The `VNodeTree` type is a very special type that allows VNodes to be created using a pluggable allocator. The html! macro creates something that looks like:
 The `VNodeTree` type is a very special type that allows VNodes to be created using a pluggable allocator. The html! macro creates something that looks like:
 
 
 ```rust
 ```rust
-pub static Example: FC<()> = |(cx, props)|{
+pub static Example: FC<()> = |cx, props|{
     html! { <div> "blah" </div> }
     html! { <div> "blah" </div> }
 };
 };
 
 
 // expands to...
 // expands to...
 
 
-pub static Example: FC<()> = |(cx, props)|{
+pub static Example: FC<()> = |cx, props|{
     // This function converts a Fn(allocator) -> DomTree closure to a VNode struct that will later be evaluated.
     // This function converts a Fn(allocator) -> DomTree closure to a VNode struct that will later be evaluated.
     html_macro_to_vnodetree(move |allocator| {
     html_macro_to_vnodetree(move |allocator| {
         let mut node0 = allocator.alloc(VElement::div);
         let mut node0 = allocator.alloc(VElement::div);
@@ -313,7 +313,7 @@ Here's how react does it:
 Any "dirty" node causes an entire subtree render. Calling "setState" at the very top will cascade all the way down. This is particularly bad for this component design:
 Any "dirty" node causes an entire subtree render. Calling "setState" at the very top will cascade all the way down. This is particularly bad for this component design:
 
 
 ```rust
 ```rust
-static APP: FC<()> = |(cx, props)|{
+static APP: FC<()> = |cx, props|{
     let title = use_context(Title);
     let title = use_context(Title);
     cx.render(html!{
     cx.render(html!{
         <div>
         <div>
@@ -334,7 +334,7 @@ static APP: FC<()> = |(cx, props)|{
         </div>
         </div>
     })
     })
 };
 };
-static HEAVY_LIST: FC<()> = |(cx, props)|{
+static HEAVY_LIST: FC<()> = |cx, props|{
     cx.render({
     cx.render({
         {0.100.map(i => <BigElement >)}
         {0.100.map(i => <BigElement >)}
     })
     })
@@ -378,7 +378,7 @@ struct Props {
 
 
 }
 }
 
 
-static Component: FC<Props> = |(cx, props)|{
+static Component: FC<Props> = |cx, props|{
 
 
 }
 }
 ```
 ```

+ 1 - 1
packages/core-macro/src/lib.rs

@@ -30,7 +30,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 ///
 ///
 /// ## Complete Reference Guide:
 /// ## Complete Reference Guide:
 /// ```
 /// ```
-/// const Example: FC<()> = |(cx, props)|{
+/// const Example: FC<()> = |cx, props|{
 ///     let formatting = "formatting!";
 ///     let formatting = "formatting!";
 ///     let formatting_tuple = ("a", "b");
 ///     let formatting_tuple = ("a", "b");
 ///     let lazy_fmt = format_args!("lazily formatted text");
 ///     let lazy_fmt = format_args!("lazily formatted text");

+ 1 - 1
packages/core-macro/src/rsxtemplate.rs

@@ -72,7 +72,7 @@ impl ToTokens for RsxTemplate {
 
 
         // // create a lazy tree that accepts a bump allocator
         // // create a lazy tree that accepts a bump allocator
         // let final_tokens = quote! {
         // let final_tokens = quote! {
-        //     dioxus::prelude::LazyNodes::new(move |(cx, props)|{
+        //     dioxus::prelude::LazyNodes::new(move |cx, props|{
         //         let bump = &cx.bump();
         //         let bump = &cx.bump();
 
 
         //         #new_toks
         //         #new_toks

+ 9 - 2
packages/core/Cargo.toml

@@ -49,8 +49,7 @@ rand = { version = "0.8.4", features = ["small_rng"] }
 simple_logger = "1.13.0"
 simple_logger = "1.13.0"
 dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
 dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
 dioxus-hooks = { path = "../hooks" }
 dioxus-hooks = { path = "../hooks" }
-# async-std = { version = "1.9.0", features = ["attributes"] }
-# criterion = "0.3.5"
+criterion = "0.3.5"
 
 
 [features]
 [features]
 default = []
 default = []
@@ -64,3 +63,11 @@ harness = false
 [[bench]]
 [[bench]]
 name = "jsframework"
 name = "jsframework"
 harness = false
 harness = false
+
+[[example]]
+name = "rows"
+path = "./examples/rows.rs"
+
+
+[profile.release]
+debug = true

+ 7 - 12
packages/core/benches/jsframework.rs

@@ -19,7 +19,6 @@ use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
 use dioxus_html as dioxus_elements;
 use rand::prelude::*;
 use rand::prelude::*;
 
 
-fn main() {}
 criterion_group!(mbenches, create_rows);
 criterion_group!(mbenches, create_rows);
 criterion_main!(mbenches);
 criterion_main!(mbenches);
 
 
@@ -28,18 +27,14 @@ fn create_rows(c: &mut Criterion) {
         let mut rng = SmallRng::from_entropy();
         let mut rng = SmallRng::from_entropy();
         let rows = (0..10_000_usize).map(|f| {
         let rows = (0..10_000_usize).map(|f| {
             let label = Label::new(&mut rng);
             let label = Label::new(&mut rng);
-            rsx! {
-                Row {
-                    row_id: f,
-                    label: label
-                }
-            }
+            rsx!(Row {
+                row_id: f,
+                label: label
+            })
         });
         });
-        cx.render(rsx! {
-            table {
-                tbody {
-                    {rows}
-                }
+        rsx!(cx, table {
+            tbody {
+                {rows}
             }
             }
         })
         })
     };
     };

+ 120 - 0
packages/core/examples/rows.rs

@@ -0,0 +1,120 @@
+#![allow(non_snake_case, non_upper_case_globals)]
+//! This benchmark tests just the overhead of Dioxus itself.
+//!
+//! For the JS Framework Benchmark, both the framework and the browser is benchmarked together. Dioxus prepares changes
+//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,
+//! we are measuring the overhead of Dioxus, not the performance of the "apply" phase.
+//!
+//! On my MBP 2019:
+//! - Dioxus takes 3ms to create 1_000 rows
+//! - Dioxus takes 30ms to create 10_000 rows
+//!
+//! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
+//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
+
+use criterion::{criterion_group, criterion_main, Criterion};
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use rand::prelude::*;
+
+fn main() {
+    static App: FC<()> = |cx, _| {
+        let mut rng = SmallRng::from_entropy();
+        let rows = (0..10_000_usize).map(|f| {
+            let label = Label::new(&mut rng);
+            rsx! {
+                Row {
+                    row_id: f,
+                    label: label
+                }
+            }
+        });
+        cx.render(rsx! {
+            table {
+                tbody {
+                    {rows}
+                }
+            }
+        })
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let g = dom.rebuild();
+    assert!(g.edits.len() > 1);
+}
+
+#[derive(PartialEq, Props)]
+struct RowProps {
+    row_id: usize,
+    label: Label,
+}
+fn Row(cx: Context, props: &RowProps) -> Element {
+    let [adj, col, noun] = props.label.0;
+    cx.render(rsx! {
+        tr {
+            td { class:"col-md-1", "{props.row_id}" }
+            td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+                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-6" }
+        }
+    })
+}
+
+#[derive(PartialEq)]
+struct Label([&'static str; 3]);
+
+impl Label {
+    fn new(rng: &mut SmallRng) -> Self {
+        Label([
+            ADJECTIVES.choose(rng).unwrap(),
+            COLOURS.choose(rng).unwrap(),
+            NOUNS.choose(rng).unwrap(),
+        ])
+    }
+}
+
+static ADJECTIVES: &[&str] = &[
+    "pretty",
+    "large",
+    "big",
+    "small",
+    "tall",
+    "short",
+    "long",
+    "handsome",
+    "plain",
+    "quaint",
+    "clean",
+    "elegant",
+    "easy",
+    "angry",
+    "crazy",
+    "helpful",
+    "mushy",
+    "odd",
+    "unsightly",
+    "adorable",
+    "important",
+    "inexpensive",
+    "cheap",
+    "expensive",
+    "fancy",
+];
+
+static COLOURS: &[&str] = &[
+    "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
+    "orange",
+];
+
+static NOUNS: &[&str] = &[
+    "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
+    "pizza", "mouse", "keyboard",
+];

+ 412 - 0
packages/core/flamegraph.svg

@@ -0,0 +1,412 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" width="1200" height="518" onload="init(evt)" viewBox="0 0 1200 518" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fg="http://github.com/jonhoo/inferno"><!--Flame graph stack visualization. See https://github.com/brendangregg/FlameGraph for latest version, and http://www.brendangregg.com/flamegraphs.html for examples.--><!--NOTES: --><defs><linearGradient id="background" y1="0" y2="1" x1="0" x2="0"><stop stop-color="#eeeeee" offset="5%"/><stop stop-color="#eeeeb0" offset="95%"/></linearGradient></defs><style type="text/css">
+text { font-family:"Verdana"; font-size:12px; fill:rgb(0,0,0); }
+#title { text-anchor:middle; font-size:17px; }
+#search { opacity:0.1; cursor:pointer; }
+#search:hover, #search.show { opacity:1; }
+#subtitle { text-anchor:middle; font-color:rgb(160,160,160); }
+#unzoom { cursor:pointer; }
+#frames > *:hover { stroke:black; stroke-width:0.5; cursor:pointer; }
+.hide { display:none; }
+.parent { opacity:0.5; }
+</style><script type="text/ecmascript"><![CDATA[var nametype = 'Function:';
+var fontsize = 12;
+var fontwidth = 0.59;
+var xpad = 10;
+var inverted = false;
+var searchcolor = 'rgb(230,0,230)';
+var fluiddrawing = true;
+var truncate_text_right = false;]]><![CDATA["use strict";
+var details, searchbtn, unzoombtn, matchedtxt, svg, searching, frames;
+function init(evt) {
+    details = document.getElementById("details").firstChild;
+    searchbtn = document.getElementById("search");
+    unzoombtn = document.getElementById("unzoom");
+    matchedtxt = document.getElementById("matched");
+    svg = document.getElementsByTagName("svg")[0];
+    frames = document.getElementById("frames");
+    total_samples = parseInt(frames.attributes.total_samples.value);
+    searching = 0;
+
+    // Use GET parameters to restore a flamegraph's state.
+    var restore_state = function() {
+        var params = get_params();
+        if (params.x && params.y)
+            zoom(find_group(document.querySelector('[*|x="' + params.x + '"][y="' + params.y + '"]')));
+        if (params.s)
+            search(params.s);
+    };
+
+    if (fluiddrawing) {
+        // Make width dynamic so the SVG fits its parent's width.
+        svg.removeAttribute("width");
+        // Edge requires us to have a viewBox that gets updated with size changes.
+        var isEdge = /Edge\/\d./i.test(navigator.userAgent);
+        if (!isEdge) {
+          svg.removeAttribute("viewBox");
+        }
+        var update_for_width_change = function() {
+            if (isEdge) {
+                svg.attributes.viewBox.value = "0 0 " + svg.width.baseVal.value + " " + svg.height.baseVal.value;
+            }
+
+            // Keep consistent padding on left and right of frames container.
+            frames.attributes.width.value = svg.width.baseVal.value - xpad * 2;
+
+            // Text truncation needs to be adjusted for the current width.
+            var el = frames.children;
+            for(var i = 0; i < el.length; i++) {
+                update_text(el[i]);
+            }
+
+            // Keep search elements at a fixed distance from right edge.
+            var svgWidth = svg.width.baseVal.value;
+            searchbtn.attributes.x.value = svgWidth - xpad - 100;
+            matchedtxt.attributes.x.value = svgWidth - xpad - 100;
+        };
+        window.addEventListener('resize', function() {
+            update_for_width_change();
+        });
+        // This needs to be done asynchronously for Safari to work.
+        setTimeout(function() {
+            unzoom();
+            update_for_width_change();
+            restore_state();
+        }, 0);
+    } else {
+        restore_state();
+    }
+}
+// event listeners
+window.addEventListener("click", function(e) {
+    var target = find_group(e.target);
+    if (target) {
+        if (target.nodeName == "a") {
+            if (e.ctrlKey === false) return;
+            e.preventDefault();
+        }
+        if (target.classList.contains("parent")) unzoom();
+        zoom(target);
+
+        // set parameters for zoom state
+        var el = target.querySelector("rect");
+        if (el && el.attributes && el.attributes.y && el.attributes["fg:x"]) {
+            var params = get_params()
+            params.x = el.attributes["fg:x"].value;
+            params.y = el.attributes.y.value;
+            history.replaceState(null, null, parse_params(params));
+        }
+    }
+    else if (e.target.id == "unzoom") {
+        unzoom();
+
+        // remove zoom state
+        var params = get_params();
+        if (params.x) delete params.x;
+        if (params.y) delete params.y;
+        history.replaceState(null, null, parse_params(params));
+    }
+    else if (e.target.id == "search") search_prompt();
+}, false)
+// mouse-over for info
+// show
+window.addEventListener("mouseover", function(e) {
+    var target = find_group(e.target);
+    if (target) details.nodeValue = nametype + " " + g_to_text(target);
+}, false)
+// clear
+window.addEventListener("mouseout", function(e) {
+    var target = find_group(e.target);
+    if (target) details.nodeValue = ' ';
+}, false)
+// ctrl-F for search
+window.addEventListener("keydown",function (e) {
+    if (e.keyCode === 114 || (e.ctrlKey && e.keyCode === 70)) {
+        e.preventDefault();
+        search_prompt();
+    }
+}, false)
+// functions
+function get_params() {
+    var params = {};
+    var paramsarr = window.location.search.substr(1).split('&');
+    for (var i = 0; i < paramsarr.length; ++i) {
+        var tmp = paramsarr[i].split("=");
+        if (!tmp[0] || !tmp[1]) continue;
+        params[tmp[0]]  = decodeURIComponent(tmp[1]);
+    }
+    return params;
+}
+function parse_params(params) {
+    var uri = "?";
+    for (var key in params) {
+        uri += key + '=' + encodeURIComponent(params[key]) + '&';
+    }
+    if (uri.slice(-1) == "&")
+        uri = uri.substring(0, uri.length - 1);
+    if (uri == '?')
+        uri = window.location.href.split('?')[0];
+    return uri;
+}
+function find_child(node, selector) {
+    var children = node.querySelectorAll(selector);
+    if (children.length) return children[0];
+    return;
+}
+function find_group(node) {
+    var parent = node.parentElement;
+    if (!parent) return;
+    if (parent.id == "frames") return node;
+    return find_group(parent);
+}
+function orig_save(e, attr, val) {
+    if (e.attributes["fg:orig_" + attr] != undefined) return;
+    if (e.attributes[attr] == undefined) return;
+    if (val == undefined) val = e.attributes[attr].value;
+    e.setAttribute("fg:orig_" + attr, val);
+}
+function orig_load(e, attr) {
+    if (e.attributes["fg:orig_"+attr] == undefined) return;
+    e.attributes[attr].value = e.attributes["fg:orig_" + attr].value;
+    e.removeAttribute("fg:orig_" + attr);
+}
+function g_to_text(e) {
+    var text = find_child(e, "title").firstChild.nodeValue;
+    return (text)
+}
+function g_to_func(e) {
+    var func = g_to_text(e);
+    // if there's any manipulation we want to do to the function
+    // name before it's searched, do it here before returning.
+    return (func);
+}
+function update_text(e) {
+    var r = find_child(e, "rect");
+    var t = find_child(e, "text");
+    var w = parseFloat(r.attributes.width.value) * frames.attributes.width.value / 100 - 3;
+    var txt = find_child(e, "title").textContent.replace(/\([^(]*\)$/,"");
+    t.attributes.x.value = format_percent((parseFloat(r.attributes.x.value) + (100 * 3 / frames.attributes.width.value)));
+    // Smaller than this size won't fit anything
+    if (w < 2 * fontsize * fontwidth) {
+        t.textContent = "";
+        return;
+    }
+    t.textContent = txt;
+    // Fit in full text width
+    if (/^ *\$/.test(txt) || t.getComputedTextLength() < w)
+        return;
+    if (truncate_text_right) {
+        // Truncate the right side of the text.
+        for (var x = txt.length - 2; x > 0; x--) {
+            if (t.getSubStringLength(0, x + 2) <= w) {
+                t.textContent = txt.substring(0, x) + "..";
+                return;
+            }
+        }
+    } else {
+        // Truncate the left side of the text.
+        for (var x = 2; x < txt.length; x++) {
+            if (t.getSubStringLength(x - 2, txt.length) <= w) {
+                t.textContent = ".." + txt.substring(x, txt.length);
+                return;
+            }
+        }
+    }
+    t.textContent = "";
+}
+// zoom
+function zoom_reset(e) {
+    if (e.tagName == "rect") {
+        e.attributes.x.value = format_percent(100 * parseInt(e.attributes["fg:x"].value) / total_samples);
+        e.attributes.width.value = format_percent(100 * parseInt(e.attributes["fg:w"].value) / total_samples);
+    }
+    if (e.childNodes == undefined) return;
+    for(var i = 0, c = e.childNodes; i < c.length; i++) {
+        zoom_reset(c[i]);
+    }
+}
+function zoom_child(e, x, zoomed_width_samples) {
+    if (e.tagName == "text") {
+        var parent_x = parseFloat(find_child(e.parentNode, "rect[x]").attributes.x.value);
+        e.attributes.x.value = format_percent(parent_x + (100 * 3 / frames.attributes.width.value));
+    } else if (e.tagName == "rect") {
+        e.attributes.x.value = format_percent(100 * (parseInt(e.attributes["fg:x"].value) - x) / zoomed_width_samples);
+        e.attributes.width.value = format_percent(100 * parseInt(e.attributes["fg:w"].value) / zoomed_width_samples);
+    }
+    if (e.childNodes == undefined) return;
+    for(var i = 0, c = e.childNodes; i < c.length; i++) {
+        zoom_child(c[i], x, zoomed_width_samples);
+    }
+}
+function zoom_parent(e) {
+    if (e.attributes) {
+        if (e.attributes.x != undefined) {
+            e.attributes.x.value = "0.0%";
+        }
+        if (e.attributes.width != undefined) {
+            e.attributes.width.value = "100.0%";
+        }
+    }
+    if (e.childNodes == undefined) return;
+    for(var i = 0, c = e.childNodes; i < c.length; i++) {
+        zoom_parent(c[i]);
+    }
+}
+function zoom(node) {
+    var attr = find_child(node, "rect").attributes;
+    var width = parseInt(attr["fg:w"].value);
+    var xmin = parseInt(attr["fg:x"].value);
+    var xmax = xmin + width;
+    var ymin = parseFloat(attr.y.value);
+    unzoombtn.classList.remove("hide");
+    var el = frames.children;
+    for (var i = 0; i < el.length; i++) {
+        var e = el[i];
+        var a = find_child(e, "rect").attributes;
+        var ex = parseInt(a["fg:x"].value);
+        var ew = parseInt(a["fg:w"].value);
+        // Is it an ancestor
+        if (!inverted) {
+            var upstack = parseFloat(a.y.value) > ymin;
+        } else {
+            var upstack = parseFloat(a.y.value) < ymin;
+        }
+        if (upstack) {
+            // Direct ancestor
+            if (ex <= xmin && (ex+ew) >= xmax) {
+                e.classList.add("parent");
+                zoom_parent(e);
+                update_text(e);
+            }
+            // not in current path
+            else
+                e.classList.add("hide");
+        }
+        // Children maybe
+        else {
+            // no common path
+            if (ex < xmin || ex >= xmax) {
+                e.classList.add("hide");
+            }
+            else {
+                zoom_child(e, xmin, width);
+                update_text(e);
+            }
+        }
+    }
+}
+function unzoom() {
+    unzoombtn.classList.add("hide");
+    var el = frames.children;
+    for(var i = 0; i < el.length; i++) {
+        el[i].classList.remove("parent");
+        el[i].classList.remove("hide");
+        zoom_reset(el[i]);
+        update_text(el[i]);
+    }
+}
+// search
+function reset_search() {
+    var el = document.querySelectorAll("#frames rect");
+    for (var i = 0; i < el.length; i++) {
+        orig_load(el[i], "fill")
+    }
+    var params = get_params();
+    delete params.s;
+    history.replaceState(null, null, parse_params(params));
+}
+function search_prompt() {
+    if (!searching) {
+        var term = prompt("Enter a search term (regexp " +
+            "allowed, eg: ^ext4_)", "");
+        if (term != null) {
+            search(term)
+        }
+    } else {
+        reset_search();
+        searching = 0;
+        searchbtn.classList.remove("show");
+        searchbtn.firstChild.nodeValue = "Search"
+        matchedtxt.classList.add("hide");
+        matchedtxt.firstChild.nodeValue = ""
+    }
+}
+function search(term) {
+    var re = new RegExp(term);
+    var el = frames.children;
+    var matches = new Object();
+    var maxwidth = 0;
+    for (var i = 0; i < el.length; i++) {
+        var e = el[i];
+        // Skip over frames which are either not visible, or below the zoomed-to frame
+        if (e.classList.contains("hide") || e.classList.contains("parent")) {
+            continue;
+        }
+        var func = g_to_func(e);
+        var rect = find_child(e, "rect");
+        if (func == null || rect == null)
+            continue;
+        // Save max width. Only works as we have a root frame
+        var w = parseInt(rect.attributes["fg:w"].value);
+        if (w > maxwidth)
+            maxwidth = w;
+        if (func.match(re)) {
+            // highlight
+            var x = parseInt(rect.attributes["fg:x"].value);
+            orig_save(rect, "fill");
+            rect.attributes.fill.value = searchcolor;
+            // remember matches
+            if (matches[x] == undefined) {
+                matches[x] = w;
+            } else {
+                if (w > matches[x]) {
+                    // overwrite with parent
+                    matches[x] = w;
+                }
+            }
+            searching = 1;
+        }
+    }
+    if (!searching)
+        return;
+    var params = get_params();
+    params.s = term;
+    history.replaceState(null, null, parse_params(params));
+
+    searchbtn.classList.add("show");
+    searchbtn.firstChild.nodeValue = "Reset Search";
+    // calculate percent matched, excluding vertical overlap
+    var count = 0;
+    var lastx = -1;
+    var lastw = 0;
+    var keys = Array();
+    for (k in matches) {
+        if (matches.hasOwnProperty(k))
+            keys.push(k);
+    }
+    // sort the matched frames by their x location
+    // ascending, then width descending
+    keys.sort(function(a, b){
+        return a - b;
+    });
+    // Step through frames saving only the biggest bottom-up frames
+    // thanks to the sort order. This relies on the tree property
+    // where children are always smaller than their parents.
+    for (var k in keys) {
+        var x = parseInt(keys[k]);
+        var w = matches[keys[k]];
+        if (x >= lastx + lastw) {
+            count += w;
+            lastx = x;
+            lastw = w;
+        }
+    }
+    // display matched percent
+    matchedtxt.classList.remove("hide");
+    var pct = 100 * count / maxwidth;
+    if (pct != 100) pct = pct.toFixed(1);
+    matchedtxt.firstChild.nodeValue = "Matched: " + pct + "%";
+}
+function format_percent(n) {
+    return n.toFixed(4) + "%";
+}
+]]></script><rect x="0" y="0" width="100%" height="518" fill="url(#background)"/><text id="title" x="50.0000%" y="24.00">Flame Graph</text><text id="details" x="10" y="501.00"> </text><text id="unzoom" class="hide" x="10" y="24.00">Reset Zoom</text><text id="search" x="1090" y="24.00">Search</text><text id="matched" x="1090" y="501.00"> </text><svg id="frames" x="10" width="1180" total_samples="55"><g><title>jsframework-a8f4acf5955e8e7f`core::ptr::drop_in_place&lt;dioxus_core::virtual_dom::VirtualDom&gt; (1 samples, 1.82%)</title><rect x="0.0000%" y="261" width="1.8182%" height="15" fill="rgb(227,0,7)" fg:x="0" fg:w="1"/><text x="0.2500%" y="271.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`core::ptr::drop_in_place&lt;alloc::boxed::Box&lt;dioxus_core::scopearena::ScopeArena&gt;&gt; (1 samples, 1.82%)</title><rect x="0.0000%" y="245" width="1.8182%" height="15" fill="rgb(217,0,24)" fg:x="0" fg:w="1"/><text x="0.2500%" y="255.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;bumpalo::Bump as core::ops::drop::Drop&gt;::drop (1 samples, 1.82%)</title><rect x="0.0000%" y="229" width="1.8182%" height="15" fill="rgb(221,193,54)" fg:x="0" fg:w="1"/><text x="0.2500%" y="239.50">j..</text></g><g><title>libsystem_malloc.dylib`free_medium (1 samples, 1.82%)</title><rect x="0.0000%" y="213" width="1.8182%" height="15" fill="rgb(248,212,6)" fg:x="0" fg:w="1"/><text x="0.2500%" y="223.50">l..</text></g><g><title>libsystem_kernel.dylib`madvise (1 samples, 1.82%)</title><rect x="0.0000%" y="197" width="1.8182%" height="15" fill="rgb(208,68,35)" fg:x="0" fg:w="1"/><text x="0.2500%" y="207.50">l..</text></g><g><title>libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 3.64%)</title><rect x="9.0909%" y="181" width="3.6364%" height="15" fill="rgb(232,128,0)" fg:x="5" fg:w="2"/><text x="9.3409%" y="191.50">libs..</text></g><g><title>libsystem_malloc.dylib`szone_malloc_should_clear (2 samples, 3.64%)</title><rect x="9.0909%" y="165" width="3.6364%" height="15" fill="rgb(207,160,47)" fg:x="5" fg:w="2"/><text x="9.3409%" y="175.50">libs..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_should_clear (2 samples, 3.64%)</title><rect x="9.0909%" y="149" width="3.6364%" height="15" fill="rgb(228,23,34)" fg:x="5" fg:w="2"/><text x="9.3409%" y="159.50">libs..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 1.82%)</title><rect x="10.9091%" y="133" width="1.8182%" height="15" fill="rgb(218,30,26)" fg:x="6" fg:w="1"/><text x="11.1591%" y="143.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 1.82%)</title><rect x="10.9091%" y="117" width="1.8182%" height="15" fill="rgb(220,122,19)" fg:x="6" fg:w="1"/><text x="11.1591%" y="127.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_element_node (7 samples, 12.73%)</title><rect x="1.8182%" y="229" width="12.7273%" height="15" fill="rgb(250,228,42)" fg:x="1" fg:w="7"/><text x="2.0682%" y="239.50">jsframework-a8f4acf..</text></g><g><title>jsframework-a8f4acf5955e8e7f`alloc::raw_vec::RawVec&lt;T,A&gt;::reserve::do_reserve_and_handle (3 samples, 5.45%)</title><rect x="9.0909%" y="213" width="5.4545%" height="15" fill="rgb(240,193,28)" fg:x="5" fg:w="3"/><text x="9.3409%" y="223.50">jsframe..</text></g><g><title>jsframework-a8f4acf5955e8e7f`alloc::raw_vec::finish_grow (3 samples, 5.45%)</title><rect x="9.0909%" y="197" width="5.4545%" height="15" fill="rgb(216,20,37)" fg:x="5" fg:w="3"/><text x="9.3409%" y="207.50">jsframe..</text></g><g><title>libsystem_malloc.dylib`realloc (1 samples, 1.82%)</title><rect x="12.7273%" y="181" width="1.8182%" height="15" fill="rgb(206,188,39)" fg:x="7" fg:w="1"/><text x="12.9773%" y="191.50">l..</text></g><g><title>libsystem_malloc.dylib`malloc_zone_realloc (1 samples, 1.82%)</title><rect x="12.7273%" y="165" width="1.8182%" height="15" fill="rgb(217,207,13)" fg:x="7" fg:w="1"/><text x="12.9773%" y="175.50">l..</text></g><g><title>libsystem_malloc.dylib`szone_realloc (1 samples, 1.82%)</title><rect x="12.7273%" y="149" width="1.8182%" height="15" fill="rgb(231,73,38)" fg:x="7" fg:w="1"/><text x="12.9773%" y="159.50">l..</text></g><g><title>libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)</title><rect x="12.7273%" y="133" width="1.8182%" height="15" fill="rgb(225,20,46)" fg:x="7" fg:w="1"/><text x="12.9773%" y="143.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::fin_head (1 samples, 1.82%)</title><rect x="16.3636%" y="213" width="1.8182%" height="15" fill="rgb(210,31,41)" fg:x="9" fg:w="1"/><text x="16.6136%" y="223.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;bumpalo::Bump as core::default::Default&gt;::default (4 samples, 7.27%)</title><rect x="21.8182%" y="197" width="7.2727%" height="15" fill="rgb(221,200,47)" fg:x="12" fg:w="4"/><text x="22.0682%" y="207.50">jsframewor..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;bumpalo::Bump as core::ops::drop::Drop&gt;::drop (1 samples, 1.82%)</title><rect x="29.0909%" y="197" width="1.8182%" height="15" fill="rgb(226,26,5)" fg:x="16" fg:w="1"/><text x="29.3409%" y="207.50">j..</text></g><g><title>libsystem_malloc.dylib`free_tiny (1 samples, 1.82%)</title><rect x="29.0909%" y="181" width="1.8182%" height="15" fill="rgb(249,33,26)" fg:x="16" fg:w="1"/><text x="29.3409%" y="191.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_free_no_lock (1 samples, 1.82%)</title><rect x="29.0909%" y="165" width="1.8182%" height="15" fill="rgb(235,183,28)" fg:x="16" fg:w="1"/><text x="29.3409%" y="175.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 1.82%)</title><rect x="29.0909%" y="149" width="1.8182%" height="15" fill="rgb(221,5,38)" fg:x="16" fg:w="1"/><text x="29.3409%" y="159.50">l..</text></g><g><title>libsystem_malloc.dylib`malloc_zone_realloc (1 samples, 1.82%)</title><rect x="30.9091%" y="149" width="1.8182%" height="15" fill="rgb(247,18,42)" fg:x="17" fg:w="1"/><text x="31.1591%" y="159.50">l..</text></g><g><title>libsystem_platform.dylib`DYLD-STUB$$_platform_memmove (1 samples, 1.82%)</title><rect x="30.9091%" y="133" width="1.8182%" height="15" fill="rgb(241,131,45)" fg:x="17" fg:w="1"/><text x="31.1591%" y="143.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`alloc::raw_vec::RawVec&lt;T,A&gt;::reserve::do_reserve_and_handle (2 samples, 3.64%)</title><rect x="30.9091%" y="197" width="3.6364%" height="15" fill="rgb(249,31,29)" fg:x="17" fg:w="2"/><text x="31.1591%" y="207.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`alloc::raw_vec::finish_grow (2 samples, 3.64%)</title><rect x="30.9091%" y="181" width="3.6364%" height="15" fill="rgb(225,111,53)" fg:x="17" fg:w="2"/><text x="31.1591%" y="191.50">jsfr..</text></g><g><title>libsystem_malloc.dylib`realloc (2 samples, 3.64%)</title><rect x="30.9091%" y="165" width="3.6364%" height="15" fill="rgb(238,160,17)" fg:x="17" fg:w="2"/><text x="31.1591%" y="175.50">libs..</text></g><g><title>libsystem_malloc.dylib`szone_size (1 samples, 1.82%)</title><rect x="32.7273%" y="149" width="1.8182%" height="15" fill="rgb(214,148,48)" fg:x="18" fg:w="1"/><text x="32.9773%" y="159.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::Bump::with_capacity (1 samples, 1.82%)</title><rect x="34.5455%" y="197" width="1.8182%" height="15" fill="rgb(232,36,49)" fg:x="19" fg:w="1"/><text x="34.7955%" y="207.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::Bump::with_capacity (2 samples, 3.64%)</title><rect x="36.3636%" y="181" width="3.6364%" height="15" fill="rgb(209,103,24)" fg:x="20" fg:w="2"/><text x="36.6136%" y="191.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scope::BumpFrame::new (3 samples, 5.45%)</title><rect x="36.3636%" y="197" width="5.4545%" height="15" fill="rgb(229,88,8)" fg:x="20" fg:w="3"/><text x="36.6136%" y="207.50">jsframe..</text></g><g><title>libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 1.82%)</title><rect x="40.0000%" y="181" width="1.8182%" height="15" fill="rgb(213,181,19)" fg:x="22" fg:w="1"/><text x="40.2500%" y="191.50">l..</text></g><g><title>libsystem_malloc.dylib`szone_malloc_should_clear (1 samples, 1.82%)</title><rect x="40.0000%" y="165" width="1.8182%" height="15" fill="rgb(254,191,54)" fg:x="22" fg:w="1"/><text x="40.2500%" y="175.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_should_clear (1 samples, 1.82%)</title><rect x="40.0000%" y="149" width="1.8182%" height="15" fill="rgb(241,83,37)" fg:x="22" fg:w="1"/><text x="40.2500%" y="159.50">l..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 1.82%)</title><rect x="40.0000%" y="133" width="1.8182%" height="15" fill="rgb(233,36,39)" fg:x="22" fg:w="1"/><text x="40.2500%" y="143.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::new_with_key (14 samples, 25.45%)</title><rect x="18.1818%" y="213" width="25.4545%" height="15" fill="rgb(226,3,54)" fg:x="10" fg:w="14"/><text x="18.4318%" y="223.50">jsframework-a8f4acf5955e8e7f`dioxus_core:..</text></g><g><title>jsframework-a8f4acf5955e8e7f`hashbrown::raw::RawTable&lt;T,A&gt;::insert (1 samples, 1.82%)</title><rect x="41.8182%" y="197" width="1.8182%" height="15" fill="rgb(245,192,40)" fg:x="23" fg:w="1"/><text x="42.0682%" y="207.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::element (6 samples, 10.91%)</title><rect x="43.6364%" y="117" width="10.9091%" height="15" fill="rgb(238,167,29)" fg:x="24" fg:w="6"/><text x="43.8864%" y="127.50">jsframework-a8f4..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::Bump::alloc_layout_slow (6 samples, 10.91%)</title><rect x="43.6364%" y="101" width="10.9091%" height="15" fill="rgb(232,182,51)" fg:x="24" fg:w="6"/><text x="43.8864%" y="111.50">jsframework-a8f4..</text></g><g><title>libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 3.64%)</title><rect x="50.9091%" y="85" width="3.6364%" height="15" fill="rgb(231,60,39)" fg:x="28" fg:w="2"/><text x="51.1591%" y="95.50">libs..</text></g><g><title>libsystem_malloc.dylib`szone_malloc_should_clear (2 samples, 3.64%)</title><rect x="50.9091%" y="69" width="3.6364%" height="15" fill="rgb(208,69,12)" fg:x="28" fg:w="2"/><text x="51.1591%" y="79.50">libs..</text></g><g><title>libsystem_malloc.dylib`tiny_malloc_should_clear (2 samples, 3.64%)</title><rect x="50.9091%" y="53" width="3.6364%" height="15" fill="rgb(235,93,37)" fg:x="28" fg:w="2"/><text x="51.1591%" y="63.50">libs..</text></g><g><title>libsystem_malloc.dylib`set_tiny_meta_header_in_use (1 samples, 1.82%)</title><rect x="52.7273%" y="37" width="1.8182%" height="15" fill="rgb(213,116,39)" fg:x="29" fg:w="1"/><text x="52.9773%" y="47.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::raw_element (12 samples, 21.82%)</title><rect x="54.5455%" y="117" width="21.8182%" height="15" fill="rgb(222,207,29)" fg:x="30" fg:w="12"/><text x="54.7955%" y="127.50">jsframework-a8f4acf5955e8e7f`dioxu..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::Bump::alloc_layout_slow (12 samples, 21.82%)</title><rect x="54.5455%" y="101" width="21.8182%" height="15" fill="rgb(206,96,30)" fg:x="30" fg:w="12"/><text x="54.7955%" y="111.50">jsframework-a8f4acf5955e8e7f`bumpa..</text></g><g><title>libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 1.82%)</title><rect x="74.5455%" y="85" width="1.8182%" height="15" fill="rgb(218,138,4)" fg:x="41" fg:w="1"/><text x="74.7955%" y="95.50">l..</text></g><g><title>libsystem_malloc.dylib`szone_malloc_should_clear (1 samples, 1.82%)</title><rect x="74.5455%" y="69" width="1.8182%" height="15" fill="rgb(250,191,14)" fg:x="41" fg:w="1"/><text x="74.7955%" y="79.50">l..</text></g><g><title>libsystem_malloc.dylib`small_malloc_should_clear (1 samples, 1.82%)</title><rect x="74.5455%" y="53" width="1.8182%" height="15" fill="rgb(239,60,40)" fg:x="41" fg:w="1"/><text x="74.7955%" y="63.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;&amp;T as core::fmt::Display&gt;::fmt (1 samples, 1.82%)</title><rect x="76.3636%" y="101" width="1.8182%" height="15" fill="rgb(206,27,48)" fg:x="42" fg:w="1"/><text x="76.6136%" y="111.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`bumpalo::collections::string::String::into_bump_str (1 samples, 1.82%)</title><rect x="78.1818%" y="101" width="1.8182%" height="15" fill="rgb(225,35,8)" fg:x="43" fg:w="1"/><text x="78.4318%" y="111.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::text (3 samples, 5.45%)</title><rect x="76.3636%" y="117" width="5.4545%" height="15" fill="rgb(250,213,24)" fg:x="42" fg:w="3"/><text x="76.6136%" y="127.50">jsframe..</text></g><g><title>jsframework-a8f4acf5955e8e7f`core::fmt::write (1 samples, 1.82%)</title><rect x="80.0000%" y="101" width="1.8182%" height="15" fill="rgb(247,123,22)" fg:x="44" fg:w="1"/><text x="80.2500%" y="111.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;&amp;mut W as core::fmt::Write&gt;::write_str (1 samples, 1.82%)</title><rect x="80.0000%" y="85" width="1.8182%" height="15" fill="rgb(231,138,38)" fg:x="44" fg:w="1"/><text x="80.2500%" y="95.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (22 samples, 40.00%)</title><rect x="43.6364%" y="149" width="40.0000%" height="15" fill="rgb(231,145,46)" fg:x="24" fg:w="22"/><text x="43.8864%" y="159.50">jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::c..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (22 samples, 40.00%)</title><rect x="43.6364%" y="133" width="40.0000%" height="15" fill="rgb(251,118,11)" fg:x="24" fg:w="22"/><text x="43.8864%" y="143.50">jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::n..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::empty_cell (1 samples, 1.82%)</title><rect x="81.8182%" y="117" width="1.8182%" height="15" fill="rgb(217,147,25)" fg:x="45" fg:w="1"/><text x="82.0682%" y="127.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render (23 samples, 41.82%)</title><rect x="43.6364%" y="165" width="41.8182%" height="15" fill="rgb(247,81,37)" fg:x="24" fg:w="23"/><text x="43.8864%" y="175.50">jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render</text></g><g><title>libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)</title><rect x="83.6364%" y="149" width="1.8182%" height="15" fill="rgb(209,12,38)" fg:x="46" fg:w="1"/><text x="83.8864%" y="159.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_node (40 samples, 72.73%)</title><rect x="14.5455%" y="229" width="72.7273%" height="15" fill="rgb(227,1,9)" fg:x="8" fg:w="40"/><text x="14.7955%" y="239.50">jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_node</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_scope (24 samples, 43.64%)</title><rect x="43.6364%" y="213" width="43.6364%" height="15" fill="rgb(248,47,43)" fg:x="24" fg:w="24"/><text x="43.8864%" y="223.50">jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_s..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component::_{{closure}} (24 samples, 43.64%)</title><rect x="43.6364%" y="197" width="43.6364%" height="15" fill="rgb(221,10,30)" fg:x="24" fg:w="24"/><text x="43.8864%" y="207.50">jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component..</text></g><g><title>jsframework-a8f4acf5955e8e7f`jsframework::Row (24 samples, 43.64%)</title><rect x="43.6364%" y="181" width="43.6364%" height="15" fill="rgb(210,229,1)" fg:x="24" fg:w="24"/><text x="43.8864%" y="191.50">jsframework-a8f4acf5955e8e7f`jsframework::Row</text></g><g><title>libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)</title><rect x="85.4545%" y="165" width="1.8182%" height="15" fill="rgb(222,148,37)" fg:x="47" fg:w="1"/><text x="85.7045%" y="175.50">l..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::work (48 samples, 87.27%)</title><rect x="1.8182%" y="245" width="87.2727%" height="15" fill="rgb(234,67,33)" fg:x="1" fg:w="48"/><text x="2.0682%" y="255.50">jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::work</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::mount (1 samples, 1.82%)</title><rect x="87.2727%" y="229" width="1.8182%" height="15" fill="rgb(247,98,35)" fg:x="48" fg:w="1"/><text x="87.5227%" y="239.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;core::option::Option&lt;dioxus_core::lazynodes::LazyNodes&gt; as dioxus_core::nodes::IntoVNode&gt;::into_vnode (1 samples, 1.82%)</title><rect x="89.0909%" y="133" width="1.8182%" height="15" fill="rgb(247,138,52)" fg:x="49" fg:w="1"/><text x="89.3409%" y="143.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (1 samples, 1.82%)</title><rect x="89.0909%" y="117" width="1.8182%" height="15" fill="rgb(213,79,30)" fg:x="49" fg:w="1"/><text x="89.3409%" y="127.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (1 samples, 1.82%)</title><rect x="89.0909%" y="101" width="1.8182%" height="15" fill="rgb(246,177,23)" fg:x="49" fg:w="1"/><text x="89.3409%" y="111.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component (1 samples, 1.82%)</title><rect x="89.0909%" y="85" width="1.8182%" height="15" fill="rgb(230,62,27)" fg:x="49" fg:w="1"/><text x="89.3409%" y="95.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`main (51 samples, 92.73%)</title><rect x="0.0000%" y="421" width="92.7273%" height="15" fill="rgb(216,154,8)" fg:x="0" fg:w="51"/><text x="0.2500%" y="431.50">jsframework-a8f4acf5955e8e7f`main</text></g><g><title>jsframework-a8f4acf5955e8e7f`std::rt::lang_start_internal (51 samples, 92.73%)</title><rect x="0.0000%" y="405" width="92.7273%" height="15" fill="rgb(244,35,45)" fg:x="0" fg:w="51"/><text x="0.2500%" y="415.50">jsframework-a8f4acf5955e8e7f`std::rt::lang_start_internal</text></g><g><title>jsframework-a8f4acf5955e8e7f`std::rt::lang_start::_{{closure}} (51 samples, 92.73%)</title><rect x="0.0000%" y="389" width="92.7273%" height="15" fill="rgb(251,115,12)" fg:x="0" fg:w="51"/><text x="0.2500%" y="399.50">jsframework-a8f4acf5955e8e7f`std::rt::lang_start::_{{closure}}</text></g><g><title>jsframework-a8f4acf5955e8e7f`std::sys_common::backtrace::__rust_begin_short_backtrace (51 samples, 92.73%)</title><rect x="0.0000%" y="373" width="92.7273%" height="15" fill="rgb(240,54,50)" fg:x="0" fg:w="51"/><text x="0.2500%" y="383.50">jsframework-a8f4acf5955e8e7f`std::sys_common::backtrace::__rust_begin_short_backtrace</text></g><g><title>jsframework-a8f4acf5955e8e7f`jsframework::main (51 samples, 92.73%)</title><rect x="0.0000%" y="357" width="92.7273%" height="15" fill="rgb(233,84,52)" fg:x="0" fg:w="51"/><text x="0.2500%" y="367.50">jsframework-a8f4acf5955e8e7f`jsframework::main</text></g><g><title>jsframework-a8f4acf5955e8e7f`criterion::Criterion&lt;M&gt;::bench_function (51 samples, 92.73%)</title><rect x="0.0000%" y="341" width="92.7273%" height="15" fill="rgb(207,117,47)" fg:x="0" fg:w="51"/><text x="0.2500%" y="351.50">jsframework-a8f4acf5955e8e7f`criterion::Criterion&lt;M&gt;::bench_function</text></g><g><title>jsframework-a8f4acf5955e8e7f`criterion::benchmark_group::BenchmarkGroup&lt;M&gt;::bench_function (51 samples, 92.73%)</title><rect x="0.0000%" y="325" width="92.7273%" height="15" fill="rgb(249,43,39)" fg:x="0" fg:w="51"/><text x="0.2500%" y="335.50">jsframework-a8f4acf5955e8e7f`criterion::benchmark_group::BenchmarkGroup&lt;M&gt;::bench_function</text></g><g><title>jsframework-a8f4acf5955e8e7f`criterion::routine::Routine::test (51 samples, 92.73%)</title><rect x="0.0000%" y="309" width="92.7273%" height="15" fill="rgb(209,38,44)" fg:x="0" fg:w="51"/><text x="0.2500%" y="319.50">jsframework-a8f4acf5955e8e7f`criterion::routine::Routine::test</text></g><g><title>jsframework-a8f4acf5955e8e7f`&lt;alloc::vec::Vec&lt;T&gt; as alloc::vec::spec_from_iter::SpecFromIter&lt;T,I&gt;&gt;::from_iter (51 samples, 92.73%)</title><rect x="0.0000%" y="293" width="92.7273%" height="15" fill="rgb(236,212,23)" fg:x="0" fg:w="51"/><text x="0.2500%" y="303.50">jsframework-a8f4acf5955e8e7f`&lt;alloc::vec::Vec&lt;T&gt; as alloc::vec::spec_from_iter::SpecFromIter&lt;T,I&gt;&gt;::from_iter</text></g><g><title>jsframework-a8f4acf5955e8e7f`criterion::bencher::Bencher&lt;M&gt;::iter (51 samples, 92.73%)</title><rect x="0.0000%" y="277" width="92.7273%" height="15" fill="rgb(242,79,21)" fg:x="0" fg:w="51"/><text x="0.2500%" y="287.50">jsframework-a8f4acf5955e8e7f`criterion::bencher::Bencher&lt;M&gt;::iter</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::rebuild (50 samples, 90.91%)</title><rect x="1.8182%" y="261" width="90.9091%" height="15" fill="rgb(211,96,35)" fg:x="1" fg:w="50"/><text x="2.0682%" y="271.50">jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::rebuild</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_scope (2 samples, 3.64%)</title><rect x="89.0909%" y="245" width="3.6364%" height="15" fill="rgb(253,215,40)" fg:x="49" fg:w="2"/><text x="89.3409%" y="255.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::new_with_props_and_scheduler::_{{closure}} (2 samples, 3.64%)</title><rect x="89.0909%" y="229" width="3.6364%" height="15" fill="rgb(211,81,21)" fg:x="49" fg:w="2"/><text x="89.3409%" y="239.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`core::ops::function::FnOnce::call_once (2 samples, 3.64%)</title><rect x="89.0909%" y="213" width="3.6364%" height="15" fill="rgb(208,190,38)" fg:x="49" fg:w="2"/><text x="89.3409%" y="223.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render (2 samples, 3.64%)</title><rect x="89.0909%" y="197" width="3.6364%" height="15" fill="rgb(235,213,38)" fg:x="49" fg:w="2"/><text x="89.3409%" y="207.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (2 samples, 3.64%)</title><rect x="89.0909%" y="181" width="3.6364%" height="15" fill="rgb(237,122,38)" fg:x="49" fg:w="2"/><text x="89.3409%" y="191.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (2 samples, 3.64%)</title><rect x="89.0909%" y="165" width="3.6364%" height="15" fill="rgb(244,218,35)" fg:x="49" fg:w="2"/><text x="89.3409%" y="175.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::fragment_from_iter (2 samples, 3.64%)</title><rect x="89.0909%" y="149" width="3.6364%" height="15" fill="rgb(240,68,47)" fg:x="49" fg:w="2"/><text x="89.3409%" y="159.50">jsfr..</text></g><g><title>jsframework-a8f4acf5955e8e7f`core::ops::function::impls::_&lt;impl core::ops::function::FnOnce&lt;A&gt; for &amp;mut F&gt;::call_once (1 samples, 1.82%)</title><rect x="90.9091%" y="133" width="1.8182%" height="15" fill="rgb(210,16,53)" fg:x="50" fg:w="1"/><text x="91.1591%" y="143.50">j..</text></g><g><title>jsframework-a8f4acf5955e8e7f`rand::rng::Rng::gen_range (1 samples, 1.82%)</title><rect x="90.9091%" y="117" width="1.8182%" height="15" fill="rgb(235,124,12)" fg:x="50" fg:w="1"/><text x="91.1591%" y="127.50">j..</text></g><g><title>all (55 samples, 100%)</title><rect x="0.0000%" y="469" width="100.0000%" height="15" fill="rgb(224,169,11)" fg:x="0" fg:w="55"/><text x="0.2500%" y="479.50"></text></g><g><title>0x1 (55 samples, 100.00%)</title><rect x="0.0000%" y="453" width="100.0000%" height="15" fill="rgb(250,166,2)" fg:x="0" fg:w="55"/><text x="0.2500%" y="463.50">0x1</text></g><g><title>libdyld.dylib`start (55 samples, 100.00%)</title><rect x="0.0000%" y="437" width="100.0000%" height="15" fill="rgb(242,216,29)" fg:x="0" fg:w="55"/><text x="0.2500%" y="447.50">libdyld.dylib`start</text></g><g><title>libsystem_kernel.dylib`__exit (4 samples, 7.27%)</title><rect x="92.7273%" y="421" width="7.2727%" height="15" fill="rgb(230,116,27)" fg:x="51" fg:w="4"/><text x="92.9773%" y="431.50">libsystem_..</text></g></svg></svg>

+ 61 - 12
packages/core/src/diff.rs

@@ -434,8 +434,15 @@ impl<'bump> DiffState<'bump> {
         }
         }
 
 
         if !children.is_empty() {
         if !children.is_empty() {
-            self.stack.create_children(children, MountType::Append);
+            if children.len() == 1 {
+                if let VNode::Text(vtext) = children[0] {
+                    self.mutations.set_text(vtext.text, real_id.as_u64());
+                    return;
+                }
+            }
         }
         }
+
+        self.stack.create_children(children, MountType::Append);
     }
     }
 
 
     fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) {
     fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) {
@@ -645,17 +652,59 @@ impl<'bump> DiffState<'bump> {
             }
             }
         }
         }
 
 
-        if old.children.is_empty() && !new.children.is_empty() {
-            self.mutations.edits.push(PushRoot {
-                root: root.as_u64(),
-            });
-            self.stack.element_stack.push(root);
-            self.stack.instructions.push(DiffInstruction::PopElement);
-            self.stack.create_children(new.children, MountType::Append);
-        } else {
-            self.stack.element_stack.push(root);
-            self.stack.instructions.push(DiffInstruction::PopElement);
-            self.diff_children(old.children, new.children);
+        match (old.children.len(), new.children.len()) {
+            (0, 0) => {}
+            (1, 1) => {
+                let old1 = &old.children[0];
+                let new1 = &new.children[0];
+
+                match (old1, new1) {
+                    (VNode::Text(old_text), VNode::Text(new_text)) => {
+                        if old_text.text != new_text.text {
+                            self.mutations.set_text(new_text.text, root.as_u64());
+                        }
+                    }
+                    (VNode::Text(old_text), _) => {
+                        self.stack.element_stack.push(root);
+                        self.stack.instructions.push(DiffInstruction::PopElement);
+                        self.stack.create_node(new1, MountType::Append);
+                    }
+                    (_, VNode::Text(new_text)) => {
+                        self.remove_nodes([old1], false);
+                        self.mutations.set_text(new_text.text, root.as_u64());
+                    }
+                    _ => {
+                        self.stack.element_stack.push(root);
+                        self.stack.instructions.push(DiffInstruction::PopElement);
+                        self.diff_children(old.children, new.children);
+                    }
+                }
+            }
+            (0, 1) => {
+                if let VNode::Text(text) = &new.children[0] {
+                    self.mutations.set_text(text.text, root.as_u64());
+                } else {
+                    self.stack.element_stack.push(root);
+                    self.stack.instructions.push(DiffInstruction::PopElement);
+                }
+            }
+            (0, _) => {
+                self.mutations.edits.push(PushRoot {
+                    root: root.as_u64(),
+                });
+                self.stack.element_stack.push(root);
+                self.stack.instructions.push(DiffInstruction::PopElement);
+                self.stack.create_children(new.children, MountType::Append);
+            }
+            (_, 0) => {
+                self.remove_nodes(old.children, false);
+                self.mutations.set_text("", root.as_u64());
+            }
+            (_, _) => {
+                self.stack.element_stack.push(root);
+                self.stack.instructions.push(DiffInstruction::PopElement);
+                self.diff_children(old.children, new.children);
+            }
         }
         }
     }
     }
 
 

+ 1 - 1
packages/core/src/scope.rs

@@ -196,7 +196,7 @@ impl Scope {
         let chan = self.sender.clone();
         let chan = self.sender.clone();
         let id = self.scope_id();
         let id = self.scope_id();
         Rc::new(move || {
         Rc::new(move || {
-            log::debug!("set on channel an update for scope {:?}", id);
+            // log::debug!("set on channel an update for scope {:?}", id);
             let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
             let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
         })
         })
     }
     }

+ 8 - 8
packages/core/src/scopearena.rs

@@ -92,15 +92,15 @@ impl ScopeArena {
         let new_scope_id = ScopeId(self.scope_counter.get());
         let new_scope_id = ScopeId(self.scope_counter.get());
         self.scope_counter.set(self.scope_counter.get() + 1);
         self.scope_counter.set(self.scope_counter.get() + 1);
 
 
-        log::debug!("new scope {:?} with parent {:?}", new_scope_id, container);
+        // log::debug!("new scope {:?} with parent {:?}", new_scope_id, container);
 
 
         if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
         if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
             let scope = unsafe { &mut *old_scope };
             let scope = unsafe { &mut *old_scope };
-            log::debug!(
-                "reusing scope {:?} as {:?}",
-                scope.our_arena_idx,
-                new_scope_id
-            );
+            // log::debug!(
+            //     "reusing scope {:?} as {:?}",
+            //     scope.our_arena_idx,
+            //     new_scope_id
+            // );
 
 
             scope.caller = caller;
             scope.caller = caller;
             scope.parent_scope = parent_scope;
             scope.parent_scope = parent_scope;
@@ -202,7 +202,7 @@ impl ScopeArena {
     pub fn try_remove(&self, id: &ScopeId) -> Option<()> {
     pub fn try_remove(&self, id: &ScopeId) -> Option<()> {
         self.ensure_drop_safety(id);
         self.ensure_drop_safety(id);
 
 
-        log::debug!("removing scope {:?}", id);
+        // log::debug!("removing scope {:?}", id);
 
 
         // Safety:
         // Safety:
         // - ensure_drop_safety ensures that no references to this scope are in use
         // - ensure_drop_safety ensures that no references to this scope are in use
@@ -311,7 +311,7 @@ impl ScopeArena {
 
 
         let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") };
         let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") };
 
 
-        log::debug!("found scope, about to run: {:?}", id);
+        // log::debug!("found scope, about to run: {:?}", id);
 
 
         // Safety:
         // Safety:
         // - We dropped the listeners, so no more &mut T can be used while these are held
         // - We dropped the listeners, so no more &mut T can be used while these are held

+ 1 - 1
packages/core/src/virtual_dom.rs

@@ -352,7 +352,7 @@ impl VirtualDom {
         let mut committed_mutations = vec![];
         let mut committed_mutations = vec![];
 
 
         while !self.dirty_scopes.is_empty() {
         while !self.dirty_scopes.is_empty() {
-            log::debug!("working with deadline");
+            // log::debug!("working with deadline");
             let scopes = &self.scopes;
             let scopes = &self.scopes;
             let mut diff_state = DiffState::new(scopes);
             let mut diff_state = DiffState::new(scopes);
 
 

+ 7 - 7
packages/desktop/README.md

@@ -7,7 +7,7 @@ fn main() {
     dioxus::desktop::launch(App, |c| c)
     dioxus::desktop::launch(App, |c| c)
 }
 }
 
 
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     let (count, set_count) = use_state(cx, || 0);
     let (count, set_count) = use_state(cx, || 0);
 
 
     cx.render(rsx!(
     cx.render(rsx!(
@@ -34,7 +34,7 @@ Window management, system trays, notifications, and other desktop-related functi
 Managing windows is done by simply rendering content into a `WebviewWindow` component. 
 Managing windows is done by simply rendering content into a `WebviewWindow` component. 
 
 
 ```rust
 ```rust
-static App: FC<()> = |(cx, props)| {
+static App: FC<()> = |cx, props| {
     rsx!(cx, WebviewWindow { "hello world" } )
     rsx!(cx, WebviewWindow { "hello world" } )
 }
 }
 ```
 ```
@@ -46,7 +46,7 @@ Notifications also use a declarative approach. Sending a notification has never
 The api has been somewhat modeled after https://github.com/mikaelbr/node-notifier
 The api has been somewhat modeled after https://github.com/mikaelbr/node-notifier
 
 
 ```rust
 ```rust
-static Notifications: FC<()> = |(cx, props)| {
+static Notifications: FC<()> = |cx, props| {
     cx.render(rsx!(
     cx.render(rsx!(
         Notification {
         Notification {
             title: "title"
             title: "title"
@@ -78,7 +78,7 @@ static Notifications: FC<()> = |(cx, props)| {
 Dioxus Desktop supports app trays, which can be built with native menu groups or with a custom window.
 Dioxus Desktop supports app trays, which can be built with native menu groups or with a custom window.
 
 
 ```rust
 ```rust
-static Tray: FC<()> = |(cx, props)| {
+static Tray: FC<()> = |cx, props| {
     cx.render(rsx!(
     cx.render(rsx!(
         GlobalTray {
         GlobalTray {
             MenuGroup {
             MenuGroup {
@@ -90,7 +90,7 @@ static Tray: FC<()> = |(cx, props)| {
 };
 };
 
 
 // using a builder
 // using a builder
-static Tray: FC<()> = |(cx, props)| {
+static Tray: FC<()> = |cx, props| {
     let menu = MenuGroup::builder(cx)
     let menu = MenuGroup::builder(cx)
         .with_items([
         .with_items([
             MenuGroupItem::builder()
             MenuGroupItem::builder()
@@ -107,7 +107,7 @@ static Tray: FC<()> = |(cx, props)| {
 }
 }
 
 
 // or with a custom window
 // or with a custom window
-static Tray: FC<()> = |(cx, props)| {
+static Tray: FC<()> = |cx, props| {
     rsx!(cx, GlobalTray { div { "custom buttons here" } })
     rsx!(cx, GlobalTray { div { "custom buttons here" } })
 };
 };
 ```
 ```
@@ -116,7 +116,7 @@ static Tray: FC<()> = |(cx, props)| {
 Declaring menus is convenient and cross-platform.
 Declaring menus is convenient and cross-platform.
 
 
 ```rust
 ```rust
-static Menu: FC<()> = |(cx, props)| {
+static Menu: FC<()> = |cx, props| {
     cx.render(rsx!(
     cx.render(rsx!(
         MenuBarMajorItem { title: "File"
         MenuBarMajorItem { title: "File"
             MenuGroup {
             MenuGroup {

+ 2 - 3
packages/desktop/src/index.html

@@ -19,10 +19,9 @@
 
 
 
 
 <body>
 <body>
-    <div id="_dioxusroot">
+    <div id="main">
     </div>
     </div>
 </body>
 </body>
-<script type="text/javascript" src="index.js">
-</script>
+<script type="text/javascript" src="index.js"> </script>
 
 
 </html>
 </html>

+ 6 - 0
packages/desktop/src/lib.rs

@@ -90,6 +90,8 @@ pub fn run<T: 'static + Send + Sync>(
     let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
     let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
     let is_ready: Arc<AtomicBool> = Default::default();
     let is_ready: Arc<AtomicBool> = Default::default();
 
 
+    let mut frame = 0;
+
     event_loop.run(move |window_event, event_loop, control_flow| {
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
         *control_flow = ControlFlow::Wait;
 
 
@@ -145,6 +147,9 @@ pub fn run<T: 'static + Send + Sync>(
                         view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
                         view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
                             .unwrap();
                             .unwrap();
                     }
                     }
+                } else {
+                    println!("waiting for onload {:?}", frame);
+                    frame += 1;
                 }
                 }
             }
             }
             Event::Resumed => {}
             Event::Resumed => {}
@@ -258,6 +263,7 @@ fn create_webview(
             // always driven through eval
             // always driven through eval
             None
             None
         })
         })
+        // .with_initialization_script(include_str!("./index.js"))
         // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
         // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
         // For now, we only serve two pieces of content which get included as bytes into the final binary.
         // For now, we only serve two pieces of content which get included as bytes into the final binary.
         .with_custom_protocol("wry".into(), move |request| {
         .with_custom_protocol("wry".into(), move |request| {

+ 1 - 1
packages/hooks/src/usecollection.rs

@@ -36,7 +36,7 @@ uses the same memoization on top of the use_context API.
 
 
 Here's a fully-functional todo app using the use_map API:
 Here's a fully-functional todo app using the use_map API:
 ```rust
 ```rust
-static TodoList: FC<()> = |(cx, props)|{
+static TodoList: FC<()> = |cx, props|{
     let todos = use_map(cx, || HashMap::new());
     let todos = use_map(cx, || HashMap::new());
     let input = use_ref(|| None);
     let input = use_ref(|| None);
 
 

+ 5 - 0
packages/hooks/src/useref.rs

@@ -33,6 +33,11 @@ impl<'a, T> UseRef<'a, T> {
         self.inner.value.borrow()
         self.inner.value.borrow()
     }
     }
 
 
+    pub fn set(&self, new: T) {
+        *self.inner.value.borrow_mut() = new;
+        self.needs_update();
+    }
+
     pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
     pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
         (self.read(), self)
         (self.read(), self)
     }
     }

+ 1 - 1
packages/hooks/src/usestate.rs

@@ -35,7 +35,7 @@ use std::{
 ///
 ///
 /// Usage:
 /// Usage:
 /// ```ignore
 /// ```ignore
-/// const Example: FC<()> = |(cx, props)|{
+/// const Example: FC<()> = |cx, props|{
 ///     let counter = use_state(cx, || 0);
 ///     let counter = use_state(cx, || 0);
 ///     let increment = |_| counter += 1;
 ///     let increment = |_| counter += 1;
 ///     let decrement = |_| counter += 1;
 ///     let decrement = |_| counter += 1;

+ 18 - 3
packages/html/src/elements.rs

@@ -717,8 +717,7 @@ builder_constructors! {
         nonce: Nonce,
         nonce: Nonce,
         src: Uri,
         src: Uri,
         text: String,
         text: String,
-        r#async: Bool,
-        r#type: String, // TODO could be an enum
+
     };
     };
 
 
 
 
@@ -823,7 +822,6 @@ builder_constructors! {
         formnovalidate: Bool,
         formnovalidate: Bool,
         formtarget: Target,
         formtarget: Target,
         name: Id,
         name: Id,
-        r#type: ButtonType,
         value: String,
         value: String,
     };
     };
 
 
@@ -1064,6 +1062,23 @@ impl input {
 volatile attributes
 volatile attributes
 */
 */
 
 
+impl script {
+    // r#async: Bool,
+    // r#type: String, // TODO could be an enum
+    pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("type", val, None, false)
+    }
+    pub fn r#script<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("script", val, None, false)
+    }
+}
+
+impl button {
+    pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
+        cx.attr("type", val, None, false)
+    }
+}
+
 impl select {
 impl select {
     pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
     pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
         cx.attr("value", val, None, true)
         cx.attr("value", val, None, true)

+ 6 - 0
packages/router/Cargo.toml

@@ -37,3 +37,9 @@ default = ["web"]
 web = ["web-sys"]
 web = ["web-sys"]
 desktop = []
 desktop = []
 mobile = []
 mobile = []
+
+[dev-dependencies]
+console_error_panic_hook = "0.1.7"
+dioxus-web = { path = "../web" }
+log = "0.4.14"
+wasm-logger = "0.2.0"

+ 40 - 0
packages/router/README.md

@@ -0,0 +1,40 @@
+# Router hook for Dioxus apps
+
+Dioxus-router provides a use_router hook that returns a different value depending on the route.
+The router is generic over any value, however it makes sense to return a different set of VNodes
+and feed them into the App's return VNodes.
+
+Using the router should feel similar to tide's routing framework where an "address" book is assembled at the head of the app.
+
+Here's an example of how to use the router hook:
+
+```rust
+#[derive(Clone, Routable)]
+enum AppRoute {
+    Home, 
+    Posts,
+    NotFound
+}
+
+static App: FC<()> = |cx, props| {
+    let route = use_router(cx, AppRoute::parse);
+    
+    match route {
+        AppRoute::Home => rsx!(cx, Home {})
+        AppRoute::Posts => rsx!(cx, Posts {})
+        AppRoute::Notfound => rsx!(cx, Notfound {})
+    }
+};
+```
+
+Adding links into your app:
+
+```rust
+static Leaf: FC<()> = |cx, props| {
+    rsx!(cx, div { 
+        Link { to: AppRoute::Home } 
+    })
+}
+```
+
+Currently, the router is only supported in a web environment, but we plan to add 1st-party support via the context API when new renderers are available.

+ 45 - 0
packages/router/examples/simple.rs

@@ -0,0 +1,45 @@
+use dioxus_core::prelude::*;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use dioxus_router::*;
+
+fn main() {
+    console_error_panic_hook::set_once();
+    dioxus_web::launch(App, |c| c);
+}
+
+#[derive(Clone, Debug, PartialEq)]
+enum Route {
+    Home,
+    About,
+    NotFound,
+}
+
+static App: FC<()> = |cx, props| {
+    let route = use_router(cx, Route::parse);
+
+    match route {
+        Route::Home => rsx!(cx, div { "Home" }),
+        Route::About => rsx!(cx, div { "About" }),
+        Route::NotFound => rsx!(cx, div { "NotFound" }),
+    }
+};
+
+impl ToString for Route {
+    fn to_string(&self) -> String {
+        match self {
+            Route::Home => "/".to_string(),
+            Route::About => "/about".to_string(),
+            Route::NotFound => "/404".to_string(),
+        }
+    }
+}
+impl Route {
+    fn parse(s: &str) -> Self {
+        match s {
+            "/" => Route::Home,
+            "/about" => Route::About,
+            _ => Route::NotFound,
+        }
+    }
+}

+ 13 - 23
packages/router/src/lib.rs

@@ -11,14 +11,18 @@ use web_sys::Event;
 
 
 use crate::utils::fetch_base_url;
 use crate::utils::fetch_base_url;
 
 
+pub trait Routable: 'static + Send + Clone + ToString + PartialEq {}
+impl<T> Routable for T where T: 'static + Send + Clone + ToString + PartialEq {}
+
 pub struct RouterService<R: Routable> {
 pub struct RouterService<R: Routable> {
-    history: RefCell<Vec<R>>,
+    historic_routes: RefCell<Vec<R>>,
+    history_service: web_sys::History,
     base_ur: RefCell<Option<String>>,
     base_ur: RefCell<Option<String>>,
 }
 }
 
 
 impl<R: Routable> RouterService<R> {
 impl<R: Routable> RouterService<R> {
     fn push_route(&self, r: R) {
     fn push_route(&self, r: R) {
-        self.history.borrow_mut().push(r);
+        self.historic_routes.borrow_mut().push(r);
     }
     }
 
 
     fn get_current_route(&self) -> &str {
     fn get_current_route(&self) -> &str {
@@ -61,7 +65,7 @@ impl<R: Routable> RouterService<R> {
 /// This hould only be used once per app
 /// This hould only be used once per app
 ///
 ///
 /// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine
 /// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine
-pub fn use_router<R: Routable>(cx: Context, cfg: impl FnOnce(&str) -> R) -> Option<&R> {
+pub fn use_router<R: Routable>(cx: Context, parse: impl FnMut(&str) -> R) -> &R {
     // for the web, attach to the history api
     // for the web, attach to the history api
     cx.use_hook(
     cx.use_hook(
         |f| {
         |f| {
@@ -71,10 +75,13 @@ pub fn use_router<R: Routable>(cx: Context, cfg: impl FnOnce(&str) -> R) -> Opti
             let base_url = fetch_base_url();
             let base_url = fetch_base_url();
 
 
             let service: RouterService<R> = RouterService {
             let service: RouterService<R> = RouterService {
-                history: RefCell::new(vec![]),
+                historic_routes: RefCell::new(vec![]),
+                history_service: web_sys::window().unwrap().history().expect("no history"),
                 base_ur: RefCell::new(base_url),
                 base_ur: RefCell::new(base_url),
             };
             };
 
 
+            // service.history_service.push_state(data, title);
+
             cx.provide_state(service);
             cx.provide_state(service);
 
 
             let regenerate = cx.schedule_update();
             let regenerate = cx.schedule_update();
@@ -105,30 +112,13 @@ pub struct LinkProps<R: Routable> {
     children: Element,
     children: Element,
 }
 }
 
 
-pub fn Link<'a, R: Routable>(cx: Context, props: &LinkProps<R>) -> Element {
+pub fn Link<R: Routable>(cx: Context, props: &LinkProps<R>) -> Element {
     let service = use_router_service::<R>(cx)?;
     let service = use_router_service::<R>(cx)?;
     cx.render(rsx! {
     cx.render(rsx! {
         a {
         a {
-            href: format_args!("{}", props.to.to_path()),
+            href: format_args!("{}", props.to.to_string()),
             onclick: move |_| service.push_route(props.to.clone()),
             onclick: move |_| service.push_route(props.to.clone()),
             {&props.children},
             {&props.children},
         }
         }
     })
     })
 }
 }
-
-pub trait Routable: Sized + Clone + 'static {
-    /// Converts path to an instance of the routes enum.
-    fn from_path(path: &str, params: &HashMap<&str, &str>) -> Option<Self>;
-
-    /// Converts the route to a string that can passed to the history API.
-    fn to_path(&self) -> String;
-
-    /// Lists all the available routes
-    fn routes() -> Vec<&'static str>;
-
-    /// The route to redirect to on 404
-    fn not_found_route() -> Option<Self>;
-
-    /// Match a route based on the path
-    fn recognize(pathname: &str) -> Option<Self>;
-}

+ 1 - 1
packages/ssr/README.md

@@ -5,7 +5,7 @@ Render a Dioxus VirtualDOM to a string.
 
 
 ```rust
 ```rust
 // Our app:
 // Our app:
-const App: FC<()> = |(cx, props)| rsx!(cx, div {"hello world!"});
+const App: FC<()> = |cx, props| rsx!(cx, div {"hello world!"});
 
 
 // Build the virtualdom from our app
 // Build the virtualdom from our app
 let mut vdom = VirtualDOM::new(App);
 let mut vdom = VirtualDOM::new(App);

+ 1 - 1
packages/ssr/src/lib.rs

@@ -110,7 +110,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
 ///
 ///
 /// ## Example
 /// ## Example
 /// ```ignore
 /// ```ignore
-/// static App: FC<()> = |(cx, props)|cx.render(rsx!(div { "hello world" }));
+/// static App: FC<()> = |cx, props|cx.render(rsx!(div { "hello world" }));
 /// let mut vdom = VirtualDom::new(App);
 /// let mut vdom = VirtualDom::new(App);
 /// vdom.rebuild();
 /// vdom.rebuild();
 ///
 ///

+ 20 - 2
packages/web/Cargo.toml

@@ -11,10 +11,17 @@ license = "MIT/Apache-2.0"
 dioxus-core = { path = "../core", version = "0.1.2" }
 dioxus-core = { path = "../core", version = "0.1.2" }
 dioxus-html = { path = "../html" }
 dioxus-html = { path = "../html" }
 js-sys = "0.3"
 js-sys = "0.3"
+# wasm-bindgen-shared = { path = "../../../Tinkering/wasm-bindgen/crates/shared" }
+# wasm-bindgen-macro-support = { path = "../../../Tinkering/wasm-bindgen/crates/macro-support" }
+# wasm-bindgen = { features = [
+
+
 wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
 wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
+# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
+# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] }
 lazy_static = "1.4.0"
 lazy_static = "1.4.0"
 wasm-bindgen-futures = "0.4.20"
 wasm-bindgen-futures = "0.4.20"
-log = "0.4.14"
+log = { version = "0.4.14", features = ["release_max_level_off"] }
 fxhash = "0.2.1"
 fxhash = "0.2.1"
 wasm-logger = "0.2.0"
 wasm-logger = "0.2.0"
 console_error_panic_hook = "0.1.6"
 console_error_panic_hook = "0.1.6"
@@ -24,6 +31,11 @@ async-channel = "1.6.1"
 anyhow = "1.0"
 anyhow = "1.0"
 gloo-timers = { version = "0.2.1", features = ["futures"] }
 gloo-timers = { version = "0.2.1", features = ["futures"] }
 futures-util = "0.3.15"
 futures-util = "0.3.15"
+smallstr = "0.2.0"
+
+[patch.crates-io]
+wasm-bindgen = { path = "../../../Tinkering/wasm-bindgen/" }
+
 
 
 [dependencies.web-sys]
 [dependencies.web-sys]
 version = "0.3.51"
 version = "0.3.51"
@@ -65,6 +77,7 @@ features = [
     "IdleDeadline",
     "IdleDeadline",
 ]
 ]
 
 
+
 [lib]
 [lib]
 crate-type = ["cdylib", "rlib"]
 crate-type = ["cdylib", "rlib"]
 
 
@@ -76,7 +89,12 @@ serde = { version = "1.0.126", features = ["derive"] }
 reqwest = { version = "0.11", features = ["json"] }
 reqwest = { version = "0.11", features = ["json"] }
 dioxus-hooks = { path = "../hooks" }
 dioxus-hooks = { path = "../hooks" }
 dioxus-core-macro = { path = "../core-macro" }
 dioxus-core-macro = { path = "../core-macro" }
-# rand = { version="0.8.4", features=["small_rng"] }
+rand = { version = "0.8.4", features = ["small_rng"] }
+
+[dev-dependencies.getrandom]
+version = "0.2"
+features = ["js"]
+
 # surf = { version = "2.3.1", default-features = false, features = [
 # surf = { version = "2.3.1", default-features = false, features = [
 #     "wasm-client",
 #     "wasm-client",
 # ] }
 # ] }

+ 243 - 0
packages/web/examples/js_bench.rs

@@ -0,0 +1,243 @@
+use std::cell::Cell;
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_hooks::{use_ref, use_state};
+use dioxus_html as dioxus_elements;
+use dioxus_web;
+use gloo_timers::future::TimeoutFuture;
+use rand::prelude::*;
+
+fn main() {
+    console_error_panic_hook::set_once();
+    if cfg!(debug_assertions) {
+        wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+        log::debug!("hello world");
+    }
+
+    for a in ADJECTIVES {
+        wasm_bindgen::intern(*a);
+    }
+    for a in COLOURS {
+        wasm_bindgen::intern(*a);
+    }
+    for a in NOUNS {
+        wasm_bindgen::intern(*a);
+    }
+    for a in [
+        "container",
+        "jumbotron",
+        "row",
+        "Dioxus",
+        "col-md-6",
+        "col-md-1",
+        "Create 1,000 rows",
+        "run",
+        "Create 10,000 rows",
+        "runlots",
+        "Append 1,000 rows",
+        "add",
+        "Update every 10th row",
+        "update",
+        "Clear",
+        "clear",
+        "Swap rows",
+        "swaprows",
+        "preloadicon glyphicon glyphicon-remove", //
+        "aria-hidden",
+        "onclick",
+        "true",
+        "false",
+        "danger",
+        "type",
+        "id",
+        "class",
+        "glyphicon glyphicon-remove remove",
+        "dioxus-id",
+        "dioxus-event-click",
+        "dioxus",
+        "click",
+        "1.10",
+        "lbl",
+        "remove",
+        "dioxus-event",
+        "col-sm-6 smallpad",
+        "btn btn-primary btn-block",
+        "",
+        " ",
+    ] {
+        wasm_bindgen::intern(a);
+    }
+    for x in 0..100_000 {
+        wasm_bindgen::intern(&x.to_string());
+    }
+
+    dioxus_web::launch(App, |c| c.rootname("main"));
+}
+
+#[derive(Clone, PartialEq, Copy)]
+struct Label {
+    key: usize,
+    labels: [&'static str; 3],
+}
+
+static mut Counter: Cell<usize> = Cell::new(1);
+
+impl Label {
+    fn new_list(num: usize) -> Vec<Self> {
+        let mut rng = SmallRng::from_entropy();
+        let mut labels = Vec::with_capacity(num);
+
+        let offset = unsafe { Counter.get() };
+        unsafe { Counter.set(offset + num) };
+
+        for k in offset..(offset + num) {
+            labels.push(Label {
+                key: k,
+                labels: [
+                    ADJECTIVES.choose(&mut rng).unwrap(),
+                    COLOURS.choose(&mut rng).unwrap(),
+                    NOUNS.choose(&mut rng).unwrap(),
+                ],
+            });
+        }
+
+        labels
+    }
+}
+
+static App: FC<()> = |cx, _props| {
+    let mut items = use_ref(cx, || vec![]);
+    let mut selected = use_state(cx, || None);
+
+    cx.render(rsx! {
+        div { class: "container"
+            div { class: "jumbotron"
+                div { class: "row"
+                    div { class: "col-md-6", h1 { "Dioxus" } }
+                    div { class: "col-md-6"
+                        div { class: "row"
+                            ActionButton { name: "Create 1,000 rows", id: "run",
+                                onclick: move || items.set(Label::new_list(1_000)),
+                            }
+                            ActionButton { name: "Create 10,000 rows", id: "runlots",
+                                onclick: move || items.set(Label::new_list(10_000)),
+                            }
+                            ActionButton { name: "Append 1,000 rows", id: "add",
+                                onclick: move || items.write().extend(Label::new_list(1_000)),
+                            }
+                            ActionButton { name: "Update every 10th row", id: "update",
+                                onclick: move || items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"),
+                            }
+                            ActionButton { name: "Clear", id: "clear",
+                                onclick: move || items.write().clear(),
+                            }
+                            ActionButton { name: "Swap Rows", id: "swaprows",
+                                onclick: move || items.write().swap(0, 998),
+                            }
+                        }
+                    }
+                }
+            }
+            table { class: "table table-hover table-striped test-data"
+                tbody { id: "tbody"
+                    {items.read().iter().enumerate().map(|(id, item)| {
+                        let [adj, col, noun] = item.labels;
+                        let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
+                        rsx!(tr { 
+                            class: "{is_in_danger}",
+                            key: "{id}",
+                            td { class:"col-md-1" }
+                            td { class:"col-md-1", "{item.key}" }
+                            td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
+                                a { class: "lbl", "{adj} {col} {noun}" }
+                            }
+                            td { class: "col-md-1"
+                                a { class: "remove", onclick: move |_| { items.write().remove(id); },
+                                    span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+                                }
+                            }
+                            td { class: "col-md-6" }
+                        })
+                    })}
+                }
+             }
+            span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" }
+        }
+    })
+};
+
+#[derive(Props)]
+struct ActionButtonProps<'a> {
+    name: &'static str,
+    id: &'static str,
+    onclick: &'a dyn Fn(),
+}
+
+fn ActionButton(cx: Context, props: &ActionButtonProps) -> Element {
+    rsx!(cx, div { class: "col-sm-6 smallpad"
+        button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}",  onclick: move |_| (props.onclick)(),
+            "{props.name}"
+        }
+    })
+}
+
+static ADJECTIVES: &[&str] = &[
+    "pretty",
+    "large",
+    "big",
+    "small",
+    "tall",
+    "short",
+    "long",
+    "handsome",
+    "plain",
+    "quaint",
+    "clean",
+    "elegant",
+    "easy",
+    "angry",
+    "crazy",
+    "helpful",
+    "mushy",
+    "odd",
+    "unsightly",
+    "adorable",
+    "important",
+    "inexpensive",
+    "cheap",
+    "expensive",
+    "fancy",
+];
+
+static COLOURS: &[&str] = &[
+    "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
+    "orange",
+];
+
+static NOUNS: &[&str] = &[
+    "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
+    "pizza", "mouse", "keyboard",
+];
+
+// #[derive(PartialEq, Props)]
+// struct RowProps<'a> {
+//     row_id: usize,
+//     label: &'a Label,
+// }
+
+// fn Row(cx: Context, props: &RowProps) -> Element {
+//     rsx!(cx, tr {
+//         td { class:"col-md-1", "{props.row_id}" }
+//         td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+//             a { class: "lbl", {props.label.labels} }
+//         }
+//         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" }
+//     })
+// }

+ 46 - 0
packages/web/examples/simple.rs

@@ -0,0 +1,46 @@
+//! Example: README.md showcase
+//!
+//! The example from the README.md.
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_hooks::use_state;
+use dioxus_html as dioxus_elements;
+use dioxus_web;
+use gloo_timers::future::TimeoutFuture;
+
+fn main() {
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    dioxus_web::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx, props| {
+    let show = use_state(cx, || true);
+
+    let inner = match *show {
+        true => {
+            rsx!( div {
+                "hello world"
+            })
+        }
+        false => {
+            rsx!( div {
+                // h1 {
+                    "bello world"
+                // }
+            })
+        }
+    };
+
+    rsx!(cx, div {
+        button {
+            "toggle"
+            onclick: move |_| {
+                let cur = *show;
+                show.set(!cur);
+            }
+        }
+        {inner}
+    })
+};

+ 5 - 0
packages/web/src/cache.rs

@@ -7,6 +7,11 @@
 /// Eventually we might want to procedurally generate these strings for common words, phrases, and values.
 /// Eventually we might want to procedurally generate these strings for common words, phrases, and values.
 pub(crate) fn intern_cached_strings() {
 pub(crate) fn intern_cached_strings() {
     let cached_words = [
     let cached_words = [
+        // Important tags to dioxus
+        "dioxus-id",
+        "dioxus",
+        "dioxus-event-click", // todo: more events
+        "click",
         // All the HTML Tags
         // All the HTML Tags
         "a",
         "a",
         "abbr",
         "abbr",

+ 1 - 1
packages/web/src/cfg.rs

@@ -17,7 +17,7 @@ impl Default for WebConfig {
     fn default() -> Self {
     fn default() -> Self {
         Self {
         Self {
             hydrate: false,
             hydrate: false,
-            rootname: "dioxusroot".to_string(),
+            rootname: "main".to_string(),
         }
         }
     }
     }
 }
 }

+ 45 - 23
packages/web/src/dom.rs

@@ -213,7 +213,7 @@ impl WebsysDom {
 
 
     fn create_placeholder(&mut self, id: u64) {
     fn create_placeholder(&mut self, id: u64) {
         self.create_element("pre", None, id);
         self.create_element("pre", None, id);
-        // self.set_attribute("hidden", "", None);
+        self.set_attribute("hidden", "", None, id);
     }
     }
 
 
     fn create_text_node(&mut self, text: &str, id: u64) {
     fn create_text_node(&mut self, text: &str, id: u64) {
@@ -246,8 +246,15 @@ impl WebsysDom {
                 .unwrap(),
                 .unwrap(),
         };
         };
 
 
+        use smallstr;
+        use smallstr::SmallString;
+        use std::fmt::Write;
+
+        let mut s: SmallString<[u8; 8]> = smallstr::SmallString::new();
+        write!(s, "{}", id).unwrap();
+
         let el2 = el.dyn_ref::<Element>().unwrap();
         let el2 = el.dyn_ref::<Element>().unwrap();
-        el2.set_attribute("dioxus-id", &format!("{}", id)).unwrap();
+        el2.set_attribute("dioxus-id", s.as_str()).unwrap();
 
 
         self.stack.push(el.clone());
         self.stack.push(el.clone());
         self.nodes[(id as usize)] = Some(el);
         self.nodes[(id as usize)] = Some(el);
@@ -263,18 +270,21 @@ impl WebsysDom {
 
 
         let el = self.stack.top();
         let el = self.stack.top();
 
 
-        let el = el
-            .dyn_ref::<Element>()
-            .expect(&format!("not an element: {:?}", el));
+        let el = el.dyn_ref::<Element>().unwrap();
+        // let el = el.dyn_ref::<Element>().unwrap();
+        // .expect(&format!("not an element: {:?}", el));
 
 
         // let scope_id = scope.data().as_ffi();
         // let scope_id = scope.data().as_ffi();
-        let scope_id = scope.0 as u64;
+        // let scope_id = scope.0 as u64;
+        // "dioxus-event-click",
+        // "1.10"
+        // &format!("", scope_id, real_id),
+        // &format!("dioxus-event-{}", event),
+        // &format!("{}.{}", scope_id, real_id),
+        // &format!("dioxus-event-{}", event),
+        // &format!("{}.{}", scope_id, real_id),
 
 
-        el.set_attribute(
-            &format!("dioxus-event-{}", event),
-            &format!("{}.{}", scope_id, real_id),
-        )
-        .unwrap();
+        el.set_attribute("dioxus-event", event).unwrap();
 
 
         // el.set_attribute(&format!("dioxus-event"), &format!("{}", event))
         // el.set_attribute(&format!("dioxus-event"), &format!("{}", event))
         //     .unwrap();
         //     .unwrap();
@@ -488,6 +498,8 @@ unsafe impl Sync for DioxusWebsysEvent {}
 fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send + Sync> {
 fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send + Sync> {
     use dioxus_html::on::*;
     use dioxus_html::on::*;
     use dioxus_html::KeyCode;
     use dioxus_html::KeyCode;
+    // event.prevent_default();
+
     // use dioxus_core::events::on::*;
     // use dioxus_core::events::on::*;
     match event.type_().as_str() {
     match event.type_().as_str() {
         "copy" | "cut" | "paste" => Arc::new(ClipboardEvent {}),
         "copy" | "cut" | "paste" => Arc::new(ClipboardEvent {}),
@@ -682,30 +694,40 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
 
 
     use anyhow::Context;
     use anyhow::Context;
 
 
+    let element_id = target
+        .get_attribute("dioxus-id")
+        .context("Could not find element id on event target")?
+        .parse()?;
+
     // The error handling here is not very descriptive and needs to be replaced with a zero-cost error system
     // The error handling here is not very descriptive and needs to be replaced with a zero-cost error system
     let val: String = target
     let val: String = target
-        .get_attribute(&format!("dioxus-event-{}", typ))
+        .get_attribute("dioxus-event")
         .context(format!("wrong format - received {:#?}", typ))?;
         .context(format!("wrong format - received {:#?}", typ))?;
+    // .get_attribute(&format!("dioxus-event-{}", typ))
+    // .context(format!("wrong format - received {:#?}", typ))?;
 
 
     let mut fields = val.splitn(3, ".");
     let mut fields = val.splitn(3, ".");
 
 
-    let gi_id = fields
-        .next()
-        .and_then(|f| f.parse::<u64>().ok())
-        .context("failed to parse gi id")?;
+    // let gi_id = fields
+    //     .next()
+    //     .and_then(|f| f.parse::<u64>().ok())
+    //     .context("failed to parse gi id")?;
 
 
-    let real_id = fields
-        .next()
-        .and_then(|raw_id| raw_id.parse::<u64>().ok())
-        .context("failed to parse real id")?;
+    // let real_id = fields
+    //     .next()
+    //     .and_then(|raw_id| raw_id.parse::<u64>().ok())
+    //     .context("failed to parse real id")?;
 
 
-    let triggered_scope = gi_id;
+    // let triggered_scope = gi_id;
 
 
     Ok(UserEvent {
     Ok(UserEvent {
         name: event_name_from_typ(&typ),
         name: event_name_from_typ(&typ),
         data: virtual_event_from_websys_event(event.clone()),
         data: virtual_event_from_websys_event(event.clone()),
-        element: Some(ElementId(real_id as usize)),
-        scope_id: Some(ScopeId(triggered_scope as usize)),
+        element: Some(ElementId(element_id)),
+        scope_id: None,
+        // scope_id: Some(ScopeId(triggered_scope as usize)),
+        // element: Some(ElementId(real_id as usize)),
+        // scope_id: Some(ScopeId(triggered_scope as usize)),
         priority: dioxus_core::EventPriority::Medium,
         priority: dioxus_core::EventPriority::Medium,
     })
     })
 }
 }

+ 9 - 6
packages/web/src/lib.rs

@@ -85,7 +85,7 @@ mod ric_raf;
 ///     dioxus_web::launch(App, |c| c);
 ///     dioxus_web::launch(App, |c| c);
 /// }
 /// }
 ///
 ///
-/// static App: FC<()> = |(cx, props)| {
+/// static App: FC<()> = |cx, props| {
 ///     rsx!(cx, div {"hello world"})
 ///     rsx!(cx, div {"hello world"})
 /// }
 /// }
 /// ```
 /// ```
@@ -109,7 +109,7 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W
 ///     name: String
 ///     name: String
 /// }
 /// }
 ///
 ///
-/// static App: FC<RootProps> = |(cx, props)| {
+/// static App: FC<RootProps> = |cx, props| {
 ///     rsx!(cx, div {"hello {props.name}"})
 ///     rsx!(cx, div {"hello {props.name}"})
 /// }
 /// }
 /// ```
 /// ```
@@ -155,7 +155,7 @@ pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg:
     // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
     // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
     // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
     // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
     if !should_hydrate {
     if !should_hydrate {
-        log::info!("Applying rebuild edits..., {:?}", mutations);
+        // log::info!("Applying rebuild edits..., {:?}", mutations);
         websys_dom.process_edits(&mut mutations.edits);
         websys_dom.process_edits(&mut mutations.edits);
     }
     }
 
 
@@ -166,17 +166,20 @@ pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg:
         // if there is work then this future resolves immediately.
         // if there is work then this future resolves immediately.
         dom.wait_for_work().await;
         dom.wait_for_work().await;
 
 
-        // wait for the mainthread to schedule us in
-        let mut deadline = work_loop.wait_for_idle_time().await;
+        // // wait for the mainthread to schedule us in
+        // let mut deadline = work_loop.wait_for_idle_time().await;
 
 
         // run the virtualdom work phase until the frame deadline is reached
         // run the virtualdom work phase until the frame deadline is reached
-        let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
+        let mutations = dom.work_with_deadline(|| false);
+        // // run the virtualdom work phase until the frame deadline is reached
+        // let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
 
 
         // wait for the animation frame to fire so we can apply our changes
         // wait for the animation frame to fire so we can apply our changes
         work_loop.wait_for_raf().await;
         work_loop.wait_for_raf().await;
 
 
         for mut edit in mutations {
         for mut edit in mutations {
             // actually apply our changes during the animation frame
             // actually apply our changes during the animation frame
+            // log::info!("Applying change edits..., {:?}", edit);
             websys_dom.process_edits(&mut edit.edits);
             websys_dom.process_edits(&mut edit.edits);
         }
         }
     }
     }

+ 7 - 2
src/lib.rs

@@ -93,7 +93,7 @@
 //! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component.
 //! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component.
 //!
 //!
 //! ```
 //! ```
-//! pub pub static Example: FC<()> = |(cx, props)|{
+//! pub pub static Example: FC<()> = |cx, props|{
 //!     let (val, set_val) = use_state(cx, || 0);
 //!     let (val, set_val) = use_state(cx, || 0);
 //!     cx.render(rsx!(
 //!     cx.render(rsx!(
 //!         button { onclick: move |_| set_val(val + 1) }
 //!         button { onclick: move |_| set_val(val + 1) }
@@ -156,7 +156,7 @@
 //!     dioxus::web::launch(Example);
 //!     dioxus::web::launch(Example);
 //! }
 //! }
 //!
 //!
-//! pub pub static Example: FC<()> = |(cx, props)|{
+//! pub pub static Example: FC<()> = |cx, props|{
 //!     cx.render(rsx! {
 //!     cx.render(rsx! {
 //!         div { "Hello World!" }
 //!         div { "Hello World!" }
 //!     })
 //!     })
@@ -190,6 +190,11 @@ pub use dioxus_router as router;
 
 
 pub mod debug {}
 pub mod debug {}
 
 
+pub mod events {
+    #[cfg(feature = "html")]
+    pub use dioxus_html::{on::*, KeyCode};
+}
+
 pub mod prelude {
 pub mod prelude {
     //! A glob import that includes helper types like FC, rsx!, html!, and required traits
     //! A glob import that includes helper types like FC, rsx!, html!, and required traits
     pub use dioxus_core::prelude::*;
     pub use dioxus_core::prelude::*;