summary refs log tree commit diff stats
path: root/rocie-macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'rocie-macros/src')
-rw-r--r--rocie-macros/src/form/generate.rs136
-rw-r--r--rocie-macros/src/form/mod.rs11
-rw-r--r--rocie-macros/src/form/parse.rs18
3 files changed, 146 insertions, 19 deletions
diff --git a/rocie-macros/src/form/generate.rs b/rocie-macros/src/form/generate.rs
index 5642e6a..89acea8 100644
--- a/rocie-macros/src/form/generate.rs
+++ b/rocie-macros/src/form/generate.rs
@@ -1,7 +1,7 @@
 use proc_macro::TokenStream;
 use proc_macro2::TokenStream as TokenStream2;
 use quote::{format_ident, quote};
-use syn::{Ident, Type, parse_macro_input};
+use syn::{Ident, Path, Type, TypePath, parse_macro_input, punctuated::Punctuated};
 
 use crate::form::{ParsedChild, ParsedInput};
 
@@ -33,7 +33,9 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
     let output = quote!({
         use crate::components::{
             input_placeholder::InputPlaceholder,
-            select_placeholder::SelectPlaceholder
+            select_placeholder::SelectPlaceholder,
+            textarea_placeholder::TextareaPlaceholder,
+            checkbox_placeholder::CheckboxPlaceholder,
         };
 
         use leptos::{
@@ -48,7 +50,7 @@ pub fn form_impl(item: TokenStream) -> TokenStream {
                 Set,
                 Show,
             },
-            html::{Input, Select},
+            html::{Input, Select, Textarea},
             web_sys::SubmitEvent
         };
 
@@ -126,6 +128,8 @@ fn node_ref(child: &ParsedChild) -> TokenStream2 {
         let (name, ty) = match child {
             ParsedChild::Input { name, .. } => (name, quote! {Input}),
             ParsedChild::Select { name, .. } => (name, quote! {Select}),
+            ParsedChild::Textarea { name, .. } => (name, quote! {Textarea}),
+            ParsedChild::Checkbox { name, .. } => (name, quote! {Input}),
             ParsedChild::Show { .. } => unreachable!("Filtered out before"),
         };
 
@@ -149,7 +153,9 @@ fn signal(child: &ParsedChild) -> TokenStream2 {
                 let (#signal_name_get, #signal_name_set) = signal(None);
             }
         }
-        ParsedChild::Select { .. } => quote! {},
+        ParsedChild::Textarea { .. }
+        | ParsedChild::Checkbox { .. }
+        | ParsedChild::Select { .. } => quote! {},
         ParsedChild::Show { children, .. } => children.iter().map(signal).collect(),
     }
 }
@@ -185,7 +191,9 @@ fn auto_complete_signal(child: &ParsedChild) -> TokenStream2 {
                 quote! {}
             }
         }
-        ParsedChild::Select { .. } => quote! {},
+        ParsedChild::Select { .. }
+        | ParsedChild::Textarea { .. }
+        | ParsedChild::Checkbox { .. } => quote! {},
         ParsedChild::Show { children, .. } => children.iter().map(auto_complete_signal).collect(),
     }
 }
@@ -238,14 +246,16 @@ fn reactive_signal(child: &ParsedChild) -> TokenStream2 {
                 quote! {}
             }
         }
