diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 5233f292b6a..af2dafc586d 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -82,8 +82,16 @@ export const createOverlay = ( return Promise.resolve() as any; }; +/** + * This query string selects elements that + * are eligible to receive focus. We select + * interactive elements that meet the following + * criteria: + * 1. Element does not have a negative tabindex + * 2. Element does not have [hidden] + */ const focusableQueryString = - '[tabindex]:not([tabindex^="-"]), input:not([type=hidden]):not([tabindex^="-"]), textarea:not([tabindex^="-"]), button:not([tabindex^="-"]), select:not([tabindex^="-"]), .ion-focusable:not([tabindex^="-"])'; + '[tabindex]:not([tabindex^="-"]):not([hidden]), input:not([type=hidden]):not([tabindex^="-"]):not([hidden]), textarea:not([tabindex^="-"]):not([hidden]), button:not([tabindex^="-"]):not([hidden]), select:not([tabindex^="-"]):not([hidden]), .ion-focusable:not([tabindex^="-"]):not([hidden])'; export const focusFirstDescendant = (ref: Element, overlay: HTMLIonOverlayElement) => { let firstInput = ref.querySelector(focusableQueryString) as HTMLElement | null; diff --git a/core/src/utils/test/overlays/overlays.e2e.ts b/core/src/utils/test/overlays/overlays.e2e.ts index 51d1fb2737a..901d298eed3 100644 --- a/core/src/utils/test/overlays/overlays.e2e.ts +++ b/core/src/utils/test/overlays/overlays.e2e.ts @@ -2,9 +2,10 @@ import { expect } from '@playwright/test'; import { test } from '@utils/test/playwright'; test.describe('overlays: focus', () => { - test('should not focus the overlay container if element inside of overlay is focused', async ({ page, skip }) => { + test.beforeEach(({ skip }) => { skip.rtl(); - + }); + test('should not focus the overlay container if element inside of overlay is focused', async ({ page }) => { await page.setContent(` Show Modal @@ -26,4 +27,30 @@ test.describe('overlays: focus', () => { await expect(page.locator('ion-input input')).toBeFocused(); }); + + test('should not select a hidden focusable element', async ({ page, browserName }) => { + await page.setContent(` + Show Modal + + + + Visible Button + + + `); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const presentButton = page.locator('ion-button#open-modal'); + const visibleButton = page.locator('ion-button#visible'); + const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; + + await presentButton.click(); + await ionModalDidPresent.next(); + + await page.keyboard.press(tabKey); + await expect(visibleButton).toBeFocused(); + + await page.keyboard.press(tabKey); + await expect(visibleButton).toBeFocused(); + }); });