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}