1use std::rc::Rc;
2use wasm_bindgen::prelude::*;
3use wasm_bindgen::JsCast;
4use web_sys::*;
5
6use super::mount::mount_to_dom;
7use super::children::patch_children;
8use super::{EventHandler, VElement, VNode};
9
10const SVG_NS: &str = "http://www.w3.org/2000/svg";
12
13pub fn patch_node(
24 old: &VNode,
25 new: &VNode,
26 parent: &web_sys::Node,
27 anchor: Option<&web_sys::Node>,
28) -> Option<web_sys::Node> {
29 if matches!(old, VNode::Empty) && matches!(new, VNode::Empty) {
31 return None;
32 }
33
34 if matches!(old, VNode::Empty) {
36 return mount_to_dom(new, parent, anchor);
37 }
38
39 if matches!(new, VNode::Empty) {
41 if let Some(old_node) = old.dom_node() {
42 let _ = parent.remove_child(&old_node);
43 }
44 return None;
45 }
46
47 if !old.same_type(new) {
49 return replace_node(old, new, parent, anchor);
50 }
51
52 match (old, new) {
54 (VNode::Element(old_el), VNode::Element(new_el)) => {
55 Some(patch_element(old_el, new_el, parent, anchor))
56 }
57 (VNode::Text(old_text), VNode::Text(new_text)) => {
58 patch_text_node(old_text, new_text, old)
59 }
60 (VNode::Fragment(old_children), VNode::Fragment(new_children)) => {
61 patch_children(old_children, new_children, parent);
65 new_children.first().and_then(|c| c.dom_node())
67 }
68 (VNode::Dynamic(_old_fn, _old_dom), VNode::Dynamic(new_fn, new_dom)) => {
69 let f = new_fn.borrow();
72 let new_inner = f();
73 drop(f);
74
75 if let Some(old_node) = old.dom_node() {
77 let _ = parent.remove_child(&old_node);
78 }
79
80 if let Some(node) = mount_to_dom(&new_inner, parent, anchor) {
82 *new_dom.borrow_mut() = Some(node.clone());
84 Some(node)
85 } else {
86 None
87 }
88 }
89 _ => {
90 replace_node(old, new, parent, anchor)
92 }
93 }
94}
95
96fn replace_node(
102 old: &VNode,
103 new: &VNode,
104 parent: &web_sys::Node,
105 anchor: Option<&web_sys::Node>,
106) -> Option<web_sys::Node> {
107 if let Some(old_node) = old.dom_node() {
109 let _ = parent.remove_child(&old_node);
110 }
111
112 mount_to_dom(new, parent, anchor)
114}
115
116fn patch_text_node(old_text: &str, new_text: &str, old_vnode: &VNode) -> Option<web_sys::Node> {
122 if let Some(node) = old_vnode.dom_node() {
123 if old_text != new_text {
124 let _ = node.set_text_content(Some(new_text));
125 }
126 Some(node)
127 } else {
128 None
130 }
131}
132
133fn patch_element(
139 old: &VElement,
140 new: &VElement,
141 _parent: &web_sys::Node,
142 _anchor: Option<&web_sys::Node>,
143) -> web_sys::Node {
144 let dom_elem: web_sys::Element = match *old.dom_ref.borrow() {
146 Some(ref node) => {
147 node.clone().dyn_into::<web_sys::Element>().unwrap_or_else(|_| {
149 create_element_ns(&new.tag)
151 })
152 }
153 None => {
154 create_element_ns(&new.tag)
156 }
157 };
158
159 patch_attributes(old, new, &dom_elem);
161
162 patch_event_listeners(old, new, &dom_elem);
164
165 let node: web_sys::Node = dom_elem.clone().into();
167 *new.dom_ref.borrow_mut() = Some(node.clone());
168
169 patch_children(&old.children, &new.children, &node);
171
172 node
173}
174
175fn create_element_ns(tag: &str) -> web_sys::Element {
178 let doc = web_sys::window().unwrap().document().unwrap();
179 if tag == "svg" {
180 doc.create_element_ns(Some(SVG_NS), tag)
181 } else {
182 doc.create_element(tag)
183 }
184 .unwrap()
185}
186
187fn patch_attributes(old: &VElement, new: &VElement, el: &web_sys::Element) {
193 let old_map: std::collections::HashMap<&str, &str> =
195 old.attrs.iter().map(|(k, v)| (*k, v.as_str())).collect();
196 let new_map: std::collections::HashMap<&str, &str> =
197 new.attrs.iter().map(|(k, v)| (*k, v.as_str())).collect();
198
199 for (name, new_val) in &new.attrs {
201 match old_map.get(name) {
202 Some(old_val) if *old_val == new_val.as_str() => {
203 }
205 _ => {
206 let _ = el.set_attribute(name, new_val);
207 }
208 }
209 }
210
211 for (name, _) in &old.attrs {
213 if !new_map.contains_key(name) {
214 let _ = el.remove_attribute(name);
215 }
216 }
217}
218
219fn patch_event_listeners(old: &VElement, new: &VElement, el: &web_sys::Element) {
225 let old_events: std::collections::HashMap<&str, &EventHandler> =
227 old.events.iter().map(|(k, v)| (*k, v)).collect();
228 let new_events: std::collections::HashMap<&str, &EventHandler> =
229 new.events.iter().map(|(k, v)| (*k, v)).collect();
230
231 let mut closures_to_keep: Vec<(&'static str, Closure<dyn FnMut(web_sys::Event)>)> = Vec::new();
233
234 for (event_name, handler) in &old.events {
235 match new_events.get(event_name) {
236 Some(new_handler) => {
237 let changed = !Rc::ptr_eq(&handler.0, &new_handler.0);
240 if changed {
241 if let Some(old_closure) = find_and_remove_closure(&old, event_name) {
243 let js_func: &js_sys::Function = old_closure.as_ref().unchecked_ref();
244 let _ = el.remove_event_listener_with_callback(event_name, js_func);
245 }
247 let new_closure = create_and_add_listener(el, event_name, new_handler);
249 closures_to_keep.push((event_name, new_closure));
250 } else {
251 if let Some(old_closure) = take_closure(&old, event_name) {
253 closures_to_keep.push((event_name, old_closure));
254 } else {
255 let new_closure = create_and_add_listener(el, event_name, new_handler);
257 closures_to_keep.push((event_name, new_closure));
258 }
259 }
260 }
261 None => {
262 if let Some(old_closure) = find_and_remove_closure(&old, event_name) {
264 let js_func: &js_sys::Function = old_closure.as_ref().unchecked_ref();
265 let _ = el.remove_event_listener_with_callback(event_name, js_func);
266 }
268 }
269 }
270 }
271
272 for (event_name, handler) in &new.events {
274 if !old_events.contains_key(event_name) {
275 let new_closure = create_and_add_listener(el, event_name, handler);
276 closures_to_keep.push((event_name, new_closure));
277 }
278 }
279
280 *new.listener_closures.borrow_mut() = closures_to_keep;
282}
283
284fn take_closure(
286 el: &VElement,
287 event_name: &str,
288) -> Option<Closure<dyn FnMut(web_sys::Event)>> {
289 let mut closures = el.listener_closures.borrow_mut();
290 if let Some(pos) = closures.iter().position(|(name, _)| *name == event_name) {
291 let (_, closure) = closures.remove(pos);
292 Some(closure)
293 } else {
294 None
295 }
296}
297
298fn find_and_remove_closure(el: &VElement, event_name: &str) -> Option<Closure<dyn FnMut(web_sys::Event)>> {
300 take_closure(el, event_name)
301}
302
303fn create_and_add_listener(
305 el: &web_sys::Element,
306 event_name: &str,
307 handler: &EventHandler,
308) -> Closure<dyn FnMut(web_sys::Event)> {
309 let handler = handler.clone();
310 let closure = Closure::wrap(Box::new(move |event: web_sys::Event| {
311 handler.call(event);
312 }) as Box<dyn FnMut(web_sys::Event)>);
313
314 let js_func: &js_sys::Function = closure.as_ref().unchecked_ref();
315 let _ = el.add_event_listener_with_callback(event_name, js_func);
316
317 closure
318}