material_dioxus/text_inputs/
textfield.rs1use std::marker::PhantomData;
2
3use super::set_on_input_handler;
4use crate::text_inputs::{validity_state::ValidityStateJS, TextFieldType, ValidityTransform};
5use crate::StaticCallback;
6use dioxus::core::AttributeValue;
7use dioxus::prelude::*;
8use gloo::events::EventListener;
9use wasm_bindgen::prelude::*;
10use wasm_bindgen::JsCast;
11use web_sys::Node;
12use web_sys::ValidityState as NativeValidityState;
13
14#[wasm_bindgen(module = "/build/mwc-textfield.js")]
15extern "C" {
16 #[derive(Debug, Clone)]
17 #[wasm_bindgen(extends = Node)]
18 type TextField;
19
20 #[wasm_bindgen(getter, static_method_of = TextField)]
21 fn _dummy_loader() -> JsValue;
22
23 #[wasm_bindgen(method, setter = validityTransform)]
24 fn set_validity_transform(
25 this: &TextField,
26 val: &Closure<dyn Fn(String, NativeValidityState) -> ValidityStateJS>,
27 );
28
29 #[wasm_bindgen(method, setter)]
30 fn set_type(this: &TextField, val: &JsValue);
31
32 #[wasm_bindgen(method, getter)]
33 fn value(this: &TextField) -> String;
34
35 #[wasm_bindgen(method, setter)]
36 fn set_value(this: &TextField, val: &JsValue);
37}
38
39loader_hack!(TextField);
40
41#[derive(Props)]
47pub struct TextFieldProps<'a> {
48 #[props(default)]
49 pub open: bool,
50 #[props(into)]
51 pub value: Option<String>,
52 #[props(default = TextFieldType::Text)]
53 pub field_type: TextFieldType,
54 #[props(into)]
55 pub label: Option<String>,
56 #[props(into)]
57 pub placeholder: Option<String>,
58 #[props(into)]
59 pub prefix: Option<String>,
60 #[props(into)]
61 pub suffix: Option<String>,
62 #[props(into)]
63 pub icon: Option<String>,
64 #[props(into)]
65 pub icon_trailing: Option<String>,
66 #[props(default)]
67 pub disabled: bool,
68 #[props(default)]
69 pub char_counter: bool,
70 #[props(default)]
71 pub outlined: bool,
72 #[props(into)]
73 pub helper: Option<String>,
74 #[props(default)]
75 pub helper_persistent: bool,
76 #[props(default)]
77 pub required: bool,
78 #[props(default)]
79 pub max_length: Option<u64>,
80 #[props(into)]
81 pub validation_message: Option<String>,
82 #[props(into)]
83 pub pattern: Option<String>,
84 #[props(into)]
86 pub min: Option<String>,
87 #[props(into)]
89 pub max: Option<String>,
90 #[props(default)]
92 pub size: Option<i64>,
93 #[props(default)]
95 pub step: Option<i64>,
96 #[props(default)]
97 pub auto_validate: bool,
98 pub validity_transform: Option<ValidityTransform>,
99 #[props(default)]
100 pub validate_on_initial_render: bool,
101 #[props(into)]
102 pub _oninput: Option<StaticCallback<String>>,
105 _lifetime: Option<PhantomData<&'a ()>>,
106 #[props(into)]
107 pub name: Option<String>,
108
109 #[props(default)]
110 pub webkit_date_picker: bool,
111 #[props(into)]
112 pub _onchange: Option<StaticCallback<String>>,
113
114 #[props(into, default)]
115 pub style: String,
116 #[props(into, default)]
117 pub class: String,
118 #[props(into)]
119 pub slot: Option<String>,
120 #[props(default)]
121 pub dialog_initial_focus: bool,
122}
123
124fn render<'a>(cx: Scope<'a, TextFieldProps<'a>>) -> Element<'a> {
125 let id = crate::use_id(cx, "textfield");
126 let input_listener = cx.use_hook(|| None);
127 let change_listener = cx.use_hook(|| None);
128 let validity_transform_closure = cx.use_hook(|| None);
129 if let Some(elem) = crate::get_elem_by_id(id) {
130 let target = elem.clone();
131 let textfield = JsValue::from(elem).dyn_into::<TextField>().unwrap();
132 textfield.set_type(&JsValue::from(cx.props.field_type.as_str()));
133 textfield.set_value(&JsValue::from_str(
134 cx.props
135 .value
136 .as_ref()
137 .map(|s| s.as_ref())
138 .unwrap_or_default(),
139 ));
140 if let Some(listener) = cx.props._oninput.clone() {
141 *input_listener = Some(set_on_input_handler(&target, listener, |(_, detail)| {
142 detail
143 .unchecked_into::<MatTextFieldInputEvent>()
144 .target()
145 .value()
146 }));
147 }
148 if let Some(listener) = cx.props._onchange.clone() {
149 to_owned![textfield];
150 *change_listener = Some(EventListener::new(&target, "change", move |_| {
151 listener.call(textfield.value())
152 }));
153 }
154 if let (Some(transform), None) = (
155 cx.props.validity_transform.clone(),
156 &validity_transform_closure,
157 ) {
158 *validity_transform_closure = Some(Closure::wrap(Box::new(
159 move |s: String, v: NativeValidityState| -> ValidityStateJS {
160 transform.0(s, v).into()
161 },
162 )
163 as Box<dyn Fn(String, NativeValidityState) -> ValidityStateJS>));
164 textfield.set_validity_transform(validity_transform_closure.as_ref().unwrap());
165 }
166 }
167 render! {
168 mwc-textfield {
169 id: id,
170
171 open: bool_attr!(cx.props.open),
172 label: optional_string_attr!(cx.props.label),
173 placeholder: optional_string_attr!(cx.props.placeholder),
174 prefix: optional_string_attr!(cx.props.prefix),
175 suffix: optional_string_attr!(cx.props.suffix),
176 icon: optional_string_attr!(cx.props.icon),
177 iconTrailing: optional_string_attr!(cx.props.icon_trailing),
178 disabled: bool_attr!(cx.props.disabled),
179 charCounter: bool_attr!(cx.props.char_counter),
180 outlined: bool_attr!(cx.props.outlined),
181 helper: optional_string_attr!(cx.props.helper),
182 helperPersistent: bool_attr!(cx.props.helper_persistent),
183 required: bool_attr!(cx.props.required),
184 maxLength: cx.props.max_length.map(|v| format_args!("{v}").into_value(cx.bump())).unwrap_or(AttributeValue::None),
185 validationMessage: optional_string_attr!(cx.props.validation_message),
186 pattern: optional_string_attr!(cx.props.pattern),
187 min: optional_string_attr!(cx.props.min),
188 max: optional_string_attr!(cx.props.max),
189 size: cx.props.size.map(|v| format_args!("{v}").into_value(cx.bump())).unwrap_or(AttributeValue::None),
190 step: cx.props.step.map(|v| format_args!("{v}").into_value(cx.bump())).unwrap_or(AttributeValue::None),
191 autoValidate: bool_attr!(cx.props.auto_validate),
192 validateOnInitialRender: bool_attr!(cx.props.validate_on_initial_render),
193 name: optional_string_attr!(cx.props.name),
194 dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus),
195 webkitDatePicker: bool_attr!(cx.props.webkit_date_picker),
196
197 style: string_attr!(cx.props.style),
198 class: string_attr!(cx.props.class),
199 slot: optional_string_attr!(cx.props.slot),
200 dialogInitialFocus: bool_attr!(cx.props.dialog_initial_focus),
201 }
202 }
203}
204
205component!('a, MatTextField, TextFieldProps, render, TextField, "textfield");
206
207#[wasm_bindgen]
208extern "C" {
209 type MatTextFieldInputEvent;
210
211 #[wasm_bindgen(method, getter)]
212 fn target(this: &MatTextFieldInputEvent) -> TextField;
213}