fix(modal): allow sheet modals to skip focus trap (#30689)

Issue number: resolves #30684

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->
Recently, we [fixed some issues with aria-hidden in
modals](https://github.com/ionic-team/ionic-framework/pull/30563),
unfortunately at this time we neglected modals that opt out of focus
trapping. As a result, a lot of modals that disable focus trapping still
have it happening and it doesn't get cleaned up properly on dismiss.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->
We're now properly checking for and skipping focus traps on modals that
do not want them.

## Does this introduce a breaking change?

- [ ] Yes
- [X] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

I created regression tests for Angular in this to prevent this from
happening again. I initially tried to do this with core, but the issue
doesn't seem to reproduce with core.

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Current dev build:
```
 8.7.5-dev.11758652700.103435a3
```
This commit is contained in:
Shane
2025-09-24 12:29:58 -07:00
committed by GitHub
parent 5a06503d4a
commit a40d957ad9
35 changed files with 1094 additions and 25 deletions

View File

@ -0,0 +1,38 @@
import { expect, test } from '@playwright/test';
test.describe('Modals: Dynamic Wrapper', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/lazy/modal-dynamic-wrapper');
});
test('should render dynamic component inside modal', async ({ page }) => {
await page.locator('#open-dynamic-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
await expect(page.locator('#dynamic-component-loaded')).toBeVisible();
});
test('should allow interacting with background content while sheet is open', async ({ page }) => {
await page.locator('#open-dynamic-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
await page.locator('#background-action').click();
await expect(page.locator('#background-action-count')).toHaveText('1');
});
test('should prevent interacting with background content when focus is trapped', async ({ page }) => {
await page.locator('#open-focused-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
// Attempt to click the background button via coordinates; click should be intercepted by backdrop
const box = await page.locator('#background-action').boundingBox();
if (box) {
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
}
await expect(page.locator('#background-action-count')).toHaveText('0');
});
});

View File

@ -0,0 +1,34 @@
import { expect, test } from '@playwright/test';
test.describe('Modals: Inline Sheet', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/lazy/modal-sheet-inline');
});
test('should open inline sheet modal', async ({ page }) => {
await page.locator('#present-inline-sheet-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
await expect(page.locator('#current-breakpoint')).toHaveText('0.2');
await expect(page.locator('ion-modal ion-item')).toHaveCount(4);
});
test('should expand to 0.75 breakpoint when searchbar is clicked', async ({ page }) => {
await page.locator('#present-inline-sheet-modal').click();
await expect(page.locator('#current-breakpoint')).toHaveText('0.2');
await page.locator('ion-modal ion-searchbar').click();
await expect(page.locator('#current-breakpoint')).toHaveText('0.75');
});
test('should allow interacting with background content while sheet is open', async ({ page }) => {
await page.locator('#present-inline-sheet-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
await page.locator('#background-action').click();
await expect(page.locator('#background-action-count')).toHaveText('1');
});
});

View File

@ -0,0 +1,38 @@
import { expect, test } from '@playwright/test';
test.describe('Modals: Dynamic Wrapper (standalone)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/standalone/modal-dynamic-wrapper');
});
test('should render dynamic component inside modal', async ({ page }) => {
await page.locator('#open-dynamic-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
await expect(page.locator('#dynamic-component-loaded')).toBeVisible();
});
test('should allow interacting with background content while sheet is open', async ({ page }) => {
await page.locator('#open-dynamic-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
await page.locator('#background-action').click();
await expect(page.locator('#background-action-count')).toHaveText('1');
});
test('should prevent interacting with background content when focus is trapped', async ({ page }) => {
await page.locator('#open-focused-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
// Attempt to click the background button via coordinates; click should be intercepted by backdrop
const box = await page.locator('#background-action').boundingBox();
if (box) {
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
}
await expect(page.locator('#background-action-count')).toHaveText('0');
});
});

View File

@ -0,0 +1,34 @@
import { expect, test } from '@playwright/test';
test.describe('Modals: Inline Sheet (standalone)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/standalone/modal-sheet-inline');
});
test('should open inline sheet modal', async ({ page }) => {
await page.locator('#present-inline-sheet-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
await expect(page.locator('#current-breakpoint')).toHaveText('0.2');
await expect(page.locator('ion-modal ion-item')).toHaveCount(4);
});
test('should expand to 0.75 breakpoint when searchbar is clicked', async ({ page }) => {
await page.locator('#present-inline-sheet-modal').click();
await expect(page.locator('#current-breakpoint')).toHaveText('0.2');
await page.locator('ion-modal ion-searchbar').click();
await expect(page.locator('#current-breakpoint')).toHaveText('0.75');
});
test('should allow interacting with background content while sheet is open', async ({ page }) => {
await page.locator('#present-inline-sheet-modal').click();
await expect(page.locator('ion-modal')).toBeVisible();
await page.locator('#background-action').click();
await expect(page.locator('#background-action-count')).toHaveText('1');
});
});