mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 14:44:12 +00:00
Merge pull request #13 from jkelleyrtp/jk/none-handling
feat: amazingly awesome error handling
This commit is contained in:
commit
e86b031c93
58 changed files with 248 additions and 1219 deletions
57
README.md
57
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.
|
||||
|
||||
|
|
|
@ -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!" }
|
||||
})
|
||||
|
|
|
@ -14,7 +14,7 @@ You'll want to write RSX where you can, and in a future release we'll have a too
|
|||
#[derive(PartialEq, Props)]
|
||||
struct ExampleProps { name: &str, pending: bool, count: i32 }
|
||||
|
||||
fn Example(cx: Context<ExampleProps> ) -> VNode {
|
||||
fn Example(cx: Context<ExampleProps> ) -> DomTree {
|
||||
let ExampleProps { name, pending, count } = cx.props;
|
||||
cx.render(html! {
|
||||
<div>
|
||||
|
@ -35,7 +35,7 @@ The Dioxus VSCode extension will eventually provide a macro to convert a selecti
|
|||
It's also a bit easier on the eyes 🙂 than HTML.
|
||||
|
||||
```rust
|
||||
fn Example(cx: Context<ExampleProps>) -> VNode {
|
||||
fn Example(cx: Context<ExampleProps>) -> DomTree {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
// cx derefs to props so you can access fields directly
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -5,19 +5,19 @@
|
|||
// Only one context can be associated with any given component
|
||||
// This is known as "exposed state". Children can access this context,
|
||||
// but will not be automatically subscribed.
|
||||
fn ContextCreate(cx: &mut Context<()>) -> VNode {
|
||||
fn ContextCreate(cx: &mut Context<()>) -> DomTree {
|
||||
let context = cx.set_context(|| CustomContext::new());
|
||||
html! { <> {cx.children()} </> }
|
||||
}
|
||||
|
||||
fn ContextRead(cx: &mut Context<()>) -> VNode {
|
||||
fn ContextRead(cx: &mut Context<()>) -> DomTree {
|
||||
// Panics if context is not available
|
||||
let some_cx = cx.get_context::<CustomContext>();
|
||||
let text = some_cx.select("some_selector");
|
||||
html! { <div> "{text}" </div> }
|
||||
}
|
||||
|
||||
fn Subscription(cx: &mut Context<()>) -> VNode {
|
||||
fn Subscription(cx: &mut Context<()>) -> DomTree {
|
||||
// Open a "port" on the component for actions to trigger a re-evaluation
|
||||
let subscription = cx.new_subscription();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
//
|
||||
|
|
|
@ -11,7 +11,7 @@ https://dmitripavlutin.com/use-react-memo-wisely/
|
|||
This behavior is defined as an implicit attribute to user components. When in React land you might wrap a component is `react.memo`, Dioxus components are automatically memoized via an implicit attribute. You can manually configure this behavior on any component with "nomemo" to disable memoization.
|
||||
|
||||
```rust
|
||||
fn test() -> VNode {
|
||||
fn test() -> DomTree {
|
||||
html! {
|
||||
<>
|
||||
<SomeComponent nomemo />
|
||||
|
@ -41,7 +41,7 @@ static TestComponent: FC<{ name: String }> = |cx| html! { <div> "Hello {name}" <
|
|||
Take a component likes this:
|
||||
|
||||
```rust
|
||||
fn test(cx: Context<()>) -> VNode {
|
||||
fn test(cx: Context<()>) -> DomTree {
|
||||
let Bundle { alpha, beta, gamma } = use_context::<SomeContext>(cx);
|
||||
html! {
|
||||
<div>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -8,7 +8,7 @@ struct MyProps {
|
|||
name: String
|
||||
}
|
||||
|
||||
fn Example(cx: Context<MyProps>) -> VNode {
|
||||
fn Example(cx: Context<MyProps>) -> DomTree {
|
||||
cx.render(html! {
|
||||
<div> "Hello {cx.name}!" </div>
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,7 +8,7 @@ struct MyProps {
|
|||
name: String
|
||||
}
|
||||
|
||||
fn Example(cx: Context<MyProps>) -> VNode {
|
||||
fn Example(cx: Context<MyProps>) -> DomTree {
|
||||
html! { <div> "Hello {cx.cx.name}!" </div> }
|
||||
}
|
||||
```
|
||||
|
@ -18,7 +18,7 @@ Here, the `Context` object is used to access hook state, create subscriptions, a
|
|||
```rust
|
||||
// A very terse component!
|
||||
#[fc]
|
||||
fn Example(cx: Context, name: String) -> VNode {
|
||||
fn Example(cx: Context, name: String) -> DomTree {
|
||||
html! { <div> "Hello {name}!" </div> }
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -160,7 +160,7 @@ struct CalculatorKeyProps<'a> {
|
|||
onclick: &'a dyn Fn(MouseEvent),
|
||||
}
|
||||
|
||||
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
|
||||
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
class: "calculator-key {cx.name}"
|
||||
|
@ -175,7 +175,7 @@ struct CalculatorDisplayProps<'a> {
|
|||
val: &'a str,
|
||||
}
|
||||
|
||||
fn CalculatorDisplay<'a>(cx: Context<'a, CalculatorDisplayProps>) -> VNode<'a> {
|
||||
fn CalculatorDisplay<'a>(cx: Context<'a, CalculatorDisplayProps>) -> DomTree<'a> {
|
||||
use separator::Separatable;
|
||||
// Todo, add float support to the num-format crate
|
||||
let formatted = cx.val.parse::<f64>().unwrap().separated_string();
|
||||
|
|
|
@ -52,7 +52,7 @@ struct ButtonProps {
|
|||
id: u8,
|
||||
}
|
||||
|
||||
fn CustomButton(cx: Context<ButtonProps>) -> VNode {
|
||||
fn CustomButton(cx: Context<ButtonProps>) -> DomTree {
|
||||
let names = cx.use_context::<CustomContext>();
|
||||
let name = names.0[cx.id as usize];
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ fn main() {
|
|||
wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(CustomA))
|
||||
}
|
||||
|
||||
fn CustomA(cx: Context<()>) -> VNode {
|
||||
fn CustomA(cx: Context<()>) -> DomTree {
|
||||
let (val, set_val) = use_state_classic(cx, || "abcdef".to_string() as String);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
@ -43,7 +43,7 @@ mod components {
|
|||
val: String,
|
||||
}
|
||||
|
||||
pub fn CustomB(cx: Context<PropsB>) -> VNode {
|
||||
pub fn CustomB(cx: Context<PropsB>) -> DomTree {
|
||||
let (first, last) = cx.val.split_at(3);
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
@ -64,7 +64,7 @@ mod components {
|
|||
val: String,
|
||||
}
|
||||
|
||||
fn CustomC(cx: Context<PropsC>) -> VNode {
|
||||
fn CustomC(cx: Context<PropsC>) -> DomTree {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
class: "m-8"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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!" }
|
||||
})
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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}"}
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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! {
|
||||
|
|
|
@ -57,7 +57,7 @@ struct ButtonProps<'src, F: Fn(MouseEvent)> {
|
|||
handler: F,
|
||||
}
|
||||
|
||||
fn CustomButton<'a, F: Fn(MouseEvent)>(cx: Context<'a, ButtonProps<'a, F>>) -> VNode {
|
||||
fn CustomButton<'a, F: Fn(MouseEvent)>(cx: Context<'a, ButtonProps<'a, F>>) -> DomTree {
|
||||
cx.render(rsx!{
|
||||
button {
|
||||
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
|
||||
|
@ -77,7 +77,7 @@ impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
|
|||
struct PlaceholderProps {
|
||||
val: &'static str,
|
||||
}
|
||||
fn Placeholder(cx: Context<PlaceholderProps>) -> VNode {
|
||||
fn Placeholder(cx: Context<PlaceholderProps>) -> DomTree {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"child: {cx.val}"
|
||||
|
|
|
@ -25,7 +25,7 @@ pub struct TodoItem {
|
|||
pub contents: String,
|
||||
}
|
||||
|
||||
pub fn App(cx: Context<()>) -> VNode {
|
||||
pub fn App(cx: Context<()>) -> DomTree {
|
||||
let (draft, set_draft) = use_state_classic(cx, || "".to_string());
|
||||
let (todos, set_todos) = use_state_classic(cx, || HashMap::<uuid::Uuid, Rc<TodoItem>>::new());
|
||||
let (filter, set_filter) = use_state_classic(cx, || FilterState::All);
|
||||
|
@ -99,7 +99,7 @@ pub struct TodoEntryProps {
|
|||
item: Rc<TodoItem>,
|
||||
}
|
||||
|
||||
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> VNode {
|
||||
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> DomTree {
|
||||
let (is_editing, set_is_editing) = use_state_classic(cx, || false);
|
||||
let contents = "";
|
||||
let todo = TodoItem {
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
};
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
pub fn TodoList(cx: Context<()>) -> VNode {
|
||||
pub fn TodoList(cx: Context<()>) -> DomTree {
|
||||
let (draft, set_draft) = use_state(cx, || "".to_string());
|
||||
let (todos, _) = use_state(cx, || Vec::<TodoItem>::new());
|
||||
let filter = use_atom(&cx, &FILTER);
|
||||
|
|
|
@ -28,7 +28,7 @@ pub struct TodoItem {
|
|||
// =======================
|
||||
// Components
|
||||
// =======================
|
||||
pub fn App(cx: Context<()>) -> VNode {
|
||||
pub fn App(cx: Context<()>) -> DomTree {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
id: "app"
|
||||
|
@ -53,7 +53,7 @@ pub fn App(cx: Context<()>) -> VNode {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn TodoList(cx: Context<()>) -> VNode {
|
||||
pub fn TodoList(cx: Context<()>) -> DomTree {
|
||||
let (draft, set_draft) = use_state_classic(cx, || "".to_string());
|
||||
let (todos, set_todos) = use_state_classic(cx, || HashMap::<uuid::Uuid, Rc<TodoItem>>::new());
|
||||
let (filter, set_filter) = use_state_classic(cx, || FilterState::All);
|
||||
|
@ -102,7 +102,7 @@ pub struct TodoEntryProps {
|
|||
item: Rc<TodoItem>,
|
||||
}
|
||||
|
||||
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> VNode {
|
||||
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> DomTree {
|
||||
let (is_editing, set_is_editing) = use_state_classic(cx, || false);
|
||||
let contents = "";
|
||||
let todo = TodoItem {
|
||||
|
@ -128,7 +128,7 @@ pub fn TodoEntry(cx: Context<TodoEntryProps>) -> VNode {
|
|||
))
|
||||
}
|
||||
|
||||
pub fn FilterToggles(cx: Context<()>) -> VNode {
|
||||
pub fn FilterToggles(cx: Context<()>) -> DomTree {
|
||||
let toggles = [
|
||||
("All", "", FilterState::All),
|
||||
("Active", "active", FilterState::Active),
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -123,7 +123,7 @@ struct CalculatorKeyProps<'a> {
|
|||
onclick: &'a dyn Fn(MouseEvent),
|
||||
}
|
||||
|
||||
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
|
||||
fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> DomTree<'a> {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
class: "calculator-key {cx.name}"
|
||||
|
@ -138,7 +138,7 @@ struct CalculatorDisplayProps {
|
|||
val: f64,
|
||||
}
|
||||
|
||||
fn CalculatorDisplay(cx: Context<CalculatorDisplayProps>) -> VNode {
|
||||
fn CalculatorDisplay(cx: Context<CalculatorDisplayProps>) -> DomTree {
|
||||
use separator::Separatable;
|
||||
// Todo, add float support to the num-format crate
|
||||
let formatted = cx.val.separated_string();
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -92,7 +92,7 @@ struct ActionButtonProps<F: Fn(MouseEvent)> {
|
|||
id: &'static str,
|
||||
action: F,
|
||||
}
|
||||
fn ActionButton<F>(cx: Context<ActionButtonProps<F>>) -> VNode
|
||||
fn ActionButton<F>(cx: Context<ActionButtonProps<F>>) -> DomTree
|
||||
where
|
||||
F: Fn(MouseEvent),
|
||||
{
|
||||
|
@ -110,7 +110,7 @@ struct RowProps {
|
|||
row_id: usize,
|
||||
label: Rc<str>,
|
||||
}
|
||||
fn Row<'a>(cx: Context<'a, RowProps>) -> VNode {
|
||||
fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree {
|
||||
cx.render(rsx! {
|
||||
tr {
|
||||
td { class:"col-md-1", "{cx.row_id}" }
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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)() }
|
||||
})
|
||||
|
|
|
@ -183,7 +183,7 @@ mod baller {
|
|||
pub struct BallerProps {}
|
||||
|
||||
/// This component totally balls
|
||||
pub fn Baller(cx: Context<()>) -> VNode {
|
||||
pub fn Baller(cx: Context<()>) -> DomTree {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ pub struct TallerProps {
|
|||
}
|
||||
|
||||
/// This component is taller than most :)
|
||||
pub fn Taller(cx: Context<TallerProps>) -> VNode {
|
||||
pub fn Taller(cx: Context<TallerProps>) -> DomTree {
|
||||
let b = true;
|
||||
todo!()
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ struct ScrollSelectorProps<'a> {
|
|||
onselect: &'a dyn Fn(Option<usize>),
|
||||
}
|
||||
|
||||
fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> VNode<'a> {
|
||||
fn ScrollSelector<'a>(cx: Context<'a, ScrollSelectorProps>) -> DomTree<'a> {
|
||||
let selection_list = (&REFERENCES).iter().enumerate().map(|(id, _)| {
|
||||
rsx! {
|
||||
li {
|
||||
|
|
|
@ -91,7 +91,7 @@ pub struct TodoEntryProps {
|
|||
todo: std::rc::Rc<TodoItem>,
|
||||
}
|
||||
|
||||
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> VNode {
|
||||
pub fn TodoEntry(cx: Context<TodoEntryProps>) -> DomTree {
|
||||
let TodoEntryProps { todo } = *cx;
|
||||
let is_editing = use_state(cx, || false);
|
||||
let contents = "";
|
||||
|
|
|
@ -17,7 +17,7 @@ Originally the syntax of the FC macro was meant to look like:
|
|||
|
||||
```rust
|
||||
#[fc]
|
||||
fn example(cx: &Context<{ name: String }>) -> VNode {
|
||||
fn example(cx: &Context<{ name: String }>) -> DomTree {
|
||||
html! { <div> "Hello, {name}!" </div> }
|
||||
}
|
||||
```
|
||||
|
@ -26,7 +26,7 @@ fn example(cx: &Context<{ name: String }>) -> VNode {
|
|||
|
||||
```rust
|
||||
#[fc]
|
||||
fn example(cx: &Context, name: String) -> VNode {
|
||||
fn example(cx: &Context, name: String) -> DomTree {
|
||||
html! { <div> "Hello, {name}!" </div> }
|
||||
}
|
||||
```
|
||||
|
@ -71,7 +71,7 @@ mod Example {
|
|||
name: String
|
||||
}
|
||||
|
||||
fn component(cx: &Context<Props>) -> VNode {
|
||||
fn component(cx: &Context<Props>) -> DomTree {
|
||||
html! { <div> "Hello, {name}!" </div> }
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ struct Props {
|
|||
name: String
|
||||
}
|
||||
|
||||
fn component(cx: &Context<Props>) -> VNode {
|
||||
fn component(cx: &Context<Props>) -> DomTree {
|
||||
html! { <div> "Hello, {name}!" </div> }
|
||||
}
|
||||
```
|
||||
|
@ -93,7 +93,7 @@ These definitions might be ugly, but the fc macro cleans it all up. The fc macro
|
|||
|
||||
```rust
|
||||
#[fc]
|
||||
fn example(cx: &Context, name: String) -> VNode {
|
||||
fn example(cx: &Context, name: String) -> DomTree {
|
||||
html! { <div> "Hello, {name}!" </div> }
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,7 @@ mod Example {
|
|||
struct Props {
|
||||
name: String
|
||||
}
|
||||
fn component(cx: &Context<Props>) -> VNode {
|
||||
fn component(cx: &Context<Props>) -> DomTree {
|
||||
html! { <div> "Hello, {name}!" </div> }
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ pub static Example: FC<()> = |cx| {
|
|||
// expands to...
|
||||
|
||||
pub static Example: FC<()> = |cx| {
|
||||
// This function converts a Fn(allocator) -> VNode closure to a VNode struct that will later be evaluated.
|
||||
// This function converts a Fn(allocator) -> DomTree closure to a VNode struct that will later be evaluated.
|
||||
html_macro_to_vnodetree(move |allocator| {
|
||||
let mut node0 = allocator.alloc(VElement::div);
|
||||
let node1 = allocator.alloc_text("blah");
|
||||
|
@ -216,7 +216,7 @@ struct ExampleContext {
|
|||
items: Vec<String>
|
||||
}
|
||||
|
||||
fn Example<'src>(cx: Context<'src, ()>) -> VNode<'src> {
|
||||
fn Example<'src>(cx: Context<'src, ()>) -> DomTree<'src> {
|
||||
let val: &'b ContextGuard<ExampleContext> = (&'b cx).use_context(|context: &'other ExampleContext| {
|
||||
// always select the last element
|
||||
context.items.last()
|
||||
|
@ -348,7 +348,7 @@ An update to the use_context subscription will mark the node as dirty. The node
|
|||
The FC layout was altered to make life easier for us inside the VirtualDom. The "view" function returns an unbounded VNode object. Calling the "view" function is unsafe under the hood, but prevents lifetimes from leaking out of the function call. Plus, it's easier to write. Because there are no lifetimes on the output (occur purely under the hood), we can escape needing to annotate them.
|
||||
|
||||
```rust
|
||||
fn component(cx: Context, props: &Props) -> VNode {
|
||||
fn component(cx: Context, props: &Props) -> DomTree {
|
||||
|
||||
}
|
||||
```
|
||||
|
|
|
@ -52,7 +52,7 @@ pub fn html_template(s: TokenStream) -> TokenStream {
|
|||
/// ```ignore
|
||||
///
|
||||
/// #[fc]
|
||||
/// fn Example(cx: Context, name: &str) -> VNode {
|
||||
/// fn Example(cx: Context, name: &str) -> DomTree {
|
||||
/// cx.render(rsx! { h1 {"hello {name}"} })
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -220,7 +220,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
|
|||
/// pub struct BallerProps {}
|
||||
///
|
||||
/// /// This component totally balls
|
||||
/// pub fn Baller(cx: Context<()>) -> VNode {
|
||||
/// pub fn Baller(cx: Context<()>) -> DomTree {
|
||||
/// todo!()
|
||||
/// }
|
||||
/// }
|
||||
|
@ -231,7 +231,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
|
|||
/// }
|
||||
///
|
||||
/// /// This component is taller than most :)
|
||||
/// pub fn Taller(cx: Context<TallerProps>) -> VNode {
|
||||
/// pub fn Taller(cx: Context<TallerProps>) -> DomTree {
|
||||
/// let b = true;
|
||||
/// todo!()
|
||||
/// }
|
||||
|
|
2
packages/core/.vscode/settings.json
vendored
2
packages/core/.vscode/settings.json
vendored
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.inlayHints.enable": true
|
||||
"rust-analyzer.inlayHints.enable": false
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}))
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ struct ListItem {
|
|||
age: u32,
|
||||
}
|
||||
|
||||
fn app(cx: Context<AppProps>) -> VNode {
|
||||
fn app(cx: Context<AppProps>) -> DomTree {
|
||||
// let (val, set_val) = use_state_classic(cx, || 0);
|
||||
|
||||
cx.render(LazyNodes::new(move |_nodecx| {
|
||||
|
@ -56,7 +56,7 @@ struct ChildProps {
|
|||
item_handler: Rc<dyn Fn(i32)>,
|
||||
}
|
||||
|
||||
fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> VNode {
|
||||
fn ChildItem<'a>(cx: Context<'a, ChildProps>) -> DomTree {
|
||||
cx.render(LazyNodes::new(move |__cx| todo!()))
|
||||
}
|
||||
|
||||
|
|
48
packages/core/examples/fragment_from_iter.rs
Normal file
48
packages/core/examples/fragment_from_iter.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
fn main() {}
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
fn App(cx: Context<()>) -> DomTree {
|
||||
//
|
||||
let vak = cx.use_suspense(
|
||||
|| async {},
|
||||
|c, res| {
|
||||
//
|
||||
c.render(LazyNodes::new(move |f| f.text(format_args!(""))))
|
||||
},
|
||||
);
|
||||
|
||||
let d1 = cx.render(LazyNodes::new(move |f| {
|
||||
f.raw_element(
|
||||
"div",
|
||||
None,
|
||||
&mut [],
|
||||
&[],
|
||||
f.bump().alloc([
|
||||
f.fragment_from_iter(vak),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
]),
|
||||
None,
|
||||
)
|
||||
}));
|
||||
|
||||
cx.render(LazyNodes::new(move |f| {
|
||||
f.raw_element(
|
||||
"div",
|
||||
None,
|
||||
&mut [],
|
||||
&[],
|
||||
f.bump().alloc([
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.fragment_from_iter(d1),
|
||||
]),
|
||||
None,
|
||||
)
|
||||
}))
|
||||
}
|
|
@ -57,7 +57,7 @@ fn main() {}
|
|||
// }
|
||||
// }
|
||||
|
||||
// fn component<'a>(cx: Context<'a, Props>) -> VNode<'a> {
|
||||
// fn component<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
|
||||
// // Write asynchronous rendering code that immediately returns a "suspended" VNode
|
||||
// // The concurrent API will then progress this component when the future finishes
|
||||
// // You can suspend the entire component, or just parts of it
|
||||
|
@ -82,7 +82,7 @@ fn main() {}
|
|||
// })
|
||||
// }
|
||||
|
||||
// fn BuilderComp<'a>(cx: Context<'a, Props>) -> VNode<'a> {
|
||||
// fn BuilderComp<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
|
||||
// // VNodes can be constructed via a builder or the html! macro
|
||||
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
|
||||
// // We can "view" them with Context for ultimate speed while inside components
|
||||
|
@ -96,7 +96,7 @@ fn main() {}
|
|||
// }
|
||||
|
||||
// #[fc]
|
||||
// fn EffcComp(cx: Context, name: &str) -> VNode {
|
||||
// fn EffcComp(cx: Context, name: &str) -> DomTree {
|
||||
// // VNodes can be constructed via a builder or the html! macro
|
||||
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
|
||||
// // We can "view" them with Context for ultimate speed while inside components
|
||||
|
@ -110,7 +110,7 @@ fn main() {}
|
|||
// })
|
||||
// }
|
||||
|
||||
// fn FullySuspended<'a>(cx: &'a Context<Props>) -> VNode<'a> {
|
||||
// fn FullySuspended<'a>(cx: &'a Context<Props>) -> DomTree<'a> {
|
||||
// cx.suspend(async {
|
||||
// let i: i32 = 0;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
||||
//! that ensures compile-time required and optional fields on cx.
|
||||
|
||||
use crate::innerlude::{Context, LazyNodes, VNode, FC};
|
||||
use crate::innerlude::{Context, DomTree, LazyNodes, VNode, FC};
|
||||
|
||||
pub trait Properties: Sized {
|
||||
type Builder;
|
||||
|
@ -51,6 +51,6 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
|||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
|
||||
#[allow(non_upper_case_globals, non_snake_case)]
|
||||
pub fn Fragment<'a>(cx: Context<'a, ()>) -> VNode<'a> {
|
||||
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
|
||||
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
|
||||
}
|
||||
|
|
|
@ -133,13 +133,13 @@ impl<'src, P> Context<'src, P> {
|
|||
pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
|
||||
self,
|
||||
lazy_nodes: LazyNodes<'src, F>,
|
||||
) -> VNode<'src> {
|
||||
) -> DomTree<'src> {
|
||||
let scope_ref = self.scope;
|
||||
let listener_id = &scope_ref.listener_idx;
|
||||
lazy_nodes.into_vnode(NodeFactory {
|
||||
Some(lazy_nodes.into_vnode(NodeFactory {
|
||||
scope_ref,
|
||||
listener_id,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// Store a value between renders
|
||||
|
@ -378,7 +378,7 @@ pub(crate) struct SuspenseHook {
|
|||
pub callback: SuspendedCallback,
|
||||
pub dom_node_id: Rc<Cell<RealDomNode>>,
|
||||
}
|
||||
type SuspendedCallback = Box<dyn for<'a> Fn(Context<'a, ()>) -> VNode<'a>>;
|
||||
type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
|
||||
|
||||
impl<'src, P> Context<'src, P> {
|
||||
/// Asynchronously render new nodes once the given future has completed.
|
||||
|
@ -395,11 +395,11 @@ impl<'src, P> Context<'src, P> {
|
|||
self,
|
||||
task_initializer: impl FnOnce() -> Fut,
|
||||
user_callback: Cb,
|
||||
) -> VNode<'src>
|
||||
) -> DomTree<'src>
|
||||
where
|
||||
Fut: Future<Output = Out> + 'static,
|
||||
Out: 'static,
|
||||
Cb: for<'a> Fn(Context<'a, ()>, &Out) -> VNode<'a> + 'static,
|
||||
Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
|
||||
{
|
||||
self.use_hook(
|
||||
move |hook_idx| {
|
||||
|
@ -410,7 +410,7 @@ impl<'src, P> Context<'src, P> {
|
|||
|
||||
let slot = value.clone();
|
||||
|
||||
let callback: SuspendedCallback = Box::new(move |ctx: Context<()>| {
|
||||
let callback: SuspendedCallback = Box::new(move |ctx: SuspendedContext| {
|
||||
let v: std::cell::Ref<Option<Box<dyn Any>>> = slot.as_ref().borrow();
|
||||
match v.as_ref() {
|
||||
Some(a) => {
|
||||
|
@ -420,13 +420,13 @@ impl<'src, P> Context<'src, P> {
|
|||
}
|
||||
None => {
|
||||
//
|
||||
VNode {
|
||||
Some(VNode {
|
||||
dom_id: RealDomNode::empty_cell(),
|
||||
key: None,
|
||||
kind: VNodeKind::Suspended {
|
||||
node: domnode.clone(),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -459,13 +459,31 @@ impl<'src, P> Context<'src, P> {
|
|||
scope: &self.scope,
|
||||
props: &(),
|
||||
};
|
||||
(&hook.callback)(cx)
|
||||
let csx = SuspendedContext { inner: cx };
|
||||
(&hook.callback)(csx)
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SuspendedContext<'a> {
|
||||
pub(crate) inner: Context<'a, ()>,
|
||||
}
|
||||
impl<'src> SuspendedContext<'src> {
|
||||
pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
|
||||
self,
|
||||
lazy_nodes: LazyNodes<'src, F>,
|
||||
) -> DomTree<'src> {
|
||||
let scope_ref = self.inner.scope;
|
||||
let listener_id = &scope_ref.listener_idx;
|
||||
Some(lazy_nodes.into_vnode(NodeFactory {
|
||||
scope_ref,
|
||||
listener_id,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TaskHandle<'src> {
|
||||
_p: PhantomData<&'src ()>,
|
||||
|
|
|
@ -18,8 +18,7 @@ pub use crate::innerlude::{
|
|||
pub mod prelude {
|
||||
pub use crate::component::{fc_to_builder, Fragment, Properties};
|
||||
pub use crate::context::Context;
|
||||
pub use crate::innerlude::DioxusElement;
|
||||
pub use crate::innerlude::{LazyNodes, NodeFactory, FC};
|
||||
pub use crate::innerlude::{DioxusElement, DomTree, LazyNodes, NodeFactory, FC};
|
||||
pub use crate::nodes::VNode;
|
||||
pub use crate::VirtualDom;
|
||||
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
|
||||
|
@ -43,7 +42,8 @@ pub(crate) mod innerlude {
|
|||
pub use crate::util::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
pub type FC<P> = fn(Context<P>) -> VNode;
|
||||
pub type DomTree<'a> = Option<VNode<'a>>;
|
||||
pub type FC<P> = fn(Context<P>) -> DomTree;
|
||||
|
||||
pub use dioxus_core_macro::{format_args_f, html, rsx};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
|
||||
use crate::{
|
||||
events::VirtualEvent,
|
||||
innerlude::{Context, Properties, RealDomNode, Scope, ScopeId, FC},
|
||||
innerlude::{Context, DomTree, Properties, RealDomNode, Scope, ScopeId, FC},
|
||||
};
|
||||
use std::{
|
||||
cell::Cell,
|
||||
|
@ -50,6 +50,7 @@ pub struct VText<'src> {
|
|||
pub struct VFragment<'src> {
|
||||
pub children: &'src [VNode<'src>],
|
||||
pub is_static: bool,
|
||||
pub is_error: bool,
|
||||
}
|
||||
|
||||
pub trait DioxusElement {
|
||||
|
@ -114,7 +115,7 @@ pub struct Listener<'bump> {
|
|||
pub struct VComponent<'src> {
|
||||
pub ass_scope: Cell<Option<ScopeId>>,
|
||||
|
||||
pub(crate) caller: Rc<dyn Fn(&Scope) -> VNode>,
|
||||
pub(crate) caller: Rc<dyn Fn(&Scope) -> DomTree>,
|
||||
|
||||
pub(crate) children: &'src [VNode<'src>],
|
||||
|
||||
|
@ -321,9 +322,9 @@ impl<'a> NodeFactory<'a> {
|
|||
pub fn create_component_caller<'g, P: 'g>(
|
||||
component: FC<P>,
|
||||
raw_props: *const (),
|
||||
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
||||
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
||||
let caller: Captured = Rc::new(move |scp: &Scope| -> VNode {
|
||||
) -> Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r>> {
|
||||
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> DomTree<'r> + 'a>;
|
||||
let caller: Captured = Rc::new(move |scp: &Scope| -> DomTree {
|
||||
// cast back into the right lifetime
|
||||
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
|
||||
let cx: Context<P> = Context {
|
||||
|
@ -371,6 +372,7 @@ To help you identify where this error is coming from, we've generated a backtrac
|
|||
kind: VNodeKind::Fragment(VFragment {
|
||||
children: nodes.into_bump_slice(),
|
||||
is_static: false,
|
||||
is_error: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -426,6 +428,7 @@ impl<'a> IntoVNode<'a> for &VNode<'a> {
|
|||
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
|
||||
children: fragment.children,
|
||||
is_static: fragment.is_static,
|
||||
is_error: false,
|
||||
}),
|
||||
VNodeKind::Component(component) => VNodeKind::Component(component),
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ pub struct Scope {
|
|||
type EventChannel = Rc<dyn Fn()>;
|
||||
|
||||
// The type of closure that wraps calling components
|
||||
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> VNode<'b>;
|
||||
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>;
|
||||
|
||||
// The type of task that gets sent to the task scheduler
|
||||
pub type FiberTask = Pin<Box<dyn Future<Output = EventTrigger>>>;
|
||||
|
@ -130,7 +130,8 @@ impl Scope {
|
|||
|
||||
// this is its own function so we can preciesly control how lifetimes flow
|
||||
unsafe fn call_user_component<'a>(&'a self, caller: &WrappedCaller) -> VNode<'static> {
|
||||
let new_head: VNode<'a> = caller(self);
|
||||
let new_head: Option<VNode<'a>> = caller(self);
|
||||
let new_head = new_head.unwrap_or(errored_fragment());
|
||||
std::mem::transmute(new_head)
|
||||
}
|
||||
|
||||
|
@ -205,3 +206,15 @@ impl Scope {
|
|||
&self.frames.cur_frame().head_node
|
||||
}
|
||||
}
|
||||
|
||||
pub fn errored_fragment() -> VNode<'static> {
|
||||
VNode {
|
||||
dom_id: RealDomNode::empty_cell(),
|
||||
key: None,
|
||||
kind: VNodeKind::Fragment(VFragment {
|
||||
children: &[],
|
||||
is_static: false,
|
||||
is_error: true,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ impl VirtualDom {
|
|||
///
|
||||
/// // or directly from a fn
|
||||
///
|
||||
/// fn Example(cx: Context<()>) -> VNode {
|
||||
/// fn Example(cx: Context<()>) -> DomTree {
|
||||
/// cx.render(rsx!{ div{"hello world"} })
|
||||
/// }
|
||||
///
|
||||
|
@ -318,9 +318,11 @@ impl VirtualDom {
|
|||
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
|
||||
|
||||
let cx = Context { scope, props: &() };
|
||||
let scx = SuspendedContext { inner: cx };
|
||||
|
||||
// generate the new node!
|
||||
let nodes: VNode<'s> = (&hook.callback)(cx);
|
||||
let nodes: Option<VNode<'s>> = (&hook.callback)(scx);
|
||||
let nodes = nodes.unwrap_or_else(|| errored_fragment());
|
||||
let nodes = scope.cur_frame().bump.alloc(nodes);
|
||||
|
||||
// push the old node's root onto the stack
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -1,690 +0,0 @@
|
|||
use std::{borrow::Borrow, convert::TryInto, default, fmt::Debug, sync::Arc};
|
||||
|
||||
use dioxus_core::{
|
||||
events::{EventTrigger, VirtualEvent},
|
||||
prelude::ScopeIdx,
|
||||
};
|
||||
use fxhash::FxHashMap;
|
||||
use wasm_bindgen::{closure::Closure, JsCast};
|
||||
use web_sys::{
|
||||
window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct CacheId(u32);
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RootCallback(Arc<dyn Fn(EventTrigger)>);
|
||||
impl std::fmt::Debug for RootCallback {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(())
|
||||
// a no-op for now
|
||||
// todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PatchMachine {
|
||||
pub(crate) stack: Stack,
|
||||
|
||||
pub(crate) known_roots: FxHashMap<u32, Node>,
|
||||
|
||||
pub(crate) root: Element,
|
||||
|
||||
pub(crate) temporaries: FxHashMap<u32, Node>,
|
||||
|
||||
pub(crate) document: Document,
|
||||
|
||||
pub(crate) events: EventDelegater,
|
||||
|
||||
pub(crate) current_known: Option<u32>,
|
||||
|
||||
// We need to make sure to add comments between text nodes
|
||||
// We ensure that the text siblings are patched by preventing the browser from merging
|
||||
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
||||
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
||||
// -> https://github.com/facebook/react/pull/5753
|
||||
//
|
||||
// `ptns` = Percy text node separator
|
||||
// TODO
|
||||
pub(crate) last_node_was_text: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct EventDelegater {
|
||||
root: Element,
|
||||
|
||||
// every callback gets a monotomically increasing callback ID
|
||||
callback_id: usize,
|
||||
|
||||
// map of listener types to number of those listeners
|
||||
listeners: FxHashMap<String, (usize, Closure<dyn FnMut(&Event)>)>,
|
||||
|
||||
// Map of callback_id to component index and listener id
|
||||
callback_map: FxHashMap<usize, (usize, usize)>,
|
||||
|
||||
trigger: RootCallback,
|
||||
}
|
||||
|
||||
impl EventDelegater {
|
||||
pub fn new(root: Element, trigger: impl Fn(EventTrigger) + 'static) -> Self {
|
||||
Self {
|
||||
trigger: RootCallback(Arc::new(trigger)),
|
||||
root,
|
||||
callback_id: 0,
|
||||
listeners: FxHashMap::default(),
|
||||
callback_map: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_listener(&mut self, event: &str, scope: ScopeIdx) {
|
||||
if let Some(entry) = self.listeners.get_mut(event) {
|
||||
entry.0 += 1;
|
||||
} else {
|
||||
let trigger = self.trigger.clone();
|
||||
let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
|
||||
log::debug!("Handling event!");
|
||||
|
||||
let target = event
|
||||
.target()
|
||||
.expect("missing target")
|
||||
.dyn_into::<Element>()
|
||||
.expect("not a valid element");
|
||||
|
||||
let typ = event.type_();
|
||||
|
||||
let gi_id: Option<usize> = target
|
||||
.get_attribute(&format!("dioxus-giid-{}", typ))
|
||||
.and_then(|v| v.parse().ok());
|
||||
|
||||
let gi_gen: Option<u64> = target
|
||||
.get_attribute(&format!("dioxus-gigen-{}", typ))
|
||||
.and_then(|v| v.parse().ok());
|
||||
|
||||
let li_idx: Option<usize> = target
|
||||
.get_attribute(&format!("dioxus-lidx-{}", typ))
|
||||
.and_then(|v| v.parse().ok());
|
||||
|
||||
if let (Some(gi_id), Some(gi_gen), Some(li_idx)) = (gi_id, gi_gen, li_idx) {
|
||||
// Call the trigger
|
||||
log::debug!(
|
||||
"decoded gi_id: {}, gi_gen: {}, li_idx: {}",
|
||||
gi_id,
|
||||
gi_gen,
|
||||
li_idx
|
||||
);
|
||||
|
||||
let triggered_scope = ScopeIdx::from_raw_parts(gi_id, gi_gen);
|
||||
trigger.0.as_ref()(EventTrigger::new(
|
||||
virtual_event_from_websys_event(event),
|
||||
triggered_scope,
|
||||
// scope,
|
||||
li_idx,
|
||||
));
|
||||
}
|
||||
}) as Box<dyn FnMut(&Event)>);
|
||||
|
||||
self.root
|
||||
.add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
|
||||
// Increment the listeners
|
||||
self.listeners.insert(event.into(), (1, handler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// callback: RootCallback,
|
||||
// callback: Option<Closure<dyn Fn(EventTrigger)>>,
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Stack {
|
||||
list: Vec<Node>,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
Stack {
|
||||
list: Vec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, node: Node) {
|
||||
// debug!("stack-push: {:?}", node);
|
||||
self.list.push(node);
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Node {
|
||||
let res = self.list.pop().unwrap();
|
||||
res
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.list.clear();
|
||||
}
|
||||
|
||||
pub fn top(&self) -> &Node {
|
||||
match self.list.last() {
|
||||
Some(a) => a,
|
||||
None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PatchMachine {
|
||||
pub fn new(root: Element, event_callback: impl Fn(EventTrigger) + 'static) -> Self {
|
||||
let document = window()
|
||||
.expect("must have access to the window")
|
||||
.document()
|
||||
.expect("must have access to the Document");
|
||||
|
||||
// attach all listeners to the container element
|
||||
let events = EventDelegater::new(root.clone(), event_callback);
|
||||
|
||||
Self {
|
||||
current_known: None,
|
||||
known_roots: Default::default(),
|
||||
root,
|
||||
events,
|
||||
stack: Stack::with_capacity(20),
|
||||
temporaries: Default::default(),
|
||||
document,
|
||||
last_node_was_text: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unmount(&mut self) {
|
||||
self.stack.clear();
|
||||
self.temporaries.clear();
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if let Some(child) = self.root.first_child() {
|
||||
self.stack.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.stack.clear();
|
||||
self.temporaries.clear();
|
||||
}
|
||||
|
||||
pub fn handle_edit(&mut self, edit: &Edit) {
|
||||
match *edit {
|
||||
// 0
|
||||
Edit::SetText { text } => {
|
||||
//
|
||||
self.stack.top().set_text_content(Some(text))
|
||||
}
|
||||
|
||||
// 1
|
||||
Edit::RemoveSelfAndNextSiblings {} => {
|
||||
let node = self.stack.pop();
|
||||
let mut sibling = node.next_sibling();
|
||||
|
||||
while let Some(inner) = sibling {
|
||||
let temp = inner.next_sibling();
|
||||
if let Some(sibling) = inner.dyn_ref::<Element>() {
|
||||
sibling.remove();
|
||||
}
|
||||
sibling = temp;
|
||||
}
|
||||
if let Some(node) = node.dyn_ref::<Element>() {
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 2
|
||||
Edit::ReplaceWith => {
|
||||
let new_node = self.stack.pop();
|
||||
let old_node = self.stack.pop();
|
||||
|
||||
if old_node.has_type::<Element>() {
|
||||
old_node
|
||||
.dyn_ref::<Element>()
|
||||
.unwrap()
|
||||
.replace_with_with_node_1(&new_node)
|
||||
.unwrap();
|
||||
} else if old_node.has_type::<web_sys::CharacterData>() {
|
||||
old_node
|
||||
.dyn_ref::<web_sys::CharacterData>()
|
||||
.unwrap()
|
||||
.replace_with_with_node_1(&new_node)
|
||||
.unwrap();
|
||||
} else if old_node.has_type::<web_sys::DocumentType>() {
|
||||
old_node
|
||||
.dyn_ref::<web_sys::DocumentType>()
|
||||
.unwrap()
|
||||
.replace_with_with_node_1(&new_node)
|
||||
.unwrap();
|
||||
} else {
|
||||
panic!("Cannot replace node: {:?}", old_node);
|
||||
}
|
||||
|
||||
// poc to see if this is a valid solution
|
||||
if let Some(id) = self.current_known {
|
||||
// update mapping
|
||||
self.known_roots.insert(id, new_node.clone());
|
||||
self.current_known = None;
|
||||
}
|
||||
|
||||
self.stack.push(new_node);
|
||||
}
|
||||
|
||||
// 3
|
||||
Edit::SetAttribute { name, value } => {
|
||||
let node = self.stack.top();
|
||||
|
||||
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
|
||||
node.set_attribute(name, value).unwrap();
|
||||
}
|
||||
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
|
||||
log::debug!("el is html input element");
|
||||
|
||||
// Some attributes are "volatile" and don't work through `setAttribute`.
|
||||
if name == "value" {
|
||||
node.set_value(value);
|
||||
}
|
||||
if name == "checked" {
|
||||
node.set_checked(true);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
|
||||
if name == "selected" {
|
||||
node.set_selected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4
|
||||
Edit::RemoveAttribute { name } => {
|
||||
let node = self.stack.top();
|
||||
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
|
||||
node.remove_attribute(name).unwrap();
|
||||
}
|
||||
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
|
||||
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
||||
if name == "value" {
|
||||
node.set_value("");
|
||||
}
|
||||
if name == "checked" {
|
||||
node.set_checked(false);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
|
||||
if name == "selected" {
|
||||
node.set_selected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5
|
||||
Edit::PushReverseChild { n } => {
|
||||
let parent = self.stack.top();
|
||||
let children = parent.child_nodes();
|
||||
let child = children.get(children.length() - n - 1).unwrap();
|
||||
self.stack.push(child);
|
||||
}
|
||||
|
||||
// 6
|
||||
Edit::PopPushChild { n } => {
|
||||
self.stack.pop();
|
||||
let parent = self.stack.top();
|
||||
let children = parent.child_nodes();
|
||||
let child = children.get(n).unwrap();
|
||||
self.stack.push(child);
|
||||
}
|
||||
|
||||
// 7
|
||||
Edit::Pop => {
|
||||
self.stack.pop();
|
||||
}
|
||||
|
||||
// 8
|
||||
Edit::AppendChild => {
|
||||
let child = self.stack.pop();
|
||||
self.stack.top().append_child(&child).unwrap();
|
||||
}
|
||||
|
||||
// 9
|
||||
Edit::CreateTextNode { text } => {
|
||||
//
|
||||
// We ensure that the text siblings are patched by preventing the browser from merging
|
||||
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
||||
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
||||
// -> https://github.com/facebook/react/pull/5753
|
||||
//
|
||||
// `ptns` = Percy text node separator
|
||||
// TODO
|
||||
if self.last_node_was_text {
|
||||
let n = self
|
||||
.document
|
||||
.create_comment("ptns")
|
||||
.dyn_into::<Node>()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
self.stack.push(
|
||||
self.document
|
||||
.create_text_node(text)
|
||||
.dyn_into::<Node>()
|
||||
.unwrap(),
|
||||
);
|
||||
self.last_node_was_text = true;
|
||||
}
|
||||
|
||||
// 10
|
||||
Edit::CreateElement { tag_name } => {
|
||||
self.last_node_was_text = false;
|
||||
let el = self
|
||||
.document
|
||||
.create_element(tag_name)
|
||||
.unwrap()
|
||||
.dyn_into::<Node>()
|
||||
.unwrap();
|
||||
self.stack.push(el);
|
||||
}
|
||||
|
||||
// 11
|
||||
Edit::NewListener { event, id, scope } => {
|
||||
// attach the correct attributes to the element
|
||||
// these will be used by accessing the event's target
|
||||
// This ensures we only ever have one handler attached to the root, but decide
|
||||
// dynamically when we want to call a listener.
|
||||
|
||||
let el = self.stack.top();
|
||||
|
||||
let el = el
|
||||
.dyn_ref::<Element>()
|
||||
.expect(&format!("not an element: {:?}", el));
|
||||
|
||||
// el.add_event_listener_with_callback(
|
||||
// event_type,
|
||||
// self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// debug!("adding attributes: {}, {}", a, b);
|
||||
|
||||
// let CbIdx {
|
||||
// gi_id,
|
||||
// gi_gen,
|
||||
// listener_idx: lidx,
|
||||
// } = idx;
|
||||
|
||||
let (gi_id, gi_gen) = (&scope).into_raw_parts();
|
||||
el.set_attribute(&format!("dioxus-giid-{}", event), &gi_id.to_string())
|
||||
.unwrap();
|
||||
el.set_attribute(&format!("dioxus-gigen-{}", event), &gi_gen.to_string())
|
||||
.unwrap();
|
||||
el.set_attribute(&format!("dioxus-lidx-{}", event), &id.to_string())
|
||||
.unwrap();
|
||||
|
||||
self.events.add_listener(event, scope);
|
||||
}
|
||||
|
||||
// 12
|
||||
Edit::UpdateListener { event, scope, id } => {
|
||||
// update our internal mapping, and then modify the attribute
|
||||
|
||||
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
||||
// el.set_attribute(&format!("dioxus-a-{}", event_type), &a.to_string())
|
||||
// .unwrap();
|
||||
// el.set_attribute(&format!("dioxus-b-{}", event_type), &b.to_string())
|
||||
// .unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// 13
|
||||
Edit::RemoveListener { event: event_type } => {
|
||||
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
||||
// el.remove_event_listener_with_callback(
|
||||
// event_type,
|
||||
// self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
|
||||
// )
|
||||
// .unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// 14
|
||||
Edit::CreateElementNs { tag_name, ns } => {
|
||||
self.last_node_was_text = false;
|
||||
let el = self
|
||||
.document
|
||||
.create_element_ns(Some(ns), tag_name)
|
||||
.unwrap()
|
||||
.dyn_into::<Node>()
|
||||
.unwrap();
|
||||
log::debug!("Made NS element {} {}", ns, tag_name);
|
||||
self.stack.push(el);
|
||||
}
|
||||
|
||||
// 15
|
||||
Edit::SaveChildrenToTemporaries {
|
||||
mut temp,
|
||||
start,
|
||||
end,
|
||||
} => {
|
||||
let parent = self.stack.top();
|
||||
let children = parent.child_nodes();
|
||||
for i in start..end {
|
||||
self.temporaries.insert(temp, children.get(i).unwrap());
|
||||
temp += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 16
|
||||
Edit::PushChild { n } => {
|
||||
let parent = self.stack.top();
|
||||
// log::debug!("PushChild {:#?}", parent);
|
||||
let child = parent.child_nodes().get(n).unwrap();
|
||||
self.stack.push(child);
|
||||
}
|
||||
|
||||
// 17
|
||||
Edit::PushTemporary { temp } => {
|
||||
let t = self.temporaries.get(&temp).unwrap().clone();
|
||||
self.stack.push(t);
|
||||
}
|
||||
|
||||
// 18
|
||||
Edit::InsertBefore => {
|
||||
let before = self.stack.pop();
|
||||
let after = self.stack.pop();
|
||||
after
|
||||
.parent_node()
|
||||
.unwrap()
|
||||
.insert_before(&before, Some(&after))
|
||||
.unwrap();
|
||||
self.stack.push(before);
|
||||
}
|
||||
|
||||
// 19
|
||||
Edit::PopPushReverseChild { n } => {
|
||||
self.stack.pop();
|
||||
let parent = self.stack.top();
|
||||
let children = parent.child_nodes();
|
||||
let child = children.get(children.length() - n - 1).unwrap();
|
||||
self.stack.push(child);
|
||||
}
|
||||
|
||||
// 20
|
||||
Edit::RemoveChild { n } => {
|
||||
let parent = self.stack.top();
|
||||
if let Some(child) = parent.child_nodes().get(n).unwrap().dyn_ref::<Element>() {
|
||||
child.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 21
|
||||
Edit::SetClass { class_name } => {
|
||||
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
||||
el.set_class_name(class_name);
|
||||
}
|
||||
}
|
||||
Edit::MakeKnown { node } => {
|
||||
let domnode = self.stack.top();
|
||||
self.known_roots.insert(node, domnode.clone());
|
||||
}
|
||||
Edit::TraverseToKnown { node } => {
|
||||
let domnode = self
|
||||
.known_roots
|
||||
.get(&node)
|
||||
.expect("Failed to pop know root");
|
||||
self.current_known = Some(node);
|
||||
self.stack.push(domnode.clone());
|
||||
}
|
||||
Edit::RemoveKnown => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
|
||||
use dioxus_core::events::on::*;
|
||||
match event.type_().as_str() {
|
||||
"copy" | "cut" | "paste" => {
|
||||
// let evt: web_sys::ClipboardEvent = event.clone().dyn_into().unwrap();
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
"compositionend" | "compositionstart" | "compositionupdate" => {
|
||||
let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"keydown" | "keypress" | "keyup" => {
|
||||
let evt: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"focus" | "blur" => {
|
||||
let evt: web_sys::FocusEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"change" => {
|
||||
let evt: web_sys::Event = event.clone().dyn_into().expect("wrong error typ");
|
||||
todo!()
|
||||
// VirtualEvent::FormEvent(FormEvent {value:})
|
||||
}
|
||||
|
||||
"input" | "invalid" | "reset" | "submit" => {
|
||||
// is a special react events
|
||||
let evt: web_sys::InputEvent = event.clone().dyn_into().expect("wrong event type");
|
||||
let this: web_sys::EventTarget = evt.target().unwrap();
|
||||
|
||||
let value = (&this)
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlInputElement| input.value())
|
||||
.or_else(|| {
|
||||
(&this)
|
||||
.dyn_ref()
|
||||
.map(|input: &web_sys::HtmlTextAreaElement| input.value())
|
||||
})
|
||||
.or_else(|| {
|
||||
(&this)
|
||||
.dyn_ref::<web_sys::HtmlElement>()
|
||||
.unwrap()
|
||||
.text_content()
|
||||
})
|
||||
.expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
|
||||
|
||||
// let p2 = evt.data_transfer();
|
||||
|
||||
// let value: Option<String> = (&evt).data();
|
||||
// let value = val;
|
||||
// let value = value.unwrap_or_default();
|
||||
// let value = (&evt).data().expect("No data to unwrap");
|
||||
|
||||
// todo - this needs to be a "controlled" event
|
||||
// these events won't carry the right data with them
|
||||
todo!()
|
||||
// VirtualEvent::FormEvent(FormEvent { value })
|
||||
}
|
||||
|
||||
"click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
|
||||
| "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
|
||||
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
|
||||
let evt: web_sys::MouseEvent = event.clone().dyn_into().unwrap();
|
||||
// MouseEvent(Box::new(RawMouseEvent {
|
||||
// alt_key: evt.alt_key(),
|
||||
// button: evt.button() as i32,
|
||||
// buttons: evt.buttons() as i32,
|
||||
// client_x: evt.client_x(),
|
||||
// client_y: evt.client_y(),
|
||||
// ctrl_key: evt.ctrl_key(),
|
||||
// meta_key: evt.meta_key(),
|
||||
// page_x: evt.page_x(),
|
||||
// page_y: evt.page_y(),
|
||||
// screen_x: evt.screen_x(),
|
||||
// screen_y: evt.screen_y(),
|
||||
// shift_key: evt.shift_key(),
|
||||
// get_modifier_state: GetModifierKey(Box::new(|f| {
|
||||
// // evt.get_modifier_state(f)
|
||||
// todo!("This is not yet implemented properly, sorry :(");
|
||||
// })),
|
||||
// }))
|
||||
todo!()
|
||||
// VirtualEvent::MouseEvent()
|
||||
}
|
||||
|
||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
||||
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
|
||||
let evt: web_sys::PointerEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"select" => {
|
||||
// let evt: web_sys::SelectionEvent = event.clone().dyn_into().unwrap();
|
||||
// not required to construct anything special beyond standard event stuff
|
||||
todo!()
|
||||
}
|
||||
|
||||
"touchcancel" | "touchend" | "touchmove" | "touchstart" => {
|
||||
let evt: web_sys::TouchEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"scroll" => {
|
||||
// let evt: web_sys::UIEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"wheel" => {
|
||||
let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
|
||||
| "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
|
||||
| "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
|
||||
| "timeupdate" | "volumechange" | "waiting" => {
|
||||
// not required to construct anything special beyond standard event stuff
|
||||
|
||||
// let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
|
||||
// let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"animationstart" | "animationend" | "animationiteration" => {
|
||||
let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"transitionend" => {
|
||||
let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
|
||||
"toggle" => {
|
||||
// not required to construct anything special beyond standard event stuff (target)
|
||||
|
||||
// let evt: web_sys::ToggleEvent = event.clone().dyn_into().unwrap();
|
||||
todo!()
|
||||
}
|
||||
_ => VirtualEvent::OtherEvent,
|
||||
}
|
||||
}
|
|
@ -1,211 +0,0 @@
|
|||
use std::{collections::HashMap, fmt, rc::Rc};
|
||||
use web_sys::{self, Element, EventTarget, Node, Text};
|
||||
|
||||
use dioxus_core::prelude::{VElement, VNode, VText, VirtualNode};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Mutex;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
pub struct DomRenderer {}
|
||||
|
||||
// Used to uniquely identify elements that contain closures so that the DomUpdater can
|
||||
// look them up by their unique id.
|
||||
// When the DomUpdater sees that the element no longer exists it will drop all of it's
|
||||
// Rc'd Closures for those events.
|
||||
use lazy_static::lazy_static;
|
||||
lazy_static! {
|
||||
static ref ELEM_UNIQUE_ID: Mutex<u32> = Mutex::new(0);
|
||||
}
|
||||
|
||||
fn create_unique_identifier() -> u32 {
|
||||
let mut elem_unique_id = ELEM_UNIQUE_ID.lock().unwrap();
|
||||
*elem_unique_id += 1;
|
||||
*elem_unique_id
|
||||
}
|
||||
|
||||
/// A node along with all of the closures that were created for that
|
||||
/// node's events and all of it's child node's events.
|
||||
pub struct CreatedNode<T> {
|
||||
/// A `Node` or `Element` that was created from a `VirtualNode`
|
||||
pub node: T,
|
||||
/// A map of a node's unique identifier along with all of the Closures for that node.
|
||||
///
|
||||
/// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
|
||||
/// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
|
||||
/// memory.
|
||||
pub closures: HashMap<u32, Vec<DynClosure>>,
|
||||
}
|
||||
|
||||
impl<T> CreatedNode<T> {
|
||||
pub fn without_closures<N: Into<T>>(node: N) -> Self {
|
||||
CreatedNode {
|
||||
node: node.into(),
|
||||
closures: HashMap::with_capacity(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for CreatedNode<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.node
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CreatedNode<Element>> for CreatedNode<Node> {
|
||||
fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
|
||||
CreatedNode {
|
||||
node: other.node.into(),
|
||||
closures: other.closures,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------
|
||||
// Create nodes for the VNode types
|
||||
// ---------------------------------
|
||||
|
||||
/// Return a `Text` element from a `VirtualNode`, typically right before adding it
|
||||
/// into the DOM.
|
||||
pub fn create_text_node(text_node: &VText) -> Text {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
document.create_text_node(&text_node.text)
|
||||
}
|
||||
|
||||
/// Build a DOM element by recursively creating DOM nodes for this element and it's
|
||||
/// children, it's children's children, etc.
|
||||
pub fn create_element_node(velement: &VElement) -> CreatedNode<Element> {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
|
||||
let element = if html_validation::is_svg_namespace(&velement.tag) {
|
||||
document
|
||||
.create_element_ns(Some("http://www.w3.org/2000/svg"), &velement.tag)
|
||||
.unwrap()
|
||||
} else {
|
||||
document.create_element(&velement.tag).unwrap()
|
||||
};
|
||||
|
||||
let mut closures = HashMap::new();
|
||||
|
||||
velement.attrs.iter().for_each(|(name, value)| {
|
||||
if name == "unsafe_inner_html" {
|
||||
element.set_inner_html(value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
element
|
||||
.set_attribute(name, value)
|
||||
.expect("Set element attribute in create element");
|
||||
});
|
||||
|
||||
todo!("Support events properly in web ");
|
||||
// if velement.events.0.len() > 0 {
|
||||
// let unique_id = create_unique_identifier();
|
||||
|
||||
// element
|
||||
// .set_attribute("data-vdom-id".into(), &unique_id.to_string())
|
||||
// .expect("Could not set attribute on element");
|
||||
|
||||
// closures.insert(unique_id, vec![]);
|
||||
|
||||
// velement.events.0.iter().for_each(|(onevent, callback)| {
|
||||
// // onclick -> click
|
||||
// let event = &onevent[2..];
|
||||
|
||||
// let current_elem: &EventTarget = element.dyn_ref().unwrap();
|
||||
|
||||
// current_elem
|
||||
// .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
|
||||
// .unwrap();
|
||||
|
||||
// closures
|
||||
// .get_mut(&unique_id)
|
||||
// .unwrap()
|
||||
// .push(Rc::clone(callback));
|
||||
// });
|
||||
// }
|
||||
|
||||
let mut previous_node_was_text = false;
|
||||
|
||||
velement.children.iter().for_each(|child| {
|
||||
match child {
|
||||
VNode::Text(text_node) => {
|
||||
let current_node = element.as_ref() as &web_sys::Node;
|
||||
|
||||
// We ensure that the text siblings are patched by preventing the browser from merging
|
||||
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
||||
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
||||
// -> https://github.com/facebook/react/pull/5753
|
||||
//
|
||||
// `ptns` = Percy text node separator
|
||||
if previous_node_was_text {
|
||||
let separator = document.create_comment("ptns");
|
||||
current_node
|
||||
.append_child(separator.as_ref() as &web_sys::Node)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
current_node
|
||||
.append_child(&create_text_node(&text_node))
|
||||
// .append_child(&text_node.create_text_node())
|
||||
.unwrap();
|
||||
|
||||
previous_node_was_text = true;
|
||||
}
|
||||
VNode::Element(element_node) => {
|
||||
previous_node_was_text = false;
|
||||
|
||||
let child = create_element_node(&element_node);
|
||||
// let child = element_node.create_element_node();
|
||||
let child_elem: Element = child.node;
|
||||
|
||||
closures.extend(child.closures);
|
||||
|
||||
element.append_child(&child_elem).unwrap();
|
||||
}
|
||||
|
||||
VNode::Component(component) => {
|
||||
//
|
||||
todo!("Support components in the web properly");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
todo!("Support events properly in web ");
|
||||
// if let Some(on_create_elem) = velement.events.0.get("on_create_elem") {
|
||||
// let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
|
||||
// on_create_elem
|
||||
// .call1(&wasm_bindgen::JsValue::NULL, &element)
|
||||
// .unwrap();
|
||||
// }
|
||||
|
||||
CreatedNode {
|
||||
node: element,
|
||||
closures,
|
||||
}
|
||||
}
|
||||
|
||||
/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
|
||||
/// any Closure regardless of the arguments.
|
||||
pub type DynClosure = Rc<dyn AsRef<JsValue>>;
|
||||
|
||||
/// We need a custom implementation of fmt::Debug since JsValue doesn't
|
||||
/// implement debug.
|
||||
pub struct Events(pub HashMap<String, DynClosure>);
|
||||
|
||||
impl PartialEq for Events {
|
||||
// TODO: What should happen here..? And why?
|
||||
fn eq(&self, _rhs: &Self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Events {
|
||||
// Print out all of the event names for this VirtualNode
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let events: String = self.0.keys().map(|key| " ".to_string() + key).collect();
|
||||
write!(f, "{}", events)
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
//! A collection of functions that are useful for unit testing your html! views.
|
||||
|
||||
use crate::VirtualNode;
|
||||
|
||||
impl VirtualNode {
|
||||
/// Get a vector of all of the VirtualNode children / grandchildren / etc of
|
||||
/// your virtual_node that have a label that matches your filter.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # #[macro_use] extern crate virtual_dom_rs; fn main() {
|
||||
///
|
||||
/// let component = html! {<div>
|
||||
/// <span label="hello",> {"Hi!"} </span>
|
||||
/// <em label="world",> {"There!!"} </em>
|
||||
/// <em label="hello",></em>
|
||||
/// </div> };
|
||||
///
|
||||
/// let hello_nodes = component.filter_label(|label| {
|
||||
/// label.contains("hello")
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(hello_nodes.len(), 2);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn filter_label<'a, F>(&'a self, filter: F) -> Vec<&'a VirtualNode>
|
||||
where
|
||||
F: Fn(&str) -> bool,
|
||||
{
|
||||
// Get descendants recursively
|
||||
let mut descendants: Vec<&'a VirtualNode> = vec![];
|
||||
match self {
|
||||
VirtualNode::Text(_) => { /* nothing to do */ }
|
||||
VirtualNode::Element(element_node) => {
|
||||
for child in element_node.children.iter() {
|
||||
get_descendants(&mut descendants, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter descendants
|
||||
descendants
|
||||
.into_iter()
|
||||
.filter(|vn: &&'a VirtualNode| match vn {
|
||||
VirtualNode::Text(_) => false,
|
||||
VirtualNode::Element(element_node) => match element_node.attrs.get("label") {
|
||||
Some(label) => filter(label),
|
||||
None => false,
|
||||
},
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get a vector of all of the descendants of this VirtualNode
|
||||
/// that have the provided `filter`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # #[macro_use] extern crate virtual_dom_rs; fn main() {
|
||||
///
|
||||
/// let component = html! {<div>
|
||||
/// <span label="hello",> {"Hi!"} </span>
|
||||
/// <em label="world",> {"There!!"} </em>
|
||||
/// <em label="hello",></em>
|
||||
/// </div> };
|
||||
///
|
||||
/// let hello_nodes = component.filter_label_equals("hello");
|
||||
///
|
||||
/// assert_eq!(hello_nodes.len(), 2);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn filter_label_equals<'a>(&'a self, label: &str) -> Vec<&'a VirtualNode> {
|
||||
self.filter_label(|node_label| node_label == label)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_descendants<'a>(descendants: &mut Vec<&'a VirtualNode>, node: &'a VirtualNode) {
|
||||
descendants.push(node);
|
||||
match node {
|
||||
VirtualNode::Text(_) => { /* nothing to do */ }
|
||||
VirtualNode::Element(element_node) => {
|
||||
for child in element_node.children.iter() {
|
||||
get_descendants(descendants, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::VElement;
|
||||
use std::collections::HashMap;
|
||||
|
||||
// TODO: Move this test somewhere that we can use the `html!` macro
|
||||
// #[test]
|
||||
// fn filter_label() {
|
||||
// let html = html! {
|
||||
// // Should not pick up labels on the root node
|
||||
// <div label="hello0",>
|
||||
// // This node gets picked up
|
||||
// <span label="hello1",>
|
||||
// </span>
|
||||
// // This node gets picked up
|
||||
// <em label="hello2",>
|
||||
// { "hello there :)!" }
|
||||
// </em>
|
||||
// <div label="world",></div>
|
||||
// </div>
|
||||
// };
|
||||
//
|
||||
// let hello_nodes = html.filter_label(|label| label.contains("hello"));
|
||||
//
|
||||
// assert_eq!(
|
||||
// hello_nodes.len(),
|
||||
// 2,
|
||||
// "2 elements with label containing 'hello'"
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn label_equals() {
|
||||
let span = VirtualNode::element("span");
|
||||
|
||||
let mut attrs = HashMap::new();
|
||||
attrs.insert("label".to_string(), "hello".to_string());
|
||||
let mut em = VElement::new("em");
|
||||
em.attrs = attrs;
|
||||
|
||||
let mut html = VElement::new("div");
|
||||
html.children.push(span);
|
||||
html.children.push(em.into());
|
||||
|
||||
let html_node = VirtualNode::Element(html);
|
||||
let hello_nodes = html_node.filter_label_equals("hello");
|
||||
|
||||
assert_eq!(hello_nodes.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_web::WebsysRenderer;
|
||||
|
||||
fn main() {
|
||||
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||
}
|
||||
|
||||
fn Component(cx: Context, props: ()) -> VNode {
|
||||
let user_data = use_sql_query(&cx, USER_DATA_QUERY);
|
||||
|
||||
cx.render(rsx! {
|
||||
h1 { "Hello, {username}"}
|
||||
button {
|
||||
"Delete user"
|
||||
onclick: move |_| user_data.delete()
|
||||
}
|
||||
})
|
||||
}
|
|
@ -32,7 +32,7 @@
|
|||
//! ```
|
||||
//! use dioxus::prelude::*;
|
||||
//!
|
||||
//! fn Example(cx: Context<()>) -> VNode {
|
||||
//! fn Example(cx: Context<()>) -> DomTree {
|
||||
//! html! { <div> "Hello, world!" </div> }
|
||||
//! }
|
||||
//! ```
|
||||
|
@ -44,7 +44,7 @@
|
|||
//! #[derive(Props)]
|
||||
//! struct Props { name: String }
|
||||
//!
|
||||
//! fn Example(cx: Context<Props>) -> VNode {
|
||||
//! fn Example(cx: Context<Props>) -> DomTree {
|
||||
//! html! { <div> "Hello {cx.props.name}!" </div> }
|
||||
//! }
|
||||
//! ```
|
||||
|
@ -59,7 +59,7 @@
|
|||
//! #[derive(Props)]
|
||||
//! struct Props<'a> { name: &'a str }
|
||||
//!
|
||||
//! fn Example<'a>(cx: Context<'a, Props<'a>>) -> VNode {
|
||||
//! fn Example<'a>(cx: Context<'a, Props<'a>>) -> DomTree {
|
||||
//! html! { <div> "Hello {cx.props.name}!" </div> }
|
||||
//! }
|
||||
//! ```
|
||||
|
|
Loading…
Reference in a new issue