diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 51ad882..e562fa4 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -17,7 +17,7 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2023-11-01 + toolchain: stable components: clippy,rustfmt - uses: Swatinem/rust-cache@v2 @@ -42,17 +42,17 @@ jobs: - run: pre-commit run --all-files --hook-stage push - - name: rust-grcov - uses: actions-rs/grcov@v0.1 - - - name: Codecov - uses: codecov/codecov-action@v4-beta - continue-on-error: true - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - # Repository upload token - get it from codecov.io. Required only for private repositories - # token: # optional - # Specify whether the Codecov output should be verbose - verbose: true - fail_ci_if_error: false + # - name: rust-grcov + # uses: actions-rs/grcov@v0.1 + + # - name: Codecov + # uses: codecov/codecov-action@v4-beta + # continue-on-error: true + # env: + # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + # with: + # # Repository upload token - get it from codecov.io. Required only for private repositories + # # token: # optional + # # Specify whether the Codecov output should be verbose + # verbose: true + # fail_ci_if_error: false diff --git a/.gitignore b/.gitignore index 16d5636..f55ba0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target Cargo.lock .DS_Store +rust-toolchain.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 98a8852..3897ab1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,6 +77,9 @@ repos: - --fix - --allow-dirty - --allow-staged + - -- + - -D + - warnings language: system types: [file] files: (\.rs|Cargo\.toml)$ @@ -108,33 +111,28 @@ repos: types: [rust] args: ["--"] - - id: cargo-doc - name: cargo-doc - description: ensure cargo doc builds - stages: [push] - entry: sh - args: - - -c - - "RUSTDOCFLAGS=\"-D warnings\" cargo doc --all-features" - language: system - types: [file] - files: (\.md|\.rs|Cargo\.toml)$ - exclude: (CHANGELOG|DEVELOPMENT)\.md$ - pass_filenames: false + # - id: cargo-doc + # name: cargo-doc + # description: ensure cargo doc builds + # stages: [push] + # entry: sh + # args: + # - -c + # - "RUSTDOCFLAGS=\"-D warnings\" cargo doc --all-features" + # language: system + # types: [file] + # files: (\.md|\.rs|Cargo\.toml)$ + # exclude: (CHANGELOG|DEVELOPMENT)\.md$ + # pass_filenames: false - id: cargo-test name: cargo-test description: run all tests stages: [push] - entry: sh + entry: cargo args: - - -c - - | - CARGO_INCREMENTAL="0" \ - RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests" \ - RUSTDOCFLAGS="-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests" \ - cargo test \ - --all-features + - test + - --all-features language: system types: [file] files: (\.md|\.rs|Cargo\.toml)$ diff --git a/Cargo.toml b/Cargo.toml index 19aef48..908e467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] # update the internal crate versions when updating this workspace version -version = "0.1.8" +version = "0.2.0-beta" edition = "2021" rust-version = "1.75.0" authors = ["Trey Lowerison <19714082+tlowerison@users.noreply.github.com>"] @@ -15,9 +15,9 @@ keywords = ["leptos", "form", "derive-macros", "ui"] [workspace.dependencies] # internal to this workspace -leptos_form_core = { version = "=0.1.8", path = "core" } -leptos_form_proc_macros = { version = "=0.1.8", path = "proc_macros" } -leptos_form_proc_macros_core = { version = "=0.1.8", path = "proc_macros/core" } +leptos_form_core = { version = "=0.2.0-beta", path = "core" } +leptos_form_proc_macros = { version = "=0.2.0-beta", path = "proc_macros" } +leptos_form_proc_macros_core = { version = "=0.2.0-beta", path = "proc_macros/core" } bigdecimal = "~0.4" chrono = { version = "0", features = ["std"] } @@ -29,8 +29,8 @@ indexmap = "2" inner = "0" itertools = "~0.12" js-sys = "~0.3" -leptos = { version = "~0.5" } -leptos_router = "~0.5" +leptos = "0.6.0-beta" +leptos_router = "0.6.0-beta" num-bigint = { version = "~0.4", default-features = false } paste = "~1" prettyplease = "0.2" diff --git a/core/src/cache.rs b/core/src/cache.rs index a1714f9..80b4853 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -56,7 +56,7 @@ impl SerdeSerializer for SerdeJson { } #[cfg(feature = "cache-local-storage")] -impl, T> Cache for LocalStorage { +impl> Cache for LocalStorage { type Error = CS::Error; fn get_item(&self, key: &str) -> impl Future, CS::Error>> + Send { use wasm_bindgen::UnwrapThrowExt; diff --git a/core/src/form_component/impls/collections.rs b/core/src/form_component/impls/collections.rs index 4ba5a43..b1946f1 100644 --- a/core/src/form_component/impls/collections.rs +++ b/core/src/form_component/impls/collections.rs @@ -155,20 +155,8 @@ where .collect::>() })) } - fn is_initial_value(signal: &Self::Signal) -> bool { - signal.initial.with(|initial| { - signal.value.with(|value| match initial.as_ref() { - Some(initial) => { - let no_keys_changed = value.len() == initial.len() - && value.last().map(|item| item.0) == initial.last().map(|item| item.0); - if !no_keys_changed { - return false; - } - value.iter().all(|(_, item)| T::is_initial_value(&item.signal)) - } - None => value.is_empty(), - }) - }) + fn is_default_value(signal: &Self::Signal) -> bool { + signal.value.with(|value| value.is_empty()) } fn into_signal(self, config: &Self::Config, initial: Option) -> Self::Signal { let has_initial = initial.is_some(); diff --git a/core/src/form_component/impls/misc.rs b/core/src/form_component/impls/misc.rs index dbce871..37837a6 100644 --- a/core/src/form_component/impls/misc.rs +++ b/core/src/form_component/impls/misc.rs @@ -20,13 +20,8 @@ mod uuid { fn default_signal(config: &Self::Config, initial: Option) -> Self::Signal { FormFieldSignal::new_with_default_value(initial.map(|x| x.to_string())) } - fn is_initial_value(signal: &Self::Signal) -> bool { - signal.value.with(|value| { - signal.initial.with(|initial| match initial { - Some(initial) => initial == value, - None => value.is_empty(), - }) - }) + fn is_default_value(signal: &Self::Signal) -> bool { + signal.value.with(|value| value.is_empty()) } fn into_signal(self, _: &Self::Config, initial: Option) -> Self::Signal { FormFieldSignal::new(self.to_string(), initial.map(|x| x.to_string())) @@ -62,9 +57,13 @@ mod uuid { name={props.name} on:input=move |ev| props.signal.value.update(|value| *value = event_target_value(&ev)) on:change=move |_| { - if let Err(form_error) = >>::try_from_signal(props.signal, &props.config) { - props.signal.error.update(|error| *error = Some(form_error)); - } else if props.signal.error.with_untracked(|error| error.is_some()) { + if !props.is_optional || !>>::is_default_value(&props.signal) { + if let Err(form_error) = >>::try_from_signal(props.signal, &props.config) { + props.signal.error.update(|error| *error = Some(form_error)); + } else if props.signal.error.with_untracked(|error| error.is_some()) { + props.signal.error.update(|error| *error = None); + } + } else { props.signal.error.update(|error| *error = None); } } @@ -86,33 +85,38 @@ pub mod chrono { #[derive(Clone, Debug)] pub struct NaiveDateConfig { + /// defaults to `"%F"` pub format: &'static str, } #[derive(Clone, Debug)] pub struct NaiveDateTimeConfig { + /// defaults to `"%FT%T"` pub format: &'static str, } #[derive(Clone, Debug)] pub struct FixedOffsetDateTimeConfig { + /// defaults to `"%+"` pub format: &'static str, } #[derive(Clone, Debug)] pub struct UtcDateTimeConfig { + /// defaults to `"%+"` pub format: &'static str, } #[derive(Clone, Debug)] pub struct LocalDateTimeConfig { + /// defaults to `"%FT%T"` pub format: &'static str, } impl Default for NaiveDateConfig { fn default() -> Self { - Self { format: "%x" } + Self { format: "%F" } } } impl Default for NaiveDateTimeConfig { fn default() -> Self { - Self { format: "%c" } + Self { format: "%FT%T" } } } impl Default for FixedOffsetDateTimeConfig { @@ -122,12 +126,12 @@ pub mod chrono { } impl Default for UtcDateTimeConfig { fn default() -> Self { - Self { format: "%c" } + Self { format: "%+" } } } impl Default for LocalDateTimeConfig { fn default() -> Self { - Self { format: "%c" } + Self { format: "%FT%T" } } } @@ -148,11 +152,8 @@ pub mod chrono { fn default_signal(config: &Self::Config, initial: Option) -> Self::Signal { FormFieldSignal::new_with_default_value(initial.map(|x| x.format(config.format).to_string())) } - fn is_initial_value(signal: &Self::Signal) -> bool { - signal.value.with(|value| signal.initial.with(|initial| match initial { - Some(initial) => initial == value, - None => value.is_empty(), - })) + fn is_default_value(signal: &Self::Signal) -> bool { + signal.value.with(|value| value.is_empty()) } fn into_signal(self, config: &Self::Config, initial: Option) -> Self::Signal { FormFieldSignal::new(self.format(config.format).to_string(), initial.map(|initial| initial.format(config.format).to_string())) @@ -181,9 +182,13 @@ pub mod chrono { name={props.name} on:input=move |ev| props.signal.value.update(|value| *value = event_target_value(&ev)) on:change=move |_| { - if let Err(form_error) = >>::try_from_signal(props.signal, &props.config) { - props.signal.error.update(|error| *error = Some(form_error)); - } else if props.signal.error.with_untracked(|error| error.is_some()) { + if !props.is_optional || !Self::is_default_value(&props.signal) { + if let Err(form_error) = >>::try_from_signal(props.signal, &props.config) { + props.signal.error.update(|error| *error = Some(form_error)); + } else if props.signal.error.with_untracked(|error| error.is_some()) { + props.signal.error.update(|error| *error = None); + } + } else { props.signal.error.update(|error| *error = None); } } diff --git a/core/src/form_component/impls/num.rs b/core/src/form_component/impls/num.rs index f9914d8..7032e30 100644 --- a/core/src/form_component/impls/num.rs +++ b/core/src/form_component/impls/num.rs @@ -16,11 +16,8 @@ macro_rules! num_impl { fn default_signal(_: &Self::Config, initial: Option) -> Self::Signal { FormFieldSignal::new_with_default_value(initial.map(|x| x.to_string())) } - fn is_initial_value(signal: &Self::Signal) -> bool { - signal.value.with(|value| signal.initial.with(|initial| match initial { - Some(initial) => initial == value, - None => value.is_empty(), - })) + fn is_default_value(signal: &Self::Signal) -> bool { + signal.value.with(|value| value.is_empty()) } fn into_signal(self, _: &Self::Config, initial: Option) -> Self::Signal { FormFieldSignal::new(self.to_string(), initial.map(|x| x.to_string())) @@ -53,9 +50,13 @@ macro_rules! num_impl { on:keydown=num_impl!(@prevent_invalid_keystrokes value $($($type)?)?) on:input=move |ev| props.signal.value.update(|value| *value = event_target_value(&ev)) on:change=move |_| { - if let Err(form_error) = >>::try_from_signal(props.signal, &props.config) { - props.signal.error.update(|error| *error = Some(form_error)); - } else if props.signal.error.with_untracked(|error| error.is_some()) { + if !props.is_optional || !>>::is_default_value(&props.signal) { + if let Err(form_error) = >>::try_from_signal(props.signal, &props.config) { + props.signal.error.update(|error| *error = Some(form_error)); + } else if props.signal.error.with_untracked(|error| error.is_some()) { + props.signal.error.update(|error| *error = None); + } + } else { props.signal.error.update(|error| *error = None); } } diff --git a/core/src/form_component/impls/str.rs b/core/src/form_component/impls/str.rs index a4b6838..8fd4222 100644 --- a/core/src/form_component/impls/str.rs +++ b/core/src/form_component/impls/str.rs @@ -22,13 +22,8 @@ macro_rules! str_impl { fn default_signal(_: &Self::Config, initial: Option) -> Self::Signal { FormFieldSignal::new_with_default_value(initial.map(|x| x.to_string())) } - fn is_initial_value(signal: &Self::Signal) -> bool { - signal.value.with(|value| signal.initial.with(|initial| { - match initial { - Some(initial) => initial == value, - None => value.is_empty(), - } - })) + fn is_default_value(signal: &Self::Signal) -> bool { + signal.value.with(|value| value.is_empty()) } fn into_signal(self, _: &Self::Config, initial: Option) -> Self::Signal { FormFieldSignal::new(self.to_string(), initial.map(|x| x.to_string())) @@ -58,9 +53,13 @@ macro_rules! str_impl { name={props.name} on:input=move |ev| props.signal.value.update(|value| *value = event_target_value(&ev)) on:change=move |_| { - if let Err(form_error) = >>::try_from_signal(props.signal, &props.config) { - props.signal.error.update(|error| *error = Some(form_error)); - } else if props.signal.error.with_untracked(|error| error.is_some()) { + if !props.is_optional || !>>::is_default_value(&props.signal) { + if let Err(form_error) = >>::try_from_signal(props.signal, &props.config) { + props.signal.error.update(|error| *error = Some(form_error)); + } else if props.signal.error.with_untracked(|error| error.is_some()) { + props.signal.error.update(|error| *error = None); + } + } else { props.signal.error.update(|error| *error = None); } } diff --git a/core/src/form_component/mod.rs b/core/src/form_component/mod.rs index 02c5b1e..9a0bae1 100644 --- a/core/src/form_component/mod.rs +++ b/core/src/form_component/mod.rs @@ -34,7 +34,7 @@ pub trait FormField: Sized { type Signal: Clone + 'static; fn default_signal(config: &Self::Config, initial: Option) -> Self::Signal; - fn is_initial_value(signal: &Self::Signal) -> bool; + fn is_default_value(signal: &Self::Signal) -> bool; fn into_signal(self, config: &Self::Config, initial: Option) -> Self::Signal; fn try_from_signal(signal: Self::Signal, config: &Self::Config) -> Result; fn recurse(signal: &Self::Signal); @@ -74,6 +74,8 @@ pub struct RenderProps { pub style: Option>, #[builder(default)] pub field_changed_class: Option>, + #[builder(default)] + pub is_optional: bool, pub signal: T, pub config: Config, } @@ -89,7 +91,7 @@ pub struct FormFieldSignal { pub error: RwSignal>, } -impl RenderProps, Config> { +impl RenderProps, Config> { pub fn class_signal(&self) -> RwSignal>> { let signal = self.signal; let class = self.class.clone(); @@ -127,12 +129,12 @@ impl RenderProps FormFieldSignal { +impl FormFieldSignal { pub fn has_changed(&self) -> bool { self.value.with(|value| { self.initial.with(|initial| match initial { Some(initial) => *initial != *value, - None => true, + None => value != &T::default(), }) }) } @@ -152,8 +154,8 @@ where fn default_signal(config: &Self::Config, initial: Option) -> Self::Signal { T::default_signal(config, initial.flatten()) } - fn is_initial_value(signal: &Self::Signal) -> bool { - T::is_initial_value(signal) + fn is_default_value(signal: &Self::Signal) -> bool { + T::is_default_value(signal) } fn into_signal(self, config: &Self::Config, initial: Option) -> Self::Signal { match self { @@ -162,7 +164,7 @@ where } } fn try_from_signal(signal: Self::Signal, config: &Self::Config) -> Result { - match Self::is_initial_value(&signal) { + match Self::is_default_value(&signal) { true => Ok(None), false => Ok(Some(T::try_from_signal(signal, config)?)), } @@ -185,7 +187,8 @@ impl FormComponent for Option where T: FormComponent, { - fn render(props: RenderProps) -> impl IntoView { + fn render(mut props: RenderProps) -> impl IntoView { + props.is_optional = true; T::render(props) } } @@ -201,7 +204,7 @@ impl Default for FormFieldSignal { } } -impl FormFieldSignal { +impl FormFieldSignal { fn new(value: T, initial: Option) -> Self { Self { value: create_rw_signal(value), diff --git a/proc_macros/core/src/form.rs b/proc_macros/core/src/form.rs index 28c2d55..eccbfe5 100644 --- a/proc_macros/core/src/form.rs +++ b/proc_macros/core/src/form.rs @@ -354,7 +354,7 @@ pub fn derive_form(tokens: TokenStream) -> Result { let class = field.class.clone().or_else(|| field_class.clone()).map(StringExpr::with_oco(&leptos_krate)).into_iter(); let style = field.style.clone().or_else(|| field_style.clone()).map(StringExpr::with_oco(&leptos_krate)).into_iter(); - let config = field.config.clone() .unwrap_or_else(|| parse2(quote!( + let config = field.config.clone().unwrap_or_else(|| parse2(quote!( <#field_ty as #leptos_form_krate::FormField<#field_el_ty>>::Config::default() )).unwrap()); @@ -881,7 +881,7 @@ pub fn derive_form(tokens: TokenStream) -> Result { pound_token: Default::default(), style: syn::AttrStyle::Outer, bracket_token: Default::default(), - meta: parse2(quote!(derive(Clone, Debug, Default)))?, + meta: parse2(quote!(derive(Clone, Debug)))?, }], vis: syn::Visibility::Public(Default::default()), struct_token: Default::default(), @@ -907,6 +907,12 @@ pub fn derive_form(tokens: TokenStream) -> Result { #config_struct_def + impl Default for #config_ty { + fn default() -> Self { + Self { #(#field_axs: #configs,)* } + } + } + impl ::core::convert::AsRef<#signal_ty> for #signal_ty { fn as_ref(&self) -> &Self { self @@ -937,9 +943,9 @@ pub fn derive_form(tokens: TokenStream) -> Result { }, } } - fn is_initial_value(signal: &Self::Signal) -> bool { + fn is_default_value(signal: &Self::Signal) -> bool { true #(&& - <#field_tys as #leptos_form_krate::FormField<#field_el_tys>>::is_initial_value(&signal.#field_axs) + <#field_tys as #leptos_form_krate::FormField<#field_el_tys>>::is_default_value(&signal.#field_axs) )* } fn into_signal(self, #config_var_ident: &Self::Config, initial: Option) -> Self::Signal { @@ -2014,7 +2020,7 @@ mod test { pub count: ::El>>::Signal, } - #[derive(Clone, Debug, Default)] + #[derive(Clone, Debug)] pub struct __MyFormDataConfig { pub id: ::El>>::Config, pub slug: ::El>>::Config, @@ -2022,6 +2028,17 @@ mod test { pub count: ::El>>::Config, } + impl Default for __MyFormDataConfig { + fn default() -> Self { + Self { + id: ::El>>::Config::default(), + slug: ::El>>::Config::default(), + created_at: ::El>>::Config::default(), + count: ::El>>::Config::default(), + } + } + } + impl ::core::convert::AsRef<__MyFormDataSignal> for __MyFormDataSignal { fn as_ref(&self) -> &Self { self @@ -2077,11 +2094,11 @@ mod test { } } } - fn is_initial_value(signal: &Self::Signal) -> bool { - true && ::El>>::is_initial_value(&signal.id) && - ::El>>::is_initial_value(&signal.slug) && - ::El>>::is_initial_value(&signal.created_at) && - ::El>>::is_initial_value(&signal.count) + fn is_default_value(signal: &Self::Signal) -> bool { + true && ::El>>::is_default_value(&signal.id) && + ::El>>::is_default_value(&signal.slug) && + ::El>>::is_default_value(&signal.created_at) && + ::El>>::is_default_value(&signal.count) } fn into_signal(self, config: &Self::Config, initial: Option) -> Self::Signal { match initial { @@ -2312,12 +2329,21 @@ mod test { pub zz: ::El>>::Signal, } - #[derive(Clone, Debug, Default)] + #[derive(Clone, Debug)] pub struct __MyFormDataConfig { pub abc_123: ::El>>::Config, pub zz: ::El>>::Config, } + impl Default for __MyFormDataConfig { + fn default() -> Self { + Self { + abc_123: ::El>>::Config::default(), + zz: ::El>>::Config::default(), + } + } + } + impl ::core::convert::AsRef<__MyFormDataSignal> for __MyFormDataSignal { fn as_ref(&self) -> &Self { self @@ -2362,9 +2388,9 @@ mod test { } } } - fn is_initial_value(signal: &Self::Signal) -> bool { - true && ::El>>::is_initial_value(&signal.abc_123) && - ::El>>::is_initial_value(&signal.zz) + fn is_default_value(signal: &Self::Signal) -> bool { + true && ::El>>::is_default_value(&signal.abc_123) && + ::El>>::is_default_value(&signal.zz) } fn into_signal(self, config: &Self::Config, initial: Option) -> Self::Signal { match initial { @@ -2513,11 +2539,19 @@ mod test { pub ayo: ::El>>::Signal, } - #[derive(Clone, Debug, Default)] + #[derive(Clone, Debug)] pub struct __MyFormDataConfig { pub ayo: ::El>>::Config, } + impl Default for __MyFormDataConfig { + fn default() -> Self { + Self { + ayo: ::El>>::Config::default(), + } + } + } + impl ::core::convert::AsRef<__MyFormDataSignal> for __MyFormDataSignal { fn as_ref(&self) -> &Self { self @@ -2548,8 +2582,8 @@ mod test { }, } } - fn is_initial_value(signal: &Self::Signal) -> bool { - true && ::El>>::is_initial_value(&signal.ayo) + fn is_default_value(signal: &Self::Signal) -> bool { + true && ::El>>::is_default_value(&signal.ayo) } fn into_signal(self, config: &Self::Config, initial: Option) -> Self::Signal { match initial { @@ -2729,11 +2763,19 @@ mod test { pub ayo: ::El>>::Signal, } - #[derive(Clone, Debug, Default)] + #[derive(Clone, Debug)] pub struct __MyFormDataConfig { pub ayo: ::El>>::Config, } + impl Default for __MyFormDataConfig { + fn default() -> Self { + Self { + ayo: ::El>>::Config::default(), + } + } + } + impl ::core::convert::AsRef<__MyFormDataSignal> for __MyFormDataSignal { fn as_ref(&self) -> &Self { self @@ -2772,8 +2814,8 @@ mod test { } } } - fn is_initial_value(signal: &Self::Signal) -> bool { - true && ::El>>::is_initial_value(&signal.ayo) + fn is_default_value(signal: &Self::Signal) -> bool { + true && ::El>>::is_default_value(&signal.ayo) } fn into_signal(self, config: &Self::Config, initial: Option) -> Self::Signal { match initial { @@ -2963,4 +3005,216 @@ mod test { Ok(()) } + + #[test] + fn custom_config_is_correctly_provided() -> Result<(), Error> { + let leptos_form_krate = quote!(::leptos_form); + let leptos_krate = quote!(#leptos_form_krate::internal::leptos); + let leptos_router_krate = quote!(#leptos_form_krate::internal::leptos_router); + let wasm_bindgen_krate = quote!(#leptos_form_krate::internal::wasm_bindgen); + + let input = quote!( + #[derive(Form)] + #[form(component(action = "/api/my-form-data"))] + pub struct MyFormData { + #[form(config = #leptos_form_krate::NaiveDateTimeConfig { format: "%c" })] + pub created_at: chrono::NaiveDateTime, + } + ); + + let expected = quote!( + #[derive(Clone, Copy, Debug)] + pub struct __MyFormDataSignal { + pub created_at: ::El>>::Signal, + } + + #[derive(Clone, Debug)] + pub struct __MyFormDataConfig { + pub created_at: ::El>>::Config, + } + + impl Default for __MyFormDataConfig { + fn default() -> Self { + Self { + created_at: #leptos_form_krate::NaiveDateTimeConfig { format: "%c" } + } + } + } + + impl ::core::convert::AsRef<__MyFormDataSignal> for __MyFormDataSignal { + fn as_ref(&self) -> &Self { + self + } + } + + impl ::core::convert::AsMut<__MyFormDataSignal> for __MyFormDataSignal { + fn as_mut(&mut self) -> &mut Self { + self + } + } + + impl #leptos_form_krate::DefaultHtmlElement for MyFormData { + type El = #leptos_krate::View; + } + + impl #leptos_form_krate::FormField<#leptos_krate::View> for MyFormData { + type Config = __MyFormDataConfig; + type Signal = __MyFormDataSignal; + fn default_signal(config: &Self::Config, initial: Option) -> Self::Signal { + match initial { + Some(initial) => { + __MyFormDataSignal { + created_at: ::El, + >>::default_signal(&config.created_at, Some(initial.created_at)), + } + } + None => { + __MyFormDataSignal { + created_at: ::El, + >>::default_signal(&config.created_at, None), + } + } + } + } + fn is_default_value(signal: &Self::Signal) -> bool { + true && ::El>>::is_default_value(&signal.created_at) + } + fn into_signal(self, config: &Self::Config, initial: Option) -> Self::Signal { + match initial { + Some(initial) => { + __MyFormDataSignal { + created_at: ::El, + >>::into_signal( + self.created_at, + &config.created_at, + Some(initial.created_at), + ), + } + } + None => { + __MyFormDataSignal { + created_at: ::El, + >>::into_signal(self.created_at, &config.created_at, None), + } + } + } + } + fn try_from_signal(signal: Self::Signal, config: &Self::Config) -> Result { + Ok(MyFormData { + created_at: ::El>>::try_from_signal(signal.created_at, &config.created_at)?, + }) + } + fn recurse(signal: &Self::Signal) { + ::El, + >>::recurse(&signal.created_at); + } + fn reset_initial_value(signal: &Self::Signal) { + ::El>>::reset_initial_value(&signal.created_at); + } + } + + impl #leptos_form_krate::FormComponent<#leptos_krate::View> for MyFormData { + #[allow(unused_imports)] + fn render(props: #leptos_form_krate::RenderProps) -> impl #leptos_krate::IntoView { + use #leptos_form_krate::FormField; + use #leptos_krate::{IntoAttribute, IntoView}; + + let _created_at_id = #leptos_form_krate::format_form_id(props.id.as_ref(), #leptos_krate::Oco::Borrowed("created-at")); + let _created_at_name = #leptos_form_krate::format_form_name(props.name.as_ref(), "created_at"); + let _created_at_props = #leptos_form_krate::RenderProps::builder() + .id(_created_at_id.clone()) + .name(_created_at_name.clone()) + .field_changed_class(props.field_changed_class.clone()) + .signal(props.signal.created_at.clone()) + .config(#leptos_form_krate::NaiveDateTimeConfig { format: "%c" }) + .build(); + + let _created_at_error = move || ::El>>::with_error( + &_created_at_props.signal, + |error| match error { + Some(form_error) => { + let error = format!("{form_error}"); + #leptos_krate::IntoView::into_view(#leptos_krate::view! { {error} }) + }, + None => #leptos_krate::View::default(), + }, + ); + let ty = <::std::marker::PhantomData<(chrono::NaiveDateTime, ::El)> as Default>::default(); + let _created_at_view = #leptos_krate::view! { }; + + #leptos_krate::view! { + + } + } + } + + pub use leptos_form_component_my_form_data::*; + mod leptos_form_component_my_form_data { + use super::*; + use #leptos_krate::IntoView; + use #wasm_bindgen_krate::{closure::Closure, JsCast, UnwrapThrowExt,}; + #[allow(unused_imports)] + #[#leptos_krate::component] + pub fn MyFormData( + mut initial: MyFormData, + #[prop(optional, into)] + top: Option<#leptos_form_krate::components::LeptosFormChildren>, + #[prop(optional, into)] + bottom: Option<#leptos_form_krate::components::LeptosFormChildren>, + ) -> impl IntoView { + use #leptos_form_krate::{FormField, components::FormSubmissionHandler}; + use #leptos_krate::{IntoAttribute, IntoView, SignalGet, SignalUpdate, SignalWith,}; + use #wasm_bindgen_krate::UnwrapThrowExt; + use #leptos_router_krate::Form; + use ::std::rc::Rc; + let config = __MyFormDataConfig { + created_at: #leptos_form_krate::NaiveDateTimeConfig { + format: "%c", + }, + }; + let signal = #leptos_krate::create_rw_signal( + #leptos_form_krate::RenderProps::builder() + .id(None) + .name("") + .signal(initial.clone().into_signal(&config, Some(initial.clone()))) + .config(config.clone()) + .build(), + ); + let _had_reset_called = #leptos_krate::create_rw_signal(false); + let parse_error_handler = |err: #leptos_form_krate::FormError| { + #leptos_krate::logging::debug_warn!("{err}") + }; + let ty = <::std::marker::PhantomData< + (MyFormData, #leptos_krate::View), + > as Default>::default(); + #leptos_krate::view! { + < Form action = "/api/my-form-data" > { top.map(| x | (x.0)()) } { move || + #leptos_krate::view! { < FormField props =signal.get() ty + = ty / > } } { bottom.map(| x | (x.0) ()) } < / Form > + } + } + } + ); + + let output = derive_form(input)?; + + let expected = cleanup(&expected); + let output = cleanup(&output); + + let expected = pretty(expected)?; + let output = pretty(output)?; + + assert_eq!(expected, output); + + Ok(()) + } } diff --git a/proc_macros/docs/Form/README.md b/proc_macros/docs/Form/README.md index 0116f4d..7fab2b2 100644 --- a/proc_macros/docs/Form/README.md +++ b/proc_macros/docs/Form/README.md @@ -63,12 +63,16 @@ An action can be specified one of two ways: - a path to a server function specified in a particular way: ```rust mod my_mod { - #[derive(Clone, Debug, leptos_form::Form, serde::Deserialize, serde::Serialize)] + use leptos::{server, ServerFnError}; + use leptos_form::Form; + use serde::{Deserialize, Serialize}; + + #[derive(Clone, Debug, Deserialize, Form, Serialize)] #[form(component(action = my_server_fn(my_data)))] struct MyForm {} - #[leptos::server] - async fn my_server_fn(my_data: MyForm) -> Result<(), leptos::ServerFnError> { + #[server] + async fn my_server_fn(my_data: MyForm) -> Result<(), ServerFnError> { Ok(()) } }