diff --git a/Cargo.toml b/Cargo.toml index 7b8d3e7c3..7a0ae8b11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ split-debuginfo = "unpacked" [dev-dependencies] futures = "0.3.15" +log = "0.4.14" # For the tide ssr examples # async-std = { version="1.9.0", features=["attributes"] } # tide = { version="0.16.0" } diff --git a/examples/README.md b/examples/README.md index 69e03b338..8b8cd0911 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,29 +1,50 @@ # Examples -Most of these examples are run through webview so you don't need the dioxus cli installed to preview the functionality. Anything labeled `_web` will need to be built with the Dioxus CLI to preview features that only a native bundle can handle. +Most of these examples are run through webview so you don't need the dioxus cli installed to preview the functionality. -List of examples: +These examples are fully-fledged micro apps. They can be ran with the `cargo run --example XYZ` -| Example | What it does | -| --------------------------------------- | ------------ | -| [The basics](./basics.rs) | this does | -| [fine grained reactivity](./signals.rs) | this does | -| Global State Management | this does | -| Virtual Refs | this does | -| Inline Styles | this does | -| Conditional Rendering | this does | -| Maps/Iterators | this does | -| Render To string | this does | -| Component Children | this does | -| Function Driven children | this does | -| Memoization | this does | -| Borrowed Data | this does | -| Fragments | this does | -| Null/None Components | this does | -| Spread Pattern for props | this does | -| Controlled Inputs | this does | -| Custom Elements | this does | -| Testing And debugging | this does | -| Asynchronous Data | this does | -| Fiber/Scheduled Rendering | this does | -| CSS Compiled Styles | this does | +| Example | What it does | Status | +| --------------------------------------------------- | ------------------------------------------- | ------ | +| [The basics](./basics.rs) | A few basic examples to preview Dioxus | 🛠 | +| [fine grained reactivity](./signals.rs) | Escape `diffing` by writing values directly | 🛠 | +| [Global State Management](./statemanagement.rs) | Share state between components | 🛠 | +| [Virtual Refs]() | Cross-platform imperative elements | 🛠 | +| [Inline Styles](./inline-styles.rs) | Define styles for elements inline | 🛠 | +| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals | ✅ | + +These examples are not necessarily meant to be run, but rather serve as a reference for the given functionality. + +| Example | What it does | Status | +| --------------------------------------------------- | ----------------------------------------------- | ------ | +| [The basics](./basics.rs) | A few basic examples to preview Dioxus | 🛠 | +| [fine grained reactivity](./signals.rs) | Escape `diffing` by writing values directly | 🛠 | +| [Global State Management](./statemanagement.rs) | Share state between components | 🛠 | +| [Virtual Refs]() | Cross-platform imperative elements | 🛠 | +| [Inline Styles](./inline-styles.rs) | Define styles for elements inline | 🛠 | +| [Conditional Rendering](./conditional-rendering.rs) | Hide/Show elements using conditionals | ✅ | +| [Maps/Iterators](./iterators.rs) | Use iterators in the rsx! macro | 🛠 | +| [Render To string](./tostring.rs) | Render a mounted virtualdom to a string | 🛠 | +| [Component Children](./children.rs) | Pass children into child components | 🛠 | +| [Function Driven children]() | Pass functions to make VNodes | 🛠 | +| [Memoization & Borrowed Data](./memo.rs) | Suppress renders, borrow from parents | ✅ | +| [Fragments](./fragments.rs) | Support root-less element groups | ✅ | +| [Null/None Components](./empty.rs) | Return nothing! | 🛠 | +| [Spread Pattern for props](./spreadpattern.rs) | Manually specify and override props | ✅ | +| [Controlled Inputs](./controlled-inputs.rs) | this does | 🛠 | +| [Custom Elements]() | Define custom elements | 🛠 | +| [Web Components]() | Custom elements to interface with WebComponents | 🛠 | +| [Testing And debugging]() | this does | 🛠 | +| [Asynchronous Data]() | Using suspense to wait for data | 🛠 | +| [Fiber/Scheduled Rendering]() | this does | 🛠 | +| [CSS Compiled Styles]() | this does | 🛠 | +| [Anti-patterns](./antipatterns.rs) | A collection of discouraged patterns | ✅ | +| [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 | diff --git a/examples/listener.rs b/examples/listener.rs deleted file mode 100644 index 0e22f8666..000000000 --- a/examples/listener.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![allow(unused, non_upper_case_globals)] - -use dioxus_core::prelude::*; - -fn main() {} - -static Example: FC<()> = |cx| { - let (name, set_name) = use_state(&cx, || "...?"); - - cx.render(rsx!( - div { - h1 { "Hello, {name}" } - // look ma - we can rsx! and html! together - {["jack", "jill"].iter().map(|f| html!())} - } - )) -}; - -pub fn render<'src, 'a, F: for<'b> FnOnce(&'b NodeFactory<'src>) -> VNode<'src> + 'src + 'a, P>( - cx: &'a Context<'src, P>, - lazy_nodes: LazyNodes<'src, F>, -) -> VNode<'src> { - cx.render(lazy_nodes) -} diff --git a/examples/antipatterns.rs b/examples/reference/antipatterns.rs similarity index 97% rename from examples/antipatterns.rs rename to examples/reference/antipatterns.rs index e562c093b..b07cf22a0 100644 --- a/examples/antipatterns.rs +++ b/examples/reference/antipatterns.rs @@ -21,10 +21,10 @@ fn main() {} /// Antipattern: Iterators without keys /// ----------------------------------- /// -/// This is considered an anti-pattern for performance reasons. Dioxus must diff your current and old layout and must +/// This is considered an anti-pattern for performance reasons. Dioxus will diff your current and old layout and must /// take a slower path if it can't correlate old elements with new elements. Lists are particularly susceptible to the /// "slow" path, so you're strongly encouraged to provide a unique ID stable between renders. Additionally, providing -/// the *wrong* keys is even worse. Keys should be: +/// the *wrong* keys is even worse - props might be assigned to the wrong components! Keys should be: /// - Unique /// - Stable /// - Predictable @@ -39,7 +39,7 @@ static AntipatternNoKeys: FC = |cx| { rsx!(in cx, ul { {cx.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))} }); - // Like this: + // RIGHT: Like this: rsx!(in cx, ul { {cx.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))} }) diff --git a/examples/reference/async.rs b/examples/reference/async.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/async.rs @@ -0,0 +1,10 @@ +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 new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/basics.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/children.rs b/examples/reference/children.rs new file mode 100644 index 000000000..38aee99f1 --- /dev/null +++ b/examples/reference/children.rs @@ -0,0 +1,45 @@ +//! Example: Children +//! ----------------- +//! +//! Dioxus supports passing children in from the parent. These children are allocated in the parent and just forced +//! into the child. Components that pass in children may not be safely memoized, though in practice its rare for a +//! change in a parent to not result in a different set of children. +//! +//! In Dioxus, children can *only be a list*. Unlike React, you cannot pass in functions or arbitrary data. This is +//! partially a limitaiton in having static types, but is rather intentional to encourage the use of attributes where +//! arbitrary child data might normally be used. Check out the `function driven children` example for how to adopt your +//! React pattern for Dioxus' semantics. +//! +//! Dioxus will let you use the `children` method more than once - and it's semantically *okay* - but you'll likely +//! ruin your page if you try to clone elements in this way. Under the hood, Dioxus shares a "mounted ID" for each node, +//! and mounting the same VNode in two places will overwrite the first mounted ID. This will likely lead to dead elements. +//! +//! In the future, this might become a runtime error, so consider it an error today. + +use dioxus::prelude::*; +fn main() {} + +static App: FC<()> = |cx| { + cx.render(rsx! { + div { + Banner { + p { "Some Content1" } + } + Banner { + p { "Some Content2" } + } + } + }) +}; + +static Banner: FC<()> = |cx| { + cx.render(rsx! { + div { + h1 { "This is a great banner!" } + div { class: "content" + {cx.children()} + } + footer { "Wow, what a great footer" } + } + }) +}; diff --git a/examples/reference/conditional_rendering.rs b/examples/reference/conditional_rendering.rs new file mode 100644 index 000000000..3048c0108 --- /dev/null +++ b/examples/reference/conditional_rendering.rs @@ -0,0 +1,92 @@ +//! Example: Conditional Rendering +//! ------------------------------ +//! +//! This example shows how to hide or show elements using conditional rendering. +//! +//! Often times you might want to display a different UI given some sort of condition. This is called "conditonal rendering". +//! In Dioxus, you can perform conditional rendering with optionals or matching. +//! +//! The rsx! and html! macro accepts anything that is `IntoIter`. Options and Results both implement +//! IntoIter for impl VNode, so you can use option/result for conditional rendering. + +use dioxus::prelude::*; + +fn main() {} + +// Convert a boolean conditional into a hide/show +#[derive(PartialEq, Props)] +struct MyProps { + should_show: bool, +} +static Example: FC = |cx| { + cx.render(rsx! { + div { + {cx.should_show.then(|| rsx!{ + h1 { "showing the title!" } + })} + } + }) +}; + +// Convert a boolean conditional into an either/or +// Because rsx! is lazy (produces a closure), we cannot use it in two branch arms. To use it in matching/branching, we +// must render it. +// +// Dioxus will let you render any `LazyNodes` into a `VNode` with `cx.render`. `rsx!` also supports the `in cx` syntax +// which will do essentially the same thing as `cx.render`. +// +// In short: +// `rsx!(in cx, ...)` is shorthand for `cx.render(rsx!(...))` +#[derive(PartialEq, Props)] +struct MyProps1 { + should_show: bool, +} +static Example1: FC = |cx| { + cx.render(rsx! { + div { + // With matching + {match cx.should_show { + true => cx.render(rsx!(div {"it is true!"})), + false => rsx!(in cx, div {"it is false!"}), + }} + + // or with just regular conditions + {if cx.should_show { + rsx!(in cx, div {"it is true!"}) + } else { + rsx!(in cx, div {"it is false!"}) + }} + + // or with optional chaining + { + cx.should_show + .then(|| rsx!(in cx, div {"it is false!"})) + .unwrap_or_else(|| rsx!(in cx, div {"it is false!"})) + } + } + }) +}; + +/// Matching can be expanded + +#[derive(PartialEq)] +enum Color { + Green, + Yellow, + Red, +} +#[derive(PartialEq, Props)] +struct MyProps2 { + color: Color, +} +static Example2: FC = |cx| { + cx.render(rsx! { + div { + {match cx.color { + Color::Green => rsx!(in cx, div {"it is Green!"}), + Color::Yellow => rsx!(in cx, div {"it is Yellow!"}), + Color::Red => rsx!(in cx, div {"it is Red!"}), + }} + } + }) +}; diff --git a/examples/reference/controlled_inputs.rs b/examples/reference/controlled_inputs.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/controlled_inputs.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/custom_elements.rs b/examples/reference/custom_elements.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/custom_elements.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/empty.rs b/examples/reference/empty.rs new file mode 100644 index 000000000..95ef10506 --- /dev/null +++ b/examples/reference/empty.rs @@ -0,0 +1,15 @@ +//! Example: Null/None Children +//! --------------------------- +//! +//! This is a simple pattern that allows you to return no elements! + +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/fiber.rs b/examples/reference/fiber.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/fiber.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/fragments.rs b/examples/reference/fragments.rs new file mode 100644 index 000000000..848c5f4e8 --- /dev/null +++ b/examples/reference/fragments.rs @@ -0,0 +1,45 @@ +//! Example: Fragments +//! ------------------ +//! +//! Dioxus can return multiple elements without a container through the use of the VNode called a "Fragment". Fragments do not +//! have a mounted root and are inserted inline with their siblings. There are three ways of creating fragments as outlined +//! in the examples below: +//! - By returning multiple elements in Rsx! +//! - By using the `Fragment` component +//! - 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| { + cx.render(rsx! { + h1 { } + h2 { } + h3 { } + // {} + // "hello world" + }) +}; + +// Using the Fragment component +static Example2: FC<()> = |cx| { + cx.render(rsx! { + Fragment { + div {} + div {} + "asd" + } + }) +}; + +// Using the `fragment` method on the NodeFactory +static Example3: FC<()> = |cx| { + cx.render(LazyNodes::new(move |fac: &NodeFactory| { + fac.fragment_builder(None, |list| { + list.add_child(fac.text(format_args!("A"))) + .add_child(fac.text(format_args!("B"))) + .finish() + }) + })) +}; diff --git a/examples/reference/global_css.rs b/examples/reference/global_css.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/global_css.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/inline_styles.rs b/examples/reference/inline_styles.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/inline_styles.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/iterators.rs b/examples/reference/iterators.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/iterators.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/listener.rs b/examples/reference/listener.rs new file mode 100644 index 000000000..10a32cde1 --- /dev/null +++ b/examples/reference/listener.rs @@ -0,0 +1,50 @@ +//! Example: Listeners +//! ------------------ +//! +//! This example demonstrates the various ways listeners may be used in Dioxus. +//! Listeners may be at most `FnMut` - but not `FnOnce`. +//! Listeners may borrow the lifetime of cx (children and hooks), but not of local (stack) data. + +use dioxus::prelude::*; + +fn main() {} + +/// We can use `set_name` in multiple closures; the closures automatically *copy* the reference to set_name. +static ButtonList: FC<()> = |cx| { + let (name, set_name) = use_state(&cx, || "...?"); + + let names = ["jack", "jill", "john", "jane"] + .iter() + .map(move |n| rsx!(button { onclick: move |_| set_name(n), "{n}" })); + + cx.render(rsx!( + div { + h1 { "Hello, {name}" } + {names} + } + )) +}; + +/// This shows how listeners may be without a visible change in the display. +/// Check the console. +static NonUpdatingEvents: FC<()> = |cx| { + rsx!(in cx, div { + button { + onclick: move |_| log::info!("Did not cause any updates!") + "Click me to log!" + } + }) +}; + +static DisablePropogation: FC<()> = |cx| { + rsx!(in cx, + div { + onclick: move |_| log::info!("event propogated to the div!") + button { + onclick: move |evt| { + log::info!("Button will allow propogation"); + } + } + } + ) +}; diff --git a/examples/reference/memo.rs b/examples/reference/memo.rs new file mode 100644 index 000000000..b8cb648a1 --- /dev/null +++ b/examples/reference/memo.rs @@ -0,0 +1,85 @@ +//! Example: Memoization +//! -------------------- +//! +//! This example showcases how memoization works in Dioxus. +//! +//! Memoization is the process in which Dioxus skips diffing child components if their props don't change. +//! In React, components are never memoized unless wrapped in `memo` or configured with `shouldComponentUpdate`. +//! +//! Due to the safety guarantees of Rust, we can automatically memoize components in some circumstances. Whenever a +//! component's properties are valid for the `'static` lifetime, Dioxus will automatically compare the props before +//! diffing the component. If the props don't change (according to PartialEq), the component will not be re-rendered. +//! +//! However, if the props use some generics or borrow from their parent, then Dioxus can't safely supress updates, +//! and is forced to render the child. If you think that this behavior is wrong for your usecase, you can implement +//! the memo method yourself, but beware, doing so is UNSAFE and may cause issues if you do it wrong. +//! +//! If you want to gain that little bit extra performance, consider using global state management, signals, or +//! 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| { + cx.render(rsx! { + div { "100% memoized!" } + }) +}; + +// These props do not borrow any content, and therefore can be safely memoized. +// 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 { + name: String, +} + +static Example1: FC = |cx| { + cx.render(rsx! { + div { "100% memoized! {cx.name}" } + }) +}; + +// These props do not borrow any content, and therefore can be safely memoized. +// 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 { + name: std::rc::Rc, +} + +static Example2: FC = |cx| { + cx.render(rsx! { + div { "100% memoized! {cx.name}" } + }) +}; + +// These props *do* borrow any content, and therefore cannot be safely memoized!. +#[derive(PartialEq, Props)] +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 +// not exactly be what you want +fn Example3<'a>(cx: Context<'a, MyProps3<'a>>) -> VNode { + cx.render(rsx! { + div { "Not memoized! {cx.name}" } + }) +} + +// 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> { + onhandle: &'a dyn Fn(), +} + +// We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime. +fn Example4<'a>(cx: Context<'a, MyProps4<'a>>) -> VNode { + cx.render(rsx! { + div { "Not memoized!", onclick: move |_| (cx.onhandle)() } + }) +} diff --git a/examples/reference/mod.rs b/examples/reference/mod.rs new file mode 100644 index 000000000..f08cae033 --- /dev/null +++ b/examples/reference/mod.rs @@ -0,0 +1,2 @@ +mod iterator; +mod memo; diff --git a/examples/reference/noderefs.rs b/examples/reference/noderefs.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/noderefs.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/signals.rs b/examples/reference/signals.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/signals.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/spreadpattern.rs b/examples/reference/spreadpattern.rs new file mode 100644 index 000000000..4810e3af8 --- /dev/null +++ b/examples/reference/spreadpattern.rs @@ -0,0 +1,40 @@ +//! Example: Spread pattern for Components +//! -------------------------------------- +//! +//! Dioxus supports the "spread" pattern for manually building a components properties. This is useful when props +//! are passed down from a parent, or it's more ergonomic to construct props from outside the rsx! macro. +//! +//! To use the spread pattern, simply pass ".." followed by a Rust epxression. This pattern also supports overriding +//! 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| { + let props = MyProps { + count: 0, + live: true, + name: "Dioxus", + }; + cx.render(rsx! { + Example { ..props, count: 10, div {"child"} } + }) +}; + +#[derive(PartialEq, Props)] +struct MyProps { + count: u32, + live: bool, + name: &'static str, +} + +static Example: FC = |cx| { + cx.render(rsx! { + div { + h1 { "Hello, {cx.name}"} + h3 {"Are we alive? {cx.live}"} + p {"Count is {cx.count}"} + { cx.children() } + } + }) +}; diff --git a/examples/reference/statemanagement.rs b/examples/reference/statemanagement.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/statemanagement.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/suspense.rs b/examples/reference/suspense.rs similarity index 100% rename from examples/suspense.rs rename to examples/reference/suspense.rs diff --git a/examples/reference/testing.rs b/examples/reference/testing.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/testing.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/tostring.rs b/examples/reference/tostring.rs new file mode 100644 index 000000000..096f0d9e0 --- /dev/null +++ b/examples/reference/tostring.rs @@ -0,0 +1,10 @@ +use dioxus::prelude::*; +fn main() {} + +static Example: FC<()> = |cx| { + cx.render(rsx! { + div { + + } + }) +}; diff --git a/examples/reference/webcomponents.rs b/examples/reference/webcomponents.rs new file mode 100644 index 000000000..c27f8ff69 --- /dev/null +++ b/examples/reference/webcomponents.rs @@ -0,0 +1,21 @@ +//! 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. +//! + +use dioxus::{builder::ElementBuilder, prelude::NodeFactory}; + +fn main() {} + +// TODO +struct MyEle<'a, 'b> { + el: ElementBuilder<'a, 'b>, + fac: &'b NodeFactory<'a>, +} diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 27614fc56..f5c811463 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -120,7 +120,7 @@ static Example: FC<()> = |cx| { {if true { rsx!(in cx, h1 {"Top text"}) } else { - cx.render(rsx!( h1 {"Bottom text"})) + rsx!(in cx, h1 {"Bottom text"}) }} // returning "None" is a bit noisy... but rare in practice @@ -168,7 +168,13 @@ static Example: FC<()> = |cx| { rsx!(Taller { ..props }) }} - // Can take children + // Spreading can also be overridden manually + Taller { + ..TallerProps { a: "ballin!" } + a: "not ballin!" + } + + // Can take children too! Taller { a: "asd", div {"hello world!"} } } }) diff --git a/examples/showcase.rs b/examples/showcase.rs index 3d128893e..7fb2db260 100644 --- a/examples/showcase.rs +++ b/examples/showcase.rs @@ -1,26 +1,31 @@ -//! A Showcase of all the useful examples -//! -//! +//! Example: Reference Showcase +//! --------------------------- //! +//! This example provides a cute interface for browsing the reference examples. -fn main() { - use_css_consumer(&cx, "mystyle"); - - // at the global head of the app - use_css_provider(&cx, |cfg| {}); - use_recoil_provider(&cx, |cfg| {}); - - let recoil = use_recoil_api(&cx, |_| {}); - use_websocket_connection(&cx, move |cfg| { - cfg.on_receive(move |event| match event.data::() { - Ok(msg) => match msg { - a => recoil.set(&ATOM, 10), - c => recoil.set(&ATOM, 20), - _ => {} - }, - Err(e) => {} - }); - cfg.on_close(move |event| {}); - cfg.on_open(move |event| {}); - }); +mod reference { + mod antipatterns; + mod basics; + mod children; + mod conditional_rendering; + mod controlled_inputs; + mod custom_elements; + mod empty; + mod fiber; + 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 testing; + mod tostring; + mod webcomponents; } + +fn main() {} diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 068a0bb86..24fc74d8e 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -80,33 +80,46 @@ impl ToTokens for Component { fn to_tokens(&self, tokens: &mut TokenStream2) { let name = &self.name; - let using_manual_override = self.manual_props.is_some(); - - let mut builder = { - match &self.manual_props { - Some(manual_props) => quote! { #manual_props }, - None => quote! { fc_to_builder(#name) }, - } - }; - let mut has_key = None; - for field in &self.body { - if field.name.to_string() == "key" { - has_key = Some(field); - } else { - match using_manual_override { - true => panic!("Currently we don't support manual props and prop fields. Choose either manual props or prop fields"), - false => builder.append_all(quote! {#field}), + let builder = match &self.manual_props { + Some(manual_props) => { + let mut toks = quote! { + let mut __manual_props = #manual_props; + }; + for field in &self.body { + if field.name.to_string() == "key" { + has_key = Some(field); + } else { + let name = &field.name; + let val = &field.content; + toks.append_all(quote! { + __manual_props.#name = #val; + }); + } } + toks.append_all(quote! { + __manual_props + }); + quote! {{ + #toks + }} } - } - - if !using_manual_override { - builder.append_all(quote! { - .build() - }); - } + None => { + let mut toks = quote! { fc_to_builder(#name) }; + for field in &self.body { + if field.name.to_string() == "key" { + has_key = Some(field); + } else { + toks.append_all(quote! {#field}) + } + } + toks.append_all(quote! { + .build() + }); + toks + } + }; let key_token = match has_key { Some(field) => { diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 931a53d9e..4002c183d 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -4,7 +4,7 @@ //! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might //! be heavy or need to interact through FFI, so the events themselves are designed to be lazy. -use std::rc::Rc; +use std::{ops::Deref, rc::Rc}; use crate::{innerlude::ScopeIdx, virtual_dom::RealDomNode}; @@ -28,21 +28,21 @@ impl EventTrigger { #[derive(Debug)] pub enum VirtualEvent { // Real events - ClipboardEvent(Rc), - CompositionEvent(Rc), - KeyboardEvent(Rc), - FocusEvent(Rc), - FormEvent(Rc), - SelectionEvent(Rc), - TouchEvent(Rc), - UIEvent(Rc), - WheelEvent(Rc), - MediaEvent(Rc), - AnimationEvent(Rc), - TransitionEvent(Rc), - ToggleEvent(Rc), - MouseEvent(Rc), - PointerEvent(Rc), + ClipboardEvent(on::ClipboardEvent), + CompositionEvent(on::CompositionEvent), + KeyboardEvent(on::KeyboardEvent), + FocusEvent(on::FocusEvent), + FormEvent(on::FormEvent), + SelectionEvent(on::SelectionEvent), + TouchEvent(on::TouchEvent), + UIEvent(on::UIEvent), + WheelEvent(on::WheelEvent), + MediaEvent(on::MediaEvent), + AnimationEvent(on::AnimationEvent), + TransitionEvent(on::TransitionEvent), + ToggleEvent(on::ToggleEvent), + MouseEvent(on::MouseEvent), + PointerEvent(on::PointerEvent), // Whenever a task is ready (complete) Dioxus produces this "FiberEvent" FiberEvent { task_id: u16 }, @@ -75,12 +75,21 @@ pub mod on { use super::VirtualEvent; macro_rules! event_directory { - ( $( $eventdata:ident: [ $( $name:ident )* ]; )* ) => { + ( $( $eventdata:ident($wrapper:ident): [ $( $name:ident )* ]; )* ) => { $( + #[derive(Debug)] + pub struct $wrapper(Rc); + impl Deref for $wrapper { + type Target = Rc; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + $( pub fn $name<'a>( c: &'_ NodeFactory<'a>, - callback: impl Fn(Rc) + 'a, + callback: impl Fn($wrapper) + 'a, ) -> Listener<'a> { let bump = &c.bump(); Listener { @@ -88,7 +97,7 @@ pub mod on { mounted_node: bump.alloc(Cell::new(RealDomNode::empty())), scope: c.scope_ref.arena_idx, callback: bump.alloc(move |evt: VirtualEvent| match evt { - VirtualEvent::$eventdata(event) => callback(event), + VirtualEvent::$wrapper(event) => callback(event), _ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!") }), } @@ -99,36 +108,36 @@ pub mod on { } event_directory! { - ClipboardEvent: [copy cut paste]; - CompositionEvent: [compositionend compositionstart compositionupdate]; - KeyboardEvent: [keydown keypress keyup]; - FocusEvent: [focus blur]; - FormEvent: [change input invalid reset submit]; - MouseEvent: [ + ClipboardEventInner(ClipboardEvent): [copy cut paste]; + CompositionEventInner(CompositionEvent): [compositionend compositionstart compositionupdate]; + KeyboardEventInner(KeyboardEvent): [keydown keypress keyup]; + FocusEventInner(FocusEvent): [focus blur]; + FormEventInner(FormEvent): [change input invalid reset submit]; + MouseEventInner(MouseEvent): [ click contextmenu doubleclick drag dragend dragenter dragexit dragleave dragover dragstart drop mousedown mouseenter mouseleave mousemove mouseout mouseover mouseup ]; - PointerEvent: [ + PointerEventInner(PointerEvent): [ pointerdown pointermove pointerup pointercancel gotpointercapture lostpointercapture pointerenter pointerleave pointerover pointerout ]; - SelectionEvent: [select]; - TouchEvent: [touchcancel touchend touchmove touchstart]; - UIEvent: [scroll]; - WheelEvent: [wheel]; - MediaEvent: [ + SelectionEventInner(SelectionEvent): [select]; + TouchEventInner(TouchEvent): [touchcancel touchend touchmove touchstart]; + UIEventInner(UIEvent): [scroll]; + WheelEventInner(WheelEvent): [wheel]; + MediaEventInner(MediaEvent): [ abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting ]; - AnimationEvent: [animationstart animationend animationiteration]; - TransitionEvent: [transitionend]; - ToggleEvent: [toggle]; + AnimationEventInner(AnimationEvent): [animationstart animationend animationiteration]; + TransitionEventInner(TransitionEvent): [transitionend]; + ToggleEventInner(ToggleEvent): [toggle]; } - trait GenericEvent { + pub trait GenericEventInner { /// Returns whether or not a specific event is a bubbling event fn bubbles(&self) -> bool; /// Sets or returns whether the event should propagate up the hierarchy or not @@ -159,15 +168,15 @@ pub mod on { fn time_stamp(&self) -> usize; } - pub trait ClipboardEvent: Debug { + pub trait ClipboardEventInner: Debug + GenericEventInner { // DOMDataTransfer clipboardData } - pub trait CompositionEvent: Debug { + pub trait CompositionEventInner: Debug { fn data(&self) -> String; } - pub trait KeyboardEvent: Debug { + pub trait KeyboardEventInner: Debug { fn char_code(&self) -> usize; fn ctrl_key(&self) -> bool; fn key(&self) -> String; @@ -181,15 +190,15 @@ pub mod on { fn get_modifier_state(&self, key_code: usize) -> bool; } - pub trait FocusEvent: Debug { - /* DOMEventTarget relatedTarget */ + pub trait FocusEventInner: Debug { + /* DOMEventInnerTarget relatedTarget */ } - pub trait FormEvent: Debug { + pub trait FormEventInner: Debug { fn value(&self) -> String; } - pub trait MouseEvent: Debug { + pub trait MouseEventInner: Debug { fn alt_key(&self) -> bool; fn button(&self) -> i16; fn buttons(&self) -> u16; @@ -205,7 +214,7 @@ pub mod on { fn get_modifier_state(&self, key_code: &str) -> bool; } - pub trait PointerEvent: Debug { + pub trait PointerEventInner: Debug { // Mouse only fn alt_key(&self) -> bool; fn button(&self) -> usize; @@ -232,9 +241,9 @@ pub mod on { fn is_primary(&self) -> bool; } - pub trait SelectionEvent: Debug {} + pub trait SelectionEventInner: Debug {} - pub trait TouchEvent: Debug { + pub trait TouchEventInner: Debug { fn alt_key(&self) -> bool; fn ctrl_key(&self) -> bool; fn meta_key(&self) -> bool; @@ -245,37 +254,37 @@ pub mod on { // touches: DOMTouchList, } - pub trait UIEvent: Debug { + pub trait UIEventInner: Debug { // DOMAbstractView view fn detail(&self) -> i32; } - pub trait WheelEvent: Debug { + pub trait WheelEventInner: Debug { fn delta_mode(&self) -> i32; fn delta_x(&self) -> i32; fn delta_y(&self) -> i32; fn delta_z(&self) -> i32; } - pub trait MediaEvent: Debug {} + pub trait MediaEventInner: Debug {} - pub trait ImageEvent: Debug { + pub trait ImageEventInner: Debug { // load error } - pub trait AnimationEvent: Debug { + pub trait AnimationEventInner: Debug { fn animation_name(&self) -> String; fn pseudo_element(&self) -> String; fn elapsed_time(&self) -> f32; } - pub trait TransitionEvent: Debug { + pub trait TransitionEventInner: Debug { fn property_name(&self) -> String; fn pseudo_element(&self) -> String; fn elapsed_time(&self) -> f32; } - pub trait ToggleEvent: Debug {} + pub trait ToggleEventInner: Debug {} } mod tests { diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index dc467ba4f..619d860d0 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -768,6 +768,25 @@ impl<'a> NodeFactory<'a> { > { ElementBuilder::new(self, tag_name) } + + pub fn child_list(&self) -> ChildrenList { + ChildrenList::new(&self) + } + + pub fn fragment_builder<'b>( + &'b self, + key: Option<&'a str>, + builder: impl FnOnce(ChildrenList<'a, 'b>) -> &'a [VNode<'a>], + ) -> VNode<'a> { + self.fragment(builder(ChildrenList::new(&self)), key) + } + + pub fn fragment(&self, children: &'a [VNode<'a>], key: Option<&'a str>) -> VNode<'a> { + VNode::Fragment(self.bump().alloc(VFragment { + children, + key: NodeKey::new_opt(key), + })) + } } use std::fmt::Debug; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 411fa8a1d..a896236ea 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -297,6 +297,11 @@ impl<'a> NodeKey<'a> { pub fn new(key: &'a str) -> Self { NodeKey(Some(key)) } + + #[inline] + pub fn new_opt(key: Option<&'a str>) -> Self { + NodeKey(key) + } } // ==============================