use super::set_on_input_handler;
use crate::text_inputs::{
validity_state::ValidityStateJS, TextFieldType, ValidityState, ValidityTransform,
};
use crate::{bool_to_option, WeakComponentLink};
use gloo::events::EventListener;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::Node;
use web_sys::ValidityState as NativeValidityState;
use yew::prelude::*;
use yew::virtual_dom::AttrValue;
#[wasm_bindgen(module = "/build/mwc-textfield.js")]
extern "C" {
#[derive(Debug)]
#[wasm_bindgen(extends = Node)]
type TextField;
#[wasm_bindgen(getter, static_method_of = TextField)]
fn _dummy_loader() -> JsValue;
#[wasm_bindgen(method, setter = validityTransform)]
fn set_validity_transform(
this: &TextField,
val: &Closure<dyn Fn(String, NativeValidityState) -> ValidityStateJS>,
);
#[wasm_bindgen(method, setter)]
fn set_type(this: &TextField, val: &JsValue);
#[wasm_bindgen(method, getter)]
fn value(this: &TextField) -> String;
#[wasm_bindgen(method, setter)]
fn set_value(this: &TextField, val: &JsValue);
}
loader_hack!(TextField);
pub struct MatTextField {
node_ref: NodeRef,
validity_transform_closure:
Option<Closure<dyn Fn(String, NativeValidityState) -> ValidityStateJS>>,
input_listener: Option<EventListener>,
}
#[derive(Properties, PartialEq, Clone)]
pub struct TextFieldProps {
#[prop_or_default]
pub open: bool,
#[prop_or_default]
pub value: Option<AttrValue>,
#[prop_or(TextFieldType::Text)]
pub field_type: TextFieldType,
#[prop_or_default]
pub label: Option<AttrValue>,
#[prop_or_default]
pub placeholder: Option<AttrValue>,
#[prop_or_default]
pub prefix: Option<AttrValue>,
#[prop_or_default]
pub suffix: Option<AttrValue>,
#[prop_or_default]
pub icon: Option<AttrValue>,
#[prop_or_default]
pub icon_trailing: Option<AttrValue>,
#[prop_or_default]
pub disabled: bool,
#[prop_or_default]
pub char_counter: bool,
#[prop_or_default]
pub outlined: bool,
#[prop_or_default]
pub helper: Option<AttrValue>,
#[prop_or_default]
pub helper_persistent: bool,
#[prop_or_default]
pub required: bool,
#[prop_or_default]
pub max_length: Option<u64>,
#[prop_or_default]
pub validation_message: Option<AttrValue>,
#[prop_or_default]
pub pattern: Option<AttrValue>,
#[prop_or_default]
pub min: Option<AttrValue>,
#[prop_or_default]
pub max: Option<AttrValue>,
#[prop_or_default]
pub size: Option<i64>,
#[prop_or_default]
pub step: Option<i64>,
#[prop_or_default]
pub auto_validate: bool,
#[prop_or_default]
pub validity_transform: Option<ValidityTransform>,
#[prop_or_default]
pub validate_on_initial_render: bool,
#[prop_or_default]
pub oninput: Callback<String>,
#[prop_or_default]
pub name: Option<AttrValue>,
#[prop_or_default]
pub component_link: WeakComponentLink<MatTextField>,
}
impl Component for MatTextField {
type Message = ();
type Properties = TextFieldProps;
fn create(ctx: &Context<Self>) -> Self {
ctx.props()
.component_link
.borrow_mut()
.replace(ctx.link().clone());
TextField::ensure_loaded();
Self {
node_ref: NodeRef::default(),
validity_transform_closure: None,
input_listener: None,
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let props = ctx.props();
html! {
<mwc-textfield
open={props.open}
label={props.label.clone()}
placeholder={props.placeholder.clone()}
prefix={props.prefix.clone()}
suffix={props.suffix.clone()}
icon={props.icon.clone()}
iconTrailing={props.icon_trailing.clone()}
disabled={props.disabled}
charCounter={bool_to_option(props.char_counter)}
outlined={bool_to_option(props.outlined)}
helper={props.helper.clone()}
helperPersistent={bool_to_option(props.helper_persistent)}
required={props.required}
maxLength={props.max_length.map(|v| v.to_string())}
validationMessage={props.validation_message.clone()}
pattern={props.pattern.clone()}
min={props.min.clone()}
max={props.max.clone()}
size={props.size.map(|v| v.to_string())}
step={props.step.map(|v| v.to_string())}
autoValidate={bool_to_option(props.auto_validate)}
validateOnInitialRender={bool_to_option(props.validate_on_initial_render)}
name={props.name.clone()}
ref={self.node_ref.clone()}
></mwc-textfield>
}
}
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
self.input_listener = None;
ctx.props()
.component_link
.borrow_mut()
.replace(ctx.link().clone());
true
}
fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
let props = ctx.props();
let element = self.node_ref.cast::<TextField>().unwrap();
element.set_type(&JsValue::from(props.field_type.as_str()));
element.set_value(&JsValue::from_str(
props.value.as_ref().map(|s| s.as_ref()).unwrap_or_default(),
));
if self.input_listener.is_none() {
self.input_listener = Some(set_on_input_handler(
&self.node_ref,
props.oninput.clone(),
|(_, detail)| {
detail
.unchecked_into::<MatTextFieldInputEvent>()
.target()
.value()
},
));
}
if first_render {
let this = self.node_ref.cast::<TextField>().unwrap();
if let Some(transform) = props.validity_transform.clone() {
self.validity_transform_closure = Some(Closure::wrap(Box::new(
move |s: String, v: NativeValidityState| -> ValidityStateJS {
transform.0(s, v).into()
},
)
as Box<dyn Fn(String, NativeValidityState) -> ValidityStateJS>));
this.set_validity_transform(self.validity_transform_closure.as_ref().unwrap());
}
}
}
}
impl MatTextField {
pub fn validity_transform<F: Fn(String, NativeValidityState) -> ValidityState + 'static>(
func: F,
) -> ValidityTransform {
ValidityTransform::new(func)
}
}
impl WeakComponentLink<MatTextField> {
pub fn value(&self) -> String {
self.borrow()
.as_ref()
.unwrap()
.get_component()
.unwrap()
.node_ref
.cast::<TextField>()
.unwrap()
.value()
}
}
#[wasm_bindgen]
extern "C" {
type MatTextFieldInputEvent;
#[wasm_bindgen(method, getter)]
fn target(this: &MatTextFieldInputEvent) -> TextField;
}