diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index d8f1c25745..8ccb015d8d 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -526,7 +526,7 @@ export const present = async ( * from returning focus as a result. */ if (overlay.el.tagName !== 'ION-TOAST') { - focusPreviousElementOnDismiss(overlay.el); + restoreElementFocus(overlay.el); } /** @@ -559,7 +559,7 @@ export const present = async ( * to where they were before they * opened the overlay. */ -const focusPreviousElementOnDismiss = async (overlayEl: any) => { +const restoreElementFocus = async (overlayEl: any) => { let previousElement = document.activeElement as HTMLElement | null; if (!previousElement) { return; @@ -572,7 +572,34 @@ const focusPreviousElementOnDismiss = async (overlayEl: any) => { } await overlayEl.onDidDismiss(); - previousElement.focus(); + + /** + * After onDidDismiss, the overlay loses focus + * because it is removed from the document + * + * > An element will also lose focus [...] + * > if the element is removed from the document) + * + * https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event + * + * Additionally, `document.activeElement` returns: + * + * > The Element which currently has focus, + * > `` or null if there is + * > no focused element. + * + * https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement#value + * + * However, if the user has already focused + * an element sometime between onWillDismiss + * and onDidDismiss (for example, focusing a + * text box after tapping a button in an + * action sheet) then don't restore focus to + * previous element + */ + if (document.activeElement === null || document.activeElement === document.body) { + previousElement.focus(); + } }; export const dismiss = async ( diff --git a/core/src/utils/test/overlays/overlays.e2e.ts b/core/src/utils/test/overlays/overlays.e2e.ts index 3d112413b4..26f86c21c5 100644 --- a/core/src/utils/test/overlays/overlays.e2e.ts +++ b/core/src/utils/test/overlays/overlays.e2e.ts @@ -254,5 +254,52 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => await expect(modalInputOne).toBeFocused(); }); + + test('should not return focus to another element if focus already manually returned', async ({ + page, + skip, + }, testInfo) => { + skip.browser( + 'webkit', + 'WebKit does not consider buttons to be focusable, so this test always passes since the input is the only focusable element.' + ); + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/28849', + }); + await page.setContent( + ` + + + + + + `, + config + ); + + const ionActionSheetDidPresent = await page.spyOnEvent('ionActionSheetDidPresent'); + const actionSheet = page.locator('ion-action-sheet'); + const input = page.locator('#test-input'); + const trigger = page.locator('#open-action-sheet'); + + // present action sheet + await trigger.click(); + await ionActionSheetDidPresent.next(); + + // dismiss action sheet + await actionSheet.evaluate((el: HTMLIonActionSheetElement) => el.dismiss()); + + // verify focus is in correct location + await expect(input).toBeFocused(); + }); }); });