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

@ -1,6 +1,6 @@
describe('IonModal', () => {
beforeEach(() => {
cy.visit('/overlay-components/modal');
cy.visit('/overlay-components/modal-basic');
});
it('display modal', () => {

View File

@ -0,0 +1,36 @@
describe('IonModal: focusTrap regression', () => {
beforeEach(() => {
cy.visit('/overlay-components/modal-focus-trap');
});
it('should allow interacting with background when focusTrap=false', () => {
cy.get('#open-non-trapped-modal').click();
cy.get('ion-modal').should('be.visible');
cy.get('#background-action').click();
cy.get('#background-action-count').should('have.text', '1');
});
it('should prevent interacting with background when focusTrap=true', () => {
cy.get('#open-trapped-modal').click();
cy.get('ion-modal').should('be.visible');
// Ensure backdrop is active and capturing pointer events
cy.get('ion-backdrop').should('exist');
cy.get('ion-backdrop').should('have.css', 'pointer-events', 'auto');
// Baseline: counter is 0
cy.get('#background-action-count').should('have.text', '0');
// Click the center of the background button via body coordinates (topmost element will receive it)
cy.get('#background-action').then(($btn) => {
const rect = $btn[0].getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
cy.get('body').click(x, y);
});
// Counter should remain unchanged
cy.get('#background-action-count').should('have.text', '0');
});
});

View File

@ -0,0 +1,27 @@
describe('IonModal: inline teleport with showBackdrop=false', () => {
beforeEach(() => {
cy.visit('/overlay-components/modal-teleport');
});
it('should render and remain interactive when appended into a page container', () => {
cy.get('#open-teleport-modal').click();
cy.get('ion-modal').should('be.visible');
// Verify modal content is interactable: close button should dismiss the modal
cy.get('#close-teleport-modal').click();
cy.get('ion-modal').should('not.exist');
});
it('should allow background interaction when showBackdrop=false', () => {
cy.get('#open-teleport-modal').click();
cy.get('ion-modal').should('be.visible');
// Ensure the background button is clickable while modal is open
cy.get('#teleport-background-action').click();
cy.get('#teleport-background-action-count').should('have.text', '1');
// Cleanup
cy.get('#close-teleport-modal').click();
cy.get('ion-modal').should('not.exist');
});
});

View File

@ -1,7 +1,7 @@
describe('keepContentsMounted', () => {
describe('modal', () => {
it('should not mount component if false', () => {
cy.visit('/overlay-components/modal');
cy.visit('/overlay-components/modal-basic');
cy.get('ion-modal ion-content').should('not.exist');
});