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}