mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Implement an internal DomSlot for positioning instead of NodeRef (#3048)
use instead of NodeRef, decoupling the two fixes #3043 * implement internal DomSlot * move DomSlot into submodule of dom_bundle * hide behind feature csr * add test cases * write get in continuation style, this saves a clone * private DomSlot::get
This commit is contained in:
parent
30a05fcf07
commit
c5ffe601f2
@ -5,11 +5,10 @@ use std::rc::Rc;
|
||||
|
||||
use web_sys::Element;
|
||||
|
||||
use crate::dom_bundle::BSubtree;
|
||||
use crate::html::{BaseComponent, NodeRef, Scope, Scoped};
|
||||
use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot};
|
||||
use crate::html::{BaseComponent, Scope, Scoped};
|
||||
|
||||
/// An instance of an application.
|
||||
#[cfg(feature = "csr")]
|
||||
#[derive(Debug)]
|
||||
pub struct AppHandle<COMP: BaseComponent> {
|
||||
/// `Scope` holder
|
||||
@ -38,8 +37,8 @@ where
|
||||
app.scope.mount_in_place(
|
||||
hosting_root,
|
||||
host,
|
||||
NodeRef::default(),
|
||||
NodeRef::default(),
|
||||
DomSlot::at_end(),
|
||||
DynamicDomSlot::new_debug_trapped(),
|
||||
props,
|
||||
);
|
||||
|
||||
@ -58,7 +57,7 @@ where
|
||||
skip_all,
|
||||
)]
|
||||
pub fn update(&mut self, new_props: COMP::Properties) {
|
||||
self.scope.reuse(Rc::new(new_props), NodeRef::default())
|
||||
self.scope.reuse(Rc::new(new_props), DomSlot::at_end())
|
||||
}
|
||||
|
||||
/// Schedule the app for destruction
|
||||
@ -115,11 +114,11 @@ mod feat_hydration {
|
||||
hosting_root,
|
||||
host.clone(),
|
||||
&mut fragment,
|
||||
NodeRef::default(),
|
||||
DynamicDomSlot::new_debug_trapped(),
|
||||
Rc::clone(&props),
|
||||
);
|
||||
#[cfg(debug_assertions)] // Fix trapped next_sibling at the root
|
||||
app.scope.reuse(props, NodeRef::default());
|
||||
app.scope.reuse(props, DomSlot::at_end());
|
||||
|
||||
// We remove all remaining nodes, this mimics the clear_element behaviour in
|
||||
// mount_with_props.
|
||||
|
||||
@ -6,18 +6,17 @@ use std::fmt;
|
||||
|
||||
use web_sys::Element;
|
||||
|
||||
use super::{BNode, BSubtree, Reconcilable, ReconcileTarget};
|
||||
use super::{BNode, BSubtree, DomSlot, DynamicDomSlot, Reconcilable, ReconcileTarget};
|
||||
use crate::html::{AnyScope, Scoped};
|
||||
use crate::virtual_dom::{Key, VComp};
|
||||
use crate::NodeRef;
|
||||
|
||||
/// A virtual component. Compare with [VComp].
|
||||
pub(super) struct BComp {
|
||||
type_id: TypeId,
|
||||
scope: Box<dyn Scoped>,
|
||||
// A internal NodeRef passed around to track this components position. This
|
||||
// is "stable", i.e. does not change when reconciled.
|
||||
internal_ref: NodeRef,
|
||||
/// An internal [`DomSlot`] passed around to track this components position. This
|
||||
/// will dynamically adjust when a lifecycle changes the render state of this component.
|
||||
own_position: DynamicDomSlot,
|
||||
key: Option<Key>,
|
||||
}
|
||||
|
||||
@ -41,10 +40,10 @@ impl ReconcileTarget for BComp {
|
||||
self.scope.destroy_boxed(parent_to_detach);
|
||||
}
|
||||
|
||||
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
|
||||
self.scope.shift_node(next_parent.clone(), next_sibling);
|
||||
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
|
||||
self.scope.shift_node(next_parent.clone(), slot);
|
||||
|
||||
self.internal_ref.clone()
|
||||
self.own_position.to_position()
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,29 +55,29 @@ impl Reconcilable for VComp {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
slot: DomSlot,
|
||||
) -> (DomSlot, Self::Bundle) {
|
||||
let VComp {
|
||||
type_id,
|
||||
mountable,
|
||||
key,
|
||||
..
|
||||
} = self;
|
||||
let internal_ref = NodeRef::default();
|
||||
let internal_ref = DynamicDomSlot::new_debug_trapped();
|
||||
|
||||
let scope = mountable.mount(
|
||||
root,
|
||||
parent_scope,
|
||||
parent.to_owned(),
|
||||
slot,
|
||||
internal_ref.clone(),
|
||||
next_sibling,
|
||||
);
|
||||
|
||||
(
|
||||
internal_ref.clone(),
|
||||
internal_ref.to_position(),
|
||||
BComp {
|
||||
type_id,
|
||||
internal_ref,
|
||||
own_position: internal_ref,
|
||||
key,
|
||||
scope,
|
||||
},
|
||||
@ -90,17 +89,17 @@ impl Reconcilable for VComp {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
match bundle {
|
||||
// If the existing bundle is the same type, reuse it and update its properties
|
||||
BNode::Comp(ref mut bcomp)
|
||||
if self.type_id == bcomp.type_id && self.key == bcomp.key =>
|
||||
{
|
||||
self.reconcile(root, parent_scope, parent, next_sibling, bcomp)
|
||||
self.reconcile(root, parent_scope, parent, slot, bcomp)
|
||||
}
|
||||
_ => self.replace(root, parent_scope, parent, next_sibling, bundle),
|
||||
_ => self.replace(root, parent_scope, parent, slot, bundle),
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,14 +108,14 @@ impl Reconcilable for VComp {
|
||||
_root: &BSubtree,
|
||||
_parent_scope: &AnyScope,
|
||||
_parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bcomp: &mut Self::Bundle,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
let VComp { mountable, key, .. } = self;
|
||||
|
||||
bcomp.key = key;
|
||||
mountable.reuse(bcomp.scope.borrow(), next_sibling);
|
||||
bcomp.internal_ref.clone()
|
||||
mountable.reuse(bcomp.scope.borrow(), slot);
|
||||
bcomp.own_position.to_position()
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,14 +131,14 @@ mod feat_hydration {
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
fragment: &mut Fragment,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
) -> Self::Bundle {
|
||||
let VComp {
|
||||
type_id,
|
||||
mountable,
|
||||
key,
|
||||
..
|
||||
} = self;
|
||||
let internal_ref = NodeRef::default();
|
||||
let internal_ref = DynamicDomSlot::new_debug_trapped();
|
||||
|
||||
let scoped = mountable.hydrate(
|
||||
root.clone(),
|
||||
@ -149,15 +148,12 @@ mod feat_hydration {
|
||||
fragment,
|
||||
);
|
||||
|
||||
(
|
||||
internal_ref.clone(),
|
||||
BComp {
|
||||
type_id,
|
||||
scope: scoped,
|
||||
internal_ref,
|
||||
key,
|
||||
},
|
||||
)
|
||||
BComp {
|
||||
type_id,
|
||||
scope: scoped,
|
||||
own_position: internal_ref,
|
||||
key,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -172,7 +168,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::dom_bundle::Reconcilable;
|
||||
use crate::virtual_dom::{Key, VChild, VNode};
|
||||
use crate::{html, scheduler, Children, Component, Context, Html, NodeRef, Properties};
|
||||
use crate::{html, scheduler, Children, Component, Context, Html, Properties};
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
@ -208,12 +204,12 @@ mod tests {
|
||||
let (root, scope, parent) = setup_parent();
|
||||
|
||||
let comp = html! { <Comp></Comp> };
|
||||
let (_, mut bundle) = comp.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut bundle) = comp.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
scheduler::start_now();
|
||||
|
||||
for _ in 0..10000 {
|
||||
let node = html! { <Comp></Comp> };
|
||||
node.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut bundle);
|
||||
node.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut bundle);
|
||||
scheduler::start_now();
|
||||
}
|
||||
}
|
||||
@ -344,7 +340,7 @@ mod tests {
|
||||
// clear parent
|
||||
parent.set_inner_html("");
|
||||
|
||||
node.attach(root, scope, parent, NodeRef::default());
|
||||
node.attach(root, scope, parent, DomSlot::at_end());
|
||||
scheduler::start_now();
|
||||
parent.inner_html()
|
||||
}
|
||||
|
||||
@ -7,9 +7,9 @@ use std::ops::Deref;
|
||||
|
||||
use web_sys::Element;
|
||||
|
||||
use super::{test_log, BNode, BSubtree};
|
||||
use super::{test_log, BNode, BSubtree, DomSlot};
|
||||
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::{Key, VList, VNode, VText};
|
||||
|
||||
/// This struct represents a mounted [VList]
|
||||
@ -36,7 +36,7 @@ struct NodeWriter<'s> {
|
||||
root: &'s BSubtree,
|
||||
parent_scope: &'s AnyScope,
|
||||
parent: &'s Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
}
|
||||
|
||||
impl<'s> NodeWriter<'s> {
|
||||
@ -44,48 +44,33 @@ impl<'s> NodeWriter<'s> {
|
||||
fn add(self, node: VNode) -> (Self, BNode) {
|
||||
test_log!("adding: {:?}", node);
|
||||
test_log!(
|
||||
" parent={:?}, next_sibling={:?}",
|
||||
" parent={:?}, slot={:?}",
|
||||
self.parent.outer_html(),
|
||||
self.next_sibling
|
||||
self.slot
|
||||
);
|
||||
let (next, bundle) =
|
||||
node.attach(self.root, self.parent_scope, self.parent, self.next_sibling);
|
||||
test_log!(" next_position: {:?}", next);
|
||||
(
|
||||
Self {
|
||||
next_sibling: next,
|
||||
..self
|
||||
},
|
||||
bundle,
|
||||
)
|
||||
let (next, bundle) = node.attach(self.root, self.parent_scope, self.parent, self.slot);
|
||||
test_log!(" next_slot: {:?}", next);
|
||||
(Self { slot: next, ..self }, bundle)
|
||||
}
|
||||
|
||||
/// Shift a bundle into place without patching it
|
||||
fn shift(&self, bundle: &mut BNode) {
|
||||
bundle.shift(self.parent, self.next_sibling.clone());
|
||||
bundle.shift(self.parent, self.slot.clone());
|
||||
}
|
||||
|
||||
/// Patch a bundle with a new node
|
||||
fn patch(self, node: VNode, bundle: &mut BNode) -> Self {
|
||||
test_log!("patching: {:?} -> {:?}", bundle, node);
|
||||
test_log!(
|
||||
" parent={:?}, next_sibling={:?}",
|
||||
" parent={:?}, slot={:?}",
|
||||
self.parent.outer_html(),
|
||||
self.next_sibling
|
||||
self.slot
|
||||
);
|
||||
// Advance the next sibling reference (from right to left)
|
||||
let next = node.reconcile_node(
|
||||
self.root,
|
||||
self.parent_scope,
|
||||
self.parent,
|
||||
self.next_sibling,
|
||||
bundle,
|
||||
);
|
||||
let next =
|
||||
node.reconcile_node(self.root, self.parent_scope, self.parent, self.slot, bundle);
|
||||
test_log!(" next_position: {:?}", next);
|
||||
Self {
|
||||
next_sibling: next,
|
||||
..self
|
||||
}
|
||||
Self { slot: next, ..self }
|
||||
}
|
||||
}
|
||||
/// Helper struct implementing [Eq] and [Hash] by only looking at a node's key
|
||||
@ -148,15 +133,15 @@ impl BList {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
lefts: Vec<VNode>,
|
||||
rights: &mut Vec<BNode>,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
let mut writer = NodeWriter {
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
next_sibling,
|
||||
slot,
|
||||
};
|
||||
|
||||
// Remove extra nodes
|
||||
@ -178,7 +163,7 @@ impl BList {
|
||||
rights.push(el);
|
||||
writer = next_writer;
|
||||
}
|
||||
writer.next_sibling
|
||||
writer.slot
|
||||
}
|
||||
|
||||
/// Diff and patch fully keyed child lists.
|
||||
@ -189,10 +174,10 @@ impl BList {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
left_vdoms: Vec<VNode>,
|
||||
rev_bundles: &mut Vec<BNode>,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
macro_rules! key {
|
||||
($v:expr) => {
|
||||
$v.key().expect("unkeyed child in fully keyed list")
|
||||
@ -216,14 +201,7 @@ impl BList {
|
||||
// Corresponds to adding or removing items from the back of the list
|
||||
if matching_len_end == std::cmp::min(left_vdoms.len(), rev_bundles.len()) {
|
||||
// No key changes
|
||||
return Self::apply_unkeyed(
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
next_sibling,
|
||||
left_vdoms,
|
||||
rev_bundles,
|
||||
);
|
||||
return Self::apply_unkeyed(root, parent_scope, parent, slot, left_vdoms, rev_bundles);
|
||||
}
|
||||
|
||||
// We partially drain the new vnodes in several steps.
|
||||
@ -232,7 +210,7 @@ impl BList {
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
next_sibling,
|
||||
slot,
|
||||
};
|
||||
// Step 1. Diff matching children at the end
|
||||
let lefts_to = lefts.len() - matching_len_end;
|
||||
@ -369,7 +347,7 @@ impl BList {
|
||||
writer = writer.patch(l, r);
|
||||
}
|
||||
|
||||
writer.next_sibling
|
||||
writer.slot
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,14 +358,12 @@ impl ReconcileTarget for BList {
|
||||
}
|
||||
}
|
||||
|
||||
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
|
||||
let mut next_sibling = next_sibling;
|
||||
|
||||
fn shift(&self, next_parent: &Element, mut slot: DomSlot) -> DomSlot {
|
||||
for node in self.rev_children.iter() {
|
||||
next_sibling = node.shift(next_parent, next_sibling.clone());
|
||||
slot = node.shift(next_parent, slot);
|
||||
}
|
||||
|
||||
next_sibling
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
@ -399,10 +375,10 @@ impl Reconcilable for VList {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
slot: DomSlot,
|
||||
) -> (DomSlot, Self::Bundle) {
|
||||
let mut self_ = BList::new();
|
||||
let node_ref = self.reconcile(root, parent_scope, parent, next_sibling, &mut self_);
|
||||
let node_ref = self.reconcile(root, parent_scope, parent, slot, &mut self_);
|
||||
(node_ref, self_)
|
||||
}
|
||||
|
||||
@ -411,13 +387,13 @@ impl Reconcilable for VList {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
// 'Forcefully' pretend the existing node is a list. Creates a
|
||||
// singleton list if it isn't already.
|
||||
let blist = bundle.make_list();
|
||||
self.reconcile(root, parent_scope, parent, next_sibling, blist)
|
||||
self.reconcile(root, parent_scope, parent, slot, blist)
|
||||
}
|
||||
|
||||
fn reconcile(
|
||||
@ -425,9 +401,9 @@ impl Reconcilable for VList {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
blist: &mut BList,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
// Here, we will try to diff the previous list elements with the new
|
||||
// ones we want to insert. For that, we will use two lists:
|
||||
// - lefts: new elements to render in the DOM
|
||||
@ -454,9 +430,9 @@ impl Reconcilable for VList {
|
||||
rights.reserve_exact(additional);
|
||||
}
|
||||
let first = if fully_keyed && blist.fully_keyed {
|
||||
BList::apply_keyed(root, parent_scope, parent, next_sibling, lefts, rights)
|
||||
BList::apply_keyed(root, parent_scope, parent, slot, lefts, rights)
|
||||
} else {
|
||||
BList::apply_unkeyed(root, parent_scope, parent, next_sibling, lefts, rights)
|
||||
BList::apply_unkeyed(root, parent_scope, parent, slot, lefts, rights)
|
||||
};
|
||||
blist.fully_keyed = fully_keyed;
|
||||
blist.key = self.key;
|
||||
@ -477,32 +453,24 @@ mod feat_hydration {
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
fragment: &mut Fragment,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
let node_ref = NodeRef::default();
|
||||
) -> Self::Bundle {
|
||||
let fully_keyed = self.fully_keyed();
|
||||
let vchildren = self.children;
|
||||
let mut children = Vec::with_capacity(vchildren.len());
|
||||
|
||||
for (index, child) in vchildren.into_iter().enumerate() {
|
||||
let (child_node_ref, child) = child.hydrate(root, parent_scope, parent, fragment);
|
||||
|
||||
if index == 0 {
|
||||
node_ref.link(child_node_ref);
|
||||
}
|
||||
for child in vchildren.into_iter() {
|
||||
let child = child.hydrate(root, parent_scope, parent, fragment);
|
||||
|
||||
children.push(child);
|
||||
}
|
||||
|
||||
children.reverse();
|
||||
|
||||
(
|
||||
node_ref,
|
||||
BList {
|
||||
rev_children: children,
|
||||
fully_keyed,
|
||||
key: self.key,
|
||||
},
|
||||
)
|
||||
BList {
|
||||
rev_children: children,
|
||||
fully_keyed,
|
||||
key: self.key,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,9 @@ use std::fmt;
|
||||
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
use super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText};
|
||||
use super::{BComp, BList, BPortal, BRaw, BSubtree, BSuspense, BTag, BText, DomSlot};
|
||||
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::{Key, VNode};
|
||||
|
||||
/// The bundle implementation to [VNode].
|
||||
@ -65,22 +65,20 @@ impl ReconcileTarget for BNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
|
||||
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
|
||||
match self {
|
||||
Self::Tag(ref vtag) => vtag.shift(next_parent, next_sibling),
|
||||
Self::Text(ref btext) => btext.shift(next_parent, next_sibling),
|
||||
Self::Comp(ref bsusp) => bsusp.shift(next_parent, next_sibling),
|
||||
Self::List(ref vlist) => vlist.shift(next_parent, next_sibling),
|
||||
Self::Tag(ref vtag) => vtag.shift(next_parent, slot),
|
||||
Self::Text(ref btext) => btext.shift(next_parent, slot),
|
||||
Self::Comp(ref bsusp) => bsusp.shift(next_parent, slot),
|
||||
Self::List(ref vlist) => vlist.shift(next_parent, slot),
|
||||
Self::Ref(ref node) => {
|
||||
next_parent
|
||||
.insert_before(node, next_sibling.get().as_ref())
|
||||
.unwrap();
|
||||
slot.insert(next_parent, node);
|
||||
|
||||
NodeRef::new(node.clone())
|
||||
DomSlot::at(node.clone())
|
||||
}
|
||||
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),
|
||||
Self::Portal(ref vportal) => vportal.shift(next_parent, slot),
|
||||
Self::Suspense(ref vsuspense) => vsuspense.shift(next_parent, slot),
|
||||
Self::Raw(ref braw) => braw.shift(next_parent, slot),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,40 +91,39 @@ impl Reconcilable for VNode {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
slot: DomSlot,
|
||||
) -> (DomSlot, Self::Bundle) {
|
||||
match self {
|
||||
VNode::VTag(vtag) => {
|
||||
let (node_ref, tag) = vtag.attach(root, parent_scope, parent, next_sibling);
|
||||
let (node_ref, tag) = vtag.attach(root, parent_scope, parent, slot);
|
||||
(node_ref, tag.into())
|
||||
}
|
||||
VNode::VText(vtext) => {
|
||||
let (node_ref, text) = vtext.attach(root, parent_scope, parent, next_sibling);
|
||||
let (node_ref, text) = vtext.attach(root, parent_scope, parent, slot);
|
||||
(node_ref, text.into())
|
||||
}
|
||||
VNode::VComp(vcomp) => {
|
||||
let (node_ref, comp) = vcomp.attach(root, parent_scope, parent, next_sibling);
|
||||
let (node_ref, comp) = vcomp.attach(root, parent_scope, parent, slot);
|
||||
(node_ref, comp.into())
|
||||
}
|
||||
VNode::VList(vlist) => {
|
||||
let (node_ref, list) = vlist.attach(root, parent_scope, parent, next_sibling);
|
||||
let (node_ref, list) = vlist.attach(root, parent_scope, parent, slot);
|
||||
(node_ref, list.into())
|
||||
}
|
||||
VNode::VRef(node) => {
|
||||
super::insert_node(&node, parent, next_sibling.get().as_ref());
|
||||
(NodeRef::new(node.clone()), BNode::Ref(node))
|
||||
slot.insert(parent, &node);
|
||||
(DomSlot::at(node.clone()), BNode::Ref(node))
|
||||
}
|
||||
VNode::VPortal(vportal) => {
|
||||
let (node_ref, portal) = vportal.attach(root, parent_scope, parent, next_sibling);
|
||||
let (node_ref, portal) = vportal.attach(root, parent_scope, parent, slot);
|
||||
(node_ref, portal.into())
|
||||
}
|
||||
VNode::VSuspense(vsuspsense) => {
|
||||
let (node_ref, suspsense) =
|
||||
vsuspsense.attach(root, parent_scope, parent, next_sibling);
|
||||
let (node_ref, suspsense) = vsuspsense.attach(root, parent_scope, parent, slot);
|
||||
(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, slot);
|
||||
(node_ref, raw.into())
|
||||
}
|
||||
}
|
||||
@ -137,10 +134,10 @@ impl Reconcilable for VNode {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef {
|
||||
self.reconcile(root, parent_scope, parent, next_sibling, bundle)
|
||||
) -> DomSlot {
|
||||
self.reconcile(root, parent_scope, parent, slot, bundle)
|
||||
}
|
||||
|
||||
fn reconcile(
|
||||
@ -148,46 +145,25 @@ impl Reconcilable for VNode {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
match self {
|
||||
VNode::VTag(vtag) => {
|
||||
vtag.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
|
||||
}
|
||||
VNode::VText(vtext) => {
|
||||
vtext.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
|
||||
}
|
||||
VNode::VComp(vcomp) => {
|
||||
vcomp.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
|
||||
}
|
||||
VNode::VList(vlist) => {
|
||||
vlist.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
|
||||
}
|
||||
VNode::VRef(node) => {
|
||||
let _existing = match bundle {
|
||||
BNode::Ref(ref n) if &node == n => n,
|
||||
_ => {
|
||||
return VNode::VRef(node).replace(
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
next_sibling,
|
||||
bundle,
|
||||
);
|
||||
}
|
||||
};
|
||||
NodeRef::new(node)
|
||||
}
|
||||
VNode::VTag(vtag) => 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::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, next_sibling, bundle)
|
||||
vportal.reconcile_node(root, parent_scope, parent, slot, bundle)
|
||||
}
|
||||
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)
|
||||
vsuspsense.reconcile_node(root, parent_scope, parent, slot, bundle)
|
||||
}
|
||||
VNode::VRaw(vraw) => vraw.reconcile_node(root, parent_scope, parent, slot, bundle),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -268,24 +244,12 @@ mod feat_hydration {
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
fragment: &mut Fragment,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
) -> Self::Bundle {
|
||||
match self {
|
||||
VNode::VTag(vtag) => {
|
||||
let (node_ref, tag) = vtag.hydrate(root, parent_scope, parent, fragment);
|
||||
(node_ref, tag.into())
|
||||
}
|
||||
VNode::VText(vtext) => {
|
||||
let (node_ref, text) = vtext.hydrate(root, parent_scope, parent, fragment);
|
||||
(node_ref, text.into())
|
||||
}
|
||||
VNode::VComp(vcomp) => {
|
||||
let (node_ref, comp) = vcomp.hydrate(root, parent_scope, parent, fragment);
|
||||
(node_ref, comp.into())
|
||||
}
|
||||
VNode::VList(vlist) => {
|
||||
let (node_ref, list) = vlist.hydrate(root, parent_scope, parent, fragment);
|
||||
(node_ref, list.into())
|
||||
}
|
||||
VNode::VTag(vtag) => 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(),
|
||||
// You cannot hydrate a VRef.
|
||||
VNode::VRef(_) => {
|
||||
panic!(
|
||||
@ -300,11 +264,9 @@ mod feat_hydration {
|
||||
use_effect."
|
||||
)
|
||||
}
|
||||
VNode::VSuspense(vsuspense) => {
|
||||
let (node_ref, suspense) =
|
||||
vsuspense.hydrate(root, parent_scope, parent, fragment);
|
||||
(node_ref, suspense.into())
|
||||
}
|
||||
VNode::VSuspense(vsuspense) => vsuspense
|
||||
.hydrate(root, parent_scope, parent, fragment)
|
||||
.into(),
|
||||
VNode::VRaw(_) => {
|
||||
panic!("VRaw is not hydratable (raw HTML string cannot be hydrated)")
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
//! This module contains the bundle implementation of a portal [BPortal].
|
||||
|
||||
use web_sys::Element;
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
use super::{test_log, BNode, BSubtree};
|
||||
use super::{test_log, BNode, BSubtree, DomSlot};
|
||||
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::{Key, VPortal};
|
||||
|
||||
/// The bundle implementation to [VPortal].
|
||||
@ -15,7 +15,7 @@ pub struct BPortal {
|
||||
/// The element under which the content is inserted.
|
||||
host: Element,
|
||||
/// The next sibling after the inserted content
|
||||
inner_sibling: NodeRef,
|
||||
inner_sibling: Option<Node>,
|
||||
/// The inserted node
|
||||
node: Box<BNode>,
|
||||
}
|
||||
@ -26,10 +26,9 @@ impl ReconcileTarget for BPortal {
|
||||
self.node.detach(&self.inner_root, &self.host, false);
|
||||
}
|
||||
|
||||
fn shift(&self, _next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
|
||||
// portals have nothing in it's original place of DOM, we also do nothing.
|
||||
|
||||
next_sibling
|
||||
fn shift(&self, _next_parent: &Element, slot: DomSlot) -> DomSlot {
|
||||
// portals have nothing in its original place of DOM, we also do nothing.
|
||||
slot
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,22 +40,18 @@ impl Reconcilable for VPortal {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
host_next_sibling: NodeRef,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
host_slot: DomSlot,
|
||||
) -> (DomSlot, Self::Bundle) {
|
||||
let Self {
|
||||
host,
|
||||
inner_sibling,
|
||||
node,
|
||||
} = self;
|
||||
let inner_sibling = {
|
||||
let sib_ref = NodeRef::default();
|
||||
sib_ref.set(inner_sibling);
|
||||
sib_ref
|
||||
};
|
||||
let inner_slot = DomSlot::create(inner_sibling.clone());
|
||||
let inner_root = root.create_subroot(parent.clone(), &host);
|
||||
let (_, inner) = node.attach(&inner_root, parent_scope, &host, inner_sibling.clone());
|
||||
let (_, inner) = node.attach(&inner_root, parent_scope, &host, inner_slot);
|
||||
(
|
||||
host_next_sibling,
|
||||
host_slot,
|
||||
BPortal {
|
||||
inner_root,
|
||||
host,
|
||||
@ -71,14 +66,12 @@ impl Reconcilable for VPortal {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
match bundle {
|
||||
BNode::Portal(portal) => {
|
||||
self.reconcile(root, parent_scope, parent, next_sibling, portal)
|
||||
}
|
||||
_ => self.replace(root, parent_scope, parent, next_sibling, bundle),
|
||||
BNode::Portal(portal) => self.reconcile(root, parent_scope, parent, slot, portal),
|
||||
_ => self.replace(root, parent_scope, parent, slot, bundle),
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,9 +80,9 @@ impl Reconcilable for VPortal {
|
||||
_root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
_parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
host_slot: DomSlot,
|
||||
portal: &mut Self::Bundle,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
let Self {
|
||||
host,
|
||||
inner_sibling,
|
||||
@ -98,23 +91,23 @@ impl Reconcilable for VPortal {
|
||||
|
||||
let old_host = std::mem::replace(&mut portal.host, host);
|
||||
|
||||
let should_shift = old_host != portal.host || portal.inner_sibling.get() != inner_sibling;
|
||||
portal.inner_sibling.set(inner_sibling);
|
||||
let should_shift = old_host != portal.host || portal.inner_sibling != inner_sibling;
|
||||
portal.inner_sibling = inner_sibling;
|
||||
let inner_slot = DomSlot::create(portal.inner_sibling.clone());
|
||||
|
||||
if should_shift {
|
||||
// Remount the inner node somewhere else instead of diffing
|
||||
// Move the node, but keep the state
|
||||
let inner_sibling = portal.inner_sibling.clone();
|
||||
portal.node.shift(&portal.host, inner_sibling);
|
||||
portal.node.shift(&portal.host, inner_slot.clone());
|
||||
}
|
||||
node.reconcile_node(
|
||||
&portal.inner_root,
|
||||
parent_scope,
|
||||
&portal.host,
|
||||
portal.inner_sibling.clone(),
|
||||
inner_slot,
|
||||
&mut portal.node,
|
||||
);
|
||||
next_sibling
|
||||
host_slot
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,6 +129,7 @@ mod layout_tests {
|
||||
use yew::virtual_dom::VPortal;
|
||||
|
||||
use super::*;
|
||||
use crate::html::NodeRef;
|
||||
use crate::tests::layout_tests::{diff_layouts, TestLayout};
|
||||
use crate::virtual_dom::VNode;
|
||||
use crate::{create_portal, html};
|
||||
@ -252,13 +246,13 @@ mod layout_tests {
|
||||
);
|
||||
let (_, mut bundle) = portal
|
||||
.clone()
|
||||
.attach(&root, &scope, &parent, NodeRef::default());
|
||||
.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
|
||||
// Focus the input, then reconcile again
|
||||
let input_el = input_ref.cast::<HtmlInputElement>().unwrap();
|
||||
input_el.focus().unwrap();
|
||||
|
||||
let _ = portal.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut bundle);
|
||||
let _ = portal.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut bundle);
|
||||
|
||||
let new_input_el = input_ref.cast::<HtmlInputElement>().unwrap();
|
||||
assert_eq!(input_el, new_input_el);
|
||||
|
||||
@ -1,26 +1,23 @@
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::Element;
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
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 super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::VRaw;
|
||||
use crate::{AttrValue, NodeRef};
|
||||
use crate::AttrValue;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BRaw {
|
||||
reference: NodeRef,
|
||||
reference: Option<Node>,
|
||||
children_count: usize,
|
||||
html: AttrValue,
|
||||
}
|
||||
|
||||
impl BRaw {
|
||||
fn create_elements(html: &str) -> Vec<Element> {
|
||||
fn create_elements(html: &str) -> Vec<Node> {
|
||||
let div = gloo::utils::document().create_element("div").unwrap();
|
||||
div.set_inner_html(html);
|
||||
let children = div.children();
|
||||
let children = div.child_nodes();
|
||||
let children = js_sys::Array::from(&children);
|
||||
let children = children.to_vec();
|
||||
children
|
||||
@ -30,7 +27,7 @@ impl BRaw {
|
||||
}
|
||||
|
||||
fn detach_bundle(&self, parent: &Element) {
|
||||
let mut next_node = self.reference.get();
|
||||
let mut next_node = self.reference.clone();
|
||||
for _ in 0..self.children_count {
|
||||
if let Some(node) = next_node {
|
||||
next_node = node.next_sibling();
|
||||
@ -38,6 +35,13 @@ impl BRaw {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn position(&self, next_slot: DomSlot) -> DomSlot {
|
||||
self.reference
|
||||
.as_ref()
|
||||
.map(|n| DomSlot::at(n.clone()))
|
||||
.unwrap_or(next_slot)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReconcileTarget for BRaw {
|
||||
@ -45,29 +49,15 @@ impl ReconcileTarget for BRaw {
|
||||
self.detach_bundle(parent);
|
||||
}
|
||||
|
||||
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
|
||||
let mut next_node = match self.reference.get() {
|
||||
Some(n) => n,
|
||||
None => return NodeRef::default(),
|
||||
};
|
||||
let insert = |n| {
|
||||
next_parent
|
||||
.insert_before(&n, next_sibling.get().as_ref())
|
||||
.unwrap()
|
||||
};
|
||||
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
|
||||
let mut next_node = self.reference.clone();
|
||||
for _ in 0..self.children_count {
|
||||
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);
|
||||
if let Some(node) = next_node {
|
||||
next_node = node.next_sibling();
|
||||
slot.insert(next_parent, &node);
|
||||
}
|
||||
}
|
||||
NodeRef::new(next_node)
|
||||
self.position(slot)
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,37 +69,24 @@ impl Reconcilable for VRaw {
|
||||
_root: &BSubtree,
|
||||
_parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
slot: DomSlot,
|
||||
) -> (DomSlot, Self::Bundle) {
|
||||
let elements = BRaw::create_elements(&self.html);
|
||||
if elements.is_empty() {
|
||||
return (
|
||||
next_sibling.clone(),
|
||||
BRaw {
|
||||
reference: next_sibling,
|
||||
children_count: 0,
|
||||
html: self.html,
|
||||
},
|
||||
);
|
||||
}
|
||||
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());
|
||||
node_ref.set(Some(first.into()));
|
||||
for child in iter {
|
||||
insert_node(&child, parent, next_sibling.get().as_ref());
|
||||
let reference = iter.next();
|
||||
if let Some(ref first) = reference {
|
||||
slot.insert(parent, first);
|
||||
for ref child in iter {
|
||||
slot.insert(parent, child);
|
||||
}
|
||||
}
|
||||
(
|
||||
node_ref.clone(),
|
||||
BRaw {
|
||||
reference: node_ref,
|
||||
children_count: count,
|
||||
html: self.html,
|
||||
},
|
||||
)
|
||||
let this = BRaw {
|
||||
reference,
|
||||
children_count: count,
|
||||
html: self.html,
|
||||
};
|
||||
(this.position(slot), this)
|
||||
}
|
||||
|
||||
fn reconcile_node(
|
||||
@ -117,13 +94,13 @@ impl Reconcilable for VRaw {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
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),
|
||||
BNode::Raw(raw) if raw.html == self.html => raw.position(slot),
|
||||
BNode::Raw(raw) => self.reconcile(root, parent_scope, parent, slot, raw),
|
||||
_ => self.replace(root, parent_scope, parent, slot, bundle),
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,18 +109,18 @@ impl Reconcilable for VRaw {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut Self::Bundle,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
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);
|
||||
let (node_ref, braw) = self.attach(root, parent_scope, parent, slot);
|
||||
*bundle = braw;
|
||||
node_ref
|
||||
} else {
|
||||
bundle.reference.clone()
|
||||
bundle.position(slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,7 +143,7 @@ mod tests {
|
||||
|
||||
const HTML: &str = "<span>text</span>";
|
||||
let elem = VNode::from_html_unchecked(HTML.into());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_braw(&mut elem);
|
||||
assert_eq!(parent.inner_html(), HTML)
|
||||
}
|
||||
@ -177,7 +154,7 @@ mod tests {
|
||||
|
||||
const HTML: &str = "";
|
||||
let elem = VNode::from_html_unchecked(HTML.into());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_braw(&mut elem);
|
||||
assert_eq!(parent.inner_html(), HTML)
|
||||
}
|
||||
@ -189,7 +166,7 @@ mod tests {
|
||||
const HTML: &str =
|
||||
r#"<p>one <a href="https://yew.rs">link</a> more paragraph</p><div>here</div>"#;
|
||||
let elem = VNode::from_html_unchecked(HTML.into());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_braw(&mut elem);
|
||||
assert_eq!(parent.inner_html(), HTML)
|
||||
}
|
||||
@ -199,7 +176,7 @@ mod tests {
|
||||
|
||||
const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
|
||||
let elem = VNode::from_html_unchecked(HTML.into());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_braw(&mut elem);
|
||||
assert_eq!(parent.inner_html(), HTML)
|
||||
}
|
||||
@ -210,7 +187,7 @@ mod tests {
|
||||
|
||||
const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
|
||||
let elem = VNode::from_html_unchecked(HTML.into());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_braw(&mut elem);
|
||||
assert_eq!(parent.inner_html(), HTML);
|
||||
elem.detach(&root, &parent, false);
|
||||
@ -223,7 +200,7 @@ mod tests {
|
||||
|
||||
const HTML: &str = r#"<p>paragraph</p>"#;
|
||||
let elem = VNode::from_html_unchecked(HTML.into());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_braw(&mut elem);
|
||||
assert_eq!(parent.inner_html(), HTML);
|
||||
elem.detach(&root, &parent, false);
|
||||
@ -236,7 +213,7 @@ mod tests {
|
||||
|
||||
const HTML: &str = "";
|
||||
let elem = VNode::from_html_unchecked(HTML.into());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_braw(&mut elem);
|
||||
assert_eq!(parent.inner_html(), HTML);
|
||||
elem.detach(&root, &parent, false);
|
||||
@ -332,14 +309,14 @@ mod tests {
|
||||
const HTML: &str = r#"<p>paragraph</p>"#;
|
||||
|
||||
let elem = VNode::from_html_unchecked(HTML.into());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
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());
|
||||
elem.shift(&new_parent, DomSlot::at_end());
|
||||
|
||||
assert_eq!(new_parent.inner_html(), HTML);
|
||||
assert_eq!(parent.inner_html(), "");
|
||||
@ -360,7 +337,7 @@ mod tests {
|
||||
|
||||
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());
|
||||
let new_sibling_ref = DomSlot::at(new_sibling.into());
|
||||
|
||||
elem.shift(&new_parent, new_sibling_ref);
|
||||
|
||||
@ -378,14 +355,14 @@ mod tests {
|
||||
const HTML: &str = r#"<p>paragraph</p><a href="https://yew.rs">link</a>"#;
|
||||
|
||||
let elem = VNode::from_html_unchecked(HTML.into());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
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());
|
||||
elem.shift(&new_parent, DomSlot::at_end());
|
||||
|
||||
assert_eq!(parent.inner_html(), "");
|
||||
assert_eq!(new_parent.inner_html(), HTML);
|
||||
|
||||
@ -5,10 +5,9 @@ use web_sys::Element;
|
||||
|
||||
#[cfg(feature = "hydration")]
|
||||
use super::Fragment;
|
||||
use super::{BNode, BSubtree, Reconcilable, ReconcileTarget};
|
||||
use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::{Key, VSuspense};
|
||||
use crate::NodeRef;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Fallback {
|
||||
@ -60,12 +59,12 @@ impl ReconcileTarget for BSuspense {
|
||||
}
|
||||
}
|
||||
|
||||
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
|
||||
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
|
||||
match self.fallback.as_ref() {
|
||||
Some(Fallback::Bundle(bundle)) => bundle.shift(next_parent, next_sibling),
|
||||
Some(Fallback::Bundle(bundle)) => bundle.shift(next_parent, slot),
|
||||
#[cfg(feature = "hydration")]
|
||||
Some(Fallback::Fragment(fragment)) => fragment.shift(next_parent, next_sibling),
|
||||
None => self.children_bundle.shift(next_parent, next_sibling),
|
||||
Some(Fallback::Fragment(fragment)) => fragment.shift(next_parent, slot),
|
||||
None => self.children_bundle.shift(next_parent, slot),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,8 +77,8 @@ impl Reconcilable for VSuspense {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
slot: DomSlot,
|
||||
) -> (DomSlot, Self::Bundle) {
|
||||
let VSuspense {
|
||||
children,
|
||||
fallback,
|
||||
@ -94,9 +93,8 @@ impl Reconcilable for VSuspense {
|
||||
// tree while rendering fallback UI into the original place where children resides in.
|
||||
if suspended {
|
||||
let (_child_ref, children_bundle) =
|
||||
children.attach(root, parent_scope, &detached_parent, NodeRef::default());
|
||||
let (fallback_ref, fallback) =
|
||||
fallback.attach(root, parent_scope, parent, next_sibling);
|
||||
children.attach(root, parent_scope, &detached_parent, DomSlot::at_end());
|
||||
let (fallback_ref, fallback) = fallback.attach(root, parent_scope, parent, slot);
|
||||
(
|
||||
fallback_ref,
|
||||
BSuspense {
|
||||
@ -107,8 +105,7 @@ impl Reconcilable for VSuspense {
|
||||
},
|
||||
)
|
||||
} else {
|
||||
let (child_ref, children_bundle) =
|
||||
children.attach(root, parent_scope, parent, next_sibling);
|
||||
let (child_ref, children_bundle) = children.attach(root, parent_scope, parent, slot);
|
||||
(
|
||||
child_ref,
|
||||
BSuspense {
|
||||
@ -126,15 +123,15 @@ impl Reconcilable for VSuspense {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
match bundle {
|
||||
// We only preserve the child state if they are the same suspense.
|
||||
BNode::Suspense(m) if m.key == self.key => {
|
||||
self.reconcile(root, parent_scope, parent, next_sibling, m)
|
||||
self.reconcile(root, parent_scope, parent, slot, m)
|
||||
}
|
||||
_ => self.replace(root, parent_scope, parent, next_sibling, bundle),
|
||||
_ => self.replace(root, parent_scope, parent, slot, bundle),
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,9 +140,9 @@ impl Reconcilable for VSuspense {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
suspense: &mut Self::Bundle,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
let VSuspense {
|
||||
children,
|
||||
fallback: vfallback,
|
||||
@ -165,41 +162,40 @@ impl Reconcilable for VSuspense {
|
||||
root,
|
||||
parent_scope,
|
||||
&suspense.detached_parent,
|
||||
NodeRef::default(),
|
||||
DomSlot::at_end(),
|
||||
children_bundle,
|
||||
);
|
||||
|
||||
match fallback {
|
||||
Fallback::Bundle(bundle) => {
|
||||
vfallback.reconcile_node(root, parent_scope, parent, next_sibling, bundle)
|
||||
vfallback.reconcile_node(root, parent_scope, parent, slot, bundle)
|
||||
}
|
||||
#[cfg(feature = "hydration")]
|
||||
Fallback::Fragment(fragment) => match fragment.front().cloned() {
|
||||
Some(m) => NodeRef::new(m),
|
||||
None => next_sibling,
|
||||
Some(m) => DomSlot::at(m),
|
||||
None => slot,
|
||||
},
|
||||
}
|
||||
}
|
||||
// Not suspended, just reconcile the children into the DOM
|
||||
(false, None) => {
|
||||
children.reconcile_node(root, parent_scope, parent, next_sibling, children_bundle)
|
||||
children.reconcile_node(root, parent_scope, parent, slot, children_bundle)
|
||||
}
|
||||
// Freshly suspended. Shift children into the detached parent, then add fallback to the
|
||||
// DOM
|
||||
(true, None) => {
|
||||
children_bundle.shift(&suspense.detached_parent, NodeRef::default());
|
||||
children_bundle.shift(&suspense.detached_parent, DomSlot::at_end());
|
||||
|
||||
children.reconcile_node(
|
||||
root,
|
||||
parent_scope,
|
||||
&suspense.detached_parent,
|
||||
NodeRef::default(),
|
||||
DomSlot::at_end(),
|
||||
children_bundle,
|
||||
);
|
||||
// first render of fallback
|
||||
|
||||
let (fallback_ref, fallback) =
|
||||
vfallback.attach(root, parent_scope, parent, next_sibling);
|
||||
let (fallback_ref, fallback) = vfallback.attach(root, parent_scope, parent, slot);
|
||||
suspense.fallback = Some(Fallback::Bundle(fallback));
|
||||
fallback_ref
|
||||
}
|
||||
@ -218,8 +214,8 @@ impl Reconcilable for VSuspense {
|
||||
}
|
||||
};
|
||||
|
||||
children_bundle.shift(parent, next_sibling.clone());
|
||||
children.reconcile_node(root, parent_scope, parent, next_sibling, children_bundle)
|
||||
children_bundle.shift(parent, slot.clone());
|
||||
children.reconcile_node(root, parent_scope, parent, slot, children_bundle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -238,7 +234,7 @@ mod feat_hydration {
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
fragment: &mut Fragment,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
) -> Self::Bundle {
|
||||
let detached_parent = document()
|
||||
.create_element("div")
|
||||
.expect("failed to create detached element");
|
||||
@ -254,33 +250,24 @@ mod feat_hydration {
|
||||
|
||||
// Even if initially suspended, these children correspond to the first non-suspended
|
||||
// content Refer to VSuspense::render_to_string
|
||||
let (_, children_bundle) =
|
||||
let children_bundle =
|
||||
self.children
|
||||
.hydrate(root, parent_scope, &detached_parent, &mut nodes);
|
||||
|
||||
// We trim all leading text nodes before checking as it's likely these are whitespaces.
|
||||
nodes.trim_start_text_nodes(&detached_parent);
|
||||
nodes.trim_start_text_nodes();
|
||||
|
||||
assert!(nodes.is_empty(), "expected end of suspense, found node.");
|
||||
|
||||
let node_ref = fallback_fragment
|
||||
.front()
|
||||
.cloned()
|
||||
.map(NodeRef::new)
|
||||
.unwrap_or_default();
|
||||
BSuspense {
|
||||
children_bundle,
|
||||
detached_parent,
|
||||
key: self.key,
|
||||
|
||||
(
|
||||
node_ref,
|
||||
BSuspense {
|
||||
children_bundle,
|
||||
detached_parent,
|
||||
key: self.key,
|
||||
|
||||
// We start hydration with the BSuspense being suspended.
|
||||
// A subsequent render will resume the BSuspense if not needed to be suspended.
|
||||
fallback: Some(Fallback::Fragment(fallback_fragment)),
|
||||
},
|
||||
)
|
||||
// We start hydration with the BSuspense being suspended.
|
||||
// A subsequent render will resume the BSuspense if not needed to be suspended.
|
||||
fallback: Some(Fallback::Fragment(fallback_fragment)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ pub use listeners::Registry;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Element, HtmlTextAreaElement as TextAreaElement};
|
||||
|
||||
use super::{insert_node, BList, BNode, BSubtree, Reconcilable, ReconcileTarget};
|
||||
use super::{BList, BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::vtag::{InputFields, VTagInner, Value, SVG_NAMESPACE};
|
||||
use crate::virtual_dom::{Attributes, Key, VTag};
|
||||
@ -93,12 +93,10 @@ impl ReconcileTarget for BTag {
|
||||
}
|
||||
}
|
||||
|
||||
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
|
||||
next_parent
|
||||
.insert_before(&self.reference, next_sibling.get().as_ref())
|
||||
.unwrap();
|
||||
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
|
||||
slot.insert(next_parent, &self.reference);
|
||||
|
||||
self.node_ref.clone()
|
||||
DomSlot::at(self.reference.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,8 +108,8 @@ impl Reconcilable for VTag {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
slot: DomSlot,
|
||||
) -> (DomSlot, Self::Bundle) {
|
||||
let el = self.create_element(parent);
|
||||
let Self {
|
||||
listeners,
|
||||
@ -120,7 +118,7 @@ impl Reconcilable for VTag {
|
||||
key,
|
||||
..
|
||||
} = self;
|
||||
insert_node(&el, parent, next_sibling.get().as_ref());
|
||||
slot.insert(parent, &el);
|
||||
|
||||
let attributes = attributes.apply(root, &el);
|
||||
let listeners = listeners.apply(root, &el);
|
||||
@ -135,14 +133,13 @@ impl Reconcilable for VTag {
|
||||
BTagInner::Textarea { value }
|
||||
}
|
||||
VTagInner::Other { children, tag } => {
|
||||
let (_, child_bundle) =
|
||||
children.attach(root, parent_scope, &el, NodeRef::default());
|
||||
let (_, child_bundle) = children.attach(root, parent_scope, &el, DomSlot::at_end());
|
||||
BTagInner::Other { child_bundle, tag }
|
||||
}
|
||||
};
|
||||
node_ref.set(Some(el.clone().into()));
|
||||
(
|
||||
node_ref.clone(),
|
||||
DomSlot::at(el.clone().into()),
|
||||
BTag {
|
||||
inner,
|
||||
listeners,
|
||||
@ -159,9 +156,9 @@ impl Reconcilable for VTag {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
// This kind of branching patching routine reduces branch predictor misses and the need to
|
||||
// unpack the enums (including `Option`s) all the time, resulting in a more streamlined
|
||||
// patching flow
|
||||
@ -179,18 +176,12 @@ impl Reconcilable for VTag {
|
||||
}
|
||||
_ => false,
|
||||
} {
|
||||
return self.reconcile(
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
next_sibling,
|
||||
ex.deref_mut(),
|
||||
);
|
||||
return self.reconcile(root, parent_scope, parent, slot, ex.deref_mut());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
self.replace(root, parent_scope, parent, next_sibling, bundle)
|
||||
self.replace(root, parent_scope, parent, slot, bundle)
|
||||
}
|
||||
|
||||
fn reconcile(
|
||||
@ -198,9 +189,9 @@ impl Reconcilable for VTag {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
_parent: &Element,
|
||||
_next_sibling: NodeRef,
|
||||
_slot: DomSlot,
|
||||
tag: &mut Self::Bundle,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
let el = &tag.reference;
|
||||
self.attributes.apply_diff(root, el, &mut tag.attributes);
|
||||
self.listeners.apply_diff(root, el, &mut tag.listeners);
|
||||
@ -218,7 +209,7 @@ impl Reconcilable for VTag {
|
||||
child_bundle: old, ..
|
||||
},
|
||||
) => {
|
||||
new.reconcile(root, parent_scope, el, NodeRef::default(), old);
|
||||
new.reconcile(root, parent_scope, el, DomSlot::at_end(), old);
|
||||
}
|
||||
// Can not happen, because we checked for tag equability above
|
||||
_ => unsafe { unreachable_unchecked() },
|
||||
@ -234,7 +225,7 @@ impl Reconcilable for VTag {
|
||||
tag.node_ref.set(Some(el.clone().into()));
|
||||
}
|
||||
|
||||
tag.node_ref.clone()
|
||||
DomSlot::at(el.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,9 +293,9 @@ mod feat_hydration {
|
||||
self,
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
_parent: &Element,
|
||||
fragment: &mut Fragment,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
) -> Self::Bundle {
|
||||
let tag_name = self.tag().to_owned();
|
||||
|
||||
let Self {
|
||||
@ -316,7 +307,7 @@ mod feat_hydration {
|
||||
} = self;
|
||||
|
||||
// We trim all text nodes as it's likely these are whitespaces.
|
||||
fragment.trim_start_text_nodes(parent);
|
||||
fragment.trim_start_text_nodes();
|
||||
|
||||
let node = fragment
|
||||
.pop_front()
|
||||
@ -355,9 +346,9 @@ mod feat_hydration {
|
||||
}
|
||||
VTagInner::Other { children, tag } => {
|
||||
let mut nodes = Fragment::collect_children(&el);
|
||||
let (_, child_bundle) = children.hydrate(root, parent_scope, &el, &mut nodes);
|
||||
let child_bundle = children.hydrate(root, parent_scope, &el, &mut nodes);
|
||||
|
||||
nodes.trim_start_text_nodes(parent);
|
||||
nodes.trim_start_text_nodes();
|
||||
|
||||
assert!(nodes.is_empty(), "expected EOF, found node.");
|
||||
|
||||
@ -367,17 +358,14 @@ mod feat_hydration {
|
||||
|
||||
node_ref.set(Some((*el).clone()));
|
||||
|
||||
(
|
||||
node_ref.clone(),
|
||||
BTag {
|
||||
inner,
|
||||
listeners,
|
||||
attributes,
|
||||
reference: el,
|
||||
node_ref,
|
||||
key,
|
||||
},
|
||||
)
|
||||
BTag {
|
||||
inner,
|
||||
listeners,
|
||||
attributes,
|
||||
reference: el,
|
||||
node_ref,
|
||||
key,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -586,17 +574,17 @@ mod tests {
|
||||
let svg_node = html! { <svg>{path_node}</svg> };
|
||||
|
||||
let svg_tag = assert_vtag(svg_node);
|
||||
let (_, svg_tag) = svg_tag.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, svg_tag) = svg_tag.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_namespace(&svg_tag, SVG_NAMESPACE);
|
||||
let path_tag = assert_btag_ref(svg_tag.children().get(0).unwrap());
|
||||
assert_namespace(path_tag, SVG_NAMESPACE);
|
||||
|
||||
let g_tag = assert_vtag(g_node.clone());
|
||||
let (_, g_tag) = g_tag.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, g_tag) = g_tag.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_namespace(&g_tag, HTML_NAMESPACE);
|
||||
|
||||
let g_tag = assert_vtag(g_node);
|
||||
let (_, g_tag) = g_tag.attach(&root, &scope, &svg_el, NodeRef::default());
|
||||
let (_, g_tag) = g_tag.attach(&root, &scope, &svg_el, DomSlot::at_end());
|
||||
assert_namespace(&g_tag, SVG_NAMESPACE);
|
||||
}
|
||||
|
||||
@ -695,7 +683,7 @@ mod tests {
|
||||
let (root, scope, parent) = setup_parent();
|
||||
|
||||
let elem = html! { <div></div> };
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
let vtag = assert_btag_mut(&mut elem);
|
||||
// test if the className has not been set
|
||||
assert!(!vtag.reference().has_attribute("class"));
|
||||
@ -705,7 +693,7 @@ mod tests {
|
||||
let (root, scope, parent) = setup_parent();
|
||||
|
||||
let elem = gen_html();
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
let vtag = assert_btag_mut(&mut elem);
|
||||
// test if the className has been set
|
||||
assert!(vtag.reference().has_attribute("class"));
|
||||
@ -729,7 +717,7 @@ mod tests {
|
||||
|
||||
// Initial state
|
||||
let elem = html! { <input value={expected} /> };
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
let vtag = assert_btag_ref(&elem);
|
||||
|
||||
// User input
|
||||
@ -741,7 +729,7 @@ mod tests {
|
||||
let elem_vtag = assert_vtag(next_elem);
|
||||
|
||||
// Sync happens here
|
||||
elem_vtag.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
|
||||
elem_vtag.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
|
||||
let vtag = assert_btag_ref(&elem);
|
||||
|
||||
// Get new current value of the input element
|
||||
@ -760,7 +748,7 @@ mod tests {
|
||||
|
||||
// Initial state
|
||||
let elem = html! { <input /> };
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
let vtag = assert_btag_ref(&elem);
|
||||
|
||||
// User input
|
||||
@ -772,7 +760,7 @@ mod tests {
|
||||
let elem_vtag = assert_vtag(next_elem);
|
||||
|
||||
// Value should not be refreshed
|
||||
elem_vtag.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
|
||||
elem_vtag.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
|
||||
let vtag = assert_btag_ref(&elem);
|
||||
|
||||
// Get user value of the input element
|
||||
@ -799,7 +787,7 @@ mod tests {
|
||||
builder
|
||||
}/> };
|
||||
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
let vtag = assert_btag_mut(&mut elem);
|
||||
// make sure the new tag name is used internally
|
||||
assert_eq!(vtag.tag(), "a");
|
||||
@ -857,7 +845,7 @@ mod tests {
|
||||
let node_ref = NodeRef::default();
|
||||
let elem: VNode = html! { <div ref={node_ref.clone()}></div> };
|
||||
assert_vtag_ref(&elem);
|
||||
let (_, elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
assert_eq!(node_ref.get(), parent.first_child());
|
||||
elem.detach(&root, &parent, false);
|
||||
assert!(node_ref.get().is_none());
|
||||
@ -869,14 +857,14 @@ mod tests {
|
||||
|
||||
let node_ref_a = NodeRef::default();
|
||||
let elem_a = html! { <div id="a" ref={node_ref_a.clone()} /> };
|
||||
let (_, mut elem) = elem_a.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem_a.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
|
||||
// save the Node to check later that it has been reused.
|
||||
let node_a = node_ref_a.get().unwrap();
|
||||
|
||||
let node_ref_b = NodeRef::default();
|
||||
let elem_b = html! { <div id="b" ref={node_ref_b.clone()} /> };
|
||||
elem_b.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
|
||||
elem_b.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
|
||||
|
||||
let node_b = node_ref_b.get().unwrap();
|
||||
|
||||
@ -906,8 +894,8 @@ mod tests {
|
||||
// The point of this diff is to first render the "after" div and then detach the "before"
|
||||
// div, while both should be bound to the same node ref
|
||||
|
||||
let (_, mut elem) = before.attach(&root, &scope, &parent, NodeRef::default());
|
||||
after.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
|
||||
let (_, mut elem) = before.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
after.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
|
||||
|
||||
assert_eq!(
|
||||
test_ref
|
||||
@ -938,7 +926,7 @@ mod tests {
|
||||
|
||||
let elem = VNode::VTag(Box::new(vtag));
|
||||
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, NodeRef::default());
|
||||
let (_, mut elem) = elem.attach(&root, &scope, &parent, DomSlot::at_end());
|
||||
|
||||
// Create <div tabindex="0"> (removed first attribute "disabled")
|
||||
let mut vtag = VTag::new("div");
|
||||
@ -949,7 +937,7 @@ mod tests {
|
||||
|
||||
// Sync happens here
|
||||
// this should remove the the "disabled" attribute
|
||||
elem_vtag.reconcile_node(&root, &scope, &parent, NodeRef::default(), &mut elem);
|
||||
elem_vtag.reconcile_node(&root, &scope, &parent, DomSlot::at_end(), &mut elem);
|
||||
|
||||
assert_eq!(
|
||||
test_ref
|
||||
|
||||
@ -3,10 +3,9 @@
|
||||
use gloo::utils::document;
|
||||
use web_sys::{Element, Text as TextNode};
|
||||
|
||||
use super::{insert_node, BNode, BSubtree, Reconcilable, ReconcileTarget};
|
||||
use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::{AttrValue, VText};
|
||||
use crate::NodeRef;
|
||||
|
||||
/// The bundle implementation to [VText]
|
||||
pub(super) struct BText {
|
||||
@ -25,14 +24,10 @@ impl ReconcileTarget for BText {
|
||||
}
|
||||
}
|
||||
|
||||
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
|
||||
let node = &self.text_node;
|
||||
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
|
||||
slot.insert(next_parent, &self.text_node);
|
||||
|
||||
next_parent
|
||||
.insert_before(node, next_sibling.get().as_ref())
|
||||
.unwrap();
|
||||
|
||||
NodeRef::new(self.text_node.clone().into())
|
||||
DomSlot::at(self.text_node.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,12 +39,12 @@ impl Reconcilable for VText {
|
||||
_root: &BSubtree,
|
||||
_parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
slot: DomSlot,
|
||||
) -> (DomSlot, Self::Bundle) {
|
||||
let Self { text } = self;
|
||||
let text_node = document().create_text_node(&text);
|
||||
insert_node(&text_node, parent, next_sibling.get().as_ref());
|
||||
let node_ref = NodeRef::new(text_node.clone().into());
|
||||
slot.insert(parent, &text_node);
|
||||
let node_ref = DomSlot::at(text_node.clone().into());
|
||||
(node_ref, BText { text, text_node })
|
||||
}
|
||||
|
||||
@ -59,12 +54,12 @@ impl Reconcilable for VText {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
match bundle {
|
||||
BNode::Text(btext) => self.reconcile(root, parent_scope, parent, next_sibling, btext),
|
||||
_ => self.replace(root, parent_scope, parent, next_sibling, bundle),
|
||||
BNode::Text(btext) => self.reconcile(root, parent_scope, parent, slot, btext),
|
||||
_ => self.replace(root, parent_scope, parent, slot, bundle),
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,15 +68,15 @@ impl Reconcilable for VText {
|
||||
_root: &BSubtree,
|
||||
_parent_scope: &AnyScope,
|
||||
_parent: &Element,
|
||||
_next_sibling: NodeRef,
|
||||
_slot: DomSlot,
|
||||
btext: &mut Self::Bundle,
|
||||
) -> NodeRef {
|
||||
) -> DomSlot {
|
||||
let Self { text } = self;
|
||||
let ancestor_text = std::mem::replace(&mut btext.text, text);
|
||||
if btext.text != ancestor_text {
|
||||
btext.text_node.set_node_value(Some(&btext.text));
|
||||
}
|
||||
NodeRef::new(btext.text_node.clone().into())
|
||||
DomSlot::at(btext.text_node.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,52 +97,47 @@ mod feat_hydration {
|
||||
impl Hydratable for VText {
|
||||
fn hydrate(
|
||||
self,
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
_root: &BSubtree,
|
||||
_parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
fragment: &mut Fragment,
|
||||
) -> (NodeRef, Self::Bundle) {
|
||||
if let Some(m) = fragment.front().cloned() {
|
||||
) -> Self::Bundle {
|
||||
let next_sibling = if let Some(m) = fragment.front().cloned() {
|
||||
// better safe than sorry.
|
||||
if m.node_type() == Node::TEXT_NODE {
|
||||
if let Ok(m) = m.dyn_into::<TextNode>() {
|
||||
// pop current node.
|
||||
fragment.pop_front();
|
||||
let m = m.unchecked_into::<TextNode>();
|
||||
// pop current node.
|
||||
fragment.pop_front();
|
||||
|
||||
// TODO: It may make sense to assert the text content in the text node
|
||||
// against the VText when #[cfg(debug_assertions)]
|
||||
// is true, but this may be complicated.
|
||||
// We always replace the text value for now.
|
||||
//
|
||||
// Please see the next comment for a detailed explanation.
|
||||
m.set_node_value(Some(self.text.as_ref()));
|
||||
// TODO: It may make sense to assert the text content in the text node
|
||||
// against the VText when #[cfg(debug_assertions)]
|
||||
// is true, but this may be complicated.
|
||||
// We always replace the text value for now.
|
||||
//
|
||||
// Please see the next comment for a detailed explanation.
|
||||
m.set_node_value(Some(self.text.as_ref()));
|
||||
|
||||
return (
|
||||
NodeRef::new(m.clone().into()),
|
||||
BText {
|
||||
text: self.text,
|
||||
text_node: m,
|
||||
},
|
||||
);
|
||||
}
|
||||
return BText {
|
||||
text: self.text,
|
||||
text_node: m,
|
||||
};
|
||||
}
|
||||
}
|
||||
Some(m)
|
||||
} else {
|
||||
fragment.sibling_at_end().cloned()
|
||||
};
|
||||
|
||||
// If there are multiple text nodes placed back-to-back in SSR, it may be parsed as a
|
||||
// single text node by browser, hence we need to add extra text nodes here
|
||||
// if the next node is not a text node. Similarly, the value of the text
|
||||
// node may be a combination of multiple VText vnodes. So we always need to
|
||||
// override their values.
|
||||
self.attach(
|
||||
root,
|
||||
parent_scope,
|
||||
parent,
|
||||
fragment
|
||||
.front()
|
||||
.cloned()
|
||||
.map(NodeRef::new)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
let text_node = document().create_text_node("");
|
||||
DomSlot::create(next_sibling).insert(parent, &text_node);
|
||||
BText {
|
||||
text: "".into(),
|
||||
text_node,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
use crate::dom_bundle::BSubtree;
|
||||
use crate::html::NodeRef;
|
||||
use super::{BSubtree, DomSlot};
|
||||
use crate::virtual_dom::Collectable;
|
||||
|
||||
/// A Hydration Fragment
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Fragment(VecDeque<Node>);
|
||||
pub(crate) struct Fragment(VecDeque<Node>, Option<Node>);
|
||||
|
||||
impl Deref for Fragment {
|
||||
type Target = VecDeque<Node>;
|
||||
@ -39,7 +39,7 @@ impl Fragment {
|
||||
fragment.push_back(m);
|
||||
}
|
||||
|
||||
Self(fragment)
|
||||
Self(fragment, None)
|
||||
}
|
||||
|
||||
/// Collects nodes for a Component Bundle or a BSuspense.
|
||||
@ -63,7 +63,7 @@ impl Fragment {
|
||||
};
|
||||
|
||||
// We trim all leading text nodes as it's likely these are whitespaces.
|
||||
collect_from.trim_start_text_nodes(parent);
|
||||
collect_from.trim_start_text_nodes();
|
||||
|
||||
let first_node = collect_from
|
||||
.pop_front()
|
||||
@ -115,19 +115,20 @@ impl Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
nodes.push_back(current_node.clone());
|
||||
nodes.push_back(current_node);
|
||||
}
|
||||
|
||||
Self(nodes)
|
||||
let next_child = collect_from.0.front().cloned();
|
||||
Self(nodes, next_child)
|
||||
}
|
||||
|
||||
/// Remove child nodes until first non-text node.
|
||||
pub fn trim_start_text_nodes(&mut self, parent: &Element) {
|
||||
pub fn trim_start_text_nodes(&mut self) {
|
||||
while let Some(ref m) = self.front().cloned() {
|
||||
if m.node_type() == Node::TEXT_NODE {
|
||||
self.pop_front();
|
||||
|
||||
parent.remove_child(m).unwrap();
|
||||
m.unchecked_ref::<web_sys::Text>().remove();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
@ -141,7 +142,8 @@ impl Fragment {
|
||||
.map(|m| m.clone_node_with_deep(true).expect("failed to clone node."))
|
||||
.collect::<VecDeque<_>>();
|
||||
|
||||
Self(nodes)
|
||||
// the cloned nodes are disconnected from the real dom, so next_child is `None`
|
||||
Self(nodes, None)
|
||||
}
|
||||
|
||||
// detaches current fragment.
|
||||
@ -156,16 +158,16 @@ impl Fragment {
|
||||
}
|
||||
|
||||
/// Shift current Fragment into a different position in the dom.
|
||||
pub fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef {
|
||||
pub fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
|
||||
for node in self.iter() {
|
||||
next_parent
|
||||
.insert_before(node, next_sibling.get().as_ref())
|
||||
.unwrap();
|
||||
slot.insert(next_parent, node);
|
||||
}
|
||||
|
||||
self.front()
|
||||
.cloned()
|
||||
.map(NodeRef::new)
|
||||
.unwrap_or(next_sibling)
|
||||
self.front().cloned().map(DomSlot::at).unwrap_or(slot)
|
||||
}
|
||||
|
||||
/// Return the node that comes after all the nodes in this fragment
|
||||
pub fn sibling_at_end(&self) -> Option<&Node> {
|
||||
self.1.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
use web_sys::Element;
|
||||
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::VNode;
|
||||
|
||||
mod bcomp;
|
||||
@ -18,6 +18,7 @@ mod braw;
|
||||
mod bsuspense;
|
||||
mod btag;
|
||||
mod btext;
|
||||
mod position;
|
||||
mod subtree_root;
|
||||
|
||||
mod traits;
|
||||
@ -31,10 +32,11 @@ use braw::BRaw;
|
||||
use bsuspense::BSuspense;
|
||||
use btag::{BTag, Registry};
|
||||
use btext::BText;
|
||||
pub(crate) use position::{DomSlot, DynamicDomSlot};
|
||||
use subtree_root::EventDescriptor;
|
||||
pub use subtree_root::{set_event_bubbling, BSubtree};
|
||||
use traits::{Reconcilable, ReconcileTarget};
|
||||
use utils::{insert_node, test_log};
|
||||
use utils::test_log;
|
||||
|
||||
/// A Bundle.
|
||||
///
|
||||
@ -53,8 +55,8 @@ impl Bundle {
|
||||
}
|
||||
|
||||
/// Shifts the bundle into a different position.
|
||||
pub fn shift(&self, next_parent: &Element, next_sibling: NodeRef) {
|
||||
self.0.shift(next_parent, next_sibling);
|
||||
pub fn shift(&self, next_parent: &Element, slot: DomSlot) {
|
||||
self.0.shift(next_parent, slot);
|
||||
}
|
||||
|
||||
/// Applies a virtual dom layout to current bundle.
|
||||
@ -63,10 +65,10 @@ impl Bundle {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
next_node: VNode,
|
||||
) -> NodeRef {
|
||||
next_node.reconcile_node(root, parent_scope, parent, next_sibling, &mut self.0)
|
||||
) -> DomSlot {
|
||||
next_node.reconcile_node(root, parent_scope, parent, slot, &mut self.0)
|
||||
}
|
||||
|
||||
/// Detaches current bundle.
|
||||
@ -82,7 +84,7 @@ mod feat_hydration {
|
||||
pub(super) use super::utils::node_type_str;
|
||||
#[path = "./fragment.rs"]
|
||||
mod fragment;
|
||||
pub use fragment::Fragment;
|
||||
pub(crate) use fragment::Fragment;
|
||||
|
||||
use super::*;
|
||||
impl Bundle {
|
||||
@ -93,9 +95,9 @@ mod feat_hydration {
|
||||
parent: &Element,
|
||||
fragment: &mut Fragment,
|
||||
node: VNode,
|
||||
) -> (NodeRef, Self) {
|
||||
let (node_ref, bundle) = node.hydrate(root, parent_scope, parent, fragment);
|
||||
(node_ref, Self(bundle))
|
||||
) -> Self {
|
||||
let bundle = node.hydrate(root, parent_scope, parent, fragment);
|
||||
Self(bundle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
256
packages/yew/src/dom_bundle/position.rs
Normal file
256
packages/yew/src/dom_bundle/position.rs
Normal file
@ -0,0 +1,256 @@
|
||||
//! Structs for keeping track where in the DOM a node belongs
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
/// A position in the list of children of an implicit parent [`Element`].
|
||||
///
|
||||
/// This can either be in front of a `DomSlot::at(next_sibling)`, at the end of the list with
|
||||
/// `DomSlot::at_end()`, or a dynamic position in the list with [`DynamicDomSlot::to_position`].
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DomSlot {
|
||||
variant: DomSlotVariant,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum DomSlotVariant {
|
||||
Node(Option<Node>),
|
||||
Chained(DynamicDomSlot),
|
||||
}
|
||||
|
||||
/// A dynamic dom slot can be reassigned. This change is also seen by the [`DomSlot`] from
|
||||
/// [`Self::to_position`] before the reassignment took place.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DynamicDomSlot {
|
||||
target: Rc<RefCell<DomSlot>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DomSlot {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.with_next_sibling(|n| {
|
||||
write!(
|
||||
f,
|
||||
"DomSlot {{ next_sibling: {:?} }}",
|
||||
n.map(crate::utils::print_node)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DynamicDomSlot {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:#?}", *self.target.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
thread_local! {
|
||||
// A special marker element that should not be referenced
|
||||
static TRAP: Node = gloo::utils::document().create_element("div").unwrap().into();
|
||||
}
|
||||
|
||||
impl DomSlot {
|
||||
/// Denotes the position just before the given node in its parent's list of children.
|
||||
pub fn at(next_sibling: Node) -> Self {
|
||||
Self::create(Some(next_sibling))
|
||||
}
|
||||
|
||||
/// Denotes the position at the end of a list of children. The parent is implicit.
|
||||
pub fn at_end() -> Self {
|
||||
Self::create(None)
|
||||
}
|
||||
|
||||
pub fn create(next_sibling: Option<Node>) -> Self {
|
||||
Self {
|
||||
variant: DomSlotVariant::Node(next_sibling),
|
||||
}
|
||||
}
|
||||
|
||||
/// A new "placeholder" [DomSlot] that should not be used to insert nodes
|
||||
#[inline]
|
||||
pub fn new_debug_trapped() -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
Self::at(TRAP.with(|trap| trap.clone()))
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
Self::at_end()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the [Node] that comes just after the position, or `None` if this denotes the position at
|
||||
/// the end
|
||||
fn with_next_sibling<R>(&self, f: impl FnOnce(Option<&Node>) -> R) -> R {
|
||||
let checkedf = |node: Option<&Node>| {
|
||||
#[cfg(debug_assertions)]
|
||||
TRAP.with(|trap| {
|
||||
assert!(
|
||||
node != Some(trap),
|
||||
"Should not use a trapped DomSlot. Please report this as an internal bug in \
|
||||
yew."
|
||||
)
|
||||
});
|
||||
f(node)
|
||||
};
|
||||
|
||||
match &self.variant {
|
||||
DomSlotVariant::Node(ref n) => checkedf(n.as_ref()),
|
||||
DomSlotVariant::Chained(ref chain) => chain.with_next_sibling(checkedf),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a [Node] at the position denoted by this slot. `parent` must be the actual parent
|
||||
/// element of the children that this slot is implicitly a part of.
|
||||
pub(super) fn insert(&self, parent: &Element, node: &Node) {
|
||||
self.with_next_sibling(|next_sibling| {
|
||||
parent
|
||||
.insert_before(node, next_sibling)
|
||||
.unwrap_or_else(|err| {
|
||||
let msg = if next_sibling.is_some() {
|
||||
"failed to insert node before next sibling"
|
||||
} else {
|
||||
"failed to append child"
|
||||
};
|
||||
// Log normally, so we can inspect the nodes in console
|
||||
gloo::console::error!(msg, err, parent, next_sibling, node);
|
||||
// Log via tracing for consistency
|
||||
tracing::error!(msg);
|
||||
// Panic to short-curcuit and fail
|
||||
panic!("{}", msg)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(test)]
|
||||
fn get(&self) -> Option<Node> {
|
||||
self.with_next_sibling(|n| n.cloned())
|
||||
}
|
||||
}
|
||||
|
||||
impl DynamicDomSlot {
|
||||
/// Create a dynamic dom slot that initially represents ("targets") the same slot as the
|
||||
/// argument.
|
||||
pub fn new(initial_position: DomSlot) -> Self {
|
||||
Self {
|
||||
target: Rc::new(RefCell::new(initial_position)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_debug_trapped() -> Self {
|
||||
Self::new(DomSlot::new_debug_trapped())
|
||||
}
|
||||
|
||||
/// Change the [`DomSlot`] that is targeted. Subsequently, this will behave as if `self` was
|
||||
/// created from the passed DomSlot in the first place.
|
||||
pub fn reassign(&self, next_position: DomSlot) {
|
||||
// TODO: is not defensive against accidental reference loops
|
||||
*self.target.borrow_mut() = next_position;
|
||||
}
|
||||
|
||||
/// Get a [`DomSlot`] that gets automatically updated when `self` gets reassigned. All such
|
||||
/// slots are equivalent to each other and point to the same position.
|
||||
pub fn to_position(&self) -> DomSlot {
|
||||
DomSlot {
|
||||
variant: DomSlotVariant::Chained(self.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_next_sibling<R>(&self, f: impl FnOnce(Option<&Node>) -> R) -> R {
|
||||
// we use an iterative approach to traverse a possible long chain for references
|
||||
// see for example issue #3043 why a recursive call is impossible for large lists in vdom
|
||||
|
||||
// TODO: there could be some data structure that performs better here. E.g. a balanced tree
|
||||
// with parent pointers come to mind, but they are a bit fiddly to implement in rust
|
||||
let mut this = self.target.clone();
|
||||
loop {
|
||||
// v------- borrow lives for this match expression
|
||||
let next_this = match &this.borrow().variant {
|
||||
DomSlotVariant::Node(ref n) => break f(n.as_ref()),
|
||||
// We clone an Rc here temporarily, so that we don't have to consume stack
|
||||
// space. The alternative would be to keep the
|
||||
// `Ref<'_, DomSlot>` above in some temporary buffer
|
||||
DomSlotVariant::Chained(ref chain) => chain.target.clone(),
|
||||
};
|
||||
this = next_this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(test)]
|
||||
mod layout_tests {
|
||||
use gloo::utils::document;
|
||||
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
|
||||
|
||||
use super::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[test]
|
||||
fn new_at_and_get() {
|
||||
let node = document().create_element("p").unwrap();
|
||||
let position = DomSlot::at(node.clone().into());
|
||||
assert_eq!(
|
||||
position.get().unwrap(),
|
||||
node.clone().into(),
|
||||
"expected the DomSlot to be at {node:#?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_at_end_and_get() {
|
||||
let position = DomSlot::at_end();
|
||||
assert!(
|
||||
position.get().is_none(),
|
||||
"expected the DomSlot to not have a next sibling"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_through_dynamic() {
|
||||
let original = DomSlot::at(document().create_element("p").unwrap().into());
|
||||
let target = DynamicDomSlot::new(original.clone());
|
||||
assert_eq!(
|
||||
target.to_position().get(),
|
||||
original.get(),
|
||||
"expected {target:#?} to point to the same position as {original:#?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_after_reassign() {
|
||||
let target = DynamicDomSlot::new(DomSlot::at_end());
|
||||
let target_pos = target.to_position();
|
||||
// We reassign *after* we called `to_position` here to be strict in the test
|
||||
let replacement = DomSlot::at(document().create_element("p").unwrap().into());
|
||||
target.reassign(replacement.clone());
|
||||
assert_eq!(
|
||||
target_pos.get(),
|
||||
replacement.get(),
|
||||
"expected {target:#?} to point to the same position as {replacement:#?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_chain_after_reassign() {
|
||||
let middleman = DynamicDomSlot::new(DomSlot::at_end());
|
||||
let target = DynamicDomSlot::new(middleman.to_position());
|
||||
let target_pos = target.to_position();
|
||||
assert!(
|
||||
target.to_position().get().is_none(),
|
||||
"should not yet point to a node"
|
||||
);
|
||||
// Now reassign the middle man, but get the node from `target`
|
||||
let replacement = DomSlot::at(document().create_element("p").unwrap().into());
|
||||
middleman.reassign(replacement.clone());
|
||||
assert_eq!(
|
||||
target_pos.get(),
|
||||
replacement.get(),
|
||||
"expected {target:#?} to point to the same position as {replacement:#?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
use web_sys::Element;
|
||||
|
||||
use super::{BNode, BSubtree};
|
||||
use crate::html::{AnyScope, NodeRef};
|
||||
use super::{BNode, BSubtree, DomSlot};
|
||||
use crate::html::AnyScope;
|
||||
|
||||
/// A Reconcile Target.
|
||||
///
|
||||
@ -16,7 +16,7 @@ pub(super) trait ReconcileTarget {
|
||||
/// Move elements from one parent to another parent.
|
||||
/// This is for example used by `VSuspense` to preserve component state without detaching
|
||||
/// (which destroys component state).
|
||||
fn shift(&self, next_parent: &Element, next_sibling: NodeRef) -> NodeRef;
|
||||
fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot;
|
||||
}
|
||||
|
||||
/// This trait provides features to update a tree by calculating a difference against another tree.
|
||||
@ -29,19 +29,19 @@ pub(super) trait Reconcilable {
|
||||
/// - `root`: bundle of the subtree root
|
||||
/// - `parent_scope`: the parent `Scope` used for passing messages to the parent `Component`.
|
||||
/// - `parent`: the parent node in the DOM.
|
||||
/// - `next_sibling`: to find where to put the node.
|
||||
/// - `slot`: 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.
|
||||
/// The [`DomSlot`] points the first element (if there are multiple nodes created),
|
||||
/// or is the passed in `slot` if there are no element is created.
|
||||
fn attach(
|
||||
self,
|
||||
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
) -> (NodeRef, Self::Bundle);
|
||||
slot: DomSlot,
|
||||
) -> (DomSlot, Self::Bundle);
|
||||
|
||||
/// Scoped diff apply to other tree.
|
||||
///
|
||||
@ -52,7 +52,7 @@ pub(super) trait Reconcilable {
|
||||
/// Parameters:
|
||||
/// - `parent_scope`: the parent `Scope` used for passing messages to the parent `Component`.
|
||||
/// - `parent`: the parent node in the DOM.
|
||||
/// - `next_sibling`: the next sibling, used to efficiently find where to put the node.
|
||||
/// - `slot`: the slot in `parent`'s children where to put the node.
|
||||
/// - `bundle`: the node that this node will be replacing in the DOM. This method will remove
|
||||
/// the `bundle` from the `parent` if it is of the wrong kind, and otherwise reuse it.
|
||||
///
|
||||
@ -63,18 +63,18 @@ pub(super) trait Reconcilable {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef;
|
||||
) -> DomSlot;
|
||||
|
||||
fn reconcile(
|
||||
self,
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut Self::Bundle,
|
||||
) -> NodeRef;
|
||||
) -> DomSlot;
|
||||
|
||||
/// Replace an existing bundle by attaching self and detaching the existing one
|
||||
fn replace(
|
||||
@ -83,14 +83,14 @@ pub(super) trait Reconcilable {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
bundle: &mut BNode,
|
||||
) -> NodeRef
|
||||
) -> DomSlot
|
||||
where
|
||||
Self: Sized,
|
||||
Self::Bundle: Into<BNode>,
|
||||
{
|
||||
let (self_ref, self_) = self.attach(root, parent_scope, parent, next_sibling);
|
||||
let (self_ref, self_) = self.attach(root, parent_scope, parent, slot);
|
||||
let ancestor = std::mem::replace(bundle, self_.into());
|
||||
ancestor.detach(root, parent, false);
|
||||
self_ref
|
||||
@ -116,7 +116,7 @@ mod feat_hydration {
|
||||
parent_scope: &AnyScope,
|
||||
parent: &Element,
|
||||
fragment: &mut Fragment,
|
||||
) -> (NodeRef, Self::Bundle);
|
||||
) -> Self::Bundle;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,28 +1,3 @@
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
/// Insert a concrete [Node] into the DOM
|
||||
pub(super) fn insert_node(node: &Node, parent: &Element, next_sibling: Option<&Node>) {
|
||||
match next_sibling {
|
||||
Some(next_sibling) => parent
|
||||
.insert_before(node, Some(next_sibling))
|
||||
.unwrap_or_else(|err| {
|
||||
// Log normally, so we can inspect the nodes in console
|
||||
gloo::console::error!(
|
||||
"failed to insert node before next sibling",
|
||||
err,
|
||||
parent,
|
||||
next_sibling,
|
||||
node
|
||||
);
|
||||
// Log via tracing for consistency
|
||||
tracing::error!("failed to insert node before next sibling");
|
||||
// Panic to short-curcuit and fail
|
||||
panic!("failed to insert node before next sibling")
|
||||
}),
|
||||
None => parent.append_child(node).expect("failed to append child"),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(test, target_arch = "wasm32", verbose_tests))]
|
||||
macro_rules! test_log {
|
||||
($fmt:literal, $($arg:expr),* $(,)?) => {
|
||||
@ -45,9 +20,7 @@ mod feat_hydration {
|
||||
use std::borrow::Cow;
|
||||
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::Element;
|
||||
|
||||
use super::*;
|
||||
use web_sys::{Element, Node};
|
||||
|
||||
pub(in crate::dom_bundle) fn node_type_str(node: &Node) -> Cow<'static, str> {
|
||||
match node.node_type() {
|
||||
@ -85,9 +58,8 @@ mod tests {
|
||||
use gloo::utils::document;
|
||||
use web_sys::Element;
|
||||
|
||||
use crate::dom_bundle::BSubtree;
|
||||
use crate::dom_bundle::{BSubtree, DomSlot};
|
||||
use crate::html::AnyScope;
|
||||
use crate::NodeRef;
|
||||
|
||||
pub fn setup_parent() -> (BSubtree, AnyScope, Element) {
|
||||
let scope = AnyScope::test();
|
||||
@ -101,7 +73,7 @@ mod tests {
|
||||
|
||||
pub const SIBLING_CONTENT: &str = "END";
|
||||
|
||||
pub fn setup_parent_and_sibling() -> (BSubtree, AnyScope, Element, NodeRef) {
|
||||
pub(crate) fn setup_parent_and_sibling() -> (BSubtree, AnyScope, Element, DomSlot) {
|
||||
let scope = AnyScope::test();
|
||||
let parent = document().create_element("div").unwrap();
|
||||
let root = BSubtree::create_root(&parent);
|
||||
@ -110,7 +82,7 @@ mod tests {
|
||||
|
||||
let end = document().create_text_node(SIBLING_CONTENT);
|
||||
parent.append_child(&end).unwrap();
|
||||
let sibling = NodeRef::new(end.into());
|
||||
let sibling = DomSlot::at(end.into());
|
||||
|
||||
(root, scope, parent, sibling)
|
||||
}
|
||||
|
||||
@ -11,9 +11,7 @@ use super::BaseComponent;
|
||||
#[cfg(feature = "hydration")]
|
||||
use crate::dom_bundle::Fragment;
|
||||
#[cfg(feature = "csr")]
|
||||
use crate::dom_bundle::{BSubtree, Bundle};
|
||||
#[cfg(feature = "csr")]
|
||||
use crate::html::NodeRef;
|
||||
use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot};
|
||||
#[cfg(feature = "hydration")]
|
||||
use crate::html::RenderMode;
|
||||
use crate::html::{Html, RenderError};
|
||||
@ -27,16 +25,19 @@ pub(crate) enum ComponentRenderState {
|
||||
bundle: Bundle,
|
||||
root: BSubtree,
|
||||
parent: Element,
|
||||
next_sibling: NodeRef,
|
||||
internal_ref: NodeRef,
|
||||
/// The dom position in front of the next sibling
|
||||
sibling_slot: DynamicDomSlot,
|
||||
/// The dom position in front of this component. Adjusted whenever this component
|
||||
/// re-renders.
|
||||
own_slot: DynamicDomSlot,
|
||||
},
|
||||
#[cfg(feature = "hydration")]
|
||||
Hydration {
|
||||
fragment: Fragment,
|
||||
root: BSubtree,
|
||||
parent: Element,
|
||||
next_sibling: NodeRef,
|
||||
internal_ref: NodeRef,
|
||||
sibling_slot: DynamicDomSlot,
|
||||
own_slot: DynamicDomSlot,
|
||||
},
|
||||
#[cfg(feature = "ssr")]
|
||||
Ssr {
|
||||
@ -52,31 +53,31 @@ impl std::fmt::Debug for ComponentRenderState {
|
||||
ref bundle,
|
||||
root,
|
||||
ref parent,
|
||||
ref next_sibling,
|
||||
ref internal_ref,
|
||||
ref sibling_slot,
|
||||
ref own_slot,
|
||||
} => f
|
||||
.debug_struct("ComponentRenderState::Render")
|
||||
.field("bundle", bundle)
|
||||
.field("root", root)
|
||||
.field("parent", parent)
|
||||
.field("next_sibling", next_sibling)
|
||||
.field("internal_ref", internal_ref)
|
||||
.field("sibling_slot", sibling_slot)
|
||||
.field("own_slot", own_slot)
|
||||
.finish(),
|
||||
|
||||
#[cfg(feature = "hydration")]
|
||||
Self::Hydration {
|
||||
ref fragment,
|
||||
ref parent,
|
||||
ref next_sibling,
|
||||
ref internal_ref,
|
||||
ref sibling_slot,
|
||||
ref own_slot,
|
||||
ref root,
|
||||
} => f
|
||||
.debug_struct("ComponentRenderState::Hydration")
|
||||
.field("fragment", fragment)
|
||||
.field("root", root)
|
||||
.field("parent", parent)
|
||||
.field("next_sibling", next_sibling)
|
||||
.field("internal_ref", internal_ref)
|
||||
.field("sibling_slot", sibling_slot)
|
||||
.field("own_slot", own_slot)
|
||||
.finish(),
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@ -96,31 +97,31 @@ impl std::fmt::Debug for ComponentRenderState {
|
||||
|
||||
#[cfg(feature = "csr")]
|
||||
impl ComponentRenderState {
|
||||
pub(crate) fn shift(&mut self, next_parent: Element, next_next_sibling: NodeRef) {
|
||||
pub(crate) fn shift(&mut self, next_parent: Element, next_slot: DomSlot) {
|
||||
match self {
|
||||
#[cfg(feature = "csr")]
|
||||
Self::Render {
|
||||
bundle,
|
||||
parent,
|
||||
next_sibling,
|
||||
sibling_slot,
|
||||
..
|
||||
} => {
|
||||
bundle.shift(&next_parent, next_next_sibling.clone());
|
||||
bundle.shift(&next_parent, next_slot.clone());
|
||||
|
||||
*parent = next_parent;
|
||||
next_sibling.link(next_next_sibling);
|
||||
sibling_slot.reassign(next_slot);
|
||||
}
|
||||
#[cfg(feature = "hydration")]
|
||||
Self::Hydration {
|
||||
fragment,
|
||||
parent,
|
||||
next_sibling,
|
||||
sibling_slot,
|
||||
..
|
||||
} => {
|
||||
fragment.shift(&next_parent, next_next_sibling.clone());
|
||||
fragment.shift(&next_parent, next_slot.clone());
|
||||
|
||||
*parent = next_parent;
|
||||
next_sibling.link(next_next_sibling);
|
||||
sibling_slot.reassign(next_slot);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@ -386,13 +387,10 @@ impl ComponentState {
|
||||
ComponentRenderState::Render {
|
||||
bundle,
|
||||
ref parent,
|
||||
ref internal_ref,
|
||||
ref root,
|
||||
..
|
||||
} => {
|
||||
bundle.detach(root, parent, parent_to_detach);
|
||||
|
||||
internal_ref.set(None);
|
||||
}
|
||||
// We need to detach the hydrate fragment if the component is not hydrated.
|
||||
#[cfg(feature = "hydration")]
|
||||
@ -400,12 +398,9 @@ impl ComponentState {
|
||||
ref root,
|
||||
fragment,
|
||||
ref parent,
|
||||
ref internal_ref,
|
||||
..
|
||||
} => {
|
||||
fragment.detach(root, parent, parent_to_detach);
|
||||
|
||||
internal_ref.set(None);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@ -496,18 +491,15 @@ impl ComponentState {
|
||||
ref mut bundle,
|
||||
ref parent,
|
||||
ref root,
|
||||
ref next_sibling,
|
||||
ref internal_ref,
|
||||
ref sibling_slot,
|
||||
ref mut own_slot,
|
||||
..
|
||||
} => {
|
||||
let scope = self.inner.any_scope();
|
||||
|
||||
#[cfg(feature = "hydration")]
|
||||
next_sibling.debug_assert_not_trapped();
|
||||
|
||||
let new_node_ref =
|
||||
bundle.reconcile(root, &scope, parent, next_sibling.clone(), new_root);
|
||||
internal_ref.link(new_node_ref);
|
||||
bundle.reconcile(root, &scope, parent, sibling_slot.to_position(), new_root);
|
||||
own_slot.reassign(new_node_ref);
|
||||
|
||||
let first_render = !self.has_rendered;
|
||||
self.has_rendered = true;
|
||||
@ -526,12 +518,12 @@ impl ComponentState {
|
||||
ComponentRenderState::Hydration {
|
||||
ref mut fragment,
|
||||
ref parent,
|
||||
ref internal_ref,
|
||||
ref next_sibling,
|
||||
ref mut own_slot,
|
||||
ref mut sibling_slot,
|
||||
ref root,
|
||||
} => {
|
||||
// We schedule a "first" render to run immediately after hydration,
|
||||
// to fix NodeRefs (first_node and next_sibling).
|
||||
// to fix NodeRefs (first_node and slot).
|
||||
scheduler::push_component_priority_render(
|
||||
self.comp_id,
|
||||
Box::new(RenderRunner {
|
||||
@ -544,21 +536,22 @@ impl ComponentState {
|
||||
// This first node is not guaranteed to be correct here.
|
||||
// As it may be a comment node that is removed afterwards.
|
||||
// but we link it anyways.
|
||||
let (node, bundle) = Bundle::hydrate(root, &scope, parent, fragment, new_root);
|
||||
let bundle = Bundle::hydrate(root, &scope, parent, fragment, new_root);
|
||||
|
||||
// We trim all text nodes before checking as it's likely these are whitespaces.
|
||||
fragment.trim_start_text_nodes(parent);
|
||||
fragment.trim_start_text_nodes();
|
||||
|
||||
assert!(fragment.is_empty(), "expected end of component, found node");
|
||||
|
||||
internal_ref.link(node);
|
||||
|
||||
self.render_state = ComponentRenderState::Render {
|
||||
root: root.clone(),
|
||||
bundle,
|
||||
parent: parent.clone(),
|
||||
internal_ref: internal_ref.clone(),
|
||||
next_sibling: next_sibling.clone(),
|
||||
own_slot: std::mem::replace(own_slot, DynamicDomSlot::new_debug_trapped()),
|
||||
sibling_slot: std::mem::replace(
|
||||
sibling_slot,
|
||||
DynamicDomSlot::new_debug_trapped(),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@ -592,7 +585,7 @@ mod feat_csr {
|
||||
pub(crate) struct PropsUpdateRunner {
|
||||
pub state: Shared<Option<ComponentState>>,
|
||||
pub props: Option<Rc<dyn Any>>,
|
||||
pub next_sibling: Option<NodeRef>,
|
||||
pub next_sibling_slot: Option<DomSlot>,
|
||||
}
|
||||
|
||||
impl ComponentState {
|
||||
@ -601,26 +594,24 @@ mod feat_csr {
|
||||
skip(self),
|
||||
fields(component.id = self.comp_id)
|
||||
)]
|
||||
fn changed(&mut self, props: Option<Rc<dyn Any>>, next_sibling: Option<NodeRef>) -> bool {
|
||||
if let Some(next_sibling) = next_sibling {
|
||||
fn changed(
|
||||
&mut self,
|
||||
props: Option<Rc<dyn Any>>,
|
||||
next_sibling_slot: Option<DomSlot>,
|
||||
) -> bool {
|
||||
if let Some(next_sibling_slot) = next_sibling_slot {
|
||||
// When components are updated, their siblings were likely also updated
|
||||
// We also need to shift the bundle so next sibling will be synced to child
|
||||
// components.
|
||||
match self.render_state {
|
||||
match &mut self.render_state {
|
||||
#[cfg(feature = "csr")]
|
||||
ComponentRenderState::Render {
|
||||
next_sibling: ref current_next_sibling,
|
||||
..
|
||||
} => {
|
||||
current_next_sibling.link(next_sibling);
|
||||
ComponentRenderState::Render { sibling_slot, .. } => {
|
||||
sibling_slot.reassign(next_sibling_slot);
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydration")]
|
||||
ComponentRenderState::Hydration {
|
||||
next_sibling: ref current_next_sibling,
|
||||
..
|
||||
} => {
|
||||
current_next_sibling.link(next_sibling);
|
||||
ComponentRenderState::Hydration { sibling_slot, .. } => {
|
||||
sibling_slot.reassign(next_sibling_slot);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@ -681,13 +672,13 @@ mod feat_csr {
|
||||
impl Runnable for PropsUpdateRunner {
|
||||
fn run(self: Box<Self>) {
|
||||
let Self {
|
||||
next_sibling,
|
||||
next_sibling_slot,
|
||||
props,
|
||||
state: shared_state,
|
||||
} = *self;
|
||||
|
||||
if let Some(state) = shared_state.borrow_mut().as_mut() {
|
||||
let schedule_render = state.changed(props, next_sibling);
|
||||
let schedule_render = state.changed(props, next_sibling_slot);
|
||||
|
||||
if schedule_render {
|
||||
scheduler::push_component_render(
|
||||
@ -738,7 +729,7 @@ mod feat_csr {
|
||||
scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
|
||||
state: self.state.clone(),
|
||||
props: None,
|
||||
next_sibling: None,
|
||||
next_sibling_slot: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -886,8 +877,8 @@ mod tests {
|
||||
scope.mount_in_place(
|
||||
root,
|
||||
parent,
|
||||
NodeRef::default(),
|
||||
NodeRef::default(),
|
||||
DomSlot::at_end(),
|
||||
DynamicDomSlot::new_debug_trapped(),
|
||||
Rc::new(props),
|
||||
);
|
||||
crate::scheduler::start_now();
|
||||
|
||||
@ -520,11 +520,10 @@ mod feat_csr {
|
||||
use web_sys::Element;
|
||||
|
||||
use super::*;
|
||||
use crate::dom_bundle::{BSubtree, Bundle};
|
||||
use crate::dom_bundle::{BSubtree, Bundle, DomSlot, DynamicDomSlot};
|
||||
use crate::html::component::lifecycle::{
|
||||
ComponentRenderState, CreateRunner, DestroyRunner, PropsUpdateRunner, RenderRunner,
|
||||
};
|
||||
use crate::html::NodeRef;
|
||||
use crate::scheduler;
|
||||
|
||||
impl AnyScope {
|
||||
@ -541,11 +540,11 @@ mod feat_csr {
|
||||
fn schedule_props_update(
|
||||
state: Shared<Option<ComponentState>>,
|
||||
props: Rc<dyn Any>,
|
||||
next_sibling: NodeRef,
|
||||
next_sibling_slot: DomSlot,
|
||||
) {
|
||||
scheduler::push_component_props_update(Box::new(PropsUpdateRunner {
|
||||
state,
|
||||
next_sibling: Some(next_sibling),
|
||||
next_sibling_slot: Some(next_sibling_slot),
|
||||
props: Some(props),
|
||||
}));
|
||||
// Not guaranteed to already have the scheduler started
|
||||
@ -561,20 +560,20 @@ mod feat_csr {
|
||||
&self,
|
||||
root: BSubtree,
|
||||
parent: Element,
|
||||
next_sibling: NodeRef,
|
||||
internal_ref: NodeRef,
|
||||
slot: DomSlot,
|
||||
internal_ref: DynamicDomSlot,
|
||||
props: Rc<COMP::Properties>,
|
||||
) {
|
||||
let bundle = Bundle::new();
|
||||
internal_ref.link(next_sibling.clone());
|
||||
let stable_next_sibling = NodeRef::default();
|
||||
stable_next_sibling.link(next_sibling);
|
||||
let sibling_slot = DynamicDomSlot::new(slot);
|
||||
internal_ref.reassign(sibling_slot.to_position());
|
||||
|
||||
let state = ComponentRenderState::Render {
|
||||
bundle,
|
||||
root,
|
||||
internal_ref,
|
||||
own_slot: internal_ref,
|
||||
parent,
|
||||
next_sibling: stable_next_sibling,
|
||||
sibling_slot,
|
||||
};
|
||||
|
||||
scheduler::push_component_create(
|
||||
@ -594,8 +593,8 @@ mod feat_csr {
|
||||
scheduler::start();
|
||||
}
|
||||
|
||||
pub(crate) fn reuse(&self, props: Rc<COMP::Properties>, next_sibling: NodeRef) {
|
||||
schedule_props_update(self.state.clone(), props, next_sibling)
|
||||
pub(crate) fn reuse(&self, props: Rc<COMP::Properties>, slot: DomSlot) {
|
||||
schedule_props_update(self.state.clone(), props, slot)
|
||||
}
|
||||
}
|
||||
|
||||
@ -604,7 +603,7 @@ mod feat_csr {
|
||||
/// Get the render state if it hasn't already been destroyed
|
||||
fn render_state(&self) -> Option<Ref<'_, ComponentRenderState>>;
|
||||
/// Shift the node associated with this scope to a new place
|
||||
fn shift_node(&self, parent: Element, next_sibling: NodeRef);
|
||||
fn shift_node(&self, parent: Element, slot: DomSlot);
|
||||
/// Process an event to destroy a component
|
||||
fn destroy(self, parent_to_detach: bool);
|
||||
fn destroy_boxed(self: Box<Self>, parent_to_detach: bool);
|
||||
@ -640,10 +639,10 @@ mod feat_csr {
|
||||
self.destroy(parent_to_detach)
|
||||
}
|
||||
|
||||
fn shift_node(&self, parent: Element, next_sibling: NodeRef) {
|
||||
fn shift_node(&self, parent: Element, slot: DomSlot) {
|
||||
let mut state_ref = self.state.borrow_mut();
|
||||
if let Some(render_state) = state_ref.as_mut() {
|
||||
render_state.render_state.shift(parent, next_sibling)
|
||||
render_state.render_state.shift(parent, slot)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -657,9 +656,8 @@ mod feat_hydration {
|
||||
use web_sys::{Element, HtmlScriptElement};
|
||||
|
||||
use super::*;
|
||||
use crate::dom_bundle::{BSubtree, Fragment};
|
||||
use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot, Fragment};
|
||||
use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner};
|
||||
use crate::html::NodeRef;
|
||||
use crate::scheduler;
|
||||
use crate::virtual_dom::Collectable;
|
||||
|
||||
@ -680,7 +678,7 @@ mod feat_hydration {
|
||||
root: BSubtree,
|
||||
parent: Element,
|
||||
fragment: &mut Fragment,
|
||||
internal_ref: NodeRef,
|
||||
internal_ref: DynamicDomSlot,
|
||||
props: Rc<COMP::Properties>,
|
||||
) {
|
||||
// This is very helpful to see which component is failing during hydration
|
||||
@ -695,14 +693,12 @@ mod feat_hydration {
|
||||
let collectable = Collectable::for_component::<COMP>();
|
||||
|
||||
let mut fragment = Fragment::collect_between(fragment, &collectable, &parent);
|
||||
match fragment.front().cloned() {
|
||||
front @ Some(_) => internal_ref.set(front),
|
||||
None =>
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
internal_ref.link(NodeRef::new_debug_trapped())
|
||||
}
|
||||
}
|
||||
let next_sibling = if let Some(n) = fragment.front() {
|
||||
Some(n.clone())
|
||||
} else {
|
||||
fragment.sibling_at_end().cloned()
|
||||
};
|
||||
internal_ref.reassign(DomSlot::create(next_sibling));
|
||||
|
||||
let prepared_state = match fragment
|
||||
.back()
|
||||
@ -720,8 +716,8 @@ mod feat_hydration {
|
||||
let state = ComponentRenderState::Hydration {
|
||||
parent,
|
||||
root,
|
||||
internal_ref,
|
||||
next_sibling: NodeRef::new_debug_trapped(),
|
||||
own_slot: internal_ref,
|
||||
sibling_slot: DynamicDomSlot::new_debug_trapped(),
|
||||
fragment,
|
||||
};
|
||||
|
||||
|
||||
@ -92,7 +92,7 @@ pub struct NodeRef(Rc<RefCell<NodeRefInner>>);
|
||||
|
||||
impl PartialEq for NodeRef {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.as_ptr() == other.0.as_ptr() || Some(self) == other.0.borrow().link.as_ref()
|
||||
self.0.as_ptr() == other.0.as_ptr()
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,14 +109,13 @@ impl std::fmt::Debug for NodeRef {
|
||||
#[derive(PartialEq, Debug, Default, Clone)]
|
||||
struct NodeRefInner {
|
||||
node: Option<Node>,
|
||||
link: Option<NodeRef>,
|
||||
}
|
||||
|
||||
impl NodeRef {
|
||||
/// Get the wrapped Node reference if it exists
|
||||
pub fn get(&self) -> Option<Node> {
|
||||
let inner = self.0.borrow();
|
||||
inner.node.clone().or_else(|| inner.link.as_ref()?.get())
|
||||
inner.node.clone()
|
||||
}
|
||||
|
||||
/// Try converting the node reference into another form
|
||||
@ -131,68 +130,9 @@ mod feat_csr {
|
||||
use super::*;
|
||||
|
||||
impl NodeRef {
|
||||
/// Link a downstream `NodeRef`
|
||||
pub(crate) fn link(&self, node_ref: Self) {
|
||||
// Avoid circular references
|
||||
debug_assert!(
|
||||
self != &node_ref,
|
||||
"no circular references allowed! Report this as a bug in yew"
|
||||
);
|
||||
|
||||
let mut this = self.0.borrow_mut();
|
||||
this.node = None;
|
||||
this.link = Some(node_ref);
|
||||
}
|
||||
|
||||
/// Wrap an existing `Node` in a `NodeRef`
|
||||
pub(crate) fn new(node: Node) -> Self {
|
||||
let node_ref = NodeRef::default();
|
||||
node_ref.set(Some(node));
|
||||
node_ref
|
||||
}
|
||||
|
||||
/// Place a Node in a reference for later use
|
||||
pub(crate) fn set(&self, node: Option<Node>) {
|
||||
let mut this = self.0.borrow_mut();
|
||||
this.node = node;
|
||||
this.link = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydration")]
|
||||
mod feat_hydration {
|
||||
use super::*;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
thread_local! {
|
||||
// A special marker element that should not be referenced
|
||||
static TRAP: Node = gloo::utils::document().create_element("div").unwrap().into();
|
||||
}
|
||||
|
||||
impl NodeRef {
|
||||
// A new "placeholder" node ref that should not be accessed
|
||||
#[inline]
|
||||
pub(crate) fn new_debug_trapped() -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
Self::new(TRAP.with(|trap| trap.clone()))
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn debug_assert_not_trapped(&self) {
|
||||
#[cfg(debug_assertions)]
|
||||
TRAP.with(|trap| {
|
||||
assert!(
|
||||
self.get().as_ref() != Some(trap),
|
||||
"should not use a trapped node ref"
|
||||
)
|
||||
})
|
||||
pub(crate) fn set(&self, new_ref: Option<Node>) {
|
||||
let mut inner = self.0.borrow_mut();
|
||||
inner.node = new_ref;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,8 @@
|
||||
//!
|
||||
//! This tests must be run in browser and thus require the `csr` feature to be enabled
|
||||
use gloo::console::log;
|
||||
use yew::NodeRef;
|
||||
|
||||
use crate::dom_bundle::{BSubtree, Bundle};
|
||||
use crate::dom_bundle::{BSubtree, Bundle, DomSlot};
|
||||
use crate::html::AnyScope;
|
||||
use crate::virtual_dom::VNode;
|
||||
use crate::{scheduler, Component, Context, Html};
|
||||
@ -48,14 +47,14 @@ pub fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
|
||||
parent_element.append_child(&end_node).unwrap();
|
||||
|
||||
// Tests each layout independently
|
||||
let next_sibling = NodeRef::new(end_node.into());
|
||||
let slot = DomSlot::at(end_node.into());
|
||||
for layout in layouts.iter() {
|
||||
// Apply the layout
|
||||
let vnode = layout.node.clone();
|
||||
log!("Independently apply layout '{}'", layout.name);
|
||||
|
||||
let mut bundle = Bundle::new();
|
||||
bundle.reconcile(&root, &scope, &parent_element, next_sibling.clone(), vnode);
|
||||
bundle.reconcile(&root, &scope, &parent_element, slot.clone(), vnode);
|
||||
scheduler::start_now();
|
||||
assert_eq!(
|
||||
parent_element.inner_html(),
|
||||
@ -69,7 +68,7 @@ pub fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
|
||||
|
||||
log!("Independently reapply layout '{}'", layout.name);
|
||||
|
||||
bundle.reconcile(&root, &scope, &parent_element, next_sibling.clone(), vnode);
|
||||
bundle.reconcile(&root, &scope, &parent_element, slot.clone(), vnode);
|
||||
scheduler::start_now();
|
||||
assert_eq!(
|
||||
parent_element.inner_html(),
|
||||
@ -95,13 +94,7 @@ pub fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
|
||||
let next_vnode = layout.node.clone();
|
||||
|
||||
log!("Sequentially apply layout '{}'", layout.name);
|
||||
bundle.reconcile(
|
||||
&root,
|
||||
&scope,
|
||||
&parent_element,
|
||||
next_sibling.clone(),
|
||||
next_vnode,
|
||||
);
|
||||
bundle.reconcile(&root, &scope, &parent_element, slot.clone(), next_vnode);
|
||||
|
||||
scheduler::start_now();
|
||||
assert_eq!(
|
||||
@ -117,13 +110,7 @@ pub fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
|
||||
let next_vnode = layout.node.clone();
|
||||
|
||||
log!("Sequentially detach layout '{}'", layout.name);
|
||||
bundle.reconcile(
|
||||
&root,
|
||||
&scope,
|
||||
&parent_element,
|
||||
next_sibling.clone(),
|
||||
next_vnode,
|
||||
);
|
||||
bundle.reconcile(&root, &scope, &parent_element, slot.clone(), next_vnode);
|
||||
|
||||
scheduler::start_now();
|
||||
assert_eq!(
|
||||
|
||||
@ -10,15 +10,15 @@ use futures::future::{FutureExt, LocalBoxFuture};
|
||||
use web_sys::Element;
|
||||
|
||||
use super::Key;
|
||||
#[cfg(feature = "csr")]
|
||||
use crate::dom_bundle::BSubtree;
|
||||
#[cfg(feature = "hydration")]
|
||||
use crate::dom_bundle::Fragment;
|
||||
#[cfg(feature = "csr")]
|
||||
use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot};
|
||||
use crate::html::BaseComponent;
|
||||
#[cfg(feature = "csr")]
|
||||
use crate::html::Scoped;
|
||||
#[cfg(any(feature = "ssr", feature = "csr"))]
|
||||
use crate::html::{AnyScope, Scope};
|
||||
#[cfg(feature = "csr")]
|
||||
use crate::html::{NodeRef, Scoped};
|
||||
#[cfg(feature = "ssr")]
|
||||
use crate::platform::fmt::BufWriter;
|
||||
|
||||
@ -61,12 +61,12 @@ pub(crate) trait Mountable {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: Element,
|
||||
internal_ref: NodeRef,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
internal_ref: DynamicDomSlot,
|
||||
) -> Box<dyn Scoped>;
|
||||
|
||||
#[cfg(feature = "csr")]
|
||||
fn reuse(self: Box<Self>, scope: &dyn Scoped, next_sibling: NodeRef);
|
||||
fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot);
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
fn render_into_stream<'a>(
|
||||
@ -82,7 +82,7 @@ pub(crate) trait Mountable {
|
||||
root: BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: Element,
|
||||
internal_ref: NodeRef,
|
||||
internal_ref: DynamicDomSlot,
|
||||
fragment: &mut Fragment,
|
||||
) -> Box<dyn Scoped>;
|
||||
}
|
||||
@ -111,19 +111,19 @@ impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
|
||||
root: &BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: Element,
|
||||
internal_ref: NodeRef,
|
||||
next_sibling: NodeRef,
|
||||
slot: DomSlot,
|
||||
internal_ref: DynamicDomSlot,
|
||||
) -> Box<dyn Scoped> {
|
||||
let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
|
||||
scope.mount_in_place(root.clone(), parent, next_sibling, internal_ref, self.props);
|
||||
scope.mount_in_place(root.clone(), parent, slot, internal_ref, self.props);
|
||||
|
||||
Box::new(scope)
|
||||
}
|
||||
|
||||
#[cfg(feature = "csr")]
|
||||
fn reuse(self: Box<Self>, scope: &dyn Scoped, next_sibling: NodeRef) {
|
||||
fn reuse(self: Box<Self>, scope: &dyn Scoped, slot: DomSlot) {
|
||||
let scope: Scope<COMP> = scope.to_any().downcast::<COMP>();
|
||||
scope.reuse(self.props, next_sibling);
|
||||
scope.reuse(self.props, slot);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
@ -149,7 +149,7 @@ impl<COMP: BaseComponent> Mountable for PropsWrapper<COMP> {
|
||||
root: BSubtree,
|
||||
parent_scope: &AnyScope,
|
||||
parent: Element,
|
||||
internal_ref: NodeRef,
|
||||
internal_ref: DynamicDomSlot,
|
||||
fragment: &mut Fragment,
|
||||
) -> Box<dyn Scoped> {
|
||||
let scope: Scope<COMP> = Scope::new(Some(parent_scope.clone()));
|
||||
|
||||
@ -25,8 +25,8 @@ impl VPortal {
|
||||
}
|
||||
|
||||
/// Creates a [VPortal] rendering `content` in the DOM hierarchy under `host`.
|
||||
/// If `next_sibling` is given, the content is inserted before that [Node].
|
||||
/// The parent of `next_sibling`, if given, must be `host`.
|
||||
/// If `inner_sibling` is given, the content is inserted before that [Node].
|
||||
/// The parent of `inner_sibling`, if given, must be `host`.
|
||||
pub fn new_before(content: VNode, host: Element, inner_sibling: Option<Node>) -> Self {
|
||||
Self {
|
||||
host,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user