Merge pull request #13 from jkelleyrtp/jk/none-handling

feat: amazingly awesome error handling
This commit is contained in:
Jonathan Kelley 2021-07-18 12:41:49 -04:00 committed by GitHub
commit e86b031c93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 248 additions and 1219 deletions

View file

@ -49,7 +49,7 @@
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
```rust
fn App(cx: Context<()>) -> VNode {
fn App(cx: Context<()>) -> DomTree {
let mut count = use_state(cx, || 0);
cx.render(rsx! {
@ -147,7 +147,7 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
### Phase 1: The Basics
| Feature | Dioxus | React | Notes for Dioxus |
| ----------------------- | ------ | ----- | ----------------------------------------------------------- |
| ------------------------- | ------ | ----- | ----------------------------------------------------------- |
| Conditional Rendering | ✅ | ✅ | if/then to hide/show component |
| Map, Iterator | ✅ | ✅ | map/filter/reduce to produce rsx! |
| Keyed Components | ✅ | ✅ | advanced diffing with keys |
@ -164,6 +164,7 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
| CSS/Inline Styles | ✅ | ✅ | syntax for inline styles/attribute groups |
| Custom elements | ✅ | ✅ | Define new element primitives |
| Suspense | ✅ | ✅ | schedule future render from future/promise |
| Integrated error handling | ✅ | ✅ | Gracefully handle errors with ? syntax |
| Effects | 🛠 | ✅ | Run effects after a component has been committed to render |
| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events |
| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] |

View file

@ -82,7 +82,7 @@ fn main() {
dioxus::web::start(App)
}
fn App(cx: Context<()>) -> VNode {
fn App(cx: Context<()>) -> DomTree {
cx.render(rsx! {
div { "Hello, world!" }
})
@ -108,7 +108,7 @@ fn main() {
Finally, our app. Every component in Dioxus is a function that takes in a `Context` object and returns a `VNode`.
```rust
fn App(cx: Context<()>) -> VNode {
fn App(cx: Context<()>) -> DomTree {
cx.render(rsx! {
div { "Hello, world!" }
})

View file

@ -14,7 +14,7 @@ You'll want to write RSX where you can, and in a future release we'll have a too
#[derive(PartialEq, Props)]
struct ExampleProps { name: &str, pending: bool, count: i32 }
fn Example(cx: Context<ExampleProps> ) -> VNode {
fn Example(cx: Context<ExampleProps> ) -> DomTree {
let ExampleProps { name, pending, count } = cx.props;
cx.render(html! {
<div>
@ -35,7 +35,7 @@ The Dioxus VSCode extension will eventually provide a macro to convert a selecti
It's also a bit easier on the eyes 🙂 than HTML.
```rust
fn Example(cx: Context<ExampleProps>) -> VNode {
fn Example(cx: Context<ExampleProps>) -> DomTree {
cx.render(rsx! {
div {
// cx derefs to props so you can access fields directly

View file

@ -1,5 +1,5 @@
```rust
fn Example(cx: &mut Context<()>) -> VNode {
fn Example(cx: &mut Context<()>) -> DomTree {
let service = use_combubulator(cx);
let Status { name, pending, count } = service.info();
html! {

View file

@ -5,19 +5,19 @@
// Only one context can be associated with any given component
// This is known as "exposed state". Children can access this context,
// but will not be automatically subscribed.
fn ContextCreate(cx: &mut Context<()>) -> VNode {
fn ContextCreate(cx: &mut Context<()>) -> DomTree {
let context = cx.set_context(|| CustomContext::new());
html! { <> {cx.children()} </> }
}
fn ContextRead(cx: &mut Context<()>) -> VNode {
fn ContextRead(cx: &mut Context<()>) -> DomTree {
// Panics if context is not available
let some_cx = cx.get_context::<CustomContext>();
let text = some_cx.select("some_selector");
html! { <div> "{text}" </div> }
}
fn Subscription(cx: &mut Context<()>) -> VNode {
fn Subscription(cx: &mut Context<()>) -> DomTree {
// Open a "port" on the component for actions to trigger a re-evaluation
let subscription = cx.new_subscription();

View file

@ -3,7 +3,7 @@
Yew subscriptions are used to schedule update for components into the future. The `Context` object can create subscriptions:
```rust
fn Component(cx: Component<()>) -> VNode {
fn Component(cx: Component<()>) -> DomTree {
let update = cx.schedule();
// Now, when the subscription is called, the component will be re-evaluted

View file

@ -62,7 +62,7 @@ async fn ExampleLoader(cx: Context<()>) -> Vnode {
```
```rust
async fn Example(cx: Context<()>) -> VNode {
async fn Example(cx: Context<()>) -> DomTree {
// Diff this set between the last set
// Check if we have any outstanding tasks?
//

View file

@ -11,7 +11,7 @@ https://dmitripavlutin.com/use-react-memo-wisely/
This behavior is defined as an implicit attribute to user components. When in React land you might wrap a component is `react.memo`, Dioxus components are automatically memoized via an implicit attribute. You can manually configure this behavior on any component with "nomemo" to disable memoization.
```rust
fn test() -> VNode {
fn test() -> DomTree {
html! {
<>
<SomeComponent nomemo />
@ -41,7 +41,7 @@ static TestComponent: FC<{ name: String }> = |cx| html! { <div> "Hello {name}" <
Take a component likes this:
```rust
fn test(cx: Context<()>) -> VNode {
fn test(cx: Context<()>) -> DomTree {
let Bundle { alpha, beta, gamma } = use_context::<SomeContext>(cx);
html! {
<div>

View file

@ -11,7 +11,7 @@ By default, Dioxus will only try to diff subtrees of components with dynamic con
Your component today might look something like this:
```rust
fn Comp(cx: Context<()>) -> VNode {
fn Comp(cx: Context<()>) -> DomTree {
let (title, set_title) = use_state(cx, || "Title".to_string());
cx.render(rsx!{
input {
@ -25,7 +25,7 @@ fn Comp(cx: Context<()>) -> VNode {
This component is fairly straightforward - the input updates its own value on every change. However, every call to set_title will re-render the component. If we add a large list, then every time we update the title input, Dioxus will need to diff the entire list, over, and over, and over. This is **a lot** of wasted clock-cycles!
```rust
fn Comp(cx: Context<()>) -> VNode {
fn Comp(cx: Context<()>) -> DomTree {
let (title, set_title) = use_state(cx, || "Title".to_string());
cx.render(rsx!{
div {
@ -48,7 +48,7 @@ Many experienced React developers will just say "this is bad design" - but we co
We can use signals to generate a two-way binding between data and the input box. Our text input is now just a two-line component!
```rust
fn Comp(cx: Context<()>) -> VNode {
fn Comp(cx: Context<()>) -> DomTree {
let mut title = use_signal(&cx, || String::from("Title"));
cx.render(rsx!(input { value: title }))
}
@ -57,7 +57,7 @@ fn Comp(cx: Context<()>) -> VNode {
For a slightly more interesting example, this component calculates the sum between two numbers, but totally skips the diffing process.
```rust
fn Calculator(cx: Context<()>) -> VNode {
fn Calculator(cx: Context<()>) -> DomTree {
let mut a = use_signal(&cx, || 0);
let mut b = use_signal(&cx, || 0);
let mut c = a + b;

View file

@ -8,7 +8,7 @@ struct MyProps {
name: String
}
fn Example(cx: Context<MyProps>) -> VNode {
fn Example(cx: Context<MyProps>) -> DomTree {
cx.render(html! {
<div> "Hello {cx.name}!" </div>
})

View file

@ -3,7 +3,7 @@
In Dioxus, VNodes are asynchronous and can their rendering can be paused at any time by awaiting a future. Hooks can combine this functionality with the Context and Subscription APIs to craft dynamic and efficient user experiences.
```rust
fn user_data(cx: Context<()>) -> VNode {
fn user_data(cx: Context<()>) -> DomTree {
// Register this future as a task
use_suspense(cx, async {
// Continue on with the component as usual, waiting for data to arrive

View file

@ -5,7 +5,7 @@ With the Context, Subscription, and Asynchronous APIs, we've built Dioxus Livevi
These set of features are still experimental. Currently, we're still working on making these components more ergonomic
```rust
fn live_component(cx: &Context<()>) -> VNode {
fn live_component(cx: &Context<()>) -> DomTree {
use_live_component(
cx,
// Rendered via the client

View file

@ -8,7 +8,7 @@ struct MyProps {
name: String
}
fn Example(cx: Context<MyProps>) -> VNode {
fn Example(cx: Context<MyProps>) -> DomTree {
html! { <div> "Hello {cx.cx.name}!" </div> }
}
```
@ -18,7 +18,7 @@ Here, the `Context` object is used to access hook state, create subscriptions, a
```rust
// A very terse component!
#[fc]
fn Example(cx: Context, name: String) -> VNode {
fn Example(cx: Context, name: String) -> DomTree {
html! { <div> "Hello {name}!" </div> }
}

View file

@ -45,7 +45,7 @@ struct IncrementerProps<'a> {
onclick: &'a dyn Fn(MouseEvent),
}
fn C1<'a, 'b>(cx: Context<'a, IncrementerProps<'b>>) -> VNode<'a> {
fn C1<'a, 'b>(cx: Context<'a, IncrementerProps<'b>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"

View file

@ -160,7 +160,7 @@ struct CalculatorKeyProps<'a> {
onclick: &'a dyn Fn(MouseEvent),
}
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "calculator-key {cx.name}"
@ -175,7 +175,7 @@ struct CalculatorDisplayProps<'a> {
val: &'a str,
}
fn CalculatorDisplay<'a>(cx: Context<'a, CalculatorDisplayProps>) -> VNode<'a> {
fn CalculatorDisplay<'a>(cx: Context<'a, CalculatorDisplayProps>) -> DomTree<'a> {
use separator::Separatable;
// Todo, add float support to the num-format crate
let formatted = cx.val.parse::<f64>().unwrap().separated_string();

View file

@ -52,7 +52,7 @@ struct ButtonProps {
id: u8,
}
fn CustomButton(cx: Context<ButtonProps>) -> VNode {
fn CustomButton(cx: Context<ButtonProps>) -> DomTree {
let names = cx.use_context::<CustomContext>();
let name = names.0[cx.id as usize];

View file

@ -11,7 +11,7 @@ fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(CustomA))
}
fn CustomA(cx: Context<()>) -> VNode {
fn CustomA(cx: Context<()>) -> DomTree {
let (val, set_val) = use_state_classic(cx, || "abcdef".to_string() as String);
cx.render(rsx! {
@ -43,7 +43,7 @@ mod components {
val: String,
}
pub fn CustomB(cx: Context<PropsB>) -> VNode {
pub fn CustomB(cx: Context<PropsB>) -> DomTree {
let (first, last) = cx.val.split_at(3);
cx.render(rsx! {
div {
@ -64,7 +64,7 @@ mod components {
val: String,
}
fn CustomC(cx: Context<PropsC>) -> VNode {
fn CustomC(cx: Context<PropsC>) -> DomTree {
cx.render(rsx! {
div {
class: "m-8"

View file

@ -9,7 +9,7 @@ fn main() {
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App))
}
fn App(cx: Context<()>) -> VNode {
fn App(cx: Context<()>) -> DomTree {
cx.render(rsx! {
div { class: "dark:bg-gray-800 bg-white relative h-screen"
NavBar {}
@ -18,7 +18,7 @@ fn App(cx: Context<()>) -> VNode {
})
}
fn NavBar(cx: Context<()>) -> VNode {
fn NavBar(cx: Context<()>) -> DomTree {
cx.render(rsx!{
header { class: "h-24 sm:h-32 flex items-center z-30 w-full"
div { class: "container mx-auto px-6 flex items-center justify-between"
@ -58,7 +58,7 @@ fn NavBar(cx: Context<()>) -> VNode {
})
}
fn Landing(cx: Context<()>) -> VNode {
fn Landing(cx: Context<()>) -> DomTree {
cx.render(rsx!{
div { class: "bg-white dark:bg-gray-800 flex relative z-20 items-center"
div { class: "container mx-auto px-6 flex flex-col justify-between items-center relative py-8"

View file

@ -9,7 +9,7 @@ fn main() {
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
fn App(cx: Context<()>) -> VNode {
fn App(cx: Context<()>) -> DomTree {
let cansee = use_state(cx, || false);
rsx! { in cx,
div {
@ -22,7 +22,7 @@ fn App(cx: Context<()>) -> VNode {
}
}
fn Child(cx: Context<()>) -> VNode {
fn Child(cx: Context<()>) -> DomTree {
rsx! { in cx,
section { class: "py-6 bg-coolGray-100 text-coolGray-900"
div { class: "container mx-auto flex flex-col items-center justify-center p-4 space-y-8 md:p-10 md:px-24 xl:px-48"

View file

@ -6,7 +6,7 @@ fn main() {
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App))
}
fn App(cx: Context<()>) -> VNode {
fn App(cx: Context<()>) -> DomTree {
cx.render(rsx! {
div { "Hello, world!" }
})

View file

@ -132,7 +132,7 @@ static App: FC<()> = |cx| {
))
};
pub fn FilterToggles(cx: Context<()>) -> VNode {
pub fn FilterToggles(cx: Context<()>) -> DomTree {
// let reducer = recoil::use_callback(&cx, || ());
// let items_left = recoil::use_atom_family(&cx, &TODOS, uuid::Uuid::new_v4());

View file

@ -11,7 +11,7 @@ fn main() {
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(JonsFavoriteCustomApp));
}
fn JonsFavoriteCustomApp(cx: Context<()>) -> VNode {
fn JonsFavoriteCustomApp(cx: Context<()>) -> DomTree {
let items = (0..20).map(|f| {
rsx! {
li {"{f}"}

View file

@ -78,7 +78,7 @@ struct CalculatorKeyProps<'a> {
onclick: &'a dyn Fn(MouseEvent),
}
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "calculator-key {cx.name}"

View file

@ -5,7 +5,7 @@
// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
// }
// fn Example(cx: Context, props: ()) -> VNode {
// fn Example(cx: Context, props: ()) -> DomTree {
// let user_data = use_sql_query(&cx, USER_DATA_QUERY);
// cx.render(rsx! {

View file

@ -57,7 +57,7 @@ struct ButtonProps<'src, F: Fn(MouseEvent)> {
handler: F,
}
fn CustomButton<'a, F: Fn(MouseEvent)>(cx: Context<'a, ButtonProps<'a, F>>) -> VNode {
fn CustomButton<'a, F: Fn(MouseEvent)>(cx: Context<'a, ButtonProps<'a, F>>) -> DomTree {
cx.render(rsx!{
button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
@ -77,7 +77,7 @@ impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
struct PlaceholderProps {
val: &'static str,
}
fn Placeholder(cx: Context<PlaceholderProps>) -> VNode {
fn Placeholder(cx: Context<PlaceholderProps>) -> DomTree {
cx.render(rsx! {
div {
"child: {cx.val}"

View file

@ -25,7 +25,7 @@ pub struct TodoItem {
pub contents: String,
}
pub fn App(cx: Context<()>) -> VNode {
pub fn App(cx: Context<()>) -> DomTree {
let (draft, set_draft) = use_state_classic(cx, || "".to_string());
let (todos, set_todos) = use_state_classic(cx, || HashMap::<uuid::Uuid, Rc<TodoItem>>::new());
let (filter, set_filter) = use_state_classic(cx, || FilterState::All);
@ -99,7 +99,7 @@ pub struct TodoEntryProps {
item: Rc<TodoItem>,
}
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> VNode {
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> DomTree {
let (is_editing, set_is_editing) = use_state_classic(cx, || false);
let contents = "";
let todo = TodoItem {

View file

@ -2,7 +2,7 @@ use crate::recoil;
use crate::state::{FilterState, TODOS};
use dioxus_core::prelude::*;
pub fn FilterToggles(cx: Context<()>) -> VNode {
pub fn FilterToggles(cx: Context<()>) -> DomTree {
let reducer = recoil::use_callback(&cx, || ());
let items_left = recoil::use_atom_family(&cx, &TODOS, uuid::Uuid::new_v4());

View file

@ -7,7 +7,7 @@ pub struct TodoEntryProps {
id: uuid::Uuid,
}
pub fn TodoEntry(cx: Context, props: &TodoEntryProps) -> VNode {
pub fn TodoEntry(cx: Context, props: &TodoEntryProps) -> DomTree {
let (is_editing, set_is_editing) = use_state(cx, || false);
let todo = use_atom_family(&cx, &TODOS, cx.id);

View file

@ -6,7 +6,7 @@ use crate::{
};
use dioxus_core::prelude::*;
pub fn TodoList(cx: Context<()>) -> VNode {
pub fn TodoList(cx: Context<()>) -> DomTree {
let (draft, set_draft) = use_state(cx, || "".to_string());
let (todos, _) = use_state(cx, || Vec::<TodoItem>::new());
let filter = use_atom(&cx, &FILTER);

View file

@ -28,7 +28,7 @@ pub struct TodoItem {
// =======================
// Components
// =======================
pub fn App(cx: Context<()>) -> VNode {
pub fn App(cx: Context<()>) -> DomTree {
cx.render(rsx! {
div {
id: "app"
@ -53,7 +53,7 @@ pub fn App(cx: Context<()>) -> VNode {
})
}
pub fn TodoList(cx: Context<()>) -> VNode {
pub fn TodoList(cx: Context<()>) -> DomTree {
let (draft, set_draft) = use_state_classic(cx, || "".to_string());
let (todos, set_todos) = use_state_classic(cx, || HashMap::<uuid::Uuid, Rc<TodoItem>>::new());
let (filter, set_filter) = use_state_classic(cx, || FilterState::All);
@ -102,7 +102,7 @@ pub struct TodoEntryProps {
item: Rc<TodoItem>,
}
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> VNode {
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> DomTree {
let (is_editing, set_is_editing) = use_state_classic(cx, || false);
let contents = "";
let todo = TodoItem {
@ -128,7 +128,7 @@ pub fn TodoEntry(cx: Context<TodoEntryProps>) -> VNode {
))
}
pub fn FilterToggles(cx: Context<()>) -> VNode {
pub fn FilterToggles(cx: Context<()>) -> DomTree {
let toggles = [
("All", "", FilterState::All),
("Active", "active", FilterState::Active),

View file

@ -73,7 +73,7 @@ impl TodoManager {
}
}
pub fn TodoList(cx: Context<()>) -> VNode {
pub fn TodoList(cx: Context<()>) -> DomTree {
let draft = use_state(cx, || "".to_string());
let todos = use_read(&cx, &TODO_LIST);
let filter = use_read(&cx, &FILTER);
@ -117,7 +117,7 @@ pub struct TodoEntryProps {
id: Uuid,
}
pub fn TodoEntry(cx: Context, props: &TodoEntryProps) -> VNode {
pub fn TodoEntry(cx: Context, props: &TodoEntryProps) -> DomTree {
let (is_editing, set_is_editing) = use_state_classic(cx, || false);
let todo = use_read(&cx, &TODO_LIST).get(&cx.id).unwrap();
@ -138,7 +138,7 @@ pub fn TodoEntry(cx: Context, props: &TodoEntryProps) -> VNode {
))
}
pub fn FilterToggles(cx: Context<()>) -> VNode {
pub fn FilterToggles(cx: Context<()>) -> DomTree {
let reducer = TodoManager(use_recoil_api(cx));
let items_left = use_read(cx, &TODOS_LEFT);
@ -167,7 +167,7 @@ pub fn FilterToggles(cx: Context<()>) -> VNode {
}
}
pub fn Footer(cx: Context<()>) -> VNode {
pub fn Footer(cx: Context<()>) -> DomTree {
rsx! { in cx,
footer { class: "info"
p {"Double-click to edit a todo"}
@ -185,7 +185,7 @@ pub fn Footer(cx: Context<()>) -> VNode {
const APP_STYLE: &'static str = include_str!("./todomvc/style.css");
fn App(cx: Context<()>) -> VNode {
fn App(cx: Context<()>) -> DomTree {
use_init_recoil_root(cx, |_| {});
rsx! { in cx,
div { id: "app"

View file

@ -123,7 +123,7 @@ struct CalculatorKeyProps<'a> {
onclick: &'a dyn Fn(MouseEvent),
}
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "calculator-key {cx.name}"
@ -138,7 +138,7 @@ struct CalculatorDisplayProps {
val: f64,
}
fn CalculatorDisplay(cx: Context<CalculatorDisplayProps>) -> VNode {
fn CalculatorDisplay(cx: Context<CalculatorDisplayProps>) -> DomTree {
use separator::Separatable;
// Todo, add float support to the num-format crate
let formatted = cx.val.separated_string();

View file

@ -66,7 +66,7 @@ struct CalculatorKeyProps<'a> {
onclick: &'a dyn Fn(MouseEvent),
}
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "calculator-key {cx.name}"

View file

@ -92,7 +92,7 @@ struct ActionButtonProps<F: Fn(MouseEvent)> {
id: &'static str,
action: F,
}
fn ActionButton<F>(cx: Context<ActionButtonProps<F>>) -> VNode
fn ActionButton<F>(cx: Context<ActionButtonProps<F>>) -> DomTree
where
F: Fn(MouseEvent),
{
@ -110,7 +110,7 @@ struct RowProps {
row_id: usize,
label: Rc<str>,
}
fn Row<'a>(cx: Context<'a, RowProps>) -> VNode {
fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree {
cx.render(rsx! {
tr {
td { class:"col-md-1", "{cx.row_id}" }

View file

@ -71,7 +71,7 @@ struct CalculatorKeyProps<'a> {
onclick: &'a dyn Fn(MouseEvent),
}
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
cx.render(rsx! {
button {
class: "calculator-key {cx.name}"

View file

@ -63,7 +63,7 @@ pub struct MyProps3<'a> {
// We need to manually specify a lifetime that ensures props and scope (the component's state) share the same lifetime.
// 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 {
fn Example3<'a>(cx: Context<'a, MyProps3<'a>>) -> DomTree {
cx.render(rsx! {
div { "Not memoized! {cx.name}" }
})
@ -77,7 +77,7 @@ pub struct MyProps4<'a> {
}
// 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 {
fn Example4<'a>(cx: Context<'a, MyProps4<'a>>) -> DomTree {
cx.render(rsx! {
div { "Not memoized!", onclick: move |_| (cx.onhandle)() }
})

View file

@ -183,7 +183,7 @@ mod baller {
pub struct BallerProps {}
/// This component totally balls
pub fn Baller(cx: Context<()>) -> VNode {
pub fn Baller(cx: Context<()>) -> DomTree {
todo!()
}
}
@ -194,7 +194,7 @@ pub struct TallerProps {
}
/// This component is taller than most :)
pub fn Taller(cx: Context<TallerProps>) -> VNode {
pub fn Taller(cx: Context<TallerProps>) -> DomTree {
let b = true;
todo!()
}

View file

@ -33,7 +33,7 @@ struct ScrollSelectorProps<'a> {
onselect: &'a dyn Fn(Option<usize>),
}
fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> VNode<'a> {
fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> DomTree<'a> {
let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| {
rsx! {
li {

View file

@ -91,7 +91,7 @@ pub struct TodoEntryProps {
todo: std::rc::Rc<TodoItem>,
}
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> VNode {
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> DomTree {
let TodoEntryProps { todo } = *cx;
let is_editing = use_state(cx, || false);
let contents = "";

View file

@ -17,7 +17,7 @@ Originally the syntax of the FC macro was meant to look like:
```rust
#[fc]
fn example(cx: &Context<{ name: String }>) -> VNode {
fn example(cx: &Context<{ name: String }>) -> DomTree {
html! { <div> "Hello, {name}!" </div> }
}
```
@ -26,7 +26,7 @@ fn example(cx: &Context<{ name: String }>) -> VNode {
```rust
#[fc]
fn example(cx: &Context, name: String) -> VNode {
fn example(cx: &Context, name: String) -> DomTree {
html! { <div> "Hello, {name}!" </div> }
}
```
@ -71,7 +71,7 @@ mod Example {
name: String
}
fn component(cx: &Context<Props>) -> VNode {
fn component(cx: &Context<Props>) -> DomTree {
html! { <div> "Hello, {name}!" </div> }
}
}
@ -84,7 +84,7 @@ struct Props {
name: String
}
fn component(cx: &Context<Props>) -> VNode {
fn component(cx: &Context<Props>) -> DomTree {
html! { <div> "Hello, {name}!" </div> }
}
```
@ -93,7 +93,7 @@ These definitions might be ugly, but the fc macro cleans it all up. The fc macro
```rust
#[fc]
fn example(cx: &Context, name: String) -> VNode {
fn example(cx: &Context, name: String) -> DomTree {
html! { <div> "Hello, {name}!" </div> }
}
@ -105,7 +105,7 @@ mod Example {
struct Props {
name: String
}
fn component(cx: &Context<Props>) -> VNode {
fn component(cx: &Context<Props>) -> DomTree {
html! { <div> "Hello, {name}!" </div> }
}
}
@ -160,7 +160,7 @@ pub static Example: FC<()> = |cx| {
// expands to...
pub static Example: FC<()> = |cx| {
// This function converts a Fn(allocator) -> VNode closure to a VNode struct that will later be evaluated.
// This function converts a Fn(allocator) -> DomTree closure to a VNode struct that will later be evaluated.
html_macro_to_vnodetree(move |allocator| {
let mut node0 = allocator.alloc(VElement::div);
let node1 = allocator.alloc_text("blah");
@ -216,7 +216,7 @@ struct ExampleContext {
items: Vec<String>
}
fn Example<'src>(cx: Context<'src, ()>) -> VNode<'src> {
fn Example<'src>(cx: Context<'src, ()>) -> DomTree<'src> {
let val: &'b ContextGuard<ExampleContext> = (&'b cx).use_context(|context: &'other ExampleContext| {
// always select the last element
context.items.last()
@ -348,7 +348,7 @@ An update to the use_context subscription will mark the node as dirty. The node
The FC layout was altered to make life easier for us inside the VirtualDom. The "view" function returns an unbounded VNode object. Calling the "view" function is unsafe under the hood, but prevents lifetimes from leaking out of the function call. Plus, it's easier to write. Because there are no lifetimes on the output (occur purely under the hood), we can escape needing to annotate them.
```rust
fn component(cx: Context, props: &Props) -> VNode {
fn component(cx: Context, props: &Props) -> DomTree {
}
```

View file

@ -52,7 +52,7 @@ pub fn html_template(s: TokenStream) -> TokenStream {
/// ```ignore
///
/// #[fc]
/// fn Example(cx: Context, name: &str) -> VNode {
/// fn Example(cx: Context, name: &str) -> DomTree {
/// cx.render(rsx! { h1 {"hello {name}"} })
/// }
/// ```
@ -220,7 +220,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// pub struct BallerProps {}
///
/// /// This component totally balls
/// pub fn Baller(cx: Context<()>) -> VNode {
/// pub fn Baller(cx: Context<()>) -> DomTree {
/// todo!()
/// }
/// }
@ -231,7 +231,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// }
///
/// /// This component is taller than most :)
/// pub fn Taller(cx: Context<TallerProps>) -> VNode {
/// pub fn Taller(cx: Context<TallerProps>) -> DomTree {
/// let b = true;
/// todo!()
/// }

View file

@ -1,3 +1,3 @@
{
"rust-analyzer.inlayHints.enable": true
"rust-analyzer.inlayHints.enable": false
}

View file

@ -1,20 +1,25 @@
fn main() {}
// use dioxus::*;
// use dioxus_core as dioxus;
// use dioxus_core::prelude::*;
use dioxus::*;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
// pub static Example: FC<()> = |cx| {
// let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
pub static Example: FC<()> = |cx| {
let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
// cx.render(LazyNodes::new(move |cx| {
// let bump = cx.bump();
// cx.raw_element("div")
// .children([
// cx.text(format_args!("hello")),
// cx.text(format_args!("hello")),
// cx.fragment_from_iter(list),
// ])
// .finish()
// }))
// };
cx.render(LazyNodes::new(move |cx| {
let bump = cx.bump();
cx.raw_element(
"div",
None,
&mut [],
&mut [],
cx.bump().alloc([
cx.text(format_args!("hello")),
cx.text(format_args!("hello")),
cx.fragment_from_iter(list),
]),
None,
)
}))
};

View file

@ -20,7 +20,7 @@ struct ListItem {
age: u32,
}
fn app(cx: Context<AppProps>) -> VNode {
fn app(cx: Context<AppProps>) -> DomTree {
// let (val, set_val) = use_state_classic(cx, || 0);
cx.render(LazyNodes::new(move |_nodecx| {
@ -56,7 +56,7 @@ struct ChildProps {
item_handler: Rc<dyn Fn(i32)>,
}
fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> VNode {
fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> DomTree {
cx.render(LazyNodes::new(move |__cx| todo!()))
}

View file

@ -0,0 +1,48 @@
fn main() {}
use dioxus_core::prelude::*;
fn App(cx: Context<()>) -> DomTree {
//
let vak = cx.use_suspense(
|| async {},
|c, res| {
//
c.render(LazyNodes::new(move |f| f.text(format_args!(""))))
},
);
let d1 = cx.render(LazyNodes::new(move |f| {
f.raw_element(
"div",
None,
&mut [],
&[],
f.bump().alloc([
f.fragment_from_iter(vak),
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
]),
None,
)
}));
cx.render(LazyNodes::new(move |f| {
f.raw_element(
"div",
None,
&mut [],
&[],
f.bump().alloc([
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
f.text(format_args!("")),
f.fragment_from_iter(d1),
]),
None,
)
}))
}

View file

@ -57,7 +57,7 @@ fn main() {}
// }
// }
// fn component<'a>(cx: Context<'a, Props>) -> VNode<'a> {
// fn component<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
// // Write asynchronous rendering code that immediately returns a "suspended" VNode
// // The concurrent API will then progress this component when the future finishes
// // You can suspend the entire component, or just parts of it
@ -82,7 +82,7 @@ fn main() {}
// })
// }
// fn BuilderComp<'a>(cx: Context<'a, Props>) -> VNode<'a> {
// fn BuilderComp<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
// // VNodes can be constructed via a builder or the html! macro
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
// // We can "view" them with Context for ultimate speed while inside components
@ -96,7 +96,7 @@ fn main() {}
// }
// #[fc]
// fn EffcComp(cx: Context, name: &str) -> VNode {
// fn EffcComp(cx: Context, name: &str) -> DomTree {
// // VNodes can be constructed via a builder or the html! macro
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
// // We can "view" them with Context for ultimate speed while inside components
@ -110,7 +110,7 @@ fn main() {}
// })
// }
// fn FullySuspended<'a>(cx: &'a Context<Props>) -> VNode<'a> {
// fn FullySuspended<'a>(cx: &'a Context<Props>) -> DomTree<'a> {
// cx.suspend(async {
// let i: i32 = 0;

View file

@ -5,7 +5,7 @@
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
//! that ensures compile-time required and optional fields on cx.
use crate::innerlude::{Context, LazyNodes, VNode, FC};
use crate::innerlude::{Context, DomTree, LazyNodes, VNode, FC};
pub trait Properties: Sized {
type Builder;
@ -51,6 +51,6 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
#[allow(non_upper_case_globals, non_snake_case)]
pub fn Fragment<'a>(cx: Context<'a, ()>) -> VNode<'a> {
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
}

View file

@ -133,13 +133,13 @@ impl<'src, P> Context<'src, P> {
pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
self,
lazy_nodes: LazyNodes<'src, F>,
) -> VNode<'src> {
) -> DomTree<'src> {
let scope_ref = self.scope;
let listener_id = &scope_ref.listener_idx;
lazy_nodes.into_vnode(NodeFactory {
Some(lazy_nodes.into_vnode(NodeFactory {
scope_ref,
listener_id,
})
}))
}
/// Store a value between renders
@ -378,7 +378,7 @@ pub(crate) struct SuspenseHook {
pub callback: SuspendedCallback,
pub dom_node_id: Rc<Cell<RealDomNode>>,
}
type SuspendedCallback = Box<dyn for<'a> Fn(Context<'a, ()>) -> VNode<'a>>;
type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
impl<'src, P> Context<'src, P> {
/// Asynchronously render new nodes once the given future has completed.
@ -395,11 +395,11 @@ impl<'src, P> Context<'src, P> {
self,
task_initializer: impl FnOnce() -> Fut,
user_callback: Cb,
) -> VNode<'src>
) -> DomTree<'src>
where
Fut: Future<Output = Out> + 'static,
Out: 'static,
Cb: for<'a> Fn(Context<'a, ()>, &Out) -> VNode<'a> + 'static,
Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
{
self.use_hook(
move |hook_idx| {
@ -410,7 +410,7 @@ impl<'src, P> Context<'src, P> {
let slot = value.clone();
let callback: SuspendedCallback = Box::new(move |ctx: Context<()>| {
let callback: SuspendedCallback = Box::new(move |ctx: SuspendedContext| {
let v: std::cell::Ref<Option<Box<dyn Any>>> = slot.as_ref().borrow();
match v.as_ref() {
Some(a) => {
@ -420,13 +420,13 @@ impl<'src, P> Context<'src, P> {
}
None => {
//
VNode {
Some(VNode {
dom_id: RealDomNode::empty_cell(),
key: None,
kind: VNodeKind::Suspended {
node: domnode.clone(),
},
}
})
}
}
});
@ -459,13 +459,31 @@ impl<'src, P> Context<'src, P> {
scope: &self.scope,
props: &(),
};
(&hook.callback)(cx)
let csx = SuspendedContext { inner: cx };
(&hook.callback)(csx)
},
|_| {},
)
}
}
pub struct SuspendedContext<'a> {
pub(crate) inner: Context<'a, ()>,
}
impl<'src> SuspendedContext<'src> {
pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
self,
lazy_nodes: LazyNodes<'src, F>,
) -> DomTree<'src> {
let scope_ref = self.inner.scope;
let listener_id = &scope_ref.listener_idx;
Some(lazy_nodes.into_vnode(NodeFactory {
scope_ref,
listener_id,
}))
}
}
#[derive(Clone, Copy)]
pub struct TaskHandle<'src> {
_p: PhantomData<&'src ()>,

View file

@ -18,8 +18,7 @@ pub use crate::innerlude::{
pub mod prelude {
pub use crate::component::{fc_to_builder, Fragment, Properties};
pub use crate::context::Context;
pub use crate::innerlude::DioxusElement;
pub use crate::innerlude::{LazyNodes, NodeFactory, FC};
pub use crate::innerlude::{DioxusElement, DomTree, LazyNodes, NodeFactory, FC};
pub use crate::nodes::VNode;
pub use crate::VirtualDom;
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
@ -43,7 +42,8 @@ pub(crate) mod innerlude {
pub use crate::util::*;
pub use crate::virtual_dom::*;
pub type FC<P> = fn(Context<P>) -> VNode;
pub type DomTree<'a> = Option<VNode<'a>>;
pub type FC<P> = fn(Context<P>) -> DomTree;
pub use dioxus_core_macro::{format_args_f, html, rsx};
}

View file

@ -5,7 +5,7 @@
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
use crate::{
events::VirtualEvent,
innerlude::{Context, Properties, RealDomNode, Scope, ScopeId, FC},
innerlude::{Context, DomTree, Properties, RealDomNode, Scope, ScopeId, FC},
};
use std::{
cell::Cell,
@ -50,6 +50,7 @@ pub struct VText<'src> {
pub struct VFragment<'src> {
pub children: &'src [VNode<'src>],
pub is_static: bool,
pub is_error: bool,
}
pub trait DioxusElement {
@ -114,7 +115,7 @@ pub struct Listener<'bump> {
pub struct VComponent<'src> {
pub ass_scope: Cell<Option<ScopeId>>,
pub(crate) caller: Rc<dyn Fn(&Scope) -> VNode>,
pub(crate) caller: Rc<dyn Fn(&Scope) -> DomTree>,
pub(crate) children: &'src [VNode<'src>],
@ -321,9 +322,9 @@ impl<'a> NodeFactory<'a> {
pub fn create_component_caller<'g, P: 'g>(
component: FC<P>,
raw_props: *const (),
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
let caller: Captured = Rc::new(move |scp: &Scope| -> VNode {
) -> Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r>> {
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r> + 'a>;
let caller: Captured = Rc::new(move |scp: &Scope| -> DomTree {
// cast back into the right lifetime
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
let cx: Context<P> = Context {
@ -371,6 +372,7 @@ To help you identify where this error is coming from, we've generated a backtrac
kind: VNodeKind::Fragment(VFragment {
children: nodes.into_bump_slice(),
is_static: false,
is_error: false,
}),
}
}
@ -426,6 +428,7 @@ impl<'a> IntoVNode<'a> for &VNode<'a> {
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
children: fragment.children,
is_static: fragment.is_static,
is_error: false,
}),
VNodeKind::Component(component) => VNodeKind::Component(component),

View file

@ -53,7 +53,7 @@ pub struct Scope {
type EventChannel = Rc<dyn Fn()>;
// The type of closure that wraps calling components
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> VNode<'b>;
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>;
// The type of task that gets sent to the task scheduler
pub type FiberTask = Pin<Box<dyn Future<Output = EventTrigger>>>;
@ -130,7 +130,8 @@ impl Scope {
// this is its own function so we can preciesly control how lifetimes flow
unsafe fn call_user_component<'a>(&'a self, caller: &WrappedCaller) -> VNode<'static> {
let new_head: VNode<'a> = caller(self);
let new_head: Option<VNode<'a>> = caller(self);
let new_head = new_head.unwrap_or(errored_fragment());
std::mem::transmute(new_head)
}
@ -205,3 +206,15 @@ impl Scope {
&self.frames.cur_frame().head_node
}
}
pub fn errored_fragment() -> VNode<'static> {
VNode {
dom_id: RealDomNode::empty_cell(),
key: None,
kind: VNodeKind::Fragment(VFragment {
children: &[],
is_static: false,
is_error: true,
}),
}
}

View file

@ -101,7 +101,7 @@ impl VirtualDom {
///
/// // or directly from a fn
///
/// fn Example(cx: Context<()>) -> VNode {
/// fn Example(cx: Context<()>) -> DomTree {
/// cx.render(rsx!{ div{"hello world"} })
/// }
///
@ -318,9 +318,11 @@ impl VirtualDom {
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
let cx = Context { scope, props: &() };
let scx = SuspendedContext { inner: cx };
// generate the new node!
let nodes: VNode<'s> = (&hook.callback)(cx);
let nodes: Option<VNode<'s>> = (&hook.callback)(scx);
let nodes = nodes.unwrap_or_else(|| errored_fragment());
let nodes = scope.cur_frame().bump.alloc(nodes);
// push the old node's root onto the stack

View file

@ -21,7 +21,7 @@ fn main() {
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
dioxus_web::launch(App, |c| c)
}
static App: FC<()> = |cx| {

View file

@ -22,7 +22,7 @@ fn main() {
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
dioxus_web::launch(App, |c| c)
}
pub static App: FC<()> = |cx| {

View file

@ -1,690 +0,0 @@
use std::{borrow::Borrow, convert::TryInto, default, fmt::Debug, sync::Arc};
use dioxus_core::{
events::{EventTrigger, VirtualEvent},
prelude::ScopeIdx,
};
use fxhash::FxHashMap;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{
window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct CacheId(u32);
#[derive(Clone)]
struct RootCallback(Arc<dyn Fn(EventTrigger)>);
impl std::fmt::Debug for RootCallback {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
// a no-op for now
// todo!()
}
}
#[derive(Debug)]
pub(crate) struct PatchMachine {
pub(crate) stack: Stack,
pub(crate) known_roots: FxHashMap<u32, Node>,
pub(crate) root: Element,
pub(crate) temporaries: FxHashMap<u32, Node>,
pub(crate) document: Document,
pub(crate) events: EventDelegater,
pub(crate) current_known: Option<u32>,
// We need to make sure to add comments between text nodes
// We ensure that the text siblings are patched by preventing the browser from merging
// neighboring text nodes. Originally inspired by some of React's work from 2016.
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
// -> https://github.com/facebook/react/pull/5753
//
// `ptns` = Percy text node separator
// TODO
pub(crate) last_node_was_text: bool,
}
#[derive(Debug)]
pub(crate) struct EventDelegater {
root: Element,
// every callback gets a monotomically increasing callback ID
callback_id: usize,
// map of listener types to number of those listeners
listeners: FxHashMap<String, (usize, Closure<dyn FnMut(&Event)>)>,
// Map of callback_id to component index and listener id
callback_map: FxHashMap<usize, (usize, usize)>,
trigger: RootCallback,
}
impl EventDelegater {
pub fn new(root: Element, trigger: impl Fn(EventTrigger) + 'static) -> Self {
Self {
trigger: RootCallback(Arc::new(trigger)),
root,
callback_id: 0,
listeners: FxHashMap::default(),
callback_map: FxHashMap::default(),
}
}
pub fn add_listener(&mut self, event: &str, scope: ScopeIdx) {
if let Some(entry) = self.listeners.get_mut(event) {
entry.0 += 1;
} else {
let trigger = self.trigger.clone();
let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
log::debug!("Handling event!");
let target = event
.target()
.expect("missing target")
.dyn_into::<Element>()
.expect("not a valid element");
let typ = event.type_();
let gi_id: Option<usize> = target
.get_attribute(&format!("dioxus-giid-{}", typ))
.and_then(|v| v.parse().ok());
let gi_gen: Option<u64> = target
.get_attribute(&format!("dioxus-gigen-{}", typ))
.and_then(|v| v.parse().ok());
let li_idx: Option<usize> = target
.get_attribute(&format!("dioxus-lidx-{}", typ))
.and_then(|v| v.parse().ok());
if let (Some(gi_id), Some(gi_gen), Some(li_idx)) = (gi_id, gi_gen, li_idx) {
// Call the trigger
log::debug!(
"decoded gi_id: {}, gi_gen: {}, li_idx: {}",
gi_id,
gi_gen,
li_idx
);
let triggered_scope = ScopeIdx::from_raw_parts(gi_id, gi_gen);
trigger.0.as_ref()(EventTrigger::new(
virtual_event_from_websys_event(event),
triggered_scope,
// scope,
li_idx,
));
}
}) as Box<dyn FnMut(&Event)>);
self.root
.add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
.unwrap();
// Increment the listeners
self.listeners.insert(event.into(), (1, handler));
}
}
}
// callback: RootCallback,
// callback: Option<Closure<dyn Fn(EventTrigger)>>,
#[derive(Debug, Default)]
pub struct Stack {
list: Vec<Node>,
}
impl Stack {
pub fn with_capacity(cap: usize) -> Self {
Stack {
list: Vec::with_capacity(cap),
}
}
pub fn push(&mut self, node: Node) {
// debug!("stack-push: {:?}", node);
self.list.push(node);
}
pub fn pop(&mut self) -> Node {
let res = self.list.pop().unwrap();
res
}
pub fn clear(&mut self) {
self.list.clear();
}
pub fn top(&self) -> &Node {
match self.list.last() {
Some(a) => a,
None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
}
}
}
impl PatchMachine {
pub fn new(root: Element, event_callback: impl Fn(EventTrigger) + 'static) -> Self {
let document = window()
.expect("must have access to the window")
.document()
.expect("must have access to the Document");
// attach all listeners to the container element
let events = EventDelegater::new(root.clone(), event_callback);
Self {
current_known: None,
known_roots: Default::default(),
root,
events,
stack: Stack::with_capacity(20),
temporaries: Default::default(),
document,
last_node_was_text: false,
}
}
pub fn unmount(&mut self) {
self.stack.clear();
self.temporaries.clear();
}
pub fn start(&mut self) {
if let Some(child) = self.root.first_child() {
self.stack.push(child);
}
}
pub fn reset(&mut self) {
self.stack.clear();
self.temporaries.clear();
}
pub fn handle_edit(&mut self, edit: &Edit) {
match *edit {
// 0
Edit::SetText { text } => {
//
self.stack.top().set_text_content(Some(text))
}
// 1
Edit::RemoveSelfAndNextSiblings {} => {
let node = self.stack.pop();
let mut sibling = node.next_sibling();
while let Some(inner) = sibling {
let temp = inner.next_sibling();
if let Some(sibling) = inner.dyn_ref::<Element>() {
sibling.remove();
}
sibling = temp;
}
if let Some(node) = node.dyn_ref::<Element>() {
node.remove();
}
}
// 2
Edit::ReplaceWith => {
let new_node = self.stack.pop();
let old_node = self.stack.pop();
if old_node.has_type::<Element>() {
old_node
.dyn_ref::<Element>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else if old_node.has_type::<web_sys::CharacterData>() {
old_node
.dyn_ref::<web_sys::CharacterData>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else if old_node.has_type::<web_sys::DocumentType>() {
old_node
.dyn_ref::<web_sys::DocumentType>()
.unwrap()
.replace_with_with_node_1(&new_node)
.unwrap();
} else {
panic!("Cannot replace node: {:?}", old_node);
}
// poc to see if this is a valid solution
if let Some(id) = self.current_known {
// update mapping
self.known_roots.insert(id, new_node.clone());
self.current_known = None;
}
self.stack.push(new_node);
}
// 3
Edit::SetAttribute { name, value } => {
let node = self.stack.top();
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
node.set_attribute(name, value).unwrap();
}
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
log::debug!("el is html input element");
// Some attributes are "volatile" and don't work through `setAttribute`.
if name == "value" {
node.set_value(value);
}
if name == "checked" {
node.set_checked(true);
}
}
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
if name == "selected" {
node.set_selected(true);
}
}
}
// 4
Edit::RemoveAttribute { name } => {
let node = self.stack.top();
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
node.remove_attribute(name).unwrap();
}
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
// Some attributes are "volatile" and don't work through `removeAttribute`.
if name == "value" {
node.set_value("");
}
if name == "checked" {
node.set_checked(false);
}
}
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
if name == "selected" {
node.set_selected(true);
}
}
}
// 5
Edit::PushReverseChild { n } => {
let parent = self.stack.top();
let children = parent.child_nodes();
let child = children.get(children.length() - n - 1).unwrap();
self.stack.push(child);
}
// 6
Edit::PopPushChild { n } => {
self.stack.pop();
let parent = self.stack.top();
let children = parent.child_nodes();
let child = children.get(n).unwrap();
self.stack.push(child);
}
// 7
Edit::Pop => {
self.stack.pop();
}
// 8
Edit::AppendChild => {
let child = self.stack.pop();
self.stack.top().append_child(&child).unwrap();
}
// 9
Edit::CreateTextNode { text } => {
//
// We ensure that the text siblings are patched by preventing the browser from merging
// neighboring text nodes. Originally inspired by some of React's work from 2016.
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
// -> https://github.com/facebook/react/pull/5753
//
// `ptns` = Percy text node separator
// TODO
if self.last_node_was_text {
let n = self
.document
.create_comment("ptns")
.dyn_into::<Node>()
.unwrap();
}
self.stack.push(
self.document
.create_text_node(text)
.dyn_into::<Node>()
.unwrap(),
);
self.last_node_was_text = true;
}
// 10
Edit::CreateElement { tag_name } => {
self.last_node_was_text = false;
let el = self
.document
.create_element(tag_name)
.unwrap()
.dyn_into::<Node>()
.unwrap();
self.stack.push(el);
}
// 11
Edit::NewListener { event, id, scope } => {
// attach the correct attributes to the element
// these will be used by accessing the event's target
// This ensures we only ever have one handler attached to the root, but decide
// dynamically when we want to call a listener.
let el = self.stack.top();
let el = el
.dyn_ref::<Element>()
.expect(&format!("not an element: {:?}", el));
// el.add_event_listener_with_callback(
// event_type,
// self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
// )
// .unwrap();
// debug!("adding attributes: {}, {}", a, b);
// let CbIdx {
// gi_id,
// gi_gen,
// listener_idx: lidx,
// } = idx;
let (gi_id, gi_gen) = (&scope).into_raw_parts();
el.set_attribute(&format!("dioxus-giid-{}", event), &gi_id.to_string())
.unwrap();
el.set_attribute(&format!("dioxus-gigen-{}", event), &gi_gen.to_string())
.unwrap();
el.set_attribute(&format!("dioxus-lidx-{}", event), &id.to_string())
.unwrap();
self.events.add_listener(event, scope);
}
// 12
Edit::UpdateListener { event, scope, id } => {
// update our internal mapping, and then modify the attribute
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
// el.set_attribute(&format!("dioxus-a-{}", event_type), &a.to_string())
// .unwrap();
// el.set_attribute(&format!("dioxus-b-{}", event_type), &b.to_string())
// .unwrap();
}
}
// 13
Edit::RemoveListener { event: event_type } => {
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
// el.remove_event_listener_with_callback(
// event_type,
// self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
// )
// .unwrap();
}
}
// 14
Edit::CreateElementNs { tag_name, ns } => {
self.last_node_was_text = false;
let el = self
.document
.create_element_ns(Some(ns), tag_name)
.unwrap()
.dyn_into::<Node>()
.unwrap();
log::debug!("Made NS element {} {}", ns, tag_name);
self.stack.push(el);
}
// 15
Edit::SaveChildrenToTemporaries {
mut temp,
start,
end,
} => {
let parent = self.stack.top();
let children = parent.child_nodes();
for i in start..end {
self.temporaries.insert(temp, children.get(i).unwrap());
temp += 1;
}
}
// 16
Edit::PushChild { n } => {
let parent = self.stack.top();
// log::debug!("PushChild {:#?}", parent);
let child = parent.child_nodes().get(n).unwrap();
self.stack.push(child);
}
// 17
Edit::PushTemporary { temp } => {
let t = self.temporaries.get(&temp).unwrap().clone();
self.stack.push(t);
}
// 18
Edit::InsertBefore => {
let before = self.stack.pop();
let after = self.stack.pop();
after
.parent_node()
.unwrap()
.insert_before(&before, Some(&after))
.unwrap();
self.stack.push(before);
}
// 19
Edit::PopPushReverseChild { n } => {
self.stack.pop();
let parent = self.stack.top();
let children = parent.child_nodes();
let child = children.get(children.length() - n - 1).unwrap();
self.stack.push(child);
}
// 20
Edit::RemoveChild { n } => {
let parent = self.stack.top();
if let Some(child) = parent.child_nodes().get(n).unwrap().dyn_ref::<Element>() {
child.remove();
}
}
// 21
Edit::SetClass { class_name } => {
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
el.set_class_name(class_name);
}
}
Edit::MakeKnown { node } => {
let domnode = self.stack.top();
self.known_roots.insert(node, domnode.clone());
}
Edit::TraverseToKnown { node } => {
let domnode = self
.known_roots
.get(&node)
.expect("Failed to pop know root");
self.current_known = Some(node);
self.stack.push(domnode.clone());
}
Edit::RemoveKnown => {}
}
}
}
fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
use dioxus_core::events::on::*;
match event.type_().as_str() {
"copy" | "cut" | "paste" => {
// let evt: web_sys::ClipboardEvent = event.clone().dyn_into().unwrap();
todo!()
}
"compositionend" | "compositionstart" | "compositionupdate" => {
let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap();
todo!()
}
"keydown" | "keypress" | "keyup" => {
let evt: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
todo!()
}
"focus" | "blur" => {
let evt: web_sys::FocusEvent = event.clone().dyn_into().unwrap();
todo!()
}
"change" => {
let evt: web_sys::Event = event.clone().dyn_into().expect("wrong error typ");
todo!()
// VirtualEvent::FormEvent(FormEvent {value:})
}
"input" | "invalid" | "reset" | "submit" => {
// is a special react events
let evt: web_sys::InputEvent = event.clone().dyn_into().expect("wrong event type");
let this: web_sys::EventTarget = evt.target().unwrap();
let value = (&this)
.dyn_ref()
.map(|input: &web_sys::HtmlInputElement| input.value())
.or_else(|| {
(&this)
.dyn_ref()
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
})
.or_else(|| {
(&this)
.dyn_ref::<web_sys::HtmlElement>()
.unwrap()
.text_content()
})
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
// let p2 = evt.data_transfer();
// let value: Option<String> = (&evt).data();
// let value = val;
// let value = value.unwrap_or_default();
// let value = (&evt).data().expect("No data to unwrap");
// todo - this needs to be a "controlled" event
// these events won't carry the right data with them
todo!()
// VirtualEvent::FormEvent(FormEvent { value })
}
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
let evt: web_sys::MouseEvent = event.clone().dyn_into().unwrap();
// MouseEvent(Box::new(RawMouseEvent {
// alt_key: evt.alt_key(),
// button: evt.button() as i32,
// buttons: evt.buttons() as i32,
// client_x: evt.client_x(),
// client_y: evt.client_y(),
// ctrl_key: evt.ctrl_key(),
// meta_key: evt.meta_key(),
// page_x: evt.page_x(),
// page_y: evt.page_y(),
// screen_x: evt.screen_x(),
// screen_y: evt.screen_y(),
// shift_key: evt.shift_key(),
// get_modifier_state: GetModifierKey(Box::new(|f| {
// // evt.get_modifier_state(f)
// todo!("This is not yet implemented properly, sorry :(");
// })),
// }))
todo!()
// VirtualEvent::MouseEvent()
}
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
let evt: web_sys::PointerEvent = event.clone().dyn_into().unwrap();
todo!()
}
"select" => {
// let evt: web_sys::SelectionEvent = event.clone().dyn_into().unwrap();
// not required to construct anything special beyond standard event stuff
todo!()
}
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
let evt: web_sys::TouchEvent = event.clone().dyn_into().unwrap();
todo!()
}
"scroll" => {
// let evt: web_sys::UIEvent = event.clone().dyn_into().unwrap();
todo!()
}
"wheel" => {
let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap();
todo!()
}
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
| "timeupdate" | "volumechange" | "waiting" => {
// not required to construct anything special beyond standard event stuff
// let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
// let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
todo!()
}
"animationstart" | "animationend" | "animationiteration" => {
let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap();
todo!()
}
"transitionend" => {
let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap();
todo!()
}
"toggle" => {
// not required to construct anything special beyond standard event stuff (target)
// let evt: web_sys::ToggleEvent = event.clone().dyn_into().unwrap();
todo!()
}
_ => VirtualEvent::OtherEvent,
}
}

View file

@ -1,211 +0,0 @@
use std::{collections::HashMap, fmt, rc::Rc};
use web_sys::{self, Element, EventTarget, Node, Text};
use dioxus_core::prelude::{VElement, VNode, VText, VirtualNode};
use std::ops::Deref;
use std::sync::Mutex;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
pub struct DomRenderer {}
// Used to uniquely identify elements that contain closures so that the DomUpdater can
// look them up by their unique id.
// When the DomUpdater sees that the element no longer exists it will drop all of it's
// Rc'd Closures for those events.
use lazy_static::lazy_static;
lazy_static! {
static ref ELEM_UNIQUE_ID: Mutex<u32> = Mutex::new(0);
}
fn create_unique_identifier() -> u32 {
let mut elem_unique_id = ELEM_UNIQUE_ID.lock().unwrap();
*elem_unique_id += 1;
*elem_unique_id
}
/// A node along with all of the closures that were created for that
/// node's events and all of it's child node's events.
pub struct CreatedNode<T> {
/// A `Node` or `Element` that was created from a `VirtualNode`
pub node: T,
/// A map of a node's unique identifier along with all of the Closures for that node.
///
/// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
/// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
/// memory.
pub closures: HashMap<u32, Vec<DynClosure>>,
}
impl<T> CreatedNode<T> {
pub fn without_closures<N: Into<T>>(node: N) -> Self {
CreatedNode {
node: node.into(),
closures: HashMap::with_capacity(0),
}
}
}
impl<T> Deref for CreatedNode<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.node
}
}
impl From<CreatedNode<Element>> for CreatedNode<Node> {
fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
CreatedNode {
node: other.node.into(),
closures: other.closures,
}
}
}
//----------------------------------
// Create nodes for the VNode types
// ---------------------------------
/// Return a `Text` element from a `VirtualNode`, typically right before adding it
/// into the DOM.
pub fn create_text_node(text_node: &VText) -> Text {
let document = web_sys::window().unwrap().document().unwrap();
document.create_text_node(&text_node.text)
}
/// Build a DOM element by recursively creating DOM nodes for this element and it's
/// children, it's children's children, etc.
pub fn create_element_node(velement: &VElement) -> CreatedNode<Element> {
let document = web_sys::window().unwrap().document().unwrap();
let element = if html_validation::is_svg_namespace(&velement.tag) {
document
.create_element_ns(Some("http://www.w3.org/2000/svg"), &velement.tag)
.unwrap()
} else {
document.create_element(&velement.tag).unwrap()
};
let mut closures = HashMap::new();
velement.attrs.iter().for_each(|(name, value)| {
if name == "unsafe_inner_html" {
element.set_inner_html(value);
return;
}
element
.set_attribute(name, value)
.expect("Set element attribute in create element");
});
todo!("Support events properly in web ");
// if velement.events.0.len() > 0 {
// let unique_id = create_unique_identifier();
// element
// .set_attribute("data-vdom-id".into(), &unique_id.to_string())
// .expect("Could not set attribute on element");
// closures.insert(unique_id, vec![]);
// velement.events.0.iter().for_each(|(onevent, callback)| {
// // onclick -> click
// let event = &onevent[2..];
// let current_elem: &EventTarget = element.dyn_ref().unwrap();
// current_elem
// .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
// .unwrap();
// closures
// .get_mut(&unique_id)
// .unwrap()
// .push(Rc::clone(callback));
// });
// }
let mut previous_node_was_text = false;
velement.children.iter().for_each(|child| {
match child {
VNode::Text(text_node) => {
let current_node = element.as_ref() as &web_sys::Node;
// We ensure that the text siblings are patched by preventing the browser from merging
// neighboring text nodes. Originally inspired by some of React's work from 2016.
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
// -> https://github.com/facebook/react/pull/5753
//
// `ptns` = Percy text node separator
if previous_node_was_text {
let separator = document.create_comment("ptns");
current_node
.append_child(separator.as_ref() as &web_sys::Node)
.unwrap();
}
current_node
.append_child(&create_text_node(&text_node))
// .append_child(&text_node.create_text_node())
.unwrap();
previous_node_was_text = true;
}
VNode::Element(element_node) => {
previous_node_was_text = false;
let child = create_element_node(&element_node);
// let child = element_node.create_element_node();
let child_elem: Element = child.node;
closures.extend(child.closures);
element.append_child(&child_elem).unwrap();
}
VNode::Component(component) => {
//
todo!("Support components in the web properly");
}
}
});
todo!("Support events properly in web ");
// if let Some(on_create_elem) = velement.events.0.get("on_create_elem") {
// let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
// on_create_elem
// .call1(&wasm_bindgen::JsValue::NULL, &element)
// .unwrap();
// }
CreatedNode {
node: element,
closures,
}
}
/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
/// any Closure regardless of the arguments.
pub type DynClosure = Rc<dyn AsRef<JsValue>>;
/// We need a custom implementation of fmt::Debug since JsValue doesn't
/// implement debug.
pub struct Events(pub HashMap<String, DynClosure>);
impl PartialEq for Events {
// TODO: What should happen here..? And why?
fn eq(&self, _rhs: &Self) -> bool {
true
}
}
impl fmt::Debug for Events {
// Print out all of the event names for this VirtualNode
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let events: String = self.0.keys().map(|key| " ".to_string() + key).collect();
write!(f, "{}", events)
}
}

View file

@ -1,160 +0,0 @@
//! A collection of functions that are useful for unit testing your html! views.
use crate::VirtualNode;
impl VirtualNode {
/// Get a vector of all of the VirtualNode children / grandchildren / etc of
/// your virtual_node that have a label that matches your filter.
///
/// # Examples
///
/// ```rust,ignore
/// # #[macro_use] extern crate virtual_dom_rs; fn main() {
///
/// let component = html! {<div>
/// <span label="hello",> {"Hi!"} </span>
/// <em label="world",> {"There!!"} </em>
/// <em label="hello",></em>
/// </div> };
///
/// let hello_nodes = component.filter_label(|label| {
/// label.contains("hello")
/// });
///
/// assert_eq!(hello_nodes.len(), 2);
/// }
/// ```
pub fn filter_label<'a, F>(&'a self, filter: F) -> Vec<&'a VirtualNode>
where
F: Fn(&str) -> bool,
{
// Get descendants recursively
let mut descendants: Vec<&'a VirtualNode> = vec![];
match self {
VirtualNode::Text(_) => { /* nothing to do */ }
VirtualNode::Element(element_node) => {
for child in element_node.children.iter() {
get_descendants(&mut descendants, child);
}
}
}
// Filter descendants
descendants
.into_iter()
.filter(|vn: &&'a VirtualNode| match vn {
VirtualNode::Text(_) => false,
VirtualNode::Element(element_node) => match element_node.attrs.get("label") {
Some(label) => filter(label),
None => false,
},
})
.collect()
}
/// Get a vector of all of the descendants of this VirtualNode
/// that have the provided `filter`.
///
/// # Examples
///
/// ```rust,ignore
/// # #[macro_use] extern crate virtual_dom_rs; fn main() {
///
/// let component = html! {<div>
/// <span label="hello",> {"Hi!"} </span>
/// <em label="world",> {"There!!"} </em>
/// <em label="hello",></em>
/// </div> };
///
/// let hello_nodes = component.filter_label_equals("hello");
///
/// assert_eq!(hello_nodes.len(), 2);
/// }
/// ```
pub fn filter_label_equals<'a>(&'a self, label: &str) -> Vec<&'a VirtualNode> {
self.filter_label(|node_label| node_label == label)
}
}
fn get_descendants<'a>(descendants: &mut Vec<&'a VirtualNode>, node: &'a VirtualNode) {
descendants.push(node);
match node {
VirtualNode::Text(_) => { /* nothing to do */ }
VirtualNode::Element(element_node) => {
for child in element_node.children.iter() {
get_descendants(descendants, child);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::VElement;
use std::collections::HashMap;
// TODO: Move this test somewhere that we can use the `html!` macro
// #[test]
// fn filter_label() {
// let html = html! {
// // Should not pick up labels on the root node
// <div label="hello0",>
// // This node gets picked up
// <span label="hello1",>
// </span>
// // This node gets picked up
// <em label="hello2",>
// { "hello there :)!" }
// </em>
// <div label="world",></div>
// </div>
// };
//
// let hello_nodes = html.filter_label(|label| label.contains("hello"));
//
// assert_eq!(
// hello_nodes.len(),
// 2,
// "2 elements with label containing 'hello'"
// );
// }
#[test]
fn label_equals() {
let span = VirtualNode::element("span");
let mut attrs = HashMap::new();
attrs.insert("label".to_string(), "hello".to_string());
let mut em = VElement::new("em");
em.attrs = attrs;
let mut html = VElement::new("div");
html.children.push(span);
html.children.push(em.into());
let html_node = VirtualNode::Element(html);
let hello_nodes = html_node.filter_label_equals("hello");
assert_eq!(hello_nodes.len(), 1);
}
}
use dioxus_core::prelude::*;
use dioxus_web::WebsysRenderer;
fn main() {
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
}
fn Component(cx: Context, props: ()) -> VNode {
let user_data = use_sql_query(&cx, USER_DATA_QUERY);
cx.render(rsx! {
h1 { "Hello, {username}"}
button {
"Delete user"
onclick: move |_| user_data.delete()
}
})
}

View file

@ -32,7 +32,7 @@
//! ```
//! use dioxus::prelude::*;
//!
//! fn Example(cx: Context<()>) -> VNode {
//! fn Example(cx: Context<()>) -> DomTree {
//! html! { <div> "Hello, world!" </div> }
//! }
//! ```
@ -44,7 +44,7 @@
//! #[derive(Props)]
//! struct Props { name: String }
//!
//! fn Example(cx: Context<Props>) -> VNode {
//! fn Example(cx: Context<Props>) -> DomTree {
//! html! { <div> "Hello {cx.props.name}!" </div> }
//! }
//! ```
@ -59,7 +59,7 @@
//! #[derive(Props)]
//! struct Props<'a> { name: &'a str }
//!
//! fn Example<'a>(cx: Context<'a, Props<'a>>) -> VNode {
//! fn Example<'a>(cx: Context<'a, Props<'a>>) -> DomTree {
//! html! { <div> "Hello {cx.props.name}!" </div> }
//! }
//! ```