diff --git a/README.md b/README.md index c438ea86..bb922716 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 14939f05..ad91849c 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 6020baba..e4f377d9 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 edb62662..c8741a9f 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 2c3a535d..319c4f8e 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 3dea9a73..5db05695 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 99053570..c14d66c3 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 4bfb9f35..4d6417a8 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 d9c4fcf8..aca4675c 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 a8f1f261..f61ca4f0 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 3c27210f..c1b2fb12 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 49fdeead..547d5f91 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 3e39612b..b6b02c0f 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 66537a37..e70d3de5 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 769d39db..0ba9ad7f 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 4a490334..dbed8057 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 bec2dd5f..cfc1388f 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 a77392ff..ee54d06d 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 bc652615..02259a11 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 cda9c4d1..5a0ae601 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 93260d30..f8415902 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 97edebf9..4515e4db 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 eae311b7..0b70003d 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 d7198fa1..40cca683 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 bd416f98..6fb13927 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 793a787f..d50a23a6 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 73ed7753..b5536655 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 d050fa36..431b6844 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 8badc38b..cf4c48fa 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 5f8b0484..2ba4495b 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 434eb299..94108974 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 0d21ffb7..67dc79be 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 e33e66d8..ebebf218 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 a2a7ea5d..a7ee8588 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 f27f9754..7ddd3685 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 92c830eb..b9b1985d 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 7e439980..ba2e9b2f 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 ed922cab..12652c88 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 b4a4a2fd..988da23e 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 ba2a168d..7f501822 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 db7d3d26..6cee65f8 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 34a81134..2b58ae25 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 cfe98b9b..44192432 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 d0b20241..7959e0f4 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 00000000..c559eabb --- /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 b0b0977c..2a8dfe4c 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 c23e3d63..c6acdb72 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 dd85815d..e2461993 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 8b4dc7f3..b9d9f20b 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 d090f866..e72ec1b7 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 d4462e28..ba21631e 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 29f82421..21eb7bc3 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 e4b0f895..a12ec2bb 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 5c6f99f9..95bda226 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 c6410922..00000000 --- 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 39a85756..00000000 --- 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 b445b725..00000000 --- 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 ee3c5f04..7c9f6c3f 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}!"
} //! } //! ```