Skip to content

Commit 089b3a0

Browse files
authored
Added parser and formatter props to input and input_number components (#209)
* Added parser and formatter properties to input and input_number components * Implemented copy trait for OptionalProp * InputNumber's formatter and parser using its generic type * Fixed InputNumber's docs
1 parent 7685e99 commit 089b3a0

File tree

5 files changed

+103
-7
lines changed

5 files changed

+103
-7
lines changed

demo_markdown/docs/input/mod.md

+21
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,25 @@ view! {
102102
}
103103
```
104104

105+
### Formatter
106+
107+
```rust demo
108+
let value = create_rw_signal(String::from("loren_ipsun"));
109+
110+
let formatter = Callback::<String, String>::new(move |v: String| {
111+
v.replace("_", " ")
112+
});
113+
114+
let parser = Callback::<String, String>::new(move |v: String| {
115+
v.replace(" ", "_")
116+
});
117+
118+
view! {
119+
<Input value parser formatter />
120+
<p>"Underlying value: "{ value }</p>
121+
}
122+
```
123+
105124
### Input Props
106125

107126
| Name | Type | Default | Description |
@@ -116,6 +135,8 @@ view! {
116135
| on_focus | `Option<Callback<ev::FocusEvent>>` | `None` | Callback triggered when the input is focussed on. |
117136
| on_blur | `Option<Callback<ev::FocusEvent>>` | `None` | Callback triggered when the input is blurred. |
118137
| attr: | `Vec<(&'static str, Attribute)>` | `Default::default()` | The dom attrs of the input element inside the component. |
138+
| parser | `OptionalProp<Callback<String, String>>` | `Default::default()` | Modifies the user input before assigning it to the value |
139+
| formatter | `OptionalProp<Callback<String, String>>` | `Default::default()` | Formats the value to be shown to the user |
119140

120141
### Input Slots
121142

demo_markdown/docs/input_number/mod.md

+45
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,49 @@ view! {
4242
}
4343
```
4444

45+
### Formatter
46+
47+
```rust demo
48+
let value = create_rw_signal(0.0);
49+
let value_2 = create_rw_signal(0.0);
50+
51+
let formatter = Callback::<f64, String>::new(move |v: f64| {
52+
let v = v.to_string();
53+
let dot_pos = v.chars().position(|c| c == '.').unwrap_or_else(|| v.chars().count());
54+
let mut int: String = v.chars().take(dot_pos).collect();
55+
56+
let sign: String = if v.chars().take(1).collect::<String>() == String::from("-") {
57+
int = int.chars().skip(1).collect();
58+
String::from("-")
59+
} else {
60+
String::from("")
61+
};
62+
63+
let dec: String = v.chars().skip(dot_pos + 1).take(2).collect();
64+
65+
let int = int
66+
.as_bytes()
67+
.rchunks(3)
68+
.rev()
69+
.map(std::str::from_utf8)
70+
.collect::<Result<Vec<&str>, _>>()
71+
.unwrap()
72+
.join(".");
73+
format!("{}{},{:0<2}", sign, int, dec)
74+
});
75+
76+
let parser = Callback::<String, f64>::new(move |v: String| {
77+
let comma_pos = v.chars().position(|c| c == ',').unwrap_or_else(|| v.chars().count());
78+
let int_part = v.chars().take(comma_pos).filter(|a| a.is_digit(10)).collect::<String>();
79+
let dec_part = v.chars().skip(comma_pos + 1).take(2).filter(|a| a.is_digit(10)).collect::<String>();
80+
format!("{:0<1}.{:0<2}", int_part, dec_part).parse::<f64>().unwrap_or_default()
81+
});
82+
83+
view! {
84+
<InputNumber value parser formatter step=1.0 />
85+
<p>"Underlying value: "{ value }</p>
86+
}
87+
```
4588
### InputNumber Props
4689

4790
| Name | Type | Default | Description |
@@ -55,6 +98,8 @@ view! {
5598
| disabled | `MaybeSignal<bool>` | `false` | Whether the input is disabled. |
5699
| invalid | `MaybeSignal<bool>` | `false` | Whether the input is invalid. |
57100
| attr: | `Vec<(&'static str, Attribute)>` | `Default::default()` | The dom attrs of the input element inside the component. |
101+
| parser | `OptionalProp<Callback<String, T>>` | `Default::default()` | Modifies the user input before assigning it to the value |
102+
| formatter | `OptionalProp<Callback<T, String>>` | `Default::default()` | Formats the value to be shown to the user |
58103

59104
#### T impl
60105

thaw/src/input/mod.rs

+24-7
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,36 @@ pub fn Input(
5353
#[prop(optional)] comp_ref: ComponentRef<InputRef>,
5454
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
5555
#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
56+
#[prop(optional, into)] parser: OptionalProp<Callback<String, String>>,
57+
#[prop(optional, into)] formatter: OptionalProp<Callback<String, String>>,
5658
) -> impl IntoView {
5759
let theme = use_theme(Theme::light);
5860
mount_style("input", include_str!("./input.css"));
5961

6062
let value_trigger = create_trigger();
6163
let on_input = move |ev| {
62-
let input_value = event_target_value(&ev);
63-
if let Some(allow_value) = allow_value.as_ref() {
64-
if !allow_value.call(input_value.clone()) {
65-
value_trigger.notify();
66-
return;
64+
if parser.is_none() {
65+
let input_value = event_target_value(&ev);
66+
if let Some(allow_value) = allow_value.as_ref() {
67+
if !allow_value.call(input_value.clone()) {
68+
value_trigger.notify();
69+
return;
70+
}
71+
}
72+
value.set(input_value);
73+
}
74+
};
75+
let on_change = move |ev| {
76+
if let Some(parser) = parser.or_else(|| None) {
77+
let parsed_input_value = parser.call(event_target_value(&ev));
78+
if let Some(allow_value) = allow_value.as_ref() {
79+
if !allow_value.call(parsed_input_value.clone()) {
80+
value_trigger.notify();
81+
return;
82+
}
6783
}
84+
value.set(parsed_input_value);
6885
}
69-
value.set(input_value);
7086
};
7187
let is_focus = create_rw_signal(false);
7288
let on_internal_focus = move |ev| {
@@ -186,9 +202,10 @@ pub fn Input(
186202
value=input_value
187203
prop:value=move || {
188204
value_trigger.track();
189-
value.get()
205+
formatter.map_or_else(|| value.get(), |c| c.call(value.get()))
190206
}
191207

208+
on:change=on_change
192209
on:input=on_input
193210
on:focus=on_internal_focus
194211
on:blur=on_internal_blur

thaw/src/input_number/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub fn InputNumber<T>(
1414
#[prop(optional, into)] invalid: MaybeSignal<bool>,
1515
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
1616
#[prop(optional)] comp_ref: ComponentRef<InputNumberRef>,
17+
#[prop(optional, into)] parser: OptionalProp<Callback<String, T>>,
18+
#[prop(optional, into)] formatter: OptionalProp<Callback<T, String>>,
1719
#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
1820
#[prop(default = MaybeSignal::Static(T::min_value()), into)] min: MaybeSignal<T>,
1921
#[prop(default = MaybeSignal::Static(T::max_value()), into)] max: MaybeSignal<T>,
@@ -77,6 +79,13 @@ where
7779
invalid.get() || value < min.get() || value > max.get()
7880
});
7981

82+
let parser = parser.map(|parser| {
83+
Callback::new(move |v| parser.call(v).to_string())
84+
});
85+
let formatter = formatter.map(|formatter| {
86+
Callback::new(move |v: String| formatter.call(v.parse::<T>().unwrap_or_default()))
87+
});
88+
8089
view! {
8190
<Input
8291
attrs
@@ -88,6 +97,8 @@ where
8897
invalid
8998
comp_ref=input_ref
9099
on_blur=set_within_range
100+
parser
101+
formatter
91102
>
92103
<InputSuffix slot>
93104
<Button disabled=minus_disabled variant=ButtonVariant::Link on_click=sub>

thaw_utils/src/optional_prop.rs

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ impl<T> From<Option<T>> for OptionalProp<T> {
9696
}
9797
}
9898

99+
impl<T: Copy> Copy for OptionalProp<T> {}
100+
99101
#[cfg(test)]
100102
mod test {
101103
use super::OptionalProp;

0 commit comments

Comments
 (0)