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 => {