input_number_within_range.rs
1 use crate::utils::stored_maybe_signal::StoredMaybeSignal; 2 use icondata as i; 3 use leptos::*; 4 use std::cmp::PartialOrd; 5 use std::ops::{Add, Sub}; 6 use std::str::FromStr; 7 use thaw::{Button, ButtonVariant, Icon, Input, InputSuffix}; 8 9 #[component] 10 pub fn InputNumberWithinRange<T>( 11 #[prop(optional, into)] value: RwSignal<T>, 12 #[prop(optional, into)] placeholder: MaybeSignal<String>, 13 #[prop(into)] step: MaybeSignal<T>, 14 #[prop(into)] min: MaybeSignal<T>, 15 #[prop(into)] max: MaybeSignal<T>, 16 ) -> impl IntoView 17 where 18 T: Add<Output = T> + Sub<Output = T> + PartialOrd, 19 T: Default + Clone + FromStr + ToString + 'static, 20 { 21 let input_value = create_rw_signal(String::default()); 22 23 create_effect(move |prev| { 24 value.with(|value| { 25 let value = value.to_string(); 26 if let Some(prev) = prev { 27 if value == prev { 28 return prev; 29 } 30 } 31 input_value.set(value.clone()); 32 value 33 }) 34 }); 35 36 let allow_value = Callback::<String, bool>::new(move |v: String| { 37 let Ok(v) = v.parse::<T>() else { return false }; 38 value.set(v); 39 true 40 }); 41 let step: StoredMaybeSignal<_> = step.into(); 42 let min: StoredMaybeSignal<_> = min.into(); 43 let max: StoredMaybeSignal<_> = max.into(); 44 45 let add = Callback::<ev::MouseEvent>::new(move |_| { 46 let prev = value.get_untracked(); 47 if prev >= max.get_untracked() { 48 return; 49 } 50 value.set(prev + step.get_untracked()); 51 }); 52 let sub = Callback::<ev::MouseEvent>::new(move |_| { 53 let prev = value.get_untracked(); 54 if prev <= min.get_untracked() { 55 return; 56 } 57 value.set(prev - step.get_untracked()); 58 }); 59 let set_within_range = Callback::<ev::FocusEvent>::new(move |_| { 60 let value_gotten = value.get_untracked(); 61 let min = min.get_untracked(); 62 let max = max.get_untracked(); 63 if value_gotten < min { 64 value.set(min.clone()); 65 } else if value_gotten > max { 66 value.set(max.clone()); 67 } 68 }); 69 view! { 70 <Input value=input_value allow_value placeholder on_blur=set_within_range> 71 <InputSuffix slot> 72 <Button variant=ButtonVariant::Link on_click=sub> 73 <Icon 74 icon=i::AiMinusOutlined 75 style="font-size: 18px" 76 /> 77 </Button> 78 <Button variant=ButtonVariant::Link on_click=add> 79 <Icon 80 icon=i::AiPlusOutlined 81 style="font-size: 18px" 82 /> 83 </Button> 84 </InputSuffix> 85 </Input> 86 } 87 }