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 #[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
47pub 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#[derive(Properties, PartialEq, Clone)]
63pub struct MenuProps {
64 #[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 #[prop_or_default]
103 pub onopened: Callback<()>,
104 #[prop_or_default]
108 pub onclosed: Callback<()>,
109 #[prop_or_default]
113 pub onaction: Callback<ListIndex>,
114 #[prop_or_default]
118 pub onselected: Callback<SelectedDetail>,
119 #[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 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 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 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 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 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 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}