From e126f1281d12c32f448d297d4418e5721047da46 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Thu, 25 Aug 2022 00:39:24 +0500 Subject: [PATCH 01/30] Add VNode::html_from_raw --- packages/yew/src/virtual_dom/vnode.rs | 94 +++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 6fcd7039823..bf099dcf5d2 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -45,6 +45,71 @@ impl VNode { pub fn has_key(&self) -> bool { self.key().is_some() } + + #[cfg(any(feature = "csr", feature = "hydration"))] + pub fn from_raw_html(html: &str) -> Self { + let div = gloo::utils::document().create_element("div").unwrap(); + div.set_inner_html(html); + VNode::VRef(div.into()) + } + + #[cfg(feature = "ssr")] + pub fn from_raw_html(html: &str) -> Self { + use html_parser::{Dom, Node}; + + use super::{ApplyAttributeAs, Attributes}; + use crate::{AttrValue, Classes}; + fn dom_node_to_vnode(node: Node) -> VNode { + match node { + Node::Text(text) => VNode::from(VText::new(text)), + Node::Element(element) => { + let mut tag = VTag::new(element.name); + if !element.attributes.is_empty() { + let attributes = element + .attributes + .into_iter() + .map(|(key, value)| { + ( + AttrValue::from(key), + ( + AttrValue::from(value.unwrap_or_default()), + ApplyAttributeAs::Attribute, + ), + ) + }) + .collect(); + tag.set_attributes(Attributes::IndexMap(attributes)); + } + if let Some(id) = element.id { + tag.add_attribute("id", id) + } + if !element.classes.is_empty() { + tag.add_attribute("class", Classes::from(element.classes).to_string()) + }; + tag.add_children( + element + .children + .into_iter() + .map(dom_node_to_vnode) + .collect::>(), + ); + VNode::from(tag) + } + Node::Comment(_) => VNode::default(), + } + } + + let dom = Dom::parse(html).map(|it| { + let vnodes = it + .children + .into_iter() + .map(dom_node_to_vnode) + .collect::>(); + VNode::from(VList::with_children(vnodes, None)) + }); + // error handling?? + dom.unwrap() + } } impl Default for VNode { @@ -202,3 +267,32 @@ mod feat_ssr { } } } + +#[cfg(test)] +mod tests { + #[cfg(target_arch = "wasm32")] + use wasm_bindgen_test::wasm_bindgen_test as test; + #[cfg(target_arch = "wasm32")] + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + + use super::*; + use crate::html; + + const HTML: &str = r#"
a link

paragraph

"#; + // const HTML: &str = r#"
a link

paragraph

"#; + + #[test] + fn from_raw_html_works() { + let vnode = html! { + <>
{"div"}
+ }; + + eprintln!("{:#?}", vnode); + + let from_raw = VNode::from_raw_html("
div
"); + eprintln!(); + eprintln!("{:#?}", from_raw); + + assert_eq!(vnode, from_raw); + } +} From 90ac5c77e5d3124c897aa55c0d9124feafaadadd Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Thu, 25 Aug 2022 00:57:29 +0500 Subject: [PATCH 02/30] Add docs for VNode::html_from_raw --- packages/yew/src/virtual_dom/vnode.rs | 171 +++++++++++++------------- 1 file changed, 86 insertions(+), 85 deletions(-) diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index bf099dcf5d2..532282586ed 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -46,69 +46,99 @@ impl VNode { self.key().is_some() } - #[cfg(any(feature = "csr", feature = "hydration"))] + /// Create a [`VNode`] from a string of HTML + /// + /// # Behavior in browser + /// + /// In the browser, this function creates an element, sets the passed HTML to its `innerHTML` + /// and returns a [`VNode::VRef`] of it + /// + /// # Behavior on server + /// + /// Since there's no DOM available on the server, the passed HTML is parsed using the + /// [`html_parser`](https://docs.rs/html_parser/0.6.3/html_parser/) crate. A VNode is created + /// from the parsed HTML + /// + /// # Example + /// + /// ```rust + /// # use yew::virtual_dom::VNode; + /// # use yew::html; + /// # fn _main() { + /// let parsed = VNode::from_raw_html("
content
"); + /// let _: VNode = html! { + ///
+ /// {parsed} + ///
+ /// } + /// # } + /// ``` pub fn from_raw_html(html: &str) -> Self { - let div = gloo::utils::document().create_element("div").unwrap(); - div.set_inner_html(html); - VNode::VRef(div.into()) - } - - #[cfg(feature = "ssr")] - pub fn from_raw_html(html: &str) -> Self { - use html_parser::{Dom, Node}; + #[cfg(any(feature = "csr", feature = "hydration"))] + fn inner(html: &str) -> VNode { + let div = gloo::utils::document().create_element("div").unwrap(); + div.set_inner_html(html); + VNode::VRef(div.into()) + } - use super::{ApplyAttributeAs, Attributes}; - use crate::{AttrValue, Classes}; - fn dom_node_to_vnode(node: Node) -> VNode { - match node { - Node::Text(text) => VNode::from(VText::new(text)), - Node::Element(element) => { - let mut tag = VTag::new(element.name); - if !element.attributes.is_empty() { - let attributes = element - .attributes - .into_iter() - .map(|(key, value)| { - ( - AttrValue::from(key), + #[cfg(feature = "ssr")] + fn inner(html: &str) -> VNode { + use html_parser::{Dom, Node}; + + use super::{ApplyAttributeAs, Attributes}; + use crate::{AttrValue, Classes}; + fn dom_node_to_vnode(node: Node) -> VNode { + match node { + Node::Text(text) => VNode::from(VText::new(text)), + Node::Element(element) => { + let mut tag = VTag::new(element.name); + if !element.attributes.is_empty() { + let attributes = element + .attributes + .into_iter() + .map(|(key, value)| { ( - AttrValue::from(value.unwrap_or_default()), - ApplyAttributeAs::Attribute, - ), - ) - }) - .collect(); - tag.set_attributes(Attributes::IndexMap(attributes)); - } - if let Some(id) = element.id { - tag.add_attribute("id", id) + AttrValue::from(key), + ( + AttrValue::from(value.unwrap_or_default()), + ApplyAttributeAs::Attribute, + ), + ) + }) + .collect(); + tag.set_attributes(Attributes::IndexMap(attributes)); + } + if let Some(id) = element.id { + tag.add_attribute("id", id) + } + if !element.classes.is_empty() { + tag.add_attribute("class", Classes::from(element.classes).to_string()) + }; + tag.add_children( + element + .children + .into_iter() + .map(dom_node_to_vnode) + .collect::>(), + ); + VNode::from(tag) } - if !element.classes.is_empty() { - tag.add_attribute("class", Classes::from(element.classes).to_string()) - }; - tag.add_children( - element - .children - .into_iter() - .map(dom_node_to_vnode) - .collect::>(), - ); - VNode::from(tag) + Node::Comment(_) => VNode::default(), } - Node::Comment(_) => VNode::default(), } - } - let dom = Dom::parse(html).map(|it| { - let vnodes = it - .children - .into_iter() - .map(dom_node_to_vnode) - .collect::>(); - VNode::from(VList::with_children(vnodes, None)) - }); - // error handling?? - dom.unwrap() + let dom = Dom::parse(html).map(|it| { + let vnodes = it + .children + .into_iter() + .map(dom_node_to_vnode) + .collect::>(); + VNode::from(VList::with_children(vnodes, None)) + }); + // error handling?? + dom.unwrap() + } + inner(html) } } @@ -267,32 +297,3 @@ mod feat_ssr { } } } - -#[cfg(test)] -mod tests { - #[cfg(target_arch = "wasm32")] - use wasm_bindgen_test::wasm_bindgen_test as test; - #[cfg(target_arch = "wasm32")] - wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - - use super::*; - use crate::html; - - const HTML: &str = r#"
a link

paragraph

"#; - // const HTML: &str = r#"
a link

paragraph

"#; - - #[test] - fn from_raw_html_works() { - let vnode = html! { - <>
{"div"}
- }; - - eprintln!("{:#?}", vnode); - - let from_raw = VNode::from_raw_html("
div
"); - eprintln!(); - eprintln!("{:#?}", from_raw); - - assert_eq!(vnode, from_raw); - } -} From 95099978a1b7baad2f0b94b5d88bfb438a52c525 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Thu, 25 Aug 2022 01:05:16 +0500 Subject: [PATCH 03/30] feature lock to available flags --- packages/yew/src/virtual_dom/vnode.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 532282586ed..db4bd7b42e5 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -73,6 +73,7 @@ impl VNode { /// } /// # } /// ``` + #[cfg(any(feature = "csr", feature = "hydration", feature = "ssr"))] pub fn from_raw_html(html: &str) -> Self { #[cfg(any(feature = "csr", feature = "hydration"))] fn inner(html: &str) -> VNode { From 2a5093e2a71efc1abc3ec7ddd93bb7fb7edadaaa Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 28 Aug 2022 01:08:36 +0500 Subject: [PATCH 04/30] Actually raw --- examples/inner_html/src/main.rs | 8 +- packages/yew/src/dom_bundle/bnode.rs | 29 +++++- packages/yew/src/dom_bundle/braw.rs | 126 ++++++++++++++++++++++++++ packages/yew/src/dom_bundle/mod.rs | 2 + packages/yew/src/virtual_dom/mod.rs | 4 + packages/yew/src/virtual_dom/vnode.rs | 81 +++-------------- packages/yew/src/virtual_dom/vraw.rs | 8 ++ 7 files changed, 183 insertions(+), 75 deletions(-) create mode 100644 packages/yew/src/dom_bundle/braw.rs create mode 100644 packages/yew/src/virtual_dom/vraw.rs diff --git a/examples/inner_html/src/main.rs b/examples/inner_html/src/main.rs index 8ccc7af4ea0..4c0dec3513e 100644 --- a/examples/inner_html/src/main.rs +++ b/examples/inner_html/src/main.rs @@ -1,4 +1,3 @@ -use web_sys::console; use yew::{Component, Context, Html}; const HTML: &str = include_str!("document.html"); @@ -16,12 +15,7 @@ impl Component for App { } fn view(&self, _ctx: &Context) -> Html { - let div = gloo::utils::document().create_element("div").unwrap(); - div.set_inner_html(HTML); - // See - console::log_1(&div); - - Html::VRef(div.into()) + Html::from_raw_html(HTML.into()) } } diff --git a/packages/yew/src/dom_bundle/bnode.rs b/packages/yew/src/dom_bundle/bnode.rs index 1791729726a..22088c50c56 100644 --- a/packages/yew/src/dom_bundle/bnode.rs +++ b/packages/yew/src/dom_bundle/bnode.rs @@ -4,7 +4,7 @@ use std::fmt; use web_sys::{Element, Node}; -use super::{BComp, BList, BPortal, BSubtree, BSuspense, BTag, BText}; +use super::{BComp, BList, BPortal, BSubtree, BSuspense, BTag, BText, BRaw}; use crate::dom_bundle::{Reconcilable, ReconcileTarget}; use crate::html::{AnyScope, NodeRef}; use crate::virtual_dom::{Key, VNode}; @@ -25,6 +25,8 @@ pub(super) enum BNode { Ref(Node), /// A suspendible document fragment. Suspense(Box), + /// A raw HTML string, represented by [`AttrValue`](crate::AttrValue). + Raw(BRaw), } impl BNode { @@ -38,6 +40,7 @@ impl BNode { Self::Text(_) => None, Self::Portal(bportal) => bportal.key(), Self::Suspense(bsusp) => bsusp.key(), + Self::Raw(_) => None, } } } @@ -58,6 +61,7 @@ impl ReconcileTarget for BNode { } Self::Portal(bportal) => bportal.detach(root, parent, parent_to_detach), Self::Suspense(bsusp) => bsusp.detach(root, parent, parent_to_detach), + Self::Raw(raw) => raw.detach(root, parent, parent_to_detach), } } @@ -76,6 +80,7 @@ impl ReconcileTarget for BNode { } Self::Portal(ref vportal) => vportal.shift(next_parent, next_sibling), Self::Suspense(ref vsuspense) => vsuspense.shift(next_parent, next_sibling), + Self::Raw(ref braw) => braw.shift(next_parent, next_sibling), } } } @@ -119,6 +124,11 @@ impl Reconcilable for VNode { let (node_ref, suspsense) = vsuspsense.attach(root, parent_scope, parent, next_sibling); (node_ref, suspsense.into()) + }, + VNode::VRaw(vraw) => { + let (node_ref, raw) = + vraw.attach(root, parent_scope, parent, next_sibling); + (node_ref, raw.into()) } } } @@ -176,6 +186,9 @@ impl Reconcilable for VNode { VNode::VSuspense(vsuspsense) => { vsuspsense.reconcile_node(root, parent_scope, parent, next_sibling, bundle) } + VNode::VRaw(vraw) => { + vraw.reconcile_node(root, parent_scope, parent, next_sibling, bundle) + } } } } @@ -222,6 +235,14 @@ impl From for BNode { } } + +impl From for BNode { + #[inline] + fn from(braw: BRaw) -> Self { + Self::Raw(braw) + } +} + impl fmt::Debug for BNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { @@ -232,6 +253,7 @@ impl fmt::Debug for BNode { Self::Ref(ref vref) => write!(f, "VRef ( \"{}\" )", crate::utils::print_node(vref)), Self::Portal(ref vportal) => vportal.fmt(f), Self::Suspense(ref bsusp) => bsusp.fmt(f), + Self::Raw(ref braw) => write!(f, "VRaw {{ {} }}", braw.html) } } } @@ -285,6 +307,11 @@ mod feat_hydration { vsuspense.hydrate(root, parent_scope, parent, fragment); (node_ref, suspense.into()) } + VNode::VRaw(_) => { + panic!( + "VRaw is not hydratable (raw HTML string cannot be hydrated)" + ) + } } } } diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs new file mode 100644 index 00000000000..1f5229048d1 --- /dev/null +++ b/packages/yew/src/dom_bundle/braw.rs @@ -0,0 +1,126 @@ +use wasm_bindgen::{JsValue, UnwrapThrowExt}; +use web_sys::Element; + +use crate::dom_bundle::bnode::BNode; +use crate::dom_bundle::traits::{Reconcilable, ReconcileTarget}; +use crate::dom_bundle::utils::insert_node; +use crate::dom_bundle::BSubtree; +use crate::html::AnyScope; +use crate::virtual_dom::VRaw; +use crate::{AttrValue, NodeRef}; + +pub struct BRaw { + pub html: AttrValue, + reference: NodeRef, +} + +impl BRaw { + fn create_element(html: &str) -> Option { + let div: JsValue = gloo::utils::document() + .create_element("div") + .unwrap_throw() + .into(); + let div: web_sys::HtmlElement = div.into(); + let html = html.trim(); + div.set_inner_html(html); + let children = div.children(); + return if children.length() == 0 { + None + } else if children.length() == 1 { + children.get_with_index(0) + } else { + Some(div.into()) + }; + } + + fn detach_bundle(&self, parent: &Element) { + if let Some(node) = self.reference.cast::() { + parent + .remove_child(&node) + .expect("failed to remove braw node"); + } + } +} + +impl ReconcileTarget for BRaw { + fn detach(self, _root: &BSubtree, parent: &Element, _parent_to_detach: bool) { + self.detach_bundle(parent); + } + + fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef { + if let Some(node) = self.reference.cast::() { + if let Some(parent) = node.parent_node() { + parent.remove_child(&node).unwrap_throw(); + } + + next_parent + .insert_before(&node, next_sibling.get().as_ref()) + .unwrap_throw(); + + return NodeRef::new(node.into()); + } + NodeRef::default() + } +} + +impl Reconcilable for VRaw { + type Bundle = BRaw; + + fn attach( + self, + _root: &BSubtree, + _parent_scope: &AnyScope, + parent: &Element, + next_sibling: NodeRef, + ) -> (NodeRef, Self::Bundle) { + + let element = BRaw::create_element(&self.html); + let node_ref = NodeRef::default(); + + if let Some(element) = element { + insert_node(&element, parent, next_sibling.get().as_ref()); + node_ref.set(Some(element.into())); + } + ( + node_ref.clone(), + BRaw { + html: self.html, + reference: node_ref, + }, + ) + } + + fn reconcile_node( + self, + root: &BSubtree, + parent_scope: &AnyScope, + parent: &Element, + next_sibling: NodeRef, + bundle: &mut BNode, + ) -> NodeRef { + // we don't have a way to diff what's changed in the string so we remove the node if it's + // present and reattach it + if let BNode::Raw(raw) = bundle { + raw.detach_bundle(parent) + } + let (node_ref, braw) = self.attach(root, parent_scope, parent, next_sibling); + *bundle = braw.into(); + node_ref + } + + fn reconcile( + self, + root: &BSubtree, + parent_scope: &AnyScope, + parent: &Element, + next_sibling: NodeRef, + bundle: &mut Self::Bundle, + ) -> NodeRef { + // we don't have a way to diff what's changed in the string so we remove the node and + // reattach it + bundle.detach_bundle(parent); + let (node_ref, braw) = self.attach(root, parent_scope, parent, next_sibling); + *bundle = braw; + node_ref + } +} diff --git a/packages/yew/src/dom_bundle/mod.rs b/packages/yew/src/dom_bundle/mod.rs index a8cac1c900d..d85a1f2bb7f 100644 --- a/packages/yew/src/dom_bundle/mod.rs +++ b/packages/yew/src/dom_bundle/mod.rs @@ -17,6 +17,7 @@ mod bportal; mod bsuspense; mod btag; mod btext; +mod braw; mod subtree_root; mod traits; @@ -29,6 +30,7 @@ use bportal::BPortal; use bsuspense::BSuspense; use btag::{BTag, Registry}; use btext::BText; +use braw::BRaw; use subtree_root::EventDescriptor; pub use subtree_root::{set_event_bubbling, BSubtree}; use traits::{Reconcilable, ReconcileTarget}; diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 4a1441c0ad1..3b677cc1b50 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -18,6 +18,8 @@ pub mod vsuspense; pub mod vtag; #[doc(hidden)] pub mod vtext; +#[doc(hidden)] +pub mod vraw; use std::hint::unreachable_unchecked; @@ -41,6 +43,8 @@ pub use self::vsuspense::VSuspense; pub use self::vtag::VTag; #[doc(inline)] pub use self::vtext::VText; +#[doc(inline)] +pub use self::vraw::VRaw; /// Attribute value pub type AttrValue = implicit_clone::unsync::IString; diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index db4bd7b42e5..6cd0abea255 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -8,6 +8,8 @@ use web_sys::Node; use super::{Key, VChild, VComp, VList, VPortal, VSuspense, VTag, VText}; use crate::html::BaseComponent; +use crate::virtual_dom::VRaw; +use crate::AttrValue; /// Bind virtual element to a DOM reference. #[derive(Clone)] @@ -26,6 +28,8 @@ pub enum VNode { VRef(Node), /// A suspendible document fragment. VSuspense(VSuspense), + /// A raw HTML string, represented by [`AttrValue`](crate::AttrValue). + VRaw(VRaw), } impl VNode { @@ -38,6 +42,7 @@ impl VNode { VNode::VText(_) => None, VNode::VPortal(vportal) => vportal.node.key(), VNode::VSuspense(vsuspense) => vsuspense.key.as_ref(), + VNode::VRaw(_) => None, } } @@ -73,73 +78,8 @@ impl VNode { /// } /// # } /// ``` - #[cfg(any(feature = "csr", feature = "hydration", feature = "ssr"))] - pub fn from_raw_html(html: &str) -> Self { - #[cfg(any(feature = "csr", feature = "hydration"))] - fn inner(html: &str) -> VNode { - let div = gloo::utils::document().create_element("div").unwrap(); - div.set_inner_html(html); - VNode::VRef(div.into()) - } - - #[cfg(feature = "ssr")] - fn inner(html: &str) -> VNode { - use html_parser::{Dom, Node}; - - use super::{ApplyAttributeAs, Attributes}; - use crate::{AttrValue, Classes}; - fn dom_node_to_vnode(node: Node) -> VNode { - match node { - Node::Text(text) => VNode::from(VText::new(text)), - Node::Element(element) => { - let mut tag = VTag::new(element.name); - if !element.attributes.is_empty() { - let attributes = element - .attributes - .into_iter() - .map(|(key, value)| { - ( - AttrValue::from(key), - ( - AttrValue::from(value.unwrap_or_default()), - ApplyAttributeAs::Attribute, - ), - ) - }) - .collect(); - tag.set_attributes(Attributes::IndexMap(attributes)); - } - if let Some(id) = element.id { - tag.add_attribute("id", id) - } - if !element.classes.is_empty() { - tag.add_attribute("class", Classes::from(element.classes).to_string()) - }; - tag.add_children( - element - .children - .into_iter() - .map(dom_node_to_vnode) - .collect::>(), - ); - VNode::from(tag) - } - Node::Comment(_) => VNode::default(), - } - } - - let dom = Dom::parse(html).map(|it| { - let vnodes = it - .children - .into_iter() - .map(dom_node_to_vnode) - .collect::>(); - VNode::from(VList::with_children(vnodes, None)) - }); - // error handling?? - dom.unwrap() - } - inner(html) + pub fn from_raw_html(html: AttrValue) -> Self { + VNode::VRaw(VRaw { html }) } } @@ -225,6 +165,7 @@ impl fmt::Debug for VNode { VNode::VRef(ref vref) => write!(f, "VRef ( \"{}\" )", crate::utils::print_node(vref)), VNode::VPortal(ref vportal) => vportal.fmt(f), VNode::VSuspense(ref vsuspense) => vsuspense.fmt(f), + VNode::VRaw(ref vraw) => write!(f, "VRaw {{ {} }}", vraw.html), } } } @@ -238,6 +179,7 @@ impl PartialEq for VNode { (VNode::VRef(a), VNode::VRef(b)) => a == b, // TODO: Need to improve PartialEq for VComp before enabling. (VNode::VComp(_), VNode::VComp(_)) => false, + (VNode::VRaw(a), VNode::VRaw(b)) => a.html == b.html, _ => false, } } @@ -245,6 +187,7 @@ impl PartialEq for VNode { #[cfg(feature = "ssr")] mod feat_ssr { + use std::borrow::Cow; use futures::future::{FutureExt, LocalBoxFuture}; use super::*; @@ -290,6 +233,10 @@ mod feat_ssr { .render_into_stream(w, parent_scope, hydratable) .await } + + VNode::VRaw(vraw) => { + w.write(Cow::Borrowed(vraw.html.as_ref())) + } } } diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs new file mode 100644 index 00000000000..e530915d93f --- /dev/null +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -0,0 +1,8 @@ +use crate::AttrValue; + +/// Rawr +#[derive(Clone, Debug)] +pub struct VRaw { + /// html?? + pub html: AttrValue +} From 38085891cd3cc90ddeffc8ca70a23261bf878c22 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 28 Aug 2022 01:16:03 +0500 Subject: [PATCH 05/30] Formatting + docs --- packages/yew/src/dom_bundle/bnode.rs | 14 +++++--------- packages/yew/src/dom_bundle/braw.rs | 16 +++++++--------- packages/yew/src/dom_bundle/mod.rs | 4 ++-- packages/yew/src/virtual_dom/mod.rs | 8 ++++---- packages/yew/src/virtual_dom/vnode.rs | 26 +++++++++++++++++--------- packages/yew/src/virtual_dom/vraw.rs | 2 +- 6 files changed, 36 insertions(+), 34 deletions(-) diff --git a/packages/yew/src/dom_bundle/bnode.rs b/packages/yew/src/dom_bundle/bnode.rs index 22088c50c56..6fd4c3f67d6 100644 --- a/packages/yew/src/dom_bundle/bnode.rs +++ b/packages/yew/src/dom_bundle/bnode.rs @@ -4,7 +4,7 @@ use std::fmt; use web_sys::{Element, Node}; -use super::{BComp, BList, BPortal, BSubtree, BSuspense, BTag, BText, BRaw}; +use super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText}; use crate::dom_bundle::{Reconcilable, ReconcileTarget}; use crate::html::{AnyScope, NodeRef}; use crate::virtual_dom::{Key, VNode}; @@ -124,10 +124,9 @@ impl Reconcilable for VNode { let (node_ref, suspsense) = vsuspsense.attach(root, parent_scope, parent, next_sibling); (node_ref, suspsense.into()) - }, + } VNode::VRaw(vraw) => { - let (node_ref, raw) = - vraw.attach(root, parent_scope, parent, next_sibling); + let (node_ref, raw) = vraw.attach(root, parent_scope, parent, next_sibling); (node_ref, raw.into()) } } @@ -235,7 +234,6 @@ impl From for BNode { } } - impl From for BNode { #[inline] fn from(braw: BRaw) -> Self { @@ -253,7 +251,7 @@ impl fmt::Debug for BNode { Self::Ref(ref vref) => write!(f, "VRef ( \"{}\" )", crate::utils::print_node(vref)), Self::Portal(ref vportal) => vportal.fmt(f), Self::Suspense(ref bsusp) => bsusp.fmt(f), - Self::Raw(ref braw) => write!(f, "VRaw {{ {} }}", braw.html) + Self::Raw(ref braw) => write!(f, "VRaw {{ {} }}", braw.html), } } } @@ -308,9 +306,7 @@ mod feat_hydration { (node_ref, suspense.into()) } VNode::VRaw(_) => { - panic!( - "VRaw is not hydratable (raw HTML string cannot be hydrated)" - ) + panic!("VRaw is not hydratable (raw HTML string cannot be hydrated)") } } } diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 1f5229048d1..9a2e8df5c2f 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -1,4 +1,3 @@ -use wasm_bindgen::{JsValue, UnwrapThrowExt}; use web_sys::Element; use crate::dom_bundle::bnode::BNode; @@ -16,11 +15,7 @@ pub struct BRaw { impl BRaw { fn create_element(html: &str) -> Option { - let div: JsValue = gloo::utils::document() - .create_element("div") - .unwrap_throw() - .into(); - let div: web_sys::HtmlElement = div.into(); + let div = gloo::utils::document().create_element("div").unwrap(); let html = html.trim(); div.set_inner_html(html); let children = div.children(); @@ -29,6 +24,10 @@ impl BRaw { } else if children.length() == 1 { children.get_with_index(0) } else { + tracing::debug!( + "HTML with more than one root node was passed as raw node. It will be wrapped in \ + a
" + ); Some(div.into()) }; } @@ -50,12 +49,12 @@ impl ReconcileTarget for BRaw { fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef { if let Some(node) = self.reference.cast::() { if let Some(parent) = node.parent_node() { - parent.remove_child(&node).unwrap_throw(); + parent.remove_child(&node).unwrap(); } next_parent .insert_before(&node, next_sibling.get().as_ref()) - .unwrap_throw(); + .unwrap(); return NodeRef::new(node.into()); } @@ -73,7 +72,6 @@ impl Reconcilable for VRaw { parent: &Element, next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle) { - let element = BRaw::create_element(&self.html); let node_ref = NodeRef::default(); diff --git a/packages/yew/src/dom_bundle/mod.rs b/packages/yew/src/dom_bundle/mod.rs index d85a1f2bb7f..9e18a1672d1 100644 --- a/packages/yew/src/dom_bundle/mod.rs +++ b/packages/yew/src/dom_bundle/mod.rs @@ -14,10 +14,10 @@ mod bcomp; mod blist; mod bnode; mod bportal; +mod braw; mod bsuspense; mod btag; mod btext; -mod braw; mod subtree_root; mod traits; @@ -27,10 +27,10 @@ use bcomp::BComp; use blist::BList; use bnode::BNode; use bportal::BPortal; +use braw::BRaw; use bsuspense::BSuspense; use btag::{BTag, Registry}; use btext::BText; -use braw::BRaw; use subtree_root::EventDescriptor; pub use subtree_root::{set_event_bubbling, BSubtree}; use traits::{Reconcilable, ReconcileTarget}; diff --git a/packages/yew/src/virtual_dom/mod.rs b/packages/yew/src/virtual_dom/mod.rs index 3b677cc1b50..503d6610f7f 100644 --- a/packages/yew/src/virtual_dom/mod.rs +++ b/packages/yew/src/virtual_dom/mod.rs @@ -13,13 +13,13 @@ pub mod vnode; #[doc(hidden)] pub mod vportal; #[doc(hidden)] +pub mod vraw; +#[doc(hidden)] pub mod vsuspense; #[doc(hidden)] pub mod vtag; #[doc(hidden)] pub mod vtext; -#[doc(hidden)] -pub mod vraw; use std::hint::unreachable_unchecked; @@ -38,13 +38,13 @@ pub use self::vnode::VNode; #[doc(inline)] pub use self::vportal::VPortal; #[doc(inline)] +pub use self::vraw::VRaw; +#[doc(inline)] pub use self::vsuspense::VSuspense; #[doc(inline)] pub use self::vtag::VTag; #[doc(inline)] pub use self::vtext::VText; -#[doc(inline)] -pub use self::vraw::VRaw; /// Attribute value pub type AttrValue = implicit_clone::unsync::IString; diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 6cd0abea255..00cd039f78f 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -29,6 +29,8 @@ pub enum VNode { /// A suspendible document fragment. VSuspense(VSuspense), /// A raw HTML string, represented by [`AttrValue`](crate::AttrValue). + /// + /// Also see: [`VNode::from_html_raw`] VRaw(VRaw), } @@ -56,21 +58,28 @@ impl VNode { /// # Behavior in browser /// /// In the browser, this function creates an element, sets the passed HTML to its `innerHTML` - /// and returns a [`VNode::VRef`] of it + /// and inserts the contents of it into the DOM. + /// + /// If there are multiple elements, they're wrapped in a `div`. If this behavior is not desired, + /// ensure there is only one top level node. /// /// # Behavior on server /// - /// Since there's no DOM available on the server, the passed HTML is parsed using the - /// [`html_parser`](https://docs.rs/html_parser/0.6.3/html_parser/) crate. A VNode is created - /// from the parsed HTML + /// When rendering on the server, the contents of HTML are directly injected into the HTML + /// stream. + /// + /// ## Warning + /// + /// The contents are **not** validated or sanitized. You, as the developer, are responsible to + /// ensure the HTML string passed to this method is valid and not malicious /// /// # Example /// /// ```rust /// # use yew::virtual_dom::VNode; - /// # use yew::html; + /// use yew::{AttrValue, html}; /// # fn _main() { - /// let parsed = VNode::from_raw_html("
content
"); + /// let parsed = VNode::from_raw_html(AttrValue::from("
content
")); /// let _: VNode = html! { ///
/// {parsed} @@ -188,6 +197,7 @@ impl PartialEq for VNode { #[cfg(feature = "ssr")] mod feat_ssr { use std::borrow::Cow; + use futures::future::{FutureExt, LocalBoxFuture}; use super::*; @@ -234,9 +244,7 @@ mod feat_ssr { .await } - VNode::VRaw(vraw) => { - w.write(Cow::Borrowed(vraw.html.as_ref())) - } + VNode::VRaw(vraw) => w.write(Cow::Borrowed(vraw.html.as_ref())), } } diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs index e530915d93f..ee089530e04 100644 --- a/packages/yew/src/virtual_dom/vraw.rs +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -4,5 +4,5 @@ use crate::AttrValue; #[derive(Clone, Debug)] pub struct VRaw { /// html?? - pub html: AttrValue + pub html: AttrValue, } From b3e834a6684938dffb2b2d20edeed9532c046f8f Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 28 Aug 2022 02:03:31 +0500 Subject: [PATCH 06/30] Tests --- packages/yew/src/dom_bundle/braw.rs | 60 +++++++++++++++++++++++++ packages/yew/src/dom_bundle/btag/mod.rs | 13 +----- packages/yew/src/dom_bundle/utils.rs | 22 +++++++++ packages/yew/tests/raw_html.rs | 50 +++++++++++++++++++++ 4 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 packages/yew/tests/raw_html.rs diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 9a2e8df5c2f..4718efb95d5 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -122,3 +122,63 @@ impl Reconcilable for VRaw { node_ref } } +#[cfg(target_arch = "wasm32")] +#[cfg(test)] +mod tests { + use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; + use super::*; + use crate::virtual_dom::VNode; + use crate::dom_bundle::utils::setup_parent; + + wasm_bindgen_test_configure!(run_in_browser); + + #[test] + fn braw_works_one_node() { + let (root, scope, parent) = setup_parent(); + + const HTML: &str ="text"; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), HTML) + } + + #[test] + fn braw_works_no_node() { + let (root, scope, parent) = setup_parent(); + + const HTML: &str =""; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), HTML) + } + + #[test] + fn braw_works_one_node_nested() { + let (root, scope, parent) = setup_parent(); + + const HTML: &str =r#"

one link more paragraph

"#; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), HTML) + } + #[test] + fn braw_works_multi_top_nodes() { + let (root, scope, parent) = setup_parent(); + + const HTML: &str =r#"

paragraph

link"#; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), format!("
{}
", HTML)) + } + + fn assert_braw(node: &mut BNode) -> &mut BRaw { + if let BNode::Raw(braw) = node { + return braw; + } + panic!("should be braw"); + } +} diff --git a/packages/yew/src/dom_bundle/btag/mod.rs b/packages/yew/src/dom_bundle/btag/mod.rs index d504ea0386b..87218d6f8d4 100644 --- a/packages/yew/src/dom_bundle/btag/mod.rs +++ b/packages/yew/src/dom_bundle/btag/mod.rs @@ -385,30 +385,19 @@ mod feat_hydration { #[cfg(target_arch = "wasm32")] #[cfg(test)] mod tests { - use gloo::utils::document; use wasm_bindgen::JsCast; use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use web_sys::HtmlInputElement as InputElement; use super::*; + use crate::dom_bundle::utils::setup_parent; use crate::dom_bundle::{BNode, Reconcilable, ReconcileTarget}; - use crate::html::AnyScope; use crate::virtual_dom::vtag::{HTML_NAMESPACE, SVG_NAMESPACE}; use crate::virtual_dom::{AttrValue, VNode, VTag}; use crate::{html, Html, NodeRef}; wasm_bindgen_test_configure!(run_in_browser); - fn setup_parent() -> (BSubtree, AnyScope, Element) { - let scope = AnyScope::test(); - let parent = document().create_element("div").unwrap(); - let root = BSubtree::create_root(&parent); - - document().body().unwrap().append_child(&parent).unwrap(); - - (root, scope, parent) - } - #[test] fn it_compares_tags() { let a = html! { diff --git a/packages/yew/src/dom_bundle/utils.rs b/packages/yew/src/dom_bundle/utils.rs index 827d16f8dac..0a37c119545 100644 --- a/packages/yew/src/dom_bundle/utils.rs +++ b/packages/yew/src/dom_bundle/utils.rs @@ -77,3 +77,25 @@ mod feat_hydration { #[cfg(feature = "hydration")] pub(super) use feat_hydration::*; + +#[cfg(test)] +mod tests { + use gloo::utils::document; + use web_sys::Element; + + use crate::dom_bundle::BSubtree; + use crate::html::AnyScope; + + pub fn setup_parent() -> (BSubtree, AnyScope, Element) { + let scope = AnyScope::test(); + let parent = document().create_element("div").unwrap(); + let root = BSubtree::create_root(&parent); + + document().body().unwrap().append_child(&parent).unwrap(); + + (root, scope, parent) + } +} + +#[cfg(test)] +pub(super) use tests::*; diff --git a/packages/yew/tests/raw_html.rs b/packages/yew/tests/raw_html.rs new file mode 100644 index 00000000000..a1878ceb05e --- /dev/null +++ b/packages/yew/tests/raw_html.rs @@ -0,0 +1,50 @@ +#![cfg(target_arch = "wasm32")] + +mod common; + +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +use std::time::Duration; +use common::obtain_result; +use wasm_bindgen_test::*; +use yew::platform::time::sleep; +use yew::prelude::*; + +macro_rules! create_test { + ($name:ident, $html:expr) => { + create_test!($name, $html, $html); + }; + ($name:ident, $html:expr, wrap_div) => { + create_test!($name, $html, format!("
{}
", $html)); + }; + ($name:ident, $raw:expr, $expected:expr) => { + #[wasm_bindgen_test] + async fn $name() { + #[function_component] + fn App() -> Html { + let raw = Html::from_raw_html(AttrValue::from($raw)); + html! { +
+ {raw} +
+ } + } + + yew::Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap()) + .render(); + + // wait for render to finish + sleep(Duration::from_millis(100)).await; + + let e = gloo::utils::document() + .get_element_by_id("raw-container") + .unwrap(); + assert_eq!(e.inner_html(), $expected); + } + }; +} + +create_test!(empty_string, ""); +create_test!(one_node, "text"); +create_test!(one_but_nested_node, r#"

