This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/dom_bundle/
fragment.rs

1use std::collections::VecDeque;
2use std::ops::{Deref, DerefMut};
3
4use wasm_bindgen::JsCast;
5use web_sys::{Element, Node};
6
7use super::{BSubtree, DomSlot};
8use crate::virtual_dom::Collectable;
9
10/// A Hydration Fragment
11#[derive(Default, Debug, Clone, PartialEq, Eq)]
12pub(crate) struct Fragment(VecDeque<Node>, Option<Node>);
13
14impl Deref for Fragment {
15    type Target = VecDeque<Node>;
16
17    fn deref(&self) -> &Self::Target {
18        &self.0
19    }
20}
21
22impl DerefMut for Fragment {
23    fn deref_mut(&mut self) -> &mut Self::Target {
24        &mut self.0
25    }
26}
27
28impl Fragment {
29    /// Collects child nodes of an element into a VecDeque.
30    pub fn collect_children(parent: &Element) -> Self {
31        let mut fragment = VecDeque::with_capacity(parent.child_nodes().length() as usize);
32
33        let mut current_node = parent.first_child();
34
35        // This is easier than iterating child nodes at the moment
36        // as we don't have to downcast iterator values.
37        while let Some(m) = current_node {
38            current_node = m.next_sibling();
39            fragment.push_back(m);
40        }
41
42        Self(fragment, None)
43    }
44
45    /// Collects nodes for a Component Bundle or a BSuspense.
46    pub fn collect_between(
47        collect_from: &mut Fragment,
48        collect_for: &Collectable,
49        parent: &Element,
50    ) -> Self {
51        let is_open_tag = |node: &Node| {
52            let comment_text = node.text_content().unwrap_or_default();
53
54            comment_text.starts_with(collect_for.open_start_mark())
55                && comment_text.ends_with(collect_for.end_mark())
56        };
57
58        let is_close_tag = |node: &Node| {
59            let comment_text = node.text_content().unwrap_or_default();
60
61            comment_text.starts_with(collect_for.close_start_mark())
62                && comment_text.ends_with(collect_for.end_mark())
63        };
64
65        // We trim all leading text nodes as it's likely these are whitespaces.
66        collect_from.trim_start_text_nodes();
67
68        let first_node = collect_from
69            .pop_front()
70            .unwrap_or_else(|| panic!("expected {} opening tag, found EOF", collect_for.name()));
71
72        assert_eq!(
73            first_node.node_type(),
74            Node::COMMENT_NODE,
75            // TODO: improve error message with human readable node type name.
76            "expected {} start, found node type {}",
77            collect_for.name(),
78            first_node.node_type()
79        );
80
81        let mut nodes = VecDeque::new();
82
83        if !is_open_tag(&first_node) {
84            panic!(
85                "expected {} opening tag, found comment node",
86                collect_for.name()
87            );
88        }
89
90        // We remove the opening tag.
91        parent.remove_child(&first_node).unwrap();
92
93        let mut nested_layers = 1;
94
95        loop {
96            let current_node = collect_from.pop_front().unwrap_or_else(|| {
97                panic!("expected {} closing tag, found EOF", collect_for.name())
98            });
99
100            if current_node.node_type() == Node::COMMENT_NODE {
101                if is_open_tag(&current_node) {
102                    // We found another opening tag, we need to increase component counter.
103                    nested_layers += 1;
104                } else if is_close_tag(&current_node) {
105                    // We found a closing tag, minus component counter.
106                    nested_layers -= 1;
107                    if nested_layers == 0 {
108                        // We have found the end of the current tag we are collecting, breaking
109                        // the loop.
110
111                        // We remove the closing tag.
112                        parent.remove_child(&current_node).unwrap();
113                        break;
114                    }
115                }
116            }
117
118            nodes.push_back(current_node);
119        }
120
121        let next_child = collect_from.0.front().cloned();
122        Self(nodes, next_child)
123    }
124
125    /// Remove child nodes until first non-text node.
126    pub fn trim_start_text_nodes(&mut self) {
127        while let Some(ref m) = self.front().cloned() {
128            if m.node_type() == Node::TEXT_NODE {
129                self.pop_front();
130
131                m.unchecked_ref::<web_sys::Text>().remove();
132            } else {
133                break;
134            }
135        }
136    }
137
138    /// Deeply clones all nodes.
139    pub fn deep_clone(&self) -> Self {
140        let nodes = self
141            .iter()
142            .map(|m| m.clone_node_with_deep(true).expect("failed to clone node."))
143            .collect::<VecDeque<_>>();
144
145        // the cloned nodes are disconnected from the real dom, so next_child is `None`
146        Self(nodes, None)
147    }
148
149    // detaches current fragment.
150    pub fn detach(self, _root: &BSubtree, parent: &Element, parent_to_detach: bool) {
151        if !parent_to_detach {
152            for node in self.iter() {
153                parent
154                    .remove_child(node)
155                    .expect("failed to remove child element");
156            }
157        }
158    }
159
160    /// Shift current Fragment into a different position in the dom.
161    pub fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
162        for node in self.iter() {
163            slot.insert(next_parent, node);
164        }
165
166        self.front().cloned().map(DomSlot::at).unwrap_or(slot)
167    }
168
169    /// Return the node that comes after all the nodes in this fragment
170    pub fn sibling_at_end(&self) -> Option<&Node> {
171        self.1.as_ref()
172    }
173}