浏览代码

Merge branch 'master' into jk/documet-everything

Jonathan Kelley 3 年之前
父节点
当前提交
77631bff1f

+ 11 - 0
.github/workflows/macos.yml

@@ -1,6 +1,17 @@
 name: macOS tests
 name: macOS tests
 
 
 on:
 on:
+  push:
+    branches:
+      - master
+    paths:
+      - packages/**
+      - examples/**
+      - src/**
+      - .github/**
+      - lib.rs
+      - Cargo.toml
+
   pull_request:
   pull_request:
     types: [opened, synchronize, reopened, ready_for_review]
     types: [opened, synchronize, reopened, ready_for_review]
     branches:
     branches:

+ 11 - 0
.github/workflows/main.yml

@@ -1,6 +1,17 @@
 name: Rust CI
 name: Rust CI
 
 
 on:
 on:
+  push:
+    branches:
+      - master
+    paths:
+      - packages/**
+      - examples/**
+      - src/**
+      - .github/**
+      - lib.rs
+      - Cargo.toml
+
   pull_request:
   pull_request:
     types: [opened, synchronize, reopened, ready_for_review]
     types: [opened, synchronize, reopened, ready_for_review]
     branches:
     branches:

+ 11 - 0
.github/workflows/windows.yml

@@ -1,6 +1,17 @@
 name: windows
 name: windows
 
 
 on:
 on:
+  push:
+    branches:
+      - master
+    paths:
+      - packages/**
+      - examples/**
+      - src/**
+      - .github/**
+      - lib.rs
+      - Cargo.toml
+
   pull_request:
   pull_request:
     types: [opened, synchronize, reopened, ready_for_review]
     types: [opened, synchronize, reopened, ready_for_review]
     branches:
     branches:

+ 4 - 2
Cargo.toml

@@ -16,8 +16,8 @@ dioxus-core = { path = "./packages/core", version = "^0.1.9" }
 dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
 dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
 dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
 dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
 dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
 dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
-dioxus-rsx = { path = "./packages/rsx", optional = true }
 fermi = { path = "./packages/fermi", version = "^0.1.0", optional = true }
 fermi = { path = "./packages/fermi", version = "^0.1.0", optional = true }
+# dioxus-rsx = { path = "./packages/rsx", optional = true }
 
 
 dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
 dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
 dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
 dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
@@ -31,12 +31,14 @@ dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.0.0", o
 [features]
 [features]
 default = ["macro", "hooks", "html"]
 default = ["macro", "hooks", "html"]
 
 
-macro = ["dioxus-core-macro", "dioxus-rsx"]
+macro = ["dioxus-core-macro"]
+# macro = ["dioxus-core-macro", "dioxus-rsx"]
 hooks = ["dioxus-hooks"]
 hooks = ["dioxus-hooks"]
 html = ["dioxus-html"]
 html = ["dioxus-html"]
 ssr = ["dioxus-ssr"]
 ssr = ["dioxus-ssr"]
 web = ["dioxus-web"]
 web = ["dioxus-web"]
 desktop = ["dioxus-desktop"]
 desktop = ["dioxus-desktop"]
+ayatana = ["dioxus-desktop/ayatana"]
 router = ["dioxus-router"]
 router = ["dioxus-router"]
 
 
 [workspace]
 [workspace]

+ 2 - 5
README.md

@@ -1,8 +1,5 @@
 <div align="center">
 <div align="center">
   <h1>Dioxus</h1>
   <h1>Dioxus</h1>
-  <p>
-    <strong>Frontend that scales.</strong>
-  </p>
 </div>
 </div>
 
 
 <div align="center">
 <div align="center">
@@ -160,9 +157,9 @@ You shouldn't use Dioxus if:
 ## Comparison with other Rust UI frameworks
 ## Comparison with other Rust UI frameworks
 Dioxus primarily emphasizes **developer experience** and **familiarity with React principles**.
 Dioxus primarily emphasizes **developer experience** and **familiarity with React principles**.
 
 
-- [Yew](https://github.com/yewstack/yew): prefers the elm pattern instead of React-hooks, no borrowed props, supports SSR (no hydration).
+- [Yew](https://github.com/yewstack/yew): prefers the elm pattern instead, no borrowed props, supports SSR (no hydration), no direct desktop/mobile support.
 - [Percy](https://github.com/chinedufn/percy): Supports SSR but with less emphasis on state management and event handling.
 - [Percy](https://github.com/chinedufn/percy): Supports SSR but with less emphasis on state management and event handling.
-- [Sycamore](https://github.com/sycamore-rs/sycamore): VDOM-less using fine-grained reactivity, but lacking in ergonomics.
+- [Sycamore](https://github.com/sycamore-rs/sycamore): VDOM-less using fine-grained reactivity, but no direct support for desktop/mobile.
 - [Dominator](https://github.com/Pauan/rust-dominator): Signal-based zero-cost alternative, less emphasis on community and docs.
 - [Dominator](https://github.com/Pauan/rust-dominator): Signal-based zero-cost alternative, less emphasis on community and docs.
 - [Azul](https://azul.rs): Fully native HTML/CSS renderer for desktop applications, no support for web/ssr
 - [Azul](https://azul.rs): Fully native HTML/CSS renderer for desktop applications, no support for web/ssr
 
 

+ 12 - 7
docs/guide/src/hello_world.md

@@ -63,7 +63,7 @@ $ cat Cargo.toml
 [package]
 [package]
 name = "hello-dioxus"
 name = "hello-dioxus"
 version = "0.1.0"
 version = "0.1.0"
-edition = "2018"
+edition = "2021"
 
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 
@@ -81,6 +81,13 @@ $ cargo add dioxus --features desktop
 
 
 It's very important to add `dioxus` with the `desktop` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `desktop` feature ensures the we only depend on the smallest set of required crates to compile and render.
 It's very important to add `dioxus` with the `desktop` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `desktop` feature ensures the we only depend on the smallest set of required crates to compile and render.
 
 
+If you system does not provide the `libappindicator3` library, like Debian/bullseye, you can enable the replacement `ayatana` with an additional flag:
+
+```shell
+$ # On Debian/bullseye use:
+$ cargo add dioxus --features desktop --features ayatana
+```
+
 If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in.
 If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in.
 
 
 ### Our first app
 ### Our first app
@@ -92,10 +99,10 @@ use dioxus::prelude::*;
 
 
 
 
 fn main() {
 fn main() {
-    dioxus::desktop::launch(App);
+    dioxus::desktop::launch(app);
 }
 }
 
 
-fn App(cx: Scope) -> Element {
+fn app(cx: Scope) -> Element {
     cx.render(rsx! (
     cx.render(rsx! (
         div { "Hello, world!" }
         div { "Hello, world!" }
     ))
     ))
@@ -118,14 +125,14 @@ This initialization code launches a Tokio runtime on a helper thread where your
 
 
 ```rust
 ```rust
 fn main() {
 fn main() {
-    dioxus::desktop::launch(App);
+    dioxus::desktop::launch(app);
 }
 }
 ```
 ```
 
 
 Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`.
 Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`.
 
 
 ```rust
 ```rust
-fn App(cx: Scope) -> Element {
+fn app(cx: Scope) -> Element {
     cx.render(rsx! {
     cx.render(rsx! {
         div { "Hello, world!" }
         div { "Hello, world!" }
     })
     })
@@ -143,5 +150,3 @@ For now, just know that `Scope` lets you store state with hooks and render eleme
 ## Moving on
 ## Moving on
 
 
 Congrats! You've built your first desktop application with Dioxus. Next, we're going to learn about the basics of building interactive user interfaces.
 Congrats! You've built your first desktop application with Dioxus. Next, we're going to learn about the basics of building interactive user interfaces.
-
-

+ 6 - 0
docs/guide/src/setup.md

@@ -52,6 +52,12 @@ Webview Linux apps require WebkitGtk. When distributing, this can be part of you
 sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev
 sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libappindicator3-dev
 ```
 ```
 
 
+When using Debian/bullseye `libappindicator3-dev` is no longer available but replaced by `libayatana-appindicator3-dev`.
+
+```
+# on Debian/bullseye use:
+sudo apt install libwebkit2gtk-4.0-dev libgtk-3-dev libayatanta-appindicator3-dev
+```
 
 
 If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/en/docs/get-started/setup-linux).
 If you run into issues, make sure you have all the basics installed, as outlined in the [Tauri docs](https://tauri.studio/en/docs/get-started/setup-linux).
 
 

+ 28 - 0
examples/heavy_compute.rs

@@ -0,0 +1,28 @@
+//! This example shows that you can place heavy work on the main thread, and then
+//!
+//! You *should* be using `tokio::spawn_blocking` instead.
+//!
+//! Your app runs in an async runtime (Tokio), so you should avoid blocking
+//! the rendering of the VirtualDom.
+//!
+//!
+
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    // This is discouraged
+    std::thread::sleep(std::time::Duration::from_millis(2_000));
+
+    // This is suggested
+    tokio::task::spawn_blocking(move || {
+        std::thread::sleep(std::time::Duration::from_millis(2_000));
+    });
+
+    cx.render(rsx! {
+        div { "Hello, world!" }
+    })
+}

+ 7 - 5
examples/login_form.rs

@@ -12,7 +12,7 @@ fn app(cx: Scope) -> Element {
     let onsubmit = move |evt: FormEvent| {
     let onsubmit = move |evt: FormEvent| {
         cx.spawn(async move {
         cx.spawn(async move {
             let resp = reqwest::Client::new()
             let resp = reqwest::Client::new()
-                .post("http://localhost/login")
+                .post("http://localhost:8080/login")
                 .form(&[
                 .form(&[
                     ("username", &evt.values["username"]),
                     ("username", &evt.values["username"]),
                     ("password", &evt.values["password"]),
                     ("password", &evt.values["password"]),
@@ -22,10 +22,12 @@ fn app(cx: Scope) -> Element {
 
 
             match resp {
             match resp {
                 // Parse data from here, such as storing a response token
                 // Parse data from here, such as storing a response token
-                Ok(_data) => println!("Login successful"),
+                Ok(_data) => println!("Login successful!"),
 
 
                 //Handle any errors from the fetch here
                 //Handle any errors from the fetch here
-                Err(_err) => println!("Login failed"),
+                Err(_err) => {
+                    println!("Login failed - you need a login server running on localhost:8080.")
+                }
             }
             }
         });
         });
     };
     };
@@ -36,10 +38,10 @@ fn app(cx: Scope) -> Element {
             onsubmit: onsubmit,
             onsubmit: onsubmit,
             prevent_default: "onsubmit", // Prevent the default behavior of <form> to post
             prevent_default: "onsubmit", // Prevent the default behavior of <form> to post
 
 
-            input { "type": "text" }
+            input { "type": "text", id: "username", name: "username" }
             label { "Username" }
             label { "Username" }
             br {}
             br {}
-            input { "type": "password" }
+            input { "type": "password", id: "password", name: "password" }
             label { "Password" }
             label { "Password" }
             br {}
             br {}
             button { "Login" }
             button { "Login" }

+ 23 - 0
examples/textarea.rs

@@ -0,0 +1,23 @@
+// How to use textareas
+
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let (model, set_model) = use_state(&cx, || String::from("asd"));
+
+    println!("{}", model);
+
+    cx.render(rsx! {
+        textarea {
+            class: "border",
+            rows: "10",
+            cols: "80",
+            value: "{model}",
+            oninput: move |e| set_model(e.value.clone()),
+        }
+    })
+}

+ 1 - 1
examples/todomvc.rs

@@ -129,7 +129,6 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
             label {
             label {
                 r#for: "cbg-{todo.id}",
                 r#for: "cbg-{todo.id}",
                 onclick: move |_| set_is_editing(true),
                 onclick: move |_| set_is_editing(true),
-                onfocusout: move |_| set_is_editing(false),
                 "{todo.contents}"
                 "{todo.contents}"
             }
             }
         }
         }
@@ -139,6 +138,7 @@ pub fn todo_entry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
                 value: "{todo.contents}",
                 value: "{todo.contents}",
                 oninput: move |evt| cx.props.set_todos.make_mut()[&cx.props.id].contents = evt.value.clone(),
                 oninput: move |evt| cx.props.set_todos.make_mut()[&cx.props.id].contents = evt.value.clone(),
                 autofocus: "true",
                 autofocus: "true",
+                onfocusout: move |_| set_is_editing(false),
                 onkeydown: move |evt| {
                 onkeydown: move |evt| {
                     match evt.key.as_str() {
                     match evt.key.as_str() {
                         "Enter" | "Escape" | "Tab" => set_is_editing(false),
                         "Enter" | "Escape" | "Tab" => set_is_editing(false),

+ 2 - 2
packages/core-macro/Cargo.toml

@@ -15,8 +15,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 proc-macro = true
 proc-macro = true
 
 
 [dependencies]
 [dependencies]
-dioxus-rsx = { path = "../rsx" }
-proc-macro-error = "1.0.4"
+# dioxus-rsx = { path = "../rsx" }
+proc-macro-error = "1"
 proc-macro2 = { version = "1.0.6" }
 proc-macro2 = { version = "1.0.6" }
 quote = "1.0"
 quote = "1.0"
 syn = { version = "1.0.11", features = ["full", "extra-traits"] }
 syn = { version = "1.0.11", features = ["full", "extra-traits"] }

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

@@ -5,6 +5,7 @@ use syn::parse_macro_input;
 mod ifmt;
 mod ifmt;
 mod inlineprops;
 mod inlineprops;
 mod props;
 mod props;
+mod rsx;
 
 
 #[proc_macro]
 #[proc_macro]
 pub fn format_args_f(input: TokenStream) -> TokenStream {
 pub fn format_args_f(input: TokenStream) -> TokenStream {
@@ -178,7 +179,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 #[proc_macro_error::proc_macro_error]
 #[proc_macro_error::proc_macro_error]
 #[proc_macro]
 #[proc_macro]
 pub fn rsx(s: TokenStream) -> TokenStream {
 pub fn rsx(s: TokenStream) -> TokenStream {
-    match syn::parse::<dioxus_rsx::CallBody>(s) {
+    match syn::parse::<rsx::CallBody>(s) {
         Err(err) => err.to_compile_error().into(),
         Err(err) => err.to_compile_error().into(),
         Ok(stream) => stream.to_token_stream().into(),
         Ok(stream) => stream.to_token_stream().into(),
     }
     }

+ 234 - 0
packages/core-macro/src/rsx/component.rs

@@ -0,0 +1,234 @@
+//! Parse components into the VComponent VNode
+//! ==========================================
+//!
+//! This parsing path emerges from [`AmbiguousElement`] which supports validation of the vcomponent format.
+//! We can be reasonably sure that whatever enters this parsing path is in the right format.
+//! This feature must support
+//! - [x] Namespaced components
+//! - [x] Fields
+//! - [x] Componentbuilder synax
+//! - [x] Optional commas
+//! - [ ] Children
+//! - [ ] Keys
+//! - [ ] Properties spreading with with `..` syntax
+
+use super::*;
+
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{
+    ext::IdentExt,
+    parse::{Parse, ParseBuffer, ParseStream},
+    token, Expr, Ident, LitStr, Result, Token,
+};
+
+pub struct Component {
+    pub name: syn::Path,
+    pub body: Vec<ComponentField>,
+    pub children: Vec<BodyNode>,
+    pub manual_props: Option<Expr>,
+}
+
+impl Parse for Component {
+    fn parse(stream: ParseStream) -> Result<Self> {
+        let name = syn::Path::parse_mod_style(stream)?;
+
+        let content: ParseBuffer;
+
+        // if we see a `{` then we have a block
+        // else parse as a function-like call
+        if stream.peek(token::Brace) {
+            syn::braced!(content in stream);
+        } else {
+            syn::parenthesized!(content in stream);
+        }
+
+        let mut body = Vec::new();
+        let mut children = Vec::new();
+        let mut manual_props = None;
+
+        while !content.is_empty() {
+            // if we splat into a component then we're merging properties
+            if content.peek(Token![..]) {
+                content.parse::<Token![..]>()?;
+                manual_props = Some(content.parse::<Expr>()?);
+            } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+                body.push(content.parse::<ComponentField>()?);
+            } else {
+                children.push(content.parse::<BodyNode>()?);
+            }
+
+            if content.peek(Token![,]) {
+                let _ = content.parse::<Token![,]>();
+            }
+        }
+
+        Ok(Self {
+            name,
+            body,
+            children,
+            manual_props,
+        })
+    }
+}
+
+impl ToTokens for Component {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        let name = &self.name;
+
+        let mut has_key = None;
+
+        let builder = match &self.manual_props {
+            Some(manual_props) => {
+                let mut toks = quote! {
+                    let mut __manual_props = #manual_props;
+                };
+                for field in &self.body {
+                    if field.name == "key" {
+                        has_key = Some(field);
+                    } else {
+                        let name = &field.name;
+                        let val = &field.content;
+                        toks.append_all(quote! {
+                            __manual_props.#name = #val;
+                        });
+                    }
+                }
+                toks.append_all(quote! {
+                    __manual_props
+                });
+                quote! {{
+                    #toks
+                }}
+            }
+            None => {
+                let mut toks = quote! { fc_to_builder(#name) };
+                for field in &self.body {
+                    match field.name.to_string().as_str() {
+                        "key" => {
+                            //
+                            has_key = Some(field);
+                        }
+                        _ => toks.append_all(quote! {#field}),
+                    }
+                }
+
+                if !self.children.is_empty() {
+                    let childs = &self.children;
+                    toks.append_all(quote! {
+                        .children(__cx.create_children([ #( #childs ),* ]))
+                    });
+                }
+
+                toks.append_all(quote! {
+                    .build()
+                });
+                toks
+            }
+        };
+
+        let key_token = match has_key {
+            Some(field) => {
+                let inners = &field.content;
+                quote! { Some(format_args_f!(#inners)) }
+            }
+            None => quote! { None },
+        };
+
+        let fn_name = self.name.segments.last().unwrap().ident.to_string();
+
+        tokens.append_all(quote! {
+            __cx.component(
+                #name,
+                #builder,
+                #key_token,
+                #fn_name
+            )
+        })
+    }
+}
+
+// the struct's fields info
+pub struct ComponentField {
+    name: Ident,
+    content: ContentField,
+}
+
+enum ContentField {
+    ManExpr(Expr),
+    Formatted(LitStr),
+    OnHandlerRaw(Expr),
+}
+
+impl ToTokens for ContentField {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        match self {
+            ContentField::ManExpr(e) => e.to_tokens(tokens),
+            ContentField::Formatted(s) => tokens.append_all(quote! {
+                __cx.raw_text(format_args_f!(#s)).0
+            }),
+            ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
+                __cx.event_handler(#e)
+            }),
+        }
+    }
+}
+
+impl Parse for ComponentField {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let name = Ident::parse_any(input)?;
+        input.parse::<Token![:]>()?;
+
+        if name.to_string().starts_with("on") {
+            let content = ContentField::OnHandlerRaw(input.parse()?);
+            return Ok(Self { name, content });
+        }
+
+        if name == "key" {
+            let content = ContentField::ManExpr(input.parse()?);
+            return Ok(Self { name, content });
+        }
+
+        if input.peek(LitStr) && input.peek2(Token![,]) {
+            let t: LitStr = input.fork().parse()?;
+
+            if is_literal_foramtted(&t) {
+                let content = ContentField::Formatted(input.parse()?);
+                return Ok(Self { name, content });
+            }
+        }
+
+        if input.peek(LitStr) && input.peek2(LitStr) {
+            let item = input.parse::<LitStr>().unwrap();
+            proc_macro_error::emit_error!(item, "This attribute is missing a trailing comma")
+        }
+
+        let content = ContentField::ManExpr(input.parse()?);
+        Ok(Self { name, content })
+    }
+}
+
+impl ToTokens for ComponentField {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        let ComponentField { name, content, .. } = self;
+        tokens.append_all(quote! {
+            .#name(#content)
+        })
+    }
+}
+
+fn is_literal_foramtted(lit: &LitStr) -> bool {
+    let s = lit.value();
+    let mut chars = s.chars();
+
+    while let Some(next) = chars.next() {
+        if next == '{' {
+            let nen = chars.next();
+            if nen != Some('{') {
+                return true;
+            }
+        }
+    }
+
+    false
+}

+ 282 - 0
packages/core-macro/src/rsx/element.rs

@@ -0,0 +1,282 @@
+use super::*;
+
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{
+    parse::{Parse, ParseBuffer, ParseStream},
+    Expr, Ident, LitStr, Result, Token,
+};
+
+// =======================================
+// Parse the VNode::Element type
+// =======================================
+pub struct Element {
+    pub name: Ident,
+    pub key: Option<LitStr>,
+    pub attributes: Vec<ElementAttrNamed>,
+    pub children: Vec<BodyNode>,
+    pub _is_static: bool,
+}
+
+impl Parse for Element {
+    fn parse(stream: ParseStream) -> Result<Self> {
+        let el_name = Ident::parse(stream)?;
+
+        // parse the guts
+        let content: ParseBuffer;
+        syn::braced!(content in stream);
+
+        let mut attributes: Vec<ElementAttrNamed> = vec![];
+        let mut children: Vec<BodyNode> = vec![];
+        let mut key = None;
+        let mut _el_ref = None;
+
+        // parse fields with commas
+        // break when we don't get this pattern anymore
+        // start parsing bodynodes
+        // "def": 456,
+        // abc: 123,
+        loop {
+            // Parse the raw literal fields
+            if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+                let name = content.parse::<LitStr>()?;
+                let ident = name.clone();
+
+                content.parse::<Token![:]>()?;
+
+                if content.peek(LitStr) && content.peek2(Token![,]) {
+                    let value = content.parse::<LitStr>()?;
+                    attributes.push(ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr::CustomAttrText { name, value },
+                    });
+                } else {
+                    let value = content.parse::<Expr>()?;
+
+                    attributes.push(ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr::CustomAttrExpression { name, value },
+                    });
+                }
+
+                if content.is_empty() {
+                    break;
+                }
+
+                // todo: add a message saying you need to include commas between fields
+                if content.parse::<Token![,]>().is_err() {
+                    proc_macro_error::emit_error!(
+                        ident,
+                        "This attribute is missing a trailing comma"
+                    )
+                }
+                continue;
+            }
+
+            if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+                let name = content.parse::<Ident>()?;
+                let ident = name.clone();
+
+                let name_str = name.to_string();
+                content.parse::<Token![:]>()?;
+
+                if name_str.starts_with("on") {
+                    attributes.push(ElementAttrNamed {
+                        el_name: el_name.clone(),
+                        attr: ElementAttr::EventTokens {
+                            name,
+                            tokens: content.parse()?,
+                        },
+                    });
+                } else {
+                    match name_str.as_str() {
+                        "key" => {
+                            key = Some(content.parse()?);
+                        }
+                        "classes" => todo!("custom class list not supported yet"),
+                        // "namespace" => todo!("custom namespace not supported yet"),
+                        "node_ref" => {
+                            _el_ref = Some(content.parse::<Expr>()?);
+                        }
+                        _ => {
+                            if content.peek(LitStr) {
+                                attributes.push(ElementAttrNamed {
+                                    el_name: el_name.clone(),
+                                    attr: ElementAttr::AttrText {
+                                        name,
+                                        value: content.parse()?,
+                                    },
+                                });
+                            } else {
+                                attributes.push(ElementAttrNamed {
+                                    el_name: el_name.clone(),
+                                    attr: ElementAttr::AttrExpression {
+                                        name,
+                                        value: content.parse()?,
+                                    },
+                                });
+                            }
+                        }
+                    }
+                }
+
+                if content.is_empty() {
+                    break;
+                }
+
+                // todo: add a message saying you need to include commas between fields
+                if content.parse::<Token![,]>().is_err() {
+                    proc_macro_error::emit_error!(
+                        ident,
+                        "This attribute is missing a trailing comma"
+                    )
+                }
+                continue;
+            }
+
+            break;
+        }
+
+        while !content.is_empty() {
+            if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
+                let ident = content.parse::<LitStr>().unwrap();
+                let name = ident.value();
+                proc_macro_error::emit_error!(
+                    ident, "This attribute `{}` is in the wrong place.", name;
+                    help =
+"All attribute fields must be placed above children elements.
+
+                div {
+                   attr: \"...\",  <---- attribute is above children
+                   div { }       <---- children are below attributes
+                }";
+                )
+            }
+
+            if (content.peek(Ident) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
+                let ident = content.parse::<Ident>().unwrap();
+                let name = ident.to_string();
+                proc_macro_error::emit_error!(
+                    ident, "This attribute `{}` is in the wrong place.", name;
+                    help =
+"All attribute fields must be placed above children elements.
+
+                div {
+                   attr: \"...\",  <---- attribute is above children
+                   div { }       <---- children are below attributes
+                }";
+                )
+            }
+
+            children.push(content.parse::<BodyNode>()?);
+            // consume comma if it exists
+            // we don't actually care if there *are* commas after elements/text
+            if content.peek(Token![,]) {
+                let _ = content.parse::<Token![,]>();
+            }
+        }
+
+        Ok(Self {
+            key,
+            name: el_name,
+            attributes,
+            children,
+            _is_static: false,
+        })
+    }
+}
+
+impl ToTokens for Element {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        let name = &self.name;
+        let children = &self.children;
+
+        let key = match &self.key {
+            Some(ty) => quote! { Some(format_args_f!(#ty)) },
+            None => quote! { None },
+        };
+
+        let listeners = self
+            .attributes
+            .iter()
+            .filter(|f| matches!(f.attr, ElementAttr::EventTokens { .. }));
+
+        let attr = self
+            .attributes
+            .iter()
+            .filter(|f| !matches!(f.attr, ElementAttr::EventTokens { .. }));
+
+        tokens.append_all(quote! {
+            __cx.element(
+                dioxus_elements::#name,
+                __cx.bump().alloc([ #(#listeners),* ]),
+                __cx.bump().alloc([ #(#attr),* ]),
+                __cx.bump().alloc([ #(#children),* ]),
+                #key,
+            )
+        });
+    }
+}
+
+pub enum ElementAttr {
+    /// attribute: "valuee {}"
+    AttrText { name: Ident, value: LitStr },
+
+    /// attribute: true,
+    AttrExpression { name: Ident, value: Expr },
+
+    /// "attribute": "value {}"
+    CustomAttrText { name: LitStr, value: LitStr },
+
+    /// "attribute": true,
+    CustomAttrExpression { name: LitStr, value: Expr },
+
+    // /// onclick: move |_| {}
+    // EventClosure { name: Ident, closure: ExprClosure },
+    /// onclick: {}
+    EventTokens { name: Ident, tokens: Expr },
+}
+
+pub struct ElementAttrNamed {
+    pub el_name: Ident,
+    pub attr: ElementAttr,
+}
+
+impl ToTokens for ElementAttrNamed {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        let ElementAttrNamed { el_name, attr } = self;
+
+        tokens.append_all(match attr {
+            ElementAttr::AttrText { name, value } => {
+                quote! {
+                    dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
+                }
+            }
+            ElementAttr::AttrExpression { name, value } => {
+                quote! {
+                    dioxus_elements::#el_name.#name(__cx, #value)
+                }
+            }
+            ElementAttr::CustomAttrText { name, value } => {
+                quote! {
+                    __cx.attr( #name, format_args_f!(#value), None, false )
+                }
+            }
+            ElementAttr::CustomAttrExpression { name, value } => {
+                quote! {
+                    __cx.attr( #name, format_args_f!(#value), None, false )
+                }
+            }
+            // ElementAttr::EventClosure { name, closure } => {
+            //     quote! {
+            //         dioxus_elements::on::#name(__cx, #closure)
+            //     }
+            // }
+            ElementAttr::EventTokens { name, tokens } => {
+                quote! {
+                    dioxus_elements::on::#name(__cx, #tokens)
+                }
+            }
+        });
+    }
+}

+ 97 - 0
packages/core-macro/src/rsx/mod.rs

@@ -0,0 +1,97 @@
+//! Parse the root tokens in the rsx!{} macro
+//! =========================================
+//!
+//! This parsing path emerges directly from the macro call, with `RsxRender` being the primary entrance into parsing.
+//! This feature must support:
+//! - [x] Optionally rendering if the `in XYZ` pattern is present
+//! - [x] Fragments as top-level element (through ambiguous)
+//! - [x] Components as top-level element (through ambiguous)
+//! - [x] Tags as top-level elements (through ambiguous)
+//! - [x] Good errors if parsing fails
+//!
+//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
+
+mod component;
+mod element;
+mod node;
+
+pub mod pretty;
+
+// Re-export the namespaces into each other
+pub use component::*;
+pub use element::*;
+pub use node::*;
+
+// imports
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{
+    parse::{Parse, ParseStream},
+    Ident, Result, Token,
+};
+
+pub struct CallBody {
+    pub custom_context: Option<Ident>,
+    pub roots: Vec<BodyNode>,
+}
+
+impl Parse for CallBody {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let custom_context = if input.peek(Ident) && input.peek2(Token![,]) {
+            let name = input.parse::<Ident>()?;
+            input.parse::<Token![,]>()?;
+
+            Some(name)
+        } else {
+            None
+        };
+
+        let mut roots = Vec::new();
+
+        while !input.is_empty() {
+            let node = input.parse::<BodyNode>()?;
+
+            if input.peek(Token![,]) {
+                let _ = input.parse::<Token![,]>();
+            }
+
+            roots.push(node);
+        }
+
+        Ok(Self {
+            custom_context,
+            roots,
+        })
+    }
+}
+
+/// Serialize the same way, regardless of flavor
+impl ToTokens for CallBody {
+    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
+        let inner = if self.roots.len() == 1 {
+            let inner = &self.roots[0];
+            quote! { #inner }
+        } else {
+            let childs = &self.roots;
+            quote! { __cx.fragment_root([ #(#childs),* ]) }
+        };
+
+        match &self.custom_context {
+            // The `in cx` pattern allows directly rendering
+            Some(ident) => out_tokens.append_all(quote! {
+                #ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
+                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
+                    #inner
+                }))
+            }),
+
+            // Otherwise we just build the LazyNode wrapper
+            None => out_tokens.append_all(quote! {
+                LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
+                    use dioxus_elements::{GlobalAttributes, SvgAttributes};
+                    #inner
+                })
+            }),
+        };
+    }
+}

+ 84 - 0
packages/core-macro/src/rsx/node.rs

@@ -0,0 +1,84 @@
+use super::*;
+
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{
+    parse::{Parse, ParseStream},
+    token, Expr, LitStr, Result, Token,
+};
+
+/*
+Parse
+-> div {}
+-> Component {}
+-> component()
+-> "text {with_args}"
+-> (0..10).map(|f| rsx!("asd")),  // <--- notice the comma - must be a complete expr
+*/
+pub enum BodyNode {
+    Element(Element),
+    Component(Component),
+    Text(LitStr),
+    RawExpr(Expr),
+}
+
+impl Parse for BodyNode {
+    fn parse(stream: ParseStream) -> Result<Self> {
+        if stream.peek(LitStr) {
+            return Ok(BodyNode::Text(stream.parse()?));
+        }
+
+        // div {} -> el
+        // Div {} -> comp
+        if stream.peek(syn::Ident) && stream.peek2(token::Brace) {
+            if stream
+                .fork()
+                .parse::<Ident>()?
+                .to_string()
+                .chars()
+                .next()
+                .unwrap()
+                .is_ascii_uppercase()
+            {
+                return Ok(BodyNode::Component(stream.parse()?));
+            } else {
+                return Ok(BodyNode::Element(stream.parse::<Element>()?));
+            }
+        }
+
+        // component() -> comp
+        // ::component {} -> comp
+        // ::component () -> comp
+        if (stream.peek(syn::Ident) && stream.peek2(token::Paren))
+            || (stream.peek(Token![::]))
+            || (stream.peek(Token![:]) && stream.peek2(Token![:]))
+        {
+            return Ok(BodyNode::Component(stream.parse::<Component>()?));
+        }
+
+        // crate::component{} -> comp
+        // crate::component() -> comp
+        if let Ok(pat) = stream.fork().parse::<syn::Path>() {
+            if pat.segments.len() > 1 {
+                return Ok(BodyNode::Component(stream.parse::<Component>()?));
+            }
+        }
+
+        Ok(BodyNode::RawExpr(stream.parse::<Expr>()?))
+    }
+}
+
+impl ToTokens for BodyNode {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        match &self {
+            BodyNode::Element(el) => el.to_tokens(tokens),
+            BodyNode::Component(comp) => comp.to_tokens(tokens),
+            BodyNode::Text(txt) => tokens.append_all(quote! {
+                __cx.text(format_args_f!(#txt))
+            }),
+            BodyNode::RawExpr(exp) => tokens.append_all(quote! {
+                 __cx.fragment_from_iter(#exp)
+            }),
+        }
+    }
+}

+ 1 - 0
packages/core-macro/src/rsx/pretty.rs

@@ -0,0 +1 @@
+//! pretty printer for rsx!

+ 7 - 16
packages/core/src/diff.rs

@@ -852,7 +852,7 @@ impl<'b> DiffState<'b> {
                     nodes_created += self.create_node(new_node);
                     nodes_created += self.create_node(new_node);
                 } else {
                 } else {
                     self.diff_node(&old[old_index], new_node);
                     self.diff_node(&old[old_index], new_node);
-                    nodes_created += self.push_all_nodes(new_node);
+                    nodes_created += self.push_all_real_nodes(new_node);
                 }
                 }
             }
             }
 
 
