mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Make Html (VNode) cheap to clone (#3431)
* Make VNode cheap to clone * Faster clone for list and portal * Fixes hopefully good * clippy * more fixes hopefully good * rustfmt * More fixes * more fixes... * more fixes * Update element-fail.stderr * Macro fixes... * CLEANUP * Benchmark with divan * WIP workflow * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * WIP * Use the 2 workflows approach, will fix after merge if not working * CLEANUP * can i push that change here pretty please * Trigger CI
This commit is contained in:
parent
aa63234419
commit
7f45af3a66
@ -1,5 +1,7 @@
|
||||
/// Original author of this code is [Nathan Ringo](https://github.com/remexre)
|
||||
/// Source: https://github.com/acmumn/mentoring/blob/master/web-client/src/view/markdown.rs
|
||||
use std::rc::Rc;
|
||||
|
||||
use pulldown_cmark::{Alignment, CodeBlockKind, Event, Options, Parser, Tag};
|
||||
use yew::virtual_dom::{VNode, VTag, VText};
|
||||
use yew::{html, Classes, Html};
|
||||
@ -56,16 +58,22 @@ pub fn render_markdown(src: &str) -> Html {
|
||||
if let Some(top_children) = top.children_mut() {
|
||||
for r in top_children.to_vlist_mut().iter_mut() {
|
||||
if let VNode::VTag(ref mut vtag) = r {
|
||||
if let Some(vtag_children) = vtag.children_mut() {
|
||||
if let Some(vtag_children) = Rc::make_mut(vtag).children_mut() {
|
||||
for (i, c) in
|
||||
vtag_children.to_vlist_mut().iter_mut().enumerate()
|
||||
{
|
||||
if let VNode::VTag(ref mut vtag) = c {
|
||||
match aligns[i] {
|
||||
Alignment::None => {}
|
||||
Alignment::Left => add_class(vtag, "text-left"),
|
||||
Alignment::Center => add_class(vtag, "text-center"),
|
||||
Alignment::Right => add_class(vtag, "text-right"),
|
||||
Alignment::Left => {
|
||||
add_class(Rc::make_mut(vtag), "text-left")
|
||||
}
|
||||
Alignment::Center => {
|
||||
add_class(Rc::make_mut(vtag), "text-center")
|
||||
}
|
||||
Alignment::Right => {
|
||||
add_class(Rc::make_mut(vtag), "text-right")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,7 +87,7 @@ pub fn render_markdown(src: &str) -> Html {
|
||||
if let VNode::VTag(ref mut vtag) = c {
|
||||
// TODO
|
||||
// vtag.tag = "th".into();
|
||||
vtag.add_attribute("scope", "col");
|
||||
Rc::make_mut(vtag).add_attribute("scope", "col");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,7 +107,7 @@ pub fn render_markdown(src: &str) -> Html {
|
||||
}
|
||||
|
||||
if elems.len() == 1 {
|
||||
VNode::VTag(Box::new(elems.pop().unwrap()))
|
||||
VNode::VTag(Rc::new(elems.pop().unwrap()))
|
||||
} else {
|
||||
html! {
|
||||
<div>{ for elems.into_iter() }</div>
|
||||
|
||||
@ -359,7 +359,7 @@ impl ToTokens for HtmlElement {
|
||||
quote! {
|
||||
::std::convert::Into::<::yew::virtual_dom::VNode>::into(
|
||||
::yew::virtual_dom::VTag::__new_other(
|
||||
::std::borrow::Cow::<'static, ::std::primitive::str>::Borrowed(#name),
|
||||
::yew::virtual_dom::AttrValue::Static(#name),
|
||||
#node_ref,
|
||||
#key,
|
||||
#attributes,
|
||||
@ -416,7 +416,7 @@ impl ToTokens for HtmlElement {
|
||||
// (note the extra braces). Hence the need for the `allow`.
|
||||
// Anyways to remove the braces?
|
||||
let mut #vtag_name = ::std::convert::Into::<
|
||||
::std::borrow::Cow::<'static, ::std::primitive::str>
|
||||
::yew::virtual_dom::AttrValue
|
||||
>::into(#expr);
|
||||
::std::debug_assert!(
|
||||
#vtag_name.is_ascii(),
|
||||
|
||||
@ -78,9 +78,9 @@ impl ToTokens for HtmlList {
|
||||
};
|
||||
|
||||
tokens.extend(quote_spanned! {spanned.span()=>
|
||||
::yew::virtual_dom::VNode::VList(
|
||||
::yew::virtual_dom::VNode::VList(::std::rc::Rc::new(
|
||||
::yew::virtual_dom::VList::with_children(#children, #key)
|
||||
)
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ impl ToTokens for HtmlTree {
|
||||
lint::lint_all(self);
|
||||
match self {
|
||||
HtmlTree::Empty => tokens.extend(quote! {
|
||||
::yew::virtual_dom::VNode::VList(::yew::virtual_dom::VList::new())
|
||||
<::yew::virtual_dom::VNode as ::std::default::Default>::default()
|
||||
}),
|
||||
HtmlTree::Component(comp) => comp.to_tokens(tokens),
|
||||
HtmlTree::Element(tag) => tag.to_tokens(tokens),
|
||||
@ -375,9 +375,9 @@ impl ToTokens for HtmlRootBraced {
|
||||
|
||||
tokens.extend(quote_spanned! {brace.span.span()=>
|
||||
{
|
||||
::yew::virtual_dom::VNode::VList(
|
||||
::yew::virtual_dom::VNode::VList(::std::rc::Rc::new(
|
||||
::yew::virtual_dom::VList::with_children(#children, ::std::option::Option::None)
|
||||
)
|
||||
))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -83,12 +83,12 @@ impl ::std::convert::From<::yew::virtual_dom::VChild<AltChild>> for ChildrenVari
|
||||
impl ::std::convert::Into<::yew::virtual_dom::VNode> for ChildrenVariants {
|
||||
fn into(self) -> ::yew::virtual_dom::VNode {
|
||||
match self {
|
||||
Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::<
|
||||
Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
|
||||
::yew::virtual_dom::VComp,
|
||||
>::into(comp)),
|
||||
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::<
|
||||
>::into(comp))),
|
||||
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
|
||||
::yew::virtual_dom::VComp,
|
||||
>::into(comp)),
|
||||
>::into(comp))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,12 +82,12 @@ impl ::std::convert::From<::yew::virtual_dom::VChild<AltChild>> for ChildrenVari
|
||||
impl ::std::convert::Into<::yew::virtual_dom::VNode> for ChildrenVariants {
|
||||
fn into(self) -> ::yew::virtual_dom::VNode {
|
||||
match self {
|
||||
Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::<
|
||||
Self::Child(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
|
||||
::yew::virtual_dom::VComp,
|
||||
>::into(comp)),
|
||||
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::convert::Into::<
|
||||
>::into(comp))),
|
||||
Self::AltChild(comp) => ::yew::virtual_dom::VNode::VComp(::std::rc::Rc::new(::std::convert::Into::<
|
||||
::yew::virtual_dom::VComp,
|
||||
>::into(comp)),
|
||||
>::into(comp))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -703,20 +703,15 @@ error[E0277]: the trait bound `(): IntoPropValue<yew::NodeRef>` is not satisfied
|
||||
and $N others
|
||||
= note: this error originates in the macro `html` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `Cow<'static, str>: From<{integer}>` is not satisfied
|
||||
error[E0277]: the trait bound `implicit_clone::unsync::IString: From<{integer}>` is not satisfied
|
||||
--> tests/html_macro/element-fail.rs:77:15
|
||||
|
|
||||
77 | html! { <@{55}></@> };
|
||||
| ^^^^ the trait `From<{integer}>` is not implemented for `Cow<'static, str>`
|
||||
| ^^^^ the trait `From<{integer}>` is not implemented for `implicit_clone::unsync::IString`
|
||||
|
|
||||
= help: the following other types implement trait `From<T>`:
|
||||
<Cow<'a, CStr> as From<&'a CStr>>
|
||||
<Cow<'a, CStr> as From<&'a CString>>
|
||||
<Cow<'a, CStr> as From<CString>>
|
||||
<Cow<'a, OsStr> as From<&'a OsStr>>
|
||||
<Cow<'a, OsStr> as From<&'a OsString>>
|
||||
<Cow<'a, OsStr> as From<OsString>>
|
||||
<Cow<'a, Path> as From<&'a Path>>
|
||||
<Cow<'a, Path> as From<&'a PathBuf>>
|
||||
and $N others
|
||||
= note: required because of the requirements on the impl of `Into<Cow<'static, str>>` for `{integer}`
|
||||
<implicit_clone::unsync::IString as From<&'static str>>
|
||||
<implicit_clone::unsync::IString as From<Cow<'static, str>>>
|
||||
<implicit_clone::unsync::IString as From<Rc<str>>>
|
||||
<implicit_clone::unsync::IString as From<String>>
|
||||
= note: required because of the requirements on the impl of `Into<implicit_clone::unsync::IString>` for `{integer}`
|
||||
|
||||
@ -4,13 +4,13 @@ use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use web_sys::Element;
|
||||
|
||||
use super::{test_log, BNode, BSubtree, DomSlot};
|
||||
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
|
||||
use crate::html::AnyScope;
|
||||
use crate::utils::RcExt;
|
||||
use crate::virtual_dom::{Key, VList, VNode, VText};
|
||||
|
||||
/// This struct represents a mounted [VList]
|
||||
@ -30,10 +30,8 @@ impl VList {
|
||||
|
||||
let children = self
|
||||
.children
|
||||
.map(Rc::try_unwrap)
|
||||
.unwrap_or_else(|| Ok(Vec::new()))
|
||||
// Rc::unwrap_or_clone is not stable yet.
|
||||
.unwrap_or_else(|m| m.to_vec());
|
||||
.map(RcExt::unwrap_or_clone)
|
||||
.unwrap_or_default();
|
||||
|
||||
(self.key, fully_keyed, children)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ use web_sys::{Element, Node};
|
||||
use super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText, DomSlot};
|
||||
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
|
||||
use crate::html::AnyScope;
|
||||
use crate::utils::RcExt;
|
||||
use crate::virtual_dom::{Key, VNode};
|
||||
|
||||
/// The bundle implementation to [VNode].
|
||||
@ -95,7 +96,8 @@ impl Reconcilable for VNode {
|
||||
) -> (DomSlot, Self::Bundle) {
|
||||
match self {
|
||||
VNode::VTag(vtag) => {
|
||||
let (node_ref, tag) = vtag.attach(root, parent_scope, parent, slot);
|
||||
let (node_ref, tag) =
|
||||
RcExt::unwrap_or_clone(vtag).attach(root, parent_scope, parent, slot);
|
||||
(node_ref, tag.into())
|
||||
}
|
||||
VNode::VText(vtext) => {
|
||||
@ -103,11 +105,13 @@ impl Reconcilable for VNode {
|
||||
(node_ref, text.into())
|
||||
}
|
||||
VNode::VComp(vcomp) => {
|
||||
let (node_ref, comp) = vcomp.attach(root, parent_scope, parent, slot);
|
||||
let (node_ref, comp) =
|
||||
RcExt::unwrap_or_clone(vcomp).attach(root, parent_scope, parent, slot);
|
||||
(node_ref, comp.into())
|
||||
}
|
||||
VNode::VList(vlist) => {
|
||||
let (node_ref, list) = vlist.attach(root, parent_scope, parent, slot);
|
||||
let (node_ref, list) =
|
||||
RcExt::unwrap_or_clone(vlist).attach(root, parent_scope, parent, slot);
|
||||
(node_ref, list.into())
|
||||
}
|
||||
VNode::VRef(node) => {
|
||||
@ -115,11 +119,13 @@ impl Reconcilable for VNode {
|
||||
(DomSlot::at(node.clone()), BNode::Ref(node))
|
||||
}
|
||||
VNode::VPortal(vportal) => {
|
||||
let (node_ref, portal) = vportal.attach(root, parent_scope, parent, slot);
|
||||
let (node_ref, portal) =
|
||||
RcExt::unwrap_or_clone(vportal).attach(root, parent_scope, parent, slot);
|
||||
(node_ref, portal.into())
|
||||
}
|
||||
VNode::VSuspense(vsuspsense) => {
|
||||
let (node_ref, suspsense) = vsuspsense.attach(root, parent_scope, parent, slot);
|
||||
let (node_ref, suspsense) =
|
||||
RcExt::unwrap_or_clone(vsuspsense).attach(root, parent_scope, parent, slot);
|
||||
(node_ref, suspsense.into())
|
||||
}
|
||||
VNode::VRaw(vraw) => {
|
||||
@ -149,20 +155,46 @@ impl Reconcilable for VNode {
|
||||
bundle: &mut BNode,
|
||||
) -> DomSlot {
|
||||
match self {
|
||||
VNode::VTag(vtag) => vtag.reconcile_node(root, parent_scope, parent, slot, bundle),
|
||||
VNode::VTag(vtag) => RcExt::unwrap_or_clone(vtag).reconcile_node(
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
slot,
|
||||
bundle,
|
||||
),
|
||||
VNode::VText(vtext) => vtext.reconcile_node(root, parent_scope, parent, slot, bundle),
|
||||
VNode::VComp(vcomp) => vcomp.reconcile_node(root, parent_scope, parent, slot, bundle),
|
||||
VNode::VList(vlist) => vlist.reconcile_node(root, parent_scope, parent, slot, bundle),
|
||||
VNode::VComp(vcomp) => RcExt::unwrap_or_clone(vcomp).reconcile_node(
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
slot,
|
||||
bundle,
|
||||
),
|
||||
VNode::VList(vlist) => RcExt::unwrap_or_clone(vlist).reconcile_node(
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
slot,
|
||||
bundle,
|
||||
),
|
||||
VNode::VRef(node) => match bundle {
|
||||
BNode::Ref(ref n) if &node == n => DomSlot::at(node),
|
||||
_ => VNode::VRef(node).replace(root, parent_scope, parent, slot, bundle),
|
||||
},
|
||||
VNode::VPortal(vportal) => {
|
||||
vportal.reconcile_node(root, parent_scope, parent, slot, bundle)
|
||||
}
|
||||
VNode::VSuspense(vsuspsense) => {
|
||||
vsuspsense.reconcile_node(root, parent_scope, parent, slot, bundle)
|
||||
}
|
||||
VNode::VPortal(vportal) => RcExt::unwrap_or_clone(vportal).reconcile_node(
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
slot,
|
||||
bundle,
|
||||
),
|
||||
VNode::VSuspense(vsuspsense) => RcExt::unwrap_or_clone(vsuspsense).reconcile_node(
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
slot,
|
||||
bundle,
|
||||
),
|
||||
VNode::VRaw(vraw) => vraw.reconcile_node(root, parent_scope, parent, slot, bundle),
|
||||
}
|
||||
}
|
||||
@ -246,10 +278,16 @@ mod feat_hydration {
|
||||
fragment: &mut Fragment,
|
||||
) -> Self::Bundle {
|
||||
match self {
|
||||
VNode::VTag(vtag) => vtag.hydrate(root, parent_scope, parent, fragment).into(),
|
||||
VNode::VTag(vtag) => RcExt::unwrap_or_clone(vtag)
|
||||
.hydrate(root, parent_scope, parent, fragment)
|
||||
.into(),
|
||||
VNode::VText(vtext) => vtext.hydrate(root, parent_scope, parent, fragment).into(),
|
||||
VNode::VComp(vcomp) => vcomp.hydrate(root, parent_scope, parent, fragment).into(),
|
||||
VNode::VList(vlist) => vlist.hydrate(root, parent_scope, parent, fragment).into(),
|
||||
VNode::VComp(vcomp) => RcExt::unwrap_or_clone(vcomp)
|
||||
.hydrate(root, parent_scope, parent, fragment)
|
||||
.into(),
|
||||
VNode::VList(vlist) => RcExt::unwrap_or_clone(vlist)
|
||||
.hydrate(root, parent_scope, parent, fragment)
|
||||
.into(),
|
||||
// You cannot hydrate a VRef.
|
||||
VNode::VRef(_) => {
|
||||
panic!(
|
||||
@ -264,7 +302,7 @@ mod feat_hydration {
|
||||
use_effect."
|
||||
)
|
||||
}
|
||||
VNode::VSuspense(vsuspense) => vsuspense
|
||||
VNode::VSuspense(vsuspense) => RcExt::unwrap_or_clone(vsuspense)
|
||||
.hydrate(root, parent_scope, parent, fragment)
|
||||
.into(),
|
||||
VNode::VRaw(vraw) => vraw.hydrate(root, parent_scope, parent, fragment).into(),
|
||||
|
||||
@ -123,6 +123,8 @@ impl BPortal {
|
||||
mod layout_tests {
|
||||
extern crate self as yew;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use gloo::utils::document;
|
||||
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
||||
use web_sys::HtmlInputElement;
|
||||
@ -151,10 +153,10 @@ mod layout_tests {
|
||||
<div>
|
||||
{VNode::VRef(first_target.clone().into())}
|
||||
{VNode::VRef(second_target.clone().into())}
|
||||
{VNode::VPortal(VPortal::new(
|
||||
{VNode::VPortal(Rc::new(VPortal::new(
|
||||
html! { {"PORTAL"} },
|
||||
first_target.clone(),
|
||||
))}
|
||||
)))}
|
||||
{"AFTER"}
|
||||
</div>
|
||||
},
|
||||
@ -166,10 +168,10 @@ mod layout_tests {
|
||||
<div>
|
||||
{VNode::VRef(first_target.clone().into())}
|
||||
{VNode::VRef(second_target.clone().into())}
|
||||
{VNode::VPortal(VPortal::new(
|
||||
{VNode::VPortal(Rc::new(VPortal::new(
|
||||
html! { {"PORTAL"} },
|
||||
second_target.clone(),
|
||||
))}
|
||||
)))}
|
||||
{"AFTER"}
|
||||
</div>
|
||||
},
|
||||
@ -181,10 +183,10 @@ mod layout_tests {
|
||||
<div>
|
||||
{VNode::VRef(first_target.clone().into())}
|
||||
{VNode::VRef(second_target.clone().into())}
|
||||
{VNode::VPortal(VPortal::new(
|
||||
{VNode::VPortal(Rc::new(VPortal::new(
|
||||
html! { <> {"PORTAL"} <b /> </> },
|
||||
second_target.clone(),
|
||||
))}
|
||||
)))}
|
||||
{"AFTER"}
|
||||
</div>
|
||||
},
|
||||
@ -207,11 +209,11 @@ mod layout_tests {
|
||||
node: html! {
|
||||
<div>
|
||||
{VNode::VRef(target_with_child.clone().into())}
|
||||
{VNode::VPortal(VPortal::new_before(
|
||||
{VNode::VPortal(Rc::new(VPortal::new_before(
|
||||
html! { {"PORTAL"} },
|
||||
target_with_child.clone(),
|
||||
Some(target_child.clone().into()),
|
||||
))}
|
||||
)))}
|
||||
</div>
|
||||
},
|
||||
expected: "<div><i>PORTAL<s></s></i></div>",
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
mod attributes;
|
||||
mod listeners;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::hint::unreachable_unchecked;
|
||||
@ -18,7 +17,7 @@ use web_sys::{Element, HtmlTextAreaElement as TextAreaElement};
|
||||
use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::vtag::{InputFields, VTagInner, Value, MATHML_NAMESPACE, SVG_NAMESPACE};
|
||||
use crate::virtual_dom::{Attributes, Key, VTag};
|
||||
use crate::virtual_dom::{AttrValue, Attributes, Key, VTag};
|
||||
use crate::NodeRef;
|
||||
|
||||
/// Applies contained changes to DOM [web_sys::Element]
|
||||
@ -51,7 +50,7 @@ enum BTagInner {
|
||||
/// Fields for all other kinds of [VTag]s
|
||||
Other {
|
||||
/// A tag of the element.
|
||||
tag: Cow<'static, str>,
|
||||
tag: AttrValue,
|
||||
/// Child node.
|
||||
child_bundle: BNode,
|
||||
},
|
||||
@ -407,6 +406,8 @@ mod feat_hydration {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
||||
use web_sys::HtmlInputElement as InputElement;
|
||||
@ -414,6 +415,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::dom_bundle::utils::setup_parent;
|
||||
use crate::dom_bundle::{BNode, Reconcilable, ReconcileTarget};
|
||||
use crate::utils::RcExt;
|
||||
use crate::virtual_dom::vtag::{HTML_NAMESPACE, SVG_NAMESPACE};
|
||||
use crate::virtual_dom::{AttrValue, VNode, VTag};
|
||||
use crate::{html, Html, NodeRef};
|
||||
@ -564,7 +566,7 @@ mod tests {
|
||||
|
||||
fn assert_vtag(node: VNode) -> VTag {
|
||||
if let VNode::VTag(vtag) = node {
|
||||
return *vtag;
|
||||
return RcExt::unwrap_or_clone(vtag);
|
||||
}
|
||||
panic!("should be vtag");
|
||||
}
|
||||
@ -971,7 +973,7 @@ mod tests {
|
||||
vtag.add_attribute("disabled", "disabled");
|
||||
vtag.add_attribute("tabindex", "0");
|
||||
|
||||
let elem = VNode::VTag(Box::new(vtag));
|
||||
let elem = VNode::VTag(Rc::new(vtag));
|
||||
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
|
||||
@ -979,7 +981,7 @@ mod tests {
|
||||
let mut vtag = VTag::new("div");
|
||||
vtag.node_ref = test_ref.clone();
|
||||
vtag.add_attribute("tabindex", "0");
|
||||
let next_elem = VNode::VTag(Box::new(vtag));
|
||||
let next_elem = VNode::VTag(Rc::new(vtag));
|
||||
let elem_vtag = assert_vtag(next_elem);
|
||||
|
||||
// Sync happens here
|
||||
|
||||
@ -6,6 +6,7 @@ use implicit_clone::ImplicitClone;
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use super::IntoPropValue;
|
||||
use crate::utils::RcExt;
|
||||
use crate::virtual_dom::AttrValue;
|
||||
|
||||
/// A set of classes, cheap to clone.
|
||||
@ -150,10 +151,7 @@ impl IntoIterator for Classes {
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
// NOTE: replace this by Rc::unwrap_or_clone() when it becomes stable
|
||||
Rc::try_unwrap(self.set)
|
||||
.unwrap_or_else(|rc| (*rc).clone())
|
||||
.into_iter()
|
||||
RcExt::unwrap_or_clone(self.set).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
//! Component children module
|
||||
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::html::Html;
|
||||
use crate::virtual_dom::{VChild, VComp, VList, VNode};
|
||||
@ -241,7 +242,7 @@ impl From<ChildrenRenderer<Html>> for Html {
|
||||
}
|
||||
}
|
||||
|
||||
Html::VList(val.into())
|
||||
Html::VList(Rc::new(val.into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,18 +46,18 @@ where
|
||||
{
|
||||
#[inline(always)]
|
||||
fn to_html(&self) -> Html {
|
||||
Html::VList(VList::with_children(
|
||||
Html::VList(Rc::new(VList::with_children(
|
||||
self.iter().map(ToHtml::to_html).collect(),
|
||||
None,
|
||||
))
|
||||
)))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn into_html(self) -> Html {
|
||||
Html::VList(VList::with_children(
|
||||
Html::VList(Rc::new(VList::with_children(
|
||||
self.into_iter().map(ToHtml::into_html).collect(),
|
||||
None,
|
||||
))
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,7 +81,7 @@ impl ToHtml for Vec<VNode> {
|
||||
|
||||
#[inline(always)]
|
||||
fn into_html(self) -> Html {
|
||||
Html::VList(VList::with_children(self, None))
|
||||
Html::VList(Rc::new(VList::with_children(self, None)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,7 +105,7 @@ impl ToHtml for VList {
|
||||
|
||||
#[inline(always)]
|
||||
fn into_html(self) -> Html {
|
||||
Html::VList(self)
|
||||
Html::VList(Rc::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +132,7 @@ where
|
||||
|
||||
#[inline(always)]
|
||||
fn into_html(self) -> Html {
|
||||
VNode::VComp(self.into())
|
||||
VNode::VComp(Rc::new(self.into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -142,5 +142,5 @@ mod feat_csr {
|
||||
/// ## Relevant examples
|
||||
/// - [Portals](https://github.com/yewstack/yew/tree/master/examples/portals)
|
||||
pub fn create_portal(child: Html, host: Element) -> Html {
|
||||
VNode::VPortal(VPortal::new(child, host))
|
||||
VNode::VPortal(Rc::new(VPortal::new(child, host)))
|
||||
}
|
||||
|
||||
@ -64,3 +64,14 @@ pub fn print_node(n: &web_sys::Node) -> String {
|
||||
None => n.text_content().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: replace this by Rc::unwrap_or_clone() when it becomes stable
|
||||
pub(crate) trait RcExt<T: Clone> {
|
||||
fn unwrap_or_clone(this: Self) -> T;
|
||||
}
|
||||
|
||||
impl<T: Clone> RcExt<T> for std::rc::Rc<T> {
|
||||
fn unwrap_or_clone(this: Self) -> T {
|
||||
std::rc::Rc::try_unwrap(this).unwrap_or_else(|rc| (*rc).clone())
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::html::ImplicitClone;
|
||||
|
||||
/// The [Listener] trait is an universal implementation of an event listener
|
||||
/// which is used to bind Rust-listener to JS-listener (DOM).
|
||||
pub trait Listener {
|
||||
@ -168,6 +170,8 @@ pub enum Listeners {
|
||||
Pending(Box<[Option<Rc<dyn Listener>>]>),
|
||||
}
|
||||
|
||||
impl ImplicitClone for Listeners {}
|
||||
|
||||
impl PartialEq for Listeners {
|
||||
fn eq(&self, rhs: &Self) -> bool {
|
||||
use Listeners::*;
|
||||
|
||||
@ -22,6 +22,7 @@ pub mod vtag;
|
||||
pub mod vtext;
|
||||
|
||||
use std::hint::unreachable_unchecked;
|
||||
use std::rc::Rc;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
@ -204,7 +205,7 @@ pub enum Attributes {
|
||||
|
||||
/// IndexMap is used to provide runtime attribute deduplication in cases where the html! macro
|
||||
/// was not used to guarantee it.
|
||||
IndexMap(IndexMap<AttrValue, (AttrValue, ApplyAttributeAs)>),
|
||||
IndexMap(Rc<IndexMap<AttrValue, (AttrValue, ApplyAttributeAs)>>),
|
||||
}
|
||||
|
||||
impl Attributes {
|
||||
@ -233,7 +234,7 @@ impl Attributes {
|
||||
macro_rules! unpack {
|
||||
() => {
|
||||
match self {
|
||||
Self::IndexMap(m) => m,
|
||||
Self::IndexMap(m) => Rc::make_mut(m),
|
||||
// SAFETY: unreachable because we set self to the `IndexMap` variant above.
|
||||
_ => unsafe { unreachable_unchecked() },
|
||||
}
|
||||
@ -241,23 +242,23 @@ impl Attributes {
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::IndexMap(m) => m,
|
||||
Self::IndexMap(m) => Rc::make_mut(m),
|
||||
Self::Static(arr) => {
|
||||
*self = Self::IndexMap(
|
||||
*self = Self::IndexMap(Rc::new(
|
||||
arr.iter()
|
||||
.map(|(k, v, ty)| ((*k).into(), ((*v).into(), *ty)))
|
||||
.collect(),
|
||||
);
|
||||
));
|
||||
unpack!()
|
||||
}
|
||||
Self::Dynamic { keys, values } => {
|
||||
*self = Self::IndexMap(
|
||||
*self = Self::IndexMap(Rc::new(
|
||||
std::mem::take(values)
|
||||
.iter_mut()
|
||||
.zip(keys.iter())
|
||||
.filter_map(|(v, k)| v.take().map(|v| (AttrValue::from(*k), v)))
|
||||
.collect(),
|
||||
);
|
||||
));
|
||||
unpack!()
|
||||
}
|
||||
}
|
||||
@ -270,7 +271,7 @@ impl From<IndexMap<AttrValue, AttrValue>> for Attributes {
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, (v, ApplyAttributeAs::Attribute)))
|
||||
.collect();
|
||||
Self::IndexMap(v)
|
||||
Self::IndexMap(Rc::new(v))
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,7 +281,7 @@ impl From<IndexMap<&'static str, AttrValue>> for Attributes {
|
||||
.into_iter()
|
||||
.map(|(k, v)| (AttrValue::Static(k), (v, ApplyAttributeAs::Attribute)))
|
||||
.collect();
|
||||
Self::IndexMap(v)
|
||||
Self::IndexMap(Rc::new(v))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut};
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::{Key, VNode};
|
||||
use crate::html::ImplicitClone;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum FullyKeyedState {
|
||||
@ -23,6 +24,8 @@ pub struct VList {
|
||||
pub key: Option<Key>,
|
||||
}
|
||||
|
||||
impl ImplicitClone for VList {}
|
||||
|
||||
impl PartialEq for VList {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.key == other.key && self.children == other.children
|
||||
|
||||
@ -2,12 +2,13 @@
|
||||
|
||||
use std::cmp::PartialEq;
|
||||
use std::iter::FromIterator;
|
||||
use std::rc::Rc;
|
||||
use std::{fmt, mem};
|
||||
|
||||
use web_sys::Node;
|
||||
|
||||
use super::{Key, VChild, VComp, VList, VPortal, VSuspense, VTag, VText};
|
||||
use crate::html::BaseComponent;
|
||||
use crate::html::{BaseComponent, ImplicitClone};
|
||||
use crate::virtual_dom::VRaw;
|
||||
use crate::AttrValue;
|
||||
|
||||
@ -16,25 +17,27 @@ use crate::AttrValue;
|
||||
#[must_use = "html does not do anything unless returned to Yew for rendering."]
|
||||
pub enum VNode {
|
||||
/// A bind between `VTag` and `Element`.
|
||||
VTag(Box<VTag>),
|
||||
VTag(Rc<VTag>),
|
||||
/// A bind between `VText` and `TextNode`.
|
||||
VText(VText),
|
||||
/// A bind between `VComp` and `Element`.
|
||||
VComp(VComp),
|
||||
VComp(Rc<VComp>),
|
||||
/// A holder for a list of other nodes.
|
||||
VList(VList),
|
||||
VList(Rc<VList>),
|
||||
/// A portal to another part of the document
|
||||
VPortal(VPortal),
|
||||
VPortal(Rc<VPortal>),
|
||||
/// A holder for any `Node` (necessary for replacing node).
|
||||
VRef(Node),
|
||||
/// A suspendible document fragment.
|
||||
VSuspense(VSuspense),
|
||||
VSuspense(Rc<VSuspense>),
|
||||
/// A raw HTML string, represented by [`AttrValue`](crate::AttrValue).
|
||||
///
|
||||
/// Also see: [`VNode::from_html_unchecked`]
|
||||
VRaw(VRaw),
|
||||
}
|
||||
|
||||
impl ImplicitClone for VNode {}
|
||||
|
||||
impl VNode {
|
||||
pub fn key(&self) -> Option<&Key> {
|
||||
match self {
|
||||
@ -60,9 +63,10 @@ impl VNode {
|
||||
pub fn to_vlist_mut(&mut self) -> &mut VList {
|
||||
loop {
|
||||
match *self {
|
||||
Self::VList(ref mut m) => return m,
|
||||
Self::VList(ref mut m) => return Rc::make_mut(m),
|
||||
_ => {
|
||||
*self = VNode::VList(VList::with_children(vec![mem::take(self)], None));
|
||||
*self =
|
||||
VNode::VList(Rc::new(VList::with_children(vec![mem::take(self)], None)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,7 +109,7 @@ impl VNode {
|
||||
|
||||
impl Default for VNode {
|
||||
fn default() -> Self {
|
||||
VNode::VList(VList::default())
|
||||
VNode::VList(Rc::new(VList::default()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,35 +123,35 @@ impl From<VText> for VNode {
|
||||
impl From<VList> for VNode {
|
||||
#[inline]
|
||||
fn from(vlist: VList) -> Self {
|
||||
VNode::VList(vlist)
|
||||
VNode::VList(Rc::new(vlist))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VTag> for VNode {
|
||||
#[inline]
|
||||
fn from(vtag: VTag) -> Self {
|
||||
VNode::VTag(Box::new(vtag))
|
||||
VNode::VTag(Rc::new(vtag))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VComp> for VNode {
|
||||
#[inline]
|
||||
fn from(vcomp: VComp) -> Self {
|
||||
VNode::VComp(vcomp)
|
||||
VNode::VComp(Rc::new(vcomp))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VSuspense> for VNode {
|
||||
#[inline]
|
||||
fn from(vsuspense: VSuspense) -> Self {
|
||||
VNode::VSuspense(vsuspense)
|
||||
VNode::VSuspense(Rc::new(vsuspense))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VPortal> for VNode {
|
||||
#[inline]
|
||||
fn from(vportal: VPortal) -> Self {
|
||||
VNode::VPortal(vportal)
|
||||
VNode::VPortal(Rc::new(vportal))
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,7 +160,7 @@ where
|
||||
COMP: BaseComponent,
|
||||
{
|
||||
fn from(vchild: VChild<COMP>) -> Self {
|
||||
VNode::VComp(VComp::from(vchild))
|
||||
VNode::VComp(Rc::new(VComp::from(vchild)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,10 +172,10 @@ impl<T: ToString> From<T> for VNode {
|
||||
|
||||
impl<A: Into<VNode>> FromIterator<A> for VNode {
|
||||
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
|
||||
VNode::VList(VList::with_children(
|
||||
VNode::VList(Rc::new(VList::with_children(
|
||||
iter.into_iter().map(|n| n.into()).collect(),
|
||||
None,
|
||||
))
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,10 +8,10 @@ use super::VNode;
|
||||
pub struct VPortal {
|
||||
/// The element under which the content is inserted.
|
||||
pub host: Element,
|
||||
/// The next sibling after the inserted content. Most be a child of `host`.
|
||||
/// The next sibling after the inserted content. Must be a child of `host`.
|
||||
pub inner_sibling: Option<Node>,
|
||||
/// The inserted node
|
||||
pub node: Box<VNode>,
|
||||
pub node: VNode,
|
||||
}
|
||||
|
||||
impl VPortal {
|
||||
@ -20,7 +20,7 @@ impl VPortal {
|
||||
Self {
|
||||
host,
|
||||
inner_sibling: None,
|
||||
node: Box::new(content),
|
||||
node: content,
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ impl VPortal {
|
||||
Self {
|
||||
host,
|
||||
inner_sibling,
|
||||
node: Box::new(content),
|
||||
node: content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use crate::html::ImplicitClone;
|
||||
use crate::AttrValue;
|
||||
|
||||
/// A raw HTML string to be used in VDOM.
|
||||
@ -6,6 +7,8 @@ pub struct VRaw {
|
||||
pub html: AttrValue,
|
||||
}
|
||||
|
||||
impl ImplicitClone for VRaw {}
|
||||
|
||||
impl From<AttrValue> for VRaw {
|
||||
fn from(html: AttrValue) -> Self {
|
||||
Self { html }
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
use super::{Key, VNode};
|
||||
use crate::html::ImplicitClone;
|
||||
|
||||
/// This struct represents a suspendable DOM fragment.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct VSuspense {
|
||||
/// Child nodes.
|
||||
pub(crate) children: Box<VNode>,
|
||||
pub(crate) children: VNode,
|
||||
/// Fallback nodes when suspended.
|
||||
pub(crate) fallback: Box<VNode>,
|
||||
pub(crate) fallback: VNode,
|
||||
/// Whether the current status is suspended.
|
||||
pub(crate) suspended: bool,
|
||||
/// The Key.
|
||||
pub(crate) key: Option<Key>,
|
||||
}
|
||||
|
||||
impl ImplicitClone for VSuspense {}
|
||||
|
||||
impl VSuspense {
|
||||
pub fn new(children: VNode, fallback: VNode, suspended: bool, key: Option<Key>) -> Self {
|
||||
Self {
|
||||
children: children.into(),
|
||||
fallback: fallback.into(),
|
||||
children,
|
||||
fallback,
|
||||
suspended,
|
||||
key,
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
//! This module contains the implementation of a virtual element node [VTag].
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::PartialEq;
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
@ -10,7 +9,7 @@ use std::rc::Rc;
|
||||
use web_sys::{HtmlInputElement as InputElement, HtmlTextAreaElement as TextAreaElement};
|
||||
|
||||
use super::{ApplyAttributeAs, AttrValue, Attributes, Key, Listener, Listeners, VNode};
|
||||
use crate::html::{IntoPropValue, NodeRef};
|
||||
use crate::html::{ImplicitClone, IntoPropValue, NodeRef};
|
||||
|
||||
/// SVG namespace string used for creating svg elements
|
||||
pub const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
|
||||
@ -22,9 +21,17 @@ pub const MATHML_NAMESPACE: &str = "http://www.w3.org/1998/Math/MathML";
|
||||
pub const HTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
/// Value field corresponding to an [Element]'s `value` property
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub(crate) struct Value<T>(Option<AttrValue>, PhantomData<T>);
|
||||
|
||||
impl<T> Clone for Value<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self::new(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ImplicitClone for Value<T> {}
|
||||
|
||||
impl<T> Default for Value<T> {
|
||||
fn default() -> Self {
|
||||
Self::new(None)
|
||||
@ -68,6 +75,8 @@ pub(crate) struct InputFields {
|
||||
pub(crate) checked: Option<bool>,
|
||||
}
|
||||
|
||||
impl ImplicitClone for InputFields {}
|
||||
|
||||
impl Deref for InputFields {
|
||||
type Target = Value<InputElement>;
|
||||
|
||||
@ -111,12 +120,14 @@ pub(crate) enum VTagInner {
|
||||
/// Fields for all other kinds of [VTag]s
|
||||
Other {
|
||||
/// A tag of the element.
|
||||
tag: Cow<'static, str>,
|
||||
tag: AttrValue,
|
||||
/// children of the element.
|
||||
children: VNode,
|
||||
},
|
||||
}
|
||||
|
||||
impl ImplicitClone for VTagInner {}
|
||||
|
||||
/// A type for a virtual
|
||||
/// [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element)
|
||||
/// representation.
|
||||
@ -133,10 +144,12 @@ pub struct VTag {
|
||||
pub key: Option<Key>,
|
||||
}
|
||||
|
||||
impl ImplicitClone for VTag {}
|
||||
|
||||
impl VTag {
|
||||
/// Creates a new [VTag] instance with `tag` name (cannot be changed later in DOM).
|
||||
pub fn new(tag: impl Into<Cow<'static, str>>) -> Self {
|
||||
let tag: Cow<'static, str> = tag.into();
|
||||
pub fn new(tag: impl Into<AttrValue>) -> Self {
|
||||
let tag = tag.into();
|
||||
Self::new_base(
|
||||
match &*tag.to_ascii_lowercase() {
|
||||
"input" => VTagInner::Input(Default::default()),
|
||||
@ -226,7 +239,7 @@ impl VTag {
|
||||
#[doc(hidden)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn __new_other(
|
||||
tag: Cow<'static, str>,
|
||||
tag: AttrValue,
|
||||
node_ref: NodeRef,
|
||||
key: Option<Key>,
|
||||
// at bottom for more readable macro-expanded coded
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
use std::cmp::PartialEq;
|
||||
|
||||
use super::AttrValue;
|
||||
use crate::html::ImplicitClone;
|
||||
|
||||
/// A type for a virtual
|
||||
/// [`TextNode`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createTextNode)
|
||||
@ -13,6 +14,8 @@ pub struct VText {
|
||||
pub text: AttrValue,
|
||||
}
|
||||
|
||||
impl ImplicitClone for VText {}
|
||||
|
||||
impl VText {
|
||||
/// Creates new virtual text node with a content.
|
||||
pub fn new(text: impl Into<AttrValue>) -> Self {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user