material_yew/
select.rs

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
36/// The `mwc-select` component
37///
38/// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/select)
39pub 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/// Props for [`MatSelect`]
50///
51/// MWC Documentation:
52///
53/// - [Properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/select#propertiesattributes)
54/// - [Events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/select#events)
55#[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    /// [`WeakComponentLink`] for `MatList` which provides the following methods
86    /// - ```select(&self)```
87    ///
88    /// See [`WeakComponentLink`] documentation for more information
89    #[prop_or_default]
90    pub select_link: WeakComponentLink<MatSelect>,
91    /// Binds to `opened` event on `mwc-select-surface`
92    ///
93    /// See events docs to learn more.
94    #[prop_or_default]
95    pub onopened: Callback<()>,
96    /// Binds to `closed` event on `mwc-select-surface`
97    ///
98    /// See events docs to learn more.
99    #[prop_or_default]
100    pub onclosed: Callback<()>,
101    /// Binds to `action` event on `mwc-list`
102    ///
103    /// See events docs to learn more.
104    #[prop_or_default]
105    pub onaction: Callback<ActionDetail>,
106    /// Binds to `selected` event on `mwc-list`
107    ///
108    /// See events docs to learn more.
109    #[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        // clear event listeners and update link in case the props changed
158        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    //noinspection DuplicatedCode
170    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    /// Returns [`ValidityTransform`] to be passed to `validity_transform` prop
232    pub fn validity_transform<F: Fn(String, NativeValidityState) -> ValidityState + 'static>(
233        func: F,
234    ) -> ValidityTransform {
235        ValidityTransform::new(func)
236    }
237}