leptos_form_core/form_component/impls/
num.rs1use 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);