Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
// @ts-strict-ignore
import { ReferenceElement } from "../../utils/floating-ui";
import { isActivationKey } from "../../utils/key";
import { isKeyboardTriggeredClick } from "../../utils/dom";
import { isKeyboardTriggeredClick, isPrimaryPointerButton } from "../../utils/dom";
import type { Popover } from "./popover";

const clickTolerance = 5;

export function isDrag({
startX,
startY,
endX,
endY,
}: {
startX: number;
startY: number;
endX: number;
endY: number;
}): boolean {
const distance = Math.hypot(endX - startX, endY - startY);
return distance > clickTolerance;
}

export default class PopoverManager {
// --------------------------------------------------------------------------
//
Expand All @@ -15,6 +32,8 @@ export default class PopoverManager {

private registeredElementCount = 0;

private pointerDownPosition?: { x: number; y: number };

// --------------------------------------------------------------------------
//
// Public Methods
Expand Down Expand Up @@ -86,20 +105,43 @@ export default class PopoverManager {
}
};

private pointerDownHandler = (event: PointerEvent): void => {
if (event.defaultPrevented || !isPrimaryPointerButton(event)) {
return;
}

const { clientX, clientY } = event;
this.pointerDownPosition = { x: clientX, y: clientY };
};

private clickHandler = (event: PointerEvent): void => {
if (isKeyboardTriggeredClick(event) || event.defaultPrevented || window.getSelection()?.type === "Range") {
if (
isKeyboardTriggeredClick(event) ||
event.defaultPrevented ||
(this.pointerDownPosition &&
isDrag({
endY: event.clientY,
endX: event.clientX,
startY: this.pointerDownPosition.y,
startX: this.pointerDownPosition.x,
}))
) {
return;
}

this.pointerDownPosition = undefined;

this.togglePopovers(event);
};

private addListeners(): void {
window.addEventListener("pointerdown", this.pointerDownHandler);
window.addEventListener("click", this.clickHandler);
window.addEventListener("keydown", this.keyDownHandler);
}

private removeListeners(): void {
window.removeEventListener("pointerdown", this.pointerDownHandler);
window.removeEventListener("click", this.clickHandler);
window.removeEventListener("keydown", this.keyDownHandler);
}
Expand Down
49 changes: 7 additions & 42 deletions packages/calcite-components/src/components/popover/popover.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,49 +400,24 @@ describe("calcite-popover", () => {
expect(await popover.getProperty("open")).toBe(false);
});

it("should not toggle popovers if selection range is present", async () => {
const page = await newE2EPage();

await page.setContent(html`
<calcite-popover reference-element="ref">Content</calcite-popover>
<button id="ref">Button</button>
`);

const popover = await page.find("calcite-popover");

expect(await popover.getProperty("open")).toBe(false);

await page.$eval("button#ref", (el) => {
const selection = window.getSelection();
selection.removeAllRanges();
const range = document.createRange();
range.selectNode(el);
selection.addRange(range);
el.click();
});
await page.waitForChanges();

expect(await popover.getProperty("open")).toBe(false);
});

it("should not close active popover if selection range occurs within the popover", async () => {
it("should not close active popover if click starts within the popover but ends outside", async () => {
const page = await newE2EPage();

await page.setContent(html`
<calcite-popover reference-element="ref" open><div id="content">Content</div></calcite-popover>
<button id="ref">Button</button>
<div id="outsideNode">Outside node</div>
`);

const popover = await page.find("calcite-popover");

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

await page.$eval("div#content", (el) => {
const selection = window.getSelection();
selection.removeAllRanges();
const range = document.createRange();
range.selectNode(el);
selection.addRange(range);
await page.evaluate(() => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: I think we can enhance our dragAndDrop test util or add a new one to simplify these interactions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed!

const content = document.getElementById("content");
content.dispatchEvent(new MouseEvent("pointerdown", { bubbles: true }));
const outsideNode = document.getElementById("outsideNode");
outsideNode.dispatchEvent(new MouseEvent("pointerup", { bubbles: true }));
});
await page.waitForChanges();

Expand All @@ -453,16 +428,6 @@ describe("calcite-popover", () => {
await ref.click();
await page.waitForChanges();

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

await page.evaluate(() => {
const selection = window.getSelection();
selection.removeAllRanges();
});

await ref.click();
await page.waitForChanges();

expect(await popover.getProperty("open")).toBe(false);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,12 @@ export default class TooltipManager {
}
};

private activeTooltipHasSelection(): boolean {
const selection = window.getSelection();
return selection?.type === "Range" && this.activeTooltip?.contains(selection?.anchorNode);
}

private pointerLeaveHandler = (event: PointerEvent): void => {
if (event.defaultPrevented) {
return;
}

this.clearHoverTimeout();

if (this.activeTooltipHasSelection()) {
return;
}

this.closeHoveredTooltip();
};

Expand All @@ -127,7 +117,7 @@ export default class TooltipManager {

const tooltip = this.queryTooltip(composedPath);

if (this.activeTooltipHasSelection() || this.pathHasOpenTooltip(tooltip, composedPath)) {
if (this.pathHasOpenTooltip(tooltip, composedPath)) {
this.clearHoverTimeout();
return;
}
Expand Down Expand Up @@ -159,8 +149,8 @@ export default class TooltipManager {
);
}

private clickHandler = (event: Event): void => {
if (event.defaultPrevented || window.getSelection()?.type === "Range") {
private clickHandler = (event: PointerEvent): void => {
if (event.defaultPrevented) {
return;
}

Expand Down
78 changes: 0 additions & 78 deletions packages/calcite-components/src/components/tooltip/tooltip.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,84 +706,6 @@ describe("calcite-tooltip", () => {
expect(await hoverTip.getProperty("open")).toBe(false);
});

it("should not open on click if selection range is present", async () => {
const page = await newE2EPage();

await page.setContent(html`
<calcite-tooltip id="hoverTip" reference-element="hoverRef">Content</calcite-tooltip>
<button id="hoverRef">Button</button>
<div id="selection">Selection</div>
`);

const hoverTip = await page.find("#hoverTip");

expect(await hoverTip.getProperty("open")).toBe(false);

await page.$eval("div#selection", (el) => {
const selection = window.getSelection();
selection.removeAllRanges();
const range = document.createRange();
range.selectNode(el);
selection.addRange(range);
});

await dispatchClickEvent(page, "#hoverRef");
await page.waitForTimeout(TOOLTIP_OPEN_DELAY_MS);

expect(await hoverTip.getProperty("open")).toBe(false);

await page.evaluate(() => {
const selection = window.getSelection();
selection.removeAllRanges();
});
await page.waitForChanges();

await dispatchClickEvent(page, "#hoverRef");
await page.waitForTimeout(TOOLTIP_OPEN_DELAY_MS);

expect(await hoverTip.getProperty("open")).toBe(true);
});

it("should not close if selection range occurs within the tooltip", async () => {
const page = await newE2EPage();

await page.setContent(html`
<button id="otherRef">Other Button</button>
<calcite-tooltip id="hoverTip" reference-element="hoverRef"><div id="content">Content</div></calcite-tooltip>
<button id="hoverRef">Button</button>
`);

const hoverTip = await page.find("#hoverTip");
await dispatchPointerEvent(page, "#hoverRef");
await page.waitForTimeout(TOOLTIP_OPEN_DELAY_MS);
expect(await hoverTip.getProperty("open")).toBe(true);

await page.$eval("div#content", (el) => {
const selection = window.getSelection();
selection.removeAllRanges();
const range = document.createRange();
range.selectNode(el);
selection.addRange(range);
});
await page.waitForChanges();

await dispatchPointerEvent(page, "#otherRef");
await page.waitForTimeout(TOOLTIP_CLOSE_DELAY_MS);

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

await page.evaluate(() => {
const selection = window.getSelection();
selection.removeAllRanges();
});
await page.waitForChanges();

await dispatchPointerEvent(page, "#otherRef");
await page.waitForTimeout(TOOLTIP_CLOSE_DELAY_MS);

expect(await hoverTip.getProperty("open")).toBe(false);
});

describe("owns a floating-ui", () => {
floatingUIOwner(
`<calcite-tooltip reference-element="ref">content</calcite-tooltip><div id="ref">referenceElement</div>`,
Expand Down
Loading