Merge branch 'master' into jk/coroutine-coroutineoverhaul

This commit is contained in:
Jonathan Kelley committed 2022-02-26 17:32:11 -05:00
commit 2b17717323
32 files changed
+860 -80

No files matched your search

+11
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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).
+7 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -15,8 +15,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
proc-macro = true proc-macro = true
[dependencies] [dependencies]
dioxus-rsx = { path = "../rsx" } # dioxus-rsx = { path = "../rsx" }
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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1 @@
//! pretty printer for rsx!
+7 -16
View File
@@ -853,7 +853,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);
} }
} }
@@ -876,7 +876,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);
} }
} }
@@ -899,7 +899,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);
} }
} }
@@ -1100,9 +1100,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
} }
@@ -1110,7 +1110,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
} }
@@ -1118,16 +1118,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) self.push_all_real_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
} }
} }
} }
+10 -2
View File
@@ -107,6 +107,7 @@ pub enum DomEdit<'bump> {
RemoveAttribute { RemoveAttribute {
root: u64, root: u64,
name: &'static str, name: &'static str,
ns: Option<&'bump str>,
}, },
} }
@@ -218,8 +219,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; let Attribute {
self.edits.push(RemoveAttribute { name, root }); 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
View File
@@ -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)) .and_then(|id| self.get_scope(id).map(|scope| scope.height + 1))
.flatten()
.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.
@@ -579,12 +579,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));
}) })
} }
@@ -594,9 +599,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));
}) })
} }
@@ -653,8 +658,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()) .and_then(|f| f.downcast::<T>().ok());
.flatten();
value value
} }
@@ -684,8 +688,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()) .and_then(|f| f.downcast::<T>().ok());
.flatten();
return value; return value;
} }
+8
View File
@@ -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
+4 -4
View File
@@ -172,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) { if cfg.disable_context_menu {
// in debug, we are okay with the reload menu showing and dev tool
webview = webview.with_dev_tool(true);
} else {
// 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#"
@@ -192,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
View File
@@ -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
View File
@@ -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
View File
@@ -1,5 +1,5 @@
use dioxus_core::{ScopeState, TaskId}; use dioxus_core::{ScopeState, TaskId};
use std::{any::Any, cell::Cell, future::Future, rc::Rc}; use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc};
/// A hook that provides a future that will resolve to a value. /// A hook that provides a future that will resolve to a value.
/// ///
@@ -55,7 +55,7 @@ pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static, D: UseFutureD
} }
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>,
slot: Rc<Cell<Option<T>>>, slot: Rc<Cell<Option<T>>>,
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 (ns !== null || ns !== undefined) {
if (name === "value") { 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
View File
@@ -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
View File
@@ -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"
proc-macro-error = "1.0.4"
proc-macro2 = { version = "1.0.6" } proc-macro2 = { version = "1.0.6" }
quote = "1.0" quote = "1.0"
syn = { version = "1.0.11", features = ["full", "extra-traits"] } syn = { version = "1.0.11", features = ["full", "extra-traits"] }
+2 -2
View File
@@ -127,8 +127,8 @@ impl WebsysDom {
self.interpreter.RemoveEventListener(root, event) self.interpreter.RemoveEventListener(root, event)
} }
DomEdit::RemoveAttribute { root, name } => { DomEdit::RemoveAttribute { root, name, ns } => {
self.interpreter.RemoveAttribute(root, name) self.interpreter.RemoveAttribute(root, name, ns)
} }
DomEdit::CreateTextNode { text, root } => { DomEdit::CreateTextNode { text, root } => {