diff --git a/src/components/action-menu/action-menu.tsx b/src/components/action-menu/action-menu.tsx index c7d2b7a4ca8..06006e91c70 100755 --- a/src/components/action-menu/action-menu.tsx +++ b/src/components/action-menu/action-menu.tsx @@ -286,7 +286,8 @@ export class ActionMenu { return ( { @@ -765,4 +765,25 @@ describe("calcite-popover", () => { expect(await popover.getProperty("open")).toBe(false); }); + + describe("setFocus", () => { + const createPopoverHTML = (contentHTML?: string, attrs?: string) => + `${contentHTML}`; + + const closeButtonFocusId = "close-button"; + + const contentButtonClass = "my-button"; + const contentHTML = ``; + + 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}` + })); + }); }); diff --git a/src/components/popover/popover.tsx b/src/components/popover/popover.tsx index c39ecde6036..deb9be2388a 100644 --- a/src/components/popover/popover.tsx +++ b/src/components/popover/popover.tsx @@ -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"; @@ -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 @@ -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. */ @@ -240,6 +252,10 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent { hasLoaded = false; + focusTrap: FocusTrap; + + focusTrapEl: HTMLDivElement; + // -------------------------------------------------------------------------- // // Lifecycle @@ -271,6 +287,7 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent { this.removeReferences(); disconnectFloatingUI(this, this.effectiveReferenceElement, this.el); disconnectOpenCloseComponent(this); + deactivateFocusTrap(this); } //-------------------------------------------------------------------------- @@ -350,7 +367,7 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent { return; } - this.el?.focus(); + activateFocusTrap(this); } /** @@ -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 => { @@ -465,6 +484,9 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent { onOpen(): void { this.calcitePopoverOpen.emit(); + if (!this.disableFocusTrap) { + activateFocusTrap(this); + } } onBeforeClose(): void { @@ -473,6 +495,7 @@ export class Popover implements FloatingUIComponent, OpenCloseComponent { onClose(): void { this.calcitePopoverClose.emit(); + deactivateFocusTrap(this); } storeArrowEl = (el: HTMLDivElement): void => {