From c2104b1c0335a23eec433da1f17fe6c2e39df987 Mon Sep 17 00:00:00 2001 From: adventful <34770367+adventful@users.noreply.github.com> Date: Sat, 29 Oct 2022 16:37:36 +0800 Subject: [PATCH 1/2] Fix lostpointercapture event handling --- packages/fiber/src/core/events.ts | 2 +- packages/fiber/tests/core/events.test.tsx | 62 ++++++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/fiber/src/core/events.ts b/packages/fiber/src/core/events.ts index 9253864bab..2335d55bcb 100644 --- a/packages/fiber/src/core/events.ts +++ b/packages/fiber/src/core/events.ts @@ -403,7 +403,7 @@ export function createEvents(store: UseBoundStore) { case 'onLostPointerCapture': return (event: DomEvent) => { const { internal } = store.getState() - if ('pointerId' in event && !internal.capturedMap.has(event.pointerId)) { + if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) { // If the object event interface had onLostPointerCapture, we'd call it here on every // object that's getting removed. internal.capturedMap.delete(event.pointerId) diff --git a/packages/fiber/tests/core/events.test.tsx b/packages/fiber/tests/core/events.test.tsx index 5a753acb5c..793e30c51c 100644 --- a/packages/fiber/tests/core/events.test.tsx +++ b/packages/fiber/tests/core/events.test.tsx @@ -270,13 +270,16 @@ describe('events', () => { describe('web pointer capture', () => { const handlePointerMove = jest.fn() const handlePointerDown = jest.fn((ev) => (ev.target as any).setPointerCapture(ev.pointerId)) + const handlePointerUp = jest.fn((ev) => (ev.target as any).releasePointerCapture(ev.pointerId)) + const handlePointerEnter = jest.fn() + const handlePointerLeave = jest.fn() /* This component lets us unmount the event-handling object */ - function PointerCaptureTest(props: { hasMesh: boolean }) { + function PointerCaptureTest(props: { hasMesh: boolean, manualRelease?: boolean }) { return ( {props.hasMesh && ( - + @@ -325,5 +328,60 @@ describe('events', () => { /* There should now be no pointer capture */ expect(handlePointerMove).not.toHaveBeenCalled() }) + + it('should not leave when captured', async () => { + let renderResult: RenderResult = undefined! + await act(async () => { + renderResult = render() + return renderResult + }) + + const canvas = getContainer() + canvas.setPointerCapture = jest.fn() + canvas.releasePointerCapture = jest.fn() + + const moveIn = new PointerEvent('pointermove', { pointerId }) + Object.defineProperty(moveIn, 'offsetX', { get: () => 577 }) + Object.defineProperty(moveIn, 'offsetY', { get: () => 480 }) + + const moveOut = new PointerEvent('pointermove', { pointerId }) + Object.defineProperty(moveOut, 'offsetX', { get: () => -10000 }) + Object.defineProperty(moveOut, 'offsetY', { get: () => -10000 }) + + /* testing-utils/react's fireEvent wraps the event like React does, so it doesn't match how our event handlers are called in production, so we call dispatchEvent directly. */ + await act(async () => canvas.dispatchEvent(moveIn)) + expect(handlePointerEnter).toHaveBeenCalledTimes(1); + expect(handlePointerMove).toHaveBeenCalledTimes(1); + + const down = new PointerEvent('pointerdown', { pointerId }) + Object.defineProperty(down, 'offsetX', { get: () => 577 }) + Object.defineProperty(down, 'offsetY', { get: () => 480 }) + + await act(async () => canvas.dispatchEvent(down)) + + // If we move the pointer now, when it is captured, it should raise the onPointerMove event even though the pointer is not over the element, + // and NOT raise the onPointerLeave event. + await act(async () => canvas.dispatchEvent(moveOut)) + expect(handlePointerMove).toHaveBeenCalledTimes(2); + expect(handlePointerLeave).not.toHaveBeenCalled(); + + await act(async () => canvas.dispatchEvent(moveIn)) + + const up = new PointerEvent('pointerup', { pointerId }) + Object.defineProperty(up, 'offsetX', { get: () => 577 }) + Object.defineProperty(up, 'offsetY', { get: () => 480 }) + const lostpointercapture = new PointerEvent('lostpointercapture', { pointerId }) + + await act(async () => canvas.dispatchEvent(up)) + await act(async () => canvas.dispatchEvent(lostpointercapture)) + + // The pointer is still over the element, so onPointerLeave should not have been called. + expect(handlePointerLeave).not.toHaveBeenCalled(); + + // The element pointer should no longer be captured, so moving it away should call onPointerLeave. + await act(async () => canvas.dispatchEvent(moveOut)); + expect(handlePointerEnter).toHaveBeenCalledTimes(1); + expect(handlePointerLeave).toHaveBeenCalledTimes(1) + }) }) }) From 867091ba3b2e19bf7f29aa390f2b7965bbe099af Mon Sep 17 00:00:00 2001 From: adventful <34770367+adventful@users.noreply.github.com> Date: Sat, 29 Oct 2022 16:42:14 +0800 Subject: [PATCH 2/2] Fix move event being called twice when capturing pointer if pointer intersects anyway --- packages/fiber/src/core/events.ts | 2 +- packages/fiber/tests/core/events.test.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/fiber/src/core/events.ts b/packages/fiber/src/core/events.ts index 2335d55bcb..7f567fc2f9 100644 --- a/packages/fiber/src/core/events.ts +++ b/packages/fiber/src/core/events.ts @@ -252,7 +252,7 @@ export function createEvents(store: UseBoundStore) { // If the interaction is captured, make all capturing targets part of the intersect. if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) { for (let captureData of state.internal.capturedMap.get(event.pointerId)!.values()) { - intersections.push(captureData.intersection) + if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection) } } return intersections diff --git a/packages/fiber/tests/core/events.test.tsx b/packages/fiber/tests/core/events.test.tsx index 793e30c51c..dcb9a88f57 100644 --- a/packages/fiber/tests/core/events.test.tsx +++ b/packages/fiber/tests/core/events.test.tsx @@ -366,6 +366,7 @@ describe('events', () => { expect(handlePointerLeave).not.toHaveBeenCalled(); await act(async () => canvas.dispatchEvent(moveIn)) + expect(handlePointerMove).toHaveBeenCalledTimes(3); const up = new PointerEvent('pointerup', { pointerId }) Object.defineProperty(up, 'offsetX', { get: () => 577 })