floem/views/dropdown.rs
1#![deny(missing_docs)]
2//! A view that allows the user to select an item from a list of items.
3//!
4//! The [`Dropdown`] struct provides several constructors, each offering different levels of customization and ease of use.
5//!
6//! The [`DropdownCustomStyle`] struct allows for easy and advanced customization of the dropdown's appearance.
7use std::{any::Any, rc::Rc};
8
9use floem_reactive::{Effect, RwSignal, Scope, SignalGet, SignalUpdate, UpdaterEffect};
10use imbl::OrdMap;
11use peniko::kurbo::{Point, Rect, Size};
12use ui_events::keyboard::{Key, NamedKey};
13
14use crate::{
15 AnyView,
16 action::{add_overlay, remove_overlay},
17 event::{Event, EventListener, EventPropagation},
18 prelude::ViewTuple,
19 prop, prop_extractor,
20 style::{CustomStylable, CustomStyle, Style},
21 style_class,
22 view::ViewId,
23 view::{IntoView, View, default_compute_layout},
24 views::{ContainerExt, Decorators, Label, ScrollExt, svg},
25};
26
27use super::list;
28
29type ChildFn<T> = dyn Fn(T) -> (AnyView, Scope);
30type ListViewFn<T> = Rc<dyn Fn(&dyn Fn(&T) -> AnyView, Option<usize>) -> AnyView>;
31
32style_class!(
33 /// A Style class that is applied to all dropdowns.
34 pub DropdownClass
35);
36
37prop!(
38 /// A property that determines whether the dropdown should close automatically when an item is selected.
39 pub CloseOnAccept: bool {} = true
40);
41prop_extractor!(DropdownStyle {
42 close_on_accept: CloseOnAccept,
43});
44
45/// # A customizable dropdown view for selecting an item from a list.
46///
47/// The `Dropdown` struct provides several constructors, each offering different levels of
48/// customization and ease of use:
49///
50/// - [`Dropdown::new_rw`]: The simplest constructor, ideal for quick setup with minimal customization.
51/// It uses default views and assumes direct access to a signal that can be both read from and written to for driving the selection of an item.
52///
53/// - [`Dropdown::new`]: Similar to `new_rw`, but uses a read-only function for the active item, and requires that you manually provide an `on_accept` callback.
54///
55/// - [`Dropdown::custom`]: Offers full customization, letting you define custom view functions for
56/// both the main display and list items. Uses a read-only function for the active item and requires that you manually provide an `on_accept` callback.
57///
58/// - The dropdown also has methods [`Dropdown::main_view`] and [`Dropdown::list_item_view`] that let you override the main view function and list item view function respectively.
59///
60/// Choose the constructor that best fits your needs based on the level of customization required.
61///
62/// ## Usage with Enums
63///
64/// A common scenario is populating a dropdown menu from an enum. The `widget-gallery` example does this.
65///
66/// The below example creates a dropdown with three items, one for each character in our `Character` enum.
67///
68/// The `strum` crate is handy for this use case. This example uses the `strum` crate to create an iterator for our `Character` enum.
69///
70/// First, define the enum and implement `Clone`, `strum::EnumIter`, and `Display` on it:
71/// ```rust
72/// use strum::IntoEnumIterator;
73///
74/// #[derive(Clone, strum::EnumIter)]
75/// enum Character {
76/// Ori,
77/// Naru,
78/// Gumo,
79/// }
80///
81/// impl std::fmt::Display for Character {
82/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
83/// match self {
84/// Self::Ori => write!(f, "Ori"),
85/// Self::Naru => write!(f, "Naru"),
86/// Self::Gumo => write!(f, "Gumo"),
87/// }
88/// }
89/// }
90/// ```
91///
92/// Then, create a signal:
93/// ```rust
94/// # use strum::IntoEnumIterator;
95/// #
96/// # #[derive(Clone, strum::EnumIter)]
97/// # enum Character {
98/// # Ori,
99/// # Naru,
100/// # Gumo,
101/// # }
102/// #
103/// # impl std::fmt::Display for Character {
104/// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
105/// # match self {
106/// # Self::Ori => write!(f, "Ori"),
107/// # Self::Naru => write!(f, "Naru"),
108/// # Self::Gumo => write!(f, "Gumo"),
109/// # }
110/// # }
111/// # }
112/// #
113/// # use floem::reactive::RwSignal;
114/// let selected = RwSignal::new(Character::Ori);
115/// ```
116///
117/// Finally, create the dropdown using one of the available constructors, like [`Dropdown::new_rw`]:
118///
119/// ```rust
120/// # use strum::IntoEnumIterator;
121/// #
122/// # #[derive(Clone, strum::EnumIter, PartialEq)]
123/// # enum Character {
124/// # Ori,
125/// # Naru,
126/// # Gumo,
127/// # }
128/// #
129/// # impl std::fmt::Display for Character {
130/// # fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
131/// # match self {
132/// # Self::Ori => write!(f, "Ori"),
133/// # Self::Naru => write!(f, "Naru"),
134/// # Self::Gumo => write!(f, "Gumo"),
135/// # }
136/// # }
137/// # }
138/// #
139/// # fn character_select() -> impl floem::IntoView {
140/// # use floem::{prelude::*, views::dropdown::Dropdown};
141/// # let selected = RwSignal::new(Character::Ori);
142/// Dropdown::new_rw(selected, Character::iter())
143/// # }
144/// ```
145///
146/// ## Styling
147///
148/// You can modify the behavior of the dropdown through the `CloseOnAccept` property.
149/// If the property is set to `true`, the dropdown will automatically close when an item is selected.
150/// If the property is set to `false`, the dropdown will not automatically close when an item is selected.
151/// The default is `true`.
152/// Styling Example:
153/// ```rust
154/// # use floem::views::dropdown;
155/// # use floem::views::empty;
156/// # use floem::views::Decorators;
157/// // root view
158/// empty().style(|s| {
159/// s.class(dropdown::DropdownClass, |s| {
160/// s.set(dropdown::CloseOnAccept, false)
161/// })
162/// });
163/// ```
164pub struct Dropdown<T: 'static> {
165 id: ViewId,
166 current_value: T,
167 main_view: ViewId,
168 main_view_scope: Scope,
169 main_fn: Box<ChildFn<T>>,
170 list_view: ListViewFn<T>,
171 list_item_fn: Rc<dyn Fn(&T) -> AnyView>,
172 overlay_id: Option<ViewId>,
173 window_origin: Option<Point>,
174 on_accept: Option<Box<dyn Fn(T)>>,
175 on_open: Option<Box<dyn Fn(bool)>>,
176 style: DropdownStyle,
177 index_to_item: OrdMap<usize, T>,
178 width: RwSignal<f64>,
179}
180
181enum Message {
182 OpenState(bool),
183 ActiveElement(Box<dyn Any>),
184 ListFocusLost,
185 ListSelect(Box<dyn Any>),
186}
187
188impl<T: 'static + Clone + PartialEq> View for Dropdown<T> {
189 fn id(&self) -> ViewId {
190 self.id
191 }
192
193 fn debug_name(&self) -> std::borrow::Cow<'static, str> {
194 "DropDown".into()
195 }
196
197 fn style_pass(&mut self, cx: &mut crate::context::StyleCx<'_>) {
198 if self.style.read(cx) {
199 cx.window_state.request_paint(self.id);
200 }
201 }
202
203 fn compute_layout(&mut self, cx: &mut crate::context::ComputeLayoutCx) -> Option<Rect> {
204 self.window_origin = Some(cx.window_origin);
205 let width = self.id.layout_rect().size().width;
206 self.width.set(width);
207
208 default_compute_layout(self.id, cx)
209 }
210
211 fn update(&mut self, cx: &mut crate::context::UpdateCx, state: Box<dyn std::any::Any>) {
212 if let Ok(state) = state.downcast::<Message>() {
213 match *state {
214 Message::OpenState(true) => self.open_dropdown(cx),
215 Message::OpenState(false) => self.close_dropdown(),
216 Message::ListFocusLost => self.close_dropdown(),
217 Message::ListSelect(val) => {
218 if let Ok(val) = val.downcast::<T>() {
219 if self.style.close_on_accept() {
220 self.close_dropdown();
221 }
222 if let Some(on_select) = &self.on_accept {
223 on_select(*val);
224 }
225 }
226 }
227 Message::ActiveElement(val) => {
228 if let Ok(val) = val.downcast::<T>() {
229 let old_child_scope = self.main_view_scope;
230 let old_main_view = self.main_view;
231 self.current_value = *val.clone();
232 let (main_view, main_view_scope) = (self.main_fn)(*val);
233 let main_view_id = main_view.id();
234 self.id.set_children([main_view]);
235 self.main_view = main_view_id;
236 self.main_view_scope = main_view_scope;
237
238 cx.window_state.remove_view(old_main_view);
239 old_child_scope.dispose();
240 self.id.request_all();
241 }
242 }
243 }
244 }
245 }
246
247 fn event_before_children(
248 &mut self,
249 _cx: &mut crate::context::EventCx,
250 event: &Event,
251 ) -> EventPropagation {
252 match event {
253 e if e.is_pointer_down() => {
254 self.swap_state();
255 return EventPropagation::Stop;
256 }
257 Event::Key(key_event)
258 if matches!(key_event.key, Key::Named(NamedKey::Enter))
259 | matches!(
260 key_event.key,
261 Key::Character(ref c) if c == " "
262 ) =>
263 {
264 self.swap_state()
265 }
266 _ => {}
267 }
268
269 EventPropagation::Continue
270 }
271}
272
273impl<T: Clone + std::cmp::PartialEq> Dropdown<T> {
274 /// Creates a default main view for the dropdown.
275 ///
276 /// This function generates a view that displays the given item as text,
277 /// along with a chevron-down icon to indicate that it's a dropdown.
278 pub fn default_main_view(item: T) -> AnyView
279 where
280 T: std::fmt::Display,
281 {
282 const CHEVRON_DOWN: &str = r##"
283 <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="-46.336 -46.336 278.016 278.016">
284 <path fill="#010002" d="M92.672 144.373a10.707 10.707 0 0 1-7.593-3.138L3.145 59.301c-4.194-4.199
285 -4.194-10.992 0-15.18a10.72 10.72 0 0 1 15.18 0l74.347 74.341 74.347-74.341a10.72 10.72 0 0 1
286 15.18 0c4.194 4.194 4.194 10.981 0 15.18l-81.939 81.934a10.694 10.694 0 0 1-7.588 3.138z"/>
287 </svg>
288 "##;
289
290 // TODO: this should be more customizable
291 (
292 Label::new(item),
293 svg(CHEVRON_DOWN).style(|s| s.items_center()),
294 )
295 .h_stack()
296 .style(|s| s.items_center().justify_between().size_full())
297 .into_any()
298 }
299
300 /// Creates a new customizable dropdown.
301 ///
302 /// You might want to use some of the simpler constructors like [`Dropdown::new`] or [`Dropdown::new_rw`].
303 ///
304 /// # Example
305 /// ```rust
306 /// # use floem::{*, views::*, reactive::*};
307 /// # use floem::views::dropdown::*;
308 /// let active_item = RwSignal::new(3);
309 ///
310 /// Dropdown::custom(
311 /// move || active_item.get(),
312 /// |main_item| text(main_item).into_any(),
313 /// 1..=5,
314 /// |list_item| text(list_item).into_any(),
315 /// )
316 /// .on_accept(move |item| active_item.set(item));
317 /// ```
318 ///
319 /// This function provides full control over the dropdown's appearance and behavior
320 /// by allowing custom view functions for both the main display and list items.
321 ///
322 /// # Arguments
323 ///
324 /// * `active_item` - A function that returns the currently selected item.
325 ///
326 /// * `main_view` - A function that takes a value of type `T` and returns an `AnyView`
327 /// to be used as the main dropdown display.
328 ///
329 /// * `iterator` - An iterator that provides the items to be displayed in the dropdown list.
330 ///
331 /// * `list_item_fn` - A function that takes a value of type `T` and returns an `AnyView`
332 /// to be used for each item in the dropdown list.
333 pub fn custom<MF, I, LF, AIF>(
334 active_item: AIF,
335 main_view: MF,
336 iterator: I,
337 list_item_fn: LF,
338 ) -> Dropdown<T>
339 where
340 MF: Fn(T) -> AnyView + 'static,
341 I: IntoIterator<Item = T> + 'static,
342 LF: Fn(&T) -> AnyView + Clone + 'static,
343 T: PartialEq + Clone + 'static,
344 AIF: Fn() -> T + 'static,
345 {
346 let dropdown_id = ViewId::new();
347
348 // Process the iterator once, building a map from indices to items
349 let mut index_to_item = OrdMap::new();
350
351 for (idx, item) in iterator.into_iter().enumerate() {
352 index_to_item.insert(idx, item);
353 }
354
355 let list_item_fn = Rc::new(list_item_fn);
356
357 let index_to_item_clone = index_to_item.clone();
358 let index_to_item_clone_ = index_to_item.clone();
359 let list_view = Rc::new(
360 move |list_item_fn: &dyn Fn(&T) -> AnyView, active: Option<usize>| {
361 let index_to_item_clone = index_to_item_clone.clone();
362 let items_view = index_to_item_clone_.values().map(list_item_fn);
363
364 let list = list(items_view)
365 .on_accept(move |opt_idx| {
366 if let Some(idx) = opt_idx {
367 let val = index_to_item_clone
368 .get(&idx)
369 .expect("Index should exist in the map")
370 .clone();
371
372 dropdown_id.update_state(Message::ActiveElement(Box::new(val.clone())));
373 dropdown_id.update_state(Message::ListSelect(Box::new(val)));
374 }
375 })
376 .style(|s| s.width_full())
377 .on_event_stop(EventListener::FocusLost, move |_| {
378 dropdown_id.update_state(Message::ListFocusLost);
379 })
380 .on_event_stop(EventListener::PointerMove, |_| {});
381
382 list.selection().set(active);
383
384 list.into_any()
385 },
386 );
387
388 let initial = UpdaterEffect::new(active_item, move |new_state| {
389 dropdown_id.update_state(Message::ActiveElement(Box::new(new_state)));
390 });
391
392 let main_fn = Box::new(Scope::current().enter_child(main_view));
393 let (child, main_view_scope) = main_fn(initial.clone());
394 let main_view = child.id();
395
396 dropdown_id.set_children([child]);
397
398 Self {
399 id: dropdown_id,
400 current_value: initial,
401 main_view,
402 main_view_scope,
403 main_fn,
404 list_view,
405 list_item_fn,
406 index_to_item,
407 overlay_id: None,
408 window_origin: None,
409 on_accept: None,
410 on_open: None,
411 style: Default::default(),
412 width: RwSignal::new(0.),
413 }
414 .class(DropdownClass)
415 }
416
417 /// Creates a new dropdown with a read-only function for the active item.
418 ///
419 /// # Example
420 /// ```rust
421 /// # use floem::{*, views::*, reactive::*};
422 /// # use floem::views::dropdown::*;
423 /// let active_item = RwSignal::new(3);
424 ///
425 /// Dropdown::new(move || active_item.get(), 1..=5).on_accept(move |val| active_item.set(val));
426 /// ```
427 ///
428 /// This function is a convenience wrapper around `Dropdown::new` that uses default views
429 /// for the main and list items.
430 ///
431 /// See also [`Dropdown::new_rw`].
432 ///
433 /// # Arguments
434 ///
435 /// * `active_item` - A function that returns the currently selected item.
436 ///
437 /// * `iterator` - An iterator that provides the items to be displayed in the dropdown list.
438 pub fn new<AIF, I>(active_item: AIF, iterator: I) -> Dropdown<T>
439 where
440 AIF: Fn() -> T + 'static,
441 I: IntoIterator<Item = T> + 'static,
442 T: Clone + PartialEq + std::fmt::Display + 'static,
443 {
444 Self::custom(active_item, Self::default_main_view, iterator, |v| {
445 crate::views::Label::new(v).into_any()
446 })
447 }
448
449 /// Creates a new dropdown with a read-write signal for the active item.
450 ///
451 /// # Example:
452 /// ```rust
453 /// # use floem::{*, views::*, reactive::*};
454 /// # use floem::{views::dropdown::*};
455 /// let dropdown_active_item = RwSignal::new(3);
456 ///
457 /// Dropdown::new_rw(dropdown_active_item, 1..=5);
458 /// ```
459 ///
460 /// This function is a convenience wrapper around `Dropdown::custom` that uses default views
461 /// for the main and list items.
462 ///
463 /// # Arguments
464 ///
465 /// * `active_item` - A read-write signal representing the currently selected item.
466 /// It must implement `SignalGet<T>` and `SignalUpdate<T>`.
467 ///
468 /// * `iterator` - An iterator that provides the items to be displayed in the dropdown list.
469 pub fn new_rw<AI, I>(active_item: AI, iterator: I) -> Dropdown<T>
470 where
471 AI: SignalGet<T> + SignalUpdate<T> + Copy + 'static,
472 I: IntoIterator<Item = T> + 'static,
473 T: Clone + PartialEq + std::fmt::Display + 'static,
474 {
475 Self::custom(
476 move || active_item.get(),
477 Self::default_main_view,
478 iterator,
479 |t| Label::new(t).into_any(),
480 )
481 .on_accept(move |nv| active_item.set(nv))
482 }
483
484 /// Overrides the main view for the dropdown.
485 pub fn main_view(mut self, main_view: impl Fn(T) -> Box<dyn View> + 'static) -> Self {
486 self.main_fn = Box::new(Scope::current().enter_child(main_view));
487 let (child, main_view_scope) = (self.main_fn)(self.current_value.clone());
488 let main_view = child.id();
489 self.main_view_scope = main_view_scope;
490 self.main_view = main_view;
491 self.id.set_children([child]);
492 self
493 }
494
495 /// Overrides the list view for each item in the dropdown list.
496 pub fn list_item_view(mut self, list_item_fn: impl Fn(&T) -> Box<dyn View> + 'static) -> Self {
497 self.list_item_fn = Rc::new(list_item_fn);
498 self
499 }
500
501 /// Sets a reactive condition for showing or hiding the dropdown list.
502 ///
503 /// # Reactivity
504 /// The `show` function will be re-run whenever any signal it depends on changes.
505 pub fn show_list(self, show: impl Fn() -> bool + 'static) -> Self {
506 let id = self.id();
507 Effect::new(move |_| {
508 let state = show();
509 id.update_state(Message::OpenState(state));
510 });
511 self
512 }
513
514 /// Sets a callback function to be called when an item is selected from the dropdown.
515 ///
516 /// Only one `on_accept` callback can be set at a time.
517 pub fn on_accept(mut self, on_accept: impl Fn(T) + 'static) -> Self {
518 self.on_accept = Some(Box::new(on_accept));
519 self
520 }
521
522 /// Sets a callback function to be called when the dropdown is opened.
523 ///
524 /// Only one `on_open` callback can be set at a time.
525 pub fn on_open(mut self, on_open: impl Fn(bool) + 'static) -> Self {
526 self.on_open = Some(Box::new(on_open));
527 self
528 }
529
530 fn swap_state(&self) {
531 if self.overlay_id.is_some() {
532 self.id.update_state(Message::OpenState(false));
533 } else {
534 self.id.request_layout();
535 self.id.update_state(Message::OpenState(true));
536 }
537 }
538
539 fn open_dropdown(&mut self, cx: &mut crate::context::UpdateCx) {
540 if self.overlay_id.is_none() {
541 self.id.request_layout();
542 cx.window_state.compute_layout();
543 if let Some(layout) = self.id.get_layout() {
544 let point =
545 self.window_origin.unwrap_or_default() + (0., layout.size.height as f64);
546 self.create_overlay(point);
547
548 if let Some(on_open) = &self.on_open {
549 on_open(true);
550 }
551 }
552 }
553 }
554
555 fn close_dropdown(&mut self) {
556 if let Some(id) = self.overlay_id.take() {
557 remove_overlay(id);
558 if let Some(on_open) = &self.on_open {
559 on_open(false);
560 }
561 }
562 }
563
564 fn create_overlay(&mut self, point: Point) {
565 let list = self.list_view.clone();
566 let list_item_fn = self.list_item_fn.clone();
567 self.overlay_id = Some(add_overlay({
568 const DEFAULT_PADDING: f64 = 5.0;
569 let list_size = RwSignal::new(None);
570 let overlay_size = RwSignal::new(None);
571 let initial_inset = Size::new(point.x, point.y);
572 let inset = RwSignal::new(initial_inset);
573
574 Effect::new(move |_| {
575 let (Some(list_size), Some(overlay_size)) = (list_size.get(), overlay_size.get())
576 else {
577 return;
578 };
579
580 let default_inset_size = Size::new(DEFAULT_PADDING, DEFAULT_PADDING);
581 let new_inset = initial_inset
582 .min(overlay_size - list_size - default_inset_size)
583 .max(default_inset_size);
584
585 if new_inset != inset.get_untracked() {
586 inset.set(new_inset);
587 }
588 });
589
590 let list = list(
591 &*list_item_fn.clone(),
592 self.index_to_item
593 .values()
594 .position(|v| *v == self.current_value),
595 );
596 let list_id = list.id();
597
598 let list = list.on_resize(move |rect| {
599 if let Some(parent_layout) = list_id.parent().and_then(|p| p.get_layout()) {
600 // resolve size of the scroll view if it wasn't squished
601 let margin = parent_layout.margin;
602 let padding = parent_layout.padding;
603 let border = parent_layout.border;
604
605 let indent_size = Size::new(
606 (margin.horizontal_components().sum()
607 + border.horizontal_components().sum()
608 + padding.horizontal_components().sum()) as _,
609 (margin.vertical_components().sum()
610 + padding.vertical_components().sum()
611 + border.vertical_components().sum()) as _,
612 );
613 let size = rect.size() + indent_size;
614
615 list_size.set(Some(size));
616 }
617 });
618
619 list_id.request_focus();
620
621 let width = self.width;
622 list.scroll()
623 .style(move |s| {
624 s.flex_col()
625 .pointer_events_auto()
626 .flex_grow(0.0)
627 .flex_shrink(1.0)
628 })
629 // Wrap scroll in a container that constrains width to match the dropdown button.
630 // The theme sets width_full() on ScrollClass inside DropdownClass, which would
631 // otherwise expand to fill the full overlay. This container ensures the scroll
632 // (and its items) are sized to match the dropdown trigger width.
633 .container()
634 .style(move |s| s.width(width.get()))
635 .container()
636 .on_resize(move |rect| {
637 overlay_size.set(Some(rect.size()));
638 })
639 .style(move |s| {
640 let inset = inset.get();
641 s.absolute()
642 .flex_col()
643 .size_full()
644 .inset_left(inset.width)
645 .inset_top(inset.height)
646 .pointer_events_none()
647 })
648 }));
649 self.overlay_id.unwrap().set_style_parent(self.id);
650 }
651
652 /// Sets the custom style properties of the `Dropdown`.
653 pub fn dropdown_style(
654 self,
655 style: impl Fn(DropdownCustomStyle) -> DropdownCustomStyle + 'static,
656 ) -> Self {
657 self.custom_style(style)
658 }
659}
660
661#[derive(Debug, Clone, Default)]
662/// A struct that allows for easy custom styling of the `Dropdown` using the [`Dropdown::dropdown_style`] method or the [`Style::custom_style`](crate::style::CustomStylable::custom_style) method.
663pub struct DropdownCustomStyle(Style);
664impl From<DropdownCustomStyle> for Style {
665 fn from(val: DropdownCustomStyle) -> Self {
666 val.0
667 }
668}
669impl From<Style> for DropdownCustomStyle {
670 fn from(val: Style) -> Self {
671 Self(val)
672 }
673}
674impl CustomStyle for DropdownCustomStyle {
675 type StyleClass = DropdownClass;
676}
677
678impl<T: Clone + PartialEq> CustomStylable<DropdownCustomStyle> for Dropdown<T> {
679 type DV = Self;
680}
681
682impl DropdownCustomStyle {
683 /// Creates a new `DropDownCustomStyle` with default values.
684 pub fn new() -> Self {
685 Self::default()
686 }
687 /// Sets the `CloseOnAccept` property for the dropdown, which determines whether the dropdown
688 /// should automatically close when an item is selected. The default value is `true`.
689 ///
690 /// # Arguments
691 /// * `close`: If set to `true`, the dropdown will close upon item selection. If `false`, it
692 /// will remain open after an item is selected.
693 pub fn close_on_accept(mut self, close: bool) -> Self {
694 self = Self(self.0.set(CloseOnAccept, close));
695 self
696 }
697}
698
699impl<T> Drop for Dropdown<T> {
700 fn drop(&mut self) {
701 if let Some(id) = self.overlay_id {
702 remove_overlay(id)
703 }
704 }
705}