mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 19:57:22 +08:00
fix(overlays): return focus to presenting element after dismissal (#22167)
resolves #21768
This commit is contained in:
@ -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);
|
||||||
|
@ -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');
|
||||||
});
|
});
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user