Skip to content

Commit

Permalink
fix(modal, popover): Add disableFocusTrap property to toggle focus …
Browse files Browse the repository at this point in the history
…trapping. (#5965)

fix(modal, popover): Add `disableFocusTrap` property to toggle focus
trapping.
  • Loading branch information
driskull committed Dec 9, 2022
1 parent aaef465 commit 7ee9e16
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 18 deletions.
20 changes: 18 additions & 2 deletions src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
FocusTrap,
connectFocusTrap,
activateFocusTrap,
deactivateFocusTrap
deactivateFocusTrap,
focusFirstTabbable
} from "../../utils/focusTrapComponent";
import {
setUpLoadableComponent,
Expand Down Expand Up @@ -83,6 +84,20 @@ export class Modal
/** When `true`, disables the component's close button. */
@Prop({ reflect: true }) disableCloseButton = false;

/**
* When `true`, prevents focus trapping.
*/
@Prop({ reflect: true }) disableFocusTrap = false;

@Watch("disableFocusTrap")
handleDisableFocusTrap(disableFocusTrap: boolean): void {
if (!this.open) {
return;
}

disableFocusTrap ? deactivateFocusTrap(this) : activateFocusTrap(this);
}

/** When `true`, disables the closing of the component when clicked outside. */
@Prop({ reflect: true }) disableOutsideClose = false;

Expand Down Expand Up @@ -363,7 +378,7 @@ export class Modal
return focusElement(closeButtonEl);
}

activateFocusTrap(this);
focusFirstTabbable(this);
}

/**
Expand Down Expand Up @@ -404,6 +419,7 @@ export class Modal
onOpen(): void {
this.transitionEl.classList.remove(CSS.openingIdle, CSS.openingActive);
this.calciteModalOpen.emit();
activateFocusTrap(this);
}

onBeforeClose(): void {
Expand Down
18 changes: 13 additions & 5 deletions src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import {
FocusTrap,
connectFocusTrap,
activateFocusTrap,
deactivateFocusTrap
deactivateFocusTrap,
focusFirstTabbable
} from "../../utils/focusTrapComponent";

import { guid } from "../../utils/guid";
Expand Down Expand Up @@ -91,6 +92,15 @@ export class Popover
*/
@Prop({ reflect: true }) disableFocusTrap = false;

@Watch("disableFocusTrap")
handleDisableFocusTrap(disableFocusTrap: boolean): void {
if (!this.open) {
return;
}

disableFocusTrap ? deactivateFocusTrap(this) : activateFocusTrap(this);
}

/**
* When `true`, removes the caret pointer.
*/
Expand Down Expand Up @@ -350,7 +360,7 @@ export class Popover
return;
}

activateFocusTrap(this);
focusFirstTabbable(this);
}

/**
Expand Down Expand Up @@ -467,9 +477,7 @@ export class Popover

onOpen(): void {
this.calcitePopoverOpen.emit();
if (!this.disableFocusTrap) {
activateFocusTrap(this);
}
activateFocusTrap(this);
}

onBeforeClose(): void {
Expand Down
46 changes: 35 additions & 11 deletions src/utils/focusTrapComponent.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { FocusTrap as _FocusTrap, Options as FocusTrapOptions, createFocusTrap } from "focus-trap";
import {
FocusTrap as _FocusTrap,
Options as FocusTrapOptions,
createFocusTrap,
FocusTrapTabbableOptions
} from "focus-trap";
import { FocusableElement, focusElement } from "./dom";
import { tabbable } from "tabbable";

const tabbableOptions: FocusTrapTabbableOptions = {
getShadowRoot: true
};

const trapStack: _FocusTrap[] = [];

/**
* Defines interface for components with a focus trap.
*/
export interface FocusTrapComponent {
/**
* When `true`, prevents focus trapping.
*/
disableFocusTrap: boolean;

/**
* The focus trap instance.
*/
Expand All @@ -21,9 +36,9 @@ export interface FocusTrapComponent {
export type FocusTrap = _FocusTrap;

/**
* Helper to set up focus trap component.
* Helper to set up the FocusTrap component.
*
* @param component
* @param {FocusTrapComponent} component The FocusTrap component.
*/
export function connectFocusTrap(component: FocusTrapComponent): void {
const { focusTrapEl } = component;
Expand All @@ -46,29 +61,38 @@ export function connectFocusTrap(component: FocusTrapComponent): void {
focusElement(el as FocusableElement);
return false;
},
tabbableOptions: {
getShadowRoot: true
},
tabbableOptions,
trapStack
};

component.focusTrap = createFocusTrap(focusTrapEl, focusTrapOptions);
}

/**
* Helper to activate focus trap component.
* Helper to activate the FocusTrap component.
*
* @param component
* @param {FocusTrapComponent} component The FocusTrap component.
*/
export function activateFocusTrap(component: FocusTrapComponent): void {
component.focusTrap?.activate();
if (!component.disableFocusTrap) {
component.focusTrap?.activate();
}
}

/**
* Helper to tear deactivate focus trap component.
* Helper to deactivate the FocusTrap component.
*
* @param component
* @param {FocusTrapComponent} component The FocusTrap component.
*/
export function deactivateFocusTrap(component: FocusTrapComponent): void {
component.focusTrap?.deactivate();
}

/**
* Helper to focus the first tabbable element within the FocusTrap component.
*
* @param {FocusTrapComponent} component The FocusTrap component.
*/
export function focusFirstTabbable(component: FocusTrapComponent): void {
tabbable(component.focusTrapEl, tabbableOptions)[0]?.focus();
}

0 comments on commit 7ee9e16

Please sign in to comment.