From 68f0723a23f1de49e392e7ced1f6e4735060b299 Mon Sep 17 00:00:00 2001 From: Matt Driscoll Date: Wed, 15 Jan 2025 15:34:16 -0800 Subject: [PATCH] fix(tooltip, popover): honor prevented events (#11278) **Related Issue:** #11244 ## Summary - updates tooltip and popover to take no action on prevented events - adds tests - This allows users to prevent an event instead of stopping propagation on it. --- .../src/components/popover/PopoverManager.ts | 2 +- .../src/components/popover/popover.e2e.ts | 29 +++++ .../src/components/tooltip/TooltipManager.ts | 12 ++ .../src/components/tooltip/tooltip.e2e.ts | 114 ++++++++++++++++++ 4 files changed, 156 insertions(+), 1 deletion(-) diff --git a/packages/calcite-components/src/components/popover/PopoverManager.ts b/packages/calcite-components/src/components/popover/PopoverManager.ts index 00f3cda446e..b872f75af11 100644 --- a/packages/calcite-components/src/components/popover/PopoverManager.ts +++ b/packages/calcite-components/src/components/popover/PopoverManager.ts @@ -87,7 +87,7 @@ export default class PopoverManager { }; private clickHandler = (event: PointerEvent): void => { - if (isKeyboardTriggeredClick(event)) { + if (isKeyboardTriggeredClick(event) || event.defaultPrevented) { return; } diff --git a/packages/calcite-components/src/components/popover/popover.e2e.ts b/packages/calcite-components/src/components/popover/popover.e2e.ts index 14568e116ea..ab7c8425a08 100644 --- a/packages/calcite-components/src/components/popover/popover.e2e.ts +++ b/packages/calcite-components/src/components/popover/popover.e2e.ts @@ -368,6 +368,35 @@ describe("calcite-popover", () => { expect(await popover.getProperty("open")).toBe(true); }); + it("should not open popovers if event is prevented", async () => { + const page = await newE2EPage(); + + await page.setContent(html` + Content +
Button
+ `); + + await page.waitForChanges(); + + const popover = await page.find("calcite-popover"); + + expect(await popover.getProperty("open")).toBe(false); + + await page.$eval("#ref", (ref) => { + ref.addEventListener("click", (event) => { + event.preventDefault(); + }); + }); + + const referenceElement = await page.find("#ref"); + + await referenceElement.click(); + + await page.waitForChanges(); + + expect(await popover.getProperty("open")).toBe(false); + }); + it("should not be visible if reference is hidden", async () => { const page = await newE2EPage(); diff --git a/packages/calcite-components/src/components/tooltip/TooltipManager.ts b/packages/calcite-components/src/components/tooltip/TooltipManager.ts index 1c5539ac0be..2f5f68ee73d 100644 --- a/packages/calcite-components/src/components/tooltip/TooltipManager.ts +++ b/packages/calcite-components/src/components/tooltip/TooltipManager.ts @@ -97,6 +97,10 @@ export default class TooltipManager { }; private pointerMoveHandler = (event: PointerEvent): void => { + if (event.defaultPrevented) { + return; + } + const composedPath = event.composedPath(); const { activeTooltip } = this; @@ -129,6 +133,10 @@ export default class TooltipManager { } private clickHandler = (event: Event): void => { + if (event.defaultPrevented) { + return; + } + this.clickedTooltip = null; const composedPath = event.composedPath(); const tooltip = this.queryTooltip(composedPath); @@ -160,6 +168,10 @@ export default class TooltipManager { }; private focusInHandler = (event: FocusEvent): void => { + if (event.defaultPrevented) { + return; + } + const composedPath = event.composedPath(); const tooltip = this.queryTooltip(composedPath); diff --git a/packages/calcite-components/src/components/tooltip/tooltip.e2e.ts b/packages/calcite-components/src/components/tooltip/tooltip.e2e.ts index db017596b8d..99db9e68c6c 100644 --- a/packages/calcite-components/src/components/tooltip/tooltip.e2e.ts +++ b/packages/calcite-components/src/components/tooltip/tooltip.e2e.ts @@ -320,6 +320,34 @@ describe("calcite-tooltip", () => { expect(await positionContainer.isVisible()).toBe(true); }); + it("should not open when hover event is prevented", async () => { + const page = await newE2EPage(); + + await page.setContent( + `content
referenceElement
`, + ); + + await page.waitForChanges(); + + const positionContainer = await page.find(`calcite-tooltip >>> .${CSS.positionContainer}`); + + expect(await positionContainer.isVisible()).toBe(false); + + await page.$eval("#ref", (ref) => { + ref.addEventListener("pointermove", (event) => { + event.preventDefault(); + }); + }); + + const ref = await page.find("#ref"); + + await ref.hover(); + + await page.waitForTimeout(TOOLTIP_OPEN_DELAY_MS); + + expect(await positionContainer.isVisible()).toBe(false); + }); + it("should honor hover interaction with span inside", async () => { const page = await newE2EPage(); @@ -426,6 +454,34 @@ describe("calcite-tooltip", () => { expect(await tooltip.getProperty("open")).toBe(false); }); + it("should not open if focus event is prevented", async () => { + const page = await newE2EPage(); + + await page.setContent(html` + + Content + + `); + + await page.waitForChanges(); + + const tooltip = await page.find("calcite-tooltip"); + + expect(await tooltip.getProperty("open")).toBe(false); + + await page.$eval("#ref", (ref) => { + ref.addEventListener("focusin", (event) => { + event.preventDefault(); + }); + + ref.dispatchEvent(new FocusEvent("focusin", { bubbles: true, cancelable: true })); + }); + + await page.waitForChanges(); + + expect(await tooltip.getProperty("open")).toBe(false); + }); + it("should handle mouse events", async () => { const page = await newE2EPage(); @@ -547,6 +603,45 @@ describe("calcite-tooltip", () => { await assertEscapeKeyCanceled(page, true); }); + it("should not close with ESC key if event is prevented", async () => { + const page = await newE2EPage(); + + await page.setContent(html` + Content + + `); + + await page.waitForChanges(); + + const tooltip = await page.find("calcite-tooltip"); + + expect(await tooltip.getProperty("open")).toBe(false); + + const referenceElement = await page.find("#ref"); + + await referenceElement.focus(); + + await referenceElement.hover(); + + await page.waitForTimeout(TOOLTIP_OPEN_DELAY_MS); + + await page.waitForChanges(); + + expect(await tooltip.getProperty("open")).toBe(true); + + await page.evaluate(() => { + document.body.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + event.preventDefault(); + } + }); + }); + + await dispatchKeydownEvent(page, "#ref", "Escape"); + + expect(await tooltip.getProperty("open")).toBe(true); + }); + it("should only open the last focused tooltip", async () => { const page = await newE2EPage(); @@ -1175,6 +1270,25 @@ describe("calcite-tooltip", () => { expect(await tooltip.getProperty("open")).toBe(false); }); + it("should not open when click event is prevented", async () => { + const page = await newE2EPage(); + await page.setContent(pageContent); + await skipAnimations(page); + await page.waitForChanges(); + const tooltip = await page.find("calcite-tooltip"); + + await page.$eval("#ref", (ref) => { + ref.addEventListener("click", (event) => { + event.preventDefault(); + }); + + ref.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true })); + }); + + await page.waitForChanges(); + expect(await tooltip.getProperty("open")).toBe(false); + }); + it("should work when focusing on a reference element first", async () => { const page = await newE2EPage(); await page.setContent(pageContent);