sauron_core/vdom/
patch.rs

1//! patch module
2
3use super::Tag;
4use super::{Attribute, Node};
5use std::borrow::Cow;
6
7use derive_where::derive_where;
8
9pub use tree_path::TreePath;
10
11mod tree_path;
12
13/// A Patch encodes an operation that modifies a real DOM element or native UI element
14///
15/// To update the real DOM that a user sees you'll want to first diff your
16/// old virtual dom and new virtual dom.
17///
18/// This diff operation will generate `Vec<Patch>` with zero or more patches that, when
19/// applied to your real DOM, will make your real DOM look like your new virtual dom.
20///
21/// Each of the Patch contains `TreePath` which contains an array of indexes for each node
22/// that we need to traverse to get the target element.
23///
24/// Consider the following html:
25///
26/// ```html
27/// <body>
28///     <main>
29///         <input type="text"/>
30///         <img src="pic.jpg"/>
31///     </main>
32///     <footer>
33///         <a>Link</a>
34///         <nav/>
35///     </footer>
36/// </body>
37/// ```
38/// The corresponding DOM tree would be
39/// ```bob
40///              .─.
41///             ( 0 )  <body>
42///              `-'
43///             /   \
44///            /     \
45///           /       \
46///          ▼         ▼
47///  <main> .─.         .─. <footer>
48///        ( 0 )       ( 1 )
49///         `-'         `-'
50///        /  \          | \ '.
51///       /    \         |  \  '.
52///      ▼      ▼        |   \   '.
53///    .─.      .─.      ▼    ▼     ▼
54///   ( 0 )    ( 1 )    .─.   .─.   .─.
55///    `─'      `─'    ( 0 ) ( 1 ) ( 2 )
56///  <input> <img>      `─'   `─'   `─'
57///                    <a>  <Text>   <nav>
58/// ```
59/// To traverse to the `<nav>` element we follow the TreePath([0,1,2]).
60/// 0 - is the root element which is always zero.
61/// 1 - is the `footer` element since it is the 2nd element of the body.
62/// 2 - is the `nav` element since it is the 3rd node in the `footer` element.
63#[derive_where(Clone, Debug, PartialEq, Eq)]
64pub struct Patch<'a, MSG> {
65    /// the tag of the node at patch_path
66    pub tag: Option<&'a Tag>,
67    /// the path to traverse to get to the target element
68    pub patch_path: TreePath,
69    /// the type of patch we are going to apply
70    pub patch_type: PatchType<'a, MSG>,
71}
72
73/// the patch variant
74#[derive_where(Clone, Debug, PartialEq, Eq)]
75pub enum PatchType<'a, MSG> {
76    /// insert the nodes before the node at patch_path
77    InsertBeforeNode {
78        /// the nodes to be inserted before patch_path
79        nodes: Vec<Cow<'a, Node<MSG>>>,
80    },
81
82    /// insert the nodes after the node at patch_path
83    InsertAfterNode {
84        /// the nodes to be inserted after the patch_path
85        nodes: Vec<&'a Node<MSG>>,
86    },
87
88    /// Append a vector of child nodes to a parent node id at patch_path
89    AppendChildren {
90        /// children nodes to be appended and their corresponding new_node_idx
91        children: Vec<&'a Node<MSG>>,
92    },
93    /// clear the chilren of this node,
94    ClearChildren,
95    /// remove the target node
96    RemoveNode,
97    /// remove the nodes pointed at these `nodes_path`
98    /// and move them before `target_element` pointed at `patch_path`
99    MoveBeforeNode {
100        /// before this target location
101        nodes_path: Vec<TreePath>,
102    },
103    /// remove the the nodes pointed at these nodes_path
104    /// and move them after the `target_element` pointed at `patch_path`
105    MoveAfterNode {
106        /// after this target location
107        nodes_path: Vec<TreePath>,
108    },
109
110    /// ReplaceNode a node with another node. This typically happens when a node's tag changes.
111    /// ex: <div> becomes <span>
112    ReplaceNode {
113        /// the node that will replace the target node
114        replacement: Vec<&'a Node<MSG>>,
115    },
116    /// Add attributes that the new node has that the old node does not
117    /// Note: the attributes is not a reference since attributes of same
118    /// name are merged to produce a new unify attribute
119    AddAttributes {
120        /// the attributes to be patched into the target node
121        attrs: Vec<&'a Attribute<MSG>>,
122    },
123    /// Remove attributes that the old node had that the new node doesn't
124    RemoveAttributes {
125        /// attributes that are to be removed from this target node
126        attrs: Vec<&'a Attribute<MSG>>,
127    },
128}
129
130impl<'a, MSG> Patch<'a, MSG> {
131    /// return the path to traverse for this patch to get to the target Node
132    pub fn path(&self) -> &TreePath {
133        &self.patch_path
134    }
135
136    /// return the node paths involve such as those in moving nodes
137    pub fn node_paths(&self) -> &[TreePath] {
138        match &self.patch_type {
139            PatchType::MoveBeforeNode { nodes_path } => nodes_path,
140            PatchType::MoveAfterNode { nodes_path } => nodes_path,
141            _ => &[],
142        }
143    }
144
145    /// return the tag of this patch
146    pub fn tag(&self) -> Option<&Tag> {
147        self.tag
148    }
149
150    /// create an InsertBeforeNode patch
151    pub fn insert_before_node(
152        tag: Option<&'a Tag>,
153        patch_path: TreePath,
154        nodes: impl IntoIterator<Item = &'a Node<MSG>>,
155    ) -> Patch<'a, MSG> {
156        Patch {
157            tag,
158            patch_path,
159            patch_type: PatchType::InsertBeforeNode {
160                nodes: nodes.into_iter().map(Cow::Borrowed).collect(),
161            },
162        }
163    }
164
165    /// create an InsertAfterNode patch
166    pub fn insert_after_node(
167        tag: Option<&'a Tag>,
168        patch_path: TreePath,
169        nodes: Vec<&'a Node<MSG>>,
170    ) -> Patch<'a, MSG> {
171        Patch {
172            tag,
173            patch_path,
174            patch_type: PatchType::InsertAfterNode { nodes },
175        }
176    }
177
178    /// create a patch where we add children to the target node
179    pub fn append_children(
180        tag: Option<&'a Tag>,
181        patch_path: TreePath,
182        children: Vec<&'a Node<MSG>>,
183    ) -> Patch<'a, MSG> {
184        Patch {
185            tag,
186            patch_path,
187            patch_type: PatchType::AppendChildren { children },
188        }
189    }
190
191    /// create a patch where the target element that can be traverse
192    /// using the patch path will be remove
193    pub fn remove_node(tag: Option<&'a Tag>, patch_path: TreePath) -> Patch<'a, MSG> {
194        Patch {
195            tag,
196            patch_path,
197            patch_type: PatchType::RemoveNode,
198        }
199    }
200
201    /// create a patch where the target element has to clear its children nodes
202    pub fn clear_children(tag: Option<&'a Tag>, patch_path: TreePath) -> Patch<'a, MSG> {
203        Patch {
204            tag,
205            patch_path,
206            patch_type: PatchType::ClearChildren,
207        }
208    }
209
210    /// remove the nodes pointed at the `nodes_path` and insert them before the target element
211    /// pointed at patch_path
212    pub fn move_before_node(
213        tag: Option<&'a Tag>,
214        patch_path: TreePath,
215        nodes_path: impl IntoIterator<Item = TreePath>,
216    ) -> Patch<'a, MSG> {
217        Patch {
218            tag,
219            patch_path,
220            patch_type: PatchType::MoveBeforeNode {
221                nodes_path: nodes_path.into_iter().collect(),
222            },
223        }
224    }
225
226    /// remove the nodes pointed at the `nodes_path` and insert them after the target element
227    /// pointed at patch_path
228    pub fn move_after_node(
229        tag: Option<&'a Tag>,
230        patch_path: TreePath,
231        nodes_path: impl IntoIterator<Item = TreePath>,
232    ) -> Patch<'a, MSG> {
233        Patch {
234            tag,
235            patch_path,
236            patch_type: PatchType::MoveAfterNode {
237                nodes_path: nodes_path.into_iter().collect(),
238            },
239        }
240    }
241
242    /// create a patch where a node is replaced by the `replacement` node.
243    /// The target node to be replace is traverse using the `patch_path`
244    pub fn replace_node(
245        tag: Option<&'a Tag>,
246        patch_path: TreePath,
247        replacement: impl IntoIterator<Item = &'a Node<MSG>>,
248    ) -> Patch<'a, MSG> {
249        Patch {
250            tag,
251            patch_path,
252            patch_type: PatchType::ReplaceNode {
253                replacement: replacement.into_iter().collect(),
254            },
255        }
256    }
257
258    /// create a patch where a new attribute is added to the target element
259    pub fn add_attributes(
260        tag: &'a Tag,
261        patch_path: TreePath,
262        attrs: impl IntoIterator<Item = &'a Attribute<MSG>>,
263    ) -> Patch<'a, MSG> {
264        Patch {
265            tag: Some(tag),
266            patch_path,
267            patch_type: PatchType::AddAttributes {
268                attrs: attrs.into_iter().collect(),
269            },
270        }
271    }
272
273    /// create patch where it remove attributes of the target element that can be traversed by the
274    /// patch_path.
275    pub fn remove_attributes(
276        tag: &'a Tag,
277        patch_path: TreePath,
278        attrs: Vec<&'a Attribute<MSG>>,
279    ) -> Patch<'a, MSG> {
280        Patch {
281            tag: Some(tag),
282            patch_path,
283            patch_type: PatchType::RemoveAttributes { attrs },
284        }
285    }
286
287    /// map the msg of this patch such that `Patch<MSG>` becomes `Patch<MSG2>`
288    pub fn map_msg<F, MSG2>(self, cb: F) -> Patch<'a, MSG2>
289    where
290        F: Fn(MSG) -> MSG2 + Clone + 'static,
291        MSG2: 'static,
292        MSG: 'static,
293    {
294        Patch {
295            tag: self.tag,
296            patch_path: self.patch_path,
297            patch_type: self.patch_type.map_msg(cb),
298        }
299    }
300}
301
302impl<'a, MSG> PatchType<'a, MSG> {
303    /// map the msg of this patch_type such that `PatchType<MSG>` becomes `PatchType<MSG2>`
304    pub fn map_msg<F, MSG2>(self, cb: F) -> PatchType<'a, MSG2>
305    where
306        F: Fn(MSG) -> MSG2 + Clone + 'static,
307        MSG2: 'static,
308        MSG: 'static,
309    {
310        match self {
311            Self::InsertBeforeNode { nodes } => PatchType::InsertBeforeNode {
312                nodes: nodes
313                    .into_iter()
314                    .map(|n| match n {
315                        Cow::Owned(n) => Cow::Owned(n.map_msg(cb.clone())),
316                        Cow::Borrowed(n) => Cow::Owned(n.clone().map_msg(cb.clone())),
317                    })
318                    .collect(),
319            },
320            _ => todo!(),
321        }
322    }
323}