feat: SpinButton adds rules prop

This commit is contained in:
luoxiao 2024-08-22 11:10:40 +08:00 committed by luoxiaozero
parent 51b04d911b
commit e1e5c02e54
3 changed files with 64 additions and 3 deletions

View file

@ -50,6 +50,17 @@ view! {
<ComboboxOption value="dog" text="Dog" /> <ComboboxOption value="dog" text="Dog" />
</Combobox> </Combobox>
</Field> </Field>
<Field name="spinbutton">
<SpinButton
step_page=1
rules=vec![SpinButtonRule::validator(move |v, _| {
if v % 2 == 0 {
Err(FieldValidationState::Error("It has to be odd!".to_string()))
} else {
Ok(())
}
})]/>
</Field>
<div style="margin-top: 8px"> <div style="margin-top: 8px">
<Button <Button
button_type=ButtonType::Submit button_type=ButtonType::Submit

View file

@ -81,6 +81,9 @@ view! {
| Name | Type | Default | Description | | Name | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| class | `MaybeProp<String>` | `Default::default()` | | | 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<SpinButtonRule<T>>` | `vec![]` | The rules to validate Field. |
| value | `Model<T>` | `T::default()` | Current value of the control. | | value | `Model<T>` | `T::default()` | Current value of the control. |
| placeholder | `MaybeProp<String>` | `Default::default()` | Placeholder of input number. | | placeholder | `MaybeProp<String>` | `Default::default()` | Placeholder of input number. |
| step_page | `MaybeSignal<T>` | | Large difference between two values. This should be greater than step and is used when users hit the Page Up or Page Down keys. | | step_page | `MaybeSignal<T>` | | Large difference between two values. This should be greater than step and is used when users hit the Page Up or Page Down keys. |

View file

@ -1,6 +1,7 @@
use crate::{FieldInjection, FieldValidationState, Rule};
use leptos::prelude::*; use leptos::prelude::*;
use num_traits::Bounded; use num_traits::Bounded;
use std::ops::{Add, Sub}; use std::ops::{Add, Deref, Sub};
use std::str::FromStr; use std::str::FromStr;
use thaw_utils::{ use thaw_utils::{
class_list, mount_style, with, BoxOneCallback, Model, OptionalProp, StoredMaybeSignal, class_list, mount_style, with, BoxOneCallback, Model, OptionalProp, StoredMaybeSignal,
@ -9,6 +10,13 @@ use thaw_utils::{
#[component] #[component]
pub fn SpinButton<T>( pub fn SpinButton<T>(
#[prop(optional, into)] class: MaybeProp<String>, #[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<SpinButtonRule<T>>,
/// Current value of the control. /// Current value of the control.
#[prop(optional, into)] #[prop(optional, into)]
value: Model<T>, value: Model<T>,
@ -41,22 +49,28 @@ where
T: Default + Clone + FromStr + ToString + 'static, T: Default + Clone + FromStr + ToString + 'static,
{ {
mount_style("spin-button", include_str!("./spin-button.css")); mount_style("spin-button", include_str!("./spin-button.css"));
let (id, name) = FieldInjection::use_id_and_name(id, name);
let validate = Rule::validate(rules, value, name);
let initialization_value = value.get_untracked().to_string(); let initialization_value = value.get_untracked().to_string();
let step_page: StoredMaybeSignal<_> = step_page.into(); let step_page: StoredMaybeSignal<_> = step_page.into();
let min: StoredMaybeSignal<_> = min.into(); let min: StoredMaybeSignal<_> = min.into();
let max: StoredMaybeSignal<_> = max.into(); let max: StoredMaybeSignal<_> = max.into();
let update_value = move |new_value| { let update_value = move |new_value| {
if with!(|value| value == &new_value) {
return;
}
let min = min.get_untracked(); let min = min.get_untracked();
let max = max.get_untracked(); let max = max.get_untracked();
if new_value < min { if new_value < min {
value.set(min); value.set(min);
} else if new_value > max { } else if new_value > max {
value.set(max); value.set(max);
} else if with!(|value| value != &new_value) { } else {
value.set(new_value); value.set(new_value);
} }
validate.run(Some(SpinButtonRuleTrigger::Change));
}; };
let increment_disabled = Memo::new(move |_| disabled.get() || value.get() >= max.get()); let increment_disabled = Memo::new(move |_| disabled.get() || value.get() >= max.get());
@ -100,6 +114,8 @@ where
} }
} }
class="thaw-spin-button__input" class="thaw-spin-button__input"
id=id
name=name
on:change=on_change on:change=on_change
/> />
<button <button
@ -163,3 +179,34 @@ where
</span> </span>
} }
} }
#[derive(Debug, Default, PartialEq, Clone, Copy)]
pub enum SpinButtonRuleTrigger {
#[default]
Change,
}
pub struct SpinButtonRule<T>(Rule<T, SpinButtonRuleTrigger>);
impl<T> SpinButtonRule<T> {
pub fn validator(
f: impl Fn(&T, Signal<Option<String>>) -> Result<(), FieldValidationState>
+ Send
+ Sync
+ 'static,
) -> Self {
Self(Rule::validator(f))
}
pub fn with_trigger(self, trigger: SpinButtonRuleTrigger) -> Self {
Self(Rule::with_trigger(self.0, trigger))
}
}
impl<T> Deref for SpinButtonRule<T> {
type Target = Rule<T, SpinButtonRuleTrigger>;
fn deref(&self) -> &Self::Target {
&self.0
}
}