1use super::attribute::{any_attribute::AnyAttribute, Attribute};
2use crate::{
3 hydration::Cursor,
4 prelude::{Render, RenderHtml},
5 ssr::StreamBuilder,
6 view::{add_attr::AddAnyAttr, Position, PositionState},
7};
8
9pub struct Island<View> {
11 has_element_representation: bool,
12 component: &'static str,
13 props_json: String,
14 view: View,
15}
16const ISLAND_TAG: &str = "leptos-island";
17const ISLAND_CHILDREN_TAG: &str = "leptos-children";
18
19impl<View> Island<View> {
20 pub fn new(component: &'static str, view: View) -> Self {
22 Island {
23 has_element_representation:
24 Self::should_have_element_representation(),
25 component,
26 props_json: String::new(),
27 view,
28 }
29 }
30
31 pub fn with_props(mut self, props_json: String) -> Self {
33 self.props_json = props_json;
34 self
35 }
36
37 fn open_tag(component: &'static str, props: &str, buf: &mut String) {
38 buf.push('<');
39 buf.push_str(ISLAND_TAG);
40 buf.push(' ');
41 buf.push_str("data-component=\"");
42 buf.push_str(component);
43 buf.push('"');
44 if !props.is_empty() {
45 buf.push_str(" data-props=\"");
46 buf.push_str(&html_escape::encode_double_quoted_attribute(&props));
47 buf.push('"');
48 }
49 buf.push('>');
50 }
51
52 fn close_tag(buf: &mut String) {
53 buf.push_str("</");
54 buf.push_str(ISLAND_TAG);
55 buf.push('>');
56 }
57
58 fn should_have_element_representation() -> bool {
60 #[cfg(feature = "reactive_graph")]
61 {
62 use reactive_graph::owner::{use_context, IsHydrating};
63 let already_hydrating =
64 use_context::<IsHydrating>().map(|h| h.0).unwrap_or(false);
65 !already_hydrating
66 }
67 #[cfg(not(feature = "reactive_graph"))]
68 {
69 true
70 }
71 }
72}
73
74impl<View> Render for Island<View>
75where
76 View: Render,
77{
78 type State = View::State;
79
80 fn build(self) -> Self::State {
81 self.view.build()
82 }
83
84 fn rebuild(self, state: &mut Self::State) {
85 self.view.rebuild(state);
86 }
87}
88
89impl<View> AddAnyAttr for Island<View>
90where
91 View: RenderHtml,
92{
93 type Output<SomeNewAttr: Attribute> =
94 Island<<View as AddAnyAttr>::Output<SomeNewAttr>>;
95
96 fn add_any_attr<NewAttr: Attribute>(
97 self,
98 attr: NewAttr,
99 ) -> Self::Output<NewAttr>
100 where
101 Self::Output<NewAttr>: RenderHtml,
102 {
103 let Island {
104 has_element_representation,
105 component,
106 props_json,
107 view,
108 } = self;
109 Island {
110 has_element_representation,
111 component,
112 props_json,
113 view: view.add_any_attr(attr),
114 }
115 }
116}
117
118impl<View> RenderHtml for Island<View>
119where
120 View: RenderHtml,
121{
122 type AsyncOutput = Island<View::AsyncOutput>;
123 type Owned = Island<View::Owned>;
124
125 const MIN_LENGTH: usize = ISLAND_TAG.len() * 2
126 + "<>".len()
127 + "</>".len()
128 + "data-component".len()
129 + View::MIN_LENGTH;
130
131 fn dry_resolve(&mut self) {
132 self.view.dry_resolve()
133 }
134
135 async fn resolve(self) -> Self::AsyncOutput {
136 let Island {
137 has_element_representation,
138 component,
139 props_json,
140 view,
141 } = self;
142 Island {
143 has_element_representation,
144 component,
145 props_json,
146 view: view.resolve().await,
147 }
148 }
149
150 fn to_html_with_buf(
151 self,
152 buf: &mut String,
153 position: &mut Position,
154 escape: bool,
155 mark_branches: bool,
156 extra_attrs: Vec<AnyAttribute>,
157 ) {
158 let has_element = self.has_element_representation;
159 if has_element {
160 Self::open_tag(self.component, &self.props_json, buf);
161 }
162 self.view.to_html_with_buf(
163 buf,
164 position,
165 escape,
166 mark_branches,
167 extra_attrs,
168 );
169 if has_element {
170 Self::close_tag(buf);
171 }
172 }
173
174 fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
175 self,
176 buf: &mut StreamBuilder,
177 position: &mut Position,
178 escape: bool,
179 mark_branches: bool,
180 extra_attrs: Vec<AnyAttribute>,
181 ) where
182 Self: Sized,
183 {
184 let has_element = self.has_element_representation;
185 let mut tag = String::new();
187 if has_element {
188 Self::open_tag(self.component, &self.props_json, &mut tag);
189 }
190 buf.push_sync(&tag);
191
192 self.view.to_html_async_with_buf::<OUT_OF_ORDER>(
194 buf,
195 position,
196 escape,
197 mark_branches,
198 extra_attrs,
199 );
200
201 tag.clear();
203 if has_element {
204 Self::close_tag(&mut tag);
205 }
206 buf.push_sync(&tag);
207 }
208
209 fn hydrate<const FROM_SERVER: bool>(
210 self,
211 cursor: &Cursor,
212 position: &PositionState,
213 ) -> Self::State {
214 if self.has_element_representation {
215 if position.get() == Position::FirstChild {
216 cursor.child();
217 } else if position.get() == Position::NextChild {
218 cursor.sibling();
219 }
220 position.set(Position::FirstChild);
221 }
222
223 self.view.hydrate::<FROM_SERVER>(cursor, position)
224 }
225
226 fn into_owned(self) -> Self::Owned {
227 Island {
228 has_element_representation: self.has_element_representation,
229 component: self.component,
230 props_json: self.props_json,
231 view: self.view.into_owned(),
232 }
233 }
234}
235
236pub struct IslandChildren<View> {
238 view: View,
239 on_hydrate: Option<Box<dyn Fn() + Send + Sync>>,
240}
241
242impl<View> IslandChildren<View> {
243 pub fn new(view: View) -> Self {
245 IslandChildren {
246 view,
247 on_hydrate: None,
248 }
249 }
250
251 pub fn new_with_on_hydrate(
254 view: View,
255 on_hydrate: impl Fn() + Send + Sync + 'static,
256 ) -> Self {
257 IslandChildren {
258 view,
259 on_hydrate: Some(Box::new(on_hydrate)),
260 }
261 }
262
263 fn open_tag(buf: &mut String) {
264 buf.push('<');
265 buf.push_str(ISLAND_CHILDREN_TAG);
266 buf.push('>');
267 }
268
269 fn close_tag(buf: &mut String) {
270 buf.push_str("</");
271 buf.push_str(ISLAND_CHILDREN_TAG);
272 buf.push('>');
273 }
274}
275
276impl<View> Render for IslandChildren<View>
277where
278 View: Render,
279{
280 type State = ();
281
282 fn build(self) -> Self::State {}
283
284 fn rebuild(self, _state: &mut Self::State) {}
285}
286
287impl<View> AddAnyAttr for IslandChildren<View>
288where
289 View: RenderHtml,
290{
291 type Output<SomeNewAttr: Attribute> =
292 IslandChildren<<View as AddAnyAttr>::Output<SomeNewAttr>>;
293
294 fn add_any_attr<NewAttr: Attribute>(
295 self,
296 attr: NewAttr,
297 ) -> Self::Output<NewAttr>
298 where
299 Self::Output<NewAttr>: RenderHtml,
300 {
301 let IslandChildren { view, on_hydrate } = self;
302 IslandChildren {
303 view: view.add_any_attr(attr),
304 on_hydrate,
305 }
306 }
307}
308
309impl<View> RenderHtml for IslandChildren<View>
310where
311 View: RenderHtml,
312{
313 type AsyncOutput = IslandChildren<View::AsyncOutput>;
314 type Owned = IslandChildren<View::Owned>;
315
316 const MIN_LENGTH: usize = ISLAND_CHILDREN_TAG.len() * 2
317 + "<>".len()
318 + "</>".len()
319 + View::MIN_LENGTH;
320
321 fn dry_resolve(&mut self) {
322 self.view.dry_resolve()
323 }
324
325 async fn resolve(self) -> Self::AsyncOutput {
326 let IslandChildren { view, on_hydrate } = self;
327 IslandChildren {
328 view: view.resolve().await,
329 on_hydrate,
330 }
331 }
332
333 fn to_html_with_buf(
334 self,
335 buf: &mut String,
336 position: &mut Position,
337 escape: bool,
338 mark_branches: bool,
339 extra_attrs: Vec<AnyAttribute>,
340 ) {
341 Self::open_tag(buf);
342 self.view.to_html_with_buf(
343 buf,
344 position,
345 escape,
346 mark_branches,
347 extra_attrs,
348 );
349 Self::close_tag(buf);
350 }
351
352 fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
353 self,
354 buf: &mut StreamBuilder,
355 position: &mut Position,
356 escape: bool,
357 mark_branches: bool,
358 extra_attrs: Vec<AnyAttribute>,
359 ) where
360 Self: Sized,
361 {
362 let mut tag = String::new();
364 Self::open_tag(&mut tag);
365 buf.push_sync(&tag);
366
367 self.view.to_html_async_with_buf::<OUT_OF_ORDER>(
369 buf,
370 position,
371 escape,
372 mark_branches,
373 extra_attrs,
374 );
375
376 tag.clear();
378 Self::close_tag(&mut tag);
379 buf.push_sync(&tag);
380 }
381
382 fn hydrate<const FROM_SERVER: bool>(
383 self,
384 cursor: &Cursor,
385 position: &PositionState,
386 ) -> Self::State {
387 let curr_position = position.get();
391 if curr_position == Position::FirstChild {
392 cursor.child();
393 } else if curr_position != Position::Current {
394 cursor.sibling();
395 }
396 position.set(Position::NextChild);
397
398 if let Some(on_hydrate) = self.on_hydrate {
399 use crate::{
400 hydration::failed_to_cast_element, renderer::CastFrom,
401 };
402
403 let el =
404 crate::renderer::types::Element::cast_from(cursor.current())
405 .unwrap_or_else(|| {
406 failed_to_cast_element(
407 "leptos-children",
408 cursor.current(),
409 )
410 });
411 let cb = wasm_bindgen::closure::Closure::wrap(
412 on_hydrate as Box<dyn Fn()>,
413 );
414 _ = js_sys::Reflect::set(
415 &el,
416 &wasm_bindgen::JsValue::from_str("$$on_hydrate"),
417 &cb.into_js_value(),
418 );
419 }
420 }
421
422 fn into_owned(self) -> Self::Owned {
423 IslandChildren {
424 view: self.view.into_owned(),
425 on_hydrate: self.on_hydrate,
426 }
427 }
428}