yew_limput/
lib.rs

1use std::rc::Rc;
2
3use tracing::error;
4use web_sys::HtmlInputElement;
5use web_sys::wasm_bindgen::JsCast;
6use yew::prelude::*;
7
8pub type LimitedInputFilter = dyn for<'a> Fn(&'a char) -> bool;
9
10#[macro_export]
11macro_rules! input_filter {
12    ($filter:expr) => {
13        Rc::new($filter) as Rc<$crate::LimitedInputFilter>
14    };
15}
16
17#[derive(Properties)]
18struct LimitedInputProps {
19    input_type: &'static str,
20    class: AttrValue,
21    filter: Rc<LimitedInputFilter>,
22    max_len: Option<usize>,
23}
24
25impl PartialEq for LimitedInputProps {
26    fn eq(&self, other: &Self) -> bool {
27        self.input_type == other.input_type
28            && self.class == other.class
29            && Rc::ptr_eq(&self.filter, &other.filter)
30            && self.max_len == other.max_len
31    }
32}
33
34#[function_component]
35fn LimitedInput(props: &LimitedInputProps) -> Html {
36    let oninput = {
37        let filter = props.filter.clone();
38        let max_len = props.max_len;
39        move |event: InputEvent| {
40            let Some(target) = event.target() else {
41                error!("input event has no target");
42                return;
43            };
44
45            let Ok(input) = target.dyn_into::<HtmlInputElement>() else {
46                error!("input event target is not an input element");
47                return;
48            };
49
50            let value = input.value();
51
52            let filtered_value: String = value
53                .chars()
54                .filter(|c| (filter)(c))
55                .take(max_len.unwrap_or(usize::MAX))
56                .collect();
57
58            input.set_value(&filtered_value);
59        }
60    };
61
62    html! {
63        <input
64            type={props.input_type}
65            class={&props.class}
66            {oninput}
67        />
68    }
69}
70
71#[derive(Properties)]
72pub struct LimitedTextInputProps {
73    #[prop_or_default]
74    pub class: AttrValue,
75    pub filter: Rc<LimitedInputFilter>,
76    pub max_len: Option<usize>,
77}
78
79impl PartialEq for LimitedTextInputProps {
80    fn eq(&self, other: &Self) -> bool {
81        self.class == other.class
82            && Rc::ptr_eq(&self.filter, &other.filter)
83            && self.max_len == other.max_len
84    }
85}
86
87#[function_component]
88pub fn LimitedTextInput(props: &LimitedTextInputProps) -> Html {
89    let input_type = "text";
90    let class = &props.class;
91    let filter = props.filter.clone();
92    let max_len = props.max_len;
93
94    html! {
95        <LimitedInput {input_type} {class} {filter} {max_len} />
96    }
97}
98
99#[derive(PartialEq, Properties)]
100pub struct LimitedNumericInputProps {
101    #[prop_or_default]
102    pub class: AttrValue,
103    pub max_len: Option<usize>,
104}
105
106#[function_component]
107pub fn LimitedNumericInput(props: &LimitedNumericInputProps) -> Html {
108    let class = &props.class;
109    let filter = Rc::new(|c: &char| c.is_ascii_digit()) as Rc<LimitedInputFilter>;
110    let max_len = props.max_len;
111
112    html! {
113        <LimitedTextInput {class} {filter} {max_len} />
114    }
115}