From d946affe22b36abbaffc7e34ff5ce5a023ee16f8 Mon Sep 17 00:00:00 2001 From: Pierre Rousseau Date: Wed, 6 Sep 2023 08:24:24 +0200 Subject: [PATCH] [FIX] web: correctly restore focus with ConfirmationDialog When a dialog is closed, the focus is meant to be restored to the previous focused element. Before this commit, it did not work for the ConfirmationDialog when the confirm button was clicked. This was because the focus was lost **before** the dialog was closed (because the confirm button is disabled when clicked), and thus the previous focused element was not in the el of the dialog anymore, and thus the focus was not restored. closes odoo/odoo#134456 Signed-off-by: Aaron Bohy (aab) --- addons/web/static/src/core/ui/ui_service.js | 14 +++++- .../tests/core/confirmation_dialog_tests.js | 48 ++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) 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" + ); + }); });