Skip to content

Commit

Permalink
feat(popover): Add focus-trap to popover and disableFocusTrap propert…
Browse files Browse the repository at this point in the history
…y. (#5725)

* refactor(modal): Update modal to use focus-trap module.

* cleanup

* cleanup

* fix test

* cleanup

* cleanup

* remove `previousActiveElement` internal prop. focus utility handles this already.

* set fallbackFocus element.

* feat(popover): Add focus-trap to popover. #2133

* cleanup

* cleanup

* cleanup

* add disableFocusTrap prop to popover

* review feedback

* add spec test

* revert changes

* add popover tests for setFocus
  • Loading branch information
driskull authored Nov 18, 2022
1 parent 64b5675 commit a8ef353
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 5 deletions.
3 changes: 2 additions & 1 deletion src/components/action-menu/action-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,8 @@ export class ActionMenu {

return (
<calcite-popover
disablePointer
disableFocusTrap={true}
disablePointer={true}
flipPlacements={flipPlacements}
label={label}
offsetDistance={0}
Expand Down
1 change: 1 addition & 0 deletions src/components/input-time-picker/input-time-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ export class InputTimePicker
/>
</div>
<calcite-popover
disableFocusTrap={true}
id={popoverId}
label="Time Picker"
open={this.open}
Expand Down
23 changes: 22 additions & 1 deletion src/components/popover/popover.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { newE2EPage } from "@stencil/core/testing";
import { html } from "../../../support/formatting";

import { accessible, defaults, hidden, renders, floatingUIOwner } from "../../tests/commonTests";
import { accessible, defaults, hidden, renders, floatingUIOwner, focusable } from "../../tests/commonTests";
import { CSS } from "./resources";

describe("calcite-popover", () => {
Expand Down Expand Up @@ -765,4 +765,25 @@ describe("calcite-popover", () => {

expect(await popover.getProperty("open")).toBe(false);
});

describe("setFocus", () => {
const createPopoverHTML = (contentHTML?: string, attrs?: string) =>
`<calcite-popover open ${attrs} reference-element="ref">${contentHTML}</calcite-popover><button id="ref">Button</button>`;

const closeButtonFocusId = "close-button";

const contentButtonClass = "my-button";
const contentHTML = `<button class="${contentButtonClass}">My Button</button>`;

it("should focus content by default", async () =>
focusable(createPopoverHTML(contentHTML), {
focusTargetSelector: `.${contentButtonClass}`
}));

it("should focus close button", async () =>
focusable(createPopoverHTML(contentHTML, "closable"), {
focusId: closeButtonFocusId,
shadowFocusTargetSelector: `.${CSS.closeButton}`
}));
});
});
29 changes: 26 additions & 3 deletions src/components/popover/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ import {
reposition,
updateAfterClose
} from "../../utils/floating-ui";
import {
FocusTrapComponent,
FocusTrap,
connectFocusTrap,
activateFocusTrap,
deactivateFocusTrap
} from "../../utils/focusTrapComponent";

import { guid } from "../../utils/guid";
import { queryElementRoots, toAriaBoolean } from "../../utils/dom";
Expand All @@ -50,7 +57,7 @@ const manager = new PopoverManager();
styleUrl: "popover.scss",
shadow: true
})
export class Popover implements FloatingUIComponent, OpenCloseComponent {
export class Popover implements FloatingUIComponent, OpenCloseComponent, FocusTrapComponent {
// --------------------------------------------------------------------------
//
// Properties
Expand Down Expand Up @@ -94,6 +101,11 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent {
*/
@Prop({ reflect: true }) disableFlip = false;

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

/**
* When `true`, removes the caret pointer.
*/
Expand Down Expand Up @@ -240,6 +252,10 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent {

hasLoaded = false;

focusTrap: FocusTrap;

focusTrapEl: HTMLDivElement;

// --------------------------------------------------------------------------
//
// Lifecycle
Expand Down Expand Up @@ -271,6 +287,7 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent {
this.removeReferences();
disconnectFloatingUI(this, this.effectiveReferenceElement, this.el);
disconnectOpenCloseComponent(this);
deactivateFocusTrap(this);
}

//--------------------------------------------------------------------------
Expand Down Expand Up @@ -350,7 +367,7 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent {
return;
}

this.el?.focus();
activateFocusTrap(this);
}

/**
Expand All @@ -369,9 +386,11 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent {
//
// --------------------------------------------------------------------------

private setTransitionEl = (el): void => {
private setTransitionEl = (el: HTMLDivElement): void => {
this.transitionEl = el;
connectOpenCloseComponent(this);
this.focusTrapEl = el;
connectFocusTrap(this);
};

setFilteredPlacements = (): void => {
Expand Down Expand Up @@ -465,6 +484,9 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent {

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

onBeforeClose(): void {
Expand All @@ -473,6 +495,7 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent {

onClose(): void {
this.calcitePopoverClose.emit();
deactivateFocusTrap(this);
}

storeArrowEl = (el: HTMLDivElement): void => {
Expand Down

0 comments on commit a8ef353

Please sign in to comment.