diff --git a/thaw/src/field/docs/mod.md b/thaw/src/field/docs/mod.md index d438997..888cad7 100644 --- a/thaw/src/field/docs/mod.md +++ b/thaw/src/field/docs/mod.md @@ -38,6 +38,9 @@ view! { + + + @@ -65,7 +68,7 @@ view! { - + ` | `Default::default()` | | +| id | `MaybeProp` | `Default::default()` | | +| name | `MaybeProp` | `Default::default()` | A string specifying a name for the input control. This name is submitted along with the control's value when the form data is submitted. | +| rules | `Vec` | `vec![]` | The rules to validate Field. | | value | `Model` | `Default::default()` | Set the input value. | | allow_value | `Option>` | `None` | Check the incoming value, if it returns false, input will not be accepted. | | input_type | `MaybeSignal` | `InputType::Text` | An input can have different text-based types based on the type of value the user will enter. | diff --git a/thaw/src/textarea/docs/mod.md b/thaw/src/textarea/docs/mod.md index 833a618..67b826a 100644 --- a/thaw/src/textarea/docs/mod.md +++ b/thaw/src/textarea/docs/mod.md @@ -36,6 +36,9 @@ view! { | Name | Type | Default | Description | | --- | --- | --- | --- | | class | `MaybeProp` | `Default::default()` | | +| id | `MaybeProp` | `Default::default()` | | +| name | `MaybeProp` | `Default::default()` | A string specifying a name for the input control. This name is submitted along with the control's value when the form data is submitted. | +| rules | `Vec` | `vec![]` | The rules to validate Field. | | value | `Model` | `Default::default()` | The value of the Textarea. | | allow_value | `Option>` | `None` | Check the incoming value, if it returns false, input will not be accepted. | | placeholder | `MaybeProp` | `Default::default()` | Placeholder text for the input. | diff --git a/thaw/src/textarea/mod.rs b/thaw/src/textarea/mod.rs index e992fb3..764ef7c 100644 --- a/thaw/src/textarea/mod.rs +++ b/thaw/src/textarea/mod.rs @@ -1,9 +1,19 @@ +use crate::{FieldInjection, FieldValidationState, Rule}; use leptos::{ev, html, prelude::*}; +use std::ops::Deref; use thaw_utils::{class_list, mount_style, BoxOneCallback, ComponentRef, Model}; #[component] pub fn Textarea( #[prop(optional, into)] class: MaybeProp, + #[prop(optional, into)] id: MaybeProp, + /// A string specifying a name for the input control. + /// This name is submitted along with the control's value when the form data is submitted. + #[prop(optional, into)] + name: MaybeProp, + /// The rules to validate Field. + #[prop(optional, into)] + rules: Vec, /// The value of the Textarea. #[prop(optional, into)] value: Model, @@ -29,7 +39,8 @@ pub fn Textarea( // #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, ) -> impl IntoView { mount_style("textarea", include_str!("./textarea.css")); - + let (id, name) = FieldInjection::use_id_and_name(id, name); + let validate = Rule::validate(rules, value, name); let value_trigger = ArcTrigger::new(); let on_input = { let value_trigger = value_trigger.clone(); @@ -42,35 +53,28 @@ pub fn Textarea( } } value.set(input_value); + validate.run(Some(TextareaRuleTrigger::Input)); } }; + let on_change = move |_| { + validate.run(Some(TextareaRuleTrigger::Change)); + }; let on_internal_focus = move |ev| { if let Some(on_focus) = on_focus.as_ref() { on_focus(ev); } + validate.run(Some(TextareaRuleTrigger::Focus)); }; let on_internal_blur = move |ev| { if let Some(on_blur) = on_blur.as_ref() { on_blur(ev); } + validate.run(Some(TextareaRuleTrigger::Blur)); }; let textarea_ref = NodeRef::::new(); comp_ref.load(TextareaRef { textarea_ref }); - // #[cfg(debug_assertions)] - // { - // const INNER_ATTRS: [&str; 3] = ["class", "disabled", "placeholder"]; - // attrs.iter().for_each(|attr| { - // if INNER_ATTRS.contains(&attr.0) { - // logging::warn!( - // "Thaw: The '{}' attribute already exists on elements inside the TextArea component, which may cause conflicts.", - // attr.0 - // ); - // } - // }); - // } - view! { ); + +impl TextareaRule { + pub fn required(required: MaybeSignal) -> Self { + Self::validator(move |value, name| { + if required.get_untracked() && value.is_empty() { + let message = name.get_untracked().map_or_else( + || String::from("Please input!"), + |name| format!("Please input {name}!"), + ); + Err(FieldValidationState::Error(message)) + } else { + Ok(()) + } + }) + } + + pub fn required_with_message( + required: MaybeSignal, + message: MaybeSignal, + ) -> Self { + Self::validator(move |value, _| { + if required.get_untracked() && value.is_empty() { + Err(FieldValidationState::Error(message.get_untracked())) + } else { + Ok(()) + } + }) + } + + pub fn validator( + f: impl Fn(&String, Signal>) -> Result<(), FieldValidationState> + + Send + + Sync + + 'static, + ) -> Self { + Self(Rule::validator(f)) + } + + pub fn with_trigger(self, trigger: TextareaRuleTrigger) -> Self { + Self(Rule::with_trigger(self.0, trigger)) + } +} + +impl Deref for TextareaRule { + type Target = Rule; + + fn deref(&self) -> &Self::Target { + &self.0 + } +}