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
|
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()
|
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,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"
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue