material_yew/
menu.rs

1mod models;
2
3pub use models::*;
4
5use crate::list::{ListIndex, SelectedDetail};
6use crate::{bool_to_option, event_into_details, to_option_string, WeakComponentLink};
7use gloo::events::EventListener;
8use wasm_bindgen::prelude::*;
9use web_sys::Node;
10use yew::prelude::*;
11use yew::virtual_dom::AttrValue;
12
13#[wasm_bindgen(module = "/build/mwc-menu.js")]
14extern "C" {
15    #[derive(Debug)]
16    #[wasm_bindgen(extends = Node)]
17    type Menu;
18
19    #[wasm_bindgen(getter, static_method_of = Menu)]
20    fn _dummy_loader() -> JsValue;
21
22    #[wasm_bindgen(method, getter)]
23    fn index(this: &Menu) -> JsValue;
24
25    #[wasm_bindgen(method)]
26    fn show(this: &Menu);
27
28    #[wasm_bindgen(method)]
29    fn close(this: &Menu);
30
31    // `MWCMenuIndex` is completely undocumented
32    #[wasm_bindgen(method)]
33    fn select(this: &Menu, index: &JsValue) -> usize;
34
35    #[wasm_bindgen(method, js_name = getFocusedItemIndex)]
36    fn get_focused_item_index(this: &Menu) -> usize;
37
38    #[wasm_bindgen(method, js_name = focusItemAtIndex)]
39    fn focus_item_at_index(this: &Menu, index: usize);
40
41    #[wasm_bindgen(method, setter)]
42    fn set_anchor(this: &Menu, value: &web_sys::HtmlElement);
43}
44
45loader_hack!(Menu);
46
47/// The `mwc-menu` Component
48///
49/// [MWC Documentation](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu)
50pub struct MatMenu {
51    node_ref: NodeRef,
52    opened_listener: Option<EventListener>,
53    closed_listener: Option<EventListener>,
54    action_listener: Option<EventListener>,
55    selected_listener: Option<EventListener>,
56}
57
58/// Props for `MatMenu`
59///
60/// MWC Documentation [properties](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#propertiesattributes)
61/// and [events](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#events)
62#[derive(Properties, PartialEq, Clone)]
63pub struct MenuProps {
64    /// Changing this prop re-renders the component.
65    /// For general usage, consider using `show` method provided by
66    /// `WeakComponentLink<MatMenu>` via `menu_link`
67    #[prop_or_default]
68    pub open: bool,
69    #[prop_or_default]
70    pub anchor: Option<web_sys::HtmlElement>,
71    #[prop_or(Corner::TopStart)]
72    pub corner: Corner,
73    #[prop_or(MenuCorner::Start)]
74    pub menu_corner: MenuCorner,
75    #[prop_or_default]
76    pub quick: bool,
77    #[prop_or_default]
78    pub absolute: bool,
79    #[prop_or_default]
80    pub fixed: bool,
81    #[prop_or_default]
82    pub x: Option<isize>,
83    #[prop_or_default]
84    pub y: Option<isize>,
85    #[prop_or_default]
86    pub force_group_selection: bool,
87    #[prop_or(DefaultFocusState::ListRoot)]
88    pub default_focus: DefaultFocusState,
89    #[prop_or_default]
90    pub fullwidth: bool,
91    #[prop_or_default]
92    pub wrap_focus: bool,
93    #[prop_or_default]
94    pub inner_role: Option<AttrValue>,
95    #[prop_or_default]
96    pub multi: bool,
97    #[prop_or_default]
98    pub activatable: bool,
99    /// Binds to `opened` event on `mwc-menu-surface`
100    ///
101    /// See events docs to learn more.
102    #[prop_or_default]
103    pub onopened: Callback<()>,
104    /// Binds to `closed` event on `mwc-menu-surface`
105    ///
106    /// See events docs to learn more.
107    #[prop_or_default]
108    pub onclosed: Callback<()>,
109    /// Binds to `action` event on `mwc-list`
110    ///
111    /// See events docs to learn more.
112    #[prop_or_default]
113    pub onaction: Callback<ListIndex>,
114    /// Binds to `selected` event on `mwc-list`
115    ///
116    /// See events docs to learn more.
117    #[prop_or_default]
118    pub onselected: Callback<SelectedDetail>,
119    /// `WeakComponentLink` for `MatMenu` which provides the following methods
120    /// - `get_focused_item_index(&self) -> usize`
121    /// - `focus_item_at_index(&self, index: usize)`
122    /// - `select(&self, index: &JsValue)`
123    /// - `show(&self)`
124    /// - `close(&self)`
125    ///
126    /// See [`WeakComponentLink`](/material_yew/struct.WeakComponentLink.html)
127    /// documentation for more information
128    #[prop_or_default]
129    pub menu_link: WeakComponentLink<MatMenu>,
130    pub children: Children,
131}
132
133impl Component for MatMenu {
134    type Message = ();
135    type Properties = MenuProps;
136
137    fn create(ctx: &Context<Self>) -> Self {
138        ctx.props()
139            .menu_link
140            .borrow_mut()
141            .replace(ctx.link().clone());
142        Menu::ensure_loaded();
143        Self {
144            node_ref: NodeRef::default(),
145            opened_listener: None,
146            closed_listener: None,
147            action_listener: None,
148            selected_listener: None,
149        }
150    }
151
152    fn view(&self, ctx: &Context<Self>) -> Html {
153        let props = ctx.props();
154        html! {
155             <mwc-menu
156                 open={props.open}
157                 corner={to_option_string(props.corner.to_string())}
158                 menuCorner={to_option_string(props.menu_corner.to_string())}
159                 quick={bool_to_option(props.quick)}
160                 absolute={bool_to_option(props.absolute)}
161                 fixed={bool_to_option(props.fixed)}
162                 x={props.x.map(|it| it.to_string())}
163                 y={props.y.map(|it| it.to_string())}
164                 forceGroupSelection={bool_to_option(props.force_group_selection)}
165                 defaultFocus={to_option_string(props.default_focus.to_string())}
166                 fullwidth={bool_to_option(props.fullwidth)}
167                 wrapFocus={bool_to_option(props.wrap_focus)}
168                 innerRole={props.inner_role.clone()}
169                 multi={bool_to_option(props.multi)}
170                 activatable={bool_to_option(props.activatable)}
171                 ref={self.node_ref.clone()}
172             >
173               {props.children.clone()}
174             </mwc-menu>
175        }
176    }
177
178    fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
179        let props = ctx.props();
180        let menu = self.node_ref.cast::<Menu>().unwrap();
181        if first_render {
182            if let Some(anchor) = props.anchor.as_ref() {
183                menu.set_anchor(anchor);
184            }
185        }
186        if self.opened_listener.is_none() {
187            let onopened = props.onopened.clone();
188            self.opened_listener = Some(EventListener::new(&menu, "opened", move |_| {
189                onopened.emit(());
190            }));
191        }
192
193        if self.closed_listener.is_none() {
194            let onclosed = props.onclosed.clone();
195            self.closed_listener = Some(EventListener::new(&menu, "closed", move |_| {
196                onclosed.emit(());
197            }));
198        }
199
200        if self.selected_listener.is_none() {
201            let onselected = props.onselected.clone();
202            self.selected_listener = Some(EventListener::new(&menu, "selected", move |event| {
203                onselected.emit(SelectedDetail::from(event_into_details(event)));
204            }));
205        }
206
207        if self.action_listener.is_none() {
208            let onaction = props.onaction.clone();
209            self.action_listener = Some(EventListener::new(&menu.clone(), "action", move |_| {
210                let val: JsValue = menu.index();
211
212                let index = ListIndex::from(val);
213                onaction.emit(index);
214            }));
215        }
216    }
217}
218
219impl WeakComponentLink<MatMenu> {
220    /// Binds to `getFocusedItemIndex` method.
221    ///
222    /// See [here](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#methods) for details
223    pub fn get_focused_item_index(&self) -> usize {
224        self.borrow()
225            .as_ref()
226            .unwrap()
227            .get_component()
228            .unwrap()
229            .node_ref
230            .cast::<Menu>()
231            .unwrap()
232            .get_focused_item_index()
233    }
234
235    /// Binds to `focusItemAtIndex` method.
236    ///
237    /// See [here](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#methods) for details
238    pub fn focus_item_at_index(&self, index: usize) {
239        self.borrow()
240            .as_ref()
241            .unwrap()
242            .get_component()
243            .unwrap()
244            .node_ref
245            .cast::<Menu>()
246            .unwrap()
247            .focus_item_at_index(index)
248    }
249
250    /// Binds to `select` method.
251    ///
252    /// `index` is `JsValue` because `MWCMenuIndex` mentioned in mwc docs is
253    /// completely undocumented.
254    ///
255    /// See [here](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#methods) for details
256    pub fn select(&self, index: &JsValue) {
257        self.borrow()
258            .as_ref()
259            .unwrap()
260            .get_component()
261            .unwrap()
262            .node_ref
263            .cast::<Menu>()
264            .unwrap()
265            .select(index);
266    }
267
268    /// Binds to `show` method.
269    ///
270    /// See [here](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#methods) for details
271    pub fn show(&self) {
272        self.borrow()
273            .as_ref()
274            .unwrap()
275            .get_component()
276            .unwrap()
277            .node_ref
278            .cast::<Menu>()
279            .unwrap()
280            .show();
281    }
282
283    /// Binds to `close` method.
284    ///
285    /// See [here](https://github.com/material-components/material-components-web-components/tree/v0.27.0/packages/menu#methods) for details
286    pub fn close(&self) {
287        self.borrow()
288            .as_ref()
289            .unwrap()
290            .get_component()
291            .unwrap()
292            .node_ref
293            .cast::<Menu>()
294            .unwrap()
295            .close();
296    }
297
298    /// Setter method for `anchor`.
299    pub fn set_anchor(&self, anchor: web_sys::HtmlElement) {
300        self.borrow()
301            .as_ref()
302            .unwrap()
303            .get_component()
304            .unwrap()
305            .node_ref
306            .cast::<Menu>()
307            .unwrap()
308            .set_anchor(&anchor);
309    }
310}