diff --git a/src/components/structures/PictureInPictureDragger.tsx b/src/components/structures/PictureInPictureDragger.tsx index 19205229c8..40c1caee6f 100644 --- a/src/components/structures/PictureInPictureDragger.tsx +++ b/src/components/structures/PictureInPictureDragger.tsx @@ -70,6 +70,8 @@ export default class PictureInPictureDragger extends React.Component { () => this.animationCallback(), () => requestAnimationFrame(() => this.scheduledUpdate.trigger()), ); + private startingPositionX = 0; + private startingPositionY = 0; private _moving = false; public get moving(): boolean { @@ -192,11 +194,22 @@ export default class PictureInPictureDragger extends React.Component { event.stopPropagation(); this.mouseHeld = true; + this.startingPositionX = event.clientX; + this.startingPositionY = event.clientY; }; private onMoving = (event: MouseEvent): void => { if (!this.mouseHeld) return; + if ( + Math.abs(this.startingPositionX - event.clientX) < 5 && + Math.abs(this.startingPositionY - event.clientY) < 5 + ) { + // User needs to move the widget by at least five pixels. + // Improves click detection when using a touchpad or with nervous hands. + return; + } + event.preventDefault(); event.stopPropagation(); diff --git a/test/components/structures/PictureInPictureDragger-test.tsx b/test/components/structures/PictureInPictureDragger-test.tsx index 9b92fefd79..9f42615287 100644 --- a/test/components/structures/PictureInPictureDragger-test.tsx +++ b/test/components/structures/PictureInPictureDragger-test.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { MouseEventHandler } from "react"; import { screen, render, RenderResult } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; @@ -82,28 +82,39 @@ describe("PictureInPictureDragger", () => { }); }); - it("doesn't leak drag events to children as clicks", async () => { - const clickSpy = jest.fn(); - render( - - {[ - ({ onStartMoving }) => ( -
- Hello -
- ), - ]} -
, - ); - const target = screen.getByText("Hello"); + describe("when rendering the dragger", () => { + let clickSpy: jest.Mocked; + let target: HTMLElement; - // A click without a drag motion should go through - await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { keys: "[/MouseLeft]" }]); - expect(clickSpy).toHaveBeenCalled(); + beforeEach(() => { + clickSpy = jest.fn(); + render( + + {[ + ({ onStartMoving }) => ( +
+ Hello +
+ ), + ]} +
, + ); + target = screen.getByText("Hello"); + }); - // A drag motion should not trigger a click - clickSpy.mockClear(); - await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { coords: { x: 60, y: 60 } }, "[/MouseLeft]"]); - expect(clickSpy).not.toHaveBeenCalled(); + it("and clicking without a drag motion, it should pass the click to children", async () => { + await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { keys: "[/MouseLeft]" }]); + expect(clickSpy).toHaveBeenCalled(); + }); + + it("and clicking with a drag motion above the threshold of 5px, it should not pass the click to children", async () => { + await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { coords: { x: 60, y: 2 } }, "[/MouseLeft]"]); + expect(clickSpy).not.toHaveBeenCalled(); + }); + + it("and clickign with a drag motion below the threshold of 5px, it should pass the click to the children", async () => { + await userEvent.pointer([{ keys: "[MouseLeft>]", target }, { coords: { x: 4, y: 4 } }, "[/MouseLeft]"]); + expect(clickSpy).toHaveBeenCalled(); + }); }); });