From d005f3481fda98e50bb107d1cd699c2bd88e23a9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 10 Apr 2023 09:45:16 -0500 Subject: [PATCH 1/8] fix non-bubbling event propagation --- packages/core/src/virtual_dom.rs | 96 ++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 2269815aa..da5526b32 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -384,51 +384,73 @@ impl VirtualDom { data, }; - // Loop through each dynamic attribute in this template before moving up to the template's parent. - while let Some(el_ref) = parent_path { - // safety: we maintain references of all vnodes in the element slab - let template = unsafe { el_ref.template.unwrap().as_ref() }; - let node_template = template.template.get(); - let target_path = el_ref.path; + // If the event bubbles, we traverse through the tree until we find the target element. + if bubbles { + // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent. + while let Some(el_ref) = parent_path { + // safety: we maintain references of all vnodes in the element slab + let template = unsafe { el_ref.template.unwrap().as_ref() }; + let node_template = template.template.get(); + let target_path = el_ref.path; - for (idx, attr) in template.dynamic_attrs.iter().enumerate() { - let this_path = node_template.attr_paths[idx]; + for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + let this_path = node_template.attr_paths[idx]; - // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one - if attr.name.trim_start_matches("on") == name - && target_path.is_decendant(&this_path) - { - listeners.push(&attr.value); + // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one + if attr.name.trim_start_matches("on") == name + && target_path.is_decendant(&this_path) + { + listeners.push(&attr.value); - // Break if the event doesn't bubble anyways - if !bubbles { - break; + // Break if this is the exact target element. + // This means we won't call two listeners with the same name on the same element. This should be + // documented, or be rejected from the rsx! macro outright + if target_path == this_path { + break; + } } + } - // Break if this is the exact target element. - // This means we won't call two listeners with the same name on the same element. This should be - // documented, or be rejected from the rsx! macro outright - if target_path == this_path { - break; + // Now that we've accumulated all the parent attributes for the target element, call them in reverse order + // We check the bubble state between each call to see if the event has been stopped from bubbling + for listener in listeners.drain(..).rev() { + if let AttributeValue::Listener(listener) = listener { + if let Some(cb) = listener.borrow_mut().as_deref_mut() { + cb(uievent.clone()); + } + + if !uievent.propagates.get() { + return; + } + } + } + + parent_path = template.parent.and_then(|id| self.elements.get(id.0)); + } + } else { + // Otherwise, we just call the listener on the target element + if let Some(el_ref) = parent_path { + // safety: we maintain references of all vnodes in the element slab + let template = unsafe { el_ref.template.unwrap().as_ref() }; + let node_template = template.template.get(); + let target_path = el_ref.path; + + for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + let this_path = node_template.attr_paths[idx]; + + // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one + // Only call the listener if this is the exact target element. + if attr.name.trim_start_matches("on") == name && target_path == this_path { + if let AttributeValue::Listener(listener) = &attr.value { + if let Some(cb) = listener.borrow_mut().as_deref_mut() { + cb(uievent.clone()); + } + + break; + } } } } - - // Now that we've accumulated all the parent attributes for the target element, call them in reverse order - // We check the bubble state between each call to see if the event has been stopped from bubbling - for listener in listeners.drain(..).rev() { - if let AttributeValue::Listener(listener) = listener { - if let Some(cb) = listener.borrow_mut().as_deref_mut() { - cb(uievent.clone()); - } - - if !uievent.propagates.get() { - return; - } - } - } - - parent_path = template.parent.and_then(|id| self.elements.get(id.0)); } } From 8669a5eb0e154a861ab4b9f67df1e859401ee783 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 20 Apr 2023 08:55:35 -0500 Subject: [PATCH 2/8] remove unused shipyard-heigharchy --- packages/native-core/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/native-core/Cargo.toml b/packages/native-core/Cargo.toml index 9ea6d4c9f..c9be2f27d 100644 --- a/packages/native-core/Cargo.toml +++ b/packages/native-core/Cargo.toml @@ -29,7 +29,6 @@ lightningcss = "1.0.0-alpha.39" rayon = "1.6.1" shipyard = { version = "0.6.2", features = ["proc", "std"], default-features = false } -shipyard_hierarchy = "0.6.0" [dev-dependencies] rand = "0.8.5" From 80a8310297d092b0f85ffc351429cadf3ad04ab9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 20 Apr 2023 15:08:56 -0500 Subject: [PATCH 3/8] switch debug logs to trace in the router and web crates --- packages/router/src/components/route.rs | 6 +++--- packages/web/src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/router/src/components/route.rs b/packages/router/src/components/route.rs index cf665d2e1..7bfc48993 100644 --- a/packages/router/src/components/route.rs +++ b/packages/router/src/components/route.rs @@ -45,13 +45,13 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { router_root.register_total_route(route_context.total_route, cx.scope_id()); }); - log::debug!("Checking Route: {:?}", cx.props.to); + log::trace!("Checking Route: {:?}", cx.props.to); if router_root.should_render(cx.scope_id()) { - log::debug!("Route should render: {:?}", cx.scope_id()); + log::trace!("Route should render: {:?}", cx.scope_id()); cx.render(rsx!(&cx.props.children)) } else { - log::debug!("Route should *not* render: {:?}", cx.scope_id()); + log::trace!("Route should *not* render: {:?}", cx.scope_id()); cx.render(rsx!(())) } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index e208b0591..b9a806c5a 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -225,7 +225,7 @@ pub async fn run_with_props(root: fn(Scope) -> Element, root_prop websys_dom.mount(); loop { - log::debug!("waiting for work"); + log::trace!("waiting for work"); // if virtualdom has nothing, wait for it to have something before requesting idle time // if there is work then this future resolves immediately. From 5a08da5ec7af89d0b54d71aa33e9d36c3d08ea62 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 23 Apr 2023 11:48:53 -0500 Subject: [PATCH 4/8] fix selected and checked with boolean attributes --- packages/interpreter/src/interpreter.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index b921c7c22..69193d369 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -17,8 +17,7 @@ class ListenerMap { } else { this.global[event_name].active++; } - } - else { + } else { const id = element.getAttribute("data-dioxus-id"); if (!this.local[id]) { this.local[id] = {}; @@ -32,11 +31,13 @@ class ListenerMap { if (bubbles) { this.global[event_name].active--; if (this.global[event_name].active === 0) { - this.root.removeEventListener(event_name, this.global[event_name].callback); + this.root.removeEventListener( + event_name, + this.global[event_name].callback + ); delete this.global[event_name]; } - } - else { + } else { const id = element.getAttribute("data-dioxus-id"); delete this.local[id][event_name]; if (this.local[id].length === 0) { @@ -143,8 +144,7 @@ class Interpreter { SetAttribute(id, field, value, ns) { if (value === null) { this.RemoveAttribute(id, field, ns); - } - else { + } else { const node = this.nodes[id]; this.SetAttributeInner(node, field, value, ns); } @@ -167,10 +167,10 @@ class Interpreter { } break; case "checked": - node.checked = value === "true"; + node.checked = value === "true" || value === true; break; case "selected": - node.selected = value === "true"; + node.selected = value === "true" || value === true; break; case "dangerous_inner_html": node.innerHTML = value; @@ -342,7 +342,6 @@ class Interpreter { this.RemoveEventListener(edit.id, edit.name); break; case "NewEventListener": - let bubbles = event_bubbles(edit.name); // this handler is only provided on desktop implementations since this @@ -360,7 +359,10 @@ class Interpreter { let a_element = target.closest("a"); if (a_element != null) { event.preventDefault(); - if (shouldPreventDefault !== `onclick` && a_element.getAttribute(`dioxus-prevent-default`) !== `onclick`) { + if ( + shouldPreventDefault !== `onclick` && + a_element.getAttribute(`dioxus-prevent-default`) !== `onclick` + ) { const href = a_element.getAttribute("href"); if (href !== "" && href !== null && href !== undefined) { window.ipc.postMessage( From 425d8c64ddcbacf7fe80975159be6ad24aa14729 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 23 Apr 2023 11:54:40 -0500 Subject: [PATCH 5/8] use the generic parameter in the generic component example --- examples/generic_component.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/generic_component.rs b/examples/generic_component.rs index 9757b1032..3d6171a9b 100644 --- a/examples/generic_component.rs +++ b/examples/generic_component.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use dioxus::prelude::*; fn main() { @@ -5,9 +7,20 @@ fn main() { } fn app(cx: Scope) -> Element { - cx.render(rsx! { generic_child::{} }) + cx.render(rsx! { generic_child { + data: 0i32 + } }) } -fn generic_child(cx: Scope) -> Element { - cx.render(rsx! { div {} }) +#[derive(PartialEq, Props)] +struct GenericChildProps { + data: T, +} + +fn generic_child(cx: Scope>) -> Element { + let data = &cx.props.data; + + cx.render(rsx! { div { + "{data}" + } }) } From 7be8ec3467c497985d0b334536dcea61685f635c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 24 Apr 2023 16:07:50 -0500 Subject: [PATCH 6/8] Fix html to rsx conversions --- packages/autofmt/src/element.rs | 13 +++--- packages/rsx-rosetta/Cargo.toml | 3 ++ packages/rsx-rosetta/tests/simple.rs | 68 ++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 packages/rsx-rosetta/tests/simple.rs diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 6d2cdc830..5bb7d42fc 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -276,13 +276,12 @@ impl Writer<'_> { let start = location.start(); let line_start = start.line - 1; - let this_line = self.src[line_start]; - - let beginning = if this_line.len() > start.column { - this_line[..start.column].trim() - } else { - "" - }; + let beginning = self + .src + .get(line_start) + .filter(|this_line| this_line.len() > start.column) + .map(|this_line| this_line[..start.column].trim()) + .unwrap_or_default(); beginning.is_empty() } diff --git a/packages/rsx-rosetta/Cargo.toml b/packages/rsx-rosetta/Cargo.toml index d160d2bf9..e6bc45225 100644 --- a/packages/rsx-rosetta/Cargo.toml +++ b/packages/rsx-rosetta/Cargo.toml @@ -25,3 +25,6 @@ convert_case = "0.5.0" # default = ["html"] # eventually more output options + +[dev-dependencies] +pretty_assertions = "1.2.1" \ No newline at end of file diff --git a/packages/rsx-rosetta/tests/simple.rs b/packages/rsx-rosetta/tests/simple.rs new file mode 100644 index 000000000..b8fd095e1 --- /dev/null +++ b/packages/rsx-rosetta/tests/simple.rs @@ -0,0 +1,68 @@ +use html_parser::Dom; + +#[test] +fn simple_elements() { + let html = r#" +
+
hello world!
+
hello world!
+
hello world!
+
hello world!
+
hello world!
+
hello world!
+ hello world! +
+ "# + .trim(); + + let dom = Dom::parse(html).unwrap(); + + let body = rsx_rosetta::rsx_from_html(&dom); + + let out = dioxus_autofmt::write_block_out(body).unwrap(); + + let expected = r#" + div { + div { class: "asd", "hello world!" } + div { id: "asd", "hello world!" } + div { id: "asd", "hello world!" } + div { r#for: "asd", "hello world!" } + div { r#async: "asd", "hello world!" } + div { large_thing: "asd", "hello world!" } + ai_is_awesome { "hello world!" } + }"#; + pretty_assertions::assert_eq!(&out, &expected); +} + +#[test] +fn deeply_nested() { + let html = r#" +
+
+
+
+
+
+
+
+
+
+ "# + .trim(); + + let dom = Dom::parse(html).unwrap(); + + let body = rsx_rosetta::rsx_from_html(&dom); + + let out = dioxus_autofmt::write_block_out(body).unwrap(); + + let expected = r#" + div { + div { class: "asd", + div { class: "asd", + div { class: "asd", div { class: "asd" } } + } + } + }"#; + pretty_assertions::assert_eq!(&out, &expected); +} From 8af3522a2e3f5094aedc9714521a1cd848530d9c Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Mon, 24 Apr 2023 14:13:54 -0700 Subject: [PATCH 7/8] fix: change crossterm poll timeout to 10ms from 1s (#983) Problem: Key events were delayed signifantly in TUI widgets. Solution: Decrease crossterm event poll timeout from 1000ms to 10ms. Fixes #978 --- packages/rink/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/rink/src/lib.rs b/packages/rink/src/lib.rs index 9fceb6b48..7f15aa751 100644 --- a/packages/rink/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -98,7 +98,8 @@ pub fn render( let event_tx_clone = event_tx.clone(); if !cfg.headless { std::thread::spawn(move || { - let tick_rate = Duration::from_millis(1000); + // Timeout after 10ms when waiting for events + let tick_rate = Duration::from_millis(10); loop { if crossterm::event::poll(tick_rate).unwrap() { let evt = crossterm::event::read().unwrap(); From 254947a1948239081b979fb740827b573f82e709 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 25 Apr 2023 15:08:53 -0500 Subject: [PATCH 8/8] fix liveview tokio features --- packages/liveview/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/liveview/Cargo.toml b/packages/liveview/Cargo.toml index 5269927b0..2dbc09c71 100644 --- a/packages/liveview/Cargo.toml +++ b/packages/liveview/Cargo.toml @@ -18,7 +18,7 @@ futures-util = { version = "0.3.25", default-features = false, features = [ "sink", ] } futures-channel = { version = "0.3.25", features = ["sink"] } -tokio = { version = "1.22.0", features = ["time"] } +tokio = { version = "1.22.0", features = ["time", "macros"] } tokio-stream = { version = "0.1.11", features = ["net"] } tokio-util = { version = "0.7.4", features = ["rt"] } serde = { version = "1.0.151", features = ["derive"] }