use crate::prelude::{
AsyncValidators, FieldStateProvider, GetFieldRegistry, GetFormState, ValidateOn, Validators,
};
use crate::{form::*, traits::*};
use dioxus::core::Task;
use dioxus::prelude::*;
use either::Either;
use std::pin::Pin;
use std::time::Duration;
use std::{
collections::{BTreeSet, HashSet},
error::Error,
hash::Hash,
marker::PhantomData,
};
#[derive(Clone, PartialEq)]
pub struct FieldApi<TForm, TField>
where
TForm: FieldStateProvider<TField>,
TField: Clone + PartialEq + 'static,
{
pub(crate) state:
FieldApiState<<TForm::FieldValue as FieldValue>::PrimitiveValue, TForm::FieldError>,
name: &'static str,
handlers: FieldHandlers<<TForm::FieldValue as FieldValue>::PrimitiveValue>,
pub value: ReadSignal<String>,
}
#[derive(Clone, PartialEq)]
pub struct FieldErrors<TFieldError: FieldError> {
pub(crate) errors: ReadSignal<Vec<TFieldError>>,
}
#[derive(Clone, PartialEq)]
pub struct RawFieldErrors {
pub(crate) errors: ReadSignal<Vec<String>>,
}
impl<TForm: FieldStateProvider<TField>, TField: Clone + PartialEq + 'static>
FieldApi<TForm, TField>
{
pub fn state(
&self,
) -> &FieldApiState<
<<TForm as FieldStateProvider<TField>>::FieldValue as FieldValue>::PrimitiveValue,
<TForm as FieldStateProvider<TField>>::FieldError,
> {
&self.state
}
pub fn name(&self) -> &'static str {
self.name
}
pub fn handlers(
&self,
) -> &FieldHandlers<
<<TForm as FieldStateProvider<TField>>::FieldValue as FieldValue>::PrimitiveValue,
> {
&self.handlers
}
pub fn value(&self) -> String {
(self.value)()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldHandlers<TFieldValue: PrimitiveFieldValue> {
on_blur: EventHandler<FocusEvent>,
on_input: EventHandler<Either<String, TFieldValue>>,
}
impl<TFieldValue: PrimitiveFieldValue> FieldHandlers<TFieldValue> {
pub fn on_blur(&self) -> Callback<Event<FocusData>, ()> {
self.on_blur
}
pub fn on_input(&self) -> Callback<Either<String, TFieldValue>> {
self.on_input
}
}
#[derive(Clone, Store)]
pub struct FieldState<TFieldValue: FieldValue, TFieldError: FieldError> {
pub data: TFieldValue,
pub is_dirty: bool,
is_sync_validating: bool,
pub is_validating: bool,
pub is_touched: bool,
pub errors: Vec<TFieldError>,
pub validators: Validators<TFieldValue, TFieldError>,
pub async_validators: AsyncValidators<TFieldValue, TFieldError>,
pub validate_on: ValidateOn,
validate_counter: u8,
}
#[store(pub)]
impl<Lens, TFieldValue: FieldValue, TFieldError: FieldError>
Store<FieldState<TFieldValue, TFieldError>, Lens>
{
fn validate(&mut self) {
let mut sync_errs = vec![];
self.is_sync_validating().set(true);
for validator in &*self.validators().peek() {
if let Err(err) = validator.validate(&*self.data().peek()) {
sync_errs.push(err);
}
}
self.is_sync_validating().set(false);
self.errors().with_mut(|errors| {
*errors = sync_errs;
errors.sort_unstable();
errors.dedup();
});
}
fn async_validate(&mut self) -> Pin<Box<impl Future<Output = ()>>> {
*self.validate_counter().write() += 1;
let async_validation_counter = *self.validate_counter().peek();
Box::pin(async move {
self.is_validating().set(true);
for validator in &*self.async_validators().peek() {
if let Err(err) = validator.validate(&*self.data().peek()).await {
if async_validation_counter == *self.validate_counter().peek()
&& let Err(idx) = self.errors().read().binary_search(&err)
{
self.errors().insert(idx, err)
}
}
}
self.is_validating().set(false);
})
}
fn can_submit(&self) -> bool {
!(!self.errors().peek().is_empty()
|| *self.is_sync_validating().peek()
|| *self.is_validating().peek())
}
}
impl<TFieldValue, TFieldError> Default for FieldState<TFieldValue, TFieldError>
where
TFieldValue: FieldValue,
TFieldError: FieldError,
{
fn default() -> Self {
Self {
is_validating: false,
data: TFieldValue::default(),
errors: Vec::with_capacity(0),
is_dirty: false,
is_touched: false,
is_sync_validating: false,
validate_on: ValidateOn::All,
validators: vec![],
async_validators: vec![],
validate_counter: 0,
}
}
}
#[inline(always)]
pub fn use_init_field_state<T: FieldValue, TFieldError: FieldError>()
-> Store<FieldState<T, TFieldError>> {
use_store(Default::default)
}
#[derive(Clone, PartialEq)]
pub struct FieldApiState<TFieldValue: PrimitiveFieldValue, TFieldError: FieldError> {
pub data: ReadSignal<TFieldValue>,
pub is_validating: ReadSignal<bool>,
pub is_dirty: ReadSignal<bool>,
pub is_touched: ReadSignal<bool>,
pub is_pristine: ReadSignal<bool>,
pub is_default_value: ReadSignal<bool>,
pub errors: ReadSignal<Vec<TFieldError>>,
}
#[component]
pub fn Field<TForm, TField>(
component: Callback<FieldApi<TForm::FieldRegistry, TField>, Element>,
field: TField,
#[props(optional)] validate_on: ValidateOn,
#[props(optional, default = Vec::with_capacity(0))] validators: Validators<
<TForm::FieldRegistry as FieldStateProvider<TField>>::FieldValue,
<TForm::FieldRegistry as FieldStateProvider<TField>>::FieldError,
>,
#[props(optional, default = Vec::with_capacity(0))] async_validators: AsyncValidators<
<TForm::FieldRegistry as FieldStateProvider<TField>>::FieldValue,
<TForm::FieldRegistry as FieldStateProvider<TField>>::FieldError,
>,
#[props(optional)] __phantom_ctx: PhantomData<TForm>,
) -> Element
where
TForm::FieldRegistry: FieldStateProvider<TField> + Clone + PartialEq,
TForm: GetFieldRegistry + GetFormState,
TField: Clone + PartialEq + 'static,
{
let ctx = use_form::<TForm>();
let field_registry = ctx.get_field_registry();
let mut async_validate_task = use_signal::<Option<Task>>(|| None);
let mut should_debounce = use_signal(|| true);
let mut is_async_validating = use_signal(|| false);
let mut field_state = field_registry.get_field_state(&field);
let field_name = use_memo(move || field_registry.name(&field));
let mut form_state = ctx.get_form_state();
let mut revision = use_signal(|| 0usize);
let mut value = use_signal(|| {
<TForm::FieldRegistry as FieldStateProvider<TField>>::FieldValue::default()
.to_string_value()
});
let mut is_default_value = use_signal(|| true);
let mut is_pristine = use_signal(|| true);
let primitive_data = use_memo(move || field_state.data().read().to_primitive_field_value());
let validate_fn = use_callback(move |_: PhantomData<bool>| {
field_state.is_dirty().set(true);
field_state.is_touched().set(true);
field_state.validate();
#[cfg(feature = "validify")]
{
use validify::Validify;
let validify_res = ctx.validate();
if let Err(errs) = validify_res {
let mut val_errs = errs
.field_errors()
.iter()
.filter_map(|e| {
use validify::ValidationError;
if let ValidationError::Field {
field: err_field, ..
} = e
&& err_field.is_some_and(|val| val == field_name())
{
return <TForm::FieldRegistry as FieldStateProvider<TField>>::FieldError::option_from((*e).clone());
}
None
})
.collect::<Vec<_>>();
err_snapshot.append(&mut val_errs);
}
}
});
let on_input = move |val: Either<String, <<TForm::FieldRegistry as FieldStateProvider<TField>>::FieldValue as FieldValue>::PrimitiveValue>| {
match val {
Either::Left(v) => {
value.set(v);
let data = match <TForm::FieldRegistry as FieldStateProvider<TField>>::FieldValue::from_str(
&*value.read(),
)
.map_err(|err| <TForm::FieldRegistry as FieldStateProvider<TField>>::FieldError::from(err))
{
Ok(res) => res,
Err(err) => {
is_default_value.set(true);
field_state.errors().with_mut(move |e| {
e.push(err);
});
<TForm::FieldRegistry as FieldStateProvider<TField>>::FieldValue::default()
}
};
field_state.data().set(data)
}
Either::Right(v) => {
field_state.data().set(v.into());
}
}
if matches!(validate_on, ValidateOn::All | ValidateOn::Input) {
async_validate_task.with_mut(|t| {
if let Some(task) = t {
task.cancel();
*t = None;
}
});
should_debounce.set(true);
let task = spawn(async_validate(field_state.into(), should_debounce()));
async_validate_task.set(Some(task));
validate_fn(PhantomData);
}
};
let on_blur = move |_| {
field_state.is_touched().set(true);
is_pristine.set(false);
async move {
if matches!(validate_on, ValidateOn::All | ValidateOn::Blur) {
async_validate_task.with_mut(|t| {
if let Some(task) = t {
task.cancel();
*t = None;
}
});
should_debounce.set(false);
validate_fn(PhantomData);
let task = spawn(async_validate(field_state.into(), should_debounce()));
async_validate_task.set(Some(task));
}
}
};
let field_api = FieldApi {
name: field_name(),
handlers: FieldHandlers::<<<TForm::FieldRegistry as FieldStateProvider<TField>>::FieldValue as FieldValue>::PrimitiveValue> {
on_blur: EventHandler::new(on_blur),
on_input: EventHandler::new(on_input),
},
state: FieldApiState {
data: ReadSignal::new(primitive_data),
is_dirty: ReadSignal::new(field_state.is_dirty()),
is_touched: ReadSignal::new(field_state.is_touched()),
is_validating: ReadSignal::new(field_state.is_validating()),
is_default_value: ReadSignal::new(is_default_value),
is_pristine: ReadSignal::new(is_pristine),
errors: ReadSignal::new(field_state.errors()),
},
value: ReadSignal::new(value),
};
let raw_field_errors = use_memo(move || {
(*field_state.errors().read())
.iter()
.map(|e| e.custom_message().unwrap_or_else(|| e.to_string()))
.collect::<Vec<_>>()
});
let raw_field_errors = use_memo(move || RawFieldErrors {
errors: ReadSignal::new(raw_field_errors),
});
use_effect(move || {
let validators = validators.clone();
let async_validators = async_validators.clone();
field_state.validators().with_mut(|e| {
for validator in validators {
if let Err(idx) = e.binary_search(&validator) {
e.insert(idx, validator)
}
}
});
field_state.async_validators().with_mut(|e| {
for validator in async_validators {
if let Err(idx) = e.binary_search(&validator) {
e.insert(idx, validator)
}
}
});
field_state.validate_on().set(validate_on);
});
use_context_provider(move || raw_field_errors());
rsx! {
{component(field_api)}
}
}
async fn async_validate<TFieldValue: FieldValue, TFieldError: FieldError>(
mut field_state: Store<FieldState<TFieldValue, TFieldError>>,
should_debounce: bool,
) {
if should_debounce {
tokio::time::sleep(Duration::from_millis(350)).await;
}
field_state.async_validate().await;
}