@@ -875,7 +875,7 @@ impl<'b> DiffState<'b> {
                         nodes_created += self.create_node(new_node);
                         nodes_created += self.create_node(new_node);
                     } else {
                     } else {
                         self.diff_node(&old[old_index], new_node);
                         self.diff_node(&old[old_index], new_node);
-                        nodes_created += self.push_all_nodes(new_node);
+                        nodes_created += self.push_all_real_nodes(new_node);
                     }
                     }
                 }
                 }
 
 
@@ -898,7 +898,7 @@ impl<'b> DiffState<'b> {
                     nodes_created += self.create_node(new_node);
                     nodes_created += self.create_node(new_node);
                 } else {
                 } else {
                     self.diff_node(&old[old_index], new_node);
                     self.diff_node(&old[old_index], new_node);
-                    nodes_created += self.push_all_nodes(new_node);
+                    nodes_created += self.push_all_real_nodes(new_node);
                 }
                 }
             }
             }
 
 
@@ -1092,9 +1092,9 @@ impl<'b> DiffState<'b> {
     }
     }
 
 
     // recursively push all the nodes of a tree onto the stack and return how many are there
     // recursively push all the nodes of a tree onto the stack and return how many are there
-    fn push_all_nodes(&mut self, node: &'b VNode<'b>) -> usize {
+    fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
         match node {
         match node {
-            VNode::Text(_) | VNode::Placeholder(_) => {
+            VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) => {
                 self.mutations.push_root(node.mounted_id());
                 self.mutations.push_root(node.mounted_id());
                 1
                 1
             }
             }
@@ -1102,7 +1102,7 @@ impl<'b> DiffState<'b> {
             VNode::Fragment(frag) => {
             VNode::Fragment(frag) => {
                 let mut added = 0;
                 let mut added = 0;
                 for child in frag.children {
                 for child in frag.children {
-                    added += self.push_all_nodes(child);
+                    added += self.push_all_real_nodes(child);
                 }
                 }
                 added
                 added
             }
             }
@@ -1110,16 +1110,7 @@ impl<'b> DiffState<'b> {
             VNode::Component(c) => {
             VNode::Component(c) => {
                 let scope_id = c.scope.get().unwrap();
                 let scope_id = c.scope.get().unwrap();
                 let root = self.scopes.root_node(scope_id);
                 let root = self.scopes.root_node(scope_id);
-                self.push_all_nodes(root)
-            }
-
-            VNode::Element(el) => {
-                let mut num_on_stack = 0;
-                for child in el.children.iter() {
-                    num_on_stack += self.push_all_nodes(child);
-                }
-                self.mutations.push_root(el.id.get().unwrap());
-                num_on_stack + 1
+                self.push_all_real_nodes(root)
             }
             }
         }
         }
     }
     }

