From 583fdfa5618e11d660985b97e570d4503be2ff49 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 16 Jul 2021 16:11:25 -0400 Subject: [PATCH] docs: big updates to the reference --- Cargo.toml | 3 +- docs/main-concepts/03-rsx.md | 2 +- docs/platforms/06-components.md | 2 +- examples/_examples/context.rs | 2 +- examples/_examples/hello.rs | 2 +- examples/_examples/infer.rs | 4 +- examples/_examples/input.rs | 2 +- examples/_examples/jackjill.rs | 2 +- examples/_examples/jackjillrsx.rs | 2 +- examples/_examples/many.rs | 2 +- examples/_examples/rsxt.rs | 2 +- examples/doggo.rs | 2 +- examples/examples/demo.rs | 4 +- examples/model.rs | 8 +- examples/reference/antipatterns.rs | 9 +- examples/reference/async.rs | 10 -- examples/reference/basics.rs | 33 +++- examples/reference/children.rs | 5 +- examples/reference/conditional_rendering.rs | 42 +++-- examples/reference/controlled_inputs.rs | 29 +++- examples/reference/custom_elements.rs | 42 ++++- examples/reference/empty.rs | 4 +- examples/reference/fragments.rs | 17 +- examples/reference/global_css.rs | 3 +- examples/reference/inline_styles.rs | 34 +++- examples/reference/iterators.rs | 14 +- examples/reference/listener.rs | 8 +- examples/reference/main.rs | 167 +++++++++++++++++++- examples/reference/memo.rs | 17 +- examples/reference/noderefs.rs | 2 +- examples/reference/signals.rs | 2 +- examples/reference/spreadpattern.rs | 9 +- examples/reference/statemanagement.rs | 2 +- examples/reference/suspense.rs | 53 ++----- examples/reference/task.rs | 61 +++++++ examples/reference/testing.rs | 3 +- examples/reference/tostring.rs | 21 ++- examples/reference/webcomponents.rs | 26 --- examples/rsx_usage.rs | 2 +- examples/tailwind.rs | 12 +- examples/todomvc.rs | 114 +++++++++++++ notes/Parity.md | 32 ++-- notes/SOLVEDPROBLEMS.md | 4 +- packages/core-macro/src/rsx/ambiguous.rs | 30 +--- packages/core-macro/src/rsx/component.rs | 4 +- packages/core-macro/src/rsx/element.rs | 5 - packages/core/examples/alternative.rs | 2 +- packages/core/examples/contextapi.rs | 6 +- packages/core/src/context.rs | 15 +- packages/core/src/nodes.rs | 2 +- packages/hooks/src/usestate.rs | 53 +++++++ packages/html/src/lib.rs | 3 + packages/mobile/src/lib.rs | 5 +- packages/ssr/examples/tide.rs | 2 +- packages/ssr/examples/tofile.rs | 12 +- packages/web/examples/demo.rs | 12 +- src/lib.rs | 6 +- 57 files changed, 726 insertions(+), 247 deletions(-) delete mode 100644 examples/reference/async.rs create mode 100644 examples/reference/task.rs delete mode 100644 examples/reference/webcomponents.rs create mode 100644 examples/todomvc.rs diff --git a/Cargo.toml b/Cargo.toml index 2c83b6d5f..e0bdd6ed3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ dioxus-mobile = { path = "./packages/mobile", optional = true } [features] # core -default = ["core"] +default = ["core", "ssr"] core = ["macro", "hooks", "html"] macro = ["dioxus-core-macro"] hooks = ["dioxus-hooks"] @@ -48,6 +48,7 @@ async-std = { version = "1.9.0", features = ["attributes"] } im-rc = "15.0.0" rand = { version = "0.8.4", features = ["small_rng"] } fxhash = "0.2.1" +gloo-timers = "0.2.1" [workspace] members = [ diff --git a/docs/main-concepts/03-rsx.md b/docs/main-concepts/03-rsx.md index 460e67f33..6020babad 100644 --- a/docs/main-concepts/03-rsx.md +++ b/docs/main-concepts/03-rsx.md @@ -60,7 +60,7 @@ Commas are entirely optional, but might be useful to delineate between elements The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it. ```rust -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { let text = "example"; diff --git a/docs/platforms/06-components.md b/docs/platforms/06-components.md index 9dbf533a5..3e39612bc 100644 --- a/docs/platforms/06-components.md +++ b/docs/platforms/06-components.md @@ -25,7 +25,7 @@ fn Example(cx: Context, name: String) -> VNode { // or #[functional_component] -static Example: FC = |cx, name: String| html! {
"Hello {name}!"
}; +pub static Example: FC = |cx, name: String| html! {
"Hello {name}!"
}; ``` The final output of components must be a tree of VNodes. We provide an html macro for using JSX-style syntax to write these, though, you could use any macro, DSL, templating engine, or the constructors directly. diff --git a/examples/_examples/context.rs b/examples/_examples/context.rs index c299c19ef..4a490334f 100644 --- a/examples/_examples/context.rs +++ b/examples/_examples/context.rs @@ -25,7 +25,7 @@ fn main() { #[derive(Debug)] struct CustomContext([&'static str; 3]); -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.use_create_context(|| CustomContext(["Jack", "Jill", "Bob"])); cx.render(rsx! { diff --git a/examples/_examples/hello.rs b/examples/_examples/hello.rs index 023ec3acb..ac13aa19f 100644 --- a/examples/_examples/hello.rs +++ b/examples/_examples/hello.rs @@ -13,7 +13,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); } -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { let nodes = (0..500).map(|f| rsx! (li {"div"})); cx.render(rsx! { div { diff --git a/examples/_examples/infer.rs b/examples/_examples/infer.rs index 167e46b94..08de6deed 100644 --- a/examples/_examples/infer.rs +++ b/examples/_examples/infer.rs @@ -14,7 +14,7 @@ fn main() { } // this is a component -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { let (event, set_event) = use_state_classic(cx, || None); let handler = move |evt| { @@ -50,7 +50,7 @@ struct ExampleProps { name: String, } -static Example2: FC = |cx| { +pub static Example2: FC = |cx| { cx.render(rsx! { div { h1 {"hello {cx.name}"} diff --git a/examples/_examples/input.rs b/examples/_examples/input.rs index 8c9da11a5..83423c8cb 100644 --- a/examples/_examples/input.rs +++ b/examples/_examples/input.rs @@ -37,7 +37,7 @@ static App: FC<()> = |cx| { }) }; -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { class: "max-w-lg max-w-xs bg-blue-800 shadow-2xl rounded-lg mx-auto text-center py-12 mt-4 rounded-xl" div { class: "container py-5 max-w-md mx-auto" diff --git a/examples/_examples/jackjill.rs b/examples/_examples/jackjill.rs index 240c287f7..932d17fdb 100644 --- a/examples/_examples/jackjill.rs +++ b/examples/_examples/jackjill.rs @@ -9,7 +9,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)) } -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { let (name, set_name) = use_state_classic(cx, || "...?"); log::debug!("Running component...."); diff --git a/examples/_examples/jackjillrsx.rs b/examples/_examples/jackjillrsx.rs index 147b57eca..fa84a03dc 100644 --- a/examples/_examples/jackjillrsx.rs +++ b/examples/_examples/jackjillrsx.rs @@ -9,7 +9,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)) } -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { let (name, set_name) = use_state_classic(cx, || "...?"); cx.render(rsx! { section { class: "py-12 px-4 text-center" diff --git a/examples/_examples/many.rs b/examples/_examples/many.rs index e283b4249..be4973acd 100644 --- a/examples/_examples/many.rs +++ b/examples/_examples/many.rs @@ -11,7 +11,7 @@ fn main() { wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example)); } -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { span { diff --git a/examples/_examples/rsxt.rs b/examples/_examples/rsxt.rs index 0a0d4e99a..bd416f98d 100644 --- a/examples/_examples/rsxt.rs +++ b/examples/_examples/rsxt.rs @@ -27,7 +27,7 @@ struct ExampleProps { initial_name: &'static str, } -static Example: FC = |cx| { +pub static Example: FC = |cx| { let name = use_state(cx, move || cx.initial_name); cx.render(rsx! { diff --git a/examples/doggo.rs b/examples/doggo.rs index 46d0302aa..2c6f3a203 100644 --- a/examples/doggo.rs +++ b/examples/doggo.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() {} -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { let (g, set_g) = use_state(cx, || 0).classic(); let v = (0..10).map(move |f| { rsx!(li { diff --git a/examples/examples/demo.rs b/examples/examples/demo.rs index c89f299b0..44017d2e0 100644 --- a/examples/examples/demo.rs +++ b/examples/examples/demo.rs @@ -17,7 +17,7 @@ fn main() { .expect("Webview finished"); } -// static Example: FC<()> = |cx| { +// pub static Example: FC<()> = |cx| { // cx.render(html! { //
// // }) // }; -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { class: "flex items-center justify-center flex-col" diff --git a/examples/model.rs b/examples/model.rs index 9e7c12b53..f27f9754b 100644 --- a/examples/model.rs +++ b/examples/model.rs @@ -20,12 +20,8 @@ use dioxus::prelude::*; const STYLE: &str = include_str!("./assets/calculator.css"); fn main() { - dioxus::desktop::launch(App, |cfg| { - cfg.with_title("Calculator Demo") - .with_resizable(true) - .with_skip_taskbar(true) - }) - .expect("failed to launch dioxus app"); + dioxus::desktop::launch(App, |cfg| cfg.with_title("Calculator Demo")) + .expect("failed to launch dioxus app"); } static App: FC<()> = |cx| { diff --git a/examples/reference/antipatterns.rs b/examples/reference/antipatterns.rs index 008889586..90b694a63 100644 --- a/examples/reference/antipatterns.rs +++ b/examples/reference/antipatterns.rs @@ -16,8 +16,6 @@ //! Feel free to file a PR or Issue if you run into another antipattern that you think users of Dioxus should know about. use dioxus::prelude::*; -fn main() {} - /// Antipattern: Iterators without keys /// ----------------------------------- /// @@ -174,3 +172,10 @@ static _example: FC<()> = |cx| todo!(); /// This will only include the `core` dioxus crate which is relatively slim and fast to compile and avoids target-specific /// libraries. static __example: FC<()> = |cx| todo!(); + +pub static Example: FC<()> = |cx| { + cx.render(rsx! { + AntipatternNoKeys { data: std::collections::HashMap::new() } + AntipatternNestedFragments {} + }) +}; diff --git a/examples/reference/async.rs b/examples/reference/async.rs deleted file mode 100644 index 096f0d9e0..000000000 --- a/examples/reference/async.rs +++ /dev/null @@ -1,10 +0,0 @@ -use dioxus::prelude::*; -fn main() {} - -static Example: FC<()> = |cx| { - cx.render(rsx! { - div { - - } - }) -}; diff --git a/examples/reference/basics.rs b/examples/reference/basics.rs index 096f0d9e0..aefc10300 100644 --- a/examples/reference/basics.rs +++ b/examples/reference/basics.rs @@ -1,10 +1,37 @@ -use dioxus::prelude::*; -fn main() {} +//! Example: The basics of Dioxus +//! ---------------------------- +//! +//! This small example covers some of the basics of Dioxus including +//! - Components +//! - Props +//! - Children +//! - the rsx! macro -static Example: FC<()> = |cx| { +use dioxus::prelude::*; + +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { + Greeting { + name: "Dioxus" + div { "Dioxus is a fun, fast, and portable UI framework for Rust" } + } + } + }) +}; +#[derive(PartialEq, Props)] +struct GreetingProps { + name: &'static str, +} + +static Greeting: FC = |cx| { + cx.render(rsx! { + div { + h1 { "Hello, {cx.name}!" } + p { "Welcome to the Diouxs framework" } + br {} + {cx.children()} } }) }; diff --git a/examples/reference/children.rs b/examples/reference/children.rs index 38aee99f1..dc4e67838 100644 --- a/examples/reference/children.rs +++ b/examples/reference/children.rs @@ -17,9 +17,8 @@ //! In the future, this might become a runtime error, so consider it an error today. use dioxus::prelude::*; -fn main() {} -static App: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { Banner { @@ -32,7 +31,7 @@ static App: FC<()> = |cx| { }) }; -static Banner: FC<()> = |cx| { +pub static Banner: FC<()> = |cx| { cx.render(rsx! { div { h1 { "This is a great banner!" } diff --git a/examples/reference/conditional_rendering.rs b/examples/reference/conditional_rendering.rs index 3048c0108..6a349d54e 100644 --- a/examples/reference/conditional_rendering.rs +++ b/examples/reference/conditional_rendering.rs @@ -11,14 +11,12 @@ use dioxus::prelude::*; -fn main() {} - // Convert a boolean conditional into a hide/show #[derive(PartialEq, Props)] -struct MyProps { +pub struct MyProps { should_show: bool, } -static Example: FC = |cx| { +pub static Example0: FC = |cx| { cx.render(rsx! { div { {cx.should_show.then(|| rsx!{ @@ -38,10 +36,10 @@ static Example: FC = |cx| { // In short: // `rsx!(in cx, ...)` is shorthand for `cx.render(rsx!(...))` #[derive(PartialEq, Props)] -struct MyProps1 { +pub struct MyProps1 { should_show: bool, } -static Example1: FC = |cx| { +pub static Example1: FC = |cx| { cx.render(rsx! { div { // With matching @@ -70,16 +68,16 @@ static Example1: FC = |cx| { /// Matching can be expanded #[derive(PartialEq)] -enum Color { +pub enum Color { Green, Yellow, Red, } #[derive(PartialEq, Props)] -struct MyProps2 { +pub struct MyProps2 { color: Color, } -static Example2: FC = |cx| { +pub static Example2: FC = |cx| { cx.render(rsx! { div { {match cx.color { @@ -90,3 +88,29 @@ static Example2: FC = |cx| { } }) }; + +pub static Example: FC<()> = |cx| { + let should_show = use_state(cx, || false); + let mut color_index = use_state(cx, || 0); + let color = match *color_index % 2 { + 2 => Color::Green, + 1 => Color::Yellow, + _ => Color::Red, + }; + + cx.render(rsx! { + div { + button { + onclick: move |_| should_show.set(!*should_show) + "click to toggle showing the examples" + } + button { + onclick: move |_| color_index += 1 + "click to for the enxt color" + } + Example0 { should_show: *should_show } + Example1 { should_show: *should_show } + Example2 { color: color } + } + }) +}; diff --git a/examples/reference/controlled_inputs.rs b/examples/reference/controlled_inputs.rs index 096f0d9e0..16e37e491 100644 --- a/examples/reference/controlled_inputs.rs +++ b/examples/reference/controlled_inputs.rs @@ -1,10 +1,37 @@ use dioxus::prelude::*; fn main() {} -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { } }) }; + +// A controlled component: +static ControlledSelect: FC<()> = |cx| { + let value = use_state(cx, || String::from("Grapefruit")); + cx.render(rsx! { + select { value: "{value}", onchange: move |evt| value.set(evt.value()), + option { value: "Grapefruit", "Grapefruit"} + option { value: "Lime", "Lime"} + option { value: "Coconut", "Coconut"} + option { value: "Mango", "Mango"} + } + }) +}; + +// TODO - how do uncontrolled things work? +static UncontrolledSelect: FC<()> = |cx| { + let value = use_state(cx, || String::new()); + + cx.render(rsx! { + select { + option { value: "Grapefruit", "Grapefruit"} + option { value: "Lime", "Lime"} + option { value: "Coconut", "Coconut"} + option { value: "Mango", "Mango"} + } + }) +}; diff --git a/examples/reference/custom_elements.rs b/examples/reference/custom_elements.rs index 096f0d9e0..7b86430d1 100644 --- a/examples/reference/custom_elements.rs +++ b/examples/reference/custom_elements.rs @@ -1,10 +1,44 @@ -use dioxus::prelude::*; -fn main() {} +//! Example: Web Components & Custom Elements +//! ----------------------------------------- +//! +//! Web components are a flavor of html elements that can be user-defined. +//! See: https://www.webcomponents.org for more information. +//! +//! Users who use webcomponents typically don't use Dioxus. However, if you would like to use webcomponents in Dioxus, +//! you can easily create a new custom element with compile-time correct wrappers around the webcomponent. +//! +//! We don't support building new webcomponents with Dioxus, however. :( -static Example: FC<()> = |cx| { +use dioxus::prelude::*; + +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { - + custom_element { + custom_attr: "custom data on custom elements" + } } }) }; + +mod dioxus_elements { + use std::fmt::Arguments; + + use dioxus::prelude::DioxusElement; + + #[allow(non_camel_case_types)] + pub struct custom_element; + impl DioxusElement for custom_element { + const TAG_NAME: &'static str = "custom_element"; + const NAME_SPACE: Option<&'static str> = None; + } + impl custom_element { + pub fn custom_attr<'a>(&self, f: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + f.attr("custom_asttr", val, None, false) + } + } + + // Re-export the normal html namespace + pub use dioxus::prelude::dioxus_elements::*; + use dioxus_core::{nodes::Attribute, NodeFactory}; +} diff --git a/examples/reference/empty.rs b/examples/reference/empty.rs index 21a171fed..d852b5bb2 100644 --- a/examples/reference/empty.rs +++ b/examples/reference/empty.rs @@ -3,8 +3,6 @@ //! //! This is a simple pattern that allows you to return no elements! -fn main() {} - use dioxus::prelude::*; -static Example: FC<()> = |cx| cx.render(rsx! { Fragment {} }); +pub static Example: FC<()> = |cx| cx.render(rsx! { Fragment {} }); diff --git a/examples/reference/fragments.rs b/examples/reference/fragments.rs index 3061ac838..7ebde4229 100644 --- a/examples/reference/fragments.rs +++ b/examples/reference/fragments.rs @@ -9,21 +9,18 @@ //! - By using the fragment() method on the node factory use dioxus::prelude::*; -fn main() {} // Returning multiple elements with rsx! or html! -static Example1: FC<()> = |cx| { +static App1: FC<()> = |cx| { cx.render(rsx! { h1 { } h2 { } h3 { } - // {} - // "hello world" }) }; // Using the Fragment component -static Example2: FC<()> = |cx| { +static App2: FC<()> = |cx| { cx.render(rsx! { Fragment { div {} @@ -34,7 +31,7 @@ static Example2: FC<()> = |cx| { }; // Using the `fragment` method on the NodeFactory -static Example3: FC<()> = |cx| { +static App3: FC<()> = |cx| { cx.render(LazyNodes::new(move |fac| { fac.fragment_from_iter([ fac.text(format_args!("A")), @@ -44,3 +41,11 @@ static Example3: FC<()> = |cx| { ]) })) }; + +pub static Example: FC<()> = |cx| { + cx.render(rsx! { + App1 {} + App2 {} + App3 {} + }) +}; diff --git a/examples/reference/global_css.rs b/examples/reference/global_css.rs index 87b02d6f1..eb3cb73be 100644 --- a/examples/reference/global_css.rs +++ b/examples/reference/global_css.rs @@ -12,7 +12,6 @@ //! A coming update with the assets system will make it possible to include global css from child components. use dioxus::prelude::*; -fn main() {} const STYLE: &str = r#" body {background-color: powderblue;} @@ -20,7 +19,7 @@ h1 {color: blue;} p {color: red;} "#; -const Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { head { style { "{STYLE}" } } body { diff --git a/examples/reference/inline_styles.rs b/examples/reference/inline_styles.rs index 096f0d9e0..f75e2ac13 100644 --- a/examples/reference/inline_styles.rs +++ b/examples/reference/inline_styles.rs @@ -1,10 +1,38 @@ +//! Example: Inline Styles +//! ---------------------- +//! +//! This example shows how to use inline styles in Dioxus components. +//! +//! Inline styles function very similar to regular attributes, just grouped together in "style". +//! +//! Inline styles in Dioxus are more performant than React since we're able to cache attributes and compare by pointers. +//! However, it's still not as performant as cascaded styles. Use with care. + use dioxus::prelude::*; -fn main() {} -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { - div { + head { + style: { background_color: "powderblue" } + } + body { + h1 { style: { color: "blue" } + "This is a heading" + } + p { style: { color: "red" } + "This is a paragraph" + } + } + }) +}; +// .... technically the rsx! macro is slightly broken at the moment and alows styles not wrapped in style {} +// I haven't noticed any name collisions yet, and am tentatively leaving this behavior in.. +// Don't rely on it. +static Example2: FC<()> = |cx| { + cx.render(rsx! { + div { color: "red" + "hello world!" } }) }; diff --git a/examples/reference/iterators.rs b/examples/reference/iterators.rs index c5c205013..9067bb46c 100644 --- a/examples/reference/iterators.rs +++ b/examples/reference/iterators.rs @@ -7,17 +7,17 @@ //! resolves to VNodes. It's more efficient and easier to write than having to `collect` everywhere. //! //! This also makes it easy to write "pull"-style iterators that don't have a known size. - -fn main() {} +//! +//! However, when the size of an iterator needs to be know for display purposes, collecting is fine. use dioxus::prelude::*; -static Example: FC<()> = |cx| { - let g = use_state(cx, || 0); + +pub static Example: FC<()> = |cx| { + let example_data = use_state(cx, || 0); let v = (0..10).map(|f| { rsx! { - li { - onclick: move |_| g.set(f) + li { onclick: move |_| example_data.set(f) "ID: {f}" ul { {(0..10).map(|k| rsx!{ @@ -31,7 +31,7 @@ static Example: FC<()> = |cx| { }); cx.render(rsx! { - h3 {"Selected: {g}"} + h3 {"Selected: {example_data}"} ul { {v} } diff --git a/examples/reference/listener.rs b/examples/reference/listener.rs index 2da21f954..454b0087e 100644 --- a/examples/reference/listener.rs +++ b/examples/reference/listener.rs @@ -7,7 +7,13 @@ use dioxus::prelude::*; -fn main() {} +pub static Example: FC<()> = |cx| { + cx.render(rsx! { + ButtonList {} + NonUpdatingEvents {} + DisablePropogation {} + }) +}; /// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name. static ButtonList: FC<()> = |cx| { diff --git a/examples/reference/main.rs b/examples/reference/main.rs index a995ffc02..5a0b5b69f 100644 --- a/examples/reference/main.rs +++ b/examples/reference/main.rs @@ -1,7 +1,170 @@ -// mod iterator; -// mod memo; +#![allow( + unused, + dead_code, + non_upper_case_globals, + non_camel_case_types, + non_snake_case +)] + +mod antipatterns; +mod basics; +mod children; +mod conditional_rendering; +mod controlled_inputs; +mod custom_elements; +mod empty; +mod fragments; +mod global_css; +mod inline_styles; mod iterators; mod listener; mod memo; +mod noderefs; +mod signals; +mod spreadpattern; +mod statemanagement; +mod suspense; +mod task; +mod testing; +mod tostring; fn main() {} + +use std::rc::Rc; + +use dioxus::prelude::*; + +static App: FC<()> = |cx| { + let (selection, set_selection) = use_state(cx, || None as Option).classic(); + + let body = match selection { + Some(id) => rsx!(in cx, ReferenceItem { selected: *id }), + None => rsx!(in cx, div { "Select an concept to explore" }), + }; + + cx.render(rsx! { + div { + ScrollSelector { onselect: move |id| set_selection(id) } + {body} + } + }) +}; + +// this is its own component to stay memoized +#[derive(Props)] +struct ScrollSelectorProps<'a> { + onselect: &'a dyn Fn(Option), +} + +fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> VNode<'a> { + let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| { + rsx! { + li { + h3 {} + } + } + }); + cx.render(rsx! { + div { + h1 {""} + ul { + {selection_list} + button { + onclick: move |_| (cx.onselect)(Some(10)) + } + } + } + }) +} + +#[derive(PartialEq, Props)] +struct ReferenceItemProps { + selected: usize, +} + +static ReferenceItem: FC = |cx| { + let (caller, name, code) = REFERENCES[cx.selected]; + + // Create the component using the factory API directly + let caller_node = LazyNodes::new(move |f| f.component(caller, (), None, &[])); + + cx.render(rsx! { + div { + // Source of the left, rendered on the right + div { + code { "{code}" } + } + div { + {caller_node} + } + } + }) +}; + +static REFERENCES: &[(FC<()>, &str, &str)] = &[ + (basics::Example, "Basics", include_str!("./basics.rs")), + (children::Example, "Children", include_str!("./children.rs")), + ( + conditional_rendering::Example, + "Conditional Rendering", + include_str!("./conditional_rendering.rs"), + ), + // TODO + ( + controlled_inputs::Example, + "Controlled Inputs", + include_str!("./controlled_inputs.rs"), + ), + (empty::Example, "empty", include_str!("./empty.rs")), + ( + custom_elements::Example, + "Custom Elements & Web Components", + include_str!("./custom_elements.rs"), + ), + ( + fragments::Example, + "Fragments", + include_str!("./fragments.rs"), + ), + ( + iterators::Example, + "Iterators", + include_str!("./iterators.rs"), + ), + ( + global_css::Example, + "Global CSS", + include_str!("./global_css.rs"), + ), + ( + inline_styles::Example, + "Inline Styles", + include_str!("./inline_styles.rs"), + ), + (listener::Example, "Listener", include_str!("./listener.rs")), + (memo::Example, "Memo", include_str!("./memo.rs")), + ( + spreadpattern::Example, + "Spread Pattern", + include_str!("./spreadpattern.rs"), + ), + (suspense::Example, "Suspense", include_str!("./suspense.rs")), + (task::Example, "Task", include_str!("./task.rs")), + (tostring::Example, "Tostring", include_str!("./tostring.rs")), + ( + antipatterns::Example, + "Anti-patterns", + include_str!("./antipatterns.rs"), + ), + /* + TODO! + */ + (signals::Example, "Signals", include_str!("./signals.rs")), + (noderefs::Example, "NodeRefs", include_str!("./noderefs.rs")), + ( + statemanagement::Example, + "State Management", + include_str!("./statemanagement.rs"), + ), + (testing::Example, "Testing", include_str!("./testing.rs")), +]; diff --git a/examples/reference/memo.rs b/examples/reference/memo.rs index b8cb648a1..92c830ebd 100644 --- a/examples/reference/memo.rs +++ b/examples/reference/memo.rs @@ -18,11 +18,10 @@ //! memoized collections like im-rc which are designed for this use case. use dioxus::prelude::*; -fn main() {} // By default, components with no props are always memoized. // A props of () is considered empty. -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { "100% memoized!" } }) @@ -32,11 +31,11 @@ static Example: FC<()> = |cx| { // However, the parent *must* create a new string on every render. // Notice how these props implement PartialEq - this is required for 'static props #[derive(PartialEq, Props)] -struct MyProps1 { +pub struct MyProps1 { name: String, } -static Example1: FC = |cx| { +pub static Example1: FC = |cx| { cx.render(rsx! { div { "100% memoized! {cx.name}" } }) @@ -46,11 +45,11 @@ static Example1: FC = |cx| { // In contrast with the `String` example, these props use `Rc` which operates similar to strings in JavaScript. // These strings cannot be modified, but may be cheaply shared in many places without issue. #[derive(PartialEq, Props)] -struct MyProps2 { +pub struct MyProps2 { name: std::rc::Rc, } -static Example2: FC = |cx| { +pub static Example2: FC = |cx| { cx.render(rsx! { div { "100% memoized! {cx.name}" } }) @@ -58,11 +57,11 @@ static Example2: FC = |cx| { // These props *do* borrow any content, and therefore cannot be safely memoized!. #[derive(PartialEq, Props)] -struct MyProps3<'a> { +pub struct MyProps3<'a> { name: &'a str, } // We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime. -// Using the `static Example: FC<()>` pattern _will_ specify a lifetime, but that lifetime will be static which might +// Using the `pub static Example: FC<()>` pattern _will_ specify a lifetime, but that lifetime will be static which might // not exactly be what you want fn Example3<'a>(cx: Context<'a, MyProps3<'a>>) -> VNode { cx.render(rsx! { @@ -73,7 +72,7 @@ fn Example3<'a>(cx: Context<'a, MyProps3<'a>>) -> VNode { // These props *do* borrow any content, and therefore cannot be safely memoized!. // However, they cannot be compared, so we don't need the PartialEq flag. #[derive(Props)] -struct MyProps4<'a> { +pub struct MyProps4<'a> { onhandle: &'a dyn Fn(), } diff --git a/examples/reference/noderefs.rs b/examples/reference/noderefs.rs index 096f0d9e0..fdab747b6 100644 --- a/examples/reference/noderefs.rs +++ b/examples/reference/noderefs.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() {} -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { diff --git a/examples/reference/signals.rs b/examples/reference/signals.rs index 096f0d9e0..fdab747b6 100644 --- a/examples/reference/signals.rs +++ b/examples/reference/signals.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() {} -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { diff --git a/examples/reference/spreadpattern.rs b/examples/reference/spreadpattern.rs index 4810e3af8..4faddf900 100644 --- a/examples/reference/spreadpattern.rs +++ b/examples/reference/spreadpattern.rs @@ -8,27 +8,26 @@ //! values, using the manual props as the base and then modifying fields specified with non-spread attributes. use dioxus::prelude::*; -fn main() {} -static App: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { let props = MyProps { count: 0, live: true, name: "Dioxus", }; cx.render(rsx! { - Example { ..props, count: 10, div {"child"} } + Example1 { ..props, count: 10, div {"child"} } }) }; #[derive(PartialEq, Props)] -struct MyProps { +pub struct MyProps { count: u32, live: bool, name: &'static str, } -static Example: FC = |cx| { +pub static Example1: FC = |cx| { cx.render(rsx! { div { h1 { "Hello, {cx.name}"} diff --git a/examples/reference/statemanagement.rs b/examples/reference/statemanagement.rs index 096f0d9e0..fdab747b6 100644 --- a/examples/reference/statemanagement.rs +++ b/examples/reference/statemanagement.rs @@ -1,7 +1,7 @@ use dioxus::prelude::*; fn main() {} -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { diff --git a/examples/reference/suspense.rs b/examples/reference/suspense.rs index fc35c795d..a86b6a407 100644 --- a/examples/reference/suspense.rs +++ b/examples/reference/suspense.rs @@ -1,27 +1,27 @@ //! Example: Suspense //! ----------------- -//! This example shows how the "use_fetch" hook is built on top of Dioxus' "suspense" API. Suspense enables components -//! to wait on futures to complete before rendering the result into VNodes. These VNodes are immediately available in a -//! "suspended" fashion and will automatically propogate to the UI when the future completes. +//! This example demonstrates how the use_suspense hook can be used to load and render asynchronous data. Suspense enables +//! components to wait on futures to complete before rendering the result into VNodes. These VNodes are immediately +//! available in a suspended" fashion and will automatically propogate to the UI when the future completes. //! -//! Note that if your component updates or receives new props while it is awating the result, the future will be dropped -//! and all work will stop. In this example, we store the future in a hook so we can always resume it. +//! Currently, suspense futures are non-restartable. In the future, we'll provide more granular control of how to start, +//! stop, and reset these futures. use dioxus::prelude::*; -fn main() {} #[derive(serde::Deserialize)] struct DogApi { message: String, } const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random"; -pub static App: FC<()> = |cx| { - let doggo = use_future_effect(cx, move || async move { - match surf::get(ENDPOINT).recv_json::().await { +pub static Example: FC<()> = |cx| { + let doggo = cx.use_suspense( + || surf::get(ENDPOINT).recv_json::(), + |cx, res| match res { Ok(res) => rsx!(in cx, img { src: "{res.message}" }), Err(_) => rsx!(in cx, div { "No doggos for you :(" }), - } - }); + }, + ); cx.render(rsx!( div { @@ -30,34 +30,3 @@ pub static App: FC<()> = |cx| { } )) }; - -// use dioxus_core::virtual_dom::SuspendedContext; -use futures::Future; -use futures::FutureExt; -use std::pin::Pin; - -fn use_fetch<'a, T, P, F>(cx: Context

, url: &str, g: F) -> VNode<'a> -where - T: serde::de::DeserializeOwned + 'static, - // F: FnOnce(SuspendedContext, surf::Result) -> VNode<'a> + 'a, - F: FnOnce((), surf::Result) -> VNode<'a> + 'a, -{ - todo!() - // // essentially we're doing a "use_effect" but with no dependent props or post-render shenanigans - // let fetch_promise = cx.use_hook( - // move || surf::get(url).recv_json::().boxed_local(), - // // just pass the future through - // |p| p, - // |_| (), - // ); - // cx.suspend(fetch_promise, g) -} - -/// Spawns the future only when the inputs change -fn use_future_effect<'a, 'b, F, P, F2>(cx: Context

, g: F2) -> VNode<'a> -where - F: Future>, - F2: FnOnce() -> F + 'a, -{ - todo!() -} diff --git a/examples/reference/task.rs b/examples/reference/task.rs new file mode 100644 index 000000000..ac1fc3686 --- /dev/null +++ b/examples/reference/task.rs @@ -0,0 +1,61 @@ +//! Example: Tasks +//! -------------- +//! +//! Built around the same system that powers suspense, Dioxus also allows users to write arbitrary tasks that can modify +//! state asynchronously. `use_task` takes a future and returns a task handle and an option that holds the tasks's return +//! value. When the task completes, the component will re-render with the result freshly available. +//! +//! Tasks don't necessarily need to complete, however. It would be completely reasonable to wire up a websocket receiver +//! in a task and have it work infinitely while the app is running. If the socket throws an error, the task could complete +//! and the UI could present a helpful error message. +//! +//! Tasks also return the `TaskHandle` type which lets other component logic start, stop, and restart the task at any time. +//! Tasks are very much like an async-flavoroued coroutine, making them incredibly powerful. +//! +//! Tasks must be valid for the 'static lifetime, so any state management neeeds to be cloned into the closure. `use_state` +//! has a method called `for_async` which generates an AsyncUseState wrapper. This has a very similar API to the regualr +//! `use_state` but is `static. +//! +//! Remeber `use_task` is a hook! Don't call it out of order or in loops. You might aaccidentally swap the task handles +//! and break things in subtle ways. +//! +//! Whenever a component is scheduled for deletion, the task is dropped. Make sure that whatever primitives used in the +//! task have a valid drop implementation and won't leave resources tied up. + +use dioxus::prelude::*; + +pub static Example: FC<()> = |cx| { + let count = use_state(cx, || 0); + let mut direction = use_state(cx, || 1); + + // Tasks are 'static, so we need to copy relevant items in + let (async_count, dir) = (count.for_async(), *direction); + let (task, result) = cx.use_task(move || async move { + // Count infinitely! + loop { + gloo_timers::future::TimeoutFuture::new(250).await; + *async_count.get_mut() += dir; + } + }); + + cx.render(rsx! { + div { + h1 {"count is {count}"} + button { + "Stop counting" + onclick: move |_| task.stop() + } + button { + "Start counting" + onclick: move |_| task.start() + } + button { + "Switch counting direcion" + onclick: move |_| { + direction *= -1; + task.restart(); + } + } + } + }) +}; diff --git a/examples/reference/testing.rs b/examples/reference/testing.rs index 096f0d9e0..d59e9619a 100644 --- a/examples/reference/testing.rs +++ b/examples/reference/testing.rs @@ -1,7 +1,6 @@ use dioxus::prelude::*; -fn main() {} -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { cx.render(rsx! { div { diff --git a/examples/reference/tostring.rs b/examples/reference/tostring.rs index 096f0d9e0..c28c1ef4f 100644 --- a/examples/reference/tostring.rs +++ b/examples/reference/tostring.rs @@ -1,10 +1,25 @@ use dioxus::prelude::*; -fn main() {} +use dioxus::ssr; + +pub static Example: FC<()> = |cx| { + let as_string = use_state(cx, || { + // Currently, SSR is only supported for whole VirtualDOMs + // This is an easy/low hanging fruit to improve upon + let mut dom = VirtualDom::new(SomeApp); + dom.rebuild_in_place().unwrap(); + ssr::render_root(&dom) + }); -static Example: FC<()> = |cx| { cx.render(rsx! { - div { + div { "{as_string}" } + }) +}; +static SomeApp: FC<()> = |cx| { + cx.render(rsx! { + div { style: {background_color: "blue"} + h1 {"Some amazing app or component"} + p {"Things are great"} } }) }; diff --git a/examples/reference/webcomponents.rs b/examples/reference/webcomponents.rs deleted file mode 100644 index e141d6cd7..000000000 --- a/examples/reference/webcomponents.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Example: Web Components -//! ----------------------- -//! -//! Web components are a flavor of html elements that can be user-defined. -//! See: https://www.webcomponents.org for more information. -//! -//! Users who use webcomponents typically don't use Dioxus. However, if you would like to use webcomponents in Dioxus, -//! you can easily create a new custom element with compile-time correct wrappers around the webcomponent. -//! -//! We don't support building new webcomponents with Dioxus, however. -//! - -fn main() {} - -mod dioxus_elements { - use dioxus::prelude::DioxusElement; - - struct custom_element; - impl DioxusElement for custom_element { - const TAG_NAME: &'static str = "custom_element"; - const NAME_SPACE: Option<&'static str> = None; - } - - // Re-export the normal html namespace - pub use dioxus::prelude::dioxus_elements::*; -} diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index ba5ec951e..7e4399800 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None; use baller::Baller; use dioxus::prelude::*; -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { let formatting = "formatting!"; let formatting_tuple = ("a", "b"); let lazy_fmt = format_args!("lazily formatted text"); diff --git a/examples/tailwind.rs b/examples/tailwind.rs index 9b6a186bd..1c71caa7f 100644 --- a/examples/tailwind.rs +++ b/examples/tailwind.rs @@ -12,7 +12,7 @@ fn main() { const STYLE: &str = "body {overflow:hidden;}"; -pub const App: FC<()> = |cx| { +pub static App: FC<()> = |cx| { cx.render(rsx!( div { class: "overflow-hidden" style { "{STYLE}" } @@ -28,7 +28,7 @@ pub const App: FC<()> = |cx| { )) }; -pub const Header: FC<()> = |cx| { +pub static Header: FC<()> = |cx| { cx.render(rsx! { div { header { class: "text-gray-400 bg-gray-900 body-font" @@ -54,7 +54,7 @@ pub const Header: FC<()> = |cx| { }) }; -pub const Hero: FC<()> = |cx| { +pub static Hero: FC<()> = |cx| { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -92,7 +92,7 @@ pub const Hero: FC<()> = |cx| { } }) }; -pub const Entry: FC<()> = |cx| { +pub static Entry: FC<()> = |cx| { // cx.render(rsx! { section{ class: "text-gray-400 bg-gray-900 body-font" @@ -105,7 +105,7 @@ pub const Entry: FC<()> = |cx| { }) }; -pub const StacksIcon: FC<()> = |cx| { +pub static StacksIcon: FC<()> = |cx| { cx.render(rsx!( svg { // xmlns: "http://www.w3.org/2000/svg" @@ -120,7 +120,7 @@ pub const StacksIcon: FC<()> = |cx| { } )) }; -pub const RightArrowIcon: FC<()> = |cx| { +pub static RightArrowIcon: FC<()> = |cx| { cx.render(rsx!( svg { fill: "none" diff --git a/examples/todomvc.rs b/examples/todomvc.rs new file mode 100644 index 000000000..b4a4a2fd8 --- /dev/null +++ b/examples/todomvc.rs @@ -0,0 +1,114 @@ +use dioxus::prelude::*; +use im_rc::HashMap; +use std::rc::Rc; + +fn main() { + #[cfg(feature = "desktop")] + // #[cfg(not(target_arch = "wasm32"))] + dioxus::desktop::launch(App, |c| c); + + #[cfg(feature = "desktop")] + dioxus::web::launch(App, |c| c); +} + +#[derive(PartialEq)] +pub enum FilterState { + All, + Active, + Completed, +} + +#[derive(Debug, PartialEq)] +pub struct TodoItem { + pub id: u32, + pub checked: bool, + pub contents: String, +} + +const STYLE: &str = include_str!("./_examples/todomvc/style.css"); +const App: FC<()> = |cx| { + let draft = use_state(cx, || "".to_string()); + let todos = use_state(cx, || HashMap::>::new()); + let filter = use_state(cx, || FilterState::All); + + let todolist = todos + .iter() + .filter(|(id, item)| match *filter { + FilterState::All => true, + FilterState::Active => !item.checked, + FilterState::Completed => item.checked, + }) + .map(|(id, todo)| { + rsx!(TodoEntry { + key: "{id}", + todo: todo.clone() + }) + }) + .collect::>(); + + let items_left = todolist.len(); + let item_text = match items_left { + 1 => "item", + _ => "items", + }; + + cx.render(rsx! { + div { id: "app" + style {"{STYLE}"} + div { + header { class: "header" + h1 {"todos"} + input { + class: "new-todo" + placeholder: "What needs to be done?" + value: "{draft}" + oninput: move |evt| draft.set(evt.value()) + } + } + {todolist} + {(!todos.is_empty()).then(|| rsx!( + footer { + 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" + 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" }} + } + } + }) +}; + +#[derive(PartialEq, Props)] +pub struct TodoEntryProps { + todo: std::rc::Rc, +} + +pub fn TodoEntry(cx: Context) -> VNode { + let TodoEntryProps { todo } = *cx; + let is_editing = use_state(cx, || false); + let contents = ""; + + cx.render(rsx! ( + li { + "{todo.id}" + input { + class: "toggle" + r#type: "checkbox" + "{todo.checked}" + } + {is_editing.then(|| rsx!{ + input { + value: "{contents}" + } + })} + } + )) +} diff --git a/notes/Parity.md b/notes/Parity.md index 76b1cf992..11655c2e7 100644 --- a/notes/Parity.md +++ b/notes/Parity.md @@ -12,7 +12,7 @@ https://github.com/rustwasm/gloo For example, resize observer would function like this: ```rust -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { let observer = use_resize_observer(); cx.render(rsx!( @@ -29,18 +29,18 @@ For other services, we shell out to gloo. If the gloo service doesn't exist, the | Service | Hook examples | Current Projects | | ---------------------------- | ------------- | ---------------- | -| Fetch | 👀 | Reqwest/surf | -| Local storage (cache) | 👀 | Gloo | -| Persistent storage (IndexDB) | 👀 | 👀 | -| WebSocket | 👀 | Gloo | -| 3D Renderer / WebGL | 👀 | Gloo | -| Web Worker | 👀 | 👀 | -| Router | 👀 | 👀 | -| Notifications | 👀 | 👀 | -| WebRTC Client | 👀 | 👀 | -| Service Workers | 👀 | 👀 | -| Resize Observer | 👀 | 👀 | -| Canvas | 👀 | 👀 | -| Clipboard | 👀 | 👀 | -| Fullscreen | 👀 | 👀 | -| History API | 👀 | 👀 | +| Fetch | 👀 | Reqwest/surf | +| Local storage (cache) | 👀 | Gloo | +| Persistent storage (IndexDB) | 👀 | 👀 | +| WebSocket | 👀 | Gloo | +| 3D Renderer / WebGL | 👀 | Gloo | +| Web Worker | 👀 | 👀 | +| Router | 👀 | 👀 | +| Notifications | 👀 | 👀 | +| WebRTC Client | 👀 | 👀 | +| Service Workers | 👀 | 👀 | +| Resize Observer | 👀 | 👀 | +| Canvas | 👀 | 👀 | +| Clipboard | 👀 | 👀 | +| Fullscreen | 👀 | 👀 | +| History API | 👀 | 👀 | diff --git a/notes/SOLVEDPROBLEMS.md b/notes/SOLVEDPROBLEMS.md index 25febf0c4..ba2a168d1 100644 --- a/notes/SOLVEDPROBLEMS.md +++ b/notes/SOLVEDPROBLEMS.md @@ -153,13 +153,13 @@ Notice that LiveComponent receivers (the client-side interpretation of a LiveCom The `VNodeTree` type is a very special type that allows VNodes to be created using a pluggable allocator. The html! macro creates something that looks like: ```rust -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { html! {

