Skip to main content

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