material_yew/text_inputs/
textfield.rs1use super::set_on_input_handler;
2use crate::text_inputs::{
3 validity_state::ValidityStateJS, TextFieldType, ValidityState, ValidityTransform,
4};
5use crate::{bool_to_option, WeakComponentLink};
6use gloo::events::EventListener;
7use wasm_bindgen::prelude::*;
8use wasm_bindgen::JsCast;
9use web_sys::Node;
10use web_sys::ValidityState as NativeValidityState;
11use yew::prelude::*;
12use yew::virtual_dom::AttrValue;
13
14#[wasm_bindgen(module = "/build/mwc-textfield.js")]
15extern "C" {
16 #[derive(Debug)]
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
41pub struct MatTextField {
45 node_ref: NodeRef,
46 validity_transform_closure:
47 Option<Closure<dyn Fn(String, NativeValidityState) -> ValidityStateJS>>,
48 input_listener: Option<EventListener>,
49}
50
51#[derive(Properties, PartialEq, Clone)]
57pub struct TextFieldProps {
58 #[prop_or_default]
59 pub open: bool,
60 #[prop_or_default]
61 pub value: Option<AttrValue>,
62 #[prop_or(TextFieldType::Text)]
63 pub field_type: TextFieldType,
64 #[prop_or_default]
65 pub label: Option<AttrValue>,
66 #[prop_or_default]
67 pub placeholder: Option<AttrValue>,
68 #[prop_or_default]
69 pub prefix: Option<AttrValue>,
70 #[prop_or_default]
71 pub suffix: Option<AttrValue>,
72 #[prop_or_default]
73 pub icon: Option<AttrValue>,
74 #[prop_or_default]
75 pub icon_trailing: Option<AttrValue>,
76 #[prop_or_default]
77 pub disabled: bool,
78 #[prop_or_default]
79 pub char_counter: bool,
80 #[prop_or_default]
81 pub outlined: bool,
82 #[prop_or_default]
83 pub helper: Option<AttrValue>,
84 #[prop_or_default]
85 pub helper_persistent: bool,
86 #[prop_or_default]
87 pub required: bool,
88 #[prop_or_default]
89 pub max_length: Option<u64>,
90 #[prop_or_default]
91 pub validation_message: Option<AttrValue>,
92 #[prop_or_default]
93 pub pattern: Option<AttrValue>,
94 #[prop_or_default]
96 pub min: Option<AttrValue>,
97 #[prop_or_default]
99 pub max: Option<AttrValue>,
100 #[prop_or_default]
102 pub size: Option<i64>,
103 #[prop_or_default]
105 pub step: Option<i64>,
106 #[prop_or_default]
107 pub auto_validate: bool,
108 #[prop_or_default]
109 pub validity_transform: Option<ValidityTransform>,
110 #[prop_or_default]
111 pub validate_on_initial_render: bool,
112 #[prop_or_default]
113 pub oninput: Callback<String>,
114 #[prop_or_default]
115 pub name: Option<AttrValue>,
116 #[prop_or_default]
117 pub component_link: WeakComponentLink<MatTextField>,
118}
119
120impl Component for MatTextField {
121 type Message = ();
122 type Properties = TextFieldProps;
123
124 fn create(ctx: &Context<Self>) -> Self {
125 ctx.props()
126 .component_link
127 .borrow_mut()
128 .replace(ctx.link().clone());
129 TextField::ensure_loaded();
130 Self {
131 node_ref: NodeRef::default(),
132 validity_transform_closure: None,
133 input_listener: None,
134 }
135 }
136
137 fn view(&self, ctx: &Context<Self>) -> Html {
138 let props = ctx.props();
139 html! {
140 <mwc-textfield
141 open={props.open}
142 label={props.label.clone()}
143 placeholder={props.placeholder.clone()}
144 prefix={props.prefix.clone()}
145 suffix={props.suffix.clone()}
146 icon={props.icon.clone()}
147 iconTrailing={props.icon_trailing.clone()}
148 disabled={props.disabled}
149 charCounter={bool_to_option(props.char_counter)}
150 outlined={bool_to_option(props.outlined)}
151 helper={props.helper.clone()}
152 helperPersistent={bool_to_option(props.helper_persistent)}
153 required={props.required}
154 maxLength={props.max_length.map(|v| v.to_string())}
155 validationMessage={props.validation_message.clone()}
156 pattern={props.pattern.clone()}
157 min={props.min.clone()}
158 max={props.max.clone()}
159 size={props.size.map(|v| v.to_string())}
160 step={props.step.map(|v| v.to_string())}
161 autoValidate={bool_to_option(props.auto_validate)}
162 validateOnInitialRender={bool_to_option(props.validate_on_initial_render)}
163 name={props.name.clone()}
164 ref={self.node_ref.clone()}
165 ></mwc-textfield>
166 }
167 }
168
169 fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
170 self.input_listener = None;
172 ctx.props()
173 .component_link
174 .borrow_mut()
175 .replace(ctx.link().clone());
176 true
177 }
178
179 fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
180 let props = ctx.props();
181 let element = self.node_ref.cast::<TextField>().unwrap();
182 element.set_type(&JsValue::from(props.field_type.as_str()));
183 element.set_value(&JsValue::from_str(
184 props.value.as_ref().map(|s| s.as_ref()).unwrap_or_default(),
185 ));
186
187 if self.input_listener.is_none() {
188 self.input_listener = Some(set_on_input_handler(
189 &self.node_ref,
190 props.oninput.clone(),
191 |(_, detail)| {
192 detail
193 .unchecked_into::<MatTextFieldInputEvent>()
194 .target()
195 .value()
196 },
197 ));
198 }
199 if first_render {
200 let this = self.node_ref.cast::<TextField>().unwrap();
201 if let Some(transform) = props.validity_transform.clone() {
202 self.validity_transform_closure = Some(Closure::wrap(Box::new(
203 move |s: String, v: NativeValidityState| -> ValidityStateJS {
204 transform.0(s, v).into()
205 },
206 )
207 as Box<dyn Fn(String, NativeValidityState) -> ValidityStateJS>));
208 this.set_validity_transform(self.validity_transform_closure.as_ref().unwrap());
209 }
210 }
211 }
212}
213
214impl MatTextField {
215 pub fn validity_transform<F: Fn(String, NativeValidityState) -> ValidityState + 'static>(
216 func: F,
217 ) -> ValidityTransform {
218 ValidityTransform::new(func)
219 }
220}
221
222impl WeakComponentLink<MatTextField> {
223 pub fn value(&self) -> String {
224 self.borrow()
225 .as_ref()
226 .unwrap()
227 .get_component()
228 .unwrap()
229 .node_ref
230 .cast::<TextField>()
231 .unwrap()
232 .value()
233 }
234}
235
236#[wasm_bindgen]
237extern "C" {
238 type MatTextFieldInputEvent;
239
240 #[wasm_bindgen(method, getter)]
241 fn target(this: &MatTextFieldInputEvent) -> TextField;
242}