Clone in a terminal:
Commit a3ef81e
Johan Mattsson - 2026-06-28T18:13:25+00:00
a3ef81e9567fac87fc7b6dbd39e8e40793e2046ejohan.mattsson.m@gmail.com

Detach web component

Changed files (1)

Diff

diff --git a/src/init.rs b/src/init.rsindex 91bd61a..a1dcfb1 100644--- a/src/init.rs+++ b/src/init.rs@@ -42,6 +42,7 @@ pub fn component_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::Token     let create_class_js = format!("createComponentClass{}", struct_name_ident);     let init_self_js = format!("initSelf{}", struct_name_ident);     let attach_js = format!("attachComponent{}", struct_name_ident);+    let detach_js = format!("detachComponent{}", struct_name_ident);      let create_class_js_indent = Ident::new(&create_class_js, Span::call_site());     let init_self_js_ident = Ident::new(&init_self_js, Span::call_site());@@ -51,10 +52,12 @@ pub fn component_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::Token         tag_lit.clone(),         init_self_js.as_str(),         attach_js.as_str(),+        detach_js.as_str(),     );      let rs_self = gen_rust_self(component_struct_item.clone(), init_self_js_ident);     let attach_fn = attach_quote(&attach_js, struct_name_ident);+    let detach_fn = detach_quote(&detach_js, struct_name_ident);      let expanded = quote! {         #global_store@@ -62,6 +65,7 @@ pub fn component_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::Token         #component_struct_item         #create_js_class_fn         #attach_fn+        #detach_fn     };      expanded.into()@@ -100,6 +104,39 @@ fn attach_quote(atach_state_js_fn: &str, struct_name: &Ident) -> proc_macro2::To     expanded } +fn detach_quote(detach_state_js_fn: &str, struct_name: &Ident) -> proc_macro2::TokenStream {+    let js_callback_ident = Ident::new(detach_state_js_fn, Span::call_site());+    let rs_name_str = from_pascal_to_snake(detach_state_js_fn);+    let rs_name_ident = Ident::new(&rs_name_str, Span::call_site());++    let store_name_str = store_name(struct_name);+    let store_ident = Ident::new(&store_name_str, Span::call_site());++    let expanded = quote! {+        #[callback(#js_callback_ident)]+        fn #rs_name_ident(element: HtmlElement, wasm_id: u32) {+            let mut store = #store_ident.write().unwrap();++            if store.is_none() {+                error!("Component not found in connect.");+                return;+            }++            let store_map = store.as_mut().unwrap();++            if let Some(component) = store_map.get_mut(&wasm_id) {+                component.disconnected(element);+            } else {+                error!("Component id {} not found in disconnected.", wasm_id);+            }++            store_map.remove(wasm_id);+        }+    };++    expanded+}+ fn store_name(struct_name: &Ident) -> String {     let store_name = from_pascal_to_snake(&struct_name.to_string()).to_uppercase() + "_COMPONENTS";     store_name@@ -140,27 +177,6 @@ fn gen_rust_self(struct_item: ItemStruct, js_name: Ident) -> proc_macro2::TokenS     let store_name_str = store_name(struct_ident);     let store_ident = Ident::new(&store_name_str, Span::call_site()); -    // Check whether the struct has a named field called `value` so we can initialize it.-    let mut has_value_field = false;-    if let syn::Fields::Named(fields_named) = &struct_item.fields {-        for field in fields_named.named.iter() {-            if let Some(ident) = &field.ident {-                if ident == "value" {-                    has_value_field = true;-                    break;-                }-            }-        }-    }--    let set_value_ts = if has_value_field {-        quote! {-            counter.value = wasm_id as usize;-        }-    } else {-        quote! {}-    };-     let struct_name_str = struct_ident.to_string();     let rust_name = format!(         "init_component_self_{}",@@ -174,11 +190,7 @@ fn gen_rust_self(struct_item: ItemStruct, js_name: Ident) -> proc_macro2::TokenS         pub fn #rust_ident() -> u32 {  // init_component_state_struct_name, returns id for the instance of the element             log!("Run counter init rs state.");             let mut counter = #struct_ident::default();-            let wasm_id = ::kaja_web::NEXT_COMPONENT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);--            #set_value_ts--            let mut store = #store_ident.write().expect("failed to acquire write lock");+            let wasm_id = ::kaja_web::NEXT_COMPONENT_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);            let mut store = #store_ident.write().expect("failed to acquire write lock");              if store.is_none() {                 *store = Some(std::collections::HashMap::new());@@ -196,77 +208,79 @@ fn gen_rust_self(struct_item: ItemStruct, js_name: Ident) -> proc_macro2::TokenS fn create_class(     class_creator: Ident,     tag: LitStr,-    init_state_fn: &str,-    atach_state_fn: &str,+    init_self_fn: &str,+    attach_state_fn: &str,+    detach_state_fn: &str, ) -> proc_macro2::TokenStream {     let name_str_lit = LitStr::new(&class_creator.to_string(), Span::call_site());      let expanded = quote! {-        ::inventory::submit! {-            InitComponentFn(#class_creator)-        }--        #[allow(non_snake_case)]-        pub fn #class_creator() { // createComponentTheStructName-            let wasm_id = 0;--            let component_name = #name_str_lit;-            let tag_name = #tag;-            log!("init component", component_name, tag_name);--            let js_init = String::from(-                            r#"-                                return class extends HTMLElement {-                                    constructor() {-                                        super();-                                        this.__componentId = -1;-                                    }--                                    connectedCallback() {-                                        if (this.__componentId === -1) {-                                            this.__componentId = __INIT_CALLBACK();-                                        }--                                        __ATTACH_CALLBACK(this, this.__componentId);-                                    }--                                    disconnectedCallback() {-                                        //counterComponentDisconnect(this, this.__componentId);-                                    }--                                    attributeChangedCallback(name, oldValue, newValue) {-                                        //callbacks.attributeChanged(this, name, oldValue, newValue);-                                    }--                                    static get observedAttributes() {-                                        return [];-                                    }-                                }-                        "#,-                        );--            let js_init_with_state = js_init.replace("__INIT_CALLBACK", #init_state_fn);-            let js_init_with_state = js_init_with_state.replace("__ATTACH_CALLBACK", #atach_state_fn);--            let component = js_sys::Function::new_no_args(js_init_with_state.as_str());--            let class = component.call0(&JsValue::NULL);--            if class.is_err() {-                error!("Failed to register class for {}", #tag);-                return;+            ::inventory::submit! {+                InitComponentFn(#class_creator)             } -            let class_fn = class.unwrap();-            let registry = web_sys::window().unwrap().custom_elements();+            #[allow(non_snake_case)]+            pub fn #class_creator() { // createComponentTheStructName+                let wasm_id = 0;++                let component_name = #name_str_lit;+                let tag_name = #tag;+                log!("init component", component_name, tag_name);++                let js_init = String::from(+                    r#"+                    return class extends HTMLElement {+                        constructor() {+                            super();+                            this.componentId = -1;+                        }++                        connectedCallback() {+                            if (this.componentId === -1) {+                                this.componentId = __INIT_CALLBACK();+                            }++                            __ATTACH_CALLBACK(this, this.componentId);+                        }++                        disconnectedCallback() {+                            __DETACH_CALLBACK(this, this.componentId);+                        }++                        attributeChangedCallback(name, oldValue, newValue) {+                            //callbacks.attributeChanged(this, name, oldValue, newValue);+                        }++                        static get observedAttributes() {+                            return [];+                        }+                    }+            "#,+                );++                let js_init_with_state = js_init.replace("__INIT_CALLBACK", #init_self_fn);+                let js_init_with_state = js_init_with_state.replace("__ATTACH_CALLBACK", #attach_state_fn);+                let js_init_with_state = js_init_with_state.replace("__DETACH_CALLBACK", #detach_state_fn);+    t+                let component = js_sys::Function::new_no_args(js_init_with_state.as_str());++                let class = component.call0(&JsValue::NULL);++                if class.is_err() {+                    error!("Failed to register class for {}", #tag);+                    return;+                }++                let class_fn = class.unwrap();+                let registry = web_sys::window().unwrap().custom_elements(); -            let result = registry.define(#tag, &class_fn.unchecked_into());+                let result = registry.define(#tag, &class_fn.unchecked_into()); -            if result.is_err() {-                error!("Failed to register component {}", #tag);+                if result.is_err() {+                    error!("Failed to register component {}", #tag);+                }             }-        }-    };+        };      expanded }

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112