feat: allow free input in AutoComplete (#107)

This commit is contained in:
Kirill Zaborsky 2024-02-22 10:38:30 +03:00 committed by GitHub
parent 3a9da44de0
commit 1dbe00b5af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 18 deletions

View file

@ -65,6 +65,7 @@ view! {
| placeholder | `OptionalProp<RwSignal<String>>` | `Default::default()` | Autocomplete's placeholder. |
| options | `MaybeSignal<Vec<AutoCompleteOption>>` | `Default::default()` | Options to autocomplete from. |
| disabled | `MaybeSignal<bool>` | `false` | Whether the input is disabled. |
| allow_free_input | `bool` | `false` | Whether free text input is allowed. |
| invalid | `MaybeSignal<bool>` | `false` | Whether the input is invalid. |
| clear_after_select | `MaybeSignal<bool>` | `false` | Whether to clear after selection. |
| on_select | `Option<Callback<String>>` | `None` | On select callback function. |

View file

@ -33,6 +33,7 @@ pub fn AutoComplete(
#[prop(optional, into)] clear_after_select: MaybeSignal<bool>,
#[prop(optional, into)] on_select: Option<Callback<String>>,
#[prop(optional, into)] disabled: MaybeSignal<bool>,
#[prop(optional, into)] allow_free_input: bool,
#[prop(optional, into)] invalid: MaybeSignal<bool>,
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
#[prop(optional)] auto_complete_prefix: Option<AutoCompletePrefix>,
@ -57,13 +58,15 @@ pub fn AutoComplete(
css_vars
});
let select_option_index = create_rw_signal::<usize>(0);
let default_index = if allow_free_input { None } else { Some(0) };
let select_option_index = create_rw_signal::<Option<usize>>(default_index);
let menu_ref = create_node_ref::<html::Div>();
let is_show_menu = create_rw_signal(false);
let auto_complete_ref = create_node_ref::<html::Div>();
let options = StoredMaybeSignal::from(options);
let open_menu = move || {
select_option_index.set(0);
select_option_index.set(default_index);
is_show_menu.set(true);
};
let allow_value = move |_| {
@ -82,9 +85,20 @@ pub fn AutoComplete(
if let Some(on_select) = on_select {
on_select.call(option_value);
}
if allow_free_input {
select_option_index.set(None);
}
is_show_menu.set(false);
};
// we unset selection index whenever options get changed
// otherwise e.g. selection could move from one item to
// another staying on the same index
create_effect(move |_| {
options.track();
select_option_index.set(default_index);
});
let on_keydown = move |event: ev::KeyboardEvent| {
if !is_show_menu.get_untracked() {
return;
@ -92,29 +106,40 @@ pub fn AutoComplete(
let key = event.key();
if key == *"ArrowDown" {
select_option_index.update(|index| {
if *index == options.with_untracked(|options| options.len()) - 1 {
*index = 0
if *index == Some(options.with_untracked(|options| options.len()) - 1) {
*index = default_index
} else {
*index += 1
*index = Some(index.map_or(0, |index| index + 1))
}
});
} else if key == *"ArrowUp" {
select_option_index.update(|index| {
if *index == 0 {
*index = options.with_untracked(|options| options.len()) - 1;
match *index {
None => *index = Some(options.with_untracked(|options| options.len()) - 1),
Some(0) => {
if allow_free_input {
*index = None
} else {
*index -= 1
*index = Some(options.with_untracked(|options| options.len()) - 1)
}
}
Some(prev_index) => *index = Some(prev_index - 1),
};
});
} else if key == *"Enter" {
event.prevent_default();
let option_value = options.with_untracked(|options| {
let index = select_option_index.get_untracked();
if options.len() > index {
match index {
None if allow_free_input => {
let value = value.get_untracked();
(!value.is_empty()).then_some(value)
}
Some(index) if options.len() > index => {
let option = &options[index];
Some(option.value.clone())
} else {
None
}
_ => None,
}
});
if let Some(option_value) = option_value {
@ -189,13 +214,13 @@ pub fn AutoComplete(
select_value(option_value.clone());
};
let on_mouseenter = move |_| {
select_option_index.set(index);
select_option_index.set(Some(index));
};
let on_mousedown = move |ev: ev::MouseEvent| {
ev.prevent_default();
};
create_effect(move |_| {
if index == select_option_index.get() {
if Some(index) == select_option_index.get() {
if !is_show_menu.get() {
return;
}
@ -218,7 +243,7 @@ pub fn AutoComplete(
class="thaw-auto-complete__menu-item"
class=(
"thaw-auto-complete__menu-item--selected",
move || index == select_option_index.get(),
move || Some(index) == select_option_index.get(),
)
on:click=on_click