Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent pointer events from triggering on elements behind hitAreaMargins #338

Merged
merged 1 commit into from
Apr 15, 2024
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
4 changes: 2 additions & 2 deletions packages/react-resizable-panels/src/PanelGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion packages/react-resizable-panels/src/PanelGroupContext.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down
22 changes: 11 additions & 11 deletions packages/react-resizable-panels/src/PanelResizeHandle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -209,39 +209,39 @@ 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);
verifyAttribute(leftElement, "data-resize-handle-state", "hover");
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);
verifyAttribute(leftElement, "data-resize-handle-state", "drag");
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);
verifyAttribute(leftElement, "data-resize-handle-state", "drag");
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);
verifyAttribute(leftElement, "data-resize-handle-state", "hover");
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);
Expand Down
34 changes: 13 additions & 21 deletions packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ function handlePointerDown(event: ResizeEvent) {
updateResizeHandlerStates("down", event);

event.preventDefault();
event.stopPropagation();
Copy link
Owner

Choose a reason for hiding this comment

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

This is the only change that gives me pause. I guess it's okay.

}
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
});
}
Expand Down
3 changes: 2 additions & 1 deletion packages/react-resizable-panels/src/types.ts
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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)) {
Copy link
Owner

Choose a reason for hiding this comment

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

This is a nice simplification

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 {
Expand Down
10 changes: 5 additions & 5 deletions packages/react-resizable-panels/src/utils/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
3 changes: 3 additions & 0 deletions packages/react-resizable-panels/src/utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export function dispatchPointerEvent(type: string, target: HTMLElement) {
return clientY;
},
},
isPrimary: {
value: true
}
});

target.dispatchEvent(event);
Expand Down