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}