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
//! Diff virtual-doms and patch the real DOM

use crate::diff::diff;
use crate::event::VirtualEvents;
use crate::patch::patch;
use std::collections::HashMap;
use virtual_node::VirtualNode;
use wasm_bindgen::JsValue;
use web_sys::{Element, Node};

mod events;

/// Used for keeping a real DOM node up to date based on the current VirtualNode
/// and a new incoming VirtualNode that represents our latest DOM state.
///
/// Also powers event delegation.
pub struct PercyDom {
    current_vdom: VirtualNode,
    /// The closures that are currently attached to elements in the page.
    /// We keep these around so that they don't get dropped,  (and thus stop working).
    pub events: VirtualEvents,
    root_node: Node,
    // We hold onto these since if we drop the listener it can no longer be called.
    event_delegation_listeners: HashMap<&'static str, Box<dyn AsRef<JsValue>>>,
}

impl PercyDom {
    /// Create a new `PercyDom`.
    ///
    /// A root `Node` will be created but not added to your DOM.
    pub fn new(current_vdom: VirtualNode) -> PercyDom {
        let mut events = VirtualEvents::new();
        let (created_node, events_node) = current_vdom.create_dom_node(&mut events);
        events.set_root(events_node);

        let mut pdom = PercyDom {
            current_vdom,
            root_node: created_node,
            events,
            event_delegation_listeners: HashMap::new(),
        };
        pdom.attach_event_listeners();

        pdom
    }

    /// Create a new `PercyDom`.
    ///
    /// A root `Node` will be created and append (as a child) to your passed
    /// in mount element.
    pub fn new_append_to_mount(current_vdom: VirtualNode, mount: &Element) -> PercyDom {
        let pdom = Self::new(current_vdom);

        mount
            .append_child(&pdom.root_node)
            .expect("Could not append child to mount");

        pdom
    }

    /// Create a new `PercyDom`.
    ///
    /// A root `Node` will be created and it will replace your passed in mount
    /// element.
    pub fn new_replace_mount(current_vdom: VirtualNode, mount: Element) -> PercyDom {
        let pdom = Self::new(current_vdom);

        mount
            .replace_with_with_node_1(&pdom.root_node)
            .expect("Could not replace mount element");

        pdom
    }

    /// Diff the current virtual dom with the new virtual dom that is being passed in.
    ///
    /// Then use that diff to patch the real DOM in the user's browser so that they are
    /// seeing the latest state of the application.
    pub fn update(&mut self, new_vdom: VirtualNode) {
        let patches = diff(&self.current_vdom, &new_vdom);

        patch(
            self.root_node.clone(),
            &new_vdom,
            &mut self.events,
            &patches,
        )
        .unwrap();

        self.current_vdom = new_vdom;
    }

    /// Return the root node of your application, the highest ancestor of all other nodes in
    /// your real DOM tree.
    pub fn root_node(&self) -> Node {
        // Note that we're cloning the `web_sys::Node`, not the DOM element.
        // So we're effectively cloning a pointer here, which is fast.
        self.root_node.clone()
    }
}