"blah"
} }; // expands to... -static Example: FC<()> = |cx| { +pub static Example: FC<()> = |cx| { // This function converts a Fn(allocator) -> VNode closure to a VNode struct that will later be evaluated. html_macro_to_vnodetree(move |allocator| { let mut node0 = allocator.alloc(VElement::div); diff --git a/packages/core-macro/src/rsx/ambiguous.rs b/packages/core-macro/src/rsx/ambiguous.rs index 002c27bb9..7b894c6e2 100644 --- a/packages/core-macro/src/rsx/ambiguous.rs +++ b/packages/core-macro/src/rsx/ambiguous.rs @@ -39,32 +39,18 @@ impl Parse for AmbiguousElement { } } - // if input.peek(Ident) { - // let name_str = input.fork().parse::().unwrap().to_string(); - // } else { - // } - if let Ok(name) = input.fork().parse::() { let name_str = name.to_string(); - match is_valid_tag(&name_str) { - true => input + let first_char = name_str.chars().next().unwrap(); + if first_char.is_ascii_uppercase() { + input + .parse::() + .map(|c| AmbiguousElement::Component(c)) + } else { + input .parse::() - .map(|c| AmbiguousElement::Element(c)), - false => { - let first_char = name_str.chars().next().unwrap(); - if first_char.is_ascii_uppercase() { - input - .parse::() - .map(|c| AmbiguousElement::Component(c)) - } else { - let name = input.parse::().unwrap(); - Err(Error::new( - name.span(), - "Components must be uppercased, perhaps you mispelled a html tag", - )) - } - } + .map(|c| AmbiguousElement::Element(c)) } } else { if input.peek(LitStr) { diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 99c1e1a40..c3a5c143b 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -96,7 +96,7 @@ pub fn parse_component_body( } content.parse::()?; *manual_props = Some(content.parse::()?); - } else if content.peek(Ident) && content.peek2(Token![:]) { + } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) { if !cfg.allow_fields { return Err(Error::new( content.span(), @@ -184,7 +184,7 @@ impl ToTokens for Component { }; tokens.append_all(quote! { - __cx.virtual_child( + __cx.component( #name, #builder, #key_token, diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs index 5a17ec635..41d39fe9d 100644 --- a/packages/core-macro/src/rsx/element.rs +++ b/packages/core-macro/src/rsx/element.rs @@ -43,11 +43,6 @@ impl Parse for Element { fn parse(stream: ParseStream) -> Result { let name = Ident::parse(stream)?; - // TODO: remove this in favor of the compile-time validation system - if !crate::util::is_valid_tag(&name.to_string()) { - return Err(Error::new(name.span(), "Not a valid Html tag")); - } - // parse the guts let content: ParseBuffer; syn::braced!(content in stream); diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index b12a8f69c..cfe98b9bc 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -4,7 +4,7 @@ fn main() {} // use dioxus_core as dioxus; // use dioxus_core::prelude::*; -// static Example: FC<()> = |cx| { +// pub static Example: FC<()> = |cx| { // let list = (0..10).map(|f| LazyNodes::new(move |f| todo!())); // cx.render(LazyNodes::new(move |cx| { diff --git a/packages/core/examples/contextapi.rs b/packages/core/examples/contextapi.rs index 1e6856ce3..61dc79837 100644 --- a/packages/core/examples/contextapi.rs +++ b/packages/core/examples/contextapi.rs @@ -5,12 +5,8 @@ struct SomeContext { items: Vec, } -struct Props { - name: String, -} - #[allow(unused)] -static Example: FC = |cpt| { +static Example: FC<()> = |cpt| { todo!() // let value = cx.use_context(|c: &SomeContext| c.items.last().unwrap()); diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index dbef3052a..27c735e7a 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -321,7 +321,10 @@ Any function prefixed with "use" should not be called conditionally. /// /// /// - pub fn use_task(&self, task_initializer: Init) -> (&TaskHandle, &Option) + pub fn use_task( + self, + task_initializer: Init, + ) -> (TaskHandle<'src>, &'src Option) where Out: 'static, Fut: Future + 'static, @@ -363,7 +366,7 @@ Any function prefixed with "use" should not be called conditionally. if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() { hook.value = Some(val); } - (&TaskHandle { _p: PhantomData }, &hook.value) + (TaskHandle { _p: PhantomData }, &hook.value) }, |_| {}, ) @@ -463,6 +466,14 @@ impl<'src, P> Context<'src, P> { } } +#[derive(Clone, Copy)] pub struct TaskHandle<'src> { _p: PhantomData<&'src ()>, } + +impl<'src> TaskHandle<'src> { + pub fn toggle(&self) {} + pub fn start(&self) {} + pub fn stop(&self) {} + pub fn restart(&self) {} +} diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index f29425d5c..d090f866f 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -266,7 +266,7 @@ impl<'a> NodeFactory<'a> { } } - pub fn virtual_child

