diff --git a/assets/js/hooks/combobox.js b/assets/js/hooks/combobox.js
index 14c436e..22ad5ba 100644
--- a/assets/js/hooks/combobox.js
+++ b/assets/js/hooks/combobox.js
@@ -442,8 +442,15 @@ export default {
if (!this.isOpen) {
return
}
+
+ const focusedOption = this.getCurrentFocusedOption()
+ if (e.key === KEYS.TAB && !this.getVisibleOptions().includes(focusedOption)) {
+ this.handleBlur()
+ return
+ }
+
e.preventDefault()
- this.selectOption(this.getCurrentFocusedOption())
+ this.selectOption(focusedOption)
},
handleBackspace(e) {
diff --git a/demo/lib/demo_web/live/fixtures_live/async_combobox_fixture.html.heex b/demo/lib/demo_web/live/fixtures_live/async_combobox_fixture.html.heex
index a37dd3a..6ce2e46 100644
--- a/demo/lib/demo_web/live/fixtures_live/async_combobox_fixture.html.heex
+++ b/demo/lib/demo_web/live/fixtures_live/async_combobox_fixture.html.heex
@@ -1,3 +1,5 @@
+
+
<.combobox id="demo-async-combobox" name="user[favourite_fruit]">
<.combobox_input
on_search="async_combobox_search"
@@ -19,3 +21,5 @@
<% end %>
+
+
diff --git a/demo/test/wallaby/demo_web/combobox_keyboard_navigation_test.exs b/demo/test/wallaby/demo_web/combobox_keyboard_navigation_test.exs
index d8d6592..2193dfd 100644
--- a/demo/test/wallaby/demo_web/combobox_keyboard_navigation_test.exs
+++ b/demo/test/wallaby/demo_web/combobox_keyboard_navigation_test.exs
@@ -97,6 +97,42 @@ defmodule DemoWeb.ComboboxKeyboardNavigationTest do
|> assert_has(@options_container |> Query.visible(false))
end
+ for {key, keys, target} <- [
+ {"Tab", [:tab], "next-input"},
+ {"Shift+Tab", [:shift, :tab], "previous-input"}
+ ] do
+ @tag keys: keys, target: target
+ @feature_name "#{key} leaves an async combobox with no results"
+ feature @feature_name, %{
+ session: session,
+ keys: keys,
+ target: target
+ } do
+ input = Query.css("#demo-async-combobox input[data-prima-ref=search_input]")
+
+ session
+ |> visit_fixture("/fixtures/async-combobox", "#demo-async-combobox")
+ |> click(input)
+ |> click(Query.css("#demo-async-combobox [role=option][data-value=Cherry]"))
+ |> fill_in(input, with: "no matching fruit")
+ |> assert_has(Query.css("#demo-async-combobox-options", visible: true))
+ |> assert_has(Query.css("#demo-async-combobox [role=option]", count: 0))
+ |> send_keys(keys)
+ |> assert_has(Query.css("##{target}:focus"))
+ |> assert_has(Query.css("#demo-async-combobox-options", visible: false))
+ |> execute_script(
+ """
+ const root = document.querySelector('#demo-async-combobox');
+ return {
+ search: root.querySelector('[data-prima-ref=search_input]').value,
+ selection: root.querySelector('[data-prima-ref=submit_input]').value
+ };
+ """,
+ fn values -> assert values == %{"search" => "Cherry", "selection" => "Cherry"} end
+ )
+ end
+ end
+
feature "ArrowDown opens combobox when closed and input is focused", %{session: session} do
session
|> visit_fixture("/fixtures/simple-combobox", "#demo-combobox")