forked from yewstack/yew
-
Notifications
You must be signed in to change notification settings - Fork 0
/
vportal.rs
189 lines (175 loc) · 6.24 KB
/
vportal.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
//! This module contains the implementation of a portal `VPortal`.
use super::{VDiff, VNode};
use crate::html::{AnyScope, NodeRef};
use web_sys::{Element, Node};
#[derive(Debug, Clone)]
pub struct VPortal {
/// The element under which the content is inserted.
pub host: Element,
/// The next sibling after the inserted content
pub next_sibling: NodeRef,
/// The inserted node
pub node: Box<VNode>,
/// The next sibling after the portal. Set when rendered
sibling_ref: NodeRef,
}
impl VDiff for VPortal {
fn detach(&mut self, _: &Element, _parent_to_detach: bool) {
self.node.detach(&self.host, false);
self.sibling_ref.set(None);
}
fn shift(&self, _previous_parent: &Element, _next_parent: &Element, _next_sibling: NodeRef) {
// portals have nothing in it's original place of DOM, we also do nothing.
}
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 {
// Remount the inner node somewhere else instead of diffing
node.detach(&old_host, false);
None
} else if old_sibling != self.next_sibling {
// Move the node, but keep the state
node.move_before(&self.host, &self.next_sibling.get());
Some(*node)
} else {
Some(*node)
}
}
Some(mut node) => {
node.detach(parent, false);
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 {
/// Creates a [VPortal] rendering `content` in the DOM hierarchy under `host`.
pub fn new(content: VNode, host: Element) -> Self {
Self {
host,
next_sibling: NodeRef::default(),
node: Box::new(content),
sibling_ref: NodeRef::default(),
}
}
/// 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`.
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(),
}
}
/// Returns the [Node] following this [VPortal], if this [VPortal]
/// has already been mounted in the DOM.
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::tests::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)
}
}