+ 10 - 2
packages/core/src/mutations.rs

@@ -181,6 +181,7 @@ pub enum DomEdit<'bump> {
 
 
         /// The name of the attribute to remove.
         /// The name of the attribute to remove.
         name: &'static str,
         name: &'static str,
+        ns: Option<&'bump str>,
     },
     },
 }
 }
 
 
@@ -292,8 +293,15 @@ impl<'a> Mutations<'a> {
     }
     }
 
 
     pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: u64) {
     pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: u64) {
-        let name = attribute.name;
-        self.edits.push(RemoveAttribute { name, root });
+        let Attribute {
+            name, namespace, ..
+        } = attribute;
+
+        self.edits.push(RemoveAttribute {
+            name,
+            ns: *namespace,
+            root,
+        });
     }
     }
 
 
     pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
     pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {

+ 14 - 11
packages/core/src/scopes.rs

@@ -11,6 +11,7 @@ use std::{
     future::Future,
     future::Future,
     pin::Pin,
     pin::Pin,
     rc::Rc,
     rc::Rc,
+    sync::Arc,
 };
 };
 
 
 /// for traceability, we use the raw fn pointer to identify the function
 /// for traceability, we use the raw fn pointer to identify the function
@@ -96,11 +97,10 @@ impl ScopeArena {
 
 
         // Get the height of the scope
         // Get the height of the scope
         let height = parent_scope
         let height = parent_scope
-            .map(|id| self.get_scope(id).map(|scope| scope.height + 1))
-            .flatten()
+            .and_then(|id| self.get_scope(id).map(|scope| scope.height + 1))
             .unwrap_or_default();
             .unwrap_or_default();
 
 
-        let parent_scope = parent_scope.map(|f| self.get_scope_raw(f)).flatten();
+        let parent_scope = parent_scope.and_then(|f| self.get_scope_raw(f));
 
 
         /*
         /*
         This scopearena aggressively reuses old scopes when possible.
         This scopearena aggressively reuses old scopes when possible.
@@ -582,12 +582,17 @@ impl ScopeState {
         self.our_arena_idx
         self.our_arena_idx
     }
     }
 
 
+    /// Get a handle to the raw update scheduler channel
+    pub fn scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
+        self.tasks.sender.clone()
+    }
+
     /// Create a subscription that schedules a future render for the reference component
     /// Create a subscription that schedules a future render for the reference component
     ///
     ///
     /// ## Notice: you should prefer using prepare_update and get_scope_id
     /// ## Notice: you should prefer using prepare_update and get_scope_id
-    pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
+    pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
         let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
         let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
-        Rc::new(move || {
+        Arc::new(move || {
             let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
             let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
         })
         })
     }
     }
@@ -597,9 +602,9 @@ impl ScopeState {
     /// A component's ScopeId can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
     /// A component's ScopeId can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
     ///
     ///
     /// This method should be used when you want to schedule an update for a component
     /// This method should be used when you want to schedule an update for a component
-    pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
+    pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
         let chan = self.tasks.sender.clone();
         let chan = self.tasks.sender.clone();
-        Rc::new(move |id| {
+        Arc::new(move |id| {
             let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
             let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
         })
         })
     }
     }
@@ -656,8 +661,7 @@ impl ScopeState {
         self.shared_contexts
         self.shared_contexts
             .borrow_mut()
             .borrow_mut()
             .insert(TypeId::of::<T>(), value.clone())
             .insert(TypeId::of::<T>(), value.clone())
-            .map(|f| f.downcast::<T>().ok())
-            .flatten();
+            .and_then(|f| f.downcast::<T>().ok());
         value
         value
     }
     }
 
 
@@ -687,8 +691,7 @@ impl ScopeState {
             self.shared_contexts
             self.shared_contexts
                 .borrow_mut()
                 .borrow_mut()
                 .insert(TypeId::of::<T>(), value.clone())
                 .insert(TypeId::of::<T>(), value.clone())
-                .map(|f| f.downcast::<T>().ok())
-                .flatten();
+                .and_then(|f| f.downcast::<T>().ok());
             return value;
             return value;
         }
         }
 
 

+ 8 - 0
packages/desktop/src/cfg.rs

@@ -17,6 +17,7 @@ pub struct DesktopConfig {
     pub(crate) protocols: Vec<WryProtocol>,
     pub(crate) protocols: Vec<WryProtocol>,
     pub(crate) pre_rendered: Option<String>,
     pub(crate) pre_rendered: Option<String>,
     pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
     pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
+    pub(crate) disable_context_menu: bool,
 }
 }
 
 
 pub(crate) type WryProtocol = (
 pub(crate) type WryProtocol = (
@@ -29,15 +30,22 @@ impl DesktopConfig {
     #[inline]
     #[inline]
     pub fn new() -> Self {
     pub fn new() -> Self {
         let window = WindowBuilder::new().with_title("Dioxus app");
         let window = WindowBuilder::new().with_title("Dioxus app");
+
         Self {
         Self {
             event_handler: None,
             event_handler: None,
             window,
             window,
             protocols: Vec::new(),
             protocols: Vec::new(),
             file_drop_handler: None,
             file_drop_handler: None,
             pre_rendered: None,
             pre_rendered: None,
+            disable_context_menu: !cfg!(debug_assertions),
         }
         }
     }
     }
 
 
+    pub fn with_disable_context_menu(&mut self, disable: bool) -> &mut Self {
+        self.disable_context_menu = disable;
+        self
+    }
+
     pub fn with_prerendered(&mut self, content: String) -> &mut Self {
     pub fn with_prerendered(&mut self, content: String) -> &mut Self {
         self.pre_rendered = Some(content);
         self.pre_rendered = Some(content);
         self
         self

+ 3 - 0
packages/desktop/src/controller.rs

@@ -56,6 +56,9 @@ impl DesktopController {
                     .unwrap()
                     .unwrap()
                     .push_front(serde_json::to_string(&edits.edits).unwrap());
                     .push_front(serde_json::to_string(&edits.edits).unwrap());
 
 
+                // Make sure the window is ready for any new updates
+                proxy.send_event(UserWindowEvent::Update).unwrap();
+
                 loop {
                 loop {
                     dom.wait_for_work().await;
                     dom.wait_for_work().await;
                     let mut muts = dom.work_with_deadline(|| false);
                     let mut muts = dom.work_with_deadline(|| false);

+ 4 - 5
packages/desktop/src/lib.rs

@@ -143,7 +143,6 @@ pub fn launch_with_props<P: 'static + Send>(
                                     let _ = proxy.send_event(UserWindowEvent::Update);
                                     let _ = proxy.send_event(UserWindowEvent::Update);
                                 }
                                 }
                                 "browser_open" => {
                                 "browser_open" => {
-                                    println!("browser_open");
                                     let data = message.params();
                                     let data = message.params();
                                     log::trace!("Open browser: {:?}", data);
                                     log::trace!("Open browser: {:?}", data);
                                     if let Some(temp) = data.as_object() {
                                     if let Some(temp) = data.as_object() {
@@ -173,10 +172,7 @@ pub fn launch_with_props<P: 'static + Send>(
                     webview = webview.with_custom_protocol(name, handler)
                     webview = webview.with_custom_protocol(name, handler)
                 }
                 }
 
 
-                if cfg!(debug_assertions) {
-                    // in debug, we are okay with the reload menu showing and dev tool
-                    webview = webview.with_dev_tool(true);
-                } else {
+                if cfg.disable_context_menu {
                     // in release mode, we don't want to show the dev tool or reload menus
                     // in release mode, we don't want to show the dev tool or reload menus
                     webview = webview.with_initialization_script(
                     webview = webview.with_initialization_script(
                         r#"
                         r#"
@@ -193,6 +189,9 @@ pub fn launch_with_props<P: 'static + Send>(
                         }
                         }
                     "#,
                     "#,
                     )
                     )
+                } else {
+                    // in debug, we are okay with the reload menu showing and dev tool
+                    webview = webview.with_dev_tool(true);
                 }
                 }
 
 
                 desktop.webviews.insert(window_id, webview.build().unwrap());
                 desktop.webviews.insert(window_id, webview.build().unwrap());

+ 3 - 3
packages/fermi/src/root.rs

@@ -1,4 +1,4 @@
-use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc};
+use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
 
 
 use dioxus_core::ScopeId;
 use dioxus_core::ScopeId;
 use im_rc::HashSet;
 use im_rc::HashSet;
@@ -9,7 +9,7 @@ pub type AtomId = *const ();
 
 
 pub struct AtomRoot {
 pub struct AtomRoot {
     pub atoms: RefCell<HashMap<AtomId, Slot>>,
     pub atoms: RefCell<HashMap<AtomId, Slot>>,
-    pub update_any: Rc<dyn Fn(ScopeId)>,
+    pub update_any: Arc<dyn Fn(ScopeId)>,
 }
 }
 
 
 pub struct Slot {
 pub struct Slot {
@@ -18,7 +18,7 @@ pub struct Slot {
 }
 }
 
 
 impl AtomRoot {
 impl AtomRoot {
-    pub fn new(update_any: Rc<dyn Fn(ScopeId)>) -> Self {
+    pub fn new(update_any: Arc<dyn Fn(ScopeId)>) -> Self {
         Self {
         Self {
             update_any,
             update_any,
             atoms: RefCell::new(HashMap::new()),
             atoms: RefCell::new(HashMap::new()),

+ 2 - 1
packages/hooks/src/use_shared_state.rs

@@ -3,6 +3,7 @@ use std::{
     cell::{Cell, Ref, RefCell, RefMut},
     cell::{Cell, Ref, RefCell, RefMut},
     collections::HashSet,
     collections::HashSet,
     rc::Rc,
     rc::Rc,
+    sync::Arc,
 };
 };
 
 
 type ProvidedState<T> = RefCell<ProvidedStateInner<T>>;
 type ProvidedState<T> = RefCell<ProvidedStateInner<T>>;
@@ -10,7 +11,7 @@ type ProvidedState<T> = RefCell<ProvidedStateInner<T>>;
 // Tracks all the subscribers to a shared State
 // Tracks all the subscribers to a shared State
 pub struct ProvidedStateInner<T> {
 pub struct ProvidedStateInner<T> {
     value: Rc<RefCell<T>>,
     value: Rc<RefCell<T>>,
-    notify_any: Rc<dyn Fn(ScopeId)>,
+    notify_any: Arc<dyn Fn(ScopeId)>,
     consumers: HashSet<ScopeId>,
     consumers: HashSet<ScopeId>,
 }
 }
 
 

+ 2 - 2
packages/hooks/src/usefuture.rs

@@ -1,6 +1,6 @@
 #![allow(missing_docs)]
 #![allow(missing_docs)]
 use dioxus_core::{ScopeState, TaskId};
 use dioxus_core::{ScopeState, TaskId};
-use std::{cell::Cell, future::Future, rc::Rc};
+use std::{cell::Cell, future::Future, rc::Rc, sync::Arc};
 
 
 /// A future that resolves to a value.
 /// A future that resolves to a value.
 ///
 ///
@@ -61,7 +61,7 @@ pub enum FutureState<'a, T> {
 }
 }
 
 
 pub struct UseFuture<T> {
 pub struct UseFuture<T> {
-    update: Rc<dyn Fn()>,
+    update: Arc<dyn Fn()>,
     needs_regen: Cell<bool>,
     needs_regen: Cell<bool>,
     value: Option<T>,
     value: Option<T>,
     pending: bool,
     pending: bool,

+ 2 - 1
packages/hooks/src/useref.rs

@@ -2,6 +2,7 @@ use dioxus_core::ScopeState;
 use std::{
 use std::{
     cell::{Ref, RefCell, RefMut},
     cell::{Ref, RefCell, RefMut},
     rc::Rc,
     rc::Rc,
+    sync::Arc,
 };
 };
 
 
 /// `use_ref` is a key foundational hook for storing state in Dioxus.
 /// `use_ref` is a key foundational hook for storing state in Dioxus.
@@ -121,7 +122,7 @@ pub fn use_ref<'a, T: 'static>(
 
 
 /// A type created by the [`use_ref`] hook. See its documentation for more details.
 /// A type created by the [`use_ref`] hook. See its documentation for more details.
 pub struct UseRef<T> {
 pub struct UseRef<T> {
-    update: Rc<dyn Fn()>,
+    update: Arc<dyn Fn()>,
     value: Rc<RefCell<T>>,
     value: Rc<RefCell<T>>,
 }
 }
 
 

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

@@ -5,6 +5,7 @@ use std::{
     cell::{RefCell, RefMut},
     cell::{RefCell, RefMut},
     fmt::{Debug, Display},
     fmt::{Debug, Display},
     rc::Rc,
     rc::Rc,
+    sync::Arc,
 };
 };
 
 
 /// Store state between component renders.
 /// Store state between component renders.
@@ -69,7 +70,7 @@ pub fn use_state<'a, T: 'static>(
 
 
 pub struct UseState<T: 'static> {
 pub struct UseState<T: 'static> {
     pub(crate) current_val: Rc<T>,
     pub(crate) current_val: Rc<T>,
-    pub(crate) update_callback: Rc<dyn Fn()>,
+    pub(crate) update_callback: Arc<dyn Fn()>,
     pub(crate) setter: Rc<dyn Fn(T)>,
     pub(crate) setter: Rc<dyn Fn(T)>,
     pub(crate) slot: Rc<RefCell<Rc<T>>>,
     pub(crate) slot: Rc<RefCell<Rc<T>>>,
 }
 }

+ 1 - 1
packages/interpreter/src/bindings.rs

@@ -63,5 +63,5 @@ extern "C" {
     );
     );
 
 
     #[wasm_bindgen(method)]
     #[wasm_bindgen(method)]
-    pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str);
+    pub fn RemoveAttribute(this: &Interpreter, root: u64, field: &str, ns: Option<&str>);
 }
 }

+ 8 - 8
packages/interpreter/src/interpreter.js

@@ -53,14 +53,12 @@ export class Interpreter {
     }
     }
   }
   }
   CreateTextNode(text, root) {
   CreateTextNode(text, root) {
-    // todo: make it so the types are okay
     const node = document.createTextNode(text);
     const node = document.createTextNode(text);
     this.nodes[root] = node;
     this.nodes[root] = node;
     this.stack.push(node);
     this.stack.push(node);
   }
   }
   CreateElement(tag, root) {
   CreateElement(tag, root) {
     const el = document.createElement(tag);
     const el = document.createElement(tag);
-    // el.setAttribute("data-dioxus-id", `${root}`);
     this.nodes[root] = el;
     this.nodes[root] = el;
     this.stack.push(el);
     this.stack.push(el);
   }
   }
@@ -105,7 +103,7 @@ export class Interpreter {
     if (ns === "style") {
     if (ns === "style") {
       // @ts-ignore
       // @ts-ignore
       node.style[name] = value;
       node.style[name] = value;
-    } else if (ns != null || ns != undefined) {
+    } else if (ns !== null || ns !== undefined) {
       node.setAttributeNS(ns, name, value);
       node.setAttributeNS(ns, name, value);
     } else {
     } else {
       switch (name) {
       switch (name) {
@@ -133,10 +131,12 @@ export class Interpreter {
       }
       }
     }
     }
   }
   }
-  RemoveAttribute(root, name) {
+  RemoveAttribute(root, field, ns) {
+    const name = field;
     const node = this.nodes[root];
     const node = this.nodes[root];
-
-    if (name === "value") {
+    if (ns !== null || ns !== undefined) {
+      node.removeAttributeNS(ns, name);
+    } else if (name === "value") {
       node.value = "";
       node.value = "";
     } else if (name === "checked") {
     } else if (name === "checked") {
       node.checked = false;
       node.checked = false;
@@ -260,7 +260,7 @@ export class Interpreter {
               }
               }
             }
             }
 
 
-            if (realId == null) {
+            if (realId === null) {
               return;
               return;
             }
             }
             window.ipc.postMessage(
             window.ipc.postMessage(
@@ -281,7 +281,7 @@ export class Interpreter {
         this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
         this.SetAttribute(edit.root, edit.field, edit.value, edit.ns);
         break;
         break;
       case "RemoveAttribute":
       case "RemoveAttribute":
-        this.RemoveAttribute(edit.root, edit.name);
+        this.RemoveAttribute(edit.root, edit.name, edit.ns);
         break;
         break;
     }
     }
   }
   }

+ 3 - 2
packages/router/src/service.rs

@@ -3,6 +3,7 @@ use std::{
     cell::{Cell, Ref, RefCell},
     cell::{Cell, Ref, RefCell},
     collections::{HashMap, HashSet},
     collections::{HashMap, HashSet},
     rc::Rc,
     rc::Rc,
+    sync::Arc,
 };
 };
 
 
 use dioxus_core::ScopeId;
 use dioxus_core::ScopeId;
@@ -10,7 +11,7 @@ use dioxus_core::ScopeId;
 use crate::platform::RouterProvider;
 use crate::platform::RouterProvider;
 
 
 pub struct RouterService {
 pub struct RouterService {
-    pub(crate) regen_route: Rc<dyn Fn(ScopeId)>,
+    pub(crate) regen_route: Arc<dyn Fn(ScopeId)>,
     pub(crate) pending_events: Rc<RefCell<Vec<RouteEvent>>>,
     pub(crate) pending_events: Rc<RefCell<Vec<RouteEvent>>>,
     slots: Rc<RefCell<Vec<(ScopeId, String)>>>,
     slots: Rc<RefCell<Vec<(ScopeId, String)>>>,
     onchange_listeners: Rc<RefCell<HashSet<ScopeId>>>,
     onchange_listeners: Rc<RefCell<HashSet<ScopeId>>>,
@@ -42,7 +43,7 @@ enum RouteSlot {
 }
 }
 
 
 impl RouterService {
 impl RouterService {
-    pub fn new(regen_route: Rc<dyn Fn(ScopeId)>, root_scope: ScopeId) -> Self {
+    pub fn new(regen_route: Arc<dyn Fn(ScopeId)>, root_scope: ScopeId) -> Self {
         let history = BrowserHistory::default();
         let history = BrowserHistory::default();
         let location = history.location();
         let location = history.location();
         let path = location.path();
         let path = location.path();

+ 2 - 3
packages/rsx/Cargo.toml

@@ -1,13 +1,12 @@
 [package]
 [package]
 name = "dioxus-rsx"
 name = "dioxus-rsx"
-version = "0.0.0"
+version = "0.1.0"
 edition = "2018"
 edition = "2018"
 
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 
 [dependencies]
 [dependencies]
-once_cell = "1.8"
-proc-macro-error = "1.0.4"
+proc-macro-error = "1"
 proc-macro2 = { version = "1.0.6" }
 proc-macro2 = { version = "1.0.6" }
 quote = "1.0"
 quote = "1.0"
 syn = { version = "1.0.11", features = ["full", "extra-traits"] }
 syn = { version = "1.0.11", features = ["full", "extra-traits"] }

+ 2 - 2
packages/web/src/dom.rs

@@ -127,8 +127,8 @@ impl WebsysDom {
                     self.interpreter.RemoveEventListener(root, event)
                     self.interpreter.RemoveEventListener(root, event)
                 }
                 }
 
 
-                DomEdit::RemoveAttribute { root, name } => {
-                    self.interpreter.RemoveAttribute(root, name)
+                DomEdit::RemoveAttribute { root, name, ns } => {
+                    self.interpreter.RemoveAttribute(root, name, ns)
                 }
                 }
 
 
                 DomEdit::CreateTextNode { text, root } => {
                 DomEdit::CreateTextNode { text, root } => {