( + pub fn component

( &self, component: FC

, props: P, diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 6e9063b03..901728dd0 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -123,7 +123,14 @@ impl<'a, T: 'static> UseState<'a, T> { let slot = self.inner.wip.clone(); Rc::new(move |new| *slot.borrow_mut() = Some(new)) } + + pub fn for_async(&self) -> AsyncUseState { + AsyncUseState { + wip: self.inner.wip.clone(), + } + } } + impl<'a, T: 'static + ToOwned> UseState<'a, T> { pub fn get_mut(self) -> RefMut<'a, T> { // make sure we get processed @@ -175,9 +182,55 @@ impl<'a, T: Copy + Sub> SubAssign for UseState<'a, T> { } } +/// 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)); + } +} + // enable displaty for the handle impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.inner.current_val) } } + +/// A less ergonmic but still capable form of use_state that's valid for `static lifetime +pub struct AsyncUseState { + wip: Rc>>, +} + +impl AsyncUseState { + pub fn get_mut<'a>(&'a self) -> RefMut<'a, T> { + // make sure we get processed + // self.needs_update(); + + // Bring out the new value, cloning if it we need to + // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this + RefMut::map(self.wip.borrow_mut(), |slot| { + // + slot.as_mut().unwrap() + }) + } +} diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index a08686d3c..9126bed4e 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -924,6 +924,8 @@ builder_constructors! { /// [`

`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div) /// element. /// + /// Part of the HTML namespace. Only works in HTML-compatible renderers + /// /// ## Definition and Usage /// - The
tag defines a division or a section in an HTML document. /// - The
tag is used as a container for HTML elements - which is then styled with CSS or manipulated with JavaScript. @@ -1575,6 +1577,7 @@ builder_constructors! { /// [`