fix: Input validate

This commit is contained in:
luoxiao 2024-08-19 23:17:02 +08:00 committed by luoxiaozero
parent 615c33a5c8
commit c0bd42ad60
5 changed files with 59 additions and 18 deletions

View file

@ -27,8 +27,11 @@ view! {
view! { view! {
<form> <form>
<FieldContextProvider> <FieldContextProvider>
<Field label="Example field"> <Field label="Username" name="username">
<Input /> <Input rules=vec![InputRule::required(true.into())]/>
</Field>
<Field label="Password" name="password">
<Input input_type=InputType::Password rules=vec![InputRule::required(true.into())]/>
</Field> </Field>
<button <button
type="submit" type="submit"

View file

@ -37,6 +37,10 @@
line-height: var(--lineHeightBase200); line-height: var(--lineHeightBase200);
} }
.thaw-field--error > .thaw-field__validation-message {
color: var(--colorPaletteRedForeground1);
}
.thaw-field__validation-message-icon { .thaw-field__validation-message-icon {
display: inline-block; display: inline-block;
font-size: 12px; font-size: 12px;

View file

@ -25,6 +25,15 @@ pub fn Field(
move || { move || {
format!("thaw-field--{}", orientation.get().as_str()) format!("thaw-field--{}", orientation.get().as_str())
}, },
move || {
validation_state.with(|state| {
if let Some(state) = state {
Some(format!("thaw-field--{}", state.as_str()))
} else {
None
}
})
},
class class
]> ]>
{ {
@ -201,3 +210,13 @@ pub enum FieldValidationState {
Success(String), Success(String),
Warning(String), Warning(String),
} }
impl FieldValidationState {
pub fn as_str(&self) -> &'static str {
match self {
Self::Error(_) => "error",
Self::Success(_) => "success",
Self::Warning(_) => "warning",
}
}
}

View file

@ -26,11 +26,18 @@ impl FieldContextInjection {
use_context() use_context()
} }
pub(crate) fn register_field(&self, name: Signal<Option<String>>, validate: impl Fn() -> bool + Send + Sync + 'static) { pub(crate) fn register_field(
&self,
name: Signal<Option<String>>,
validate: impl Fn() -> bool + Send + Sync + 'static,
) {
let mut key = None; let mut key = None;
self.0 let validate = Callback::from(move |_| validate());
.update_value(|map| key = Some(map.insert((name, Callback::from(move |_| validate()))))); self.0.update_value(|map| {
key = Some(map.insert((name, validate)));
()
});
let map = self.0.clone(); let map = self.0.clone();
Owner::on_cleanup(move || { Owner::on_cleanup(move || {
map.update_value(|map| map.remove(key.unwrap())); map.update_value(|map| map.remove(key.unwrap()));
@ -39,12 +46,13 @@ impl FieldContextInjection {
pub fn validate(&self) -> bool { pub fn validate(&self) -> bool {
self.0.with_value(|map| { self.0.with_value(|map| {
let mut rt = true;
for (_, (_, validate)) in map.iter() { for (_, (_, validate)) in map.iter() {
if !validate.run(()) { if !validate.run(()) {
return false; rt = false;
} }
} }
true rt
}) })
} }

View file

@ -68,24 +68,24 @@ pub fn Input(
if rules.is_empty() { if rules.is_empty() {
return None; return None;
} }
let mut rules_iter = rules.iter(); let mut rules_iter = rules.iter();
loop { loop {
let Some(rule) = rules_iter.next() else { let Some(rule) = rules_iter.next() else {
return None; return None;
}; };
if let Err(state) = rule.call_validator(trigger, value) { if let Err(state) = rule.call_validator(trigger, value, name) {
return Some(state); return Some(state);
} }
} }
}); });
let rt = state.is_some(); let rt = state.is_none();
if let Some(field_injection) = field_injection.as_ref() { if let Some(field_injection) = field_injection.as_ref() {
field_injection.update_validation_state(state); field_injection.update_validation_state(state);
}; };
rt rt
}); });
if let Some(field_context) = FieldContextInjection::use_context() { if let Some(field_context) = FieldContextInjection::use_context() {
field_context.register_field(name, move || validate.run(None)); field_context.register_field(name, move || validate.run(None));
@ -318,7 +318,9 @@ impl InputRule {
} }
} }
pub fn validator(f: impl Fn(&String) -> Result<(), FieldValidationState> + Send + Sync + 'static) -> Self { pub fn validator(
f: impl Fn(&String) -> Result<(), FieldValidationState> + Send + Sync + 'static,
) -> Self {
Self { Self {
trigger: Default::default(), trigger: Default::default(),
validator: InputRuleValidator::Validator(Callback::from(move |v| f(&v))), validator: InputRuleValidator::Validator(Callback::from(move |v| f(&v))),
@ -335,6 +337,7 @@ impl InputRule {
&self, &self,
trigger: Option<InputRuleTrigger>, trigger: Option<InputRuleTrigger>,
value: Model<String>, value: Model<String>,
name: Signal<Option<String>>,
) -> Result<(), FieldValidationState> { ) -> Result<(), FieldValidationState> {
if let Some(trigger) = trigger { if let Some(trigger) = trigger {
if self.trigger != trigger { if self.trigger != trigger {
@ -345,7 +348,11 @@ impl InputRule {
value.with_untracked(|value| match &self.validator { value.with_untracked(|value| match &self.validator {
InputRuleValidator::Required(required) => { InputRuleValidator::Required(required) => {
if required.get_untracked() && value.is_empty() { if required.get_untracked() && value.is_empty() {
Err(FieldValidationState::Error(String::from(""))) let message = name.get_untracked().map_or_else(
|| String::from("Please input!"),
|name| format!("Please input {name}!"),
);
Err(FieldValidationState::Error(message))
} else { } else {
Ok(()) Ok(())
} }