material_yew/text_inputs/
textarea.rs1use super::set_on_input_handler;
2use crate::bool_to_option;
3use crate::text_inputs::validity_state::ValidityStateJS;
4use crate::text_inputs::{TextFieldType, ValidityState, ValidityTransform};
5use gloo::events::EventListener;
6use wasm_bindgen::prelude::*;
7use wasm_bindgen::JsCast;
8use web_sys::Node;
9pub use web_sys::ValidityState as NativeValidityState;
10use yew::prelude::*;
11use yew::virtual_dom::AttrValue;
12
13#[wasm_bindgen(module = "/build/mwc-textarea.js")]
14extern "C" {
15 #[derive(Debug)]
16 #[wasm_bindgen(extends = Node)]
17 type TextArea;
18
19 #[wasm_bindgen(getter, static_method_of = TextArea)]
20 fn _dummy_loader() -> JsValue;
21
22 #[wasm_bindgen(method, setter = validityTransform)]
23 fn set_validity_transform(
24 this: &TextArea,
25 val: &Closure<dyn Fn(String, NativeValidityState) -> ValidityStateJS>,
26 );
27
28 #[wasm_bindgen(method, setter)]
29 fn set_type(this: &TextArea, val: &JsValue);
30
31 #[wasm_bindgen(method, getter)]
32 fn value(this: &TextArea) -> String;
33
34 #[wasm_bindgen(method, setter)]
35 fn set_value(this: &TextArea, val: &JsValue);
36}
37
38loader_hack!(TextArea);
39
40pub struct MatTextArea {
44 node_ref: NodeRef,
45 validity_transform_closure:
46 Option<Closure<dyn Fn(String, NativeValidityState) -> ValidityStateJS>>,
47 input_listener: Option<EventListener>,
48}
49
50#[derive(Clone, Copy, PartialEq)]
55pub enum TextAreaCharCounter {
56 Internal,
57 External,
58}
59
60impl TextAreaCharCounter {
61 pub fn as_str(&self) -> &'static str {
62 match self {
63 TextAreaCharCounter::Internal => "internal",
64 TextAreaCharCounter::External => "external",
65 }
66 }
67}
68
69#[derive(Properties, PartialEq, Clone)]
75pub struct TextAreaProps {
76 #[prop_or_default]
77 pub rows: Option<i64>,
78 #[prop_or_default]
79 pub cols: Option<i64>,
80 #[prop_or_default]
81 pub value: Option<AttrValue>,
82 #[prop_or(TextFieldType::Text)]
83 pub field_type: TextFieldType,
84 #[prop_or_default]
85 pub label: Option<AttrValue>,
86 #[prop_or_default]
87 pub placeholder: Option<AttrValue>,
88 #[prop_or_default]
89 pub icon: Option<AttrValue>,
90 #[prop_or_default]
91 pub icon_trailing: Option<AttrValue>,
92 #[prop_or_default]
93 pub disabled: bool,
94 #[prop_or_default]
98 pub char_counter: Option<TextAreaCharCounter>,
99 #[prop_or_default]
100 pub outlined: bool,
101 #[prop_or_default]
102 pub helper: Option<AttrValue>,
103 #[prop_or_default]
104 pub helper_persistent: bool,
105 #[prop_or_default]
106 pub required: bool,
107 #[prop_or_default]
108 pub max_length: Option<u64>,
109 #[prop_or_default]
110 pub validation_message: Option<AttrValue>,
111 #[prop_or_default]
113 pub min: Option<AttrValue>,
114 #[prop_or_default]
116 pub max: Option<AttrValue>,
117 #[prop_or_default]
118 pub size: Option<i64>, #[prop_or_default] pub step: Option<i64>, #[prop_or_default]
122 pub auto_validate: bool,
123 #[prop_or_default]
124 pub validity_transform: Option<ValidityTransform>,
125 #[prop_or_default]
126 pub validate_on_initial_render: bool,
127 #[prop_or_default]
128 pub oninput: Callback<String>,
129 #[prop_or_default]
130 pub name: Option<AttrValue>,
131}
132
133impl Component for MatTextArea {
134 type Message = ();
135 type Properties = TextAreaProps;
136
137 fn create(_: &Context<Self>) -> Self {
138 TextArea::ensure_loaded();
139 Self {
140 node_ref: NodeRef::default(),
141 validity_transform_closure: None,
142 input_listener: None,
143 }
144 }
145
146 fn view(&self, ctx: &Context<Self>) -> Html {
147 let props = ctx.props();
148 html! {
149 <mwc-textarea
150 rows={props.rows.map(|v| v.to_string())}
151 cols={props.cols.map(|v| v.to_string())}
152 label={props.label.clone()}
153 placeholder={props.placeholder.clone()}
154 icon={props.icon.clone()}
155 iconTrailing={props.icon_trailing.clone()}
156 disabled={props.disabled}
157 charCounter={props.char_counter.map(|it| it.as_str())}
158 outlined={bool_to_option(props.outlined)}
159 helper={props.helper.clone()}
160 helperPersistent={bool_to_option(props.helper_persistent)}
161 required={props.required}
162 maxLength={props.max_length.map(|v| v.to_string())}
163 validationMessage={props.validation_message.clone()}
164 min={props.min.clone()}
165 max={props.max.clone()}
166 size={props.size.map(|v| v.to_string())}
167 step={props.step.map(|v| v.to_string())}
168 autoValidate={bool_to_option(props.auto_validate)}
169 validateOnInitialRender={bool_to_option(props.validate_on_initial_render)}
170 name={props.name.clone()}
171 ref={self.node_ref.clone()}
172 ></mwc-textarea>
173 }
174 }
175
176 fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
177 let props = ctx.props();
178 let element = self.node_ref.cast::<TextArea>().unwrap();
179 element.set_type(&JsValue::from(props.field_type.as_str()));
180 element.set_value(&JsValue::from_str(
181 props.value.as_ref().map(|s| s.as_ref()).unwrap_or_default(),
182 ));
183
184 if self.input_listener.is_none() {
185 self.input_listener = Some(set_on_input_handler(
186 &self.node_ref,
187 props.oninput.clone(),
188 |(_, detail)| {
189 detail
190 .unchecked_into::<MatTextAreaInputEvent>()
191 .target()
192 .value()
193 },
194 ));
195 };
196
197 if first_render {
198 let this = self.node_ref.cast::<TextArea>().unwrap();
199 if let Some(transform) = props.validity_transform.clone() {
200 self.validity_transform_closure = Some(Closure::wrap(Box::new(
201 move |s: String, v: NativeValidityState| -> ValidityStateJS {
202 transform.0(s, v).into()
203 },
204 )
205 as Box<dyn Fn(String, NativeValidityState) -> ValidityStateJS>));
206 this.set_validity_transform(self.validity_transform_closure.as_ref().unwrap());
207 }
208 }
209 }
210}
211
212impl MatTextArea {
213 pub fn validity_transform<F: Fn(String, NativeValidityState) -> ValidityState + 'static>(
214 func: F,
215 ) -> ValidityTransform {
216 ValidityTransform::new(func)
217 }
218}
219
220#[wasm_bindgen]
221extern "C" {
222 type MatTextAreaInputEvent;
223
224 #[wasm_bindgen(method, getter)]
225 fn target(this: &MatTextAreaInputEvent) -> TextArea;
226}