one link more paragraph

"#); +create_test!(multi_node, r#"

paragraph

link"#, wrap_div); From 859a76cd67334fb6552755553e086cd4c7f0e2b1 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 28 Aug 2022 02:15:00 +0500 Subject: [PATCH 07/30] More tests + docs --- examples/Cargo.lock | 65 +++++++++++++++++++++++++++ packages/yew/src/virtual_dom/vnode.rs | 12 ++--- packages/yew/src/virtual_dom/vraw.rs | 31 ++++++++++++- packages/yew/tests/raw_html.rs | 42 ++++++++++------- 4 files changed, 128 insertions(+), 22 deletions(-) diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 1936e732bdd..f8a5793d723 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -1064,6 +1064,20 @@ dependencies = [ "utf8-width", ] +[[package]] +name = "html_parser" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec016cabcf7c9c48f9d5fdc6b03f273585bfce640a0f47a69552039e92b1959a" +dependencies = [ + "pest", + "pest_derive", + "serde", + "serde_derive", + "serde_json", + "thiserror", +] + [[package]] name = "http" version = "0.2.8" @@ -1612,6 +1626,50 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pest" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "905708f7f674518498c1f8d644481440f476d39ca6ecae83319bba7c6c12da91" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5803d8284a629cc999094ecd630f55e91b561a1d1ba75e233b00ae13b91a69ad" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1538eb784f07615c6d9a8ab061089c6c54a344c5b4301db51990ca1c241e8c04" +dependencies = [ + "once_cell", + "pest", + "sha-1 0.10.0", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -2502,6 +2560,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "ucd-trie" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" + [[package]] name = "unicase" version = "2.6.0" @@ -2884,6 +2948,7 @@ dependencies = [ "futures 0.3.25", "gloo", "html-escape", + "html_parser", "implicit-clone", "indexmap", "js-sys", diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 00cd039f78f..1d974417066 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -70,8 +70,12 @@ impl VNode { /// /// ## Warning /// - /// The contents are **not** validated or sanitized. You, as the developer, are responsible to - /// ensure the HTML string passed to this method is valid and not malicious + /// The contents are **not** sanitized. You, as the developer, are responsible to + /// ensure the HTML string passed to this method not malicious + /// + /// ## Panics + /// + /// If the HTML string is invalid, the [`ServerRenderer`](crate::ServerRenderer) will panic /// /// # Example /// @@ -196,8 +200,6 @@ impl PartialEq for VNode { #[cfg(feature = "ssr")] mod feat_ssr { - use std::borrow::Cow; - use futures::future::{FutureExt, LocalBoxFuture}; use super::*; @@ -244,7 +246,7 @@ mod feat_ssr { .await } - VNode::VRaw(vraw) => w.write(Cow::Borrowed(vraw.html.as_ref())), + VNode::VRaw(vraw) => vraw.render_into_stream(w, parent_scope, hydratable).await, } } diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs index ee089530e04..c37128adadb 100644 --- a/packages/yew/src/virtual_dom/vraw.rs +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -1,8 +1,35 @@ use crate::AttrValue; -/// Rawr +/// A raw HTML string to be used in VDOM. #[derive(Clone, Debug)] pub struct VRaw { - /// html?? pub html: AttrValue, } + +#[cfg(feature = "ssr")] +mod feat_ssr { + use super::*; + use html_parser::Dom; + use std::borrow::Cow; + + use crate::html::AnyScope; + use crate::platform::io::BufWriter; + + impl VRaw { + pub(crate) async fn render_into_stream( + &self, + w: &mut BufWriter, + _parent_scope: &AnyScope, + _hydratable: bool, + ) { + + // this is needed to ensure the resulting HTML during CSR and SSR is the same + let dom = Dom::parse(self.html.as_ref()).expect("invalid HTML was passed"); + if dom.children.len() > 1 { + w.write(Cow::Owned(format!("
{}
", self.html))) + } else { + w.write(Cow::Borrowed(self.html.as_ref())) + } + } + } +} diff --git a/packages/yew/tests/raw_html.rs b/packages/yew/tests/raw_html.rs index a1878ceb05e..3053df3935b 100644 --- a/packages/yew/tests/raw_html.rs +++ b/packages/yew/tests/raw_html.rs @@ -1,15 +1,16 @@ -#![cfg(target_arch = "wasm32")] - mod common; -wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); -use std::time::Duration; -use common::obtain_result; -use wasm_bindgen_test::*; -use yew::platform::time::sleep; use yew::prelude::*; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::wasm_bindgen_test as test; +#[cfg(target_arch = "wasm32")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); +#[cfg(feature = "tokio")] +use tokio::test; + macro_rules! create_test { ($name:ident, $html:expr) => { create_test!($name, $html, $html); @@ -18,7 +19,7 @@ macro_rules! create_test { create_test!($name, $html, format!("
{}
", $html)); }; ($name:ident, $raw:expr, $expected:expr) => { - #[wasm_bindgen_test] + #[test] async fn $name() { #[function_component] fn App() -> Html { @@ -30,16 +31,27 @@ macro_rules! create_test { } } - yew::Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap()) + #[cfg(target_arch = "wasm32")] + { + use std::time::Duration; + use yew::platform::time::sleep; + + yew::Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap()) .render(); - // wait for render to finish - sleep(Duration::from_millis(100)).await; + // wait for render to finish + sleep(Duration::from_millis(100)).await; - let e = gloo::utils::document() - .get_element_by_id("raw-container") - .unwrap(); - assert_eq!(e.inner_html(), $expected); + let e = gloo::utils::document() + .get_element_by_id("raw-container") + .unwrap(); + assert_eq!(e.inner_html(), $expected); + } + #[cfg(not(target_arch = "wasm32"))] + { + let actual = yew::ServerRenderer::::new() .hydratable(false).render().await; + assert_eq!(actual, format!(r#"
{}
"#, $expected)); + } } }; } From ad63a59244a1035086960d95adac7dc4f1c6dd18 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 28 Aug 2022 02:15:20 +0500 Subject: [PATCH 08/30] fmt --- packages/yew/src/dom_bundle/braw.rs | 11 +++++----- packages/yew/src/virtual_dom/vraw.rs | 6 +++--- packages/yew/tests/raw_html.rs | 31 ++++++++++++++++++++-------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 4718efb95d5..0985ddd2202 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -126,9 +126,10 @@ impl Reconcilable for VRaw { #[cfg(test)] mod tests { use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; + use super::*; - use crate::virtual_dom::VNode; use crate::dom_bundle::utils::setup_parent; + use crate::virtual_dom::VNode; wasm_bindgen_test_configure!(run_in_browser); @@ -136,7 +137,7 @@ mod tests { fn braw_works_one_node() { let (root, scope, parent) = setup_parent(); - const HTML: &str ="text"; + const HTML: &str = "text"; let elem = VNode::from_raw_html(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); @@ -147,7 +148,7 @@ mod tests { fn braw_works_no_node() { let (root, scope, parent) = setup_parent(); - const HTML: &str =""; + const HTML: &str = ""; let elem = VNode::from_raw_html(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); @@ -158,7 +159,7 @@ mod tests { fn braw_works_one_node_nested() { let (root, scope, parent) = setup_parent(); - const HTML: &str =r#"

one link more paragraph

"#; + const HTML: &str = r#"

one link more paragraph

"#; let elem = VNode::from_raw_html(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); @@ -168,7 +169,7 @@ mod tests { fn braw_works_multi_top_nodes() { let (root, scope, parent) = setup_parent(); - const HTML: &str =r#"

paragraph

link"#; + const HTML: &str = r#"

paragraph

link"#; let elem = VNode::from_raw_html(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs index c37128adadb..17a28729896 100644 --- a/packages/yew/src/virtual_dom/vraw.rs +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -8,10 +8,11 @@ pub struct VRaw { #[cfg(feature = "ssr")] mod feat_ssr { - use super::*; - use html_parser::Dom; use std::borrow::Cow; + use html_parser::Dom; + + use super::*; use crate::html::AnyScope; use crate::platform::io::BufWriter; @@ -22,7 +23,6 @@ mod feat_ssr { _parent_scope: &AnyScope, _hydratable: bool, ) { - // this is needed to ensure the resulting HTML during CSR and SSR is the same let dom = Dom::parse(self.html.as_ref()).expect("invalid HTML was passed"); if dom.children.len() > 1 { diff --git a/packages/yew/tests/raw_html.rs b/packages/yew/tests/raw_html.rs index 3053df3935b..500da29f614 100644 --- a/packages/yew/tests/raw_html.rs +++ b/packages/yew/tests/raw_html.rs @@ -1,11 +1,8 @@ mod common; - -use yew::prelude::*; - - #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; +use yew::prelude::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); #[cfg(feature = "tokio")] @@ -34,9 +31,12 @@ macro_rules! create_test { #[cfg(target_arch = "wasm32")] { use std::time::Duration; + use yew::platform::time::sleep; - yew::Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap()) + yew::Renderer::::with_root( + gloo::utils::document().get_element_by_id("output").unwrap(), + ) .render(); // wait for render to finish @@ -49,8 +49,14 @@ macro_rules! create_test { } #[cfg(not(target_arch = "wasm32"))] { - let actual = yew::ServerRenderer::::new() .hydratable(false).render().await; - assert_eq!(actual, format!(r#"
{}
"#, $expected)); + let actual = yew::ServerRenderer::::new() + .hydratable(false) + .render() + .await; + assert_eq!( + actual, + format!(r#"
{}
"#, $expected) + ); } } }; @@ -58,5 +64,12 @@ macro_rules! create_test { create_test!(empty_string, ""); create_test!(one_node, "text"); -create_test!(one_but_nested_node, r#"

one link more paragraph

"#); -create_test!(multi_node, r#"

paragraph

link"#, wrap_div); +create_test!( + one_but_nested_node, + r#"

one link more paragraph

"# +); +create_test!( + multi_node, + r#"

paragraph

link"#, + wrap_div +); From 94840dfc62f954512b4d17fdbd1c1639e67aca18 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 28 Aug 2022 02:16:35 +0500 Subject: [PATCH 09/30] clippy --- packages/yew/src/dom_bundle/braw.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 0985ddd2202..fc8b036bd77 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -19,7 +19,7 @@ impl BRaw { let html = html.trim(); div.set_inner_html(html); let children = div.children(); - return if children.length() == 0 { + if children.length() == 0 { None } else if children.length() == 1 { children.get_with_index(0) @@ -28,8 +28,8 @@ impl BRaw { "HTML with more than one root node was passed as raw node. It will be wrapped in \ a
" ); - Some(div.into()) - }; + Some(div) + } } fn detach_bundle(&self, parent: &Element) { From 4a507847905e5de9d758e8a4d87bd167d0a41cfa Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 28 Aug 2022 02:23:08 +0500 Subject: [PATCH 10/30] CI --- packages/yew/src/dom_bundle/utils.rs | 3 +++ packages/yew/src/virtual_dom/vnode.rs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/yew/src/dom_bundle/utils.rs b/packages/yew/src/dom_bundle/utils.rs index 0a37c119545..eae2ccb3dc5 100644 --- a/packages/yew/src/dom_bundle/utils.rs +++ b/packages/yew/src/dom_bundle/utils.rs @@ -86,6 +86,7 @@ mod tests { use crate::dom_bundle::BSubtree; use crate::html::AnyScope; + #[allow(dead_code)] pub fn setup_parent() -> (BSubtree, AnyScope, Element) { let scope = AnyScope::test(); let parent = document().create_element("div").unwrap(); @@ -98,4 +99,6 @@ mod tests { } #[cfg(test)] +// this is needed because clippy doesn't like the import not being used +#[allow(unused_imports)] pub(super) use tests::*; diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 1d974417066..da759084f6f 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -87,8 +87,8 @@ impl VNode { /// let _: VNode = html! { ///
/// {parsed} - ///
- /// } + ///
+ /// }; /// # } /// ``` pub fn from_raw_html(html: AttrValue) -> Self { From c33c4daf6870e52ab15ef6e26133681b885cb9fe Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 28 Aug 2022 17:17:48 +0500 Subject: [PATCH 11/30] No
around multi top-level nodes --- packages/yew/src/dom_bundle/braw.rs | 42 ++++++++++++-------- packages/yew/src/virtual_dom/vnode.rs | 2 +- packages/yew/src/virtual_dom/vraw.rs | 16 ++++---- packages/yew/tests/raw_html.rs | 57 ++++++++++++++++++++++++--- 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index fc8b036bd77..8edda4fb318 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -1,3 +1,4 @@ +use wasm_bindgen::JsCast; use web_sys::Element; use crate::dom_bundle::bnode::BNode; @@ -14,22 +15,17 @@ pub struct BRaw { } impl BRaw { - fn create_element(html: &str) -> Option { + fn create_elements(html: &str) -> Vec { let div = gloo::utils::document().create_element("div").unwrap(); let html = html.trim(); div.set_inner_html(html); let children = div.children(); - if children.length() == 0 { - None - } else if children.length() == 1 { - children.get_with_index(0) - } else { - tracing::debug!( - "HTML with more than one root node was passed as raw node. It will be wrapped in \ - a
" - ); - Some(div) - } + let children = js_sys::Array::from(&children); + let children = children.to_vec(); + children + .into_iter() + .map(|it| it.unchecked_into()) + .collect::>() } fn detach_bundle(&self, parent: &Element) { @@ -72,12 +68,24 @@ impl Reconcilable for VRaw { parent: &Element, next_sibling: NodeRef, ) -> (NodeRef, Self::Bundle) { - let element = BRaw::create_element(&self.html); + let elements = BRaw::create_elements(&self.html); + if elements.is_empty() { + return ( + next_sibling.clone(), + BRaw { + html: self.html, + reference: next_sibling, + }, + ); + } let node_ref = NodeRef::default(); - if let Some(element) = element { - insert_node(&element, parent, next_sibling.get().as_ref()); - node_ref.set(Some(element.into())); + let mut iter = elements.into_iter(); + let first = iter.next().unwrap(); + insert_node(&first, parent, next_sibling.get().as_ref()); + node_ref.set(Some(first.into())); + for child in iter { + insert_node(&child, parent, next_sibling.get().as_ref()); } ( node_ref.clone(), @@ -173,7 +181,7 @@ mod tests { let elem = VNode::from_raw_html(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); - assert_eq!(parent.inner_html(), format!("
{}
", HTML)) + assert_eq!(parent.inner_html(), HTML) } fn assert_braw(node: &mut BNode) -> &mut BRaw { diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index da759084f6f..13108c66e03 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -81,7 +81,7 @@ impl VNode { /// /// ```rust /// # use yew::virtual_dom::VNode; - /// use yew::{AttrValue, html}; + /// use yew::{html, AttrValue}; /// # fn _main() { /// let parsed = VNode::from_raw_html(AttrValue::from("
content
")); /// let _: VNode = html! { diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs index 17a28729896..df3a3fe6afe 100644 --- a/packages/yew/src/virtual_dom/vraw.rs +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -6,12 +6,16 @@ pub struct VRaw { pub html: AttrValue, } +impl From for VRaw { + fn from(html: AttrValue) -> Self { + Self { html } + } +} + #[cfg(feature = "ssr")] mod feat_ssr { use std::borrow::Cow; - use html_parser::Dom; - use super::*; use crate::html::AnyScope; use crate::platform::io::BufWriter; @@ -23,13 +27,7 @@ mod feat_ssr { _parent_scope: &AnyScope, _hydratable: bool, ) { - // this is needed to ensure the resulting HTML during CSR and SSR is the same - let dom = Dom::parse(self.html.as_ref()).expect("invalid HTML was passed"); - if dom.children.len() > 1 { - w.write(Cow::Owned(format!("
{}
", self.html))) - } else { - w.write(Cow::Borrowed(self.html.as_ref())) - } + w.write(Cow::Borrowed(self.html.as_ref())) } } } diff --git a/packages/yew/tests/raw_html.rs b/packages/yew/tests/raw_html.rs index 500da29f614..af5aec892a0 100644 --- a/packages/yew/tests/raw_html.rs +++ b/packages/yew/tests/raw_html.rs @@ -1,5 +1,7 @@ mod common; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; use yew::prelude::*; @@ -12,9 +14,6 @@ macro_rules! create_test { ($name:ident, $html:expr) => { create_test!($name, $html, $html); }; - ($name:ident, $html:expr, wrap_div) => { - create_test!($name, $html, format!("
{}
", $html)); - }; ($name:ident, $raw:expr, $expected:expr) => { #[test] async fn $name() { @@ -70,6 +69,54 @@ create_test!( ); create_test!( multi_node, - r#"

paragraph

link"#, - wrap_div + r#"

paragraph

link"# ); + +#[cfg(target_arch = "wasm32")] +#[test] +async fn set_new_html_string() { + #[function_component] + fn App() -> Html { + let raw_html = use_state(|| ("first")); + let onclick = { + let raw_html = raw_html.clone(); + move |_| raw_html.set("second") + }; + let raw = Html::from_raw_html(AttrValue::from(*raw_html)); + html! { + <> +
+ {raw} +
+ + + } + } + use std::time::Duration; + + use yew::platform::time::sleep; + + yew::Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap()) + .render(); + + // wait for render to finish + sleep(Duration::from_millis(100)).await; + + let e = gloo::utils::document() + .get_element_by_id("raw-container") + .unwrap(); + assert_eq!(e.inner_html(), "first"); + + gloo::utils::document() + .get_element_by_id("click-me-btn") + .unwrap() + .unchecked_into::() + .click(); + + sleep(Duration::from_millis(100)).await; + + let e = gloo::utils::document() + .get_element_by_id("raw-container") + .unwrap(); + assert_eq!(e.inner_html(), "second"); +} From 4037025f8521adedd078bbfd0f89dc82657798e9 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 28 Aug 2022 17:21:54 +0500 Subject: [PATCH 12/30] Update docs --- packages/yew/src/dom_bundle/traits.rs | 2 ++ packages/yew/src/virtual_dom/vnode.rs | 11 ++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/yew/src/dom_bundle/traits.rs b/packages/yew/src/dom_bundle/traits.rs index 10712be2c04..6a1d5e5cf19 100644 --- a/packages/yew/src/dom_bundle/traits.rs +++ b/packages/yew/src/dom_bundle/traits.rs @@ -32,6 +32,8 @@ pub(super) trait Reconcilable { /// - `next_sibling`: to find where to put the node. /// /// Returns a reference to the newly inserted element. + /// The [`NodeRef`] points the first element (if there are multiple nodes created), + /// or is the passed in next_sibling if there are no element is created. fn attach( self, diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 13108c66e03..80b626163a5 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -60,9 +60,6 @@ impl VNode { /// In the browser, this function creates an element, sets the passed HTML to its `innerHTML` /// and inserts the contents of it into the DOM. /// - /// If there are multiple elements, they're wrapped in a `div`. If this behavior is not desired, - /// ensure there is only one top level node. - /// /// # Behavior on server /// /// When rendering on the server, the contents of HTML are directly injected into the HTML @@ -70,12 +67,8 @@ impl VNode { /// /// ## Warning /// - /// The contents are **not** sanitized. You, as the developer, are responsible to - /// ensure the HTML string passed to this method not malicious - /// - /// ## Panics - /// - /// If the HTML string is invalid, the [`ServerRenderer`](crate::ServerRenderer) will panic + /// The contents are **not** sanitized or validated. You, as the developer, are responsible to + /// ensure the HTML string passed to this method are _valid_ and _not malicious_ /// /// # Example /// From 83f58f6bf10040a762f687df004af33248153019 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 3 Sep 2022 18:56:10 +0500 Subject: [PATCH 13/30] Fix braw detach --- examples/Cargo.lock | 65 ---------------------------- packages/yew/src/dom_bundle/bnode.rs | 5 ++- packages/yew/src/dom_bundle/braw.rs | 36 ++++++++++++--- 3 files changed, 34 insertions(+), 72 deletions(-) diff --git a/examples/Cargo.lock b/examples/Cargo.lock index f8a5793d723..1936e732bdd 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -1064,20 +1064,6 @@ dependencies = [ "utf8-width", ] -[[package]] -name = "html_parser" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec016cabcf7c9c48f9d5fdc6b03f273585bfce640a0f47a69552039e92b1959a" -dependencies = [ - "pest", - "pest_derive", - "serde", - "serde_derive", - "serde_json", - "thiserror", -] - [[package]] name = "http" version = "0.2.8" @@ -1626,50 +1612,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" -[[package]] -name = "pest" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905708f7f674518498c1f8d644481440f476d39ca6ecae83319bba7c6c12da91" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5803d8284a629cc999094ecd630f55e91b561a1d1ba75e233b00ae13b91a69ad" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1538eb784f07615c6d9a8ab061089c6c54a344c5b4301db51990ca1c241e8c04" -dependencies = [ - "once_cell", - "pest", - "sha-1 0.10.0", -] - [[package]] name = "pin-project" version = "1.0.12" @@ -2560,12 +2502,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" -[[package]] -name = "ucd-trie" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" - [[package]] name = "unicase" version = "2.6.0" @@ -2948,7 +2884,6 @@ dependencies = [ "futures 0.3.25", "gloo", "html-escape", - "html_parser", "implicit-clone", "indexmap", "js-sys", diff --git a/packages/yew/src/dom_bundle/bnode.rs b/packages/yew/src/dom_bundle/bnode.rs index 6fd4c3f67d6..825988796c7 100644 --- a/packages/yew/src/dom_bundle/bnode.rs +++ b/packages/yew/src/dom_bundle/bnode.rs @@ -1,5 +1,6 @@ //! This module contains the bundle version of an abstract node [BNode] +use fmt::Debug; use std::fmt; use web_sys::{Element, Node}; @@ -241,7 +242,7 @@ impl From for BNode { } } -impl fmt::Debug for BNode { +impl Debug for BNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::Tag(ref vtag) => vtag.fmt(f), @@ -251,7 +252,7 @@ impl fmt::Debug for BNode { Self::Ref(ref vref) => write!(f, "VRef ( \"{}\" )", crate::utils::print_node(vref)), Self::Portal(ref vportal) => vportal.fmt(f), Self::Suspense(ref bsusp) => bsusp.fmt(f), - Self::Raw(ref braw) => write!(f, "VRaw {{ {} }}", braw.html), + Self::Raw(ref braw) => braw.fmt(f), } } } diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 8edda4fb318..97d7ed3d7fa 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -7,11 +7,12 @@ use crate::dom_bundle::utils::insert_node; use crate::dom_bundle::BSubtree; use crate::html::AnyScope; use crate::virtual_dom::VRaw; -use crate::{AttrValue, NodeRef}; +use crate::{NodeRef}; +#[derive(Debug)] pub struct BRaw { - pub html: AttrValue, reference: NodeRef, + children_count: usize } impl BRaw { @@ -30,9 +31,20 @@ impl BRaw { fn detach_bundle(&self, parent: &Element) { if let Some(node) = self.reference.cast::() { + let mut next_sibling = node.unchecked_ref::().next_sibling(); + parent .remove_child(&node) .expect("failed to remove braw node"); + let len = self.children_count - 1; + for _ in 0..len { + if let Some(node) = next_sibling { + next_sibling = node.clone().unchecked_ref::().next_sibling(); + parent + .remove_child(&node) + .expect("failed to remove braw node"); + } + } } } } @@ -73,13 +85,14 @@ impl Reconcilable for VRaw { return ( next_sibling.clone(), BRaw { - html: self.html, reference: next_sibling, + children_count: 0, }, ); } let node_ref = NodeRef::default(); + let count = elements.len(); let mut iter = elements.into_iter(); let first = iter.next().unwrap(); insert_node(&first, parent, next_sibling.get().as_ref()); @@ -90,8 +103,8 @@ impl Reconcilable for VRaw { ( node_ref.clone(), BRaw { - html: self.html, reference: node_ref, + children_count: count, }, ) } @@ -167,7 +180,7 @@ mod tests { fn braw_works_one_node_nested() { let (root, scope, parent) = setup_parent(); - const HTML: &str = r#"

one link more paragraph

"#; + const HTML: &str = r#"

one link more paragraph

here
"#; let elem = VNode::from_raw_html(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); @@ -184,6 +197,19 @@ mod tests { assert_eq!(parent.inner_html(), HTML) } + #[test] + fn braw_detach_works() { + let (root, scope, parent) = setup_parent(); + + const HTML: &str = r#"

paragraph

link"#; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), HTML); + elem.detach(&root, &parent, false); + assert_eq!(parent.inner_html(), ""); + } + fn assert_braw(node: &mut BNode) -> &mut BRaw { if let BNode::Raw(braw) = node { return braw; From 6cd8b16e988e47f66e31ddea41839b81e8f61b3b Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 3 Sep 2022 19:04:46 +0500 Subject: [PATCH 14/30] Clippy & fmt --- packages/yew/src/dom_bundle/bnode.rs | 3 +-- packages/yew/src/dom_bundle/braw.rs | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/yew/src/dom_bundle/bnode.rs b/packages/yew/src/dom_bundle/bnode.rs index 825988796c7..616832af0a4 100644 --- a/packages/yew/src/dom_bundle/bnode.rs +++ b/packages/yew/src/dom_bundle/bnode.rs @@ -1,6 +1,5 @@ //! This module contains the bundle version of an abstract node [BNode] -use fmt::Debug; use std::fmt; use web_sys::{Element, Node}; @@ -242,7 +241,7 @@ impl From for BNode { } } -impl Debug for BNode { +impl fmt::Debug for BNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::Tag(ref vtag) => vtag.fmt(f), diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 97d7ed3d7fa..6af512fdf4d 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -7,12 +7,12 @@ use crate::dom_bundle::utils::insert_node; use crate::dom_bundle::BSubtree; use crate::html::AnyScope; use crate::virtual_dom::VRaw; -use crate::{NodeRef}; +use crate::NodeRef; #[derive(Debug)] pub struct BRaw { reference: NodeRef, - children_count: usize + children_count: usize, } impl BRaw { @@ -180,7 +180,8 @@ mod tests { fn braw_works_one_node_nested() { let (root, scope, parent) = setup_parent(); - const HTML: &str = r#"

one link more paragraph

here
"#; + const HTML: &str = + r#"

one link more paragraph

here
"#; let elem = VNode::from_raw_html(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); From f9ecba8c4114dcf64855091e9430f427310621e6 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 17 Sep 2022 02:06:18 +0500 Subject: [PATCH 15/30] Fix compile errors --- packages/yew/src/virtual_dom/vraw.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs index df3a3fe6afe..9351fa5e26b 100644 --- a/packages/yew/src/virtual_dom/vraw.rs +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -15,10 +15,11 @@ impl From for VRaw { #[cfg(feature = "ssr")] mod feat_ssr { use std::borrow::Cow; + use std::fmt::Write; use super::*; use crate::html::AnyScope; - use crate::platform::io::BufWriter; + use crate::platform::fmt::BufWriter; impl VRaw { pub(crate) async fn render_into_stream( @@ -27,7 +28,7 @@ mod feat_ssr { _parent_scope: &AnyScope, _hydratable: bool, ) { - w.write(Cow::Borrowed(self.html.as_ref())) + let _ = w.write_str(self.html.as_ref()); } } } From bd89e60f2de3928ee9605fe7aee489e1ad603207 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 17 Sep 2022 02:31:33 +0500 Subject: [PATCH 16/30] I hope you get attacked by Cow, Clippy --- packages/yew/src/virtual_dom/vraw.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/yew/src/virtual_dom/vraw.rs b/packages/yew/src/virtual_dom/vraw.rs index 9351fa5e26b..9c634b7223a 100644 --- a/packages/yew/src/virtual_dom/vraw.rs +++ b/packages/yew/src/virtual_dom/vraw.rs @@ -14,7 +14,6 @@ impl From for VRaw { #[cfg(feature = "ssr")] mod feat_ssr { - use std::borrow::Cow; use std::fmt::Write; use super::*; From a6665f28eb1faf8c7400cf259505feca8c3b4ecd Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 24 Sep 2022 04:25:58 +0500 Subject: [PATCH 17/30] Address review --- packages/yew/src/dom_bundle/braw.rs | 68 +++++++++---- packages/yew/src/virtual_dom/vnode.rs | 9 +- packages/yew/tests/raw_html.rs | 139 ++++++++++++++++++++++++-- 3 files changed, 185 insertions(+), 31 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 6af512fdf4d..97a69e818b3 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -7,12 +7,13 @@ use crate::dom_bundle::utils::insert_node; use crate::dom_bundle::BSubtree; use crate::html::AnyScope; use crate::virtual_dom::VRaw; -use crate::NodeRef; +use crate::{AttrValue, NodeRef}; #[derive(Debug)] pub struct BRaw { reference: NodeRef, children_count: usize, + html: AttrValue, } impl BRaw { @@ -30,8 +31,8 @@ impl BRaw { } fn detach_bundle(&self, parent: &Element) { - if let Some(node) = self.reference.cast::() { - let mut next_sibling = node.unchecked_ref::().next_sibling(); + if let Some(node) = self.reference.get() { + let mut next_sibling = node.next_sibling(); parent .remove_child(&node) @@ -39,7 +40,7 @@ impl BRaw { let len = self.children_count - 1; for _ in 0..len { if let Some(node) = next_sibling { - next_sibling = node.clone().unchecked_ref::().next_sibling(); + next_sibling = node.clone().next_sibling(); parent .remove_child(&node) .expect("failed to remove braw node"); @@ -55,7 +56,7 @@ impl ReconcileTarget for BRaw { } fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef { - if let Some(node) = self.reference.cast::() { + if let Some(node) = self.reference.get() { if let Some(parent) = node.parent_node() { parent.remove_child(&node).unwrap(); } @@ -87,6 +88,7 @@ impl Reconcilable for VRaw { BRaw { reference: next_sibling, children_count: 0, + html: self.html, }, ); } @@ -105,6 +107,7 @@ impl Reconcilable for VRaw { BRaw { reference: node_ref, children_count: count, + html: self.html, }, ) } @@ -117,14 +120,11 @@ impl Reconcilable for VRaw { next_sibling: NodeRef, bundle: &mut BNode, ) -> NodeRef { - // we don't have a way to diff what's changed in the string so we remove the node if it's - // present and reattach it - if let BNode::Raw(raw) = bundle { - raw.detach_bundle(parent) + match bundle { + BNode::Raw(raw) if raw.html == self.html => raw.reference.clone(), + BNode::Raw(raw) => self.reconcile(root, parent_scope, parent, next_sibling, raw), + _ => self.replace(root, parent_scope, parent, next_sibling, bundle), } - let (node_ref, braw) = self.attach(root, parent_scope, parent, next_sibling); - *bundle = braw.into(); - node_ref } fn reconcile( @@ -135,12 +135,16 @@ impl Reconcilable for VRaw { next_sibling: NodeRef, bundle: &mut Self::Bundle, ) -> NodeRef { - // we don't have a way to diff what's changed in the string so we remove the node and - // reattach it - bundle.detach_bundle(parent); - let (node_ref, braw) = self.attach(root, parent_scope, parent, next_sibling); - *bundle = braw; - node_ref + if self.html != bundle.html { + // we don't have a way to diff what's changed in the string so we remove the node and + // reattach it + bundle.detach_bundle(parent); + let (node_ref, braw) = self.attach(root, parent_scope, parent, next_sibling); + *bundle = braw; + node_ref + } else { + bundle.reference.clone() + } } } #[cfg(target_arch = "wasm32")] @@ -199,7 +203,7 @@ mod tests { } #[test] - fn braw_detach_works() { + fn braw_detach_works_multi_node() { let (root, scope, parent) = setup_parent(); const HTML: &str = r#"

paragraph

link"#; @@ -211,6 +215,32 @@ mod tests { assert_eq!(parent.inner_html(), ""); } + #[test] + fn braw_detach_works_single_node() { + let (root, scope, parent) = setup_parent(); + + const HTML: &str = r#"

paragraph

"#; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), HTML); + elem.detach(&root, &parent, false); + assert_eq!(parent.inner_html(), ""); + } + + #[test] + fn braw_detach_works_empty() { + let (root, scope, parent) = setup_parent(); + + const HTML: &str = ""; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), HTML); + elem.detach(&root, &parent, false); + assert_eq!(parent.inner_html(), ""); + } + fn assert_braw(node: &mut BNode) -> &mut BRaw { if let BNode::Raw(braw) = node { return braw; diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 80b626163a5..86abf853ee3 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -30,7 +30,7 @@ pub enum VNode { VSuspense(VSuspense), /// A raw HTML string, represented by [`AttrValue`](crate::AttrValue). /// - /// Also see: [`VNode::from_html_raw`] + /// Also see: [`VNode::from_raw_html`] VRaw(VRaw), } @@ -73,11 +73,10 @@ impl VNode { /// # Example /// /// ```rust - /// # use yew::virtual_dom::VNode; - /// use yew::{html, AttrValue}; + /// use yew::{html, AttrValue, Html}; /// # fn _main() { - /// let parsed = VNode::from_raw_html(AttrValue::from("
content
")); - /// let _: VNode = html! { + /// let parsed = Html::from_raw_html(AttrValue::from("
content
")); + /// let _: Html = html! { ///
/// {parsed} ///
diff --git a/packages/yew/tests/raw_html.rs b/packages/yew/tests/raw_html.rs index af5aec892a0..9bcb806dfdb 100644 --- a/packages/yew/tests/raw_html.rs +++ b/packages/yew/tests/raw_html.rs @@ -72,21 +72,98 @@ create_test!( r#"

paragraph

link"# ); +macro_rules! create_update_html_test { + ($name:ident, $initial:expr, $updated:expr) => { + #[cfg(target_arch = "wasm32")] + #[test] + async fn $name() { + #[function_component] + fn App() -> Html { + let raw_html = use_state(|| ($initial)); + let onclick = { + let raw_html = raw_html.clone(); + move |_| raw_html.set($updated) + }; + let raw = Html::from_raw_html(AttrValue::from(*raw_html)); + html! { + <> +
+ {raw} +
+ + + } + } + use std::time::Duration; + + use yew::platform::time::sleep; + + yew::Renderer::::with_root( + gloo::utils::document().get_element_by_id("output").unwrap(), + ) + .render(); + + // wait for render to finish + sleep(Duration::from_millis(100)).await; + + let e = gloo::utils::document() + .get_element_by_id("raw-container") + .unwrap(); + assert_eq!(e.inner_html(), $initial); + + gloo::utils::document() + .get_element_by_id("click-me-btn") + .unwrap() + .unchecked_into::() + .click(); + + sleep(Duration::from_millis(100)).await; + + let e = gloo::utils::document() + .get_element_by_id("raw-container") + .unwrap(); + assert_eq!(e.inner_html(), $updated); + } + }; +} + +create_update_html_test!( + set_new_html_string, + "first", + "second" +); + +create_update_html_test!( + set_new_html_string_multiple_children, + "firstsecond", + "second" +); + +create_update_html_test!( + clear_html_string_multiple_children, + "firstsecond", + "" +); +create_update_html_test!( + nothing_changes, + "firstsecond", + "firstsecond" +); + #[cfg(target_arch = "wasm32")] #[test] -async fn set_new_html_string() { +async fn change_vnode_types_from_other_to_vraw() { #[function_component] fn App() -> Html { - let raw_html = use_state(|| ("first")); + let node = use_state(|| html!("text")); let onclick = { - let raw_html = raw_html.clone(); - move |_| raw_html.set("second") + let node = node.clone(); + move |_| node.set(Html::from_raw_html(AttrValue::from("second"))) }; - let raw = Html::from_raw_html(AttrValue::from(*raw_html)); html! { <>
- {raw} + {(*node).clone()}
@@ -105,7 +182,7 @@ async fn set_new_html_string() { let e = gloo::utils::document() .get_element_by_id("raw-container") .unwrap(); - assert_eq!(e.inner_html(), "first"); + assert_eq!(e.inner_html(), "text"); gloo::utils::document() .get_element_by_id("click-me-btn") @@ -120,3 +197,51 @@ async fn set_new_html_string() { .unwrap(); assert_eq!(e.inner_html(), "second"); } + +#[cfg(target_arch = "wasm32")] +#[test] +async fn change_vnode_types_from_vraw_to_other() { + #[function_component] + fn App() -> Html { + let node = use_state(|| Html::from_raw_html(AttrValue::from("second"))); + let onclick = { + let node = node.clone(); + move |_| node.set(html!("text")) + }; + html! { + <> +
+ {(*node).clone()} +
+ + + } + } + use std::time::Duration; + + use yew::platform::time::sleep; + + yew::Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap()) + .render(); + + // wait for render to finish + sleep(Duration::from_millis(100)).await; + + let e = gloo::utils::document() + .get_element_by_id("raw-container") + .unwrap(); + assert_eq!(e.inner_html(), "second"); + + gloo::utils::document() + .get_element_by_id("click-me-btn") + .unwrap() + .unchecked_into::() + .click(); + + sleep(Duration::from_millis(100)).await; + + let e = gloo::utils::document() + .get_element_by_id("raw-container") + .unwrap(); + assert_eq!(e.inner_html(), "text"); +} From 68b2d8c841bb50da302d38cb748107aa320f3a4d Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 24 Sep 2022 04:37:44 +0500 Subject: [PATCH 18/30] Reduce DOM calls --- packages/yew/src/dom_bundle/braw.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 97a69e818b3..54d8a7669a3 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -57,15 +57,11 @@ impl ReconcileTarget for BRaw { fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef { if let Some(node) = self.reference.get() { - if let Some(parent) = node.parent_node() { - parent.remove_child(&node).unwrap(); - } - - next_parent + let new_node = next_parent .insert_before(&node, next_sibling.get().as_ref()) .unwrap(); - return NodeRef::new(node.into()); + return NodeRef::new(new_node); } NodeRef::default() } From a5a7787f49fb65e06238c327695460e04f2743c3 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 24 Sep 2022 19:45:02 +0500 Subject: [PATCH 19/30] improve detach bundle impl --- packages/yew/src/dom_bundle/braw.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 54d8a7669a3..8b8b4a4b4cf 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -31,20 +31,11 @@ impl BRaw { } fn detach_bundle(&self, parent: &Element) { - if let Some(node) = self.reference.get() { - let mut next_sibling = node.next_sibling(); - - parent - .remove_child(&node) - .expect("failed to remove braw node"); - let len = self.children_count - 1; - for _ in 0..len { - if let Some(node) = next_sibling { - next_sibling = node.clone().next_sibling(); - parent - .remove_child(&node) - .expect("failed to remove braw node"); - } + let mut next_node = self.reference.get(); + for _ in 0..self.children_count { + if let Some(node) = next_node { + next_node = node.next_sibling(); + parent.remove_child(&node).unwrap(); } } } From 8fa601b25bc4d431ea8e5497ca0cbb2d1cf85ad2 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 25 Sep 2022 17:13:34 +0500 Subject: [PATCH 20/30] Add more tests --- packages/yew/src/dom_bundle/braw.rs | 87 +++++++++++++++++++++++++++- packages/yew/src/dom_bundle/utils.rs | 20 ++++++- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 8b8b4a4b4cf..5f1a0a153a9 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -140,7 +140,7 @@ mod tests { use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use super::*; - use crate::dom_bundle::utils::setup_parent; + use crate::dom_bundle::utils::{setup_parent, SIBLING_CONTENT, setup_parent_and_sibling}; use crate::virtual_dom::VNode; wasm_bindgen_test_configure!(run_in_browser); @@ -228,6 +228,91 @@ mod tests { assert_eq!(parent.inner_html(), ""); } + #[test] + fn braw_works_one_node_sibling_attached() { + let (root, scope, parent, sibling) = setup_parent_and_sibling(); + + const HTML: &str = "text"; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + } + + #[test] + fn braw_works_no_node_sibling_attached() { + let (root, scope, parent, sibling) = setup_parent_and_sibling(); + + const HTML: &str = ""; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + } + + #[test] + fn braw_works_one_node_nested_sibling_attached() { + let (root, scope, parent, sibling) = setup_parent_and_sibling(); + + const HTML: &str = + r#"

one link more paragraph

here
"#; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + } + #[test] + fn braw_works_multi_top_nodes_sibling_attached() { + let (root, scope, parent, sibling) = setup_parent_and_sibling(); + + const HTML: &str = r#"

paragraph

link"#; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + } + + + #[test] + fn braw_detach_works_multi_node_sibling_attached() { + let (root, scope, parent, sibling) = setup_parent_and_sibling(); + + const HTML: &str = r#"

paragraph

link"#; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + elem.detach(&root, &parent, false); + assert_eq!(parent.inner_html(), format!("{}", SIBLING_CONTENT)) + } + + #[test] + fn braw_detach_works_single_node_sibling_attached() { + let (root, scope, parent, sibling) = setup_parent_and_sibling(); + + const HTML: &str = r#"

paragraph

"#; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + elem.detach(&root, &parent, false); + assert_eq!(parent.inner_html(), format!("{}", SIBLING_CONTENT)) + } + + #[test] + fn braw_detach_works_empty_sibling_attached() { + let (root, scope, parent, sibling) = setup_parent_and_sibling(); + + const HTML: &str = ""; + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + elem.detach(&root, &parent, false); + assert_eq!(parent.inner_html(), format!("{}", SIBLING_CONTENT)) + } + + fn assert_braw(node: &mut BNode) -> &mut BRaw { if let BNode::Raw(braw) = node { return braw; diff --git a/packages/yew/src/dom_bundle/utils.rs b/packages/yew/src/dom_bundle/utils.rs index eae2ccb3dc5..5582b2c20c8 100644 --- a/packages/yew/src/dom_bundle/utils.rs +++ b/packages/yew/src/dom_bundle/utils.rs @@ -80,13 +80,15 @@ pub(super) use feat_hydration::*; #[cfg(test)] mod tests { + #![allow(dead_code)] + use gloo::utils::document; use web_sys::Element; use crate::dom_bundle::BSubtree; use crate::html::AnyScope; + use crate::NodeRef; - #[allow(dead_code)] pub fn setup_parent() -> (BSubtree, AnyScope, Element) { let scope = AnyScope::test(); let parent = document().create_element("div").unwrap(); @@ -96,6 +98,22 @@ mod tests { (root, scope, parent) } + + pub const SIBLING_CONTENT: &str = "END"; + + pub fn setup_parent_and_sibling() -> (BSubtree, AnyScope, Element, NodeRef) { + let scope = AnyScope::test(); + let parent = document().create_element("div").unwrap(); + let root = BSubtree::create_root(&parent); + + document().body().unwrap().append_child(&parent).unwrap(); + + let end = document().create_text_node(SIBLING_CONTENT); + parent.append_child(&end).unwrap(); + let sibling = NodeRef::new(end.into()); + + (root, scope, parent, sibling) + } } #[cfg(test)] From 8d45239871012d58aae0e0776327498f39844051 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 25 Sep 2022 17:14:18 +0500 Subject: [PATCH 21/30] Update example --- examples/inner_html/src/document.html | 4 ---- examples/inner_html/src/main.rs | 6 ++---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/inner_html/src/document.html b/examples/inner_html/src/document.html index dbff84fe611..8ccc6707657 100644 --- a/examples/inner_html/src/document.html +++ b/examples/inner_html/src/document.html @@ -4,10 +4,6 @@

Inline HTML with SVG

Rust source code. The code queries the DOM, creates a new element, and applies this snippet of HTML to the element's innerHTML.

-

- If you look at your browser's console you can see the DOM element (logged to - the console). -

) -> Self { - Self { value: 0 } + Self } fn view(&self, _ctx: &Context) -> Html { From e264adf90345a6d5134a7c098c0cf153036198f7 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 25 Sep 2022 18:09:33 +0500 Subject: [PATCH 22/30] fmt --- packages/yew/src/dom_bundle/braw.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 5f1a0a153a9..8e37bc01e7c 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -140,7 +140,7 @@ mod tests { use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use super::*; - use crate::dom_bundle::utils::{setup_parent, SIBLING_CONTENT, setup_parent_and_sibling}; + use crate::dom_bundle::utils::{setup_parent, setup_parent_and_sibling, SIBLING_CONTENT}; use crate::virtual_dom::VNode; wasm_bindgen_test_configure!(run_in_browser); @@ -272,7 +272,6 @@ mod tests { assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); } - #[test] fn braw_detach_works_multi_node_sibling_attached() { let (root, scope, parent, sibling) = setup_parent_and_sibling(); @@ -312,7 +311,6 @@ mod tests { assert_eq!(parent.inner_html(), format!("{}", SIBLING_CONTENT)) } - fn assert_braw(node: &mut BNode) -> &mut BRaw { if let BNode::Raw(braw) = node { return braw; From b69f53eb4d12101e5d1ed60ce5272bf7b8427312 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 22 Oct 2022 18:59:04 +0500 Subject: [PATCH 23/30] Apply review suggestions --- packages/yew/src/dom_bundle/braw.rs | 60 +++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 8e37bc01e7c..0bccc42f309 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -19,7 +19,6 @@ pub struct BRaw { impl BRaw { fn create_elements(html: &str) -> Vec { let div = gloo::utils::document().create_element("div").unwrap(); - let html = html.trim(); div.set_inner_html(html); let children = div.children(); let children = js_sys::Array::from(&children); @@ -47,14 +46,16 @@ impl ReconcileTarget for BRaw { } fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef { - if let Some(node) = self.reference.get() { - let new_node = next_parent - .insert_before(&node, next_sibling.get().as_ref()) + let mut next_node = match self.reference.get() { + Some(n) => n, + None => return NodeRef::default(), + }; + for _ in 0..self.children_count { + next_node = next_parent + .insert_before(&next_node, next_sibling.get().as_ref()) .unwrap(); - - return NodeRef::new(new_node); } - NodeRef::default() + return NodeRef::new(next_node); } } @@ -134,9 +135,11 @@ impl Reconcilable for VRaw { } } } + #[cfg(target_arch = "wasm32")] #[cfg(test)] mod tests { + use gloo::utils::document; use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure}; use super::*; @@ -311,6 +314,49 @@ mod tests { assert_eq!(parent.inner_html(), format!("{}", SIBLING_CONTENT)) } + #[test] + fn braw_shift_works() { + let (root, scope, parent) = setup_parent(); + const HTML: &str = r#"

paragraph

"#; + + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), HTML); + + let new_parent = document().create_element("section").unwrap(); + document().body().unwrap().append_child(&parent).unwrap(); + + elem.shift(&new_parent, NodeRef::default()); + + assert_eq!(new_parent.inner_html(), HTML); + assert_eq!(parent.inner_html(), ""); + } + + #[test] + fn braw_shift_with_sibling_works() { + let (root, scope, parent, sibling) = setup_parent_and_sibling(); + const HTML: &str = r#"

paragraph

"#; + + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + + let new_parent = document().create_element("section").unwrap(); + document().body().unwrap().append_child(&parent).unwrap(); + + let new_sibling = document().create_text_node(SIBLING_CONTENT); + new_parent.append_child(&new_sibling).unwrap(); + let new_sibling_ref = NodeRef::new(new_sibling.into()); + + elem.shift(&new_parent, new_sibling_ref); + + assert_eq!(parent.inner_html(), SIBLING_CONTENT); + + assert_eq!(new_parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + } + fn assert_braw(node: &mut BNode) -> &mut BRaw { if let BNode::Raw(braw) = node { return braw; From 4bd8d0146d6f73bce09c711fcca39bfe393f3dc0 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 22 Oct 2022 19:00:31 +0500 Subject: [PATCH 24/30] fmt --- packages/yew/src/dom_bundle/braw.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 0bccc42f309..fcc049bb44d 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -354,7 +354,10 @@ mod tests { assert_eq!(parent.inner_html(), SIBLING_CONTENT); - assert_eq!(new_parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); + assert_eq!( + new_parent.inner_html(), + format!("{}{}", HTML, SIBLING_CONTENT) + ); } fn assert_braw(node: &mut BNode) -> &mut BRaw { From dfe71723a81ead88e0d49cb2ffa8e856268ea227 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 23 Oct 2022 13:00:49 +0500 Subject: [PATCH 25/30] fix ci --- packages/yew/src/dom_bundle/braw.rs | 2 +- packages/yew/tests/raw_html.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index fcc049bb44d..2d78c9f35c7 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -55,7 +55,7 @@ impl ReconcileTarget for BRaw { .insert_before(&next_node, next_sibling.get().as_ref()) .unwrap(); } - return NodeRef::new(next_node); + NodeRef::new(next_node) } } diff --git a/packages/yew/tests/raw_html.rs b/packages/yew/tests/raw_html.rs index 9bcb806dfdb..1802f3c597f 100644 --- a/packages/yew/tests/raw_html.rs +++ b/packages/yew/tests/raw_html.rs @@ -7,7 +7,7 @@ use wasm_bindgen_test::wasm_bindgen_test as test; use yew::prelude::*; #[cfg(target_arch = "wasm32")] wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); -#[cfg(feature = "tokio")] +#[cfg(not(target_arch = "wasm32"))] use tokio::test; macro_rules! create_test { From a49843206c4a13997b2e70113a2d681ec011cc2a Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 5 Nov 2022 18:55:56 +0500 Subject: [PATCH 26/30] fix braw shift with multiple nodes --- packages/yew/src/dom_bundle/braw.rs | 37 ++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 2d78c9f35c7..9be98f8d142 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -50,10 +50,22 @@ impl ReconcileTarget for BRaw { Some(n) => n, None => return NodeRef::default(), }; + let insert = |n| { + next_parent + .insert_before(&n, next_sibling.get().as_ref()) + .unwrap() + }; for _ in 0..self.children_count { - next_node = next_parent - .insert_before(&next_node, next_sibling.get().as_ref()) - .unwrap(); + let current = next_node; + next_node = match current.next_sibling() { + Some(n) => n, + None => { + // if nothing is next, add whatever is the current node and return early + insert(current.clone()); + return NodeRef::new(current); + } + }; + insert(current); } NodeRef::new(next_node) } @@ -360,6 +372,25 @@ mod tests { ); } + #[test] + fn braw_shift_works_multi_node() { + let (root, scope, parent) = setup_parent(); + const HTML: &str = r#"

paragraph

link"#; + + let elem = VNode::from_raw_html(HTML.into()); + let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); + assert_braw(&mut elem); + assert_eq!(parent.inner_html(), HTML); + + let new_parent = document().create_element("section").unwrap(); + document().body().unwrap().append_child(&parent).unwrap(); + + elem.shift(&new_parent, NodeRef::default()); + + assert_eq!(parent.inner_html(), ""); + assert_eq!(new_parent.inner_html(), HTML); + } + fn assert_braw(node: &mut BNode) -> &mut BRaw { if let BNode::Raw(braw) = node { return braw; From cb789f6af56eed1376c880282269b15503b2929b Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 5 Nov 2022 18:57:00 +0500 Subject: [PATCH 27/30] rename function name --- examples/inner_html/src/main.rs | 2 +- packages/yew/src/dom_bundle/braw.rs | 34 +++++++++++++-------------- packages/yew/src/virtual_dom/vnode.rs | 6 ++--- packages/yew/tests/raw_html.rs | 8 +++---- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/inner_html/src/main.rs b/examples/inner_html/src/main.rs index e8ae6df80d1..de7fb993b45 100644 --- a/examples/inner_html/src/main.rs +++ b/examples/inner_html/src/main.rs @@ -13,7 +13,7 @@ impl Component for App { } fn view(&self, _ctx: &Context) -> Html { - Html::from_raw_html(HTML.into()) + Html::from_html_unchecked(HTML.into()) } } diff --git a/packages/yew/src/dom_bundle/braw.rs b/packages/yew/src/dom_bundle/braw.rs index 9be98f8d142..d0b3828e316 100644 --- a/packages/yew/src/dom_bundle/braw.rs +++ b/packages/yew/src/dom_bundle/braw.rs @@ -165,7 +165,7 @@ mod tests { let (root, scope, parent) = setup_parent(); const HTML: &str = "text"; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); assert_eq!(parent.inner_html(), HTML) @@ -176,7 +176,7 @@ mod tests { let (root, scope, parent) = setup_parent(); const HTML: &str = ""; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); assert_eq!(parent.inner_html(), HTML) @@ -188,7 +188,7 @@ mod tests { const HTML: &str = r#"

one link more paragraph

here
"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); assert_eq!(parent.inner_html(), HTML) @@ -198,7 +198,7 @@ mod tests { let (root, scope, parent) = setup_parent(); const HTML: &str = r#"

paragraph

link"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); assert_eq!(parent.inner_html(), HTML) @@ -209,7 +209,7 @@ mod tests { let (root, scope, parent) = setup_parent(); const HTML: &str = r#"

paragraph

link"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); assert_eq!(parent.inner_html(), HTML); @@ -222,7 +222,7 @@ mod tests { let (root, scope, parent) = setup_parent(); const HTML: &str = r#"

paragraph

"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); assert_eq!(parent.inner_html(), HTML); @@ -235,7 +235,7 @@ mod tests { let (root, scope, parent) = setup_parent(); const HTML: &str = ""; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); assert_eq!(parent.inner_html(), HTML); @@ -248,7 +248,7 @@ mod tests { let (root, scope, parent, sibling) = setup_parent_and_sibling(); const HTML: &str = "text"; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); assert_braw(&mut elem); assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); @@ -259,7 +259,7 @@ mod tests { let (root, scope, parent, sibling) = setup_parent_and_sibling(); const HTML: &str = ""; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); assert_braw(&mut elem); assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); @@ -271,7 +271,7 @@ mod tests { const HTML: &str = r#"

one link more paragraph

here
"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); assert_braw(&mut elem); assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); @@ -281,7 +281,7 @@ mod tests { let (root, scope, parent, sibling) = setup_parent_and_sibling(); const HTML: &str = r#"

paragraph

link"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); assert_braw(&mut elem); assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); @@ -292,7 +292,7 @@ mod tests { let (root, scope, parent, sibling) = setup_parent_and_sibling(); const HTML: &str = r#"

paragraph

link"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); assert_braw(&mut elem); assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); @@ -305,7 +305,7 @@ mod tests { let (root, scope, parent, sibling) = setup_parent_and_sibling(); const HTML: &str = r#"

paragraph

"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); assert_braw(&mut elem); assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); @@ -318,7 +318,7 @@ mod tests { let (root, scope, parent, sibling) = setup_parent_and_sibling(); const HTML: &str = ""; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); assert_braw(&mut elem); assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); @@ -331,7 +331,7 @@ mod tests { let (root, scope, parent) = setup_parent(); const HTML: &str = r#"

paragraph

"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); assert_eq!(parent.inner_html(), HTML); @@ -350,7 +350,7 @@ mod tests { let (root, scope, parent, sibling) = setup_parent_and_sibling(); const HTML: &str = r#"

paragraph

"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, sibling); assert_braw(&mut elem); assert_eq!(parent.inner_html(), format!("{}{}", HTML, SIBLING_CONTENT)); @@ -377,7 +377,7 @@ mod tests { let (root, scope, parent) = setup_parent(); const HTML: &str = r#"

paragraph

link"#; - let elem = VNode::from_raw_html(HTML.into()); + let elem = VNode::from_html_unchecked(HTML.into()); let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default()); assert_braw(&mut elem); assert_eq!(parent.inner_html(), HTML); diff --git a/packages/yew/src/virtual_dom/vnode.rs b/packages/yew/src/virtual_dom/vnode.rs index 86abf853ee3..a03c59352cc 100644 --- a/packages/yew/src/virtual_dom/vnode.rs +++ b/packages/yew/src/virtual_dom/vnode.rs @@ -30,7 +30,7 @@ pub enum VNode { VSuspense(VSuspense), /// A raw HTML string, represented by [`AttrValue`](crate::AttrValue). /// - /// Also see: [`VNode::from_raw_html`] + /// Also see: [`VNode::from_html_unchecked`] VRaw(VRaw), } @@ -75,7 +75,7 @@ impl VNode { /// ```rust /// use yew::{html, AttrValue, Html}; /// # fn _main() { - /// let parsed = Html::from_raw_html(AttrValue::from("
content
")); + /// let parsed = Html::from_html_unchecked(AttrValue::from("
content
")); /// let _: Html = html! { ///
/// {parsed} @@ -83,7 +83,7 @@ impl VNode { /// }; /// # } /// ``` - pub fn from_raw_html(html: AttrValue) -> Self { + pub fn from_html_unchecked(html: AttrValue) -> Self { VNode::VRaw(VRaw { html }) } } diff --git a/packages/yew/tests/raw_html.rs b/packages/yew/tests/raw_html.rs index 1802f3c597f..c1979f35a9d 100644 --- a/packages/yew/tests/raw_html.rs +++ b/packages/yew/tests/raw_html.rs @@ -19,7 +19,7 @@ macro_rules! create_test { async fn $name() { #[function_component] fn App() -> Html { - let raw = Html::from_raw_html(AttrValue::from($raw)); + let raw = Html::from_html_unchecked(AttrValue::from($raw)); html! {
{raw} @@ -84,7 +84,7 @@ macro_rules! create_update_html_test { let raw_html = raw_html.clone(); move |_| raw_html.set($updated) }; - let raw = Html::from_raw_html(AttrValue::from(*raw_html)); + let raw = Html::from_html_unchecked(AttrValue::from(*raw_html)); html! { <>
@@ -158,7 +158,7 @@ async fn change_vnode_types_from_other_to_vraw() { let node = use_state(|| html!("text")); let onclick = { let node = node.clone(); - move |_| node.set(Html::from_raw_html(AttrValue::from("second"))) + move |_| node.set(Html::from_html_unchecked(AttrValue::from("second"))) }; html! { <> @@ -203,7 +203,7 @@ async fn change_vnode_types_from_other_to_vraw() { async fn change_vnode_types_from_vraw_to_other() { #[function_component] fn App() -> Html { - let node = use_state(|| Html::from_raw_html(AttrValue::from("second"))); + let node = use_state(|| Html::from_html_unchecked(AttrValue::from("second"))); let onclick = { let node = node.clone(); move |_| node.set(html!("text")) From 117c48f0389c52374384dc03057441687a2857b6 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sat, 5 Nov 2022 19:04:56 +0500 Subject: [PATCH 28/30] fmt --- packages/yew/tests/raw_html.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/yew/tests/raw_html.rs b/packages/yew/tests/raw_html.rs index c1979f35a9d..031ba7f50db 100644 --- a/packages/yew/tests/raw_html.rs +++ b/packages/yew/tests/raw_html.rs @@ -158,7 +158,11 @@ async fn change_vnode_types_from_other_to_vraw() { let node = use_state(|| html!("text")); let onclick = { let node = node.clone(); - move |_| node.set(Html::from_html_unchecked(AttrValue::from("second"))) + move |_| { + node.set(Html::from_html_unchecked(AttrValue::from( + "second", + ))) + } }; html! { <> From f2241cc393c7bfeb2f61efcfff021b5865bc1ca5 Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 6 Nov 2022 18:13:22 +0500 Subject: [PATCH 29/30] this should've been there --- packages/yew/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index 1d5c6f72d53..a1c83c0c6b0 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -56,6 +56,7 @@ features = [ "FocusEvent", "HtmlElement", "HtmlInputElement", + "HtmlCollection", "HtmlTextAreaElement", "InputEvent", "InputEventInit", From 4850d5282cc92fa4b29b7f9a8fc984a00562bced Mon Sep 17 00:00:00 2001 From: Muhammad Hamza Date: Sun, 6 Nov 2022 21:16:05 +0500 Subject: [PATCH 30/30] ci be green --- packages/yew/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/yew/Cargo.toml b/packages/yew/Cargo.toml index a1c83c0c6b0..e609be9c194 100644 --- a/packages/yew/Cargo.toml +++ b/packages/yew/Cargo.toml @@ -92,6 +92,7 @@ version = "0.3" features = [ "ShadowRootInit", "ShadowRootMode", + "HtmlButtonElement" ] [features]