use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::*;
use super::mount::mount_to_dom;
use super::children::patch_children;
use super::{EventHandler, VElement, VNode};
const SVG_NS: &str = "http://www.w3.org/2000/svg";
pub fn patch_node(
old: &VNode,
new: &VNode,
parent: &web_sys::Node,
anchor: Option<&web_sys::Node>,
) -> Option<web_sys::Node> {
if matches!(old, VNode::Empty) && matches!(new, VNode::Empty) {
return None;
}
if matches!(old, VNode::Empty) {
return mount_to_dom(new, parent, anchor);
}
if matches!(new, VNode::Empty) {
if let Some(old_node) = old.dom_node() {
let _ = parent.remove_child(&old_node);
}
return None;
}
if !old.same_type(new) {
return replace_node(old, new, parent, anchor);
}
match (old, new) {
(VNode::Element(old_el), VNode::Element(new_el)) => {
Some(patch_element(old_el, new_el, parent, anchor))
}
(VNode::Text(old_text), VNode::Text(new_text)) => {
patch_text_node(old_text, new_text, old)
}
(VNode::Fragment(old_children), VNode::Fragment(new_children)) => {
patch_children(old_children, new_children, parent);
new_children.first().and_then(|c| c.dom_node())
}
(VNode::Dynamic(_old_fn, _old_dom), VNode::Dynamic(new_fn, new_dom)) => {
let f = new_fn.borrow();
let new_inner = f();
drop(f);
if let Some(old_node) = old.dom_node() {
let _ = parent.remove_child(&old_node);
}
if let Some(node) = mount_to_dom(&new_inner, parent, anchor) {
*new_dom.borrow_mut() = Some(node.clone());
Some(node)
} else {
None
}
}
_ => {
replace_node(old, new, parent, anchor)
}
}
}
fn replace_node(
old: &VNode,
new: &VNode,
parent: &web_sys::Node,
anchor: Option<&web_sys::Node>,
) -> Option<web_sys::Node> {
if let Some(old_node) = old.dom_node() {
let _ = parent.remove_child(&old_node);
}
mount_to_dom(new, parent, anchor)
}
fn patch_text_node(old_text: &str, new_text: &str, old_vnode: &VNode) -> Option<web_sys::Node> {
if let Some(node) = old_vnode.dom_node() {
if old_text != new_text {
let _ = node.set_text_content(Some(new_text));
}
Some(node)
} else {
None
}
}
fn patch_element(
old: &VElement,
new: &VElement,
_parent: &web_sys::Node,
_anchor: Option<&web_sys::Node>,
) -> web_sys::Node {
let dom_elem: web_sys::Element = match *old.dom_ref.borrow() {
Some(ref node) => {
node.clone().dyn_into::<web_sys::Element>().unwrap_or_else(|_| {
create_element_ns(&new.tag)
})
}
None => {
create_element_ns(&new.tag)
}
};
patch_attributes(old, new, &dom_elem);
patch_event_listeners(old, new, &dom_elem);
let node: web_sys::Node = dom_elem.clone().into();
*new.dom_ref.borrow_mut() = Some(node.clone());
patch_children(&old.children, &new.children, &node);
node
}
fn create_element_ns(tag: &str) -> web_sys::Element {
let doc = web_sys::window().unwrap().document().unwrap();
if tag == "svg" {
doc.create_element_ns(Some(SVG_NS), tag)
} else {
doc.create_element(tag)
}
.unwrap()
}
fn patch_attributes(old: &VElement, new: &VElement, el: &web_sys::Element) {
let old_map: std::collections::HashMap<&str, &str> =
old.attrs.iter().map(|(k, v)| (*k, v.as_str())).collect();
let new_map: std::collections::HashMap<&str, &str> =
new.attrs.iter().map(|(k, v)| (*k, v.as_str())).collect();
for (name, new_val) in &new.attrs {
match old_map.get(name) {
Some(old_val) if *old_val == new_val.as_str() => {
}
_ => {
let _ = el.set_attribute(name, new_val);
}
}
}
for (name, _) in &old.attrs {
if !new_map.contains_key(name) {
let _ = el.remove_attribute(name);
}
}
}
fn patch_event_listeners(old: &VElement, new: &VElement, el: &web_sys::Element) {
let old_events: std::collections::HashMap<&str, &EventHandler> =
old.events.iter().map(|(k, v)| (*k, v)).collect();
let new_events: std::collections::HashMap<&str, &EventHandler> =
new.events.iter().map(|(k, v)| (*k, v)).collect();
let mut closures_to_keep: Vec<(&'static str, Closure<dyn FnMut(web_sys::Event)>)> = Vec::new();
for (event_name, handler) in &old.events {
match new_events.get(event_name) {
Some(new_handler) => {
let changed = !Rc::ptr_eq(&handler.0, &new_handler.0);
if changed {
if let Some(old_closure) = find_and_remove_closure(&old, event_name) {
let js_func: &js_sys::Function = old_closure.as_ref().unchecked_ref();
let _ = el.remove_event_listener_with_callback(event_name, js_func);
}
let new_closure = create_and_add_listener(el, event_name, new_handler);
closures_to_keep.push((event_name, new_closure));
} else {
if let Some(old_closure) = take_closure(&old, event_name) {
closures_to_keep.push((event_name, old_closure));
} else {
let new_closure = create_and_add_listener(el, event_name, new_handler);
closures_to_keep.push((event_name, new_closure));
}
}
}
None => {
if let Some(old_closure) = find_and_remove_closure(&old, event_name) {
let js_func: &js_sys::Function = old_closure.as_ref().unchecked_ref();
let _ = el.remove_event_listener_with_callback(event_name, js_func);
}
}
}
}
for (event_name, handler) in &new.events {
if !old_events.contains_key(event_name) {
let new_closure = create_and_add_listener(el, event_name, handler);
closures_to_keep.push((event_name, new_closure));
}
}
*new.listener_closures.borrow_mut() = closures_to_keep;
}
fn take_closure(
el: &VElement,
event_name: &str,
) -> Option<Closure<dyn FnMut(web_sys::Event)>> {
let mut closures = el.listener_closures.borrow_mut();
if let Some(pos) = closures.iter().position(|(name, _)| *name == event_name) {
let (_, closure) = closures.remove(pos);
Some(closure)
} else {
None
}
}
fn find_and_remove_closure(el: &VElement, event_name: &str) -> Option<Closure<dyn FnMut(web_sys::Event)>> {
take_closure(el, event_name)
}
fn create_and_add_listener(
el: &web_sys::Element,
event_name: &str,
handler: &EventHandler,
) -> Closure<dyn FnMut(web_sys::Event)> {
let handler = handler.clone();
let closure = Closure::wrap(Box::new(move |event: web_sys::Event| {
handler.call(event);
}) as Box<dyn FnMut(web_sys::Event)>);
let js_func: &js_sys::Function = closure.as_ref().unchecked_ref();
let _ = el.add_event_listener_with_callback(event_name, js_func);
closure
}