Skip to main content

tachys/view/
keyed.rs

1use super::{
2    add_attr::AddAnyAttr, MarkBranch, Mountable, Position, PositionState,
3    Render, RenderHtml,
4};
5use crate::{
6    html::attribute::{any_attribute::AnyAttribute, Attribute},
7    hydration::Cursor,
8    renderer::{CastFrom, Rndr},
9    ssr::StreamBuilder,
10};
11use drain_filter_polyfill::VecExt as VecDrainFilterExt;
12use indexmap::IndexSet;
13use rustc_hash::FxHasher;
14use std::hash::{BuildHasherDefault, Hash};
15
16type FxIndexSet<T> = IndexSet<T, BuildHasherDefault<FxHasher>>;
17
18/// Creates a keyed list of views.
19pub fn keyed<T, I, K, KF, VF, VFS, V>(
20    items: I,
21    key_fn: KF,
22    view_fn: VF,
23) -> Keyed<T, I, K, KF, VF, VFS, V>
24where
25    I: IntoIterator<Item = T>,
26    K: Eq + Hash + SerializableKey + 'static,
27    KF: Fn(&T) -> K,
28    V: Render,
29    VF: Fn(usize, T) -> (VFS, V),
30    VFS: Fn(usize),
31{
32    Keyed {
33        #[cfg(not(feature = "ssr"))]
34        items: Some(items),
35        #[cfg(feature = "ssr")]
36        items: None,
37        #[cfg(feature = "ssr")]
38        ssr_items: items
39            .into_iter()
40            .enumerate()
41            .map(|(i, t)| {
42                let key = if cfg!(feature = "islands") {
43                    let key = (key_fn)(&t);
44                    key.ser_key()
45                } else {
46                    String::new()
47                };
48                let (_, view) = (view_fn)(i, t);
49                (key, view)
50            })
51            .collect::<Vec<_>>(),
52        key_fn,
53        view_fn,
54    }
55}
56
57/// A keyed list of views.
58pub struct Keyed<T, I, K, KF, VF, VFS, V>
59where
60    I: IntoIterator<Item = T>,
61    K: Eq + Hash + 'static,
62    KF: Fn(&T) -> K,
63    VF: Fn(usize, T) -> (VFS, V),
64    VFS: Fn(usize),
65{
66    items: Option<I>,
67    #[cfg(feature = "ssr")]
68    ssr_items: Vec<(String, V)>,
69    key_fn: KF,
70    view_fn: VF,
71}
72
73/// By default, keys used in for keyed iteration do not need to be serializable.
74///
75/// However, for some scenarios (like the “islands routing” mode that mixes server-side
76/// rendering with client-side navigation) it is useful to have serializable keys.
77///
78/// When the `islands` feature is not enabled, this trait is implemented by all types.
79///
80/// When the `islands` features is enabled, this is automatically implemented for all types
81/// that implement [`Serialize`](serde::Serialize), and can be manually implemented otherwise.
82pub trait SerializableKey {
83    /// Serializes the key to a unique string.
84    ///
85    /// The string can have any value, as long as it is idempotent (i.e., serializing the same key
86    /// multiple times will give the same value).
87    fn ser_key(&self) -> String;
88}
89
90#[cfg(not(feature = "islands"))]
91impl<T> SerializableKey for T {
92    fn ser_key(&self) -> String {
93        panic!(
94            "SerializableKey called without the `islands` feature enabled. \
95             Something has gone wrong."
96        );
97    }
98}
99#[cfg(feature = "islands")]
100impl<T: serde::Serialize> SerializableKey for T {
101    fn ser_key(&self) -> String {
102        serde_json::to_string(self).expect("failed to serialize key")
103    }
104}
105
106/// Retained view state for a keyed list.
107pub struct KeyedState<K, VFS, V>
108where
109    K: Eq + Hash + 'static,
110    VFS: Fn(usize),
111    V: Render,
112{
113    parent: Option<crate::renderer::types::Element>,
114    marker: crate::renderer::types::Placeholder,
115    hashed_items: IndexSet<K, BuildHasherDefault<FxHasher>>,
116    rendered_items: Vec<Option<(VFS, V::State)>>,
117}
118
119impl<T, I, K, KF, VF, VFS, V> Render for Keyed<T, I, K, KF, VF, VFS, V>
120where
121    I: IntoIterator<Item = T>,
122    K: Eq + Hash + SerializableKey + 'static,
123    KF: Fn(&T) -> K,
124    V: Render,
125    VF: Fn(usize, T) -> (VFS, V),
126    VFS: Fn(usize),
127{
128    type State = KeyedState<K, VFS, V>;
129
130    fn build(self) -> Self::State {
131        let items = self.items.into_iter().flatten();
132        let (capacity, _) = items.size_hint();
133        let mut hashed_items =
134            FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
135        let mut rendered_items = Vec::with_capacity(capacity);
136        for (index, item) in items.enumerate() {
137            hashed_items.insert((self.key_fn)(&item));
138            let (set_index, view) = (self.view_fn)(index, item);
139            rendered_items.push(Some((set_index, view.build())));
140        }
141        KeyedState {
142            parent: None,
143            marker: Rndr::create_placeholder(),
144            hashed_items,
145            rendered_items,
146        }
147    }
148
149    fn rebuild(self, state: &mut Self::State) {
150        let KeyedState {
151            parent,
152            marker,
153            hashed_items,
154            ref mut rendered_items,
155        } = state;
156        let new_items = self.items.into_iter().flatten();
157        let (capacity, _) = new_items.size_hint();
158        let mut new_hashed_items =
159            FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
160
161        let mut items = Vec::new();
162        for item in new_items {
163            new_hashed_items.insert((self.key_fn)(&item));
164            items.push(Some(item));
165        }
166
167        let cmds = diff(hashed_items, &new_hashed_items);
168
169        apply_diff(
170            parent.as_ref(),
171            marker,
172            cmds,
173            rendered_items,
174            &self.view_fn,
175            items,
176        );
177
178        *hashed_items = new_hashed_items;
179    }
180}
181
182impl<T, I, K, KF, VF, VFS, V> AddAnyAttr for Keyed<T, I, K, KF, VF, VFS, V>
183where
184    I: IntoIterator<Item = T> + Send + 'static,
185    K: Eq + Hash + SerializableKey + 'static,
186    KF: Fn(&T) -> K + Send + 'static,
187    V: RenderHtml,
188    V: 'static,
189    VF: Fn(usize, T) -> (VFS, V) + Send + 'static,
190    VFS: Fn(usize) + 'static,
191    T: 'static,
192{
193    type Output<SomeNewAttr: Attribute> = Keyed<
194        T,
195        I,
196        K,
197        KF,
198        Box<
199            dyn Fn(
200                    usize,
201                    T,
202                ) -> (
203                    VFS,
204                    <V as AddAnyAttr>::Output<SomeNewAttr::CloneableOwned>,
205                ) + Send,
206        >,
207        VFS,
208        V::Output<SomeNewAttr::CloneableOwned>,
209    >;
210
211    fn add_any_attr<NewAttr: Attribute>(
212        self,
213        attr: NewAttr,
214    ) -> Self::Output<NewAttr>
215    where
216        Self::Output<NewAttr>: RenderHtml,
217    {
218        let Keyed {
219            items,
220            #[cfg(feature = "ssr")]
221            ssr_items,
222            key_fn,
223            view_fn,
224        } = self;
225        let attr = attr.into_cloneable_owned();
226        Keyed {
227            items,
228            key_fn,
229            #[cfg(feature = "ssr")]
230            ssr_items: ssr_items
231                .into_iter()
232                .map(|(k, v)| (k, v.add_any_attr(attr.clone())))
233                .collect(),
234            view_fn: Box::new(move |index, item| {
235                let (index, view) = view_fn(index, item);
236                (index, view.add_any_attr(attr.clone()))
237            }),
238        }
239    }
240}
241
242impl<T, I, K, KF, VF, VFS, V> RenderHtml for Keyed<T, I, K, KF, VF, VFS, V>
243where
244    I: IntoIterator<Item = T> + Send + 'static,
245    K: Eq + Hash + SerializableKey + 'static,
246    KF: Fn(&T) -> K + Send + 'static,
247    V: RenderHtml + 'static,
248    VF: Fn(usize, T) -> (VFS, V) + Send + 'static,
249    VFS: Fn(usize) + 'static,
250    T: 'static,
251{
252    type AsyncOutput = Vec<V::AsyncOutput>; // TODO
253    type Owned = Self;
254
255    const MIN_LENGTH: usize = 0;
256
257    fn dry_resolve(&mut self) {
258        #[cfg(feature = "ssr")]
259        for view in &mut self.ssr_items {
260            view.dry_resolve();
261        }
262    }
263
264    async fn resolve(self) -> Self::AsyncOutput {
265        #[cfg(feature = "ssr")]
266        {
267            futures::future::join_all(
268                self.ssr_items.into_iter().map(|(_, view)| view.resolve()),
269            )
270            .await
271            .into_iter()
272            .collect::<Vec<_>>()
273        }
274        #[cfg(not(feature = "ssr"))]
275        {
276            futures::future::join_all(
277                self.items.into_iter().flatten().enumerate().map(
278                    |(index, item)| {
279                        let (_, view) = (self.view_fn)(index, item);
280                        view.resolve()
281                    },
282                ),
283            )
284            .await
285            .into_iter()
286            .collect::<Vec<_>>()
287        }
288    }
289
290    #[allow(unused)]
291    fn to_html_with_buf(
292        self,
293        buf: &mut String,
294        position: &mut Position,
295        escape: bool,
296        mark_branches: bool,
297        extra_attrs: Vec<AnyAttribute>,
298    ) {
299        if mark_branches && escape {
300            buf.open_branch("for");
301        }
302
303        #[cfg(feature = "ssr")]
304        for item in self.ssr_items {
305            if mark_branches && escape {
306                buf.open_branch("item");
307            }
308            item.to_html_with_buf(
309                buf,
310                position,
311                escape,
312                mark_branches,
313                extra_attrs.clone(),
314            );
315            if mark_branches && escape {
316                buf.close_branch("item");
317            }
318            *position = Position::NextChild;
319        }
320        if mark_branches && escape {
321            buf.close_branch("for");
322        }
323        buf.push_str("<!>");
324    }
325
326    #[allow(unused)]
327    fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
328        self,
329        buf: &mut StreamBuilder,
330        position: &mut Position,
331        escape: bool,
332        mark_branches: bool,
333        extra_attrs: Vec<AnyAttribute>,
334    ) {
335        if mark_branches && escape {
336            buf.open_branch("for");
337        }
338
339        #[cfg(feature = "ssr")]
340        for (key, item) in self.ssr_items {
341            let branch_name = mark_branches.then(|| format!("item-{key}"));
342            if mark_branches && escape {
343                buf.open_branch(branch_name.as_ref().unwrap());
344            }
345            item.to_html_async_with_buf::<OUT_OF_ORDER>(
346                buf,
347                position,
348                escape,
349                mark_branches,
350                extra_attrs.clone(),
351            );
352            if mark_branches && escape {
353                buf.close_branch(branch_name.as_ref().unwrap());
354            }
355            *position = Position::NextChild;
356        }
357
358        if mark_branches && escape {
359            buf.close_branch("for");
360        }
361        buf.push_sync("<!>");
362    }
363
364    fn hydrate<const FROM_SERVER: bool>(
365        self,
366        cursor: &Cursor,
367        position: &PositionState,
368    ) -> Self::State {
369        // get parent and position
370        let current = cursor.current();
371        let parent = if position.get() == Position::FirstChild {
372            current
373        } else {
374            Rndr::get_parent(&current)
375                .expect("first child of keyed list has no parent")
376        };
377        let parent = crate::renderer::types::Element::cast_from(parent)
378            .expect("parent of keyed list should be an element");
379
380        // build list
381        let items = self.items.into_iter().flatten();
382        let (capacity, _) = items.size_hint();
383        let mut hashed_items =
384            FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
385        let mut rendered_items = Vec::with_capacity(capacity);
386        for (index, item) in items.enumerate() {
387            hashed_items.insert((self.key_fn)(&item));
388            let (set_index, view) = (self.view_fn)(index, item);
389            let item = view.hydrate::<FROM_SERVER>(cursor, position);
390            rendered_items.push(Some((set_index, item)));
391        }
392        let marker = cursor.next_placeholder(position);
393        position.set(Position::NextChild);
394
395        KeyedState {
396            parent: Some(parent),
397            marker,
398            hashed_items,
399            rendered_items,
400        }
401    }
402
403    async fn hydrate_async(
404        self,
405        cursor: &Cursor,
406        position: &PositionState,
407    ) -> Self::State {
408        // get parent and position
409        let current = cursor.current();
410        let parent = if position.get() == Position::FirstChild {
411            current
412        } else {
413            Rndr::get_parent(&current)
414                .expect("first child of keyed list has no parent")
415        };
416        let parent = crate::renderer::types::Element::cast_from(parent)
417            .expect("parent of keyed list should be an element");
418
419        // build list
420        let items = self.items.into_iter().flatten();
421        let (capacity, _) = items.size_hint();
422        let mut hashed_items =
423            FxIndexSet::with_capacity_and_hasher(capacity, Default::default());
424        let mut rendered_items = Vec::with_capacity(capacity);
425        for (index, item) in items.enumerate() {
426            hashed_items.insert((self.key_fn)(&item));
427            let (set_index, view) = (self.view_fn)(index, item);
428            let item = view.hydrate_async(cursor, position).await;
429            rendered_items.push(Some((set_index, item)));
430        }
431        let marker = cursor.next_placeholder(position);
432        position.set(Position::NextChild);
433
434        KeyedState {
435            parent: Some(parent),
436            marker,
437            hashed_items,
438            rendered_items,
439        }
440    }
441
442    fn into_owned(self) -> Self::Owned {
443        self
444    }
445}
446
447impl<K, VFS, V> Mountable for KeyedState<K, VFS, V>
448where
449    K: Eq + Hash + 'static,
450    VFS: Fn(usize),
451    V: Render,
452{
453    fn mount(
454        &mut self,
455        parent: &crate::renderer::types::Element,
456        marker: Option<&crate::renderer::types::Node>,
457    ) {
458        self.parent = Some(parent.clone());
459        for (_, item) in self.rendered_items.iter_mut().flatten() {
460            item.mount(parent, marker);
461        }
462        self.marker.mount(parent, marker);
463    }
464
465    fn unmount(&mut self) {
466        for (_, item) in self.rendered_items.iter_mut().flatten() {
467            item.unmount();
468        }
469        self.marker.unmount();
470    }
471
472    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
473        self.rendered_items
474            .first()
475            .map(|item| {
476                if let Some((_, item)) = item {
477                    item.insert_before_this(child)
478                } else {
479                    false
480                }
481            })
482            .unwrap_or_else(|| self.marker.insert_before_this(child))
483    }
484
485    fn elements(&self) -> Vec<crate::renderer::types::Element> {
486        self.rendered_items
487            .iter()
488            .flatten()
489            .flat_map(|item| item.1.elements())
490            .collect()
491    }
492}
493
494trait VecExt<T> {
495    fn get_next_closest_mounted_sibling(
496        &self,
497        start_at: usize,
498    ) -> Option<&Option<T>>;
499}
500
501impl<T> VecExt<T> for Vec<Option<T>> {
502    fn get_next_closest_mounted_sibling(
503        &self,
504        start_at: usize,
505    ) -> Option<&Option<T>> {
506        self[start_at..].iter().find(|s| s.is_some())
507    }
508}
509
510/// Calculates the operations needed to get from `from` to `to`.
511fn diff<K: Eq + Hash>(from: &FxIndexSet<K>, to: &FxIndexSet<K>) -> Diff {
512    if from.is_empty() && to.is_empty() {
513        return Diff::default();
514    } else if to.is_empty() {
515        return Diff {
516            clear: true,
517            ..Default::default()
518        };
519    } else if from.is_empty() {
520        return Diff {
521            added: to
522                .iter()
523                .enumerate()
524                .map(|(at, _)| DiffOpAdd {
525                    at,
526                    mode: DiffOpAddMode::Append,
527                })
528                .collect(),
529            ..Default::default()
530        };
531    }
532
533    let mut removed = vec![];
534    let mut moved = vec![];
535    let mut added = vec![];
536    let max_len = std::cmp::max(from.len(), to.len());
537
538    for index in 0..max_len {
539        let from_item = from.get_index(index);
540        let to_item = to.get_index(index);
541
542        // if they're the same, do nothing
543        if from_item != to_item {
544            // if it's only in old, not new, remove it
545            if from_item.is_some() && !to.contains(from_item.unwrap()) {
546                let op = DiffOpRemove { at: index };
547                removed.push(op);
548            }
549            // if it's only in new, not old, add it
550            if to_item.is_some() && !from.contains(to_item.unwrap()) {
551                let op = DiffOpAdd {
552                    at: index,
553                    mode: DiffOpAddMode::Normal,
554                };
555                added.push(op);
556            }
557            // if it's in both old and new, it can either
558            // 1) be moved (and need to move in the DOM)
559            // 2) be moved (but not need to move in the DOM)
560            //    * this would happen if, for example, 2 items
561            //      have been added before it, and it has moved by 2
562            if let Some(from_item) = from_item {
563                if let Some(to_item) = to.get_full(from_item) {
564                    let moves_forward_by = (to_item.0 as i32) - (index as i32);
565                    let move_in_dom = moves_forward_by
566                        != (added.len() as i32) - (removed.len() as i32);
567
568                    let op = DiffOpMove {
569                        from: index,
570                        len: 1,
571                        to: to_item.0,
572                        move_in_dom,
573                    };
574                    moved.push(op);
575                }
576            }
577        }
578    }
579
580    moved = group_adjacent_moves(moved);
581
582    Diff {
583        removed,
584        items_to_move: moved.iter().map(|m| m.len).sum(),
585        moved,
586        added,
587        clear: false,
588    }
589}
590
591/// Group adjacent items that are being moved as a group.
592/// For example from `[2, 3, 5, 6]` to `[1, 2, 3, 4, 5, 6]` should result
593/// in a move for `2,3` and `5,6` rather than 4 individual moves.
594fn group_adjacent_moves(moved: Vec<DiffOpMove>) -> Vec<DiffOpMove> {
595    let mut prev: Option<DiffOpMove> = None;
596    let mut new_moved = Vec::with_capacity(moved.len());
597    for m in moved {
598        match prev {
599            Some(mut p) => {
600                if (m.from == p.from + p.len) && (m.to == p.to + p.len) {
601                    p.len += 1;
602                    prev = Some(p);
603                } else {
604                    new_moved.push(prev.take().unwrap());
605                    prev = Some(m);
606                }
607            }
608            None => prev = Some(m),
609        }
610    }
611    if let Some(prev) = prev {
612        new_moved.push(prev)
613    }
614    new_moved
615}
616
617#[derive(Debug, Default, PartialEq, Eq)]
618struct Diff {
619    removed: Vec<DiffOpRemove>,
620    moved: Vec<DiffOpMove>,
621    items_to_move: usize,
622    added: Vec<DiffOpAdd>,
623    clear: bool,
624}
625
626#[derive(Clone, Copy, Debug, PartialEq, Eq)]
627struct DiffOpMove {
628    /// The index this range is starting relative to `from`.
629    from: usize,
630    /// The number of elements included in this range.
631    len: usize,
632    /// The starting index this range will be moved to relative to `to`.
633    to: usize,
634    /// Marks this move to be applied to the DOM, or just to the underlying
635    /// storage
636    move_in_dom: bool,
637}
638
639impl Default for DiffOpMove {
640    fn default() -> Self {
641        Self {
642            from: 0,
643            to: 0,
644            len: 1,
645            move_in_dom: true,
646        }
647    }
648}
649
650#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
651struct DiffOpAdd {
652    at: usize,
653    mode: DiffOpAddMode,
654}
655
656#[derive(Debug, PartialEq, Eq)]
657struct DiffOpRemove {
658    at: usize,
659}
660
661#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
662enum DiffOpAddMode {
663    #[default]
664    Normal,
665    Append,
666}
667
668fn apply_diff<T, VFS, V>(
669    parent: Option<&crate::renderer::types::Element>,
670    marker: &crate::renderer::types::Placeholder,
671    diff: Diff,
672    children: &mut Vec<Option<(VFS, V::State)>>,
673    view_fn: &dyn Fn(usize, T) -> (VFS, V),
674    mut items: Vec<Option<T>>,
675) where
676    VFS: Fn(usize),
677    V: Render,
678{
679    // The order of cmds needs to be:
680    // 1. Clear
681    // 2. Removals
682    // 3. Move out
683    // 4. Resize
684    // 5. Move in
685    // 6. Additions
686    // 7. Removes holes
687    if diff.clear {
688        for (_, mut child) in children.drain(0..).flatten() {
689            child.unmount();
690        }
691
692        if diff.added.is_empty() {
693            return;
694        }
695    }
696
697    for DiffOpRemove { at } in &diff.removed {
698        let (_, mut item_to_remove) = children[*at].take().unwrap();
699
700        item_to_remove.unmount();
701    }
702
703    let (move_cmds, add_cmds) = unpack_moves(&diff);
704
705    let mut moved_children = move_cmds
706        .iter()
707        .map(|move_| children[move_.from].take())
708        .collect::<Vec<_>>();
709
710    children.resize_with(children.len() + diff.added.len(), || None);
711
712    for (i, DiffOpMove { to, .. }) in move_cmds
713        .iter()
714        .enumerate()
715        .filter(|(_, move_)| !move_.move_in_dom)
716    {
717        children[*to] = moved_children[i]
718            .take()
719            .inspect(|(set_index, _)| set_index(*to));
720    }
721
722    for (i, DiffOpMove { to, .. }) in move_cmds
723        .into_iter()
724        .enumerate()
725        .filter(|(_, move_)| move_.move_in_dom)
726    {
727        let (set_index, mut each_item) = moved_children[i].take().unwrap();
728
729        if let Some(parent) = parent {
730            if let Some(Some((_, state))) =
731                children.get_next_closest_mounted_sibling(to)
732            {
733                state.insert_before_this_or_marker(
734                    parent,
735                    &mut each_item,
736                    Some(marker.as_ref()),
737                )
738            } else {
739                each_item.try_mount(parent, Some(marker.as_ref()));
740            }
741        }
742
743        set_index(to);
744        children[to] = Some((set_index, each_item));
745    }
746
747    for DiffOpAdd { at, mode } in add_cmds {
748        let item = items[at].take().unwrap();
749        let (set_index, item) = view_fn(at, item);
750        let mut item = item.build();
751
752        if let Some(parent) = parent {
753            match mode {
754                DiffOpAddMode::Normal => {
755                    if let Some(Some((_, state))) =
756                        children.get_next_closest_mounted_sibling(at)
757                    {
758                        state.insert_before_this_or_marker(
759                            parent,
760                            &mut item,
761                            Some(marker.as_ref()),
762                        )
763                    } else {
764                        item.try_mount(parent, Some(marker.as_ref()));
765                    }
766                }
767                DiffOpAddMode::Append => {
768                    item.try_mount(parent, Some(marker.as_ref()));
769                }
770            }
771        }
772
773        children[at] = Some((set_index, item));
774    }
775
776    #[allow(unstable_name_collisions)]
777    children.drain_filter(|c| c.is_none());
778}
779
780fn unpack_moves(diff: &Diff) -> (Vec<DiffOpMove>, Vec<DiffOpAdd>) {
781    let mut moves = Vec::with_capacity(diff.items_to_move);
782    let mut adds = Vec::with_capacity(diff.added.len());
783
784    let mut removes_iter = diff.removed.iter();
785    let mut adds_iter = diff.added.iter();
786    let mut moves_iter = diff.moved.iter();
787
788    let mut removes_next = removes_iter.next();
789    let mut adds_next = adds_iter.next();
790    let mut moves_next = moves_iter.next().copied();
791
792    for i in 0..diff.items_to_move + diff.added.len() + diff.removed.len() {
793        if let Some(DiffOpRemove { at, .. }) = removes_next {
794            if i == *at {
795                removes_next = removes_iter.next();
796
797                continue;
798            }
799        }
800
801        match (adds_next, &mut moves_next) {
802            (Some(add), Some(move_)) => {
803                if add.at == i {
804                    adds.push(*add);
805
806                    adds_next = adds_iter.next();
807                } else {
808                    let mut single_move = *move_;
809                    single_move.len = 1;
810
811                    moves.push(single_move);
812
813                    move_.len -= 1;
814                    move_.from += 1;
815                    move_.to += 1;
816
817                    if move_.len == 0 {
818                        moves_next = moves_iter.next().copied();
819                    }
820                }
821            }
822            (Some(add), None) => {
823                adds.push(*add);
824
825                adds_next = adds_iter.next();
826            }
827            (None, Some(move_)) => {
828                let mut single_move = *move_;
829                single_move.len = 1;
830
831                moves.push(single_move);
832
833                move_.len -= 1;
834                move_.from += 1;
835                move_.to += 1;
836
837                if move_.len == 0 {
838                    moves_next = moves_iter.next().copied();
839                }
840            }
841            (None, None) => break,
842        }
843    }
844
845    (moves, adds)
846}
847/*
848#[cfg(test)]
849mod tests {
850    use crate::{
851        html::element::{li, ul, HtmlElement, Li},
852        renderer::mock_dom::MockDom,
853        view::{keyed::keyed, Render},
854    };
855
856    fn item(key: usize) -> HtmlElement<Li, (), String, MockDom> {
857        li((), key.to_string())
858    }
859
860    #[test]
861    fn keyed_creates_list() {
862        let el = ul((), keyed(1..=3, |k| *k, item));
863        let el_state = el.build();
864        assert_eq!(
865            el_state.el.to_debug_html(),
866            "<ul><li>1</li><li>2</li><li>3</li></ul>"
867        );
868    }
869
870    #[test]
871    fn adding_items_updates_list() {
872        let el = ul((), keyed(1..=3, |k| *k, item));
873        let mut el_state = el.build();
874        let el = ul((), keyed(1..=5, |k| *k, item));
875        el.rebuild(&mut el_state);
876        assert_eq!(
877            el_state.el.to_debug_html(),
878            "<ul><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>"
879        );
880    }
881
882    #[test]
883    fn removing_items_updates_list() {
884        let el = ul((), keyed(1..=3, |k| *k, item));
885        let mut el_state = el.build();
886        let el = ul((), keyed(1..=2, |k| *k, item));
887        el.rebuild(&mut el_state);
888        assert_eq!(
889            el_state.el.to_debug_html(),
890            "<ul><li>1</li><li>2</li></ul>"
891        );
892    }
893
894    #[test]
895    fn swapping_items_updates_list() {
896        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
897        let mut el_state = el.build();
898        let el = ul((), keyed([1, 4, 3, 2, 5], |k| *k, item));
899        el.rebuild(&mut el_state);
900        assert_eq!(
901            el_state.el.to_debug_html(),
902            "<ul><li>1</li><li>4</li><li>3</li><li>2</li><li>5</li></ul>"
903        );
904    }
905
906    #[test]
907    fn swapping_and_removing_orders_correctly() {
908        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
909        let mut el_state = el.build();
910        let el = ul((), keyed([1, 4, 3, 5], |k| *k, item));
911        el.rebuild(&mut el_state);
912        assert_eq!(
913            el_state.el.to_debug_html(),
914            "<ul><li>1</li><li>4</li><li>3</li><li>5</li></ul>"
915        );
916    }
917
918    #[test]
919    fn arbitrarily_hard_adjustment() {
920        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
921        let mut el_state = el.build();
922        let el = ul((), keyed([2, 4, 3], |k| *k, item));
923        el.rebuild(&mut el_state);
924        assert_eq!(
925            el_state.el.to_debug_html(),
926            "<ul><li>2</li><li>4</li><li>3</li></ul>"
927        );
928    }
929
930    #[test]
931    fn a_series_of_moves() {
932        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
933        let mut el_state = el.build();
934        let el = ul((), keyed([2, 4, 3], |k| *k, item));
935        el.rebuild(&mut el_state);
936        let el = ul((), keyed([1, 7, 5, 11, 13, 17], |k| *k, item));
937        el.rebuild(&mut el_state);
938        let el = ul((), keyed([2, 6, 8, 7, 13], |k| *k, item));
939        el.rebuild(&mut el_state);
940        let el = ul((), keyed([13, 4, 5, 3], |k| *k, item));
941        el.rebuild(&mut el_state);
942        let el = ul((), keyed([1, 2, 3, 4], |k| *k, item));
943        el.rebuild(&mut el_state);
944        assert_eq!(
945            el_state.el.to_debug_html(),
946            "<ul><li>1</li><li>2</li><li>3</li><li>4</li></ul>"
947        );
948    }
949
950    #[test]
951    fn clearing_works() {
952        let el = ul((), keyed([1, 2, 3, 4, 5], |k| *k, item));
953        let mut el_state = el.build();
954        let el = ul((), keyed([], |k| *k, item));
955        el.rebuild(&mut el_state);
956        assert_eq!(el_state.el.to_debug_html(), "<ul></ul>");
957    }
958}
959*/