feat: Textarea adds rules prop

This commit is contained in:
luoxiao 2024-08-22 15:46:44 +08:00 committed by luoxiaozero
parent db0148258d
commit f773fada0c
4 changed files with 92 additions and 15 deletions

View file

@ -38,6 +38,9 @@ view! {
<Checkbox label="Remember me" value="true"/>
</CheckboxGroup>
</Field>
<Field label="Textarea" name="textarea">
<Textarea rules=vec![TextareaRule::required(true.into())]/>
</Field>
<Field name="radio">
<RadioGroup rules=vec![RadioGroupRule::required(true.into())] >
<Radio label="0" value="false"/>
@ -65,7 +68,7 @@ view! {
<Field name="date">
<DatePicker rules=vec![DatePickerRule::required(true.into())]/>
</Field>
<div style="margin-top: 8px">
<Button
button_type=ButtonType::Submit

View file

@ -106,6 +106,9 @@ view! {
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| class | `MaybeProp<String>` | `Default::default()` | |
| id | `MaybeProp<String>` | `Default::default()` | |
| name | `MaybeProp<String>` | `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<InputRule>` | `vec![]` | The rules to validate Field. |
| value | `Model<String>` | `Default::default()` | Set the input value. |
| allow_value | `Option<ArcOneCallback<String, bool>>` | `None` | Check the incoming value, if it returns false, input will not be accepted. |
| input_type | `MaybeSignal<InputType>` | `InputType::Text` | An input can have different text-based types based on the type of value the user will enter. |

View file

@ -36,6 +36,9 @@ view! {
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| class | `MaybeProp<String>` | `Default::default()` | |
| id | `MaybeProp<String>` | `Default::default()` | |
| name | `MaybeProp<String>` | `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<TextareaRule>` | `vec![]` | The rules to validate Field. |
| value | `Model<String>` | `Default::default()` | The value of the Textarea. |
| allow_value | `Option<BoxOneCallback<String, bool>>` | `None` | Check the incoming value, if it returns false, input will not be accepted. |
| placeholder | `MaybeProp<String>` | `Default::default()` | Placeholder text for the input. |

View file

@ -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<String>,
#[prop(optional, into)] id: MaybeProp<String>,
/// 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<String>,
/// The rules to validate Field.
#[prop(optional, into)]
rules: Vec<TextareaRule>,
/// The value of the Textarea.
#[prop(optional, into)]
value: Model<String>,
@ -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::<html::Textarea>::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! {
<span class=class_list![
"thaw-textarea",
@ -85,9 +89,12 @@ pub fn Textarea(
}
on:input=on_input
on:change=on_change
on:focus=on_internal_focus
on:blur=on_internal_blur
class="thaw-textarea__textarea"
id=id
name=name
disabled=move || disabled.get()
placeholder=move || placeholder.get()
node_ref=textarea_ref
@ -136,3 +143,64 @@ impl TextareaResize {
}
}
}
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub enum TextareaRuleTrigger {
#[default]
Blur,
Focus,
Input,
Change,
}
pub struct TextareaRule(Rule<String, TextareaRuleTrigger>);
impl TextareaRule {
pub fn required(required: MaybeSignal<bool>) -> 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<bool>,
message: MaybeSignal<String>,
) -> 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<Option<String>>) -> 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<String, TextareaRuleTrigger>;
fn deref(&self) -> &Self::Target {
&self.0
}
}