mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-02 18:57:02 +08:00
fix(sheet): disable focus trap with string-based logic as well
This commit is contained in:
@ -98,7 +98,11 @@ export const createSheetGesture = (
|
||||
// Respect explicit opt-out of focus trapping/backdrop interactions
|
||||
// If focusTrap is false or showBackdrop is false, do not enable the backdrop or re-enable focus trap
|
||||
const el = baseEl as HTMLIonModalElement & { focusTrap?: boolean; showBackdrop?: boolean };
|
||||
if (el.focusTrap === false || el.showBackdrop === false) {
|
||||
const focusTrapAttr = el.getAttribute?.('focus-trap');
|
||||
const showBackdropAttr = el.getAttribute?.('show-backdrop');
|
||||
const focusTrapDisabled = el.focusTrap === false || focusTrapAttr === 'false';
|
||||
const backdropDisabled = el.showBackdrop === false || showBackdropAttr === 'false';
|
||||
if (focusTrapDisabled || backdropDisabled) {
|
||||
return;
|
||||
}
|
||||
baseEl.style.setProperty('pointer-events', 'auto');
|
||||
@ -241,10 +245,12 @@ export const createSheetGesture = (
|
||||
* ion-backdrop and .modal-wrapper always have pointer-events: auto
|
||||
* applied, so the modal content can still be interacted with.
|
||||
*/
|
||||
const shouldEnableBackdrop =
|
||||
currentBreakpoint > backdropBreakpoint &&
|
||||
(baseEl as HTMLIonModalElement & { focusTrap?: boolean }).focusTrap !== false &&
|
||||
(baseEl as HTMLIonModalElement & { showBackdrop?: boolean }).showBackdrop !== false;
|
||||
const modalEl = baseEl as HTMLIonModalElement & { focusTrap?: boolean; showBackdrop?: boolean };
|
||||
const focusTrapAttr = modalEl.getAttribute?.('focus-trap');
|
||||
const showBackdropAttr = modalEl.getAttribute?.('show-backdrop');
|
||||
const focusTrapDisabled = modalEl.focusTrap === false || focusTrapAttr === 'false';
|
||||
const backdropDisabled = modalEl.showBackdrop === false || showBackdropAttr === 'false';
|
||||
const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint && !focusTrapDisabled && !backdropDisabled;
|
||||
if (shouldEnableBackdrop) {
|
||||
enableBackdrop();
|
||||
} else {
|
||||
@ -591,10 +597,16 @@ export const createSheetGesture = (
|
||||
* Backdrop should become enabled
|
||||
* after the backdropBreakpoint value
|
||||
*/
|
||||
const modalEl = baseEl as HTMLIonModalElement & {
|
||||
focusTrap?: boolean;
|
||||
showBackdrop?: boolean;
|
||||
};
|
||||
const focusTrapAttr = modalEl.getAttribute?.('focus-trap');
|
||||
const showBackdropAttr = modalEl.getAttribute?.('show-backdrop');
|
||||
const focusTrapDisabled = modalEl.focusTrap === false || focusTrapAttr === 'false';
|
||||
const backdropDisabled = modalEl.showBackdrop === false || showBackdropAttr === 'false';
|
||||
const shouldEnableBackdrop =
|
||||
currentBreakpoint > backdropBreakpoint &&
|
||||
(baseEl as HTMLIonModalElement & { focusTrap?: boolean }).focusTrap !== false &&
|
||||
(baseEl as HTMLIonModalElement & { showBackdrop?: boolean }).showBackdrop !== false;
|
||||
currentBreakpoint > backdropBreakpoint && !focusTrapDisabled && !backdropDisabled;
|
||||
if (shouldEnableBackdrop) {
|
||||
enableBackdrop();
|
||||
} else {
|
||||
|
||||
@ -1237,6 +1237,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
const isHandleCycle = handleBehavior === 'cycle';
|
||||
const isSheetModalWithHandle = isSheetModal && showHandle;
|
||||
|
||||
const focusTrapAttr = this.el.getAttribute('focus-trap');
|
||||
return (
|
||||
<Host
|
||||
no-router
|
||||
@ -1253,7 +1254,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
[`modal-sheet`]: isSheetModal,
|
||||
[`modal-no-expand-scroll`]: isSheetModal && !expandToScroll,
|
||||
'overlay-hidden': true,
|
||||
[FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false,
|
||||
[FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false || focusTrapAttr === 'false',
|
||||
...getClassMap(this.cssClass),
|
||||
}}
|
||||
onIonBackdropTap={this.onBackdropTap}
|
||||
|
||||
@ -28,6 +28,18 @@ describe('modal: focus trap', () => {
|
||||
|
||||
expect(modal.classList.contains(FOCUS_TRAP_DISABLE_CLASS)).toBe(true);
|
||||
});
|
||||
it('should set the focus trap class when disabled via attribute string', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
html: `
|
||||
<ion-modal focus-trap="false"></ion-modal>
|
||||
`,
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
|
||||
expect(modal.classList.contains(FOCUS_TRAP_DISABLE_CLASS)).toBe(true);
|
||||
});
|
||||
it('should not set the focus trap class by default', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
|
||||
@ -687,6 +687,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
|
||||
const { onLifecycle, parentPopover, dismissOnSelect, side, arrow, htmlAttributes, focusTrap } = this;
|
||||
const desktop = isPlatform('desktop');
|
||||
const enableArrow = arrow && !parentPopover;
|
||||
const focusTrapAttr = this.el.getAttribute('focus-trap');
|
||||
|
||||
return (
|
||||
<Host
|
||||
@ -704,7 +705,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
|
||||
'overlay-hidden': true,
|
||||
'popover-desktop': desktop,
|
||||
[`popover-side-${side}`]: true,
|
||||
[FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false,
|
||||
[FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false || focusTrapAttr === 'false',
|
||||
'popover-nested': !!parentPopover,
|
||||
}}
|
||||
onIonPopoverDidPresent={onLifecycle}
|
||||
|
||||
@ -29,6 +29,18 @@ describe('popover: focus trap', () => {
|
||||
|
||||
expect(popover.classList.contains(FOCUS_TRAP_DISABLE_CLASS)).toBe(true);
|
||||
});
|
||||
it('should set the focus trap class when disabled via attribute string', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Popover],
|
||||
html: `
|
||||
<ion-popover focus-trap="false"></ion-popover>
|
||||
`,
|
||||
});
|
||||
|
||||
const popover = page.body.querySelector('ion-popover')!;
|
||||
|
||||
expect(popover.classList.contains(FOCUS_TRAP_DISABLE_CLASS)).toBe(true);
|
||||
});
|
||||
it('should not set the focus trap class by default', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Popover],
|
||||
|
||||
@ -539,11 +539,18 @@ export const present = async <OverlayPresentOptions>(
|
||||
* view container subtree, skip adding aria-hidden/inert there
|
||||
* to avoid disabling the overlay.
|
||||
*/
|
||||
const overlayEl = overlay.el as HTMLIonOverlayElement & { focusTrap?: boolean; showBackdrop?: boolean };
|
||||
const shouldTrapFocus = overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false;
|
||||
const overlayEl = overlay.el as HTMLIonOverlayElement & {
|
||||
focusTrap?: boolean;
|
||||
showBackdrop?: boolean;
|
||||
};
|
||||
const focusTrapAttr = overlayEl.getAttribute?.('focus-trap');
|
||||
const showBackdropAttr = overlayEl.getAttribute?.('show-backdrop');
|
||||
const focusTrapDisabled = overlayEl.focusTrap === false || focusTrapAttr === 'false';
|
||||
const backdropDisabled = overlayEl.showBackdrop === false || showBackdropAttr === 'false';
|
||||
const shouldTrapFocus = overlayEl.tagName !== 'ION-TOAST' && !focusTrapDisabled;
|
||||
// Only lock out root content when backdrop is active. Developers relying on showBackdrop=false
|
||||
// expect background interaction to remain enabled.
|
||||
const shouldLockRoot = shouldTrapFocus && overlayEl.showBackdrop !== false;
|
||||
const shouldLockRoot = shouldTrapFocus && !backdropDisabled;
|
||||
|
||||
overlay.presented = true;
|
||||
overlay.willPresent.emit();
|
||||
@ -681,11 +688,21 @@ export const dismiss = async <OverlayDismissOptions>(
|
||||
*/
|
||||
const overlaysLockingRoot = presentedOverlays.filter((o) => {
|
||||
const el = o as HTMLIonOverlayElement & { focusTrap?: boolean; showBackdrop?: boolean };
|
||||
return el.tagName !== 'ION-TOAST' && el.focusTrap !== false && el.showBackdrop !== false;
|
||||
const focusTrapAttr = el.getAttribute?.('focus-trap');
|
||||
const showBackdropAttr = el.getAttribute?.('show-backdrop');
|
||||
const focusTrapDisabled = el.focusTrap === false || focusTrapAttr === 'false';
|
||||
const backdropDisabled = el.showBackdrop === false || showBackdropAttr === 'false';
|
||||
return el.tagName !== 'ION-TOAST' && !focusTrapDisabled && !backdropDisabled;
|
||||
});
|
||||
const overlayEl = overlay.el as HTMLIonOverlayElement & { focusTrap?: boolean; showBackdrop?: boolean };
|
||||
const locksRoot =
|
||||
overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false && overlayEl.showBackdrop !== false;
|
||||
const overlayEl = overlay.el as HTMLIonOverlayElement & {
|
||||
focusTrap?: boolean;
|
||||
showBackdrop?: boolean;
|
||||
};
|
||||
const focusTrapAttr = overlayEl.getAttribute?.('focus-trap');
|
||||
const showBackdropAttr = overlayEl.getAttribute?.('show-backdrop');
|
||||
const focusTrapDisabled = overlayEl.focusTrap === false || focusTrapAttr === 'false';
|
||||
const backdropDisabled = overlayEl.showBackdrop === false || showBackdropAttr === 'false';
|
||||
const locksRoot = overlayEl.tagName !== 'ION-TOAST' && !focusTrapDisabled && !backdropDisabled;
|
||||
|
||||
/**
|
||||
* If this is the last visible overlay that is trapping focus
|
||||
|
||||
@ -37,6 +37,26 @@ describe('overlays: scroll blocking', () => {
|
||||
expect(body).not.toHaveClass('backdrop-no-scroll');
|
||||
});
|
||||
|
||||
it('should not block scroll when focus-trap attribute is set to "false"', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
html: `
|
||||
<ion-modal focus-trap="false"></ion-modal>
|
||||
`,
|
||||
});
|
||||
|
||||
const modal = page.body.querySelector('ion-modal')!;
|
||||
const body = page.doc.querySelector('body')!;
|
||||
|
||||
await modal.present();
|
||||
|
||||
expect(body).not.toHaveClass('backdrop-no-scroll');
|
||||
|
||||
await modal.dismiss();
|
||||
|
||||
expect(body).not.toHaveClass('backdrop-no-scroll');
|
||||
});
|
||||
|
||||
it('should not block scroll when the overlay is dismissed', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Modal],
|
||||
|
||||
Reference in New Issue
Block a user