From 922121d571f54a21b60163ad7e19d8c38d03672b Mon Sep 17 00:00:00 2001 From: 4nnikaf Date: Mon, 15 Apr 2024 16:24:35 +0200 Subject: [PATCH] Prevent click event from triggering on elements behind hitAreaMargins --- .../react-resizable-panels/src/PanelGroup.ts | 4 +-- .../src/PanelGroupContext.ts | 3 +- .../src/PanelResizeHandle.test.tsx | 22 ++++++------ .../src/PanelResizeHandleRegistry.ts | 34 +++++++------------ packages/react-resizable-panels/src/types.ts | 3 +- .../utils/events/getResizeEventCoordinates.ts | 19 +++++------ .../src/utils/events/index.ts | 10 +++--- .../src/utils/test-utils.ts | 3 ++ 8 files changed, 47 insertions(+), 51 deletions(-) diff --git a/packages/react-resizable-panels/src/PanelGroup.ts b/packages/react-resizable-panels/src/PanelGroup.ts index ccbc2110e..b51ff3a12 100644 --- a/packages/react-resizable-panels/src/PanelGroup.ts +++ b/packages/react-resizable-panels/src/PanelGroup.ts @@ -28,7 +28,7 @@ import { computePanelFlexBoxStyle } from "./utils/computePanelFlexBoxStyle"; import debounce from "./utils/debounce"; import { determinePivotIndices } from "./utils/determinePivotIndices"; import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement"; -import { isKeyDown, isMouseEvent, isTouchEvent } from "./utils/events"; +import { isKeyDown, isMouseEvent, isPointerEvent } from "./utils/events"; import { getResizeEventCursorPosition } from "./utils/events/getResizeEventCursorPosition"; import { initializeDefaultStorage } from "./utils/initializeDefaultStorage"; import { @@ -658,7 +658,7 @@ function PanelGroupWithForwardedRef({ // Only update the cursor for layout changes triggered by touch/mouse events (not keyboard) // Update the cursor even if the layout hasn't changed (we may need to show an invalid cursor state) - if (isMouseEvent(event) || isTouchEvent(event)) { + if (isPointerEvent(event) || isMouseEvent(event)) { // Watch for multiple subsequent deltas; this might occur for tiny cursor movements. // In this case, Panel sizes might not change– // but updating cursor in this scenario would cause a flicker. diff --git a/packages/react-resizable-panels/src/PanelGroupContext.ts b/packages/react-resizable-panels/src/PanelGroupContext.ts index a531e44e5..d810ee083 100644 --- a/packages/react-resizable-panels/src/PanelGroupContext.ts +++ b/packages/react-resizable-panels/src/PanelGroupContext.ts @@ -1,7 +1,8 @@ import { PanelConstraints, PanelData } from "./Panel"; import { CSSProperties, createContext } from "./vendor/react"; -export type ResizeEvent = KeyboardEvent | MouseEvent | TouchEvent; +// The "contextmenu" event is not supported as a PointerEvent in all browsers yet, so MouseEvent still need to be handled +export type ResizeEvent = KeyboardEvent | PointerEvent | MouseEvent; export type ResizeHandler = (event: ResizeEvent) => void; export type DragState = { diff --git a/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx b/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx index 03c81b9fe..a2bf94506 100644 --- a/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx +++ b/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx @@ -127,18 +127,18 @@ describe("PanelResizeHandle", () => { }); act(() => { - dispatchPointerEvent("mousemove", leftElement); + dispatchPointerEvent("pointermove", leftElement); }); expect(onDragging).not.toHaveBeenCalled(); act(() => { - dispatchPointerEvent("mousedown", leftElement); + dispatchPointerEvent("pointerdown", leftElement); }); expect(onDragging).toHaveBeenCalledTimes(1); expect(onDragging).toHaveBeenCalledWith(true); act(() => { - dispatchPointerEvent("mouseup", leftElement); + dispatchPointerEvent("pointerup", leftElement); }); expect(onDragging).toHaveBeenCalledTimes(2); expect(onDragging).toHaveBeenCalledWith(false); @@ -154,20 +154,20 @@ describe("PanelResizeHandle", () => { }); act(() => { - dispatchPointerEvent("mousemove", leftElement); + dispatchPointerEvent("pointermove", leftElement); }); expect(onDraggingLeft).not.toHaveBeenCalled(); expect(onDraggingRight).not.toHaveBeenCalled(); act(() => { - dispatchPointerEvent("mousedown", leftElement); + dispatchPointerEvent("pointerdown", leftElement); }); expect(onDraggingLeft).toHaveBeenCalledTimes(1); expect(onDraggingLeft).toHaveBeenCalledWith(true); expect(onDraggingRight).not.toHaveBeenCalled(); act(() => { - dispatchPointerEvent("mouseup", leftElement); + dispatchPointerEvent("pointerup", leftElement); }); expect(onDraggingLeft).toHaveBeenCalledTimes(2); expect(onDraggingLeft).toHaveBeenCalledWith(false); @@ -209,7 +209,7 @@ describe("PanelResizeHandle", () => { verifyAttribute(rightElement, "data-resize-handle-state", "inactive"); act(() => { - dispatchPointerEvent("mousemove", leftElement); + dispatchPointerEvent("pointermove", leftElement); }); verifyAttribute(leftElement, "data-resize-handle-active", null); verifyAttribute(rightElement, "data-resize-handle-active", null); @@ -217,7 +217,7 @@ describe("PanelResizeHandle", () => { verifyAttribute(rightElement, "data-resize-handle-state", "inactive"); act(() => { - dispatchPointerEvent("mousedown", leftElement); + dispatchPointerEvent("pointerdown", leftElement); }); verifyAttribute(leftElement, "data-resize-handle-active", "pointer"); verifyAttribute(rightElement, "data-resize-handle-active", null); @@ -225,7 +225,7 @@ describe("PanelResizeHandle", () => { verifyAttribute(rightElement, "data-resize-handle-state", "inactive"); act(() => { - dispatchPointerEvent("mousemove", leftElement); + dispatchPointerEvent("pointermove", leftElement); }); verifyAttribute(leftElement, "data-resize-handle-active", "pointer"); verifyAttribute(rightElement, "data-resize-handle-active", null); @@ -233,7 +233,7 @@ describe("PanelResizeHandle", () => { verifyAttribute(rightElement, "data-resize-handle-state", "inactive"); act(() => { - dispatchPointerEvent("mouseup", leftElement); + dispatchPointerEvent("pointerup", leftElement); }); verifyAttribute(leftElement, "data-resize-handle-active", null); verifyAttribute(rightElement, "data-resize-handle-active", null); @@ -241,7 +241,7 @@ describe("PanelResizeHandle", () => { verifyAttribute(rightElement, "data-resize-handle-state", "inactive"); act(() => { - dispatchPointerEvent("mousemove", rightElement); + dispatchPointerEvent("pointermove", rightElement); }); verifyAttribute(leftElement, "data-resize-handle-active", null); verifyAttribute(rightElement, "data-resize-handle-active", null); diff --git a/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts b/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts index c666d6dc6..5b0300f66 100644 --- a/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts +++ b/packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts @@ -89,6 +89,7 @@ function handlePointerDown(event: ResizeEvent) { updateResizeHandlerStates("down", event); event.preventDefault(); + event.stopPropagation(); } } @@ -258,16 +259,13 @@ function updateListeners() { const { body } = ownerDocument; body.removeEventListener("contextmenu", handlePointerUp); - body.removeEventListener("mousedown", handlePointerDown); - body.removeEventListener("mouseleave", handlePointerMove); - body.removeEventListener("mousemove", handlePointerMove); - body.removeEventListener("touchmove", handlePointerMove); - body.removeEventListener("touchstart", handlePointerDown); + body.removeEventListener("pointerdown", handlePointerDown); + body.removeEventListener("pointerleave", handlePointerMove); + body.removeEventListener("pointermove", handlePointerMove); }); - window.removeEventListener("mouseup", handlePointerUp); - window.removeEventListener("touchcancel", handlePointerUp); - window.removeEventListener("touchend", handlePointerUp); + window.removeEventListener("pointerup", handlePointerUp); + window.removeEventListener("pointercancel", handlePointerUp); if (registeredResizeHandlers.size > 0) { if (isPointerDown) { @@ -277,29 +275,23 @@ function updateListeners() { if (count > 0) { body.addEventListener("contextmenu", handlePointerUp); - body.addEventListener("mouseleave", handlePointerMove); - body.addEventListener("mousemove", handlePointerMove); - body.addEventListener("touchmove", handlePointerMove, { - passive: false, - }); + body.addEventListener("pointerleave", handlePointerMove); + body.addEventListener("pointermove", handlePointerMove); } }); } - window.addEventListener("mouseup", handlePointerUp); - window.addEventListener("touchcancel", handlePointerUp); - window.addEventListener("touchend", handlePointerUp); + window.addEventListener("pointerup", handlePointerUp); + window.addEventListener("pointercancel", handlePointerUp); } else { ownerDocumentCounts.forEach((count, ownerDocument) => { const { body } = ownerDocument; if (count > 0) { - body.addEventListener("mousedown", handlePointerDown); - body.addEventListener("mousemove", handlePointerMove); - body.addEventListener("touchmove", handlePointerMove, { - passive: false, + body.addEventListener("pointerdown", handlePointerDown, { + capture: true, }); - body.addEventListener("touchstart", handlePointerDown); + body.addEventListener("pointermove", handlePointerMove); } }); } diff --git a/packages/react-resizable-panels/src/types.ts b/packages/react-resizable-panels/src/types.ts index ef9ffc8ce..e0528090f 100644 --- a/packages/react-resizable-panels/src/types.ts +++ b/packages/react-resizable-panels/src/types.ts @@ -1,4 +1,5 @@ export type Direction = "horizontal" | "vertical"; -export type ResizeEvent = KeyboardEvent | MouseEvent | TouchEvent; +// The "contextmenu" event is not supported as a PointerEvent in all browsers yet, so MouseEvent still need to be handled +export type ResizeEvent = KeyboardEvent | PointerEvent | MouseEvent; export type ResizeHandler = (event: ResizeEvent) => void; diff --git a/packages/react-resizable-panels/src/utils/events/getResizeEventCoordinates.ts b/packages/react-resizable-panels/src/utils/events/getResizeEventCoordinates.ts index 567be6477..3e7cbdc20 100644 --- a/packages/react-resizable-panels/src/utils/events/getResizeEventCoordinates.ts +++ b/packages/react-resizable-panels/src/utils/events/getResizeEventCoordinates.ts @@ -1,20 +1,19 @@ import { ResizeEvent } from "../../types"; -import { isMouseEvent, isTouchEvent } from "."; +import { isMouseEvent, isPointerEvent } from "."; export function getResizeEventCoordinates(event: ResizeEvent) { - if (isMouseEvent(event)) { + if (isPointerEvent(event)) { + if (event.isPrimary) { + return { + x: event.clientX, + y: event.clientY, + }; + } + } else if (isMouseEvent(event)) { return { x: event.clientX, y: event.clientY, }; - } else if (isTouchEvent(event)) { - const touch = event.touches[0]; - if (touch && touch.clientX && touch.clientY) { - return { - x: touch.clientX, - y: touch.clientY, - }; - } } return { diff --git a/packages/react-resizable-panels/src/utils/events/index.ts b/packages/react-resizable-panels/src/utils/events/index.ts index 5425887db..71032ce99 100644 --- a/packages/react-resizable-panels/src/utils/events/index.ts +++ b/packages/react-resizable-panels/src/utils/events/index.ts @@ -4,10 +4,10 @@ export function isKeyDown(event: ResizeEvent): event is KeyboardEvent { return event.type === "keydown"; } -export function isMouseEvent(event: ResizeEvent): event is MouseEvent { - return event.type.startsWith("mouse"); +export function isPointerEvent(event: ResizeEvent): event is PointerEvent { + return event.type.startsWith("pointer"); } -export function isTouchEvent(event: ResizeEvent): event is TouchEvent { - return event.type.startsWith("touch"); -} +export function isMouseEvent(event: ResizeEvent): event is MouseEvent { + return event.type.startsWith("mouse"); +} \ No newline at end of file diff --git a/packages/react-resizable-panels/src/utils/test-utils.ts b/packages/react-resizable-panels/src/utils/test-utils.ts index 83f467d5e..eb2b0917d 100644 --- a/packages/react-resizable-panels/src/utils/test-utils.ts +++ b/packages/react-resizable-panels/src/utils/test-utils.ts @@ -24,6 +24,9 @@ export function dispatchPointerEvent(type: string, target: HTMLElement) { return clientY; }, }, + isPrimary: { + value: true + } }); target.dispatchEvent(event);