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,56 +68,6 @@ pub fn Combobox(
}); });
} }
#[cfg(any(feature = "csr", feature = "hydrate"))]
{
let handle = window_event_listener(ev::click, move |ev| {
use leptos::wasm_bindgen::__rt::IntoJsResult;
if !is_show_listbox.get_untracked() {
return;
}
let el = ev.target();
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 {
return;
}
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 input_value = event_target_value(&ev);
if selected_options.with_untracked(|options| match options {
VecModelWithValue::T(v) => v != &input_value,
VecModelWithValue::Option(v) => {
if let Some(v) = v.as_ref() {
v != &input_value
} else {
false
}
}
VecModelWithValue::Vec(_) => false,
}) {
selected_options.set(vec![]);
}
value.set(input_value);
};
let multiselect = selected_options.is_vec(); let multiselect = selected_options.is_vec();
let combobox_injection = ComboboxInjection { let combobox_injection = ComboboxInjection {
value, value,
@ -130,6 +80,51 @@ pub fn Combobox(
let (set_listbox, active_descendant_controller) = let (set_listbox, active_descendant_controller) =
use_active_descendant(move |el| el.class_list().contains("thaw-combobox-option")); use_active_descendant(move |el| el.class_list().contains("thaw-combobox-option"));
let on_input = {
let active_descendant_controller = active_descendant_controller.clone();
move |ev| {
let input_value = event_target_value(&ev);
if selected_options.with_untracked(|options| match options {
VecModelWithValue::T(v) => v != &input_value,
VecModelWithValue::Option(v) => {
if let Some(v) = v.as_ref() {
v != &input_value
} else {
false
}
}
VecModelWithValue::Vec(_) => false,
}) {
selected_options.set(vec![]);
}
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
.find(|id| {
options.with_value(|options| {
let Some((_, text, _)) = options.get(&id) else {
return false;
};
text.to_ascii_lowercase().contains(&value)
})
})
.is_none()
{
active_descendant_controller.blur();
}
}
};
let on_blur = { let on_blur = {
let active_descendant_controller = active_descendant_controller.clone(); let active_descendant_controller = active_descendant_controller.clone();
move |_| { move |_| {
@ -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>
} }