diff --git a/README.md b/README.md
index ffbfba8a6..0573fa040 100644
--- a/README.md
+++ b/README.md
@@ -79,7 +79,9 @@ If you know React, then you already know Dioxus.
-## Examples:
+
+
+## Examples Projects:
| File Navigator (Desktop) | WiFi scanner (Desktop) | TodoMVC (All platforms) | Ecommerce w/ Tailwind (Liveview) |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -88,6 +90,10 @@ If you know React, then you already know Dioxus.
See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.
+## Running examples locally
+
+All local examples are built for the desktop renderer. This means you can simply clone this repo and call `cargo run --example EXAMPLE_NAME`. To run non-desktop examples, checkout the example projects shown above.
+
## Why Dioxus and why Rust?
TypeScript is a fantastic addition to JavaScript, but it's still fundamentally JavaScript. TS code runs slightly slower, has tons of configuration options, and not every package is properly typed.
diff --git a/examples/README.md b/examples/README.md
index eb0037683..2e4959e7f 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -42,14 +42,6 @@ These examples are not necessarily meant to be run, but rather serve as a refere
| [Complete rsx reference](./rsx_usage.rs) | A complete reference for all rsx! usage | ✅ |
| [Event Listeners](./listener.rs) | Attach closures to events on elements | ✅ |
-These web-specific examples must be run with `dioxus-cli` using `dioxus develop --example XYZ`
-
-| Example | What it does |
-| ------- | ------------ |
-| asd | this does |
-| asd | this does |
-
-
## Show me some examples!
@@ -75,9 +67,9 @@ fn Toggle(cx: Scope) -> Element {
let mut toggled = use_state(&cx, || false);
cx.render(rsx!{
div {
- {&cx.props.children}
+ &cx.props.children
button { onclick: move |_| toggled.set(true),
- {toggled.and_then(|| "On").or_else(|| "Off")}
+ toggled.and_then(|| "On").or_else(|| "Off")
}
}
})
@@ -112,8 +104,8 @@ fn App(cx: Scope) -> Element {
if should_show {
cx.render(rsx!(
- {title}
- ul { {list} }
+ title,
+ ul { list }
))
} else {
None
@@ -169,10 +161,10 @@ enum Route {
fn App(cx: Scope) -> Element {
let route = use_router(cx, Route::parse);
cx.render(rsx!(div {
- {match route {
+ match route {
Route::Home => rsx!( Home {} ),
Route::Post(id) => rsx!( Post { id: id })
- }}
+ }
}))
}
```
@@ -187,8 +179,8 @@ fn App(cx: Scope) -> Element {
cx.render(rsx!{
div {
- "One doggo coming right up:"
- {doggo}
+ "One doggo coming right up:",
+ doggo
}
})
}
diff --git a/examples/async.rs b/examples/async.rs
index e399ea016..4d292779e 100644
--- a/examples/async.rs
+++ b/examples/async.rs
@@ -26,20 +26,18 @@ fn app(cx: Scope) -> Element {
rsx!(cx, div {
h1 {"count is {count}"}
- button {
+ button { onclick: move |_| task.stop(),
"Stop counting"
- onclick: move |_| task.stop()
}
- button {
+ button { onclick: move |_| task.resume(),
"Start counting"
- onclick: move |_| task.resume()
}
button {
- "Switch counting direcion"
onclick: move |_| {
*direction.modify() *= -1;
task.restart();
- }
+ },
+ "Switch counting direcion"
}
})
}
diff --git a/examples/borrowed.rs b/examples/borrowed.rs
index 29c502e13..6533a21a7 100644
--- a/examples/borrowed.rs
+++ b/examples/borrowed.rs
@@ -21,7 +21,7 @@ fn main() {
}
fn App(cx: Scope) -> Element {
- let text: &mut Vec = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
+ let text = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
let first = text.get_mut(0).unwrap();
diff --git a/examples/calculator.rs b/examples/calculator.rs
index c3814c088..e43323a21 100644
--- a/examples/calculator.rs
+++ b/examples/calculator.rs
@@ -24,7 +24,9 @@ fn app(cx: Scope) -> Element {
let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str());
cx.render(rsx!(
- div { class: "calculator",
+ style { [include_str!("./assets/calculator.css")] }
+ div {
+ class: "calculator",
onkeydown: move |evt| match evt.key_code {
KeyCode::Add => operator.set(Some("+")),
KeyCode::Subtract => operator.set(Some("-")),
@@ -46,12 +48,11 @@ fn app(cx: Scope) -> Element {
}
}
_ => {}
- }
- div { class: "calculator-display", {[cur_val.separated_string()]} }
- div { class: "input-keys"
- div { class: "function-keys"
+ },
+ div { class: "calculator-display", [cur_val.separated_string()] }
+ div { class: "input-keys",
+ div { class: "function-keys",
CalculatorKey {
- {[if display_value == "0" { "C" } else { "AC" }]}
name: "key-clear",
onclick: move |_| {
display_value.set("0".to_string());
@@ -59,10 +60,10 @@ fn app(cx: Scope) -> Element {
operator.set(None);
cur_val.set(0.0);
}
- }
+ },
+ [if display_value == "0" { "C" } else { "AC" }]
}
CalculatorKey {
- "±"
name: "key-sign",
onclick: move |_| {
if display_value.starts_with("-") {
@@ -71,28 +72,53 @@ fn app(cx: Scope) -> Element {
display_value.set(format!("-{}", *display_value))
}
},
+ "±"
}
CalculatorKey {
- "%"
- onclick: {toggle_percent}
+ onclick: toggle_percent,
name: "key-percent",
+ "%"
}
}
- div { class: "digit-keys"
- CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
- CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."), "●" }
-
- {(1..9).map(|k| rsx!{
- CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
- })}
+ div { class: "digit-keys",
+ CalculatorKey { name: "key-0", onclick: move |_| input_digit(0),
+ "0"
+ }
+ CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."),
+ "●"
+ }
+ (1..9).map(|k| rsx!{
+ CalculatorKey {
+ key: "{k}",
+ name: "key-{k}",
+ onclick: move |_| input_digit(k),
+ "{k}"
+ }
+ }),
}
- div { class: "operator-keys"
- CalculatorKey { name: "key-divide", onclick: move |_| operator.set(Some("/")) "÷" }
- CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some("*")) "×" }
- CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some("-")) "−" }
- CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some("+")) "+" }
+
+ div { class: "operator-keys",
+ CalculatorKey {
+ name: "key-divide",
+ onclick: move |_| operator.set(Some("/")),
+ "÷"
+ }
+ CalculatorKey {
+ name: "key-multiply",
+ onclick: move |_| operator.set(Some("*")),
+ "×"
+ }
+ CalculatorKey {
+ name: "key-subtract",
+ onclick: move |_| operator.set(Some("-")),
+ "−"
+ }
+ CalculatorKey {
+ name: "key-add",
+ onclick: move |_| operator.set(Some("+")),
+ "+"
+ }
CalculatorKey {
- "="
name: "key-equals",
onclick: move |_| {
if let Some(op) = operator.as_ref() {
@@ -109,6 +135,7 @@ fn app(cx: Scope) -> Element {
operator.set(None);
}
},
+ "="
}
}
}
@@ -125,9 +152,9 @@ fn CalculatorKey<'a>(
) -> Element {
cx.render(rsx! {
button {
- class: "calculator-key {name}"
- onclick: {onclick}
- {children}
+ class: "calculator-key {name}",
+ onclick: onclick,
+ children
}
})
}
diff --git a/examples/core_reference/iterators.rs b/examples/core_reference/iterators.rs
index 07452921c..ad376a1bd 100644
--- a/examples/core_reference/iterators.rs
+++ b/examples/core_reference/iterators.rs
@@ -20,11 +20,11 @@ pub static Example: Component = |cx| {
li { onclick: move |_| example_data.set(f)
"ID: {f}"
ul {
- {(0..10).map(|k| rsx!{
+ (0..10).map(|k| rsx!{
li {
"Sub iterator: {f}.{k}"
}
- })}
+ })
}
}
}
diff --git a/examples/crm.rs b/examples/crm.rs
index a8eeb456b..ca1ae19cf 100644
--- a/examples/crm.rs
+++ b/examples/crm.rs
@@ -4,7 +4,7 @@ Tiny CRM: A port of the Yew CRM example to Dioxus.
use dioxus::prelude::*;
fn main() {
- dioxus::web::launch(App);
+ dioxus::desktop::launch(app);
}
enum Scene {
ClientsList,
@@ -19,88 +19,101 @@ pub struct Client {
pub description: String,
}
-static App: Component = |cx| {
- let mut clients = use_ref(&cx, || vec![] as Vec);
- let mut scene = use_state(&cx, || Scene::ClientsList);
+fn app(cx: Scope) -> Element {
+ let clients = use_ref(&cx, || vec![] as Vec);
- let mut firstname = use_state(&cx, || String::new());
- let mut lastname = use_state(&cx, || String::new());
- let mut description = use_state(&cx, || String::new());
-
- let scene = match *scene {
- Scene::ClientsList => {
- rsx!(cx, div { class: "crm"
- h2 { "List of clients" margin_bottom: "10px" }
- div { class: "clients" margin_left: "10px"
- {clients.read().iter().map(|client| rsx!(
- div { class: "client" style: "margin-bottom: 50px"
- p { "First Name: {client.first_name}" }
- p { "Last Name: {client.last_name}" }
- p {"Description: {client.description}"}
- })
- )}
- }
- button { class: "pure-button pure-button-primary" onclick: move |_| scene.set(Scene::NewClientForm), "Add New" }
- button { class: "pure-button" onclick: move |_| scene.set(Scene::Settings), "Settings" }
- })
- }
- Scene::NewClientForm => {
- let add_new = move |_| {
- clients.write().push(Client {
- description: (*description).clone(),
- first_name: (*firstname).clone(),
- last_name: (*lastname).clone(),
- });
- description.set(String::new());
- firstname.set(String::new());
- lastname.set(String::new());
- };
- rsx!(cx, div { class: "crm"
- h2 {"Add new client" margin_bottom: "10px" }
- form { class: "pure-form"
- input { class: "new-client firstname" placeholder: "First name" value: "{firstname}"
- oninput: move |e| firstname.set(e.value.clone())
- }
- input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}"
- oninput: move |e| lastname.set(e.value.clone())
- }
- textarea { class: "new-client description" placeholder: "Description" value: "{description}"
- oninput: move |e| description.set(e.value.clone())
- }
- }
- button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" }
- button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" }
- })
- }
- Scene::Settings => {
- rsx!(cx, div {
- h2 {"Settings" margin_bottom: "10px" }
- button {
- background: "rgb(202, 60, 60)"
- class: "pure-button pure-button-primary"
- onclick: move |_| clients.write().clear(),
- "Remove all clients"
- }
- button {
- class: "pure-button pure-button-primary"
- onclick: move |_| scene.set(Scene::ClientsList),
- "Go Back"
- }
- })
- }
- };
+ let scene = use_state(&cx, || Scene::ClientsList);
+ let firstname = use_state(&cx, String::new);
+ let lastname = use_state(&cx, String::new);
+ let description = use_state(&cx, String::new);
cx.render(rsx!(
- body {
- link {
- rel: "stylesheet"
- href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
- integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
- crossorigin: "anonymous"
- }
- margin_left: "35%"
- h1 {"Dioxus CRM Example"}
- {scene}
- }
+ body { margin_left: "35%",
+ link {
+ rel: "stylesheet",
+ href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",
+ integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5",
+ crossorigin: "anonymous",
+ }
+
+ h1 {"Dioxus CRM Example"}
+
+ match *scene {
+ Scene::ClientsList => rsx!(
+ div { class: "crm",
+ h2 { margin_bottom: "10px", "List of clients" }
+ div { class: "clients", margin_left: "10px",
+ clients.read().iter().map(|client| rsx!(
+ div { class: "client", style: "margin-bottom: 50px",
+ p { "First Name: {client.first_name}" }
+ p { "Last Name: {client.last_name}" }
+ p {"Description: {client.description}"}
+ })
+ )
+ }
+ button { class: "pure-button pure-button-primary", onclick: move |_| scene.set(Scene::NewClientForm), "Add New" }
+ button { class: "pure-button", onclick: move |_| scene.set(Scene::Settings), "Settings" }
+ }
+ ),
+ Scene::NewClientForm => rsx!(
+ div { class: "crm",
+ h2 { margin_bottom: "10px", "Add new client" }
+ form { class: "pure-form",
+ input {
+ class: "new-client firstname",
+ placeholder: "First name",
+ value: "{firstname}",
+ oninput: move |e| firstname.set(e.value.clone())
+ }
+ input {
+ class: "new-client lastname",
+ placeholder: "Last name",
+ value: "{lastname}",
+ oninput: move |e| lastname.set(e.value.clone())
+ }
+ textarea {
+ class: "new-client description",
+ placeholder: "Description",
+ value: "{description}",
+ oninput: move |e| description.set(e.value.clone())
+ }
+ }
+ button {
+ class: "pure-button pure-button-primary",
+ onclick: move |_| {
+ clients.write().push(Client {
+ description: (*description).clone(),
+ first_name: (*firstname).clone(),
+ last_name: (*lastname).clone(),
+ });
+ description.set(String::new());
+ firstname.set(String::new());
+ lastname.set(String::new());
+ },
+ "Add New"
+ }
+ button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList),
+ "Go Back"
+ }
+ }
+ ),
+ Scene::Settings => rsx!(
+ div {
+ h2 { margin_bottom: "10px", "Settings" }
+ button {
+ background: "rgb(202, 60, 60)",
+ class: "pure-button pure-button-primary",
+ onclick: move |_| clients.write().clear(),
+ "Remove all clients"
+ }
+ button {
+ class: "pure-button pure-button-primary",
+ onclick: move |_| scene.set(Scene::ClientsList),
+ "Go Back"
+ }
+ }
+ )
+ }
+ }
))
-};
+}
diff --git a/examples/desktop/demo.rs b/examples/desktop/demo.rs
index 1782d8cb0..d8dc51697 100644
--- a/examples/desktop/demo.rs
+++ b/examples/desktop/demo.rs
@@ -13,6 +13,6 @@ static App: Component = |cx| {
div {
"hello world!"
}
- {(0..10).map(|f| rsx!( div {"abc {f}"}))}
+ (0..10).map(|f| rsx!( div {"abc {f}"}))
))
};
diff --git a/examples/desktop/kitchensink.rs b/examples/desktop/kitchensink.rs
deleted file mode 100644
index 5ec049ee9..000000000
--- a/examples/desktop/kitchensink.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use dioxus_core as dioxus;
-use dioxus_core::prelude::*;
-use dioxus_core_macro::*;
-use dioxus_hooks::*;
-
-use dioxus_html as dioxus_elements;
-
-fn main() {}
diff --git a/examples/desktop/tauri.rs b/examples/desktop/tauri.rs
deleted file mode 100644
index f328e4d9d..000000000
--- a/examples/desktop/tauri.rs
+++ /dev/null
@@ -1 +0,0 @@
-fn main() {}
diff --git a/examples/desktop/todomvc.rs b/examples/desktop/todomvc.rs
index e70ed1806..46ee5ec1d 100644
--- a/examples/desktop/todomvc.rs
+++ b/examples/desktop/todomvc.rs
@@ -98,24 +98,24 @@ pub static App: Component = |cx| {
}
}
}
- ul { class: "todo-list"
- {filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id }))}
+ ul { class: "todo-list",
+ filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id }))
}
- {(!todos.read().is_empty()).then(|| rsx!(
- footer { class: "footer"
+ (!todos.read().is_empty()).then(|| rsx!(
+ footer { class: "footer",
span { class: "todo-count" strong {"{items_left} "} span {"{item_text} left"} }
ul { class: "filters"
li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
}
- {(show_clear_completed).then(|| rsx!(
+ (show_clear_completed).then(|| rsx!(
button { class: "clear-completed", onclick: move |_| clear_completed(),
"Clear completed"
}
- ))}
+ ))
}
- ))}
+ ))
}
}
footer { class: "info"
diff --git a/examples/file_explorer.rs b/examples/file_explorer.rs
index 05f5c4ca7..f27e018a9 100644
--- a/examples/file_explorer.rs
+++ b/examples/file_explorer.rs
@@ -4,50 +4,45 @@
//! This is a fun little desktop application that lets you explore the file system.
//!
//! This example is interesting because it's mixing filesystem operations and GUI, which is typically hard for UI to do.
+//!
+//! It also uses `use_ref` to maintain a model, rather than `use_state`. That way,
+//! we dont need to clutter our code with `read` commands.
use dioxus::prelude::*;
fn main() {
- dioxus::desktop::launch_cfg(App, |c| {
+ dioxus::desktop::launch_cfg(app, |c| {
c.with_window(|w| {
- w.with_resizable(true).with_inner_size(
- dioxus::desktop::wry::application::dpi::LogicalSize::new(400.0, 800.0),
- )
+ w.with_resizable(true)
+ .with_inner_size(dioxus::desktop::tao::dpi::LogicalSize::new(400.0, 800.0))
})
});
}
-static App: Component = |cx| {
- let file_manager = use_ref(&cx, Files::new);
- let files = file_manager.read();
-
- let file_list = files.path_names.iter().enumerate().map(|(dir_id, path)| {
- rsx! (
- li { a {"{path}", onclick: move |_| file_manager.write().enter_dir(dir_id), href: "#"} }
- )
- });
-
- let err_disp = files.err.as_ref().map(|err| {
- rsx! (
- div {
- code {"{err}"}
- button {"x", onclick: move |_| file_manager.write().clear_err() }
- }
- )
- });
-
- let current_dir = files.current();
+fn app(cx: Scope) -> Element {
+ let files = use_ref(&cx, Files::new);
cx.render(rsx!(
- div {
- h1 {"Files: "}
- h3 {"Cur dir: {current_dir}"}
- button { "go up", onclick: move |_| file_manager.write().go_up() }
- ol { {file_list} }
- {err_disp}
+ h1 { "Files: " }
+ h3 { "Cur dir: " [files.read().current()] }
+ button { onclick: move |_| files.write().go_up(), "go up" }
+ ol {
+ files.read().path_names.iter().enumerate().map(|(dir_id, path)| rsx!(
+ li { key: "{path}",
+ a { href: "#", onclick: move |_| files.write().enter_dir(dir_id),
+ "{path}",
+ }
+ }
+ ))
}
+ files.read().err.as_ref().map(|err| rsx!(
+ div {
+ code { "{err}" }
+ button { onclick: move |_| files.write().clear_err(), "x" }
+ }
+ ))
))
-};
+}
struct Files {
path_stack: Vec,
@@ -69,29 +64,21 @@ impl Files {
}
fn reload_path_list(&mut self) {
- let cur_path = self.path_stack.last().unwrap();
- log::info!("Reloading path list for {:?}", cur_path);
- let paths = match std::fs::read_dir(cur_path) {
+ let paths = match std::fs::read_dir(self.path_stack.last().unwrap()) {
Ok(e) => e,
Err(err) => {
- let err = format!("An error occured: {:?}", err);
- self.err = Some(err);
+ self.err = Some(format!("An error occured: {:?}", err));
self.path_stack.pop();
return;
}
};
- let collected = paths.collect::>();
- log::info!("Path list reloaded {:#?}", collected);
// clear the current state
self.clear_err();
self.path_names.clear();
- for path in collected {
- self.path_names
- .push(path.unwrap().path().display().to_string());
- }
- log::info!("path namees are {:#?}", self.path_names);
+ self.path_names
+ .extend(paths.map(|path| path.unwrap().path().display().to_string()));
}
fn go_up(&mut self) {
diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs
index 100169679..f09693f1b 100644
--- a/examples/framework_benchmark.rs
+++ b/examples/framework_benchmark.rs
@@ -2,8 +2,8 @@ use dioxus::prelude::*;
use rand::prelude::*;
fn main() {
- dioxus::web::launch(App);
- // dioxus::desktop::launch(App);
+ // dioxus::web::launch(App);
+ dioxus::desktop::launch(App);
}
#[derive(Clone, PartialEq)]
@@ -31,16 +31,16 @@ impl Label {
}
static App: Component = |cx| {
- let mut items = use_ref(&cx, || vec![]);
- let mut selected = use_state(&cx, || None);
+ let items = use_ref(&cx, || vec![]);
+ let selected = use_state(&cx, || None);
cx.render(rsx! {
- div { class: "container"
- div { class: "jumbotron"
- div { class: "row"
+ div { class: "container",
+ div { class: "jumbotron",
+ div { class: "row",
div { class: "col-md-6", h1 { "Dioxus" } }
- div { class: "col-md-6"
- div { class: "row"
+ div { class: "col-md-6",
+ div { class: "row",
ActionButton { name: "Create 1,000 rows", id: "run",
onclick: move || items.set(Label::new_list(1_000)),
}
@@ -67,15 +67,15 @@ static App: Component = |cx| {
tbody {
{items.read().iter().enumerate().map(|(id, item)| {
let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""};
- rsx!(tr { class: "{is_in_danger}"
+ rsx!(tr { class: "{is_in_danger}",
td { class:"col-md-1" }
td { class:"col-md-1", "{item.key}" }
td { class:"col-md-1", onclick: move |_| selected.set(Some(id)),
- a { class: "lbl", {item.labels} }
+ a { class: "lbl", item.labels }
}
- td { class: "col-md-1"
+ td { class: "col-md-1",
a { class: "remove", onclick: move |_| { items.write().remove(id); },
- span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+ span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
}
}
td { class: "col-md-6" }
@@ -83,7 +83,7 @@ static App: Component = |cx| {
})}
}
}
- // span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" }
+ span { class: "preloadicon glyphicon glyphicon-remove", aria_hidden: "true" }
}
})
};
@@ -96,7 +96,7 @@ struct ActionButtonProps<'a> {
}
fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
- rsx!(cx, div { class: "col-sm-6 smallpad"
+ rsx!(cx, div { class: "col-sm-6 smallpad",
button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(),
"{cx.props.name}"
}
diff --git a/examples/hydration.rs b/examples/hydration.rs
index 3c4faa55d..1f769e799 100644
--- a/examples/hydration.rs
+++ b/examples/hydration.rs
@@ -16,18 +16,18 @@ fn main() {
let vdom = VirtualDom::new(App);
let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
- dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
+ // dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
}
static App: Component = |cx| {
- let mut val = use_state(&cx, || 0);
+ let val = use_state(&cx, || 0);
cx.render(rsx! {
div {
- h1 {"hello world. Count: {val}"}
+ h1 { "hello world. Count: {val}" }
button {
+ onclick: move |_| *val.modify() += 1,
"click to increment"
- onclick: move |_| val += 1
}
}
})
diff --git a/examples/pattern_model.rs b/examples/pattern_model.rs
index 252a000c9..45b0ccf4e 100644
--- a/examples/pattern_model.rs
+++ b/examples/pattern_model.rs
@@ -24,42 +24,42 @@ use dioxus::prelude::*;
const STYLE: &str = include_str!("./assets/calculator.css");
fn main() {
env_logger::init();
- dioxus::desktop::launch_cfg(App, |cfg| {
- cfg.with_window(|w| {
- w.with_title("Calculator Demo")
- .with_resizable(false)
- .with_inner_size(LogicalSize::new(320.0, 530.0))
- })
- });
+ // dioxus::desktop::launch_cfg(App, |cfg| {
+ // cfg.with_window(|w| {
+ // w.with_title("Calculator Demo")
+ // .with_resizable(false)
+ // .with_inner_size(LogicalSize::new(320.0, 530.0))
+ // })
+ // });
}
-static App: Component = |cx| {
+fn app(cx: Scope) -> Element {
let state = use_ref(&cx, || Calculator::new());
let clear_display = state.read().display_value.eq("0");
let clear_text = if clear_display { "C" } else { "AC" };
let formatted = state.read().formatted_display();
- rsx!(cx, div { id: "wrapper"
+ rsx!(cx, div { id: "wrapper",
div { class: "app", style { "{STYLE}" }
div { class: "calculator", onkeypress: move |evt| state.write().handle_keydown(evt),
div { class: "calculator-display", "{formatted}"}
- div { class: "calculator-keypad"
- div { class: "input-keys"
- div { class: "function-keys"
+ div { class: "calculator-keypad",
+ div { class: "input-keys",
+ div { class: "function-keys",
CalculatorKey { name: "key-clear", onclick: move |_| state.write().clear_display(), "{clear_text}" }
CalculatorKey { name: "key-sign", onclick: move |_| state.write().toggle_sign(), "±"}
CalculatorKey { name: "key-percent", onclick: move |_| state.write().toggle_percent(), "%"}
}
- div { class: "digit-keys"
+ div { class: "digit-keys",
CalculatorKey { name: "key-0", onclick: move |_| state.write().input_digit(0), "0" }
CalculatorKey { name: "key-dot", onclick: move |_| state.write().input_dot(), "●" }
- {(1..10).map(move |k| rsx!{
+ (1..10).map(move |k| rsx!{
CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| state.write().input_digit(k), "{k}" }
- })}
+ })
}
}
- div { class: "operator-keys"
+ div { class: "operator-keys",
CalculatorKey { name:"key-divide", onclick: move |_| state.write().set_operator(Operator::Div), "÷" }
CalculatorKey { name:"key-multiply", onclick: move |_| state.write().set_operator(Operator::Mul), "×" }
CalculatorKey { name:"key-subtract", onclick: move |_| state.write().set_operator(Operator::Sub), "−" }
@@ -70,7 +70,7 @@ static App: Component = |cx| {
}
}
})
-};
+}
#[derive(Props)]
struct CalculatorKeyProps<'a> {
@@ -82,9 +82,9 @@ struct CalculatorKeyProps<'a> {
fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
cx.render(rsx! {
button {
- class: "calculator-key {cx.props.name}"
- onclick: move |e| (cx.props.onclick)(e)
- {&cx.props.children}
+ class: "calculator-key {cx.props.name}",
+ onclick: move |e| (cx.props.onclick)(e),
+ &cx.props.children
}
})
}
diff --git a/examples/pattern_reducer.rs b/examples/pattern_reducer.rs
index cd14460f6..26bf3acff 100644
--- a/examples/pattern_reducer.rs
+++ b/examples/pattern_reducer.rs
@@ -14,20 +14,18 @@ fn main() {
pub static App: Component = |cx| {
let state = use_state(&cx, PlayerState::new);
- let is_playing = state.is_playing();
-
- rsx!(cx, div {
- h1 {"Select an option"}
- h3 {"The radio is... {is_playing}!"}
- button {
- "Pause"
- onclick: move |_| state.modify().reduce(PlayerAction::Pause)
+ cx.render(rsx!(
+ div {
+ h1 {"Select an option"}
+ h3 { "The radio is... " [state.is_playing()], "!" }
+ button { onclick: move |_| state.modify().reduce(PlayerAction::Pause),
+ "Pause"
+ }
+ button { onclick: move |_| state.modify().reduce(PlayerAction::Play),
+ "Play"
+ }
}
- button {
- "Play"
- onclick: move |_| state.modify().reduce(PlayerAction::Play)
- }
- })
+ ))
};
enum PlayerAction {
diff --git a/examples/readme.rs b/examples/readme.rs
index 9f0a288e3..ae17c1cd9 100644
--- a/examples/readme.rs
+++ b/examples/readme.rs
@@ -9,13 +9,13 @@ fn main() {
}
static App: Component = |cx| {
- let count = use_state(&cx, || 0);
+ let mut count = use_state(&cx, || 0);
cx.render(rsx! {
div {
h1 { "High-Five counter: {count}" }
- button { onclick: move |_| *count.modify() += 1, "Up high!" }
- button { onclick: move |_| *count.modify() -= 1, "Down low!" }
+ button { onclick: move |_| count += 1, "Up high!" }
+ button { onclick: move |_| count -= 1, "Down low!" }
}
})
};
diff --git a/examples/rsx_compile_fail.rs b/examples/rsx_compile_fail.rs
new file mode 100644
index 000000000..c95f3d7fe
--- /dev/null
+++ b/examples/rsx_compile_fail.rs
@@ -0,0 +1,61 @@
+use dioxus::prelude::*;
+
+fn main() {
+ let mut vdom = VirtualDom::new(example);
+ vdom.rebuild();
+
+ let out = dioxus::ssr::render_vdom_cfg(&vdom, |c| c.newline(true).indent(true));
+ println!("{}", out);
+}
+
+fn example(cx: Scope) -> Element {
+ let items = use_state(&cx, || {
+ vec![Thing {
+ a: "asd".to_string(),
+ b: 10,
+ }]
+ });
+
+ let things = use_ref(&cx, || {
+ vec![Thing {
+ a: "asd".to_string(),
+ b: 10,
+ }]
+ });
+ let things_list = things.read();
+
+ let mything = use_ref(&cx, || Some(String::from("asd")));
+ let mything_read = mything.read();
+
+ cx.render(rsx!(
+ div {
+ div {
+ id: "asd",
+ "your neighborhood spiderman"
+
+ items.iter().cycle().take(5).map(|f| rsx!{
+ div {
+ "{f.a}"
+ }
+ })
+
+ things_list.iter().map(|f| rsx!{
+ div {
+ "{f.a}"
+ }
+ })
+
+ mything_read.as_ref().map(|f| rsx!{
+ div {
+ "{f}"
+ }
+ })
+ }
+ }
+ ))
+}
+
+struct Thing {
+ a: String,
+ b: u32,
+}
diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs
index 195edac00..04afb7407 100644
--- a/examples/rsx_usage.rs
+++ b/examples/rsx_usage.rs
@@ -60,6 +60,7 @@ pub static EXAMPLE: Component = |cx| {
h1 {"Some text"}
h1 {"Some text with {formatting}"}
h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
+ h1 {"Formatting without interpolation " [formatting_tuple.0] "and" [formatting_tuple.1] }
h2 {
"Multiple"
"Text"
@@ -72,7 +73,7 @@ pub static EXAMPLE: Component = |cx| {
h3 {"elements"}
}
div {
- class: "my special div"
+ class: "my special div",
h1 {"Headers and attributes!"}
}
div {
@@ -88,42 +89,51 @@ pub static EXAMPLE: Component = |cx| {
}
// Expressions can be used in element position too:
- {rsx!(p { "More templating!" })}
- // {html!("Even HTML templating!!"
)}
+ rsx!(p { "More templating!" }),
// Iterators
- {(0..10).map(|i| rsx!(li { "{i}" }))}
- {{
+ (0..10).map(|i| rsx!(li { "{i}" })),
+
+ // Iterators within expressions
+ {
let data = std::collections::HashMap::<&'static str, &'static str>::new();
// Iterators *should* have keys when you can provide them.
// Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
// Using an "ID" associated with your data is a good idea.
- data.into_iter().map(|(k, v)| rsx!(li { key: "{k}" "{v}" }))
- }}
+ data.into_iter().map(|(k, v)| rsx!(li { key: "{k}", "{v}" }))
+ }
// Matching
- {match true {
+ match true {
true => rsx!( h1 {"Top text"}),
false => rsx!( h1 {"Bottom text"})
- }}
+ }
// Conditional rendering
// Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
// You can convert a bool condition to rsx! with .then and .or
- {true.then(|| rsx!(div {}))}
+ true.then(|| rsx!(div {})),
- // True conditions need to be rendered (same reasons as matching)
- {if true {
- rsx!(cx, h1 {"Top text"})
+ // Alternatively, you can use the "if" syntax - but both branches must be resolve to Element
+ if false {
+ rsx!(h1 {"Top text"})
} else {
- rsx!(cx, h1 {"Bottom text"})
- }}
+ rsx!(h1 {"Bottom text"})
+ }
- // returning "None" is a bit noisy... but rare in practice
- {None as Option<()>}
+ // Using optionals for diverging branches
+ if true {
+ Some(rsx!(h1 {"Top text"}))
+ } else {
+ None
+ }
+
+
+ // returning "None" without a diverging branch is a bit noisy... but rare in practice
+ None as Option<()>,
// Use the Dioxus type-alias for less noise
- {NONE_ELEMENT}
+ NONE_ELEMENT,
// can also just use empty fragments
Fragment {}
@@ -137,9 +147,8 @@ pub static EXAMPLE: Component = |cx| {
Fragment {
"D"
Fragment {
- "heavily nested fragments is an antipattern"
- "they cause Dioxus to do unnecessary work"
- "don't use them carelessly if you can help it"
+ "E"
+ "F"
}
}
}
@@ -158,22 +167,29 @@ pub static EXAMPLE: Component = |cx| {
Taller { a: "asd" }
// Can pass in props directly as an expression
- {{
+ {
let props = TallerProps {a: "hello", children: Default::default()};
rsx!(Taller { ..props })
- }}
+ }
// Spreading can also be overridden manually
Taller {
- ..TallerProps { a: "ballin!", children: Default::default() }
+ ..TallerProps { a: "ballin!", children: Default::default() },
a: "not ballin!"
}
// Can take children too!
Taller { a: "asd", div {"hello world!"} }
+ // Components can be used with the `call` syntax
+ // This component's props are defined *inline* with the `inline_props` macro
+ with_inline(
+ text: "using functionc all syntax"
+ )
+
// helper functions
- {helper(&cx, "hello world!")}
+ // Single values must be wrapped in braces or `Some` to satisfy `IntoIterator`
+ [helper(&cx, "hello world!")]
}
})
};
@@ -187,6 +203,7 @@ mod baller {
#[derive(Props, PartialEq)]
pub struct BallerProps {}
+ #[allow(non_snake_case)]
/// This component totally balls
pub fn Baller(_: Scope) -> Element {
todo!()
@@ -195,12 +212,20 @@ mod baller {
#[derive(Props)]
pub struct TallerProps<'a> {
+ /// Fields are documented and accessible in rsx!
a: &'static str,
children: Element<'a>,
}
-/// This component is taller than most :)
-pub fn Taller<'a>(_: Scope<'a, TallerProps<'a>>) -> Element {
- let b = true;
- todo!()
+/// Documention for this component is visible within the rsx macro
+#[allow(non_snake_case)]
+pub fn Taller<'a>(cx: Scope<'a, TallerProps<'a>>) -> Element {
+ cx.render(rsx! {
+ &cx.props.children
+ })
+}
+
+#[inline_props]
+fn with_inline<'a>(cx: Scope<'a>, text: &'a str) -> Element {
+ rsx!(cx, p { "{text}" })
}
diff --git a/examples/tailwind.rs b/examples/tailwind.rs
index 49da99ee8..9bd87c15e 100644
--- a/examples/tailwind.rs
+++ b/examples/tailwind.rs
@@ -16,9 +16,9 @@ const STYLE: &str = "body {overflow:hidden;}";
pub static App: Component = |cx| {
cx.render(rsx!(
- div { class: "overflow-hidden"
+ div { class: "overflow-hidden",
style { "{STYLE}" }
- link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" }
+ link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css", rel:"stylesheet" }
Header {}
Entry {}
Hero {}
@@ -33,20 +33,20 @@ pub static App: Component = |cx| {
pub static Header: Component = |cx| {
cx.render(rsx! {
div {
- header { class: "text-gray-400 bg-gray-900 body-font"
- div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center"
- a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0"
+ header { class: "text-gray-400 bg-gray-900 body-font",
+ div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center",
+ a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0",
StacksIcon {}
- span { class: "ml-3 text-xl" "Hello Dioxus!"}
+ span { class: "ml-3 text-xl", "Hello Dioxus!"}
}
- nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center"
- a { class: "mr-5 hover:text-white" "First Link"}
- a { class: "mr-5 hover:text-white" "Second Link"}
- a { class: "mr-5 hover:text-white" "Third Link"}
- a { class: "mr-5 hover:text-white" "Fourth Link"}
+ nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center",
+ a { class: "mr-5 hover:text-white", "First Link"}
+ a { class: "mr-5 hover:text-white", "Second Link"}
+ a { class: "mr-5 hover:text-white", "Third Link"}
+ a { class: "mr-5 hover:text-white", "Fourth Link"}
}
button {
- class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0"
+ class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0",
"Button"
RightArrowIcon {}
}
@@ -59,34 +59,34 @@ pub static Header: Component = |cx| {
pub static Hero: Component = |cx| {
//
cx.render(rsx! {
- section{ class: "text-gray-400 bg-gray-900 body-font"
- div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center"
- div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center"
- h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white"
+ section{ class: "text-gray-400 bg-gray-900 body-font",
+ div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center",
+ div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center",
+ h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white",
br { class: "hidden lg:inline-block" }
"Dioxus Sneak Peek"
}
p {
- class: "mb-8 leading-relaxed"
+ class: "mb-8 leading-relaxed",
"Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web
technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and
on mobile and embedded platforms."
}
- div { class: "flex justify-center"
+ div { class: "flex justify-center",
button {
- class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg"
+ class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg",
"Learn more"
}
button {
- class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg"
+ class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg",
"Build an app"
}
}
}
- div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6"
- img { class: "object-cover object-center rounded" alt: "hero" src: "https://i.imgur.com/oK6BLtw.png"
+ div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6",
+ img { class: "object-cover object-center rounded", alt: "hero", src: "https://i.imgur.com/oK6BLtw.png",
referrerpolicy:"no-referrer"
}
}
@@ -97,8 +97,8 @@ pub static Hero: Component = |cx| {
pub static Entry: Component = |cx| {
//
cx.render(rsx! {
- section{ class: "text-gray-400 bg-gray-900 body-font"
- div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center"
+ section{ class: "text-gray-400 bg-gray-900 body-font",
+ div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center",
textarea {
}
@@ -111,13 +111,13 @@ pub static StacksIcon: Component = |cx| {
cx.render(rsx!(
svg {
// xmlns: "http://www.w3.org/2000/svg"
- fill: "none"
- stroke: "currentColor"
- stroke_linecap: "round"
- stroke_linejoin: "round"
- stroke_width: "2"
- class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full"
- view_box: "0 0 24 24"
+ fill: "none",
+ stroke: "currentColor",
+ stroke_linecap: "round",
+ stroke_linejoin: "round",
+ stroke_width: "2",
+ class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full",
+ view_box: "0 0 24 24",
path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"}
}
))
@@ -125,13 +125,13 @@ pub static StacksIcon: Component = |cx| {
pub static RightArrowIcon: Component = |cx| {
cx.render(rsx!(
svg {
- fill: "none"
- stroke: "currentColor"
- stroke_linecap: "round"
- stroke_linejoin: "round"
- stroke_width: "2"
- class: "w-4 h-4 ml-1"
- view_box: "0 0 24 24"
+ fill: "none",
+ stroke: "currentColor",
+ stroke_linecap: "round",
+ stroke_linejoin: "round",
+ stroke_width: "2",
+ class: "w-4 h-4 ml-1",
+ view_box: "0 0 24 24",
path { d: "M5 12h14M12 5l7 7-7 7"}
}
))
diff --git a/examples/tasks.rs b/examples/tasks.rs
index a378c24a6..0838c1fa2 100644
--- a/examples/tasks.rs
+++ b/examples/tasks.rs
@@ -15,8 +15,9 @@ fn app(cx: Scope) -> Element {
use_future(&cx, || {
for_async![count];
async move {
- while let _ = tokio::time::sleep(Duration::from_millis(1000)).await {
- *count.modify() += 1;
+ loop {
+ tokio::time::sleep(Duration::from_millis(1000)).await;
+ count += 1;
}
}
});
diff --git a/examples/todomvc.rs b/examples/todomvc.rs
index a41f78d5e..8a62e0ddd 100644
--- a/examples/todomvc.rs
+++ b/examples/todomvc.rs
@@ -29,7 +29,7 @@ const App: Component = |cx| {
let todolist = todos
.iter()
- .filter(|(id, item)| match *filter {
+ .filter(|(_id, item)| match *filter {
FilterState::All => true,
FilterState::Active => !item.checked,
FilterState::Completed => item.checked,
@@ -48,34 +48,37 @@ const App: Component = |cx| {
_ => "items",
};
- rsx!(cx, div { id: "app"
+ rsx!(cx, div { id: "app",
style {"{STYLE}"}
div {
- header { class: "header"
+ header { class: "header",
h1 {"todos"}
input {
- class: "new-todo"
- placeholder: "What needs to be done?"
- value: "{draft}"
- oninput: move |evt| draft.set(evt.value.clone())
+ class: "new-todo",
+ placeholder: "What needs to be done?",
+ value: "{draft}",
+ oninput: move |evt| draft.set(evt.value.clone()),
}
}
- {todolist}
- {(!todos.is_empty()).then(|| rsx!(
+ todolist,
+ (!todos.is_empty()).then(|| rsx!(
footer {
- span { strong {"{items_left}"} span {"{item_text} left"} }
- ul { class: "filters"
+ span {
+ strong {"{items_left}"}
+ span {"{item_text} left"}
+ }
+ ul { class: "filters",
li { class: "All", a { href: "", onclick: move |_| filter.set(FilterState::All), "All" }}
li { class: "Active", a { href: "active", onclick: move |_| filter.set(FilterState::Active), "Active" }}
li { class: "Completed", a { href: "completed", onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
}
}
- ))}
+ ))
}
- footer { class: "info"
+ footer { class: "info",
p {"Double-click to edit a todo"}
- p { "Created by ", a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }}
- p { "Part of ", a { "TodoMVC", href: "http://todomvc.com" }}
+ p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
+ p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
}
})
};
@@ -93,13 +96,13 @@ pub fn TodoEntry(cx: Scope) -> Element {
rsx!(cx, li {
"{todo.id}"
input {
- class: "toggle"
- r#type: "checkbox"
+ class: "toggle",
+ r#type: "checkbox",
"{todo.checked}"
}
{is_editing.then(|| rsx!{
input {
- value: "{contents}"
+ value: "{contents}",
oninput: move |evt| contents.set(evt.value.clone())
}
})}
diff --git a/examples/web_tick.rs b/examples/web_tick.rs
index 447bd1121..74cc1897f 100644
--- a/examples/web_tick.rs
+++ b/examples/web_tick.rs
@@ -13,28 +13,19 @@
use dioxus::prelude::*;
fn main() {
- #[cfg(target_arch = "wasm32")]
- intern_strings();
-
- dioxus::web::launch(App);
+ dioxus::desktop::launch(App);
}
static App: Component = |cx| {
let mut rng = SmallRng::from_entropy();
- let rows = (0..1_000).map(|f| {
- let label = Label::new(&mut rng);
- rsx! {
- Row {
- row_id: f,
- label: label
- }
- }
- });
cx.render(rsx! {
table {
tbody {
- {rows}
+ (0..1_000).map(|f| {
+ let label = Label::new(&mut rng);
+ rsx! (Row { row_id: f, label: label })
+ })
}
}
})
@@ -50,12 +41,12 @@ fn Row(cx: Scope) -> Element {
cx.render(rsx! {
tr {
td { class:"col-md-1", "{cx.props.row_id}" }
- td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+ td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
a { class: "lbl", "{adj}" "{col}" "{noun}" }
}
- td { class: "col-md-1"
- a { class: "remove", onclick: move |_| {/* remove */}
- span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+ td { class: "col-md-1",
+ a { class: "remove", onclick: move |_| {/* remove */},
+ span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
}
}
td { class: "col-md-6" }
diff --git a/examples/webview_web.rs b/examples/webview_web.rs
index 5a7df845d..aa762cb88 100644
--- a/examples/webview_web.rs
+++ b/examples/webview_web.rs
@@ -16,13 +16,13 @@ fn main() {
}
fn app(cx: Scope) -> Element {
- let count = use_state(&cx, || 0);
+ let mut count = use_state(&cx, || 0);
cx.render(rsx! {
div {
h1 { "Hifive counter: {count}" }
- button { onclick: move |_| *count.modify() += 1, "Up high!" }
- button { onclick: move |_| *count.modify() -= 1, "Down low!" }
+ button { onclick: move |_| count += 1, "Up high!" }
+ button { onclick: move |_| count -= 1, "Down low!" }
}
})
}
diff --git a/examples/xss_safety.rs b/examples/xss_safety.rs
new file mode 100644
index 000000000..cdb0705ca
--- /dev/null
+++ b/examples/xss_safety.rs
@@ -0,0 +1,31 @@
+use dioxus::prelude::*;
+
+fn main() {
+ dioxus::desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+ let contents = use_state(&cx, || String::from(""));
+
+ cx.render(rsx! {
+ div {
+ "hello world!"
+
+ h1 {
+ "{contents}"
+ }
+ h3 {
+ [contents.as_str()]
+ }
+
+ input {
+ value: "{contents}",
+ oninput: move |e| {
+ contents.set(e.value.clone());
+ eprintln!("asd");
+ },
+ "type": "text",
+ }
+ }
+ })
+}
diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml
index e3179510e..9af9f3126 100644
--- a/packages/core-macro/Cargo.toml
+++ b/packages/core-macro/Cargo.toml
@@ -16,6 +16,7 @@ proc-macro = true
[dependencies]
once_cell = "1.8"
+proc-macro-error = "1.0.4"
proc-macro2 = { version = "1.0.6" }
quote = "1.0"
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs
index 64dab1ee3..acbf778d3 100644
--- a/packages/core-macro/src/inlineprops.rs
+++ b/packages/core-macro/src/inlineprops.rs
@@ -99,6 +99,7 @@ impl ToTokens for InlinePropsBody {
};
out_tokens.append_all(quote! {
+ #[allow(non_camel_case)]
#modifiers
#vis struct #struct_name #generics {
#(#fields),*
diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs
index 2d17f4bbc..8f603bbb7 100644
--- a/packages/core-macro/src/lib.rs
+++ b/packages/core-macro/src/lib.rs
@@ -177,11 +177,12 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// todo!()
/// }
/// ```
+#[proc_macro_error::proc_macro_error]
#[proc_macro]
pub fn rsx(s: TokenStream) -> TokenStream {
match syn::parse::(s) {
- Err(e) => e.to_compile_error().into(),
- Ok(s) => s.to_token_stream().into(),
+ Err(err) => err.to_compile_error().into(),
+ Ok(stream) => stream.to_token_stream().into(),
}
}
diff --git a/packages/core-macro/src/rsx/ambiguous.rs b/packages/core-macro/src/rsx/ambiguous.rs
deleted file mode 100644
index 16cb2c4e5..000000000
--- a/packages/core-macro/src/rsx/ambiguous.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-//! Parse anything that has a pattern of < Ident, Bracket >
-//! ========================================================
-//!
-//! Whenever a `name {}` pattern emerges, we need to parse it into an element, a component, or a fragment.
-//! This feature must support:
-//! - Namepsaced/pathed components
-//! - Differentiating between built-in and custom elements
-
-use super::*;
-
-use proc_macro2::TokenStream as TokenStream2;
-use quote::ToTokens;
-use syn::{
- parse::{Parse, ParseStream},
- Error, Ident, Result, Token,
-};
-
-#[allow(clippy::large_enum_variant)]
-pub enum AmbiguousElement {
- Element(Element),
- Component(Component),
-}
-
-impl Parse for AmbiguousElement {
- fn parse(input: ParseStream) -> Result {
- // Try to parse as an absolute path and immediately defer to the componetn
- if input.peek(Token![::]) {
- return input.parse::().map(AmbiguousElement::Component);
- }
-
- // If not an absolute path, then parse the ident and check if it's a valid tag
- if let Ok(pat) = input.fork().parse::() {
- if pat.segments.len() > 1 {
- return input.parse::().map(AmbiguousElement::Component);
- }
- }
-
- use syn::ext::IdentExt;
- if let Ok(name) = input.fork().call(Ident::parse_any) {
- let name_str = name.to_string();
-
- let first_char = name_str.chars().next().unwrap();
- if first_char.is_ascii_uppercase() {
- input.parse::().map(AmbiguousElement::Component)
- } else {
- if input.peek2(syn::token::Paren) {
- input.parse::().map(AmbiguousElement::Component)
- } else {
- input.parse::().map(AmbiguousElement::Element)
- }
- }
- } else {
- Err(Error::new(input.span(), "Not a valid Html tag"))
- }
- }
-}
-impl ToTokens for AmbiguousElement {
- fn to_tokens(&self, tokens: &mut TokenStream2) {
- match self {
- AmbiguousElement::Element(el) => el.to_tokens(tokens),
- AmbiguousElement::Component(comp) => comp.to_tokens(tokens),
- }
- }
-}
diff --git a/packages/core-macro/src/rsx/body.rs b/packages/core-macro/src/rsx/body.rs
deleted file mode 100644
index 83d2886e6..000000000
--- a/packages/core-macro/src/rsx/body.rs
+++ /dev/null
@@ -1,66 +0,0 @@
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{quote, ToTokens, TokenStreamExt};
-use syn::{
- parse::{Parse, ParseStream},
- Ident, Result, Token,
-};
-
-use super::*;
-
-pub struct CallBody {
- custom_context: Option,
- roots: Vec,
-}
-
-/// The custom rusty variant of parsing rsx!
-impl Parse for CallBody {
- fn parse(input: ParseStream) -> Result {
- let custom_context = try_parse_custom_context(input)?;
- let (_, roots, _) = BodyConfig::new_call_body().parse_component_body(input)?;
- Ok(Self {
- custom_context,
- roots,
- })
- }
-}
-
-fn try_parse_custom_context(input: ParseStream) -> Result> {
- let res = if input.peek(Ident) && input.peek2(Token![,]) {
- let name = input.parse::()?;
- input.parse::()?;
- Some(name)
- } else {
- None
- };
- Ok(res)
-}
-
-/// Serialize the same way, regardless of flavor
-impl ToTokens for CallBody {
- fn to_tokens(&self, out_tokens: &mut TokenStream2) {
- let inner = if self.roots.len() == 1 {
- let inner = &self.roots[0];
- quote! {#inner}
- } else {
- let childs = &self.roots;
- quote! { __cx.fragment_root([ #(#childs),* ]) }
- };
-
- match &self.custom_context {
- // The `in cx` pattern allows directly rendering
- Some(ident) => out_tokens.append_all(quote! {
- #ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
- use dioxus_elements::{GlobalAttributes, SvgAttributes};
- #inner
- }))
- }),
- // Otherwise we just build the LazyNode wrapper
- None => out_tokens.append_all(quote! {
- LazyNodes::new_some(move |__cx: NodeFactory| -> VNode {
- use dioxus_elements::{GlobalAttributes, SvgAttributes};
- #inner
- })
- }),
- };
- }
-}
diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs
index 9c51cb748..a19f55bac 100644
--- a/packages/core-macro/src/rsx/component.rs
+++ b/packages/core-macro/src/rsx/component.rs
@@ -19,11 +19,10 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream},
- token, Error, Expr, ExprClosure, Ident, Result, Token,
+ token, Expr, ExprClosure, Ident, Result, Token,
};
pub struct Component {
- // accept any path-like argument
name: syn::Path,
body: Vec,
children: Vec,
@@ -32,12 +31,8 @@ pub struct Component {
impl Parse for Component {
fn parse(stream: ParseStream) -> Result {
- // let name = s.parse::()?;
- // todo: look into somehow getting the crate/super/etc
-
let name = syn::Path::parse_mod_style(stream)?;
- // parse the guts
let content: ParseBuffer;
// if we see a `{` then we have a block
@@ -48,13 +43,25 @@ impl Parse for Component {
syn::parenthesized!(content in stream);
}
- let cfg: BodyConfig = BodyConfig {
- allow_children: true,
- allow_fields: true,
- allow_manual_props: true,
- };
+ let mut body = Vec::new();
+ let mut children = Vec::new();
+ let mut manual_props = None;
- let (body, children, manual_props) = cfg.parse_component_body(&content)?;
+ while !content.is_empty() {
+ // if we splat into a component then we're merging properties
+ if content.peek(Token![..]) {
+ content.parse::()?;
+ manual_props = Some(content.parse::()?);
+ } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+ body.push(content.parse::()?);
+ } else {
+ children.push(content.parse::()?);
+ }
+
+ if content.peek(Token![,]) {
+ let _ = content.parse::();
+ }
+ }
Ok(Self {
name,
@@ -65,77 +72,6 @@ impl Parse for Component {
}
}
-pub struct BodyConfig {
- pub allow_fields: bool,
- pub allow_children: bool,
- pub allow_manual_props: bool,
-}
-
-impl BodyConfig {
- /// The configuration to parse the root
- pub fn new_call_body() -> Self {
- Self {
- allow_children: true,
- allow_fields: false,
- allow_manual_props: false,
- }
- }
-}
-
-impl BodyConfig {
- // todo: unify this body parsing for both elements and components
- // both are style rather ad-hoc, though components are currently more configured
- pub fn parse_component_body(
- &self,
- content: &ParseBuffer,
- ) -> Result<(Vec, Vec, Option)> {
- let mut body = Vec::new();
- let mut children = Vec::new();
- let mut manual_props = None;
-
- 'parsing: loop {
- // [1] Break if empty
- if content.is_empty() {
- break 'parsing;
- }
-
- if content.peek(Token![..]) {
- if !self.allow_manual_props {
- return Err(Error::new(
- content.span(),
- "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
- ));
- }
- content.parse::()?;
- manual_props = Some(content.parse::()?);
- } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
- if !self.allow_fields {
- return Err(Error::new(
- content.span(),
- "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
- ));
- }
- body.push(content.parse::()?);
- } else {
- if !self.allow_children {
- return Err(Error::new(
- content.span(),
- "This item is not allowed to accept children.",
- ));
- }
- children.push(content.parse::()?);
- }
-
- // consume comma if it exists
- // we don't actually care if there *are* commas between attrs
- if content.peek(Token![,]) {
- let _ = content.parse::();
- }
- }
- Ok((body, children, manual_props))
- }
-}
-
impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
@@ -219,9 +155,7 @@ pub struct ComponentField {
enum ContentField {
ManExpr(Expr),
- OnHandler(ExprClosure),
- // A handler was provided in {} tokens
OnHandlerRaw(Expr),
}
@@ -229,9 +163,6 @@ impl ToTokens for ContentField {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
ContentField::ManExpr(e) => e.to_tokens(tokens),
- ContentField::OnHandler(e) => tokens.append_all(quote! {
- __cx.bump().alloc(#e)
- }),
ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
__cx.bump().alloc(#e)
}),
@@ -246,13 +177,7 @@ impl Parse for ComponentField {
let name_str = name.to_string();
let content = if name_str.starts_with("on") {
- if input.peek(token::Brace) {
- let content;
- syn::braced!(content in input);
- ContentField::OnHandlerRaw(content.parse()?)
- } else {
- ContentField::OnHandler(input.parse()?)
- }
+ ContentField::OnHandlerRaw(input.parse()?)
} else {
ContentField::ManExpr(input.parse::()?)
};
diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs
index babaee825..cb0b7bdaa 100644
--- a/packages/core-macro/src/rsx/element.rs
+++ b/packages/core-macro/src/rsx/element.rs
@@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseBuffer, ParseStream},
- token, Expr, ExprClosure, Ident, LitStr, Result, Token,
+ Expr, Ident, LitStr, Result, Token,
};
// =======================================
@@ -33,45 +33,69 @@ impl Parse for Element {
let mut key = None;
let mut _el_ref = None;
- // todo: more descriptive error handling
- while !content.is_empty() {
+ // parse fields with commas
+ // break when we don't get this pattern anymore
+ // start parsing bodynodes
+ // "def": 456,
+ // abc: 123,
+ loop {
+ // Parse the raw literal fields
+ if content.peek(LitStr) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+ let name = content.parse::()?;
+ let ident = name.clone();
+
+ content.parse::()?;
+
+ if content.peek(LitStr) {
+ let value = content.parse::()?;
+ attributes.push(ElementAttrNamed {
+ el_name: el_name.clone(),
+ attr: ElementAttr::CustomAttrText { name, value },
+ });
+ } else {
+ let value = content.parse::()?;
+ 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::().is_err() {
+ proc_macro_error::emit_error!(
+ ident,
+ "This attribute is misisng a trailing comma"
+ )
+ }
+ continue;
+ }
+
if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
let name = content.parse::()?;
+ let ident = name.clone();
+
let name_str = name.to_string();
content.parse::()?;
if name_str.starts_with("on") {
- if content.peek(token::Brace) {
- let mycontent;
- syn::braced!(mycontent in content);
-
- listeners.push(ElementAttrNamed {
- el_name: el_name.clone(),
- attr: ElementAttr::EventTokens {
- name,
- tokens: mycontent.parse()?,
- },
- });
- } else {
- listeners.push(ElementAttrNamed {
- el_name: el_name.clone(),
- attr: ElementAttr::EventClosure {
- name,
- closure: content.parse()?,
- },
- });
- };
+ listeners.push(ElementAttrNamed {
+ el_name: el_name.clone(),
+ attr: ElementAttr::EventTokens {
+ name,
+ tokens: content.parse()?,
+ },
+ });
} else {
match name_str.as_str() {
"key" => {
key = Some(content.parse()?);
}
- "classes" => {
- todo!("custom class list not supported")
- }
- "namespace" => {
- todo!("custom namespace not supported")
- }
+ "classes" => todo!("custom class list not supported yet"),
+ // "namespace" => todo!("custom namespace not supported yet"),
"node_ref" => {
_el_ref = Some(content.parse::()?);
}
@@ -96,27 +120,56 @@ impl Parse for Element {
}
}
}
- } else if content.peek(LitStr) && content.peek2(Token![:]) {
- let name = content.parse::()?;
- content.parse::()?;
- if content.peek(LitStr) {
- let value = content.parse::()?;
- attributes.push(ElementAttrNamed {
- el_name: el_name.clone(),
- attr: ElementAttr::CustomAttrText { name, value },
- });
- } else {
- let value = content.parse::()?;
- attributes.push(ElementAttrNamed {
- el_name: el_name.clone(),
- attr: ElementAttr::CustomAttrExpression { name, value },
- });
+ if content.is_empty() {
+ break;
}
- } else {
- children.push(content.parse::()?);
+
+ // todo: add a message saying you need to include commas between fields
+ if content.parse::().is_err() {
+ proc_macro_error::emit_error!(
+ ident,
+ "This attribute is misisng a trailing comma"
+ )
+ }
+ continue;
}
+ break;
+ }
+
+ while !content.is_empty() {
+ if (content.peek(LitStr) && content.peek2(Token![:])) && !content.peek3(Token![:]) {
+ let ident = content.parse::().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::().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::()?);
// consume comma if it exists
// we don't actually care if there *are* commas after elements/text
if content.peek(Token![,]) {
@@ -138,7 +191,7 @@ impl Parse for Element {
impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name;
- let childs = &self.children;
+ let children = &self.children;
let listeners = &self.listeners;
let attr = &self.attributes;
@@ -153,7 +206,7 @@ impl ToTokens for Element {
dioxus_elements::#name,
[ #(#listeners),* ],
[ #(#attr),* ],
- [ #(#childs),* ],
+ [ #(#children),* ],
#key,
)
});
@@ -173,8 +226,8 @@ enum ElementAttr {
// "attribute": true,
CustomAttrExpression { name: LitStr, value: Expr },
- // onclick: move |_| {}
- EventClosure { name: Ident, closure: ExprClosure },
+ // // onclick: move |_| {}
+ // EventClosure { name: Ident, closure: ExprClosure },
// onclick: {}
EventTokens { name: Ident, tokens: Expr },
@@ -189,7 +242,7 @@ impl ToTokens for ElementAttrNamed {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let ElementAttrNamed { el_name, attr } = self;
- let toks = match attr {
+ tokens.append_all(match attr {
ElementAttr::AttrText { name, value } => {
quote! {
dioxus_elements::#el_name.#name(__cx, format_args_f!(#value))
@@ -200,26 +253,26 @@ impl ToTokens for ElementAttrNamed {
dioxus_elements::#el_name.#name(__cx, #value)
}
}
-
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)
+ __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)
}
}
- };
-
- tokens.append_all(toks);
+ });
}
}
diff --git a/packages/core-macro/src/rsx/fragment.rs b/packages/core-macro/src/rsx/fragment.rs
deleted file mode 100644
index 9743b1501..000000000
--- a/packages/core-macro/src/rsx/fragment.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-//! Parse `Fragments` into the Fragment VNode
-//! ==========================================
-//!
-//! This parsing path emerges from [`AmbiguousElement`] which supports validation of the Fragment format.
-//! We can be reasonably sure that whatever enters this parsing path is in the right format.
-//! This feature must support:
-//! - [x] Optional commas
-//! - [ ] Children
-//! - [ ] Keys
-
-use super::AmbiguousElement;
-use syn::parse::ParseBuffer;
-use {
- proc_macro2::TokenStream as TokenStream2,
- quote::{quote, ToTokens, TokenStreamExt},
- syn::{
- parse::{Parse, ParseStream},
- Ident, Result, Token,
- },
-};
-
-pub struct Fragment {
- children: Vec,
-}
-
-impl Parse for Fragment {
- fn parse(input: ParseStream) -> Result {
- input.parse::()?;
-
- let children = Vec::new();
-
- // parse the guts
- let content: ParseBuffer;
- syn::braced!(content in input);
- while !content.is_empty() {
- content.parse::()?;
-
- if content.peek(Token![,]) {
- let _ = content.parse::();
- }
- }
- Ok(Self { children })
- }
-}
-
-impl ToTokens for Fragment {
- fn to_tokens(&self, tokens: &mut TokenStream2) {
- let childs = &self.children;
- let children = quote! {
- ChildrenList::new(__cx)
- #( .add_child(#childs) )*
- .finish()
- };
- tokens.append_all(quote! {
- // #key_token,
- dioxus::builder::vfragment(
- __cx,
- None,
- #children
- )
- })
- }
-}
diff --git a/packages/core-macro/src/rsx/mod.rs b/packages/core-macro/src/rsx/mod.rs
index 5a0592b36..5ff5d85f7 100644
--- a/packages/core-macro/src/rsx/mod.rs
+++ b/packages/core-macro/src/rsx/mod.rs
@@ -11,17 +11,85 @@
//!
//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
-mod ambiguous;
-mod body;
mod component;
mod element;
-mod fragment;
mod node;
// Re-export the namespaces into each other
-pub use ambiguous::*;
-pub use body::*;
pub use component::*;
pub use element::*;
-pub use fragment::*;
pub use node::*;
+
+// imports
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{quote, ToTokens, TokenStreamExt};
+use syn::{
+ parse::{Parse, ParseStream},
+ Ident, Result, Token,
+};
+
+pub struct CallBody {
+ custom_context: Option,
+ roots: Vec,
+}
+
+impl Parse for CallBody {
+ fn parse(input: ParseStream) -> Result {
+ let custom_context = if input.peek(Ident) && input.peek2(Token![,]) {
+ let name = input.parse::()?;
+ input.parse::()?;
+
+ Some(name)
+ } else {
+ None
+ };
+
+ let mut roots = Vec::new();
+
+ while !input.is_empty() {
+ let node = input.parse::()?;
+
+ if input.peek(Token![,]) {
+ let _ = input.parse::();
+ }
+
+ 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
+ })
+ }),
+ };
+ }
+}
diff --git a/packages/core-macro/src/rsx/node.rs b/packages/core-macro/src/rsx/node.rs
index e027711e6..d5d701767 100644
--- a/packages/core-macro/src/rsx/node.rs
+++ b/packages/core-macro/src/rsx/node.rs
@@ -4,34 +4,67 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
- token, Expr, LitStr, Result,
+ token, Expr, LitStr, Result, Token,
};
-// ==============================================
-// Parse any div {} as a VElement
-// ==============================================
+/*
+Parse
+-> div {}
+-> Component {}
+-> component()
+-> "text {with_args}"
+-> (0..10).map(|f| rsx!("asd")), // <--- notice the comma - must be a complete expr
+*/
pub enum BodyNode {
- Element(AmbiguousElement),
- Text(TextNode),
+ Element(Element),
+ Component(Component),
+ Text(LitStr),
RawExpr(Expr),
}
impl Parse for BodyNode {
fn parse(stream: ParseStream) -> Result {
- // Supposedly this approach is discouraged due to inability to return proper errors
- // TODO: Rework this to provide more informative errors
-
- if stream.peek(token::Brace) {
- let content;
- syn::braced!(content in stream);
- return Ok(BodyNode::RawExpr(content.parse::()?));
- }
-
if stream.peek(LitStr) {
- return Ok(BodyNode::Text(stream.parse::()?));
+ return Ok(BodyNode::Text(stream.parse()?));
}
- Ok(BodyNode::Element(stream.parse::()?))
+ // div {} -> el
+ // Div {} -> comp
+ if stream.peek(syn::Ident) && stream.peek2(token::Brace) {
+ if stream
+ .fork()
+ .parse::()?
+ .to_string()
+ .chars()
+ .next()
+ .unwrap()
+ .is_ascii_uppercase()
+ {
+ return Ok(BodyNode::Component(stream.parse()?));
+ } else {
+ return Ok(BodyNode::Element(stream.parse::()?));
+ }
+ }
+
+ // 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::()?));
+ }
+
+ // crate::component{} -> comp
+ // crate::component() -> comp
+ if let Ok(pat) = stream.fork().parse::() {
+ if pat.segments.len() > 1 {
+ return Ok(BodyNode::Component(stream.parse::()?));
+ }
+ }
+
+ Ok(BodyNode::RawExpr(stream.parse::()?))
}
}
@@ -39,31 +72,13 @@ impl ToTokens for BodyNode {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match &self {
BodyNode::Element(el) => el.to_tokens(tokens),
- BodyNode::Text(txt) => txt.to_tokens(tokens),
+ BodyNode::Component(comp) => comp.to_tokens(tokens),
+ BodyNode::Text(txt) => tokens.append_all(quote! {
+ __cx.text(format_args_f!(#txt))
+ }),
BodyNode::RawExpr(exp) => tokens.append_all(quote! {
__cx.fragment_from_iter(#exp)
}),
}
}
}
-
-// =======================================
-// Parse just plain text
-// =======================================
-pub struct TextNode(LitStr);
-
-impl Parse for TextNode {
- fn parse(s: ParseStream) -> Result {
- Ok(Self(s.parse()?))
- }
-}
-
-impl ToTokens for TextNode {
- fn to_tokens(&self, tokens: &mut TokenStream2) {
- // todo: use heuristics to see if we can promote to &static str
- let token_stream = &self.0.to_token_stream();
- tokens.append_all(quote! {
- __cx.text(format_args_f!(#token_stream))
- });
- }
-}
diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs
index 2ebd1e250..36cca7f0c 100644
--- a/packages/core/src/nodes.rs
+++ b/packages/core/src/nodes.rs
@@ -734,9 +734,9 @@ impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
}
}
-impl IntoVNode<'_> for &'static str {
+impl<'b> IntoVNode<'_> for &'b str {
fn into_vnode(self, cx: NodeFactory) -> VNode {
- cx.static_text(self)
+ cx.text(format_args!("{}", self))
}
}
diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs
index 21619d9c2..5d1dbcdf8 100644
--- a/packages/core/tests/vdom_rebuild.rs
+++ b/packages/core/tests/vdom_rebuild.rs
@@ -44,7 +44,7 @@ fn lists_work() {
static App: Component = |cx| {
cx.render(rsx!(
h1 {"hello"}
- {(0..6).map(|f| rsx!(span{ "{f}" }))}
+ (0..6).map(|f| rsx!(span{ "{f}" }))
))
};
let mut vdom = VirtualDom::new(App);
diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs
index 6ef67efcb..9a02d6bcb 100644
--- a/packages/hooks/src/usestate.rs
+++ b/packages/hooks/src/usestate.rs
@@ -215,3 +215,67 @@ impl<'a, O, T: std::ops::Not + Copy> std::ops::Not for UseState<'a,
!*self.get()
}
}
+
+/*
+
+Convenience methods for UseState.
+
+Note!
+
+This is not comprehensive.
+This is *just* meant to make common operations easier.
+*/
+
+use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
+
+impl<'a, T: Copy + Add> Add for UseState<'a, T> {
+ type Output = T;
+
+ fn add(self, rhs: T) -> Self::Output {
+ self.inner.current_val.add(rhs)
+ }
+}
+impl<'a, T: Copy + Add> AddAssign for UseState<'a, T> {
+ fn add_assign(&mut self, rhs: T) {
+ self.set(self.inner.current_val.add(rhs));
+ }
+}
+impl<'a, T: Copy + Sub> Sub for UseState<'a, T> {
+ type Output = T;
+
+ fn sub(self, rhs: T) -> Self::Output {
+ self.inner.current_val.sub(rhs)
+ }
+}
+impl<'a, T: Copy + Sub> SubAssign for UseState<'a, T> {
+ fn sub_assign(&mut self, rhs: T) {
+ self.set(self.inner.current_val.sub(rhs));
+ }
+}
+
+/// MUL
+impl<'a, T: Copy + Mul> Mul for UseState<'a, T> {
+ type Output = T;
+
+ fn mul(self, rhs: T) -> Self::Output {
+ self.inner.current_val.mul(rhs)
+ }
+}
+impl<'a, T: Copy + Mul> MulAssign for UseState<'a, T> {
+ fn mul_assign(&mut self, rhs: T) {
+ self.set(self.inner.current_val.mul(rhs));
+ }
+}
+/// DIV
+impl<'a, T: Copy + Div> Div for UseState<'a, T> {
+ type Output = T;
+
+ fn div(self, rhs: T) -> Self::Output {
+ self.inner.current_val.div(rhs)
+ }
+}
+impl<'a, T: Copy + Div> DivAssign for UseState<'a, T> {
+ fn div_assign(&mut self, rhs: T) {
+ self.set(self.inner.current_val.div(rhs));
+ }
+}
diff --git a/packages/router/src/link.rs b/packages/router/src/link.rs
index 818d2d283..dd8e683d9 100644
--- a/packages/router/src/link.rs
+++ b/packages/router/src/link.rs
@@ -41,8 +41,8 @@ pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
a {
href: "#",
class: format_args!("{}", cx.props.class.unwrap_or("")),
- {&cx.props.children}
onclick: move |_| service.push_route(cx.props.to.clone()),
+ {&cx.props.children}
}
})
}
diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs
index 23e93084f..aefd2062e 100644
--- a/packages/ssr/src/lib.rs
+++ b/packages/ssr/src/lib.rs
@@ -309,19 +309,18 @@ mod tests {
static SLIGHTLY_MORE_COMPLEX: Component = |cx| {
cx.render(rsx! {
- div {
- title: "About W3Schools"
- {(0..20).map(|f| rsx!{
+ div { title: "About W3Schools",
+ (0..20).map(|f| rsx!{
div {
- title: "About W3Schools"
- style: "color:blue;text-align:center"
- class: "About W3Schools"
+ title: "About W3Schools",
+ style: "color:blue;text-align:center",
+ class: "About W3Schools",
p {
- title: "About W3Schools"
+ title: "About W3Schools",
"Hello world!: {f}"
}
}
- })}
+ })
}
})
};