1use crate::components::config_provider::{ComponentSize, use_config};
6use crate::components::control::{ControlStatus, push_status_class};
7use crate::components::form::{FormItemControlContext, use_form_item_control};
8use crate::components::icon::{Icon, IconKind};
9use crate::components::select_base::{
10 DropdownLayer, OptionKey, SelectOption, handle_option_list_key_event, option_key_to_value,
11 option_keys_to_value, toggle_option_key, use_dropdown_layer, value_to_option_key,
12 value_to_option_keys,
13};
14use crate::foundation::{
15 ClassListExt, SelectClassNames, SelectSemantic, SelectStyles, StyleStringExt, Variant,
16 variant_from_bordered,
17};
18use dioxus::events::KeyboardEvent;
19use dioxus::prelude::*;
20use serde_json::Value;
21use std::rc::Rc;
22
23pub use crate::components::select_base::SelectOption as PublicSelectOption;
26
27#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
29pub enum SelectMode {
30 #[default]
32 Single,
33 Multiple,
35 Tags,
37 Combobox,
39}
40
41impl SelectMode {
42 pub fn is_multiple(&self) -> bool {
44 matches!(self, SelectMode::Multiple | SelectMode::Tags)
45 }
46
47 pub fn allows_input(&self) -> bool {
49 matches!(self, SelectMode::Tags | SelectMode::Combobox)
50 }
51}
52
53#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
55pub enum SelectPlacement {
56 #[default]
57 BottomLeft,
58 BottomRight,
59 TopLeft,
60 TopRight,
61}
62
63impl SelectPlacement {
64 fn as_style(&self) -> &'static str {
65 match self {
66 SelectPlacement::BottomLeft => "top: 100%; left: 0;",
67 SelectPlacement::BottomRight => "top: 100%; right: 0;",
68 SelectPlacement::TopLeft => "bottom: 100%; left: 0;",
69 SelectPlacement::TopRight => "bottom: 100%; right: 0;",
70 }
71 }
72}
73
74#[derive(Props, Clone)]
76pub struct SelectProps {
77 #[props(optional)]
79 pub value: Option<String>,
80 #[props(optional)]
82 pub values: Option<Vec<String>>,
83 pub options: Vec<SelectOption>,
85 #[props(default)]
87 pub mode: SelectMode,
88 #[props(default)]
90 pub multiple: bool,
91 #[props(default)]
93 pub allow_clear: bool,
94 #[props(optional)]
96 pub placeholder: Option<String>,
97 #[props(default)]
99 pub disabled: bool,
100 #[props(default)]
102 pub show_search: bool,
103 #[props(optional)]
106 pub filter_option: Option<Rc<dyn Fn(&str, &SelectOption) -> bool>>,
107 #[props(optional)]
110 pub token_separators: Option<Vec<String>>,
111 #[props(optional)]
113 pub status: Option<ControlStatus>,
114 #[props(optional)]
116 pub size: Option<ComponentSize>,
117 #[props(optional)]
119 pub variant: Option<Variant>,
120 #[props(optional)]
122 pub bordered: Option<bool>,
123 #[props(optional)]
125 pub prefix: Option<Element>,
126 #[props(optional)]
128 pub suffix_icon: Option<Element>,
129 #[props(default)]
131 pub placement: SelectPlacement,
132 #[props(default = true)]
134 pub popup_match_select_width: bool,
135 #[props(optional)]
136 pub class: Option<String>,
137 #[props(optional)]
139 pub root_class_name: Option<String>,
140 #[props(optional)]
141 pub style: Option<String>,
142 #[props(optional)]
144 pub class_names: Option<SelectClassNames>,
145 #[props(optional)]
147 pub styles: Option<SelectStyles>,
148 #[props(optional)]
150 pub dropdown_class: Option<String>,
151 #[props(optional)]
152 pub dropdown_style: Option<String>,
153 #[props(optional)]
155 pub dropdown_class_name: Option<String>,
156 #[props(optional)]
158 pub dropdown_style_deprecated: Option<String>,
159 #[props(optional)]
161 pub dropdown_match_select_width: Option<bool>,
162 #[props(optional)]
164 pub popup_render: Option<Rc<dyn Fn(Element) -> Element>>,
165 #[props(optional)]
167 pub on_change: Option<EventHandler<Vec<String>>>,
168 #[props(optional)]
170 pub on_dropdown_visible_change: Option<EventHandler<bool>>,
171 #[props(optional)]
173 pub on_open_change: Option<EventHandler<bool>>,
174}
175
176impl PartialEq for SelectProps {
177 fn eq(&self, other: &Self) -> bool {
178 self.value == other.value
180 && self.values == other.values
181 && self.options == other.options
182 && self.mode == other.mode
183 && self.multiple == other.multiple
184 && self.allow_clear == other.allow_clear
185 && self.placeholder == other.placeholder
186 && self.disabled == other.disabled
187 && self.show_search == other.show_search
188 && self.status == other.status
189 && self.size == other.size
190 && self.variant == other.variant
191 && self.bordered == other.bordered
192 && self.prefix == other.prefix
193 && self.suffix_icon == other.suffix_icon
194 && self.placement == other.placement
195 && self.popup_match_select_width == other.popup_match_select_width
196 && self.class == other.class
197 && self.root_class_name == other.root_class_name
198 && self.style == other.style
199 && self.class_names == other.class_names
200 && self.styles == other.styles
201 && self.dropdown_class == other.dropdown_class
202 && self.dropdown_style == other.dropdown_style
203 && self.dropdown_class_name == other.dropdown_class_name
204 && self.dropdown_style_deprecated == other.dropdown_style_deprecated
205 && self.dropdown_match_select_width == other.dropdown_match_select_width
206 && self.on_change == other.on_change
207 && self.on_dropdown_visible_change == other.on_dropdown_visible_change
208 && self.on_open_change == other.on_open_change
209 && self.token_separators == other.token_separators
210 }
212}
213
214#[allow(clippy::collapsible_if)]
216#[component]
217pub fn Select(props: SelectProps) -> Element {
218 let SelectProps {
219 value,
220 values,
221 options,
222 mode,
223 multiple,
224 allow_clear,
225 placeholder,
226 disabled,
227 show_search,
228 status,
229 size,
230 variant,
231 bordered,
232 prefix,
233 suffix_icon,
234 placement,
235 popup_match_select_width,
236 class,
237 root_class_name,
238 style,
239 class_names,
240 styles,
241 dropdown_class,
242 dropdown_style,
243 on_change,
244 on_dropdown_visible_change,
245 filter_option: _,
246 token_separators: _,
247 dropdown_class_name: _,
248 dropdown_style_deprecated: _,
249 dropdown_match_select_width: _,
250 popup_render: _,
251 on_open_change: _,
252 } = props;
253
254 let config = use_config();
255 let form_control = use_form_item_control();
256
257 let is_multiple = mode.is_multiple() || multiple;
259
260 let resolved_variant = variant_from_bordered(bordered, variant);
262
263 let final_size = size.unwrap_or(config.size);
264
265 let is_disabled =
266 disabled || config.disabled || form_control.as_ref().is_some_and(|ctx| ctx.is_disabled());
267
268 let internal_selected: Signal<Vec<OptionKey>> = use_signal(Vec::new);
271
272 let has_form = form_control.is_some();
273 let prop_single = value.clone();
274 let prop_multi = values.clone();
275 let multiple_flag = is_multiple;
276
277 let selected_keys: Vec<OptionKey> = if let Some(ctx) = form_control.as_ref() {
279 if multiple_flag {
280 value_to_option_keys(ctx.value())
281 } else {
282 match value_to_option_key(ctx.value()) {
283 Some(k) => vec![k],
284 None => Vec::new(),
285 }
286 }
287 } else if let Some(vs) = prop_multi {
288 vs
289 } else if let Some(v) = prop_single {
290 vec![v]
291 } else {
292 internal_selected.read().clone()
293 };
294
295 let controlled_by_prop = has_form || value.is_some() || values.is_some();
296
297 let open_state: Signal<bool> = use_signal(|| false);
299 let active_index: Signal<Option<usize>> = use_signal(|| None);
300
301 let tags_input: Signal<String> = use_signal(String::new);
303
304 let internal_click_flag: Signal<bool> = use_signal(|| false);
309
310 #[cfg(target_arch = "wasm32")]
313 {
314 let mut open_for_global = open_state;
315 let mut internal_flag = internal_click_flag;
316 let on_visible_cb = on_dropdown_visible_change;
317 use_effect(move || {
318 use wasm_bindgen::{JsCast, closure::Closure};
319
320 if let Some(window) = web_sys::window() {
321 if let Some(document) = window.document() {
322 let target: web_sys::EventTarget = document.into();
323 let handler = Closure::<dyn FnMut(web_sys::MouseEvent)>::wrap(Box::new(
324 move |_evt: web_sys::MouseEvent| {
325 let mut flag = internal_flag;
326 if *flag.read() {
327 flag.set(false);
330 return;
331 }
332 let mut open_signal = open_for_global;
333 if *open_signal.read() {
334 open_signal.set(false);
335 if let Some(cb) = on_visible_cb {
336 cb.call(false);
337 }
338 }
339 },
340 ));
341 let _ = target.add_event_listener_with_callback(
342 "click",
343 handler.as_ref().unchecked_ref(),
344 );
345 handler.forget();
349 }
350 }
351 });
352 }
353
354 let search_query: Signal<String> = use_signal(String::new);
356
357 let open_flag = *open_state.read();
358 let DropdownLayer { z_index, .. } = use_dropdown_layer(open_flag);
359 let current_z = *z_index.read();
360
361 let placeholder_str = placeholder.unwrap_or_default();
362
363 let filtered_options: Vec<SelectOption> = if show_search {
365 let query = search_query.read().clone();
366 crate::components::select_base::filter_options_by_query(&options, &query)
367 } else {
368 options.clone()
369 };
370
371 let mut class_list = vec!["adui-select".to_string()];
373 if is_multiple {
374 class_list.push("adui-select-multiple".into());
375 }
376 if matches!(mode, SelectMode::Tags) {
377 class_list.push("adui-select-tags".into());
378 }
379 if is_disabled {
380 class_list.push("adui-select-disabled".into());
381 }
382 if open_flag {
383 class_list.push("adui-select-open".into());
384 }
385 match final_size {
386 ComponentSize::Small => class_list.push("adui-select-sm".into()),
387 ComponentSize::Large => class_list.push("adui-select-lg".into()),
388 ComponentSize::Middle => {}
389 }
390 class_list.push(resolved_variant.class_for("adui-select"));
391 push_status_class(&mut class_list, status);
392 class_list.push_semantic(&class_names, SelectSemantic::Root);
393 if let Some(extra) = class {
394 class_list.push(extra);
395 }
396 if let Some(extra) = root_class_name {
397 class_list.push(extra);
398 }
399 let class_attr = class_list
400 .into_iter()
401 .filter(|s| !s.is_empty())
402 .collect::<Vec<_>>()
403 .join(" ");
404
405 let mut style_attr = style.unwrap_or_default();
406 style_attr.append_semantic(&styles, SelectSemantic::Root);
407
408 let find_label = |key: &str| -> String {
410 options
411 .iter()
412 .find(|opt| opt.key == key)
413 .map(|opt| opt.label.clone())
414 .unwrap_or_else(|| key.to_string())
415 };
416
417 let form_for_tags = form_control.clone();
419 let selected_for_tags = selected_keys.clone();
420 let selected_for_clear = selected_keys.clone();
421
422 let display_node = if is_multiple {
423 if selected_keys.is_empty() {
424 rsx! { span { class: "adui-select-selection-placeholder", "{placeholder_str}" } }
425 } else {
426 rsx! {
427 div { class: "adui-select-selection-overflow",
428 {selected_keys.iter().map(|k| {
429 let label = find_label(k);
430 let key_for_remove = k.clone();
431 let form_for_remove = form_control.clone();
432 let internal_selected_for_remove = internal_selected;
433 let selected_snapshot = selected_keys.clone();
434
435 rsx! {
436 span { class: "adui-select-selection-item",
437 "{label}"
438 span {
439 class: "adui-select-selection-item-remove",
440 onclick: move |evt| {
441 evt.stop_propagation();
442 let next_keys = selected_snapshot.iter()
443 .filter(|k| **k != key_for_remove)
444 .cloned()
445 .collect();
446 apply_selected_keys(
447 &form_for_remove,
448 multiple_flag,
449 controlled_by_prop,
450 &internal_selected_for_remove,
451 on_change,
452 next_keys,
453 );
454 },
455 "×"
456 }
457 }
458 }
459 })}
460 if matches!(mode, SelectMode::Tags) {
461 input {
462 class: "adui-select-selection-search-input",
463 value: "{tags_input.read()}",
464 oninput: move |evt| {
465 let mut sig = tags_input;
466 sig.set(evt.value());
467 },
468 onkeydown: move |evt: KeyboardEvent| {
469 use dioxus::prelude::Key;
470 if matches!(evt.key(), Key::Enter) {
471 let input_val = tags_input.read().trim().to_string();
472 if !input_val.is_empty() && !selected_for_tags.contains(&input_val) {
473 let mut next_keys = selected_for_tags.clone();
474 next_keys.push(input_val);
475 apply_selected_keys(
476 &form_for_tags,
477 multiple_flag,
478 controlled_by_prop,
479 &internal_selected,
480 on_change,
481 next_keys,
482 );
483 let mut sig = tags_input;
484 sig.set(String::new());
485 }
486 }
487 }
488 }
489 }
490 }
491 }
492 }
493 } else if let Some(first) = selected_keys.first() {
494 let label = find_label(first);
495 rsx! { span { class: "adui-select-selection-item", "{label}" } }
496 } else {
497 rsx! { span { class: "adui-select-selection-placeholder", "{placeholder_str}" } }
498 };
499
500 let form_for_handlers = form_control.clone();
502 let internal_selected_for_handlers = internal_selected;
503 let on_change_cb = on_change;
504
505 let open_for_toggle = open_state;
506 let is_disabled_flag = is_disabled;
507
508 let search_for_input = search_query;
509
510 let active_for_keydown = active_index;
511 let internal_selected_for_keydown = internal_selected;
512 let form_for_keydown = form_for_handlers.clone();
513 let open_for_keydown = open_for_toggle;
514
515 let internal_click_for_toggle = internal_click_flag;
517 let internal_click_for_keydown = internal_click_flag;
518
519 let dropdown_class_attr = {
520 let mut list = vec!["adui-select-dropdown".to_string()];
521 if let Some(extra) = dropdown_class {
522 list.push(extra);
523 }
524 list.join(" ")
525 };
526
527 let min_width_style = if popup_match_select_width {
528 "min-width: 100%;"
529 } else {
530 ""
531 };
532
533 let dropdown_style_attr = format!(
534 "position: absolute; {} {} z-index: {}; {}",
535 placement.as_style(),
536 min_width_style,
537 current_z,
538 dropdown_style.unwrap_or_default()
539 );
540
541 let suffix_element = suffix_icon.unwrap_or_else(|| {
543 rsx! {
544 span { class: "adui-select-arrow",
545 Icon { kind: IconKind::ArrowDown, size: 12.0 }
546 }
547 }
548 });
549
550 let on_visible_cb = on_dropdown_visible_change;
551
552 rsx! {
553 div {
554 class: "adui-select-root",
555 style: "position: relative; display: inline-block;",
556 div {
557 class: "{class_attr}",
558 style: "{style_attr}",
559 role: "combobox",
560 tabindex: 0,
561 "aria-expanded": open_flag,
562 "aria-disabled": is_disabled_flag,
563 onclick: move |_| {
564 if is_disabled_flag {
565 return;
566 }
567 let mut flag = internal_click_for_toggle;
570 flag.set(true);
571
572 let mut open_signal = open_for_toggle;
573 let current = *open_signal.read();
574 let next = !current;
575 open_signal.set(next);
576 if let Some(cb) = on_visible_cb {
577 cb.call(next);
578 }
579 },
580 onkeydown: move |evt: KeyboardEvent| {
581 if is_disabled_flag {
582 return;
583 }
584 use dioxus::prelude::Key;
585
586 let open_now = *open_for_keydown.read();
587 if !open_now {
588 match evt.key() {
589 Key::Enter | Key::ArrowDown => {
590 evt.prevent_default();
591 let mut open_signal = open_for_keydown;
592 open_signal.set(true);
593 if let Some(cb) = on_visible_cb {
594 cb.call(true);
595 }
596 }
597 Key::Escape => {
598 }
600 _ => {}
601 }
602 return;
603 }
604
605 if matches!(evt.key(), Key::Escape) {
606 let mut open_signal = open_for_keydown;
607 open_signal.set(false);
608 if let Some(cb) = on_visible_cb {
609 cb.call(false);
610 }
611 return;
612 }
613
614 let opts_len = filtered_options.len();
615 if opts_len == 0 {
616 return;
617 }
618
619 let mut flag = internal_click_for_keydown;
622 flag.set(true);
623
624 if let Some(idx) = handle_option_list_key_event(&evt, opts_len, &active_for_keydown) {
625 if idx < opts_len {
626 let opt = &filtered_options[idx];
627 if opt.disabled {
628 return;
629 }
630
631 let key = opt.key.clone();
632 let current_keys = selected_keys.clone();
633 let next_keys = if multiple_flag {
634 toggle_option_key(¤t_keys, &key)
635 } else {
636 vec![key.clone()]
637 };
638
639 apply_selected_keys(
640 &form_for_keydown,
641 multiple_flag,
642 controlled_by_prop,
643 &internal_selected_for_keydown,
644 on_change_cb,
645 next_keys,
646 );
647
648 if !multiple_flag {
649 let mut open_signal = open_for_keydown;
650 open_signal.set(false);
651 if let Some(cb) = on_visible_cb {
652 cb.call(false);
653 }
654 }
655 }
656 }
657 },
658 if let Some(prefix_el) = prefix {
659 span { class: "adui-select-prefix", {prefix_el} }
660 }
661 div { class: "adui-select-selector", {display_node} }
662 {suffix_element}
663 if allow_clear && !selected_for_clear.is_empty() && !is_disabled_flag {
664 span {
665 class: "adui-select-clear",
666 onclick: move |evt| {
667 evt.stop_propagation();
668 apply_selected_keys(
669 &form_for_handlers,
670 multiple_flag,
671 controlled_by_prop,
672 &internal_selected_for_handlers,
673 on_change_cb,
674 Vec::new(),
675 );
676 },
677 "×"
678 }
679 }
680 }
681 if open_flag {
682 div {
683 class: "{dropdown_class_attr}",
684 style: "{dropdown_style_attr}",
685 role: "listbox",
686 "aria-multiselectable": multiple_flag,
687 onclick: move |_| {
688 let mut flag = internal_click_flag;
690 flag.set(true);
691 },
692 if show_search {
693 div { class: "adui-select-search",
694 input {
695 class: "adui-select-search-input",
696 value: "{search_for_input.read()}",
697 oninput: move |evt| {
698 let mut signal = search_for_input;
699 signal.set(evt.value());
700 }
701 }
702 }
703 }
704 ul { class: "adui-select-item-list",
705 {filtered_options.iter().enumerate().map(|(index, opt)| {
706 let key = opt.key.clone();
707 let label = opt.label.clone();
708 let disabled_opt = opt.disabled || is_disabled_flag;
709 let is_selected = selected_keys.contains(&key);
710 let is_active = active_index
711 .read()
712 .as_ref()
713 .map(|i| *i == index)
714 .unwrap_or(false);
715 let selected_snapshot = selected_keys.clone();
716 let form_for_click = form_control.clone();
717 let internal_selected_for_click = internal_selected;
718 let open_for_click = open_state;
719 let internal_click_for_item = internal_click_flag;
720
721 rsx! {
722 li {
723 class: {
724 let mut classes = vec!["adui-select-item".to_string()];
725 if is_selected {
726 classes.push("adui-select-item-option-selected".into());
727 }
728 if disabled_opt {
729 classes.push("adui-select-item-option-disabled".into());
730 }
731 if is_active {
732 classes.push("adui-select-item-option-active".into());
733 }
734 classes.join(" ")
735 },
736 role: "option",
737 "aria-selected": is_selected,
738 onclick: move |_| {
739 if disabled_opt {
740 return;
741 }
742 let mut flag = internal_click_for_item;
745 flag.set(true);
746
747 let current_keys = selected_snapshot.clone();
748 let next_keys = if multiple_flag {
749 toggle_option_key(¤t_keys, &key)
750 } else {
751 vec![key.clone()]
752 };
753
754 apply_selected_keys(
755 &form_for_click,
756 multiple_flag,
757 controlled_by_prop,
758 &internal_selected_for_click,
759 on_change_cb,
760 next_keys,
761 );
762
763 if !multiple_flag {
764 let mut open_signal = open_for_click;
765 open_signal.set(false);
766 if let Some(cb) = on_visible_cb {
767 cb.call(false);
768 }
769 }
770 },
771 "{label}"
772 if is_selected {
773 span { class: "adui-select-item-option-state",
774 Icon { kind: IconKind::Check, size: 12.0 }
775 }
776 }
777 }
778 }
779 })}
780 }
781 }
782 }
783 }
784 }
785}
786
787fn apply_selected_keys(
788 form_control: &Option<FormItemControlContext>,
789 multiple: bool,
790 controlled_by_prop: bool,
791 selected_signal: &Signal<Vec<OptionKey>>,
792 on_change: Option<EventHandler<Vec<String>>>,
793 new_keys: Vec<OptionKey>,
794) {
795 if let Some(ctx) = form_control {
796 if multiple {
797 let json = option_keys_to_value(&new_keys);
798 ctx.set_value(json);
799 } else if let Some(first) = new_keys.first() {
800 let json = option_key_to_value(first);
801 ctx.set_value(json);
802 } else {
803 ctx.set_value(Value::Null);
804 }
805 } else if !controlled_by_prop {
806 let mut signal = *selected_signal;
807 signal.set(new_keys.clone());
808 }
809
810 if let Some(cb) = on_change {
811 cb.call(new_keys);
812 }
813}
814
815#[cfg(test)]
816mod tests {
817 use super::*;
818
819 #[test]
820 fn select_mode_is_multiple() {
821 assert!(!SelectMode::Single.is_multiple());
822 assert!(SelectMode::Multiple.is_multiple());
823 assert!(SelectMode::Tags.is_multiple());
824 }
825
826 #[test]
827 fn select_placement_styles() {
828 assert!(SelectPlacement::BottomLeft.as_style().contains("top: 100%"));
829 assert!(SelectPlacement::TopLeft.as_style().contains("bottom: 100%"));
830 }
831
832 #[test]
833 fn select_mode_allows_input() {
834 assert!(!SelectMode::Single.allows_input());
835 assert!(!SelectMode::Multiple.allows_input());
836 assert!(SelectMode::Tags.allows_input());
837 assert!(SelectMode::Combobox.allows_input());
838 }
839
840 #[test]
841 fn select_mode_default() {
842 assert_eq!(SelectMode::default(), SelectMode::Single);
843 }
844
845 #[test]
846 fn select_mode_all_variants() {
847 assert_eq!(SelectMode::Single, SelectMode::Single);
848 assert_eq!(SelectMode::Multiple, SelectMode::Multiple);
849 assert_eq!(SelectMode::Tags, SelectMode::Tags);
850 assert_eq!(SelectMode::Combobox, SelectMode::Combobox);
851 assert_ne!(SelectMode::Single, SelectMode::Multiple);
852 }
853
854 #[test]
855 fn select_placement_all_variants() {
856 assert_eq!(SelectPlacement::BottomLeft, SelectPlacement::BottomLeft);
857 assert_eq!(SelectPlacement::BottomRight, SelectPlacement::BottomRight);
858 assert_eq!(SelectPlacement::TopLeft, SelectPlacement::TopLeft);
859 assert_eq!(SelectPlacement::TopRight, SelectPlacement::TopRight);
860 assert_ne!(SelectPlacement::BottomLeft, SelectPlacement::TopLeft);
861 }
862
863 #[test]
864 fn select_placement_default() {
865 assert_eq!(SelectPlacement::default(), SelectPlacement::BottomLeft);
866 }
867
868 #[test]
869 fn select_placement_all_styles() {
870 let bottom_left = SelectPlacement::BottomLeft.as_style();
871 let bottom_right = SelectPlacement::BottomRight.as_style();
872 let top_left = SelectPlacement::TopLeft.as_style();
873 let top_right = SelectPlacement::TopRight.as_style();
874
875 assert!(bottom_left.contains("top: 100%"));
876 assert!(bottom_left.contains("left: 0"));
877 assert!(bottom_right.contains("top: 100%"));
878 assert!(bottom_right.contains("right: 0"));
879 assert!(top_left.contains("bottom: 100%"));
880 assert!(top_left.contains("left: 0"));
881 assert!(top_right.contains("bottom: 100%"));
882 assert!(top_right.contains("right: 0"));
883 }
884
885 #[test]
886 fn select_mode_multiple_variants() {
887 assert!(SelectMode::Multiple.is_multiple());
889 assert!(SelectMode::Tags.is_multiple());
890 assert!(!SelectMode::Single.is_multiple());
891 assert!(!SelectMode::Combobox.is_multiple());
892 }
893
894 #[test]
895 fn select_mode_input_variants() {
896 assert!(SelectMode::Tags.allows_input());
898 assert!(SelectMode::Combobox.allows_input());
899 assert!(!SelectMode::Single.allows_input());
900 assert!(!SelectMode::Multiple.allows_input());
901 }
902}