diff --git a/README.md b/README.md index c438ea86d..bb9227165 100644 --- a/README.md +++ b/README.md @@ -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! { @@ -146,33 +146,34 @@ 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 | -| Web | ✅ | ✅ | renderer for web browser | -| Desktop (webview) | ✅ | ✅ | renderer for desktop | -| Shared State (Context) | ✅ | ✅ | share state through the tree | -| Hooks | ✅ | ✅ | memory cells in components | -| SSR | ✅ | ✅ | render directly to string | -| Component Children | ✅ | ✅ | cx.children() as a list of nodes | -| Headless components | ✅ | ✅ | components that don't return real elements | -| Fragments | ✅ | ✅ | multiple elements without a real root | -| Manual Props | ✅ | ✅ | Manually pass in props with spread syntax | -| Controlled Inputs | ✅ | ✅ | stateful wrappers around inputs | -| CSS/Inline Styles | ✅ | ✅ | syntax for inline styles/attribute groups | -| Custom elements | ✅ | ✅ | Define new element primitives | -| Suspense | ✅ | ✅ | schedule future render from future/promise | -| 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] | -| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) | -| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context | -| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees | -| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts | -| Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations | -| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates | +| 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 | +| Web | ✅ | ✅ | renderer for web browser | +| Desktop (webview) | ✅ | ✅ | renderer for desktop | +| Shared State (Context) | ✅ | ✅ | share state through the tree | +| Hooks | ✅ | ✅ | memory cells in components | +| SSR | ✅ | ✅ | render directly to string | +| Component Children | ✅ | ✅ | cx.children() as a list of nodes | +| Headless components | ✅ | ✅ | components that don't return real elements | +| Fragments | ✅ | ✅ | multiple elements without a real root | +| Manual Props | ✅ | ✅ | Manually pass in props with spread syntax | +| Controlled Inputs | ✅ | ✅ | stateful wrappers around inputs | +| 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] | +| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) | +| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context | +| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees | +| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts | +| Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations | +| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates | - [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API. diff --git a/docs/main-concepts/02-hello.md b/docs/main-concepts/02-hello.md index 14939f05d..ad91849c9 100644 --- a/docs/main-concepts/02-hello.md +++ b/docs/main-concepts/02-hello.md @@ -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!" } }) diff --git a/docs/main-concepts/03-rsx.md b/docs/main-concepts/03-rsx.md index 6020babad..e4f377d98 100644 --- a/docs/main-concepts/03-rsx.md +++ b/docs/main-concepts/03-rsx.md @@ -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 ) -> VNode { +fn Example(cx: Context ) -> DomTree { let ExampleProps { name, pending, count } = cx.props; cx.render(html! {
@@ -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) -> VNode { +fn Example(cx: Context) -> DomTree { cx.render(rsx! { div { // cx derefs to props so you can access fields directly diff --git a/docs/main-concepts/04-hooks.md b/docs/main-concepts/04-hooks.md index edb62662a..c8741a9fd 100644 --- a/docs/main-concepts/04-hooks.md +++ b/docs/main-concepts/04-hooks.md @@ -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! { diff --git a/docs/main-concepts/05-context-api.md b/docs/main-concepts/05-context-api.md index 2c3a535da..319c4f8ee 100644 --- a/docs/main-concepts/05-context-api.md +++ b/docs/main-concepts/05-context-api.md @@ -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::(); let text = some_cx.select("some_selector"); html! {
"{text}"
} } -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(); diff --git a/docs/main-concepts/06-subscription-api.md b/docs/main-concepts/06-subscription-api.md index 3dea9a730..5db05695f 100644 --- a/docs/main-concepts/06-subscription-api.md +++ b/docs/main-concepts/06-subscription-api.md @@ -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 diff --git a/docs/main-concepts/10-concurrent-mode.md b/docs/main-concepts/10-concurrent-mode.md index 990535704..c14d66c34 100644 --- a/docs/main-concepts/10-concurrent-mode.md +++ b/docs/main-concepts/10-concurrent-mode.md @@ -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? // diff --git a/docs/main-concepts/11-arena-memo.md b/docs/main-concepts/11-arena-memo.md index 4bfb9f353..4d6417a8b 100644 --- a/docs/main-concepts/11-arena-memo.md +++ b/docs/main-concepts/11-arena-memo.md @@ -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! { <> @@ -41,7 +41,7 @@ static TestComponent: FC<{ name: String }> = |cx| html! {
"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::(cx); html! {
diff --git a/docs/main-concepts/12-signals.md b/docs/main-concepts/12-signals.md index d9c4fcf83..aca4675c2 100644 --- a/docs/main-concepts/12-signals.md +++ b/docs/main-concepts/12-signals.md @@ -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; diff --git a/docs/main-concepts/9901-hello-world.md b/docs/main-concepts/9901-hello-world.md index a8f1f261d..f61ca4f09 100644 --- a/docs/main-concepts/9901-hello-world.md +++ b/docs/main-concepts/9901-hello-world.md @@ -8,7 +8,7 @@ struct MyProps { name: String } -fn Example(cx: Context) -> VNode { +fn Example(cx: Context) -> DomTree { cx.render(html! {
"Hello {cx.name}!"
}) diff --git a/docs/platforms/04-concurrency.md b/docs/platforms/04-concurrency.md index 3c27210f1..c1b2fb12d 100644 --- a/docs/platforms/04-concurrency.md +++ b/docs/platforms/04-concurrency.md @@ -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 diff --git a/docs/platforms/05-liveview.md b/docs/platforms/05-liveview.md index 49fdeead5..547d5f91a 100644 --- a/docs/platforms/05-liveview.md +++ b/docs/platforms/05-liveview.md @@ -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 diff --git a/docs/platforms/06-components.md b/docs/platforms/06-components.md index 3e39612bc..b6b02c0fb 100644 --- a/docs/platforms/06-components.md +++ b/docs/platforms/06-components.md @@ -8,7 +8,7 @@ struct MyProps { name: String } -fn Example(cx: Context) -> VNode { +fn Example(cx: Context) -> DomTree { html! {
"Hello {cx.cx.name}!"
} } ``` @@ -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! {
"Hello {name}!"
} } diff --git a/examples/_examples/basic.rs b/examples/_examples/basic.rs index 66537a379..e70d3de5a 100644 --- a/examples/_examples/basic.rs +++ b/examples/_examples/basic.rs @@ -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" diff --git a/examples/_examples/calculator.rs b/examples/_examples/calculator.rs index 769d39db7..0ba9ad7f8 100644 --- a/examples/_examples/calculator.rs +++ b/examples/_examples/calculator.rs @@ -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::().unwrap().separated_string(); diff --git a/examples/_examples/context.rs b/examples/_examples/context.rs index 4a490334f..dbed8057a 100644 --- a/examples/_examples/context.rs +++ b/examples/_examples/context.rs @@ -52,7 +52,7 @@ struct ButtonProps { id: u8, } -fn CustomButton(cx: Context) -> VNode { +fn CustomButton(cx: Context) -> DomTree { let names = cx.use_context::(); let name = names.0[cx.id as usize]; diff --git a/examples/_examples/deep.rs b/examples/_examples/deep.rs index bec2dd5fd..cfc1388fd 100644 --- a/examples/_examples/deep.rs +++ b/examples/_examples/deep.rs @@ -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) -> VNode { + pub fn CustomB(cx: Context) -> 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) -> VNode { + fn CustomC(cx: Context) -> DomTree { cx.render(rsx! { div { class: "m-8" diff --git a/examples/_examples/demoday.rs b/examples/_examples/demoday.rs index a77392ff8..ee54d06d3 100644 --- a/examples/_examples/demoday.rs +++ b/examples/_examples/demoday.rs @@ -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" diff --git a/examples/_examples/derive.rs b/examples/_examples/derive.rs index bc6526155..02259a111 100644 --- a/examples/_examples/derive.rs +++ b/examples/_examples/derive.rs @@ -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" diff --git a/examples/_examples/helloworld.rs b/examples/_examples/helloworld.rs index cda9c4d1a..5a0ae6010 100644 --- a/examples/_examples/helloworld.rs +++ b/examples/_examples/helloworld.rs @@ -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!" } }) diff --git a/examples/_examples/list.rs b/examples/_examples/list.rs index 93260d300..f8415902a 100644 --- a/examples/_examples/list.rs +++ b/examples/_examples/list.rs @@ -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()); diff --git a/examples/_examples/listy.rs b/examples/_examples/listy.rs index 97edebf99..4515e4db2 100644 --- a/examples/_examples/listy.rs +++ b/examples/_examples/listy.rs @@ -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}"} diff --git a/examples/_examples/model.rs b/examples/_examples/model.rs index eae311b72..0b70003dd 100644 --- a/examples/_examples/model.rs +++ b/examples/_examples/model.rs @@ -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}" diff --git a/examples/_examples/query.rs b/examples/_examples/query.rs index d7198fa10..40cca683a 100644 --- a/examples/_examples/query.rs +++ b/examples/_examples/query.rs @@ -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! { diff --git a/examples/_examples/rsxt.rs b/examples/_examples/rsxt.rs index bd416f98d..6fb139278 100644 --- a/examples/_examples/rsxt.rs +++ b/examples/_examples/rsxt.rs @@ -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 PartialEq for ButtonProps<'_, F> { struct PlaceholderProps { val: &'static str, } -fn Placeholder(cx: Context) -> VNode { +fn Placeholder(cx: Context) -> DomTree { cx.render(rsx! { div { "child: {cx.val}" diff --git a/examples/_examples/todomvc-nodeps.rs b/examples/_examples/todomvc-nodeps.rs index 793a787f3..d50a23a63 100644 --- a/examples/_examples/todomvc-nodeps.rs +++ b/examples/_examples/todomvc-nodeps.rs @@ -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::>::new()); let (filter, set_filter) = use_state_classic(cx, || FilterState::All); @@ -99,7 +99,7 @@ pub struct TodoEntryProps { item: Rc, } -pub fn TodoEntry(cx: Context) -> VNode { +pub fn TodoEntry(cx: Context) -> DomTree { let (is_editing, set_is_editing) = use_state_classic(cx, || false); let contents = ""; let todo = TodoItem { diff --git a/examples/_examples/todomvc/filtertoggles.rs b/examples/_examples/todomvc/filtertoggles.rs index 73ed77533..b55366552 100644 --- a/examples/_examples/todomvc/filtertoggles.rs +++ b/examples/_examples/todomvc/filtertoggles.rs @@ -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()); diff --git a/examples/_examples/todomvc/todoitem.rs b/examples/_examples/todomvc/todoitem.rs index d050fa361..431b68445 100644 --- a/examples/_examples/todomvc/todoitem.rs +++ b/examples/_examples/todomvc/todoitem.rs @@ -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); diff --git a/examples/_examples/todomvc/todolist.rs b/examples/_examples/todomvc/todolist.rs index 8badc38b4..cf4c48fac 100644 --- a/examples/_examples/todomvc/todolist.rs +++ b/examples/_examples/todomvc/todolist.rs @@ -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::::new()); let filter = use_atom(&cx, &FILTER); diff --git a/examples/_examples/todomvc_simple.rs b/examples/_examples/todomvc_simple.rs index 5f8b0484f..2ba4495b0 100644 --- a/examples/_examples/todomvc_simple.rs +++ b/examples/_examples/todomvc_simple.rs @@ -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::>::new()); let (filter, set_filter) = use_state_classic(cx, || FilterState::All); @@ -102,7 +102,7 @@ pub struct TodoEntryProps { item: Rc, } -pub fn TodoEntry(cx: Context) -> VNode { +pub fn TodoEntry(cx: Context) -> 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) -> VNode { )) } -pub fn FilterToggles(cx: Context<()>) -> VNode { +pub fn FilterToggles(cx: Context<()>) -> DomTree { let toggles = [ ("All", "", FilterState::All), ("Active", "active", FilterState::Active), diff --git a/examples/_examples/todomvcsingle.rs b/examples/_examples/todomvcsingle.rs index 434eb299f..94108974d 100644 --- a/examples/_examples/todomvcsingle.rs +++ b/examples/_examples/todomvcsingle.rs @@ -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" diff --git a/examples/calculator.rs b/examples/calculator.rs index 0d21ffb76..67dc79be4 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -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) -> VNode { +fn CalculatorDisplay(cx: Context) -> DomTree { use separator::Separatable; // Todo, add float support to the num-format crate let formatted = cx.val.separated_string(); diff --git a/examples/examples/calculator.rs b/examples/examples/calculator.rs index e33e66d81..ebebf218b 100644 --- a/examples/examples/calculator.rs +++ b/examples/examples/calculator.rs @@ -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}" diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index a2a7ea5d0..a7ee8588d 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -92,7 +92,7 @@ struct ActionButtonProps { id: &'static str, action: F, } -fn ActionButton(cx: Context>) -> VNode +fn ActionButton(cx: Context>) -> DomTree where F: Fn(MouseEvent), { @@ -110,7 +110,7 @@ struct RowProps { row_id: usize, label: Rc, } -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}" } diff --git a/examples/model.rs b/examples/model.rs index f27f9754b..7ddd36854 100644 --- a/examples/model.rs +++ b/examples/model.rs @@ -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}" diff --git a/examples/reference/memo.rs b/examples/reference/memo.rs index 92c830ebd..b9b1985d5 100644 --- a/examples/reference/memo.rs +++ b/examples/reference/memo.rs @@ -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)() } }) diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 7e4399800..ba2e9b2f5 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -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) -> VNode { +pub fn Taller(cx: Context) -> DomTree { let b = true; todo!() } diff --git a/examples/showcase.rs b/examples/showcase.rs index ed922cabf..12652c887 100644 --- a/examples/showcase.rs +++ b/examples/showcase.rs @@ -33,7 +33,7 @@ struct ScrollSelectorProps<'a> { onselect: &'a dyn Fn(Option), } -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 { diff --git a/examples/todomvc.rs b/examples/todomvc.rs index b4a4a2fd8..988da23e5 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -91,7 +91,7 @@ pub struct TodoEntryProps { todo: std::rc::Rc, } -pub fn TodoEntry(cx: Context) -> VNode { +pub fn TodoEntry(cx: Context) -> DomTree { let TodoEntryProps { todo } = *cx; let is_editing = use_state(cx, || false); let contents = ""; diff --git a/notes/SOLVEDPROBLEMS.md b/notes/SOLVEDPROBLEMS.md index ba2a168d1..7f501822e 100644 --- a/notes/SOLVEDPROBLEMS.md +++ b/notes/SOLVEDPROBLEMS.md @@ -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! {
"Hello, {name}!"
} } ``` @@ -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! {
"Hello, {name}!"
} } ``` @@ -71,7 +71,7 @@ mod Example { name: String } - fn component(cx: &Context) -> VNode { + fn component(cx: &Context) -> DomTree { html! {
"Hello, {name}!"
} } } @@ -84,7 +84,7 @@ struct Props { name: String } -fn component(cx: &Context) -> VNode { +fn component(cx: &Context) -> DomTree { html! {
"Hello, {name}!"
} } ``` @@ -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! {
"Hello, {name}!"
} } @@ -105,7 +105,7 @@ mod Example { struct Props { name: String } - fn component(cx: &Context) -> VNode { + fn component(cx: &Context) -> DomTree { html! {
"Hello, {name}!"
} } } @@ -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 } -fn Example<'src>(cx: Context<'src, ()>) -> VNode<'src> { +fn Example<'src>(cx: Context<'src, ()>) -> DomTree<'src> { let val: &'b ContextGuard = (&'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 { } ``` diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index db7d3d268..6cee65f8d 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -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) -> VNode { +/// pub fn Taller(cx: Context) -> DomTree { /// let b = true; /// todo!() /// } diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 34a811347..2b58ae251 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": true + "rust-analyzer.inlayHints.enable": false } diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index cfe98b9bc..441924326 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -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, + ) + })) +}; diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index d0b20241c..7959e0f44 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -20,7 +20,7 @@ struct ListItem { age: u32, } -fn app(cx: Context) -> VNode { +fn app(cx: Context) -> 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, } -fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> VNode { +fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> DomTree { cx.render(LazyNodes::new(move |__cx| todo!())) } diff --git a/packages/core/examples/fragment_from_iter.rs b/packages/core/examples/fragment_from_iter.rs new file mode 100644 index 000000000..c559eabba --- /dev/null +++ b/packages/core/examples/fragment_from_iter.rs @@ -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, + ) + })) +} diff --git a/packages/core/examples/macrosrc.rs b/packages/core/examples/macrosrc.rs index b0b0977c7..2a8dfe4cd 100644 --- a/packages/core/examples/macrosrc.rs +++ b/packages/core/examples/macrosrc.rs @@ -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) -> VNode<'a> { +// fn FullySuspended<'a>(cx: &'a Context) -> DomTree<'a> { // cx.suspend(async { // let i: i32 = 0; diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index c23e3d631..c6acdb721 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -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(_: FC) -> 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()))) } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index dd85815db..e2461993f 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -133,13 +133,13 @@ impl<'src, P> Context<'src, P> { pub fn render) -> 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>, } -type SuspendedCallback = Box Fn(Context<'a, ()>) -> VNode<'a>>; +type SuspendedCallback = Box 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 + '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>> = 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) -> 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 ()>, diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 8b4dc7f33..b9d9f20bc 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -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

= fn(Context

) -> VNode; + pub type DomTree<'a> = Option>; + pub type FC

= fn(Context

) -> DomTree; pub use dioxus_core_macro::{format_args_f, html, rsx}; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index d090f866f..e72ec1b7a 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -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>, - pub(crate) caller: Rc VNode>, + pub(crate) caller: Rc 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

, raw_props: *const (), - ) -> Rc Fn(&'r Scope) -> VNode<'r>> { - type Captured<'a> = Rc Fn(&'r Scope) -> VNode<'r> + 'a>; - let caller: Captured = Rc::new(move |scp: &Scope| -> VNode { + ) -> Rc Fn(&'r Scope) -> DomTree<'r>> { + type Captured<'a> = Rc 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

= 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), diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index d4462e288..ba21631e5 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -53,7 +53,7 @@ pub struct Scope { type EventChannel = Rc; // 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>>; @@ -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> = 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, + }), + } +} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 29f82421e..21eb7bc3b 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -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::(*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> = (&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 diff --git a/packages/web/examples/blah.rs b/packages/web/examples/blah.rs index e4b0f895a..a12ec2bb0 100644 --- a/packages/web/examples/blah.rs +++ b/packages/web/examples/blah.rs @@ -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| { diff --git a/packages/web/examples/demo.rs b/packages/web/examples/demo.rs index 5c6f99f9a..95bda2267 100644 --- a/packages/web/examples/demo.rs +++ b/packages/web/examples/demo.rs @@ -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| { diff --git a/packages/web/src/old/interpreter.rs b/packages/web/src/old/interpreter.rs deleted file mode 100644 index c6410922e..000000000 --- a/packages/web/src/old/interpreter.rs +++ /dev/null @@ -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); -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, - - pub(crate) root: Element, - - pub(crate) temporaries: FxHashMap, - - pub(crate) document: Document, - - pub(crate) events: EventDelegater, - - pub(crate) current_known: Option, - - // 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)>, - - // Map of callback_id to component index and listener id - callback_map: FxHashMap, - - 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::() - .expect("not a valid element"); - - let typ = event.type_(); - - let gi_id: Option = target - .get_attribute(&format!("dioxus-giid-{}", typ)) - .and_then(|v| v.parse().ok()); - - let gi_gen: Option = target - .get_attribute(&format!("dioxus-gigen-{}", typ)) - .and_then(|v| v.parse().ok()); - - let li_idx: Option = 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); - - 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>, - -#[derive(Debug, Default)] -pub struct Stack { - list: Vec, -} - -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::() { - sibling.remove(); - } - sibling = temp; - } - if let Some(node) = node.dyn_ref::() { - node.remove(); - } - } - - // 2 - Edit::ReplaceWith => { - let new_node = self.stack.pop(); - let old_node = self.stack.pop(); - - if old_node.has_type::() { - old_node - .dyn_ref::() - .unwrap() - .replace_with_with_node_1(&new_node) - .unwrap(); - } else if old_node.has_type::() { - old_node - .dyn_ref::() - .unwrap() - .replace_with_with_node_1(&new_node) - .unwrap(); - } else if old_node.has_type::() { - old_node - .dyn_ref::() - .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::() { - node.set_attribute(name, value).unwrap(); - } - if let Some(node) = node.dyn_ref::() { - 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::() { - if name == "selected" { - node.set_selected(true); - } - } - } - - // 4 - Edit::RemoveAttribute { name } => { - let node = self.stack.top(); - if let Some(node) = node.dyn_ref::() { - node.remove_attribute(name).unwrap(); - } - if let Some(node) = node.dyn_ref::() { - // 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::() { - 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::() - .unwrap(); - } - - self.stack.push( - self.document - .create_text_node(text) - .dyn_into::() - .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::() - .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::() - .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::() { - // 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::() { - // 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::() - .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::() { - child.remove(); - } - } - - // 21 - Edit::SetClass { class_name } => { - if let Some(el) = self.stack.top().dyn_ref::() { - 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::() - .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 = (&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, - } -} diff --git a/packages/web/src/old/lib.rs b/packages/web/src/old/lib.rs deleted file mode 100644 index 39a857565..000000000 --- a/packages/web/src/old/lib.rs +++ /dev/null @@ -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 = 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 { - /// 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>, -} - -impl CreatedNode { - pub fn without_closures>(node: N) -> Self { - CreatedNode { - node: node.into(), - closures: HashMap::with_capacity(0), - } - } -} - -impl Deref for CreatedNode { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.node - } -} - -impl From> for CreatedNode { - fn from(other: CreatedNode) -> CreatedNode { - 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 { - 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>> is our js_sys::Closure. Stored this way to allow us to store -/// any Closure regardless of the arguments. -pub type DynClosure = Rc>; - -/// We need a custom implementation of fmt::Debug since JsValue doesn't -/// implement debug. -pub struct Events(pub HashMap); - -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) - } -} diff --git a/packages/web/src/old/virtual_node_test_utils.rs b/packages/web/src/old/virtual_node_test_utils.rs deleted file mode 100644 index b445b725e..000000000 --- a/packages/web/src/old/virtual_node_test_utils.rs +++ /dev/null @@ -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! {

- /// {"Hi!"} - /// {"There!!"} - /// - ///
}; - /// - /// 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! {
- /// {"Hi!"} - /// {"There!!"} - /// - ///
}; - /// - /// 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 - //
- // // This node gets picked up - // - // - // // This node gets picked up - // - // { "hello there :)!" } - // - //
- //
- // }; - // - // 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() - } - }) -} diff --git a/src/lib.rs b/src/lib.rs index ee3c5f041..7c9f6c3f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ //! ``` //! use dioxus::prelude::*; //! -//! fn Example(cx: Context<()>) -> VNode { +//! fn Example(cx: Context<()>) -> DomTree { //! html! {
"Hello, world!"
} //! } //! ``` @@ -44,7 +44,7 @@ //! #[derive(Props)] //! struct Props { name: String } //! -//! fn Example(cx: Context) -> VNode { +//! fn Example(cx: Context) -> DomTree { //! html! {
"Hello {cx.props.name}!"
} //! } //! ``` @@ -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! {
"Hello {cx.props.name}!"
} //! } //! ```