fix(overlays): return focus to presenting element after dismissal (#22167)

resolves #21768
This commit is contained in:
Liam DeBeasi
2020-09-24 18:07:25 -04:00
committed by GitHub
parent e9b2cc8453
commit cc45ad815c
3 changed files with 65 additions and 6 deletions

View File

@ -7,14 +7,12 @@ const getActiveElementText = async (page) => {
test('datetime/picker: focus trap', async () => { test('datetime/picker: focus trap', async () => {
const page = await newE2EPage({ url: '/src/components/datetime/test/basic?ionic:_testing=true' }); const page = await newE2EPage({ url: '/src/components/datetime/test/basic?ionic:_testing=true' });
await page.click('#datetime-part'); await page.click('#datetime-part');
await page.waitForSelector('#datetime-part'); await page.waitForSelector('#datetime-part');
let datetime = await page.find('ion-datetime'); let datetime = await page.find('ion-datetime');
expect(datetime).not.toBe(null); expect(datetime).not.toBe(null);
await datetime.waitForVisible();
// TODO fix // TODO fix
await page.waitFor(100); await page.waitFor(100);

View File

@ -9,17 +9,15 @@ const getActiveElementText = async (page) => {
test('modal: focus trap', async () => { test('modal: focus trap', async () => {
const page = await newE2EPage({ url: '/src/components/modal/test/basic?ionic:_testing=true' }); const page = await newE2EPage({ url: '/src/components/modal/test/basic?ionic:_testing=true' });
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
await page.click('#basic-modal'); await page.click('#basic-modal');
await page.waitForSelector('#basic-modal'); await page.waitForSelector('#basic-modal');
let modal = await page.find('ion-modal'); let modal = await page.find('ion-modal');
expect(modal).not.toBe(null); expect(modal).not.toBe(null);
await modal.waitForVisible();
// TODO fix await ionModalDidPresent.next();
await page.waitFor(50);
await page.keyboard.press('Tab'); await page.keyboard.press('Tab');
@ -39,6 +37,33 @@ test('modal: focus trap', async () => {
expect(activeElementTextThree).toEqual('Dismiss Modal'); expect(activeElementTextThree).toEqual('Dismiss Modal');
}); });
test('modal: return focus', async () => {
const page = await newE2EPage({ url: '/src/components/modal/test/basic?ionic:_testing=true' });
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
await page.click('#basic-modal');
await page.waitForSelector('#basic-modal');
let modal = await page.find('ion-modal');
expect(modal).not.toBe(null);
await ionModalDidPresent.next()
await Promise.all([
await modal.callMethod('dismiss'),
await ionModalDidDismiss.next(),
await modal.waitForNotVisible(),
]);
modal = await page.find('ion-modal');
expect(modal).toBeNull();
const activeElement = await page.evaluateHandle(() => document.activeElement);
const id = await activeElement.evaluate((node) => node.id);
expect(id).toEqual('basic-modal');
});
test('modal: basic', async () => { test('modal: basic', async () => {
await testModal(DIRECTORY, '#basic-modal'); await testModal(DIRECTORY, '#basic-modal');
}); });

View File

@ -260,11 +260,47 @@ export const present = async (
overlay.didPresent.emit(); overlay.didPresent.emit();
} }
/**
* When an overlay that steals focus
* is dismissed, focus should be returned
* to the element that was focused
* prior to the overlay opening. Toast
* does not steal focus and is excluded
* from returning focus as a result.
*/
if (overlay.el.tagName !== 'ION-TOAST') {
focusPreviousElementOnDismiss(overlay.el);
}
if (overlay.keyboardClose) { if (overlay.keyboardClose) {
overlay.el.focus(); overlay.el.focus();
} }
}; };
/**
* When an overlay component is dismissed,
* focus should be returned to the element
* that presented the overlay. Otherwise
* focus will be set on the body which
* means that people using screen readers
* or tabbing will need to re-navigate
* to where they were before they
* opened the overlay.
*/
const focusPreviousElementOnDismiss = async (overlayEl: any) => {
let previousElement = document.activeElement as HTMLElement | null;
if (!previousElement) { return; }
const shadowRoot = previousElement && previousElement.shadowRoot;
if (shadowRoot) {
// If there are no inner focusable elements, just focus the host element.
previousElement = shadowRoot.querySelector(innerFocusableQueryString) || previousElement;
}
await overlayEl.onDidDismiss();
previousElement.focus();
}
export const dismiss = async ( export const dismiss = async (
overlay: OverlayInterface, overlay: OverlayInterface,
data: any | undefined, data: any | undefined,