ridstack-form 0.1.0

End-to-End Type-safe form handling for Dioxus applications
use std::marker::PhantomData;

use crate::{field::FieldHandlers, traits::*, validators::*};
use dioxus::{core::SpawnIfAsync, prelude::*};
type AppForm = fn(AppFormInput) -> AppFormOutput;
struct AppFormInput {}
struct AppFormOutput {}

/// Form state. For now, its very basic.
#[derive(PartialEq, Clone, Debug, Store)]
pub struct FormState {
    pub(crate) can_submit: bool,
    pub(crate) is_submiting: bool,
    pub(crate) is_validating: bool,
}

impl Default for FormState {
    fn default() -> Self {
        Self {
            can_submit: true,
            is_submiting: false,
            is_validating: false,
        }
    }
}

/// Inits form state store. This is a hook so use at top level of a component.
#[inline(always)]
pub fn use_init_form_state() -> Store<FormState> {
    use_store(Default::default)
}

/// If form generated by `GenForm`, then this is not needed. the generated data [Form] component already includes
/// the provided. This is for advanced use-cases such as getting values outside the [Form] component.
pub fn use_form_provider<TForm>()
where
    TForm: InitForm + GetFieldRegistry + FormSubmitInput + GetFormState + CalculateCanSubmit,
{
    let mut form = TForm::use_form();
    let mut form_state = form.get_form_state();
    use_context_provider(|| form);
}

/// The main form component. Use this to init the form.
/// By default, it encloses the html form component, so you don't need to use html form
/// component inside it.
/// **Note: onsubmit prop and on_submit prop are different. To manage submissions properly, use
/// the on_submit prop as it contains the data you need for submission.**
#[component]
pub fn Form<TForm>(
    #[props(extends=GlobalAttributes)]
    #[props(extends=form)]
    attributes: Vec<Attribute>,
    children: Element,
    on_submit: EventHandler<TForm::SubmitInput>,
    #[props(optional, default=PhantomData)] _phantom_form: PhantomData<TForm>,
) -> Element
where
    TForm: GetFormState + FormSubmitInput + Clone + ValidateOnSubmit + CalculateCanSubmit + 'static,
{
    let mut form = use_context::<TForm>();
    let mut form_state = form.get_form_state();

    rsx! {
        form {

            onsubmit: move |ev| {
                ev.prevent_default();
                let mut form = form.clone();
                async move {

                    form.validate_on_submit().await;
                    let can_submit = form.calculate_can_submit();
                    if can_submit && let Some(output) = form.build_submit_input() {
                        tracing::info!("submitting now...");
                        form_state.can_submit().set(false);
                        form_state.is_submiting().set(true);
                        on_submit.call(output);
                        form_state.can_submit().set(true);
                        form_state.is_submiting().set(false);
                    }
                }

            },
            ..attributes,
            {children}
        }
    }
}

pub fn use_form<TForm>() -> TForm
where
    TForm: GetFieldRegistry + GetFormState,
{
    try_use_context::<TForm>().expect("You may have forgotten to use `use_form_provider` with the required form struct in the parent component.")
}