diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-03-19 07:45:14 +0100 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-03-19 07:45:14 +0100 |
| commit | f6a3fb9c4d8dd86f78c9f75a23c1ac35bf35d4eb (patch) | |
| tree | 5f28fbca03d83921b568f7cb1708374456d9ec42 /rocie-macros/src | |
| parent | feat(treewide): Add further buttons (diff) | |
| download | web-client-f6a3fb9c4d8dd86f78c9f75a23c1ac35bf35d4eb.zip | |
feat(treewide): Commit MVP
Diffstat (limited to 'rocie-macros/src')
| -rw-r--r-- | rocie-macros/src/form/generate.rs | 136 | ||||
| -rw-r--r-- | rocie-macros/src/form/mod.rs | 11 | ||||
| -rw-r--r-- | rocie-macros/src/form/parse.rs | 18 |
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}"), }; |
