orbital_base_components/form/listbox/
mod.rs1mod active_descendant;
2mod option_walker;
3mod utils;
4
5use leptos::{context::Provider, ev, html, prelude::*};
6use std::sync::Arc;
7use web_sys::{HtmlElement, Node};
8
9pub use active_descendant::{use_active_descendant, ActiveDescendantController};
10pub use utils::{get_dropdown_action_from_key, DropdownAction};
11
12#[component]
14pub fn BaseListbox(
15 #[prop(optional, into)] class: MaybeProp<String>,
16 set_listbox: Arc<dyn Fn(Node) + Send + Sync>,
17 listbox_ref: NodeRef<html::Div>,
18 children: Children,
19) -> impl IntoView {
20 let trigger = ArcTrigger::new();
21 let effect = RenderEffect::new({
22 let trigger = trigger.clone();
23 move |_| {
24 trigger.track();
25 if let Some(listbox_el) = listbox_ref.get() {
26 set_listbox(listbox_el.into());
27 }
28 }
29 });
30
31 on_cleanup(move || {
32 drop(effect);
33 });
34
35 view! {
36 <div
37 class=move || {
38 let mut parts = vec!["orbital-listbox".to_string()];
39 if let Some(extra) = class.get() {
40 if !extra.is_empty() {
41 parts.push(extra);
42 }
43 }
44 parts.join(" ")
45 }
46 node_ref=listbox_ref
47 role="listbox"
48 on:mousedown=|e| e.prevent_default()
49 >
50 <Provider value=ListboxInjection(trigger)>{children()}</Provider>
51 </div>
52 }
53}
54
55#[derive(Clone)]
57pub struct ListboxInjection(ArcTrigger);
58
59impl ListboxInjection {
60 #[inline]
61 pub fn expect_context() -> Self {
62 expect_context()
63 }
64
65 #[inline]
66 pub fn trigger(&self) {
67 self.0.notify();
68 }
69}
70
71pub fn listbox_keyboard_event(
73 e: ev::KeyboardEvent,
74 open: RwSignal<bool>,
75 multiselect: bool,
76 active_descendant_controller: &ActiveDescendantController,
77 select_option: impl Fn(HtmlElement),
78) {
79 let (open, set_open) = open.split();
80 let open = open.get_untracked();
81 let action = get_dropdown_action_from_key(&e, open, multiselect);
82 let active_option = active_descendant_controller.active();
83
84 match action {
85 DropdownAction::Type | DropdownAction::Open => {
86 if !open {
87 set_open.set(true);
88 }
89 if action == DropdownAction::Open {
90 e.prevent_default();
91 }
92 }
93 DropdownAction::CloseSelect | DropdownAction::Select => {
94 if let Some(option) = active_option {
95 e.prevent_default();
96 select_option(option);
97 }
98 }
99 DropdownAction::Next => {
100 e.prevent_default();
101 if active_option.is_some() {
102 active_descendant_controller.next();
103 } else {
104 active_descendant_controller.first();
105 }
106 }
107 DropdownAction::Previous => {
108 e.prevent_default();
109 if active_option.is_some() {
110 active_descendant_controller.prev();
111 } else {
112 active_descendant_controller.first();
113 }
114 }
115 DropdownAction::First | DropdownAction::PageUp => {
116 e.prevent_default();
117 active_descendant_controller.first();
118 }
119 DropdownAction::Last | DropdownAction::PageDown => {
120 e.prevent_default();
121 active_descendant_controller.last();
122 }
123 DropdownAction::Tab | DropdownAction::Close | DropdownAction::None => {}
124 };
125}