Feat/combobox aria (#244)

* pref: Combobox blur

* feat: Combobox input navigation
This commit is contained in:
luoxiaozero 2024-08-28 00:05:52 +08:00 committed by GitHub
parent e153ef722b
commit 0f989bed73
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 75 additions and 51 deletions

View file

@ -96,4 +96,11 @@ impl ActiveDescendantController {
None None
} }
} }
pub fn find(&self, predicate: impl Fn(String) -> bool) -> Option<String> {
let target = self.option_walker.find(predicate)?;
let id = target.id();
self.focus_active_descendant(target);
Some(id)
}
} }

View file

@ -87,4 +87,24 @@ impl OptionWalker {
tree_walker.previous_node().unwrap_throw()?.dyn_into().ok() tree_walker.previous_node().unwrap_throw()?.dyn_into().ok()
}) })
} }
pub fn find(&self, predicate: impl Fn(String) -> bool) -> Option<HtmlElement> {
self.0.with_value(|tree_walker| {
let Some((tree_walker, _)) = tree_walker.as_ref() else {
return None;
};
tree_walker.set_current_node(&tree_walker.root());
let mut current: Option<HtmlElement> =
tree_walker.first_child().unwrap_throw()?.dyn_into().ok();
while let Some(cur) = current.as_ref() {
if predicate(cur.id()) {
break;
}
current = tree_walker.next_node().unwrap_throw()?.dyn_into().ok();
}
current
})
}
} }

View file

@ -68,39 +68,21 @@ pub fn Combobox(
}); });
} }
#[cfg(any(feature = "csr", feature = "hydrate"))] let multiselect = selected_options.is_vec();
{ let combobox_injection = ComboboxInjection {
let handle = window_event_listener(ev::click, move |ev| { value,
use leptos::wasm_bindgen::__rt::IntoJsResult; multiselect,
if !is_show_listbox.get_untracked() { selected_options,
return; options,
} is_show_listbox,
let el = ev.target(); validate,
let mut el: Option<web_sys::Element> =
el.into_js_result().map_or(None, |el| Some(el.into()));
let body = document().body().unwrap();
if let Some(listbox_el) = listbox_ref.get_untracked() {
if let Some(trigger_el) = trigger_ref.get_untracked() {
while let Some(current_el) = el {
if current_el == *body {
break;
}; };
if current_el == **listbox_el { let (set_listbox, active_descendant_controller) =
return; use_active_descendant(move |el| el.class_list().contains("thaw-combobox-option"));
}
if current_el == **trigger_el {
return;
}
el = current_el.parent_element();
}
}
}
is_show_listbox.set(false);
});
on_cleanup(move || handle.remove());
}
let on_input = move |ev| { let on_input = {
let active_descendant_controller = active_descendant_controller.clone();
move |ev| {
let input_value = event_target_value(&ev); let input_value = event_target_value(&ev);
if selected_options.with_untracked(|options| match options { if selected_options.with_untracked(|options| match options {
VecModelWithValue::T(v) => v != &input_value, VecModelWithValue::T(v) => v != &input_value,
@ -116,19 +98,32 @@ pub fn Combobox(
selected_options.set(vec![]); selected_options.set(vec![]);
} }
value.set(input_value); value.set(input_value);
let Some(value) = value.with_untracked(|value| {
let value = value.trim().to_ascii_lowercase();
if value.is_empty() {
None
} else {
Some(value)
}
}) else {
active_descendant_controller.blur();
return;
}; };
if active_descendant_controller
let multiselect = selected_options.is_vec(); .find(|id| {
let combobox_injection = ComboboxInjection { options.with_value(|options| {
value, let Some((_, text, _)) = options.get(&id) else {
multiselect, return false;
selected_options, };
options, text.to_ascii_lowercase().contains(&value)
is_show_listbox, })
validate, })
.is_none()
{
active_descendant_controller.blur();
}
}
}; };
let (set_listbox, active_descendant_controller) =
use_active_descendant(move |el| el.class_list().contains("thaw-combobox-option"));
let on_blur = { let on_blur = {
let active_descendant_controller = active_descendant_controller.clone(); let active_descendant_controller = active_descendant_controller.clone();
@ -148,6 +143,7 @@ pub fn Combobox(
}); });
active_descendant_controller.blur(); active_descendant_controller.blur();
validate.run(Some(ComboboxRuleTrigger::Blur)); validate.run(Some(ComboboxRuleTrigger::Blur));
is_show_listbox.set(false);
} }
}; };
@ -209,6 +205,7 @@ pub fn Combobox(
.unwrap_or_default() .unwrap_or_default()
} }
node_ref=clear_icon_ref node_ref=clear_icon_ref
on:mousedown=|e| e.prevent_default()
> >
<svg <svg
fill="currentColor" fill="currentColor"

View file

@ -18,7 +18,7 @@ let selected_options = RwSignal::new(vec![]);
view! { view! {
<Combobox selected_options clearable=true> <Combobox selected_options clearable=true>
<ComboboxOption value="cat" text="Cat" disabled=true/> <ComboboxOption value="cat" text="Cat"/>
<ComboboxOption value="dog" text="Dog" /> <ComboboxOption value="dog" text="Dog" />
</Combobox> </Combobox>
} }