web_sys_query_derive/
lib.rs1use 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}