1#[doc(inline)]
2pub use crate::list::{ActionDetail, ListIndex, SelectedDetail};
3
4use crate::text_inputs::{
5 validity_state::ValidityStateJS, NativeValidityState, ValidityState, ValidityTransform,
6};
7use crate::utils::WeakComponentLink;
8use crate::{bool_to_option, event_into_details, to_option_string};
9use gloo::events::EventListener;
10use wasm_bindgen::prelude::*;
11use web_sys::Node;
12use yew::prelude::*;
13use yew::virtual_dom::AttrValue;
14
15#[wasm_bindgen(module = "/build/mwc-select.js")]
16extern "C" {
17 #[derive(Debug)]
18 #[wasm_bindgen(extends = Node)]
19 type Select;
20
21 #[wasm_bindgen(getter, static_method_of = Select)]
22 fn _dummy_loader() -> JsValue;
23
24 #[wasm_bindgen(method)]
25 fn select(this: &Select, index: usize);
26
27 #[wasm_bindgen(method, setter = validityTransform)]
28 fn set_validity_transform(
29 this: &Select,
30 val: &Closure<dyn Fn(String, NativeValidityState) -> ValidityStateJS>,
31 );
32}
33
34loader_hack!(Select);
35
36pub struct MatSelect {
40 node_ref: NodeRef,
41 validity_transform_closure:
42 Option<Closure<dyn Fn(String, NativeValidityState) -> ValidityStateJS>>,
43 opened_listener: Option<EventListener>,
44 closed_listener: Option<EventListener>,
45 action_listener: Option<EventListener>,
46 selected_listener: Option<EventListener>,
47}
48
49#[derive(Properties, PartialEq, Clone)]
56pub struct Props {
57 #[prop_or_default]
58 pub value: Option<AttrValue>,
59 #[prop_or_default]
60 pub label: Option<AttrValue>,
61 #[prop_or_default]
62 pub natural_menu_width: bool,
63 #[prop_or_default]
64 pub icon: Option<AttrValue>,
65 #[prop_or_default]
66 pub disabled: bool,
67 #[prop_or_default]
68 pub outlined: bool,
69 #[prop_or_default]
70 pub helper: Option<AttrValue>,
71 #[prop_or_default]
72 pub required: bool,
73 #[prop_or_default]
74 pub validation_message: Option<AttrValue>,
75 #[prop_or_default]
76 pub items: Option<AttrValue>,
77 #[prop_or(- 1)]
78 pub index: i64,
79 #[prop_or_default]
80 pub validity_transform: Option<ValidityTransform>,
81 #[prop_or_default]
82 pub validate_on_initial_render: bool,
83 #[prop_or_default]
84 pub children: Children,
85 #[prop_or_default]
90 pub select_link: WeakComponentLink<MatSelect>,
91 #[prop_or_default]
95 pub onopened: Callback<()>,
96 #[prop_or_default]
100 pub onclosed: Callback<()>,
101 #[prop_or_default]
105 pub onaction: Callback<ActionDetail>,
106 #[prop_or_default]
110 pub onselected: Callback<SelectedDetail>,
111}
112
113impl Component for MatSelect {
114 type Message = ();
115 type Properties = Props;
116
117 fn create(ctx: &Context<Self>) -> Self {
118 ctx.props()
119 .select_link
120 .borrow_mut()
121 .replace(ctx.link().clone());
122 Select::ensure_loaded();
123 Self {
124 node_ref: NodeRef::default(),
125 validity_transform_closure: None,
126 opened_listener: None,
127 closed_listener: None,
128 action_listener: None,
129 selected_listener: None,
130 }
131 }
132
133 fn view(&self, ctx: &Context<Self>) -> Html {
134 let props = ctx.props();
135 html! {
136 <mwc-select
137 value={props.value.clone()}
138 label={props.label.clone()}
139 naturalMenuWidth={bool_to_option(props.natural_menu_width)}
140 icon={props.icon.clone()}
141 disabled={props.disabled}
142 outlined={bool_to_option(props.outlined)}
143 helper={props.helper.clone()}
144 required={props.required}
145 validationMessage={props.validation_message.clone()}
146 items={props.items.clone()}
147 index={to_option_string(props.index)}
148 validateOnInitialRender={bool_to_option(props.validate_on_initial_render)}
149 ref={self.node_ref.clone()}
150 >
151 {props.children.clone()}
152 </mwc-select>
153 }
154 }
155
156 fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
157 self.opened_listener = None;
159 self.closed_listener = None;
160 self.action_listener = None;
161 self.selected_listener = None;
162 ctx.props()
163 .select_link
164 .borrow_mut()
165 .replace(ctx.link().clone());
166 true
167 }
168
169 fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
171 let props = ctx.props();
172 let element = self.node_ref.cast::<Select>().unwrap();
173 if first_render {
174 if let Some(transform) = props.validity_transform.clone() {
175 self.validity_transform_closure = Some(Closure::wrap(Box::new(
176 move |s: String, v: NativeValidityState| -> ValidityStateJS {
177 transform.0(s, v).into()
178 },
179 )
180 as Box<dyn Fn(String, NativeValidityState) -> ValidityStateJS>));
181 element.set_validity_transform(self.validity_transform_closure.as_ref().unwrap());
182 }
183 }
184
185 if self.opened_listener.is_none() {
186 let onopened = props.onopened.clone();
187 self.opened_listener = Some(EventListener::new(&element, "opened", move |_| {
188 onopened.emit(())
189 }));
190 }
191
192 if self.closed_listener.is_none() {
193 let onclosed = props.onclosed.clone();
194 self.closed_listener = Some(EventListener::new(&element, "closed", move |_| {
195 onclosed.emit(())
196 }));
197 }
198
199 if self.action_listener.is_none() {
200 let on_action = props.onaction.clone();
201 self.action_listener = Some(EventListener::new(&element, "action", move |event| {
202 on_action.emit(ActionDetail::from(event_into_details(event)))
203 }));
204 }
205
206 if self.selected_listener.is_none() {
207 let on_selected = props.onselected.clone();
208 self.selected_listener = Some(EventListener::new(&element, "selected", move |event| {
209 on_selected.emit(SelectedDetail::from(event_into_details(event)))
210 }));
211 }
212 }
213}
214
215impl WeakComponentLink<MatSelect> {
216 pub fn select(&self, val: usize) {
217 let c = self
218 .borrow()
219 .as_ref()
220 .unwrap()
221 .get_component()
222 .unwrap()
223 .node_ref
224 .clone();
225 let select_element = c.cast::<Select>().unwrap();
226 select_element.select(val);
227 }
228}
229
230impl MatSelect {
231 pub fn validity_transform<F: Fn(String, NativeValidityState) -> ValidityState + 'static>(
233 func: F,
234 ) -> ValidityTransform {
235 ValidityTransform::new(func)
236 }
237}