diff --git a/addons/web/static/src/core/ui/ui_service.js b/addons/web/static/src/core/ui/ui_service.js index 68c8696f041..c0ff1528b19 100644 --- a/addons/web/static/src/core/ui/ui_service.js +++ b/addons/web/static/src/core/ui/ui_service.js @@ -77,7 +77,19 @@ export function useActiveElement(refName) { return () => { uiService.deactivateElement(el); el.removeEventListener("keydown", trapFocus); - if (el.contains(document.activeElement)) { + + /** + * In some cases, the current active element is not + * anymore in el (e.g. with ConfirmationDialog, the + * confirm button is disabled when clicked, so the + * focus is lost). In that case, we also want to restore + * the focus to the previous active element so we + * check if the current active element is the body + */ + if ( + el.contains(document.activeElement) || + document.activeElement === document.body + ) { oldActiveElement.focus(); } }; diff --git a/addons/web/static/tests/core/confirmation_dialog_tests.js b/addons/web/static/tests/core/confirmation_dialog_tests.js index bed9f02d908..67d1e49fbea 100644 --- a/addons/web/static/tests/core/confirmation_dialog_tests.js +++ b/addons/web/static/tests/core/confirmation_dialog_tests.js @@ -5,8 +5,17 @@ import { uiService } from "@web/core/ui/ui_service"; import { hotkeyService } from "@web/core/hotkeys/hotkey_service"; import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; import { makeDialogTestEnv } from "../helpers/mock_env"; -import { click, getFixture, makeDeferred, mount, nextTick, triggerHotkey } from "../helpers/utils"; +import { + click, + destroy, + getFixture, + makeDeferred, + mount, + nextTick, + triggerHotkey, +} from "../helpers/utils"; import { makeFakeDialogService } from "../helpers/mock_services"; +import { Component, xml } from "@odoo/owl"; const serviceRegistry = registry.category("services"); let target; @@ -193,4 +202,41 @@ QUnit.module("Components", (hooks) => { await nextTick(); assert.verifySteps(["close"]); }); + + QUnit.test("Focus is correctly restored after confirmation", async function (assert) { + const env = await makeDialogTestEnv(); + + class MyComp extends Component {} + MyComp.template = xml`
`; + + await mount(MyComp, target, { env }); + target.querySelector(".my-input").focus(); + assert.strictEqual(document.activeElement, target.querySelector(".my-input")); + + const comp = await mount(ConfirmationDialog, target, { + env, + props: { + body: "Some content", + title: "Confirmation", + confirm: () => {}, + close: () => {}, + }, + }); + assert.strictEqual( + document.activeElement, + target.querySelector(".modal-footer .btn-primary") + ); + await click(target, ".modal-footer .btn-primary"); + assert.strictEqual( + document.activeElement, + document.body, + "As the button is disabled, the focus is now on the body" + ); + destroy(comp); + assert.strictEqual( + document.activeElement, + target.querySelector(".my-input"), + "After destruction of the dialog, the focus is restored to the input" + ); + }); });