use super::{VDiff, VNode};
use crate::html::{AnyScope, NodeRef};
use web_sys::{Element, Node};
#[derive(Debug, Clone)]
pub struct VPortal {
pub host: Element,
pub next_sibling: NodeRef,
pub node: Box<VNode>,
sibling_ref: NodeRef,
}
impl VDiff for VPortal {
fn detach(&mut self, _: &Element) {
self.node.detach(&self.host);
self.sibling_ref.set(None);
}
fn apply(
&mut self,
parent_scope: &AnyScope,
parent: &Element,
next_sibling: NodeRef,
ancestor: Option<VNode>,
) -> NodeRef {
let inner_ancestor = match ancestor {
Some(VNode::VPortal(old_portal)) => {
let VPortal {
host: old_host,
next_sibling: old_sibling,
mut node,
..
} = old_portal;
if old_host != self.host {
node.detach(&old_host);
None
} else if old_sibling != self.next_sibling {
node.move_before(&self.host, &self.next_sibling.get());
Some(*node)
} else {
Some(*node)
}
}
Some(mut node) => {
node.detach(parent);
None
}
None => None,
};
self.node.apply(
parent_scope,
&self.host,
self.next_sibling.clone(),
inner_ancestor,
);
self.sibling_ref = next_sibling.clone();
next_sibling
}
}
impl VPortal {
pub fn new(content: VNode, host: Element) -> Self {
Self {
host,
next_sibling: NodeRef::default(),
node: Box::new(content),
sibling_ref: NodeRef::default(),
}
}
pub fn new_before(content: VNode, host: Element, next_sibling: Option<Node>) -> Self {
Self {
host,
next_sibling: {
let sib_ref = NodeRef::default();
sib_ref.set(next_sibling);
sib_ref
},
node: Box::new(content),
sibling_ref: NodeRef::default(),
}
}
pub fn next_sibling(&self) -> Option<Node> {
self.sibling_ref.get()
}
}
#[cfg(test)]
mod layout_tests {
extern crate self as yew;
use crate::html;
use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout};
use crate::virtual_dom::VNode;
use yew::virtual_dom::VPortal;
#[cfg(feature = "wasm_test")]
use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
#[cfg(feature = "wasm_test")]
wasm_bindgen_test_configure!(run_in_browser);
#[test]
fn diff() {
let mut layouts = vec![];
let first_target = gloo_utils::document().create_element("i").unwrap();
let second_target = gloo_utils::document().create_element("o").unwrap();
let target_with_child = gloo_utils::document().create_element("i").unwrap();
let target_child = gloo_utils::document().create_element("s").unwrap();
target_with_child.append_child(&target_child).unwrap();
layouts.push(TestLayout {
name: "Portal - first target",
node: html! {
<div>
{VNode::VRef(first_target.clone().into())}
{VNode::VRef(second_target.clone().into())}
{VNode::VPortal(VPortal::new(
html! { {"PORTAL"} },
first_target.clone(),
))}
{"AFTER"}
</div>
},
expected: "<div><i>PORTAL</i><o></o>AFTER</div>",
});
layouts.push(TestLayout {
name: "Portal - second target",
node: html! {
<div>
{VNode::VRef(first_target.clone().into())}
{VNode::VRef(second_target.clone().into())}
{VNode::VPortal(VPortal::new(
html! { {"PORTAL"} },
second_target.clone(),
))}
{"AFTER"}
</div>
},
expected: "<div><i></i><o>PORTAL</o>AFTER</div>",
});
layouts.push(TestLayout {
name: "Portal - replaced by text",
node: html! {
<div>
{VNode::VRef(first_target.clone().into())}
{VNode::VRef(second_target.clone().into())}
{"FOO"}
{"AFTER"}
</div>
},
expected: "<div><i></i><o></o>FOOAFTER</div>",
});
layouts.push(TestLayout {
name: "Portal - next sibling",
node: html! {
<div>
{VNode::VRef(target_with_child.clone().into())}
{VNode::VPortal(VPortal::new_before(
html! { {"PORTAL"} },
target_with_child.clone(),
Some(target_child.clone().into()),
))}
</div>
},
expected: "<div><i>PORTAL<s></s></i></div>",
});
diff_layouts(layouts)
}
}