Skip to content

fix(tooltip): prevent closing of Esc-key-closing parent components when dismissing a tooltip with Esc #6343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/components/tooltip/TooltipManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isPrimaryPointerButton } from "../../utils/dom";
import { ReferenceElement } from "../../utils/floating-ui";
import { TOOLTIP_DELAY_MS } from "./resources";
import { getEffectiveReferenceElement } from "./utils";

export default class TooltipManager {
// --------------------------------------------------------------------------
Expand Down Expand Up @@ -60,12 +61,18 @@ export default class TooltipManager {
};

private keyDownHandler = (event: KeyboardEvent): void => {
if (event.key === "Escape") {
if (event.key === "Escape" && !event.defaultPrevented) {
const { activeTooltipEl } = this;

if (activeTooltipEl) {
if (activeTooltipEl && activeTooltipEl.open) {
this.clearHoverTimeout();
this.toggleTooltip(activeTooltipEl, false);

const referenceElement = getEffectiveReferenceElement(activeTooltipEl);

if (referenceElement instanceof Element && referenceElement.contains(event.target as HTMLElement)) {
event.preventDefault();
}
}
}
};
Expand Down Expand Up @@ -119,15 +126,15 @@ export default class TooltipManager {
};

private addListeners(): void {
document.addEventListener("keydown", this.keyDownHandler);
document.addEventListener("keydown", this.keyDownHandler, { capture: true });
document.addEventListener("pointermove", this.pointerMoveHandler, { capture: true });
document.addEventListener("pointerdown", this.pointerDownHandler, { capture: true });
document.addEventListener("focusin", this.focusInHandler, { capture: true });
document.addEventListener("focusout", this.focusOutHandler, { capture: true });
}

private removeListeners(): void {
document.removeEventListener("keydown", this.keyDownHandler);
document.removeEventListener("keydown", this.keyDownHandler, { capture: true });
document.removeEventListener("pointermove", this.pointerMoveHandler, { capture: true });
document.removeEventListener("pointerdown", this.pointerDownHandler, { capture: true });
document.removeEventListener("focusin", this.focusInHandler, { capture: true });
Expand Down
33 changes: 32 additions & 1 deletion src/components/tooltip/tooltip.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import { newE2EPage } from "@stencil/core/testing";
import { E2EPage, newE2EPage } from "@stencil/core/testing";
import { TOOLTIP_DELAY_MS } from "../tooltip/resources";
import { accessible, defaults, hidden, floatingUIOwner, renders } from "../../tests/commonTests";
import { html } from "../../../support/formatting";
import { GlobalTestProps } from "../../tests/utils";

describe("calcite-tooltip", () => {
type CanceledEscapeKeyPressTestWindow = GlobalTestProps<{
escapeKeyCanceled: boolean;
}>;

/**
* Helps assert the canceled Esc key press when closing tooltips
* Must be called before the tooltip is closed via keyboard.
*/
async function setUpEscapeKeyCancelListener(page: E2EPage): Promise<void> {
await page.evaluate(() => {
document.addEventListener(
"keydown",
(event) => {
(window as CanceledEscapeKeyPressTestWindow).escapeKeyCanceled = event.defaultPrevented;
},
{ once: true }
);
});
}

async function assertEscapeKeyCanceled(page: E2EPage, expected: boolean): Promise<void> {
expect(await page.evaluate(() => (window as CanceledEscapeKeyPressTestWindow).escapeKeyCanceled)).toBe(expected);
}

it("renders", async () => {
await renders(`calcite-tooltip`, { visible: false, display: "block" });
await renders(`<calcite-tooltip open reference-element="ref"></calcite-tooltip><div id="ref">😄</div>`, {
Expand Down Expand Up @@ -351,11 +376,13 @@ describe("calcite-tooltip", () => {

expect(await tooltip.getProperty("open")).toBe(true);

await setUpEscapeKeyCancelListener(page);
await referenceElement.press("Escape");

await page.waitForChanges();

expect(await tooltip.getProperty("open")).toBe(false);
await assertEscapeKeyCanceled(page, true);
});

it("should honor hovered tooltip closing with ESC key", async () => {
Expand Down Expand Up @@ -384,11 +411,13 @@ describe("calcite-tooltip", () => {

expect(await tooltip.getProperty("open")).toBe(true);

await setUpEscapeKeyCancelListener(page);
await page.keyboard.press("Escape");

await page.waitForChanges();

expect(await tooltip.getProperty("open")).toBe(false);
await assertEscapeKeyCanceled(page, false);
});

it("should honor hovered and focused tooltip closing with ESC key", async () => {
Expand Down Expand Up @@ -419,11 +448,13 @@ describe("calcite-tooltip", () => {

expect(await tooltip.getProperty("open")).toBe(true);

await setUpEscapeKeyCancelListener(page);
await page.keyboard.press("Escape");

await page.waitForChanges();

expect(await tooltip.getProperty("open")).toBe(false);
await assertEscapeKeyCanceled(page, true);
});

it("should only open the last focused tooltip", async () => {
Expand Down
15 changes: 3 additions & 12 deletions src/components/tooltip/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
VNode,
Watch
} from "@stencil/core";
import { queryElementRoots, toAriaBoolean } from "../../utils/dom";
import { toAriaBoolean } from "../../utils/dom";
import {
connectFloatingUI,
defaultOffsetDistance,
Expand All @@ -33,6 +33,7 @@ import {
import { ARIA_DESCRIBED_BY, CSS } from "./resources";

import TooltipManager from "./TooltipManager";
import { getEffectiveReferenceElement } from "./utils";

const manager = new TooltipManager();

Expand Down Expand Up @@ -264,7 +265,7 @@ export class Tooltip implements FloatingUIComponent, OpenCloseComponent {

setUpReferenceElement = (warn = true): void => {
this.removeReferences();
this.effectiveReferenceElement = this.getReferenceElement();
this.effectiveReferenceElement = getEffectiveReferenceElement(this.el);
connectFloatingUI(this, this.effectiveReferenceElement, this.el);

const { el, referenceElement, effectiveReferenceElement } = this;
Expand Down Expand Up @@ -311,16 +312,6 @@ export class Tooltip implements FloatingUIComponent, OpenCloseComponent {
manager.unregisterElement(effectiveReferenceElement);
};

getReferenceElement(): ReferenceElement {
const { referenceElement, el } = this;

return (
(typeof referenceElement === "string"
? queryElementRoots(el, { id: referenceElement })
: referenceElement) || null
);
}

// --------------------------------------------------------------------------
//
// Render Methods
Expand Down
11 changes: 11 additions & 0 deletions src/components/tooltip/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ReferenceElement } from "../../utils/floating-ui";
import { queryElementRoots } from "../../utils/dom";

export function getEffectiveReferenceElement(tooltip: HTMLCalciteTooltipElement): ReferenceElement {
const { referenceElement } = tooltip;

return (
(typeof referenceElement === "string" ? queryElementRoots(tooltip, { id: referenceElement }) : referenceElement) ||
null
);
}