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}