Skip to content

fix(overlays): do not return focus if application has already moved focus manually #28850

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 13, 2024

Conversation

aeharding
Copy link
Contributor

@aeharding aeharding commented Jan 18, 2024

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
  • 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
RPReplay_Final1705547611.MP4
RPReplay_Final1705547768.MP4
@aeharding aeharding requested a review from a team as a code owner January 18, 2024 03:18
@aeharding aeharding requested review from mapsandapps and removed request for a team January 18, 2024 03:18
@github-actions github-actions bot added the package: core @ionic/core package label Jan 18, 2024
@aeharding aeharding changed the title feat(overlay): do not restore focus on dismiss if already set feat(overlay): restore focus on dismiss if unset Jan 18, 2024
aeharding added a commit to aeharding/voyager that referenced this pull request Jan 19, 2024
aeharding added a commit to aeharding/voyager that referenced this pull request Jan 19, 2024
@liamdebeasi liamdebeasi self-requested a review February 7, 2024 15:07
@liamdebeasi liamdebeasi changed the title feat(overlay): restore focus on dismiss if unset fix(overlays): do not return focus if application has already moved focus manually Feb 7, 2024
Copy link
Contributor

@liamdebeasi liamdebeasi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good! A few changes for tests and maintainability.

* action sheet) then don't restore focus
* to previous element
*/
if (document.activeElement === null || document.activeElement === document.body) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need a test for this. Something like the following should suffice:

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(); });

You can put this in src/utils/test/overlays/overlays.e2e.ts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done thanks!

* action sheet) then don't restore focus
* to previous element
*/
if (document.activeElement === null || document.activeElement === document.body) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add a comment that explains focus is always moved to the body when the overlay is dismissed? Maintainers may not know why we specifically check the body here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some MDN references. Apologies if its now a bit too wordy, feel free to make further suggestions!

Copy link
Contributor

@liamdebeasi liamdebeasi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@liamdebeasi liamdebeasi added this pull request to the merge queue Feb 13, 2024
Merged via the queue into ionic-team:main with commit a016670 Feb 13, 2024
@aeharding aeharding deleted the custom-focus-restore branch February 14, 2024 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package: core @ionic/core package
2 participants