-        ParsedChild::Select { .. } => quote! {},
+        ParsedChild::Select { .. }
+        | ParsedChild::Textarea { .. }
+        | ParsedChild::Checkbox { .. } => quote! {},
         ParsedChild::Show { children, .. } => children.iter().map(reactive_signal).collect(),
     }
 }
 
 fn type_verification(child: &ParsedChild) -> TokenStream2 {
     match child {
-        ParsedChild::Input { .. } => {
+        ParsedChild::Input { .. } | ParsedChild::Textarea { .. } | ParsedChild::Checkbox { .. } => {
             quote! {}
         }
         ParsedChild::Select { name, options, .. } => {
@@ -278,24 +288,65 @@ fn type_verification(child: &ParsedChild) -> TokenStream2 {
 
 fn get_names(input: &ParsedChild) -> Vec<&Ident> {
     match input {
-        ParsedChild::Input { name, .. } => vec![name],
-        ParsedChild::Select { name, .. } => vec![name],
+        ParsedChild::Input { name, .. }
+        | ParsedChild::Textarea { name, .. }
+        | ParsedChild::Checkbox { name, .. }
+        | ParsedChild::Select { name, .. } => vec![name],
         ParsedChild::Show { children, .. } => children.iter().flat_map(get_names).collect(),
     }
 }
 
 fn output_struct_definition(children: &[ParsedChild]) -> TokenStream2 {
-    fn get_rust_types(input: &ParsedChild) -> Vec<&Type> {
+    macro_rules! mk_type {
+        ($name:ident) => {{
+            let segments = {
+                let mut p = Punctuated::new();
+                p.push(syn::PathSegment {
+                    ident: format_ident!(stringify!($name)),
+                    arguments: syn::PathArguments::None,
+                });
+                p
+            };
+
+            Type::Path(TypePath {
+                qself: None,
+                path: Path {
+                    leading_colon: None,
+                    segments,
+                },
+            })
+        }};
+    }
+
+    let string: Type = mk_type!(String);
+    let boolean: Type = mk_type!(bool);
+
+    fn get_rust_types<'a>(
+        input: &'a ParsedChild,
+        string: &'a Type,
+        bool: &'a Type,
+    ) -> Vec<&'a Type> {
         match input {
-            ParsedChild::Input { rust_type, .. } => vec![rust_type],
-            ParsedChild::Select { rust_type, .. } => vec![rust_type],
-            ParsedChild::Show { children, .. } => {
-                children.iter().flat_map(get_rust_types).collect()
+            ParsedChild::Textarea { .. } => {
+                vec![string]
             }
+            ParsedChild::Checkbox { .. } => {
+                vec![bool]
+            }
+            ParsedChild::Input { rust_type, .. } | ParsedChild::Select { rust_type, .. } => {
+                vec![rust_type]
+            }
+            ParsedChild::Show { children, .. } => children
+                .iter()
+                .flat_map(|i| get_rust_types(i, string, bool))
+                .collect(),
         }
     }
+
     let names = children.iter().flat_map(get_names);
-    let rust_types = children.iter().flat_map(get_rust_types);
+    let rust_types = children
+        .iter()
+        .flat_map(|i| get_rust_types(i, &string, &boolean));
 
     quote! {
         struct Inputs {
@@ -358,6 +409,42 @@ fn fetch_value(child: &ParsedChild) -> TokenStream2 {
                 };
             }
         }
+        ParsedChild::Textarea { name, .. } => {
+            let node_ref_name = node_ref_name(name);
+
+            quote! {
+                let #name: String = {
+                    let output = #node_ref_name
+                        .get()
+                        // event handlers can only fire after the view
+                        // is mounted to the DOM, so the `NodeRef` will be `Some`
+                        .expect("<textarea> to exist")
+                        .value();
+
+                    let fin: String = output.to_owned();
+
+                    fin
+                };
+            }
+        }
+        ParsedChild::Checkbox { name, .. } => {
+            let node_ref_name = node_ref_name(name);
+
+            quote! {
+                let #name: bool = {
+                    let output = #node_ref_name
+                        .get()
+                        // event handlers can only fire after the view
+                        // is mounted to the DOM, so the `NodeRef` will be `Some`
+                        .expect("<checkbox> to exist")
+                        .value();
+
+                    let fin: bool = if output == "on" { true } else { false };
+
+                    fin
+                };
+            }
+        }
         ParsedChild::Select {
             name, rust_type, ..
         } => {
@@ -370,7 +457,7 @@ fn fetch_value(child: &ParsedChild) -> TokenStream2 {
                             .get()
                             // event handlers can only fire after the view
                             // is mounted to the DOM, so the `NodeRef` will be `Some`
-                            .expect("<input> to exist")
+                            .expect("<select> to exist")
                             .value();
 
                         let fin: Result<#rust_type, leptos::error::Error> = output
@@ -384,6 +471,9 @@ fn fetch_value(child: &ParsedChild) -> TokenStream2 {
                             ok
                         } ,
                         Err(err) => {
+                            // TODO: This can certainly happen (think of an empty string in a numeric input.)
+                            // As such, we should have an error field per input, that we can populate with the
+                            // error we received here. <2025-12-30>
                             unreachable!("Should be ruled out at compile time: {err}")
                         }
                     }
@@ -450,6 +540,20 @@ fn input_html(child: &ParsedChild) -> TokenStream2 {
                 </Show>
             }
         }
+        ParsedChild::Textarea { name, label, .. } => {
+            let node_ref_name = node_ref_name(name);
+
+            quote! {
+                <TextareaPlaceholder label=#label node_ref=#node_ref_name />
+            }
+        }
+        ParsedChild::Checkbox { name, label } => {
+            let node_ref_name = node_ref_name(name);
+
+            quote! {
+                <CheckboxPlaceholder label=#label node_ref=#node_ref_name />
+            }
+        }
         ParsedChild::Select {
             name,
             label,
diff --git a/rocie-macros/src/form/mod.rs b/rocie-macros/src/form/mod.rs
index b165750..978b081 100644
--- a/rocie-macros/src/form/mod.rs
+++ b/rocie-macros/src/form/mod.rs
@@ -5,14 +5,12 @@ mod parse;
 
 pub use generate::form_impl;
 
-#[derive(Debug)]
 pub struct ParsedOnSubmit {
     inputs: Vec<Ident>,
     block: Expr,
     pub(crate) should_use_move: bool,
 }
 
-#[derive(Debug)]
 pub enum ParsedChild {
     Input {
         name: Ident,
@@ -22,6 +20,10 @@ pub enum ParsedChild {
         reactive: Option<Ident>,
         auto_complete: Option<Ident>,
     },
+    Checkbox {
+        name: Ident,
+        label: LitStr,
+    },
     Select {
         name: Ident,
         label: LitStr,
@@ -32,9 +34,12 @@ pub enum ParsedChild {
         when: Expr,
         children: Vec<ParsedChild>,
     },
+    Textarea {
+        name: Ident,
+        label: LitStr,
+    },
 }
 
-#[derive(Debug)]
 pub struct ParsedInput {
     on_submit: ParsedOnSubmit,
     children: Vec<ParsedChild>,
diff --git a/rocie-macros/src/form/parse.rs b/rocie-macros/src/form/parse.rs
index 59a82c1..2cf8799 100644
--- a/rocie-macros/src/form/parse.rs
+++ b/rocie-macros/src/form/parse.rs
@@ -86,6 +86,15 @@ impl Parse for ParsedChild {
                     auto_complete,
                 }
             }
+            variant if variant == format_ident!("Textarea") => {
+                let name = parse_key_value!(input, name as Ident);
+                let label = parse_key_value!(input, label as LitStr);
+
+                input.parse::<Token![/]>()?;
+                input.parse::<Token![>]>()?;
+
+                ParsedChild::Textarea { name, label }
+            }
             variant if variant == format_ident!("Select") => {
                 let name = parse_key_value!(input, name as Ident);
                 let rust_type = parse_key_value!(input, rust_type as Type);
@@ -130,6 +139,15 @@ impl Parse for ParsedChild {
 
                 ParsedChild::Show { when, children }
             }
+            variant if variant == format_ident!("Checkbox") => {
+                let name = parse_key_value!(input, name as Ident);
+                let label = parse_key_value!(input, label as LitStr);
+
+                input.parse::<Token![/]>()?;
+                input.parse::<Token![>]>()?;
+
+                ParsedChild::Checkbox { name, label }
+            }
             _ => panic!("Unknown form child variant: {variant}"),
         };