mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 01:03:03 +08:00
fix(overlays): do not return focus if application has already moved focus manually (#28850)
Issue number: resolves #28849 --------- ## What is the current behavior? If the developer tries to set focus to a custom element on overlay dismissal, Ionic will always override that focus. ## What is the new behavior? - If focus is already set by developer during dismissal, then Ionic will not restore focus to previous element ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information In the before video, you can see the text box is focused by developer code when "Mention User" is tapped, which opens the keyboard. Shortly after that, when the bottom sheet fully dismisses, Ionic focuses the button, removing focus from the text box and hiding the keyboard. In the after, Ionic detects that the developer has already focused the text box and does not change that focus. |Before|After| |---|---| |<video src="https://github.com/ionic-team/ionic-framework/assets/2166114/47d55eff-29af-4019-ac3c-00f9fe722ca7"></video>| <video src="https://github.com/ionic-team/ionic-framework/assets/2166114/508ae466-d037-41eb-b518-92338a122b22"></video>|
This commit is contained in:

committed by
GitHub

parent
f07eabe12e
commit
a016670a8a
@ -526,7 +526,7 @@ export const present = async <OverlayPresentOptions>(
|
||||
* 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 <OverlayPresentOptions>(
|
||||
* 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,
|
||||
* > `<body>` 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 <OverlayDismissOptions>(
|
||||
|
@ -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(
|
||||
`
|
||||
<button id="open-action-sheet">open</button>
|
||||
<ion-action-sheet trigger="open-action-sheet"></ion-action-sheet>
|
||||
<input id="test-input" />
|
||||
|
||||
<script>
|
||||
const actionSheet = document.querySelector('ion-action-sheet');
|
||||
|
||||
actionSheet.addEventListener('ionActionSheetWillDismiss', () => {
|
||||
requestAnimationFrame(() => {
|
||||
document.querySelector('#test-input').focus();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
`,
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user