mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
Feat/combobox aria (#244)
* pref: Combobox blur * feat: Combobox input navigation
This commit is contained in:
parent
e153ef722b
commit
0f989bed73
4 changed files with 75 additions and 51 deletions
|
@ -96,4 +96,11 @@ impl ActiveDescendantController {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,4 +87,24 @@ impl OptionWalker {
|
|||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,39 +68,21 @@ 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;
|
||||
let multiselect = selected_options.is_vec();
|
||||
let combobox_injection = ComboboxInjection {
|
||||
value,
|
||||
multiselect,
|
||||
selected_options,
|
||||
options,
|
||||
is_show_listbox,
|
||||
validate,
|
||||
};
|
||||
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 (set_listbox, active_descendant_controller) =
|
||||
use_active_descendant(move |el| el.class_list().contains("thaw-combobox-option"));
|
||||
|
||||
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);
|
||||
if selected_options.with_untracked(|options| match options {
|
||||
VecModelWithValue::T(v) => v != &input_value,
|
||||
|
@ -116,19 +98,32 @@ pub fn Combobox(
|
|||
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;
|
||||
};
|
||||
|
||||
let multiselect = selected_options.is_vec();
|
||||
let combobox_injection = ComboboxInjection {
|
||||
value,
|
||||
multiselect,
|
||||
selected_options,
|
||||
options,
|
||||
is_show_listbox,
|
||||
validate,
|
||||
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 (set_listbox, active_descendant_controller) =
|
||||
use_active_descendant(move |el| el.class_list().contains("thaw-combobox-option"));
|
||||
|
||||
let on_blur = {
|
||||
let active_descendant_controller = active_descendant_controller.clone();
|
||||
|
@ -148,6 +143,7 @@ pub fn Combobox(
|
|||
});
|
||||
active_descendant_controller.blur();
|
||||
validate.run(Some(ComboboxRuleTrigger::Blur));
|
||||
is_show_listbox.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -209,6 +205,7 @@ pub fn Combobox(
|
|||
.unwrap_or_default()
|
||||
}
|
||||
node_ref=clear_icon_ref
|
||||
on:mousedown=|e| e.prevent_default()
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
|
|
|
@ -18,7 +18,7 @@ let selected_options = RwSignal::new(vec![]);
|
|||
|
||||
view! {
|
||||
<Combobox selected_options clearable=true>
|
||||
<ComboboxOption value="cat" text="Cat" disabled=true/>
|
||||
<ComboboxOption value="cat" text="Cat"/>
|
||||
<ComboboxOption value="dog" text="Dog" />
|
||||
</Combobox>
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue