1use gloo::utils::document;
4use web_sys::Element;
5
6#[cfg(feature = "hydration")]
7use super::Fragment;
8use super::{BNode, BSubtree, DomSlot, Reconcilable, ReconcileTarget};
9use crate::html::AnyScope;
10use crate::virtual_dom::{Key, VSuspense};
11
12#[derive(Debug)]
13enum Fallback {
14 Bundle(BNode),
16 #[cfg(feature = "hydration")]
18 Fragment(Fragment),
19}
20
21#[derive(Debug)]
23pub(super) struct BSuspense {
24 children_bundle: BNode,
25 fallback: Option<Fallback>,
27 detached_parent: Element,
28 key: Option<Key>,
29}
30
31impl BSuspense {
32 pub fn key(&self) -> Option<&Key> {
34 self.key.as_ref()
35 }
36}
37
38impl ReconcileTarget for BSuspense {
39 fn detach(self, root: &BSubtree, parent: &Element, parent_to_detach: bool) {
40 match self.fallback {
41 Some(m) => {
42 match m {
43 Fallback::Bundle(bundle) => {
44 bundle.detach(root, parent, parent_to_detach);
45 }
46
47 #[cfg(feature = "hydration")]
48 Fallback::Fragment(fragment) => {
49 fragment.detach(root, parent, parent_to_detach);
50 }
51 }
52
53 self.children_bundle
54 .detach(root, &self.detached_parent, false);
55 }
56 None => {
57 self.children_bundle.detach(root, parent, parent_to_detach);
58 }
59 }
60 }
61
62 fn shift(&self, next_parent: &Element, slot: DomSlot) -> DomSlot {
63 match self.fallback.as_ref() {
64 Some(Fallback::Bundle(bundle)) => bundle.shift(next_parent, slot),
65 #[cfg(feature = "hydration")]
66 Some(Fallback::Fragment(fragment)) => fragment.shift(next_parent, slot),
67 None => self.children_bundle.shift(next_parent, slot),
68 }
69 }
70}
71
72impl Reconcilable for VSuspense {
73 type Bundle = BSuspense;
74
75 fn attach(
76 self,
77 root: &BSubtree,
78 parent_scope: &AnyScope,
79 parent: &Element,
80 slot: DomSlot,
81 ) -> (DomSlot, Self::Bundle) {
82 let VSuspense {
83 children,
84 fallback,
85 suspended,
86 key,
87 } = self;
88 let detached_parent = document()
89 .create_element("div")
90 .expect("failed to create detached element");
91
92 if suspended {
95 let (_child_ref, children_bundle) =
96 children.attach(root, parent_scope, &detached_parent, DomSlot::at_end());
97 let (fallback_ref, fallback) = fallback.attach(root, parent_scope, parent, slot);
98 (
99 fallback_ref,
100 BSuspense {
101 children_bundle,
102 fallback: Some(Fallback::Bundle(fallback)),
103 detached_parent,
104 key,
105 },
106 )
107 } else {
108 let (child_ref, children_bundle) = children.attach(root, parent_scope, parent, slot);
109 (
110 child_ref,
111 BSuspense {
112 children_bundle,
113 fallback: None,
114 detached_parent,
115 key,
116 },
117 )
118 }
119 }
120
121 fn reconcile_node(
122 self,
123 root: &BSubtree,
124 parent_scope: &AnyScope,
125 parent: &Element,
126 slot: DomSlot,
127 bundle: &mut BNode,
128 ) -> DomSlot {
129 match bundle {
130 BNode::Suspense(m) if m.key == self.key => {
132 self.reconcile(root, parent_scope, parent, slot, m)
133 }
134 _ => self.replace(root, parent_scope, parent, slot, bundle),
135 }
136 }
137
138 fn reconcile(
139 self,
140 root: &BSubtree,
141 parent_scope: &AnyScope,
142 parent: &Element,
143 slot: DomSlot,
144 suspense: &mut Self::Bundle,
145 ) -> DomSlot {
146 let VSuspense {
147 children,
148 fallback: vfallback,
149 suspended,
150 key: _,
151 } = self;
152
153 let children_bundle = &mut suspense.children_bundle;
154 match (suspended, &mut suspense.fallback) {
159 (true, Some(fallback)) => {
161 children.reconcile_node(
162 root,
163 parent_scope,
164 &suspense.detached_parent,
165 DomSlot::at_end(),
166 children_bundle,
167 );
168
169 match fallback {
170 Fallback::Bundle(bundle) => {
171 vfallback.reconcile_node(root, parent_scope, parent, slot, bundle)
172 }
173 #[cfg(feature = "hydration")]
174 Fallback::Fragment(fragment) => match fragment.front().cloned() {
175 Some(m) => DomSlot::at(m),
176 None => slot,
177 },
178 }
179 }
180 (false, None) => {
182 children.reconcile_node(root, parent_scope, parent, slot, children_bundle)
183 }
184 (true, None) => {
187 children_bundle.shift(&suspense.detached_parent, DomSlot::at_end());
188
189 children.reconcile_node(
190 root,
191 parent_scope,
192 &suspense.detached_parent,
193 DomSlot::at_end(),
194 children_bundle,
195 );
196 let (fallback_ref, fallback) = vfallback.attach(root, parent_scope, parent, slot);
199 suspense.fallback = Some(Fallback::Bundle(fallback));
200 fallback_ref
201 }
202 (false, Some(_)) => {
204 match suspense.fallback.take() {
205 Some(Fallback::Bundle(bundle)) => {
206 bundle.detach(root, parent, false);
207 }
208 #[cfg(feature = "hydration")]
209 Some(Fallback::Fragment(fragment)) => {
210 fragment.detach(root, parent, false);
211 }
212 None => {
213 unreachable!("None condition has been checked before.")
214 }
215 };
216
217 children_bundle.shift(parent, slot.clone());
218 children.reconcile_node(root, parent_scope, parent, slot, children_bundle)
219 }
220 }
221 }
222}
223
224#[cfg(feature = "hydration")]
225mod feat_hydration {
226 use super::*;
227 use crate::dom_bundle::{Fragment, Hydratable};
228 use crate::virtual_dom::Collectable;
229
230 impl Hydratable for VSuspense {
231 fn hydrate(
232 self,
233 root: &BSubtree,
234 parent_scope: &AnyScope,
235 parent: &Element,
236 fragment: &mut Fragment,
237 ) -> Self::Bundle {
238 let detached_parent = document()
239 .create_element("div")
240 .expect("failed to create detached element");
241
242 let collectable = Collectable::Suspense;
243 let fallback_fragment = Fragment::collect_between(fragment, &collectable, parent);
244
245 let mut nodes = fallback_fragment.deep_clone();
246
247 for node in nodes.iter() {
248 detached_parent.append_child(node).unwrap();
249 }
250
251 let children_bundle =
254 self.children
255 .hydrate(root, parent_scope, &detached_parent, &mut nodes);
256
257 nodes.trim_start_text_nodes();
259
260 assert!(nodes.is_empty(), "expected end of suspense, found node.");
261
262 BSuspense {
263 children_bundle,
264 detached_parent,
265 key: self.key,
266
267 fallback: Some(Fallback::Fragment(fallback_fragment)),
270 }
271 }
272 }
273}