use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use islands_morph::{
AfterNodeAdded, BeforeNodeAdded, BeforeNodeMorphed, BeforeNodeRemoved, CallbackOutcome,
HeadConfig, MorphCallbacks, MorphOptions, MorphStyle,
};
use crate::{mount_all, unmount_island};
use super::{dom, prefetch};
const ATTRIBUTE_MOUNTED: &str = "data-island-mounted";
const ATTRIBUTE_SCOPE_ID: &str = "data-island-scope-id";
const ATTRIBUTE_PROPS: &str = "data-island-props";
const ATTRIBUTE_ISLAND: &str = "data-island";
fn is_mounted_island(node: &web_sys::Node) -> bool {
let element = match node.dyn_ref::<web_sys::Element>() {
Some(element) => element,
None => return false,
};
element.has_attribute(ATTRIBUTE_ISLAND) && element.has_attribute(ATTRIBUTE_MOUNTED)
}
fn strip_client_state(node: &web_sys::Node) {
let element = match node.dyn_ref::<web_sys::Element>() {
Some(element) => element,
None => return,
};
let _ = element.remove_attribute(ATTRIBUTE_SCOPE_ID);
let _ = element.remove_attribute(ATTRIBUTE_MOUNTED);
}
fn props_of(node: &web_sys::Node) -> Option<String> {
node.dyn_ref::<web_sys::Element>()
.and_then(|element| element.get_attribute(ATTRIBUTE_PROPS))
}
fn island_name_of(node: &web_sys::Node) -> Option<String> {
node.dyn_ref::<web_sys::Element>()
.and_then(|element| element.get_attribute(ATTRIBUTE_ISLAND))
}
pub(crate) fn nav_morph_options() -> MorphOptions {
let before_node_removed: BeforeNodeRemoved = Box::new(|node: &web_sys::Node| {
if is_mounted_island(node) {
if let Some(element) = node.dyn_ref::<web_sys::Element>() {
let _ = unmount_island(element);
}
}
CallbackOutcome::Continue
});
let before_node_morphed: BeforeNodeMorphed =
Box::new(|old_node: &web_sys::Node, new_content: &web_sys::Node| {
strip_client_state(new_content);
if !is_mounted_island(old_node) {
return CallbackOutcome::Continue;
}
let unchanged = island_name_of(old_node) == island_name_of(new_content)
&& props_of(old_node) == props_of(new_content);
if unchanged {
return CallbackOutcome::Skip;
}
if let Some(element) = old_node.dyn_ref::<web_sys::Element>() {
let _ = unmount_island(element);
}
CallbackOutcome::Continue
});
let before_node_added: BeforeNodeAdded = Box::new(|node: &web_sys::Node| {
strip_client_state(node);
CallbackOutcome::Continue
});
let after_node_added: AfterNodeAdded = Box::new(|_node: &web_sys::Node| {
});
let callbacks = MorphCallbacks {
before_node_added: Some(before_node_added),
after_node_added: Some(after_node_added),
before_node_morphed: Some(before_node_morphed),
before_node_removed: Some(before_node_removed),
..MorphCallbacks::default()
};
MorphOptions {
morph_style: MorphStyle::OuterHtml,
callbacks,
head: HeadConfig::default(),
..MorphOptions::default()
}
}
pub(crate) fn activate_suspense_then_mount() -> Result<(), JsValue> {
activate_suspense_slots()?;
mount_all()?;
prefetch::observe_current_anchors();
Ok(())
}
fn activate_suspense_slots() -> Result<(), JsValue> {
let document = dom::document()?;
let templates = document.query_selector_all("template[id^=\"T:\"]")?;
for index in 0..templates.length() {
let node = match templates.get(index) {
Some(node) => node,
None => continue,
};
let template = match node.dyn_into::<web_sys::HtmlTemplateElement>() {
Ok(template) => template,
Err(_) => continue,
};
let template_id = template.id();
let slot_id = match template_id.strip_prefix("T:") {
Some(suffix) => format!("S:{suffix}"),
None => continue,
};
let slot = match document.get_element_by_id(&slot_id) {
Some(slot) => slot,
None => continue,
};
let content = template.content();
slot.replace_children_with_node_1(content.as_ref());
template.remove();
}
Ok(())
}