mod effect;
#[cfg(feature = "nav")]
mod nav;
mod panic;
mod registry;
mod scope;
mod signal;
pub use signal::Signal;
#[cfg(feature = "nav")]
pub use nav::navigate;
use scope::{current_scope, with_current_scope};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn init_panic_hook() {
panic::init_panic_hook();
}
#[cfg(feature = "nav")]
#[wasm_bindgen(start)]
pub fn init_nav() {
nav::init();
}
#[wasm_bindgen]
pub fn register_island(name: &str, mount: &js_sys::Function) -> Result<(), JsValue> {
registry::register_island_fn(name.to_owned(), mount.clone());
Ok(())
}
#[wasm_bindgen]
pub fn mount_all() -> Result<(), JsValue> {
let window = web_sys::window().ok_or_else(|| JsValue::from_str("no window"))?;
let document = window
.document()
.ok_or_else(|| JsValue::from_str("no document"))?;
let nodes = document
.query_selector_all("[data-island]:not([data-island-mounted])")?;
for i in 0..nodes.length() {
let node = nodes.get(i).ok_or_else(|| JsValue::from_str("missing node"))?;
let element = registry::node_to_element(node.into())
.ok_or_else(|| JsValue::from_str("node is not an Element"))?;
let name = match element.get_attribute("data-island") {
Some(n) => n,
None => {
web_sys::console::warn_1(&JsValue::from_str(
"data-island attribute missing on node",
));
continue;
}
};
let mount_fn = match registry::get_island_fn(&name) {
Some(f) => f,
None => {
web_sys::console::warn_1(&JsValue::from_str(&format!(
"no island registered: {name}"
)));
continue;
}
};
let props_json = element
.get_attribute("data-island-props")
.unwrap_or_else(|| "{}".to_owned());
let scope = scope::Scope::new();
with_current_scope(&scope, || {
let result = mount_fn.call2(
&JsValue::NULL,
&element.clone().into(),
&JsValue::from_str(&props_json),
);
if let Err(e) = result {
web_sys::console::error_2(
&JsValue::from_str(&format!("island {name} failed:")),
&e,
);
}
});
registry::mark_mounted(&element, scope);
}
Ok(())
}
#[wasm_bindgen]
pub fn __islands_remount() -> Result<(), JsValue> {
mount_all()
}
#[wasm_bindgen]
pub fn unmount_island(element: &web_sys::Element) -> Result<(), JsValue> {
let scope_id = match registry::lookup_scope_id(element) {
Some(id) => id,
None => return Ok(()),
};
let removed_scope = registry::remove_scope(scope_id);
let _ = element.remove_attribute("data-island-mounted");
let _ = element.remove_attribute("data-island-scope-id");
drop(removed_scope);
Ok(())
}
#[wasm_bindgen]
pub fn scope_registry_len() -> usize {
registry::scope_registry_len()
}
#[wasm_bindgen]
pub fn on_event(
target: &web_sys::EventTarget,
event_name: &str,
handler: js_sys::Function,
) -> Result<(), JsValue> {
let scope = current_scope();
target.add_event_listener_with_callback(event_name, &handler)?;
let target_clone = target.clone();
let event_name_owned = event_name.to_owned();
let handler_for_cleanup = handler.clone();
scope.on_cleanup(move || {
let _ = target_clone
.remove_event_listener_with_callback(&event_name_owned, &handler_for_cleanup);
});
scope.keep_alive(handler);
Ok(())
}
#[wasm_bindgen]
pub fn effect(callback: js_sys::Function) -> Result<(), JsValue> {
use std::rc::Rc;
let scope = current_scope();
let effect_handle = Rc::new(effect::EffectImpl { callback });
scope.add_effect(effect_handle.clone());
effect::run_effect(&effect_handle);
Ok(())
}
#[wasm_bindgen]
pub fn keep_alive_in_current_scope(value: JsValue) {
current_scope().keep_alive(value);
}