Skip to content

Commit 118e546

Browse files
committed
fix(popover): address touch tooltip review
1 parent 25b7163 commit 118e546

3 files changed

Lines changed: 56 additions & 1 deletion

File tree

packages/stacks-svelte/src/components/Popover/Popover.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@
8282
*/
8383
dismissible?: boolean;
8484
/**
85-
* Whether or not the popover should be an on-hover tooltip
85+
* Enables tooltip interactions. Tooltips open on hover for hover-capable devices or keyboard focus,
86+
* toggle on touch, and ignore mouse clicks. They close when hover or focus leaves, when touch repeats,
87+
* or—when dismissible—on outside interaction or Escape.
8688
*/
8789
tooltip?: boolean;
8890
/**

packages/stacks-svelte/src/components/Popover/Popover.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,16 @@ describe("Popover", () => {
13021302
await tick();
13031303
expect(screen.getByRole("tooltip")).to.exist;
13041304

1305+
tap(document.body);
1306+
await clock.runAllAsync();
1307+
await tick();
1308+
expect(screen.queryByRole("tooltip")).not.to.exist;
1309+
1310+
tap(button);
1311+
await clock.runAllAsync();
1312+
await tick();
1313+
expect(screen.getByRole("tooltip")).to.exist;
1314+
13051315
tap(button);
13061316
await clock.runAllAsync();
13071317
await tick();
@@ -1340,6 +1350,42 @@ describe("Popover", () => {
13401350
clock.restore();
13411351
});
13421352

1353+
it("should ignore a keyboard click after an aborted touch", async () => {
1354+
const clock = sinon.useFakeTimers({
1355+
shouldAdvanceTime: true,
1356+
shouldClearNativeTimers: true,
1357+
});
1358+
stubHoverSupport(false);
1359+
render(Popover, {
1360+
props: {
1361+
...defaultProps,
1362+
tooltip: true,
1363+
children: createSvelteComponentsSnippet([
1364+
defaultChildren.reference,
1365+
defaultChildren.content,
1366+
]),
1367+
},
1368+
});
1369+
1370+
const button = screen.getByRole("button");
1371+
button.dispatchEvent(
1372+
new PointerEvent("pointerdown", {
1373+
bubbles: true,
1374+
pointerType: "touch",
1375+
})
1376+
);
1377+
window.dispatchEvent(
1378+
new PointerEvent("pointerup", { pointerType: "touch" })
1379+
);
1380+
await clock.runAllAsync();
1381+
1382+
button.click();
1383+
await clock.runAllAsync();
1384+
await tick();
1385+
expect(screen.queryByRole("tooltip")).not.to.exist;
1386+
clock.restore();
1387+
});
1388+
13431389
it("should toggle on touch when the device also supports hover", async () => {
13441390
const clock = sinon.useFakeTimers({
13451391
shouldAdvanceTime: true,

packages/stacks-svelte/src/components/Popover/PopoverReference.svelte

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,20 @@
7777
const hoverMedia = window.matchMedia("(hover: hover)");
7878
let pointerDown = false;
7979
let pointerType = "";
80+
let pointerTypeReset: number;
8081
const onPointerDown = (event: PointerEvent) => {
82+
window.clearTimeout(pointerTypeReset);
8183
pointerDown = true;
8284
pointerType = event.pointerType;
8385
};
8486
const onPointerUp = () => {
8587
pointerDown = false;
88+
pointerTypeReset = window.setTimeout(() => {
89+
pointerType = "";
90+
});
8691
};
8792
const onPointerCancel = () => {
93+
window.clearTimeout(pointerTypeReset);
8894
pointerDown = false;
8995
pointerType = "";
9096
};
@@ -128,6 +134,7 @@
128134
ref.addEventListener("focusout", pstate.closeTooltip);
129135
ref.setAttribute("aria-describedby", `${pstate.id}-popover`);
130136
return () => {
137+
window.clearTimeout(pointerTypeReset);
131138
hoverMedia.removeEventListener("change", onHoverSupportChange);
132139
ref.removeEventListener("mouseenter", pstate.openTooltip);
133140
ref.removeEventListener("mouseleave", pstate.closeTooltip);

0 commit comments

Comments
 (0)