diff --git a/demo_markdown/docs/input/mod.md b/demo_markdown/docs/input/mod.md index 285ecbf..9196fd5 100644 --- a/demo_markdown/docs/input/mod.md +++ b/demo_markdown/docs/input/mod.md @@ -102,6 +102,25 @@ view! { } ``` +### Formatter + +```rust demo +let value = create_rw_signal(String::from("loren_ipsun")); + +let formatter = Callback::::new(move |v: String| { + v.replace("_", " ") +}); + +let parser = Callback::::new(move |v: String| { + v.replace(" ", "_") +}); + +view! { + +

"Underlying value: "{ value }

+} +``` + ### Input Props | Name | Type | Default | Description | @@ -116,6 +135,8 @@ view! { | on_focus | `Option>` | `None` | Callback triggered when the input is focussed on. | | on_blur | `Option>` | `None` | Callback triggered when the input is blurred. | | attr: | `Vec<(&'static str, Attribute)>` | `Default::default()` | The dom attrs of the input element inside the component. | +| parser | `OptionalProp>` | `Default::default()` | Modifies the user input before assigning it to the value | +| formatter | `OptionalProp>` | `Default::default()` | Formats the value to be shown to the user | ### Input Slots diff --git a/demo_markdown/docs/input_number/mod.md b/demo_markdown/docs/input_number/mod.md index c50a21c..8305d8c 100644 --- a/demo_markdown/docs/input_number/mod.md +++ b/demo_markdown/docs/input_number/mod.md @@ -42,6 +42,49 @@ view! { } ``` +### Formatter + +```rust demo +let value = create_rw_signal(0.0); +let value_2 = create_rw_signal(0.0); + +let formatter = Callback::::new(move |v: f64| { + let v = v.to_string(); + let dot_pos = v.chars().position(|c| c == '.').unwrap_or_else(|| v.chars().count()); + let mut int: String = v.chars().take(dot_pos).collect(); + + let sign: String = if v.chars().take(1).collect::() == String::from("-") { + int = int.chars().skip(1).collect(); + String::from("-") + } else { + String::from("") + }; + + let dec: String = v.chars().skip(dot_pos + 1).take(2).collect(); + + let int = int + .as_bytes() + .rchunks(3) + .rev() + .map(std::str::from_utf8) + .collect::, _>>() + .unwrap() + .join("."); + format!("{}{},{:0<2}", sign, int, dec) +}); + +let parser = Callback::::new(move |v: String| { + let comma_pos = v.chars().position(|c| c == ',').unwrap_or_else(|| v.chars().count()); + let int_part = v.chars().take(comma_pos).filter(|a| a.is_digit(10)).collect::(); + let dec_part = v.chars().skip(comma_pos + 1).take(2).filter(|a| a.is_digit(10)).collect::(); + format!("{:0<1}.{:0<2}", int_part, dec_part).parse::().unwrap_or_default() +}); + +view! { + +

"Underlying value: "{ value }

+} +``` ### InputNumber Props | Name | Type | Default | Description | @@ -55,6 +98,8 @@ view! { | disabled | `MaybeSignal` | `false` | Whether the input is disabled. | | invalid | `MaybeSignal` | `false` | Whether the input is invalid. | | attr: | `Vec<(&'static str, Attribute)>` | `Default::default()` | The dom attrs of the input element inside the component. | +| parser | `OptionalProp>` | `Default::default()` | Modifies the user input before assigning it to the value | +| formatter | `OptionalProp>` | `Default::default()` | Formats the value to be shown to the user | #### T impl diff --git a/thaw/src/input/mod.rs b/thaw/src/input/mod.rs index 0403f39..741a9b4 100644 --- a/thaw/src/input/mod.rs +++ b/thaw/src/input/mod.rs @@ -53,20 +53,36 @@ pub fn Input( #[prop(optional)] comp_ref: ComponentRef, #[prop(optional, into)] class: OptionalProp>, #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, + #[prop(optional, into)] parser: OptionalProp>, + #[prop(optional, into)] formatter: OptionalProp>, ) -> impl IntoView { let theme = use_theme(Theme::light); mount_style("input", include_str!("./input.css")); let value_trigger = create_trigger(); let on_input = move |ev| { - let input_value = event_target_value(&ev); - if let Some(allow_value) = allow_value.as_ref() { - if !allow_value.call(input_value.clone()) { - value_trigger.notify(); - return; + if parser.is_none() { + let input_value = event_target_value(&ev); + if let Some(allow_value) = allow_value.as_ref() { + if !allow_value.call(input_value.clone()) { + value_trigger.notify(); + return; + } } + value.set(input_value); + } + }; + let on_change = move |ev| { + if let Some(parser) = parser.or_else(|| None) { + let parsed_input_value = parser.call(event_target_value(&ev)); + if let Some(allow_value) = allow_value.as_ref() { + if !allow_value.call(parsed_input_value.clone()) { + value_trigger.notify(); + return; + } + } + value.set(parsed_input_value); } - value.set(input_value); }; let is_focus = create_rw_signal(false); let on_internal_focus = move |ev| { @@ -186,9 +202,10 @@ pub fn Input( value=input_value prop:value=move || { value_trigger.track(); - value.get() + formatter.map_or_else(|| value.get(), |c| c.call(value.get())) } + on:change=on_change on:input=on_input on:focus=on_internal_focus on:blur=on_internal_blur diff --git a/thaw/src/input_number/mod.rs b/thaw/src/input_number/mod.rs index 8999530..4747586 100644 --- a/thaw/src/input_number/mod.rs +++ b/thaw/src/input_number/mod.rs @@ -14,6 +14,8 @@ pub fn InputNumber( #[prop(optional, into)] invalid: MaybeSignal, #[prop(optional, into)] class: OptionalProp>, #[prop(optional)] comp_ref: ComponentRef, + #[prop(optional, into)] parser: OptionalProp>, + #[prop(optional, into)] formatter: OptionalProp>, #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, #[prop(default = MaybeSignal::Static(T::min_value()), into)] min: MaybeSignal, #[prop(default = MaybeSignal::Static(T::max_value()), into)] max: MaybeSignal, @@ -77,6 +79,13 @@ where invalid.get() || value < min.get() || value > max.get() }); + let parser = parser.map(|parser| { + Callback::new(move |v| parser.call(v).to_string()) + }); + let formatter = formatter.map(|formatter| { + Callback::new(move |v: String| formatter.call(v.parse::().unwrap_or_default())) + }); + view! {