diff --git a/addons/web/static/src/js/components/dropdown_menu.js b/addons/web/static/src/js/components/dropdown_menu.js index c536f3caba7..64c30b6500e 100644 --- a/addons/web/static/src/js/components/dropdown_menu.js +++ b/addons/web/static/src/js/components/dropdown_menu.js @@ -121,7 +121,11 @@ odoo.define('web.DropdownMenu', function (require) { * @param {MouseEvent} ev */ _onWindowClick(ev) { - if (this.state.open && !this.el.contains(ev.target)) { + if ( + this.state.open && + !this.el.contains(ev.target) && + !this.el.contains(document.activeElement) + ) { this.state.open = false; } } diff --git a/addons/web/static/src/js/components/dropdown_menu_item.js b/addons/web/static/src/js/components/dropdown_menu_item.js index bc028e17719..bcc54ab4ffb 100644 --- a/addons/web/static/src/js/components/dropdown_menu_item.js +++ b/addons/web/static/src/js/components/dropdown_menu_item.js @@ -86,7 +86,11 @@ odoo.define('web.DropdownMenuItem', function (require) { * @param {MouseEvent} ev */ _onWindowClick(ev) { - if (this.state.open && !this.el.contains(ev.target)) { + if ( + this.state.open && + !this.el.contains(ev.target) && + !this.el.contains(document.activeElement) + ) { this.state.open = false; } } diff --git a/addons/web/static/tests/components/dropdown_menu_tests.js b/addons/web/static/tests/components/dropdown_menu_tests.js index a0f00b68ef1..da0d458d680 100644 --- a/addons/web/static/tests/components/dropdown_menu_tests.js +++ b/addons/web/static/tests/components/dropdown_menu_tests.js @@ -339,5 +339,48 @@ odoo.define('web.dropdown_menu_tests', function (require) { parent.destroy(); }); + + QUnit.test("dropdown doesn't get close on mousedown inside and mouseup outside dropdown", async function (assert) { + // In this test, we simulate a case where the user clicks inside a dropdown menu item + // (e.g. in the input of the 'Save current search' item in the Favorites menu), keeps + // the click pressed, moves the cursor outside the dropdown and releases the click + // (i.e. mousedown and focus inside the item, mouseup and click outside the dropdown). + // In this case, we want to keep the dropdown menu open. + assert.expect(5); + + const items = this.items; + class Parent extends owl.Component { + constructor() { + super(...arguments); + this.items = items; + } + } + Parent.components = { DropdownMenu }; + Parent.template = owl.tags.xml` +
+ +
`; + const parent = new Parent(); + await parent.mount(testUtils.prepareTarget(), { position: "first-child" }); + + const menu = parent.el.querySelector(".o_dropdown"); + assert.doesNotHaveClass(menu, "show", "dropdown should not be open"); + + await testUtils.dom.click(menu.querySelector("button")); + assert.hasClass(menu, "show", "dropdown should be open"); + + const firstItemEl = menu.querySelector(".o_menu_item > a"); + // open options menu + await testUtils.dom.click(firstItemEl); + assert.hasClass(firstItemEl.querySelector("i"), "o_icon_right fa fa-caret-down"); + + // force the focus inside the dropdown item and click outside + firstItemEl.parentElement.querySelector(".o_menu_item_options .o_item_option a").focus(); + await testUtils.dom.triggerEvents(parent.el, "click"); + assert.hasClass(menu, "show", "dropdown should still be open"); + assert.hasClass(firstItemEl.querySelector("i"), "o_icon_right fa fa-caret-down"); + + parent.destroy(); + }); }); });