mt_dom/
patch.rs

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