1use super::{Key, VChild, VComp, VDiff, VList, VTag, VText};
4use crate::html::{AnyScope, Component, NodeRef};
5use cfg_if::cfg_if;
6use cfg_match::cfg_match;
7use log::warn;
8use std::cmp::PartialEq;
9use std::fmt;
10use std::iter::FromIterator;
11
12cfg_if! {
13 if #[cfg(feature = "std_web")] {
14 use stdweb::web::{Element, INode, Node};
15 } else if #[cfg(feature = "web_sys")] {
16 use web_sys::{Element, Node};
17 }
18}
19
20#[derive(Clone)]
22pub enum VNode {
23 VTag(Box<VTag>),
25 VText(VText),
27 VComp(VComp),
29 VList(VList),
31 VRef(Node),
33}
34
35impl VNode {
36 pub fn key(&self) -> Option<Key> {
37 match self {
38 VNode::VComp(vcomp) => vcomp.key.clone(),
39 VNode::VList(vlist) => vlist.key.clone(),
40 VNode::VRef(_) => None,
41 VNode::VTag(vtag) => vtag.key.clone(),
42 VNode::VText(_) => None,
43 }
44 }
45
46 pub(crate) fn first_node(&self) -> Node {
48 match self {
49 VNode::VTag(vtag) => vtag
50 .reference
51 .as_ref()
52 .expect("VTag is not mounted")
53 .clone()
54 .into(),
55 VNode::VText(vtext) => {
56 let text_node = vtext.reference.as_ref().expect("VText is not mounted");
57 cfg_match! {
58 feature = "std_web" => text_node.as_node().clone(),
59 feature = "web_sys" => text_node.clone().into(),
60 }
61 }
62 VNode::VComp(vcomp) => vcomp.node_ref.get().expect("VComp is not mounted"),
63 VNode::VList(vlist) => vlist
64 .children
65 .get(0)
66 .expect("VList is not mounted")
67 .first_node(),
68 VNode::VRef(node) => node.clone(),
69 }
70 }
71
72 pub(crate) fn move_before(&self, parent: &Element, next_sibling: Option<Node>) {
73 match self {
74 VNode::VList(vlist) => {
75 for node in vlist.children.iter() {
76 node.move_before(parent, next_sibling.clone());
77 }
78 }
79 VNode::VComp(vcomp) => {
80 vcomp
81 .root_vnode()
82 .expect("VComp has no root vnode")
83 .move_before(parent, next_sibling);
84 }
85 _ => super::insert_node(&self.first_node(), parent, next_sibling),
86 };
87 }
88}
89
90impl VDiff for VNode {
91 fn detach(&mut self, parent: &Element) {
93 match *self {
94 VNode::VTag(ref mut vtag) => vtag.detach(parent),
95 VNode::VText(ref mut vtext) => vtext.detach(parent),
96 VNode::VComp(ref mut vcomp) => vcomp.detach(parent),
97 VNode::VList(ref mut vlist) => vlist.detach(parent),
98 VNode::VRef(ref node) => {
99 if parent.remove_child(node).is_err() {
100 warn!("Node not found to remove VRef");
101 }
102 }
103 }
104 }
105
106 fn apply(
107 &mut self,
108 parent_scope: &AnyScope,
109 parent: &Element,
110 next_sibling: NodeRef,
111 ancestor: Option<VNode>,
112 ) -> NodeRef {
113 match *self {
114 VNode::VTag(ref mut vtag) => vtag.apply(parent_scope, parent, next_sibling, ancestor),
115 VNode::VText(ref mut vtext) => {
116 vtext.apply(parent_scope, parent, next_sibling, ancestor)
117 }
118 VNode::VComp(ref mut vcomp) => {
119 vcomp.apply(parent_scope, parent, next_sibling, ancestor)
120 }
121 VNode::VList(ref mut vlist) => {
122 vlist.apply(parent_scope, parent, next_sibling, ancestor)
123 }
124 VNode::VRef(ref mut node) => {
125 if let Some(mut ancestor) = ancestor {
126 if let VNode::VRef(n) = &ancestor {
127 if node == n {
128 return NodeRef::new(node.clone());
129 }
130 }
131 ancestor.detach(parent);
132 }
133 super::insert_node(node, parent, next_sibling.get());
134 NodeRef::new(node.clone())
135 }
136 }
137 }
138}
139
140impl Default for VNode {
141 fn default() -> Self {
142 VNode::VList(VList::default())
143 }
144}
145
146impl From<VText> for VNode {
147 fn from(vtext: VText) -> Self {
148 VNode::VText(vtext)
149 }
150}
151
152impl From<VList> for VNode {
153 fn from(vlist: VList) -> Self {
154 VNode::VList(vlist)
155 }
156}
157
158impl From<VTag> for VNode {
159 fn from(vtag: VTag) -> Self {
160 VNode::VTag(Box::new(vtag))
161 }
162}
163
164impl From<VComp> for VNode {
165 fn from(vcomp: VComp) -> Self {
166 VNode::VComp(vcomp)
167 }
168}
169
170impl<COMP> From<VChild<COMP>> for VNode
171where
172 COMP: Component,
173{
174 fn from(vchild: VChild<COMP>) -> Self {
175 VNode::VComp(VComp::from(vchild))
176 }
177}
178
179impl<T: ToString> From<T> for VNode {
180 fn from(value: T) -> Self {
181 VNode::VText(VText::new(value.to_string()))
182 }
183}
184
185impl<A: Into<VNode>> FromIterator<A> for VNode {
186 fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
187 let vlist = iter.into_iter().fold(VList::default(), |mut acc, x| {
188 acc.add_child(x.into());
189 acc
190 });
191 VNode::VList(vlist)
192 }
193}
194
195impl fmt::Debug for VNode {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 match *self {
198 VNode::VTag(ref vtag) => vtag.fmt(f),
199 VNode::VText(ref vtext) => vtext.fmt(f),
200 VNode::VComp(ref vcomp) => vcomp.fmt(f),
201 VNode::VList(ref vlist) => vlist.fmt(f),
202 VNode::VRef(ref vref) => vref.fmt(f),
203 }
204 }
205}
206
207impl PartialEq for VNode {
208 fn eq(&self, other: &VNode) -> bool {
209 match (self, other) {
210 (VNode::VTag(a), VNode::VTag(b)) => a == b,
211 (VNode::VText(a), VNode::VText(b)) => a == b,
212 (VNode::VList(a), VNode::VList(b)) => a == b,
213 (VNode::VRef(a), VNode::VRef(b)) => a == b,
214 (VNode::VComp(_), VNode::VComp(_)) => false,
216 _ => false,
217 }
218 }
219}
220
221#[cfg(all(test, feature = "web_sys"))]
222mod layout_tests {
223 use super::*;
224 use crate::virtual_dom::layout_tests::{diff_layouts, TestLayout};
225
226 #[cfg(feature = "wasm_test")]
227 use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
228
229 #[cfg(feature = "wasm_test")]
230 wasm_bindgen_test_configure!(run_in_browser);
231
232 #[test]
233 fn diff() {
234 let document = crate::utils::document();
235 let vref_node_1 = VNode::VRef(document.create_element("i").unwrap().into());
236 let vref_node_2 = VNode::VRef(document.create_element("b").unwrap().into());
237
238 let layout1 = TestLayout {
239 name: "1",
240 node: vref_node_1,
241 expected: "<i></i>",
242 };
243
244 let layout2 = TestLayout {
245 name: "2",
246 node: vref_node_2,
247 expected: "<b></b>",
248 };
249
250 diff_layouts(vec![layout1, layout2]);
251 }
252}