fix(overlays): focus trapping no longer includes disabled elements (#25949)

This commit is contained in:
Liam DeBeasi
2022-09-16 10:45:45 -05:00
committed by GitHub
parent e06a7d0f73
commit 6cb5827d06
2 changed files with 63 additions and 2 deletions

View File

@ -88,10 +88,14 @@ export const createOverlay = <T extends HTMLIonOverlayElement>(
* interactive elements that meet the following * interactive elements that meet the following
* criteria: * criteria:
* 1. Element does not have a negative tabindex * 1. Element does not have a negative tabindex
* 2. Element does not have [hidden] * 2. Element does not have `hidden`
* 3. Element does not have `disabled` for non-Ionic components.
* 4. Element does not have `disabled` or `disabled="true"` for Ionic components.
* Note: We need this distinction because `disabled="false"` is
* valid usage for the disabled property on ion-button.
*/ */
const focusableQueryString = const focusableQueryString =
'[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])'; '[tabindex]:not([tabindex^="-"]):not([hidden]):not([disabled]), input:not([type=hidden]):not([tabindex^="-"]):not([hidden]):not([disabled]), textarea:not([tabindex^="-"]):not([hidden]):not([disabled]), button:not([tabindex^="-"]):not([hidden]):not([disabled]), select:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable[disabled="false"]:not([tabindex^="-"]):not([hidden])';
export const focusFirstDescendant = (ref: Element, overlay: HTMLIonOverlayElement) => { export const focusFirstDescendant = (ref: Element, overlay: HTMLIonOverlayElement) => {
let firstInput = ref.querySelector(focusableQueryString) as HTMLElement | null; let firstInput = ref.querySelector(focusableQueryString) as HTMLElement | null;

View File

@ -53,4 +53,61 @@ test.describe('overlays: focus', () => {
await page.keyboard.press(tabKey); await page.keyboard.press(tabKey);
await expect(visibleButton).toBeFocused(); await expect(visibleButton).toBeFocused();
}); });
test('should not select a disabled focusable element', async ({ page, browserName }) => {
await page.setContent(`
<ion-button id="open-modal">Show Modal</ion-button>
<ion-modal trigger="open-modal">
<ion-content>
<ion-button disabled="true" id="disabled">Button</ion-button>
<ion-button id="active">Active Button</ion-button>
</ion-content>
</ion-modal>
`);
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
const presentButton = page.locator('ion-button#open-modal');
const activeButton = page.locator('ion-button#active');
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
await presentButton.click();
await ionModalDidPresent.next();
await page.keyboard.press(tabKey);
await expect(activeButton).toBeFocused();
await page.keyboard.press(tabKey);
await expect(activeButton).toBeFocused();
});
test('should select a focusable element with disabled="false"', async ({ page, browserName }) => {
await page.setContent(`
<ion-button id="open-modal">Show Modal</ion-button>
<ion-modal trigger="open-modal">
<ion-content>
<ion-button disabled="false" id="disabled-false">Button</ion-button>
<ion-button id="active">Active Button</ion-button>
</ion-content>
</ion-modal>
`);
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
const presentButton = page.locator('ion-button#open-modal');
const disabledFalseButton = page.locator('ion-button#disabled-false');
const activeButton = page.locator('ion-button#active');
const tabKey = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
await presentButton.click();
await ionModalDidPresent.next();
await page.keyboard.press(tabKey);
await expect(disabledFalseButton).toBeFocused();
await page.keyboard.press(tabKey);
await expect(activeButton).toBeFocused();
// Loop back to beginning of overlay
await page.keyboard.press(tabKey);
await expect(disabledFalseButton).toBeFocused();
});
}); });