use std::sync::{Arc, Mutex};
use wasm_bindgen::prelude::*;
use wasm_bindgen::UnwrapThrowExt;
use web_sys::{window, HtmlElement};
pub trait CustomElement: Default + 'static {
fn inject_children(&mut self, this: &HtmlElement);
fn shadow() -> bool {
true
}
fn observed_attributes() -> &'static [&'static str] {
&[]
}
fn constructor(&mut self, _this: &HtmlElement) {}
fn connected_callback(&mut self, _this: &HtmlElement) {}
fn disconnected_callback(&mut self, _this: &HtmlElement) {}
fn adopted_callback(&mut self, _this: &HtmlElement) {}
fn attribute_changed_callback(
&mut self,
_this: &HtmlElement,
_name: String,
_old_value: Option<String>,
_new_value: Option<String>,
) {
}
fn superclass() -> (Option<&'static str>, &'static js_sys::Function) {
(None, &HtmlElementConstructor)
}
fn define(tag_name: &'static str) {
let constructor = Closure::wrap(Box::new(move |this: HtmlElement| {
let component = Arc::new(Mutex::new(Self::default()));
let cmp = component.clone();
let constructor = Closure::wrap(Box::new({
move |el| {
let mut lock = cmp.lock().unwrap_throw();
lock.constructor(&el);
}
}) as Box<dyn FnMut(HtmlElement)>);
js_sys::Reflect::set(
&this,
&JsValue::from_str("_constructor"),
&constructor.into_js_value(),
)
.unwrap_throw();
let cmp = component.clone();
let inject_children = Closure::wrap(Box::new({
move |el| {
let mut lock = cmp.lock().unwrap_throw();
lock.inject_children(&el);
}
}) as Box<dyn FnMut(HtmlElement)>);
js_sys::Reflect::set(
&this,
&JsValue::from_str("_injectChildren"),
&inject_children.into_js_value(),
)
.unwrap_throw();
let cmp = component.clone();
let connected = Closure::wrap(Box::new({
move |el| {
let mut lock = cmp.lock().unwrap_throw();
lock.connected_callback(&el);
}
}) as Box<dyn FnMut(HtmlElement)>);
js_sys::Reflect::set(
&this,
&JsValue::from_str("_connectedCallback"),
&connected.into_js_value(),
)
.unwrap_throw();
let cmp = component.clone();
let disconnected = Closure::wrap(Box::new(move |el| {
let mut lock = cmp.lock().unwrap_throw();
lock.disconnected_callback(&el);
}) as Box<dyn FnMut(HtmlElement)>);
js_sys::Reflect::set(
&this,
&JsValue::from_str("_disconnectedCallback"),
&disconnected.into_js_value(),
)
.unwrap_throw();
let cmp = component.clone();
let adopted = Closure::wrap(Box::new(move |el| {
let mut lock = cmp.lock().unwrap_throw();
lock.adopted_callback(&el);
}) as Box<dyn FnMut(HtmlElement)>);
js_sys::Reflect::set(
&this,
&JsValue::from_str("_adoptedCallback"),
&adopted.into_js_value(),
)
.unwrap_throw();
let cmp = component;
let attribute_changed = Closure::wrap(Box::new(move |el, name, old_value, new_value| {
let mut lock = cmp.lock().unwrap_throw();
lock.attribute_changed_callback(&el, name, old_value, new_value);
})
as Box<dyn FnMut(HtmlElement, String, Option<String>, Option<String>)>);
js_sys::Reflect::set(
&this,
&JsValue::from_str("_attributeChangedCallback"),
&attribute_changed.into_js_value(),
)
.unwrap_throw();
}) as Box<dyn FnMut(HtmlElement)>);
let attributes = Self::observed_attributes();
let observed_attributes = JsValue::from(
attributes
.iter()
.map(|attr| JsValue::from_str(attr))
.collect::<js_sys::Array>(),
);
let (super_tag, super_constructor) = Self::superclass();
make_custom_element(
super_constructor,
tag_name,
Self::shadow(),
constructor.into_js_value(),
observed_attributes,
super_tag,
);
}
}
pub fn inject_style(this: &HtmlElement, style: &str) {
let style_el = window()
.unwrap_throw()
.document()
.unwrap_throw()
.create_element("style")
.unwrap_throw();
style_el.set_inner_html(style);
match this.shadow_root() {
Some(shadow_root) => shadow_root.append_child(&style_el).unwrap_throw(),
None => this.append_child(&style_el).unwrap_throw(),
};
}
pub fn inject_stylesheet(this: &HtmlElement, url: &str) {
let style_el = window()
.unwrap_throw()
.document()
.unwrap_throw()
.create_element("link")
.unwrap_throw();
style_el.set_attribute("rel", "stylesheet").unwrap_throw();
style_el.set_attribute("href", url).unwrap_throw();
match this.shadow_root() {
Some(shadow_root) => shadow_root.append_child(&style_el).unwrap_throw(),
None => this.append_child(&style_el).unwrap_throw(),
};
}
#[wasm_bindgen(module = "/src/make_custom_element.js")]
extern "C" {
fn make_custom_element(
superclass: &js_sys::Function,
tag_name: &str,
shadow: bool,
constructor: JsValue,
observed_attributes: JsValue,
superclass_tag: Option<&str>,
);
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = HTMLElement, js_namespace = window)]
pub static HtmlElementConstructor: js_sys::Function;
}