leptos_form_core/form_component/impls/
num.rs

1use crate::*;
2use ::leptos::html::*;
3use ::leptos::*;
4use ::wasm_bindgen::JsValue;
5
6macro_rules! num_impl {
7    ($($ty:ty $({ $(type: $type:literal)?$(,)? $(min: $min:expr, max: $max:expr)?$(,)? })? ),*$(,)?) => { $(
8        impl DefaultHtmlElement for $ty {
9            type El = HtmlElement<Input>;
10        }
11
12        impl FormField<HtmlElement<Input>> for $ty {
13            type Config = ();
14            type Signal = FormFieldSignal<String>;
15
16            fn default_signal(_: &Self::Config, initial: Option<Self>) -> Self::Signal {
17                FormFieldSignal::new_with_default_value(initial.map(|x| x.to_string()))
18            }
19            fn is_initial_value(signal: &Self::Signal) -> bool {
20                signal.value.with(|value| signal.initial.with(|initial| match initial {
21                    Some(initial) => initial == value,
22                    None => value.is_empty(),
23                }))
24            }
25            fn into_signal(self, _: &Self::Config, initial: Option<Self>) -> Self::Signal {
26                FormFieldSignal::new(self.to_string(), initial.map(|x| x.to_string()))
27            }
28            fn try_from_signal(signal: Self::Signal, _: &Self::Config) -> Result<Self, FormError> {
29                signal.value.with(|value| value.parse()).map_err(FormError::parse)
30            }
31            fn recurse(signal: &Self::Signal) {
32                signal.value.with(|_| {})
33            }
34            fn reset_initial_value(signal: &Self::Signal) {
35                signal.value.with(|value| signal.initial.update(|initial| *initial = Some(value.clone())));
36            }
37            fn with_error<O>(signal: &Self::Signal, f: impl FnOnce(Option<&FormError>) -> O) -> O {
38                signal.error.with(|error| f(error.as_ref()))
39            }
40        }
41
42        impl FormComponent<HtmlElement<Input>> for $ty {
43            fn render(props: RenderProps<Self::Signal, Self::Config>) -> impl IntoView {
44                let class = props.class_signal();
45                view! {
46                    <input
47                        type=num_impl!(@type $($($type)?)?)
48                        class={class}
49                        id={props.id.or_else(|| props.name.clone())}
50                        max=num_impl!(@max $ty $($(, $max)?)?)
51                        min=num_impl!(@min $ty $($(, $min)?)?)
52                        name={props.name}
53                        on:keydown=num_impl!(@prevent_invalid_keystrokes value $($($type)?)?)
54                        on:input=move |ev| props.signal.value.update(|value| *value = event_target_value(&ev))
55                        on:change=move |_| {
56                            if let Err(form_error) = <Self as FormField<HtmlElement<Input>>>::try_from_signal(props.signal, &props.config) {
57                                props.signal.error.update(|error| *error = Some(form_error));
58                            } else if props.signal.error.with_untracked(|error| error.is_some()) {
59                                props.signal.error.update(|error| *error = None);
60                            }
61                        }
62                        prop:class={move || class.with(|x| x.as_ref().map(|x| JsValue::from_str(&*x)))}
63                        prop:value={props.signal.value}
64                        style={props.style}
65                        value=props.signal.value
66                    />
67                }
68            }
69        }
70    )* };
71    (@type $type:literal) => {$type};
72    (@type) => {"number"};
73
74    (@min $ty:ty, $min:expr) => {$min};
75    (@min $ty:ty) => {<$ty>::MIN};
76
77    (@max $ty:ty, $max:expr) => {$max};
78    (@max $ty:ty) => {<$ty>::MAX};
79
80    (@prevent_invalid_keystrokes $value:ident $type:literal) => {|ev| {
81        let key = ev.key();
82        if key.len() > 1 {
83            return;
84        }
85        if let Some(c) = key.chars().next() {
86            if !matches!(c, '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'|'.'|'-'|'+') {
87                ev.prevent_default();
88            }
89        }
90    }};
91    (@prevent_invalid_keystrokes $value:ident) => {|_| {}};
92}
93
94num_impl!(
95    u8,
96    u16,
97    u32,
98    u64,
99    u128,
100    usize,
101    i8,
102    i16,
103    i32,
104    i64,
105    i128,
106    isize,
107    f32 { type: "text" },
108    f64 { type: "text" },
109);
110
111#[cfg(feature = "num-bigint")]
112num_impl!(
113    num_bigint::BigInt {
114        min: None::<&'static str>,
115        max: None::<&'static str>
116    },
117    num_bigint::BigUint {
118        min: "0",
119        max: None::<&'static str>
120    },
121);
122
123#[cfg(feature = "bigdecimal")]
124num_impl!(
125    bigdecimal::BigDecimal { type: "text", min: None::<&'static str>, max: None::<&'static str> },
126);