1#![allow(non_snake_case)]
7
8use std::collections::HashMap;
9use std::hash::Hash;
10use std::ops::Deref;
11
12use sycamore_macro::{component, Props};
13use wasm_bindgen::prelude::*;
14
15use crate::*;
16
17#[derive(Props)]
19pub struct KeyedProps<T, K, U, List, F, Key>
20where
21 List: Into<MaybeDyn<Vec<T>>> + 'static,
22 F: Fn(T) -> U + 'static,
23 Key: Fn(&T) -> K + 'static,
24 T: 'static,
25{
26 list: List,
27 view: F,
28 key: Key,
29 #[prop(default)]
30 _phantom: std::marker::PhantomData<(T, K, U)>,
31}
32
33#[component]
73pub fn Keyed<T, K, U, List, F, Key>(props: KeyedProps<T, K, U, List, F, Key>) -> View
74where
75 T: PartialEq + Clone + 'static,
76 K: Hash + Eq + 'static,
77 U: Into<View>,
78 List: Into<MaybeDyn<Vec<T>>> + 'static,
79 F: Fn(T) -> U + 'static,
80 Key: Fn(&T) -> K + 'static,
81{
82 let KeyedProps {
83 list, view, key, ..
84 } = props;
85
86 if is_ssr!() {
87 View::from(
89 list.into()
90 .evaluate()
91 .into_iter()
92 .map(|x| view(x).into())
93 .collect::<Vec<_>>(),
94 )
95 } else {
96 let start = HtmlNode::create_marker_node();
97 let start_node = start.as_web_sys().clone();
98 let end = HtmlNode::create_marker_node();
99 let end_node = end.as_web_sys().clone();
100
101 let scope = use_current_scope();
106 create_effect_initial(move || {
107 scope.run_in(move || {
108 let nodes = map_keyed(list, move |x| view(x).into().as_web_sys(), key);
109 let flattened = nodes.map(|x| x.iter().flatten().cloned().collect::<Vec<_>>());
111 let view = flattened.with(|x| {
112 View::from_nodes(
113 x.iter()
114 .map(|x| HtmlNode::from_web_sys(x.clone()))
115 .collect(),
116 )
117 });
118 (
119 Box::new(move || {
120 let mut new = flattened.get_clone();
122 let mut old = utils::get_nodes_between(&start_node, &end_node);
123 new.push(end_node.clone());
126 old.push(end_node.clone());
127
128 if let Some(parent) = start_node.parent_node() {
129 reconcile_fragments(&parent, &mut old, &new);
130 }
131 }) as Box<dyn FnMut()>,
132 (start, view, end).into(),
133 )
134 })
135 })
136 }
137}
138
139#[derive(Props)]
141pub struct IndexedProps<T, U, List, F>
142where
143 List: Into<MaybeDyn<Vec<T>>> + 'static,
144 F: Fn(T) -> U + 'static,
145 T: 'static,
146{
147 list: List,
148 view: F,
149 #[prop(default)]
150 _phantom: std::marker::PhantomData<(T, U)>,
151}
152
153#[component]
179pub fn Indexed<T, U, List, F>(props: IndexedProps<T, U, List, F>) -> View
180where
181 T: PartialEq + Clone + 'static,
182 U: Into<View>,
183 List: Into<MaybeDyn<Vec<T>>> + 'static,
184 F: Fn(T) -> U + 'static,
185{
186 let IndexedProps { list, view, .. } = props;
187
188 if is_ssr!() {
189 View::from(
191 list.into()
192 .evaluate()
193 .into_iter()
194 .map(|x| view(x).into())
195 .collect::<Vec<_>>(),
196 )
197 } else {
198 let start = HtmlNode::create_marker_node();
199 let start_node = start.as_web_sys().clone();
200 let end = HtmlNode::create_marker_node();
201 let end_node = end.as_web_sys().clone();
202
203 let scope = use_current_scope();
208 create_effect_initial(move || {
209 scope.run_in(move || {
210 let nodes = map_indexed(list, move |x| view(x).into().as_web_sys());
211 let flattened = nodes.map(|x| x.iter().flatten().cloned().collect::<Vec<_>>());
213 let view = flattened.with(|x| {
214 View::from_nodes(
215 x.iter()
216 .map(|x| HtmlNode::from_web_sys(x.clone()))
217 .collect(),
218 )
219 });
220 (
221 Box::new(move || {
222 let mut new = flattened.get_clone();
224 let mut old = utils::get_nodes_between(&start_node, &end_node);
225 new.push(end_node.clone());
228 old.push(end_node.clone());
229
230 if let Some(parent) = start_node.parent_node() {
231 reconcile_fragments(&parent, &mut old, &new);
232 }
233 }) as Box<dyn FnMut()>,
234 (start, view, end).into(),
235 )
236 })
237 })
238 }
239}
240
241#[wasm_bindgen]
242extern "C" {
243 #[wasm_bindgen(extends = web_sys::Node)]
246 pub(super) type NodeWithId;
247 #[wasm_bindgen(method, getter, js_name = "$id")]
248 pub fn node_id(this: &NodeWithId) -> Option<usize>;
249 #[wasm_bindgen(method, setter, js_name = "$id")]
250 pub fn set_node_id(this: &NodeWithId, id: usize);
251}
252
253struct HashableNode<'a>(&'a NodeWithId, usize);
255
256impl<'a> HashableNode<'a> {
257 thread_local! {
258 static NEXT_ID: Cell<usize> = const { Cell::new(0) };
259 }
260
261 fn new(node: &'a web_sys::Node) -> Self {
262 let node = node.unchecked_ref::<NodeWithId>();
263 let id = if let Some(id) = node.node_id() {
264 id
265 } else {
266 Self::NEXT_ID.with(|cell| {
267 let id = cell.get();
268 cell.set(id + 1);
269 node.set_node_id(id);
270 id
271 })
272 };
273 Self(node, id)
274 }
275}
276
277impl<'a> PartialEq for HashableNode<'a> {
278 fn eq(&self, other: &Self) -> bool {
279 self.1 == other.1
280 }
281}
282
283impl<'a> Eq for HashableNode<'a> {}
284
285impl<'a> Hash for HashableNode<'a> {
286 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
287 self.1.hash(state);
288 }
289}
290
291impl Deref for HashableNode<'_> {
292 type Target = NodeWithId;
293
294 fn deref(&self) -> &Self::Target {
295 self.0
296 }
297}
298
299fn reconcile_fragments(parent: &web_sys::Node, a: &mut [web_sys::Node], b: &[web_sys::Node]) {
310 debug_assert!(!a.is_empty(), "a cannot be empty");
311
312 #[cfg(debug_assertions)]
314 {
315 for (i, node) in a.iter().enumerate() {
316 if node.parent_node().as_ref() != Some(parent) {
317 panic!("node {i} in existing nodes Vec is not a child of parent. node = {node:#?}",);
318 }
319 }
320 }
321
322 let b_len = b.len();
323 let mut a_end = a.len();
324 let mut b_end = b_len;
325 let mut a_start = 0;
326 let mut b_start = 0;
327 let mut map = None::<HashMap<HashableNode, usize>>;
328
329 let after = a[a_end - 1].next_sibling();
331
332 while a_start < a_end || b_start < b_end {
333 if a_end == a_start {
334 let node = if b_end < b_len {
336 if b_start != 0 {
337 b[b_start - 1].next_sibling()
338 } else {
339 Some(b[b_end - b_start].clone())
340 }
341 } else {
342 after.clone()
343 };
344
345 for new_node in &b[b_start..b_end] {
346 parent.insert_before(new_node, node.as_ref()).unwrap();
347 }
348 b_start = b_end;
349 } else if b_end == b_start {
350 for node in &a[a_start..a_end] {
352 if map.is_none() || !map.as_ref().unwrap().contains_key(&HashableNode::new(node)) {
353 parent.remove_child(node).unwrap();
354 }
355 }
356 a_start = a_end;
357 } else if a[a_start] == b[b_start] {
358 a_start += 1;
360 b_start += 1;
361 } else if a[a_end - 1] == b[b_end - 1] {
362 a_end -= 1;
364 b_end -= 1;
365 } else if a[a_start] == b[b_end - 1] && b[b_start] == a[a_end - 1] {
366 let node = a[a_end - 1].next_sibling();
368 parent
369 .insert_before(&b[b_start], a[a_start].next_sibling().as_ref())
370 .unwrap();
371 parent.insert_before(&b[b_end - 1], node.as_ref()).unwrap();
372 a_start += 1;
373 b_start += 1;
374 a_end -= 1;
375 b_end -= 1;
376 a[a_end] = b[b_end].clone();
377 } else {
378 if map.is_none() {
380 let tmp = b[b_start..b_end]
381 .iter()
382 .enumerate()
383 .map(|(i, g)| (HashableNode::new(g), i))
384 .collect();
385 map = Some(tmp);
386 }
387 let map = map.as_ref().unwrap();
388
389 if let Some(&index) = map.get(&HashableNode::new(&a[a_start])) {
390 if b_start < index && index < b_end {
391 let mut i = a_start;
392 let mut sequence = 1;
393 let mut t;
394
395 while i + 1 < a_end && i + 1 < b_end {
396 i += 1;
397 t = map.get(&HashableNode::new(&a[i])).copied();
398 if t != Some(index + sequence) {
399 break;
400 }
401 sequence += 1;
402 }
403
404 if sequence > index - b_start {
405 let node = &a[a_start];
406 while b_start < index {
407 parent.insert_before(&b[b_start], Some(node)).unwrap();
408 b_start += 1;
409 }
410 } else {
411 parent.replace_child(&b[b_start], &a[a_start]).unwrap();
412 a_start += 1;
413 b_start += 1;
414 }
415 } else {
416 a_start += 1;
417 }
418 } else {
419 parent.remove_child(&a[a_start]).unwrap();
420 a_start += 1;
421 }
422 }
423 }
424
425 #[cfg(debug_assertions)]
427 {
428 for (i, node) in b.iter().enumerate() {
429 if node.parent_node().as_ref() != Some(parent) {
430 panic!(
431 "node {i} in new nodes Vec is not a child of parent after reconciliation. node = {node:#?}",
432 );
433 }
434 }
435 }
436}