1use web_sys::Node;
2
3use crate::component::{node::AnyComponentNode, Component};
4
5use std::{
6 any::{Any, TypeId},
7 cell::RefCell,
8 collections::hash_map::DefaultHasher,
9 fmt,
10 hash::{Hash, Hasher},
11 rc::Rc,
12};
13
14use super::VNode;
15
16pub(crate) type PropertiesHash = u64;
17pub(crate) type AnyProps = Option<Box<dyn Any>>;
18pub(crate) type ComponentNodeGenerator =
19 Box<dyn Fn(AnyProps, &Node) -> Rc<RefCell<AnyComponentNode>> + 'static>;
20
21pub struct VComponent {
24 props: AnyProps,
25 hash: PropertiesHash,
26 generator: ComponentNodeGenerator,
27 key: Option<String>,
28 depth: Option<u32>,
29
30 pub(crate) comp: Option<Rc<RefCell<AnyComponentNode>>>,
31}
32
33impl VComponent {
34 pub fn new<C>(props: C::Properties, key: Option<String>) -> VComponent
48 where
49 C: Component + 'static,
50 {
51 let hash = Self::calculate_hash::<C>(&props);
52 let generator = Box::new(Self::generator::<C>);
53 VComponent {
54 props: Some(Box::new(props)),
55 generator,
56 hash,
57 key,
58 depth: None,
59 comp: None,
60 }
61 }
62
63 fn calculate_hash<C>(props: &C::Properties) -> PropertiesHash
64 where
65 C: Component + 'static,
66 {
67 let mut hasher = DefaultHasher::new();
68 props.hash(&mut hasher);
69 TypeId::of::<C>().hash(&mut hasher);
70 hasher.finish()
71 }
72
73 fn generator<C: Component + 'static>(
74 props: AnyProps,
75 ancestor: &Node,
76 ) -> Rc<RefCell<AnyComponentNode>> {
77 let props = props
78 .unwrap()
79 .downcast::<C::Properties>()
80 .expect("Trying to unpack others component properties");
81
82 AnyComponentNode::new(C::new(*props), ancestor.clone())
83 }
84
85 pub(crate) fn patch(&mut self, last: Option<VNode>, ancestor: &Node) {
86 let mut old_virt: Option<VComponent> = None;
87
88 match last {
89 Some(VNode::Component(vcomp)) => {
90 old_virt = Some(vcomp);
91 }
92 Some(VNode::Element(v)) => {
93 v.erase();
94 }
95 Some(VNode::Text(v)) => {
96 v.erase();
97 }
98 None => {}
99 Some(VNode::List(v)) => {
100 v.erase();
101 }
102 }
103
104 self.render(old_virt, ancestor);
105 }
106
107 pub(crate) fn erase(&self) {
108 if let Some(node) = self.comp.as_ref() {
109 node.borrow_mut().vdom.as_ref().unwrap().erase();
110 }
111 }
112
113 pub(crate) fn set_depth(&mut self, depth: u32) {
114 self.depth = Some(depth);
115 }
116
117 fn render(&mut self, last: Option<VComponent>, ancestor: &Node) {
118 match last {
119 Some(mut old_vcomp) if self.key.is_some() && old_vcomp.key == self.key => {
120 self.comp = old_vcomp.comp.take();
121 }
122 Some(mut old_vcomp) if old_vcomp.hash == self.hash => {
123 self.comp = old_vcomp.comp.take();
124 }
125 Some(old_vcomp) => {
126 let any_component_node_rc = (self.generator)(self.props.take(), ancestor);
127 {
128 let mut any_component_node = any_component_node_rc.borrow_mut();
129 any_component_node.depth = self.depth;
130 any_component_node.view();
131 any_component_node.patch(old_vcomp.comp.clone(), ancestor);
132 }
133 self.comp = Some(any_component_node_rc);
134 }
135 None => {
136 let any_component_node_rc = (self.generator)(self.props.take(), ancestor);
137 {
138 let mut any_component_node = any_component_node_rc.borrow_mut();
139 any_component_node.depth = self.depth;
140 any_component_node.view();
141 any_component_node.patch(None, ancestor);
142 }
143 self.comp = Some(any_component_node_rc);
144 }
145 }
146 }
147}
148
149impl fmt::Debug for VComponent {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.debug_struct("VComponent")
152 .field("props", &self.props)
153 .field("hash", &self.hash)
154 .field("comp", &self.comp)
155 .finish()
156 }
157}
158
159impl PartialEq for VComponent {
160 fn eq(&self, other: &Self) -> bool {
161 self.hash == other.hash
162 && match (&self.props, &other.props) {
163 (Some(self_props), Some(other_props)) => {
164 (*(*self_props)).type_id() == (*(*other_props)).type_id()
165 }
166 (None, None) => true,
167 _ => false,
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use wasm_bindgen_test::wasm_bindgen_test;
175
176 use crate::{
177 component::{behavior::Behavior, Component},
178 virtual_dom::{dom, VElement, VList, VNode, VText},
179 };
180
181 use super::VComponent;
182 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
183
184 macro_rules! function_name {
185 () => {{
186 fn f() {}
187 fn type_name_of<T>(_: T) -> &'static str {
188 std::any::type_name::<T>()
189 }
190 let name = type_name_of(f);
191 name.strip_suffix("::f").unwrap()
192 }};
193 }
194
195 const VALID_TEXT: &str = "";
196
197 struct Tmp;
198 impl Component for Tmp {
199 type Message = ();
200 type Properties = ();
201
202 fn new(_props: Self::Properties) -> Self {
203 Tmp
204 }
205 fn view(&self, _behavior: &mut impl Behavior<Self>) -> VNode {
206 VElement::new(
207 "div".into(),
208 [(String::from("result"), String::from(VALID_TEXT))].into(),
209 vec![],
210 None,
211 vec![],
212 )
213 .into()
214 }
215 fn update(&mut self, _message: Self::Message) -> bool {
216 false
217 }
218 }
219
220 #[wasm_bindgen_test]
221 fn patch_last_none() {
222 let ancestor = dom::create_element("div");
223 dom::set_attribute(&ancestor, "id", function_name!());
224 dom::append_child(&dom::get_root_element(), &ancestor);
225
226 let mut target = VComponent::new::<Tmp>((), None);
227 target.set_depth(0);
228 target.patch(None, &ancestor);
229 }
230
231 #[wasm_bindgen_test]
232 fn patch_last_text() {
233 let ancestor = dom::create_element("div");
234 dom::set_attribute(&ancestor, "id", function_name!());
235
236 let current = dom::create_text_node("I dont love Rust");
237 dom::append_child(&ancestor, ¤t);
238
239 dom::append_child(&dom::get_root_element(), &ancestor);
240
241 let text = VNode::Text(VText {
242 text: "I dont love Rust".into(),
243 dom: Some(current),
244 });
245
246 let mut target = VComponent::new::<Tmp>((), None);
247 target.set_depth(0);
248 target.patch(Some(text), &ancestor);
249 }
250
251 #[wasm_bindgen_test]
252 fn patch_last_elem() {
253 let ancestor = dom::create_element("div");
254 dom::set_attribute(&ancestor, "id", function_name!());
255
256 let current = dom::create_element("div");
257 dom::set_attribute(¤t, "id", "I dont love Rust");
258 dom::append_child(&ancestor, ¤t);
259
260 dom::append_child(&dom::get_root_element(), &ancestor);
261
262 let elem = VNode::Element(VElement {
263 tag_name: "div".into(),
264 attr: [("id".into(), "I dont love Rust".into())].into(),
265 event_handlers: vec![],
266 key: None,
267 children: vec![],
268 dom: Some(current),
269 });
270
271 let mut target = VComponent::new::<Tmp>((), None);
272 target.set_depth(0);
273 target.patch(Some(elem), &ancestor);
274 }
275
276 struct Comp;
277 impl Component for Comp {
278 type Message = ();
279 type Properties = ();
280
281 fn new(_props: Self::Properties) -> Self {
282 Comp
283 }
284 fn view(&self, _behavior: &mut impl Behavior<Self>) -> VNode {
285 VElement::new(
286 "div".into(),
287 [(String::from("result"), String::from("I dont love Rust"))].into(),
288 vec![],
289 None,
290 vec![],
291 )
292 .into()
293 }
294 fn update(&mut self, _message: Self::Message) -> bool {
295 false
296 }
297 }
298
299 #[wasm_bindgen_test]
300 fn patch_last_comp_diff_keys() {
301 let ancestor = dom::create_element("div");
302 dom::set_attribute(&ancestor, "id", function_name!());
303 dom::append_child(&dom::get_root_element(), &ancestor);
304
305 let mut comp = VNode::Component(VComponent::new::<Comp>((), None));
306 comp.set_depth(0);
307 comp.patch(None, &ancestor);
308
309 let mut target = VComponent::new::<Tmp>((), None);
310 target.set_depth(0);
311 target.patch(Some(comp), &ancestor);
312 }
313
314 #[wasm_bindgen_test]
315 fn patch_last_comp_same_keys() {
316 let ancestor = dom::create_element("div");
317 dom::set_attribute(&ancestor, "id", function_name!());
318 dom::append_child(&dom::get_root_element(), &ancestor);
319
320 let key = Some(String::from("Same_key"));
321 let mut comp = VNode::Component(VComponent::new::<Comp>((), key.clone()));
322 comp.set_depth(0);
323 comp.patch(None, &ancestor);
324
325 let mut target = VComponent::new::<Tmp>((), key);
326 target.set_depth(0);
327 target.patch(Some(comp), &ancestor);
328 }
329
330 #[wasm_bindgen_test]
331 fn patch_last_list() {
332 let ancestor = dom::create_element("div");
333 dom::set_attribute(&ancestor, "id", function_name!());
334 dom::append_child(&dom::get_root_element(), &ancestor);
335
336 let mut list = VNode::List(VList::new(
337 vec![VText::new("I dont love Rust").into()],
338 None,
339 ));
340 list.set_depth(0);
341 list.patch(None, &ancestor);
342
343 let mut target = VComponent::new::<Tmp>((), None);
344 target.set_depth(0);
345 target.patch(Some(list), &ancestor);
346 }
347}