iocraft_macros/
lib.rs

1//! This crate defines the macros used by `iocraft`.
2
3#![warn(missing_docs)]
4
5use proc_macro::TokenStream;
6use proc_macro2::Span;
7use quote::{quote, ToTokens};
8use syn::{
9    braced, parenthesized,
10    parse::{Parse, ParseStream, Parser},
11    parse_macro_input,
12    punctuated::Punctuated,
13    spanned::Spanned,
14    token::{Brace, Comma, Paren},
15    DeriveInput, Error, Expr, FieldValue, FnArg, GenericParam, Generics, Ident, ItemFn, ItemStruct,
16    Lifetime, Lit, Member, Pat, Result, Token, Type, TypePath, WhereClause, WherePredicate,
17};
18use uuid::Uuid;
19
20enum ParsedElementChild {
21    Element(ParsedElement),
22    Expr(Expr),
23}
24
25struct ParsedElement {
26    ty: TypePath,
27    props: Punctuated<FieldValue, Comma>,
28    children: Vec<ParsedElementChild>,
29}
30
31impl Parse for ParsedElement {
32    /// Parses a single element of the form:
33    ///
34    /// MyComponent(my_prop: "foo") {
35    ///     // children
36    /// }
37    fn parse(input: ParseStream) -> Result<Self> {
38        let ty: TypePath = input.parse()?;
39
40        let props = if input.peek(Paren) {
41            let props_input;
42            parenthesized!(props_input in input);
43            Punctuated::parse_terminated(&props_input)?
44        } else {
45            Punctuated::new()
46        };
47
48        let mut children = Vec::new();
49        if input.peek(Brace) {
50            let children_input;
51            braced!(children_input in input);
52            while !children_input.is_empty() {
53                if children_input.peek(Token![#]) {
54                    children_input.parse::<Token![#]>()?;
55                    let child_input;
56                    parenthesized!(child_input in children_input);
57                    children.push(ParsedElementChild::Expr(child_input.parse()?));
58                } else {
59                    children.push(ParsedElementChild::Element(children_input.parse()?));
60                }
61            }
62        }
63
64        Ok(Self {
65            props,
66            ty,
67            children,
68        })
69    }
70}
71
72impl ToTokens for ParsedElement {
73    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
74        let ty = &self.ty;
75
76        let decl_key = Uuid::new_v4().as_u128();
77
78        let key = self
79            .props
80            .iter()
81            .find_map(|FieldValue { member, expr, .. }| match member {
82                Member::Named(ident) if ident == "key" => Some(quote!((#decl_key, #expr))),
83                _ => None,
84            })
85            .unwrap_or_else(|| quote!(#decl_key));
86
87        let prop_assignments = self
88            .props
89            .iter()
90            .filter_map(|FieldValue { member, expr, .. }| match member {
91                Member::Named(ident) if ident == "key" => None,
92                _ => Some(match expr {
93                    Expr::Lit(lit) => match &lit.lit {
94                        Lit::Int(lit) if lit.suffix() == "pct" => {
95                            let value = lit.base10_parse::<f32>().unwrap();
96                            quote!(_iocraft_props.#member = ::iocraft::Percent(#value).into())
97                        }
98                        Lit::Float(lit) if lit.suffix() == "pct" => {
99                            let value = lit.base10_parse::<f32>().unwrap();
100                            quote!(_iocraft_props.#member = ::iocraft::Percent(#value).into())
101                        }
102                        _ => quote!(_iocraft_props.#member = (#expr).into()),
103                    },
104                    _ => quote!(_iocraft_props.#member = (#expr).into()),
105                }),
106            })
107            .collect::<Vec<_>>();
108
109        let set_children = if !self.children.is_empty() {
110            let children = self.children.iter().map(|child| match child {
111                ParsedElementChild::Element(child) => quote!(#child),
112                ParsedElementChild::Expr(expr) => quote!(#expr),
113            });
114            Some(quote! {
115                #(::iocraft::extend_with_elements(&mut _iocraft_element.props.children, #children);)*
116            })
117        } else {
118            None
119        };
120
121        tokens.extend(quote! {
122            {
123                type Props<'a> = <#ty as ::iocraft::ElementType>::Props<'a>;
124                let mut _iocraft_props: Props = Default::default();
125                #(#prop_assignments;)*
126                let mut _iocraft_element = ::iocraft::Element::<#ty>{
127                    key: ::iocraft::ElementKey::new(#key),
128                    props: _iocraft_props,
129                };
130                #set_children
131                _iocraft_element
132            }
133        });
134    }
135}
136
137// This is documented in the `iocraft` crate instead so that links to `iocraft` types resolve correctly.
138#[allow(missing_docs)]
139#[proc_macro]
140pub fn element(input: TokenStream) -> TokenStream {
141    let element = parse_macro_input!(input as ParsedElement);
142    quote!(#element).into()
143}
144
145struct ParsedProps {
146    def: ItemStruct,
147}
148
149impl Parse for ParsedProps {
150    fn parse(input: ParseStream) -> Result<Self> {
151        let def: ItemStruct = input.parse()?;
152
153        // Make sure there are no props named "key", as that's a reserved name.
154        for field in &def.fields {
155            if let Some(ident) = &field.ident {
156                if ident == "key" {
157                    return Err(Error::new(
158                        ident.span(),
159                        "the `key` property name is reserved",
160                    ));
161                }
162            }
163        }
164
165        Ok(Self { def })
166    }
167}
168
169impl ToTokens for ParsedProps {
170    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
171        let def = &self.def;
172        let name = &def.ident;
173        let where_clause = &def.generics.where_clause;
174
175        let has_generics = !def.generics.params.is_empty();
176        let lifetime_generic_count = def
177            .generics
178            .params
179            .iter()
180            .filter(|param| matches!(param, GenericParam::Lifetime(_)))
181            .count();
182
183        let generics = &def.generics;
184
185        let generics_names = def.generics.params.iter().map(|param| match param {
186            GenericParam::Type(ty) => {
187                let name = &ty.ident;
188                quote!(#name)
189            }
190            GenericParam::Lifetime(lt) => {
191                let name = &lt.lifetime;
192                quote!(#name)
193            }
194            GenericParam::Const(c) => {
195                let name = &c.ident;
196                quote!(#name)
197            }
198        });
199        let bracketed_generic_names = match has_generics {
200            true => quote!(<#(#generics_names),*>),
201            false => quote!(),
202        };
203
204        // If the struct is generic over lifetimes, emit code that will break things at compile
205        // time when the struct is not covariant with respect to its lifetimes.
206        if lifetime_generic_count > 0 {
207            let generic_decls = {
208                let mut lifetime_index = 0;
209                def.generics.params.iter().map(move |param| match param {
210                    GenericParam::Lifetime(_) => {
211                        let a = Lifetime::new(
212                            format!("'a{}", lifetime_index).as_str(),
213                            Span::call_site(),
214                        );
215                        let b = Lifetime::new(
216                            format!("'b{}", lifetime_index).as_str(),
217                            Span::call_site(),
218                        );
219                        lifetime_index += 1;
220                        quote!(#a, #b: #a)
221                    }
222                    _ => quote!(#param),
223                })
224            };
225
226            let test_args = ["a", "b"].iter().map(|arg| {
227                let mut lifetime_index = 0;
228                let generic_params = def.generics.params.iter().map(|param| match param {
229                    GenericParam::Type(ty) => {
230                        let name = &ty.ident;
231                        quote!(#name)
232                    }
233                    GenericParam::Lifetime(_) => {
234                        let lt = Lifetime::new(
235                            format!("'{}{}", arg, lifetime_index).as_str(),
236                            Span::call_site(),
237                        );
238                        lifetime_index += 1;
239                        quote!(#lt)
240                    }
241                    GenericParam::Const(c) => {
242                        let name = &c.ident;
243                        quote!(#name)
244                    }
245                });
246                let arg_ident = Ident::new(arg, Span::call_site());
247                quote!(#arg_ident: &#name<#(#generic_params),*>)
248            });
249
250            tokens.extend(quote! {
251                const _: () = {
252                    fn take_two<T>(_a: T, _b: T) {}
253
254                    fn test_type_covariance<#(#generic_decls),*>(#(#test_args),*) {
255                        take_two(a, b)
256                    }
257                };
258            });
259        }
260
261        tokens.extend(quote! {
262            unsafe impl #generics ::iocraft::Props for #name #bracketed_generic_names #where_clause {}
263        });
264    }
265}
266
267/// Makes a struct available for use as component properties.
268///
269/// Most importantly, this marks a struct as being
270/// [covariant](https://doc.rust-lang.org/nomicon/subtyping.html). If the struct is not actually
271/// covariant, compilation will fail.
272#[proc_macro_derive(Props)]
273pub fn derive_props(item: TokenStream) -> TokenStream {
274    let props = parse_macro_input!(item as ParsedProps);
275    quote!(#props).into()
276}
277
278struct ParsedComponent {
279    f: ItemFn,
280    props_type: Option<Box<Type>>,
281    impl_args: Vec<proc_macro2::TokenStream>,
282}
283
284impl Parse for ParsedComponent {
285    fn parse(input: ParseStream) -> Result<Self> {
286        let f: ItemFn = input.parse()?;
287
288        let mut props_type = None;
289        let mut impl_args = Vec::new();
290
291        for arg in &f.sig.inputs {
292            match arg {
293                FnArg::Typed(arg) => {
294                    let name = match &*arg.pat {
295                        Pat::Ident(arg) => arg.ident.to_string(),
296                        _ => return Err(Error::new(arg.pat.span(), "invalid argument")),
297                    };
298
299                    match name.as_str() {
300                        "props" | "_props" => {
301                            if props_type.is_some() {
302                                return Err(Error::new(arg.span(), "duplicate `props` argument"));
303                            }
304                            match &*arg.ty {
305                                Type::Reference(r) => {
306                                    props_type = Some(r.elem.clone());
307                                    impl_args.push(quote!(props));
308                                }
309                                _ => {
310                                    return Err(Error::new(
311                                        arg.ty.span(),
312                                        "invalid `props` type (must be a reference)",
313                                    ))
314                                }
315                            }
316                        }
317                        "hooks" | "_hooks" => match &*arg.ty {
318                            Type::Reference(_) => {
319                                impl_args.push(quote!(&mut hooks));
320                            }
321                            Type::Path(_) => {
322                                impl_args.push(quote!(hooks));
323                            }
324                            _ => {
325                                return Err(Error::new(
326                                    arg.ty.span(),
327                                    "invalid `hooks` type (must be a reference or a value)",
328                                ))
329                            }
330                        },
331                        _ => {
332                            return Err(Error::new(
333                                arg.span(),
334                                "unexpected argument (must be named `props` or `hooks`)",
335                            ))
336                        }
337                    }
338                }
339                _ => return Err(Error::new(arg.span(), "invalid argument")),
340            }
341        }
342
343        Ok(Self {
344            f,
345            props_type,
346            impl_args,
347        })
348    }
349}
350
351impl ToTokens for ParsedComponent {
352    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
353        let attrs = &self.f.attrs;
354        let vis = &self.f.vis;
355        let name = &self.f.sig.ident;
356        let args = &self.f.sig.inputs;
357        let block = &self.f.block;
358        let output = &self.f.sig.output;
359        let generics = &self.f.sig.generics;
360        let lifetime_generics = {
361            Generics {
362                params: generics
363                    .params
364                    .iter()
365                    .filter(|param| matches!(param, GenericParam::Lifetime(_)))
366                    .cloned()
367                    .collect(),
368                where_clause: generics
369                    .where_clause
370                    .as_ref()
371                    .map(|where_clause| WhereClause {
372                        where_token: where_clause.where_token,
373                        predicates: where_clause
374                            .predicates
375                            .iter()
376                            .filter(|predicate| matches!(predicate, WherePredicate::Lifetime(_)))
377                            .cloned()
378                            .collect(),
379                    }),
380                ..generics.clone()
381            }
382        };
383        let (lifetime_impl_generics, _lifetime_ty_generics, lifetime_where_clause) =
384            lifetime_generics.split_for_impl();
385        let type_generics = {
386            Generics {
387                params: generics
388                    .params
389                    .iter()
390                    .filter(|param| !matches!(param, GenericParam::Lifetime(_)))
391                    .cloned()
392                    .collect(),
393                where_clause: generics
394                    .where_clause
395                    .as_ref()
396                    .map(|where_clause| WhereClause {
397                        where_token: where_clause.where_token,
398                        predicates: where_clause
399                            .predicates
400                            .iter()
401                            .filter(|predicate| !matches!(predicate, WherePredicate::Lifetime(_)))
402                            .cloned()
403                            .collect(),
404                    }),
405                ..generics.clone()
406            }
407        };
408        let (impl_generics, ty_generics, where_clause) = type_generics.split_for_impl();
409        let ty_generic_names = type_generics.params.iter().filter_map(|param| match param {
410            GenericParam::Type(ty) => {
411                let name = &ty.ident;
412                Some(quote!(#name))
413            }
414            _ => None,
415        });
416        let impl_args = &self.impl_args;
417
418        let props_type_name = self
419            .props_type
420            .as_ref()
421            .map(|ty| quote!(#ty))
422            .unwrap_or_else(|| quote!(::iocraft::NoProps));
423
424        tokens.extend(quote! {
425            #(#attrs)*
426            #vis struct #name #impl_generics {
427                _marker: std::marker::PhantomData<fn(#(#ty_generic_names),*)>,
428            }
429
430            impl #impl_generics #name #ty_generics #where_clause {
431                fn implementation #lifetime_impl_generics (#args) #output #lifetime_where_clause #block
432            }
433
434            impl #impl_generics ::iocraft::Component for #name #ty_generics #where_clause {
435                type Props<'a> = #props_type_name;
436
437                fn new(_props: &Self::Props<'_>) -> Self {
438                    Self{
439                        _marker: std::marker::PhantomData,
440                    }
441                }
442
443                fn update(&mut self, props: &mut Self::Props<'_>, mut hooks: ::iocraft::Hooks, updater: &mut ::iocraft::ComponentUpdater) {
444                    let mut e = {
445                        let mut hooks = hooks.with_context_stack(updater.component_context_stack());
446                        Self::implementation(#(#impl_args),*).into()
447                    };
448                    updater.set_transparent_layout(true);
449                    updater.update_children([&mut e], None);
450                }
451            }
452        });
453    }
454}
455
456/// Defines a custom component type.
457///
458/// Custom components are defined by adding this macro to a function that returns an `Element`:
459///
460/// ```
461/// # use iocraft::prelude::*;
462/// # use std::time::Duration;
463/// #[component]
464/// fn Counter(mut hooks: Hooks) -> impl Into<AnyElement<'static>> {
465///     let mut count = hooks.use_state(|| 0);
466///
467///     hooks.use_future(async move {
468///         loop {
469///             smol::Timer::after(Duration::from_millis(100)).await;
470///             count += 1;
471///         }
472///     });
473///
474///     element! {
475///         Text(color: Color::Blue, content: format!("counter: {}", count))
476///     }
477/// }
478/// ```
479///
480/// The function is allowed to take up to two arguments, one named `props`, for the component's
481/// properties and one named `hooks`, for hooks.
482///
483/// Here is an example of a component that takes a reference to a `Vec` of `User` structs via properties:
484///
485/// ```
486/// # use iocraft::prelude::*;
487/// # struct User {
488/// #     id: i32,
489/// #     name: String,
490/// #     email: String,
491/// # }
492/// #[derive(Default, Props)]
493/// struct UsersTableProps<'a> {
494///     users: Option<&'a Vec<User>>,
495/// }
496///
497/// #[component]
498/// fn UsersTable<'a>(props: &UsersTableProps<'a>) -> impl Into<AnyElement<'a>> {
499///     element! {
500///         View(
501///             margin_top: 1,
502///             margin_bottom: 1,
503///             flex_direction: FlexDirection::Column,
504///             width: 60,
505///             border_style: BorderStyle::Round,
506///             border_color: Color::Cyan,
507///         ) {
508///             View(border_style: BorderStyle::Single, border_edges: Edges::Bottom, border_color: Color::Grey) {
509///                 View(width: 10pct, justify_content: JustifyContent::End, padding_right: 2) {
510///                     Text(content: "Id", weight: Weight::Bold, decoration: TextDecoration::Underline)
511///                 }
512///
513///                 View(width: 40pct) {
514///                     Text(content: "Name", weight: Weight::Bold, decoration: TextDecoration::Underline)
515///                 }
516///
517///                 View(width: 50pct) {
518///                     Text(content: "Email", weight: Weight::Bold, decoration: TextDecoration::Underline)
519///                 }
520///             }
521///
522///             #(props.users.map(|users| users.iter().enumerate().map(|(i, user)| element! {
523///                 View(background_color: if i % 2 == 0 { None } else { Some(Color::DarkGrey) }) {
524///                     View(width: 10pct, justify_content: JustifyContent::End, padding_right: 2) {
525///                         Text(content: user.id.to_string())
526///                     }
527///
528///                     View(width: 40pct) {
529///                         Text(content: user.name.clone())
530///                     }
531///
532///                     View(width: 50pct) {
533///                         Text(content: user.email.clone())
534///                     }
535///                 }
536///             })).into_iter().flatten())
537///         }
538///     }
539/// }
540/// ```
541///
542/// Components can also be generic:
543///
544/// ```
545/// # use iocraft::prelude::*;
546/// #[derive(Default, Props)]
547/// struct MyGenericComponentProps<T: Send + Sync> {
548///     items: Vec<T>,
549/// }
550///
551/// #[component]
552/// fn MyGenericComponent<T: Send + Sync + 'static>(
553///     _props: &MyGenericComponentProps<T>,
554/// ) -> impl Into<AnyElement<'static>> {
555///     element!(View)
556/// }
557/// ```
558///
559/// However, note that generic type parameters must be `'static`.
560#[proc_macro_attribute]
561pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
562    let component = parse_macro_input!(item as ParsedComponent);
563    quote!(#component).into()
564}
565
566#[doc(hidden)]
567#[proc_macro_attribute]
568pub fn with_layout_style_props(_attr: TokenStream, item: TokenStream) -> TokenStream {
569    let layout_style_fields = [
570        quote! {
571            /// Sets the display mode for the element. Defaults to [`Display::Flex`].
572            ///
573            /// See [the MDN documentation for display](https://developer.mozilla.org/en-US/docs/Web/CSS/display).
574            pub display: ::iocraft::Display
575        },
576        quote! {
577            /// Sets the width of the element.
578            pub width: ::iocraft::Size
579        },
580        quote! {
581            /// Sets the height of the element.
582            pub height: ::iocraft::Size
583        },
584        quote! {
585            /// Sets the minimum width of the element.
586            pub min_width: ::iocraft::Size
587        },
588        quote! {
589            /// Sets the minimum height of the element.
590            pub min_height: ::iocraft::Size
591        },
592        quote! {
593            /// Sets the maximum width of the element.
594            pub max_width: ::iocraft::Size
595        },
596        quote! {
597            /// Sets the maximum height of the element.
598            pub max_height: ::iocraft::Size
599        },
600        quote! {
601            /// Defines the gaps in between rows or columns of flex items.
602            ///
603            /// See [the MDN documentation for gap](https://developer.mozilla.org/en-US/docs/Web/CSS/gap).
604            pub gap: ::iocraft::Gap
605        },
606        quote! {
607            /// Defines the gaps in between columns of flex items.
608            ///
609            /// See [the MDN documentation for column-gap](https://developer.mozilla.org/en-US/docs/Web/CSS/column-gap).
610            pub column_gap: ::iocraft::Gap
611        },
612        quote! {
613            /// Defines the gaps in between rows of flex items.
614            ///
615            /// See [the MDN documentation for row-gap](https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap).
616            pub row_gap: ::iocraft::Gap
617        },
618        quote! {
619            /// Defines the area to reserve around the element's content, but inside the border.
620            ///
621            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
622            pub padding: ::iocraft::Padding
623        },
624        quote! {
625            /// Defines the area to reserve above the element's content, but inside the border.
626            ///
627            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
628            pub padding_top: ::iocraft::Padding
629        },
630        quote! {
631            /// Defines the area to reserve to the right of the element's content, but inside the border.
632            ///
633            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
634            pub padding_right: ::iocraft::Padding
635        },
636        quote! {
637            /// Defines the area to reserve below the element's content, but inside the border.
638            ///
639            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
640            pub padding_bottom: ::iocraft::Padding
641        },
642        quote! {
643            /// Defines the area to reserve to the left of the element's content, but inside the border.
644            ///
645            /// See [the MDN documentation for padding](https://developer.mozilla.org/en-US/docs/Web/CSS/padding).
646            pub padding_left: ::iocraft::Padding
647        },
648        quote! {
649            /// Controls how the element is layed out and whether it will be controlled by the flexbox.
650            pub position: ::iocraft::Position
651        },
652        quote! {
653            /// Sets the position of a positioned element.
654            ///
655            /// See [the MDN documentation for inset](https://developer.mozilla.org/en-US/docs/Web/CSS/inset).
656            pub inset: ::iocraft::Inset
657        },
658        quote! {
659            /// Sets the vertical position of a positioned element.
660            ///
661            /// See [the MDN documentation for top](https://developer.mozilla.org/en-US/docs/Web/CSS/top).
662            pub top: ::iocraft::Inset
663        },
664        quote! {
665            /// Sets the horizontal position of a positioned element.
666            ///
667            /// See [the MDN documentation for right](https://developer.mozilla.org/en-US/docs/Web/CSS/right).
668            pub right: ::iocraft::Inset
669        },
670        quote! {
671            /// Sets the vertical position of a positioned element.
672            ///
673            /// See [the MDN documentation for bottom](https://developer.mozilla.org/en-US/docs/Web/CSS/bottom).
674            pub bottom: ::iocraft::Inset
675        },
676        quote! {
677            /// Sets the horizontal position of a positioned element.
678            ///
679            /// See [the MDN documentation for left](https://developer.mozilla.org/en-US/docs/Web/CSS/left).
680            pub left: ::iocraft::Inset
681        },
682        quote! {
683            /// Defines the area to reserve around the element's content, but outside the border.
684            ///
685            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
686            pub margin: ::iocraft::Margin
687        },
688        quote! {
689            /// Defines the area to reserve above the element's content, but outside the border.
690            ///
691            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
692            pub margin_top: ::iocraft::Margin
693        },
694        quote! {
695            /// Defines the area to reserve to the right of the element's content, but outside the border.
696            ///
697            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
698            pub margin_right: ::iocraft::Margin
699        },
700        quote! {
701            /// Defines the area to reserve below the element's content, but outside the border.
702            ///
703            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
704            pub margin_bottom: ::iocraft::Margin
705        },
706        quote! {
707            /// Defines the area to reserve to the left of the element's content, but outside the border.
708            ///
709            /// See [the MDN documentation for margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin).
710            pub margin_left: ::iocraft::Margin
711        },
712        quote! {
713            /// Defines how items are placed along the main axis of a flex container.
714            ///
715            /// See [the MDN documentation for flex-direction](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-direction).
716            pub flex_direction: ::iocraft::FlexDirection
717        },
718        quote! {
719            /// Defines whether items are forced onto one line or can wrap into multiple lines.
720            ///
721            /// See [the MDN documentation for flex-wrap](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-wrap).
722            pub flex_wrap: ::iocraft::FlexWrap
723        },
724        quote! {
725            /// Sets the initial main size of a flex item.
726            ///
727            /// See [the MDN documentation for flex-basis](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis).
728            pub flex_basis: ::iocraft::FlexBasis
729        },
730        quote! {
731            /// Sets the flex grow factor, which specifies how much free space should be assigned
732            /// to the item's main size.
733            ///
734            /// See [the MDN documentation for flex-grow](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow).
735            pub flex_grow: f32
736        },
737        quote! {
738            /// Sets the flex shrink factor, which specifies how the item should shrink when the
739            /// container doesn't have enough room for all flex items.
740            ///
741            /// See [the MDN documentation for flex-shrink](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-shrink).
742            pub flex_shrink: Option<f32>
743        },
744        quote! {
745            /// Controls the alignment of items along the cross axis of a flex container.
746            ///
747            /// See [the MDN documentation for align-items](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items).
748            pub align_items: Option<::iocraft::AlignItems>
749        },
750        quote! {
751            /// Controls the distribution of space between and around items along a flex container's cross axis.
752            ///
753            /// See [the MDN documentation for align-content](https://developer.mozilla.org/en-US/docs/Web/CSS/align-content).
754            pub align_content: Option<::iocraft::AlignContent>
755        },
756        quote! {
757            /// Controls the distribution of space between and around items along a flex container's main axis.
758            ///
759            /// See [the MDN documentation for justify-content](https://developer.mozilla.org/en-US/docs/Web/CSS/justify-content).
760            pub justify_content: Option<::iocraft::JustifyContent>
761        },
762    ]
763    .map(|tokens| syn::Field::parse_named.parse2(tokens).unwrap());
764
765    let mut ast = parse_macro_input!(item as DeriveInput);
766    match &mut ast.data {
767        syn::Data::Struct(ref mut struct_data) => {
768            if let syn::Fields::Named(fields) = &mut struct_data.fields {
769                fields.named.extend(layout_style_fields.iter().cloned());
770            }
771
772            let struct_name = &ast.ident;
773            let field_assignments = layout_style_fields.iter().map(|field| {
774                let field_name = &field.ident;
775                quote! { #field_name: self.#field_name }
776            });
777
778            let where_clause = &ast.generics.where_clause;
779
780            let has_generics = !ast.generics.params.is_empty();
781            let generics = &ast.generics;
782
783            let generics_names = ast.generics.params.iter().map(|param| match param {
784                GenericParam::Type(ty) => {
785                    let name = &ty.ident;
786                    quote!(#name)
787                }
788                GenericParam::Lifetime(lt) => {
789                    let name = &lt.lifetime;
790                    quote!(#name)
791                }
792                GenericParam::Const(c) => {
793                    let name = &c.ident;
794                    quote!(#name)
795                }
796            });
797            let bracketed_generic_names = match has_generics {
798                true => quote!(<#(#generics_names),*>),
799                false => quote!(),
800            };
801
802            quote! {
803                #ast
804
805                impl #generics #struct_name #bracketed_generic_names #where_clause {
806                    /// Returns the layout style based on the layout-related fields of this struct.
807                    pub fn layout_style(&self) -> ::iocraft::LayoutStyle {
808                        ::iocraft::LayoutStyle{
809                            #(#field_assignments,)*
810                        }
811                    }
812                }
813            }
814            .into()
815        }
816        _ => panic!("`with_layout_style_props` can only be used with structs "),
817    }
818}