use crate::dom::{
Application, Cmd, Component, Container, Effects, MountAction, MountTarget, Program, Task,
};
use crate::vdom::Node;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
#[cfg(feature = "use-snippets")]
#[wasm_bindgen(module = "/js/define_custom_element.js")]
extern "C" {
pub fn register_custom_element(custom_tag: &str, adapter: &str);
}
#[cfg(not(feature = "use-snippets"))]
thread_local!(static REGISTER_CUSTOM_ELEMENT_FUNCTION: js_sys::Function = create_register_custom_element_function());
#[cfg(not(feature = "use-snippets"))]
pub fn register_custom_element(custom_tag: &str, adapter: &str) {
REGISTER_CUSTOM_ELEMENT_FUNCTION.with(|func| {
log::debug!("register_custom_element_function: {}", func.to_string());
func.call2(
&JsValue::NULL,
&JsValue::from_str(custom_tag),
&JsValue::from_str(adapter),
)
.expect("must call");
})
}
#[cfg(not(feature = "use-snippets"))]
fn create_register_custom_element_function() -> js_sys::Function {
js_sys::Function::new_with_args(
"custom_tag, adapterClassName",
r#"
function define_custom_element(custom_tag, adapterClassName)
{
console.log(`custom tag: ${custom_tag}, adapterClassName: ${adapterClassName}`);
let adapter = window[adapterClassName];
console.log("adapter: ", adapter);
if (window.customElements.get(custom_tag) === undefined ){
window.customElements.define(custom_tag,
class extends HTMLElement{
constructor(){
super();
this.instance = new adapter(this);
}
static get observedAttributes(){
return adapter.observedAttributes;
}
connectedCallback(){
this.instance.connectedCallback();
}
disconnectedCallback(){
this.instance.disconnectedCallback();
}
adoptedCallback(){
this.instance.adoptedCallback();
}
attributeChangedCallback(name, oldValue, newValue){
this.instance.attributeChangedCallback(name, oldValue, newValue);
}
appendChild(child){
console.log("appending a child:", child);
this.instance.appendChild(child);
}
}
);
}
}
define_custom_element(custom_tag, adapterClassName);
"#,
)
}
pub trait CustomElement<MSG> {
fn custom_tag() -> &'static str;
fn observed_attributes() -> Vec<&'static str>;
fn attribute_changed(
program: &Program<Self, MSG>,
attr_name: &str,
old_value: JsValue,
new_value: JsValue,
) where
Self: Sized + Application<MSG>;
fn connected_callback(&mut self);
fn disconnected_callback(&mut self);
fn adopted_callback(&mut self);
}
pub struct WebComponent<APP, MSG>
where
MSG: 'static,
{
pub program: Program<APP, MSG>,
}
impl<COMP, MSG> Application<MSG> for COMP
where
COMP: Component<MSG, ()> + 'static,
COMP: CustomElement<MSG>,
MSG: 'static,
{
fn init(&mut self) -> Vec<Cmd<Self, MSG>> {
<Self as Component<MSG, ()>>::init(self)
.into_iter()
.map(Cmd::from)
.collect()
}
fn update(&mut self, msg: MSG) -> Cmd<Self, MSG> {
let effects = <Self as Component<MSG, ()>>::update(self, msg);
Cmd::from(effects)
}
fn view(&self) -> Node<MSG> {
<Self as Component<MSG, ()>>::view(self)
}
fn stylesheet() -> Vec<String> {
<Self as Component<MSG, ()>>::stylesheet()
}
fn style(&self) -> Vec<String> {
<Self as Component<MSG, ()>>::style(self)
}
}
impl<CONT, MSG> Component<MSG, ()> for CONT
where
CONT: Container<MSG, ()>,
CONT: CustomElement<MSG>,
MSG: 'static,
{
fn init(&mut self) -> Vec<Task<MSG>> {
<Self as Container<MSG, ()>>::init(self)
}
fn update(&mut self, msg: MSG) -> Effects<MSG, ()> {
<Self as Container<MSG, ()>>::update(self, msg)
}
fn view(&self) -> Node<MSG> {
<Self as Container<MSG, ()>>::view(self, [])
}
fn stylesheet() -> Vec<String> {
<Self as Container<MSG, ()>>::stylesheet()
}
fn style(&self) -> Vec<String> {
<Self as Container<MSG, ()>>::style(self)
}
}
impl<APP, MSG> WebComponent<APP, MSG>
where
APP: Application<MSG> + Default + 'static,
APP: CustomElement<MSG>,
MSG: 'static,
{
pub fn new(node: JsValue) -> Self {
let mount_node: &web_sys::Node = node.unchecked_ref();
Self {
program: Program::new(
APP::default(),
mount_node,
MountAction::Append,
MountTarget::ShadowRoot,
),
}
}
pub fn attribute_changed(&self, attr_name: &str, old_value: JsValue, new_value: JsValue) {
APP::attribute_changed(&self.program, attr_name, old_value, new_value);
}
pub fn connected_callback(&mut self) {
self.program.mount();
self.program.app.borrow_mut().connected_callback();
let component_style = <APP as Application<MSG>>::stylesheet().join("");
self.program.inject_style_to_mount(&component_style);
self.program.update_dom().expect("must update dom");
}
pub fn disconnected_callback(&mut self) {
self.program.app.borrow_mut().disconnected_callback();
}
pub fn adopted_callback(&mut self) {
self.program.app.borrow_mut().adopted_callback();
}
}