horfimbor_client_derive/
lib.rs1use proc_macro::{self, TokenStream};
2
3use proc_macro2::{Span, TokenStream as TokenStream2};
4use syn::__private::quote::quote;
5use syn::{Data, DeriveInput, Error, parse_macro_input};
6
7macro_rules! derive_error {
8 ($string: tt) => {
9 Error::new(Span::call_site(), $string)
10 .to_compile_error()
11 .into()
12 };
13}
14
15#[allow(clippy::too_many_lines)]
16#[proc_macro_derive(WebComponent, attributes(component, optionnal))]
17pub fn derive_web_component(input: TokenStream) -> TokenStream {
18 let input: DeriveInput = parse_macro_input!(input as DeriveInput);
19
20 let attrs = &input.attrs;
21
22 let Some(component) = attrs.iter().find(|attr| attr.path().is_ident("component")) else {
23 return derive_error!("WebComponent need a base component");
24 };
25
26 let component: syn::Ident = match component.parse_args() {
27 Ok(s) => s,
28 Err(_) => {
29 return derive_error!("attribute 'state' cannot be parsed");
30 }
31 };
32
33 let name = &input.ident;
34 let data = &input.data;
35
36 let mut opt_struct = TokenStream2::new();
37 let mut default_props = TokenStream2::new();
38 let mut observed = TokenStream2::new();
39 let mut get_attributes = TokenStream2::new();
40 let mut check_none = TokenStream2::new();
41 let mut real_props = TokenStream2::new();
42
43 match data {
44 Data::Struct(data_struct) => {
45 for field in &data_struct.fields {
46 let Some(ident) = &field.ident else {
47 break;
48 };
49 let kind = &field.ty;
50 let attribute_html = format!("{ident}").replace('_', "-");
51
52 let attrs = &field.attrs;
53
54 if attrs.iter().any(|a| a.path().is_ident("optionnal")) {
55 opt_struct.extend(quote! {
56 #ident : #kind,
57 });
58
59 check_none.extend(quote! {
60 let #ident = match ctx.props().#ident.clone(){
61 None => None,
62 Some(s) => {
63 if s.is_empty(){ None }
64 else{ Some(s) }
65 }
66 };
67 });
68 } else {
69 opt_struct.extend(quote! {
70 #ident : Option<#kind>,
71 });
72
73 check_none.extend(quote! {
74 let Some(#ident) = ctx.props().#ident.clone() else{
75 return html!();
76 };
77 if #ident.is_empty() {
78 return html!();
79 }
80 });
81 }
82 default_props.extend(quote! {
83 #ident : this.get_attribute(#attribute_html),
84 });
85 observed.extend(quote! {
86 #attribute_html,
87 });
88 get_attributes.extend(quote! {
89 #ident: this.get_attribute(#attribute_html),
90 });
91 real_props.extend(quote! {
92 #ident={{#ident.clone()}}
93 });
94 }
95 }
96 _ => return derive_error!("WebComponent is only implemented for struct"),
97 }
98
99 let output = quote! {
100 pub mod optional {
101
102 use yew::AppHandle;
103 use yew::prelude::*;
104 use custom_elements::CustomElement;
105 use web_sys::HtmlElement;
106 use super::#component;
107
108 #[derive(Default, Properties, PartialEq)]
109 pub struct #name {
110 #opt_struct
111 }
112
113 struct WrappedComponent{}
114
115 impl Component for WrappedComponent{
116 type Message = ();
117 type Properties = #name;
118
119 fn create(_ctx: &Context<Self>) -> Self {
120 Self {}
121 }
122
123 fn changed(&mut self, _ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
124 true
125 }
126
127 fn view(&self, ctx: &Context<Self>) -> Html {
128 #check_none
129 html! {
130 <#component #real_props>
131 </#component>
132 }
133 }
134
135 }
136
137 #[derive(Default)]
138 pub struct ComponentWrapper {
139 content: Option<AppHandle<WrappedComponent>>,
140 }
141
142 impl CustomElement for ComponentWrapper {
143 fn inject_children(&mut self, this: &HtmlElement) {
144 let props = #name {
145 #default_props
146 };
147
148 self.content = Some(
149 yew::Renderer::<WrappedComponent>::with_root_and_props(this.clone().into(), props).render(),
150 );
151 }
152
153 fn shadow() -> bool {
154 false
155 }
156
157 fn observed_attributes() -> &'static [&'static str] {
158 &[#observed]
159 }
160
161 fn attribute_changed_callback(
162 &mut self,
163 this: &HtmlElement,
164 name: String,
165 old_value: Option<String>,
166 new_value: Option<String>,
167 ) {
168 let props = #name {
169 #get_attributes
170 };
171 match &mut self.content {
172 None => {}
173 Some(handle) => {
174 handle.update(props);
175 }
176 }
177 }
178 }
179 }
180 };
181
182 output.into()
183}