Skip to content

Commit

Permalink
fix(tooltip): prevent closing of Esc-key-closing parent components wh…
Browse files Browse the repository at this point in the history
…en dismissing a tooltip with Esc (#6343)

**Related Issue:** #6292

## Summary

This updates tooltip manager to cancel the Esc key press if handled to
prevent closing parent components that also close with Esc.

Additionally, extracts a utility to get the tooltip's effective
reference element.
  • Loading branch information
jcfranco committed Feb 8, 2023
1 parent 7408d53 commit b4cbf54
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 17 deletions.
15 changes: 11 additions & 4 deletions src/components/tooltip/TooltipManager.ts
@@ -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
@@ -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
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
@@ -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
);
}

0 comments on commit b4cbf54

Please sign in to comment.