diff --git a/addons/web/static/src/core/select_menu/select_menu.js b/addons/web/static/src/core/select_menu/select_menu.js
index 82924d1b68e..da86db1ee3a 100644
--- a/addons/web/static/src/core/select_menu/select_menu.js
+++ b/addons/web/static/src/core/select_menu/select_menu.js
@@ -83,7 +83,6 @@ export class SelectMenu extends Component {
searchValue: "",
});
this.inputRef = useRef("inputRef");
- this.inputContainerRef = useRef("inputContainerRef");
this.debouncedOnInput = useDebounced(
() => this.onInput(this.inputRef.el ? this.inputRef.el.value.trim() : ""),
250
@@ -162,9 +161,24 @@ export class SelectMenu extends Component {
this.state.searchValue = searchString;
// Get reference to dropdown container and scroll to the top.
- const inputContainer = this.inputContainerRef.el;
- if (inputContainer && inputContainer.parentNode) {
- inputContainer.parentNode.scrollTo(0, 0);
+ const inputEl = this.inputRef.el;
+ if (inputEl && inputEl.parentNode) {
+ inputEl.parentNode.scrollTo(0, 0);
+ }
+ }
+
+ onSearchKeydown(ev) {
+ if (ev.key === "ArrowDown" || ev.key === "Enter") {
+ // Focus the first choice when navigating from the input using the arrow down key
+ const target = ev.target.parentElement.querySelector(".o_select_menu_item");
+ ev.target.classList.remove("focus");
+ target?.classList.add("focus");
+ target?.focus();
+ ev.preventDefault();
+ }
+ if (ev.key === "Enter" && this.state.choices.length === 1) {
+ // When there is only one displayed option, the enter key selects the value
+ ev.target.parentElement.querySelector(".o_select_menu_item").click();
}
}
diff --git a/addons/web/static/src/core/select_menu/select_menu.scss b/addons/web/static/src/core/select_menu/select_menu.scss
index 0e54926f652..e88fb227452 100644
--- a/addons/web/static/src/core/select_menu/select_menu.scss
+++ b/addons/web/static/src/core/select_menu/select_menu.scss
@@ -31,8 +31,14 @@
max-height: 350px;
border: 1px solid $o-brand-primary;
- .o_select_menu_input, .o_select_menu_bottom_area {
- background-color: $dropdown-bg;
+ .o_select_menu_sticky {
+ background-color: $dropdown-bg !important;
+
+ &.o_select_menu_item {
+ &:focus, &:hover {
+ background: $dropdown-border-color !important;
+ }
+ }
}
.o_select_menu_group {
top: 40px;
diff --git a/addons/web/static/src/core/select_menu/select_menu.xml b/addons/web/static/src/core/select_menu/select_menu.xml
index b0d949e80c4..c380b677dfe 100644
--- a/addons/web/static/src/core/select_menu/select_menu.xml
+++ b/addons/web/static/src/core/select_menu/select_menu.xml
@@ -30,18 +30,18 @@
-
+
No result found
@@ -61,9 +61,7 @@
-
+
diff --git a/addons/web/static/tests/core/select_menu_tests.js b/addons/web/static/tests/core/select_menu_tests.js
index a0559dbee06..87cbb346d7b 100644
--- a/addons/web/static/tests/core/select_menu_tests.js
+++ b/addons/web/static/tests/core/select_menu_tests.js
@@ -12,6 +12,7 @@ import {
click,
triggerEvent,
nextTick,
+ triggerHotkey,
editInput,
} from "../helpers/utils";
@@ -171,8 +172,8 @@ QUnit.module("Web Components", (hooks) => {
await mount(Parent, target, { env });
await open();
- assert.containsOnce(target, ".o_select_menu_input");
- assert.equal(document.activeElement, target.querySelector(".o_select_menu_input input"));
+ assert.containsOnce(target, "input.o_select_menu_sticky");
+ assert.equal(document.activeElement, target.querySelector("input.o_select_menu_sticky"));
});
QUnit.test(
@@ -426,7 +427,7 @@ QUnit.module("Web Components", (hooks) => {
>
-
+
@@ -440,7 +441,7 @@ QUnit.module("Web Components", (hooks) => {
"2 items selected"
);
- await click(target, ".o_select_menu_item:nth-child(2)");
+ await click(target, ".o_select_menu_item:nth-child(3)");
await open();
assert.strictEqual(
target.querySelector(".o_select_menu_bottom_area").textContent.trim(),
@@ -481,7 +482,7 @@ QUnit.module("Web Components", (hooks) => {
await open();
assert.containsNone(target, ".coolClass");
- await editInput(target, ".o_select_menu_input input", "coucou");
+ await editInput(target, "input.o_select_menu_sticky", "coucou");
assert.containsOnce(target, ".coolClass");
await click(target.querySelector(".coolClass"));
@@ -651,6 +652,11 @@ QUnit.module("Web Components", (hooks) => {
// Select first choice
await open();
+ assert.containsNone(
+ target,
+ ".o_select_menu_sticky.top-0",
+ "Search box is not present."
+ );
assert.containsNone(
target,
".o_select_menu_item.o_select_active",
@@ -766,4 +772,99 @@ QUnit.module("Web Components", (hooks) => {
assert.containsNone(target, ".o_select_menu .o_tag", "There should be no tags.");
}
);
+
+ QUnit.test("Navigation is possible from the input when it is focused", async (assert) => {
+ assert.expect(6);
+
+ class Parent extends Component {
+ setup() {
+ this.state = useState({ value: "b" });
+ this.choices = [
+ { label: "A", value: "a" },
+ { label: "B", value: "b" },
+ { label: "C", value: "c" },
+ ];
+ }
+
+ onSelect(newValue) {
+ assert.step(newValue);
+ this.state.value = newValue;
+ }
+ }
+ Parent.components = { SelectMenu };
+ Parent.template = xml`
+
+ `;
+
+ await mount(Parent, target, { env });
+ await open();
+ assert.equal(
+ document.activeElement,
+ target.querySelector("input.o_select_menu_sticky"),
+ "search input is focused by default"
+ );
+
+ await triggerHotkey("ArrowDown");
+ assert.strictEqual(
+ document.activeElement.textContent,
+ "A",
+ "first item is focused after keyboard navigation"
+ );
+
+ await triggerHotkey("ArrowDown");
+ assert.strictEqual(document.activeElement.textContent, "B", "second item is now focused");
+
+ await triggerHotkey("ArrowUp");
+ await triggerHotkey("ArrowUp");
+ assert.equal(
+ document.activeElement,
+ target.querySelector("input.o_select_menu_sticky"),
+ "search input is focused again"
+ );
+
+ await triggerHotkey("ArrowDown");
+ await triggerHotkey("Enter");
+ assert.verifySteps(["a"], "value has been selected after keyboard navigation");
+ });
+
+ QUnit.test(
+ "When only one choice is displayed, 'enter' key should select the value",
+ async (assert) => {
+ assert.expect(2);
+
+ class Parent extends Component {
+ setup() {
+ this.state = useState({ value: "b" });
+ this.choices = [
+ { label: "A", value: "a" },
+ { label: "B", value: "b" },
+ { label: "C", value: "c" },
+ ];
+ }
+
+ onSelect(newValue) {
+ assert.step(newValue);
+ this.state.value = newValue;
+ }
+ }
+ Parent.components = { SelectMenu };
+ Parent.template = xml`
+
+ `;
+
+ await mount(Parent, target, { env });
+ await open();
+ await editInput(target, "input.o_select_menu_sticky", "a");
+ await triggerHotkey("Enter");
+ assert.verifySteps(["a"], "value has been selected after keyboard navigation");
+ }
+ );
});