web_sys_query_derive/
lib.rs

1//! Helper macros for `web-sys-query`
2
3use convert_case::{Case, Casing};
4use proc_macro2::{Ident, Span, TokenStream};
5use quote::quote;
6use syn::{parse_macro_input, ItemEnum};
7
8#[proc_macro_derive(OnEvent, attributes(unimplemented))]
9pub fn derive_on_event(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
10    let input = parse_macro_input!(item as ItemEnum);
11
12    derive_on_event_enum(input)
13        .unwrap_or_else(|err| err.to_compile_error())
14        .into()
15}
16
17fn derive_on_event_enum(item: ItemEnum) -> syn::Result<TokenStream> {
18    let mut on_event_handlers = vec![];
19    let mut set_on_event_handlers = vec![];
20    let mut off_event_handlers = vec![];
21    let mut event_handlers = vec![];
22    let mut collection_event_handlers = vec![];
23
24    let doc1 = item.attrs.iter().filter(|attr| attr.path.is_ident("doc"));
25    let doc2 = doc1.clone();
26
27    let html = quote! {
28        let html = self.dyn_ref::<web_sys::HtmlElement>()?;
29    };
30    let init_callback = quote! {
31        #html
32        let callback = Some(callback.as_ref().unchecked_ref());
33    };
34    let callback = quote! {
35        callback: &Closure<dyn FnMut(web_sys::Event)>,
36    };
37
38    for variant in item.variants.iter().filter(|variant| {
39        !variant
40            .attrs
41            .iter()
42            .any(|attr| attr.path.is_ident("unimplemented"))
43    }) {
44        let ident = &variant.ident;
45        let name = ident.to_string().to_case(Case::Snake);
46        let get_ident = Ident::new(&name, Span::call_site());
47        let set_ident = Ident::new(&format!("set_{}", name), Span::call_site());
48        let name2 = ident.to_string().to_lowercase();
49        let set_ident2 = Ident::new(&format!("set_on{}", name2), Span::call_site());
50        let get_ident2 = Ident::new(&format!("on{}", name2), Span::call_site());
51
52        on_event_handlers.push(quote! {
53            Event::#ident => html.#get_ident2().ok_or(Error::EventNotHandled(Event::#ident)),
54        });
55
56        set_on_event_handlers.push(quote! {
57            Event::#ident => Ok(html.#set_ident2(callback)),
58        });
59
60        off_event_handlers.push(quote! {
61            Event::#ident => Ok(html.#set_ident2(None)),
62        });
63
64        event_handlers.push(quote! {
65            fn #get_ident(&self) -> Result<js_sys::Function, Error> {
66                #html
67                html.#get_ident2().ok_or(Error::EventNotHandled(Event::#ident))
68            }
69
70            pub fn #set_ident(&self, #callback) -> Result<(), Error> {
71                #init_callback
72                Ok(html.#set_ident2(callback))
73            }
74        });
75
76        collection_event_handlers.push(quote! {
77            fn #get_ident(&self) -> Result<Vec<js_sys::Function>, Error> {
78                self.0.iter().map(|elem| elem.#get_ident()).collect::<Result<Vec<_>, _>>()
79            }
80
81            pub fn #set_ident(&self, #callback) {
82                self.0.iter().for_each(|elem| { elem.#set_ident(callback).ok(); })
83            }
84        });
85    }
86
87    Ok(quote! {
88        #(#doc1)*
89        impl Element {
90            pub fn on(&self, event: Event) -> Result<js_sys::Function, Error> {
91                #html
92
93                match event {
94                    #(#on_event_handlers)*
95                    _ => Err(Error::EventNotImplemented(event)),
96                }
97            }
98
99            pub fn set_on(&self, event: Event, #callback) -> Result<(), Error> {
100                #init_callback
101
102                match event {
103                    #(#set_on_event_handlers)*
104                    _ => Err(Error::EventNotImplemented(event)),
105                }
106            }
107
108            pub fn set_off(&self, event: Event) -> Result<(), Error> {
109                #html
110
111                match event {
112                    #(#off_event_handlers)*
113                    _ => Err(Error::EventNotImplemented(event)),
114                }
115            }
116
117            #(#event_handlers)*
118        }
119
120        #(#doc2)*
121        impl Collection {
122            pub fn on(&self, event: Event) -> Result<Vec<js_sys::Function>, Error> {
123                self.0.iter().map(|elem| elem.on(event)).collect::<Result<Vec<_>, _>>()
124            }
125
126            pub fn set_on(&self, event: Event, #callback) {
127                self.0.iter().for_each(|elem| { elem.set_on(event, callback).ok(); })
128            }
129
130            pub fn set_off(&self, event: Event) {
131                self.0.iter().for_each(|elem| { elem.set_off(event).ok(); })
132            }
133
134        #(#collection_event_handlers)*
135        }
136    })
137}