Skip to content

Commit 94a3257

Browse files
committed
rework focus handling to allow non consecutive and out of order indexes
1 parent 798ec9d commit 94a3257

File tree

6 files changed

+143
-113
lines changed

6 files changed

+143
-113
lines changed

primitives/src/select/components/list.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ pub fn SelectList(props: SelectListProps) -> Element {
8181

8282
let mut open = ctx.open;
8383
let mut listbox_ref: Signal<Option<std::rc::Rc<MountedData>>> = use_signal(|| None);
84-
let focused = move || open() && !ctx.focus_state.any_focused();
84+
let focused = move || open() && !ctx.any_focused();
8585

8686
use_effect(move || {
8787
let Some(listbox_ref) = listbox_ref() else {

primitives/src/select/components/option.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
//! SelectOption and SelectItemIndicator component implementations.
22
33
use crate::{
4-
focus::use_focus_controlled_item,
54
select::context::{RcPartialEqValue, SelectListContext},
65
use_effect, use_effect_cleanup, use_id_or, use_unique_id,
76
};
87
use dioxus::html::input_data::MouseButton;
98
use dioxus::prelude::*;
9+
use std::rc::Rc;
1010

1111
use super::super::context::{OptionState, SelectContext, SelectOptionContext};
1212

@@ -28,7 +28,7 @@ pub struct SelectOptionProps<T: Clone + PartialEq + 'static> {
2828
#[props(default)]
2929
pub id: ReadSignal<Option<String>>,
3030

31-
/// The index of the option in the list. This is used to define the focus order for keyboard navigation.
31+
/// The index of the option in the list. This is used to define the focus order for keyboard navigation. Each option must have a unique index.
3232
pub index: ReadSignal<usize>,
3333

3434
/// Optional label for the option (for accessibility)
@@ -136,19 +136,36 @@ pub fn SelectOption<T: PartialEq + Clone + 'static>(props: SelectOptionProps<T>)
136136
value: RcPartialEqValue::new(value.cloned()),
137137
text_value: text_value.cloned(),
138138
id: id(),
139-
disabled
139+
disabled,
140140
};
141141

142142
// Add the option to the context's options
143-
ctx.options.write().push(option_state);
143+
ctx.options
144+
.write()
145+
.insert(option_state.tab_index, option_state);
144146
});
145147

146148
use_effect_cleanup(move || {
147-
ctx.options.write().retain(|opt| opt.id != *id.read());
149+
ctx.options.write().remove(&index());
148150
});
149151

150-
let onmounted = use_focus_controlled_item(props.index);
151-
let focused = move || ctx.focus_state.is_focused(index());
152+
// customized focus handle for this option. Based on `use_focus_controlled_item`.
153+
let mut controlled_ref: Signal<Option<Rc<MountedData>>> = use_signal(|| None);
154+
use_effect(move || {
155+
if disabled {
156+
return;
157+
}
158+
let is_focused = ctx.is_focused(index.cloned());
159+
if is_focused {
160+
if let Some(md) = controlled_ref() {
161+
spawn(async move {
162+
let _ = md.set_focus(true).await;
163+
});
164+
}
165+
}
166+
});
167+
168+
let focused = move || ctx.is_focused(index());
152169
let selected = use_memo(move || {
153170
ctx.value.read().as_ref().and_then(|v| v.as_ref::<T>()) == Some(&props.value.read())
154171
});
@@ -165,7 +182,7 @@ pub fn SelectOption<T: PartialEq + Clone + 'static>(props: SelectOptionProps<T>)
165182
role: "option",
166183
id,
167184
tabindex: if focused() { "0" } else { "-1" },
168-
onmounted,
185+
onmounted: move |data: Event<MountedData>| controlled_ref.set(Some(data.data())),
169186

170187
// ARIA attributes
171188
aria_selected: selected(),
@@ -184,7 +201,7 @@ pub fn SelectOption<T: PartialEq + Clone + 'static>(props: SelectOptionProps<T>)
184201
},
185202
onblur: move |_| {
186203
if focused() {
187-
ctx.focus_state.blur();
204+
ctx.blur();
188205
ctx.open.set(false);
189206
}
190207
},

primitives/src/select/components/select.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
//! Main Select component implementation.
22
33
use core::panic;
4+
use std::collections::BTreeMap;
45
use std::time::Duration;
56

67
use crate::{select::context::RcPartialEqValue, use_controlled, use_effect};
78
use dioxus::prelude::*;
89
use dioxus_core::Task;
910

1011
use super::super::context::SelectContext;
11-
use crate::focus::use_focus_provider;
1212

1313
/// Props for the main Select component
1414
#[derive(Props, Clone, PartialEq)]
@@ -109,7 +109,7 @@ pub fn Select<T: Clone + PartialEq + 'static>(props: SelectProps<T>) -> Element
109109

110110
let open = use_signal(|| false);
111111
let mut typeahead_buffer = use_signal(String::new);
112-
let options = use_signal(Vec::default);
112+
let options = use_signal(BTreeMap::new);
113113
let adaptive_keyboard = use_signal(super::super::text_search::AdaptiveKeyboard::new);
114114
let list_id = use_signal(|| None);
115115
let mut typeahead_clear_task: Signal<Option<Task>> = use_signal(|| None);
@@ -130,8 +130,6 @@ pub fn Select<T: Clone + PartialEq + 'static>(props: SelectProps<T>) -> Element
130130
}
131131
});
132132

133-
let focus_state = use_focus_provider(props.roving_loop);
134-
135133
// Clear the typeahead buffer when the select is closed
136134
use_effect(move || {
137135
if !open() {
@@ -144,21 +142,23 @@ pub fn Select<T: Clone + PartialEq + 'static>(props: SelectProps<T>) -> Element
144142
}
145143
});
146144
let initial_focus_last = use_signal(|| None);
145+
let current_focus = use_signal(|| None);
147146

148147
use_context_provider(|| SelectContext {
149148
typeahead_buffer,
150149
open,
151150
value,
152151
set_value,
153152
options,
153+
roving_loop: props.roving_loop,
154154
adaptive_keyboard,
155155
list_id,
156-
focus_state,
157156
disabled: props.disabled,
158157
placeholder: props.placeholder,
159158
typeahead_clear_task,
160159
typeahead_timeout: props.typeahead_timeout,
161160
initial_focus_last,
161+
current_focus,
162162
});
163163

164164
rsx! {

primitives/src/select/components/value.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ pub fn SelectValue(props: SelectValueProps) -> Element {
7070
value.as_ref().and_then(|v| {
7171
ctx.options
7272
.read()
73-
.iter()
74-
.find(|opt| opt.value == *v)
75-
.map(|opt| opt.text_value.clone())
73+
.values()
74+
.find(|state| state.value == *v)
75+
.map(|state| state.text_value.clone())
7676
})
7777
});
7878

0 commit comments

Comments
 (0)