diff --git a/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx b/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx index 42886238a..239b7d8b1 100644 --- a/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx +++ b/packages/react-resizable-panels/src/PanelResizeHandle.test.tsx @@ -279,6 +279,18 @@ describe("PanelResizeHandle", () => { verifyAttribute(leftElement, "data-resize-handle-active", null); verifyAttribute(rightElement, "data-resize-handle-active", null); }); + + it("should stop dragging when the mouse goes beyond the browser size", () => { + const { leftElement, rightElement } = setupMockedGroup(); + verifyAttribute(leftElement, "data-resize-handle-active", null); + verifyAttribute(rightElement, "data-resize-handle-active", null); + + act(() => { + dispatchPointerEvent("pointermove", leftElement, { clientX: -1 }); + }); + verifyAttribute(leftElement, "data-resize-handle-state", "inactive"); + verifyAttribute(rightElement, "data-resize-handle-state", "inactive"); + }); }); describe("a11y", () => { diff --git a/packages/react-resizable-panels/src/PanelResizeHandle.ts b/packages/react-resizable-panels/src/PanelResizeHandle.ts index 4a9f50295..e4a44a093 100644 --- a/packages/react-resizable-panels/src/PanelResizeHandle.ts +++ b/packages/react-resizable-panels/src/PanelResizeHandle.ts @@ -193,6 +193,28 @@ export function PanelResizeHandle({ stopDragging, ]); + useEffect(() => { + const handleMouseOut = (event: MouseEvent) => { + if ( + event.clientX < 0 || + event.clientY < 0 || + event.clientX > window.innerWidth || + event.clientY > window.innerHeight + ) { + setState("hover"); + stopDragging(); + const { onDragging } = callbacksRef.current; + if (onDragging) { + onDragging(false); + } + } + }; + window.addEventListener('mouseout', handleMouseOut); + return () => { + window.removeEventListener('mouseout', handleMouseOut); + }; + }, []); + useWindowSplitterResizeHandlerBehavior({ disabled, handleId: resizeHandleId, diff --git a/packages/react-resizable-panels/src/utils/test-utils.ts b/packages/react-resizable-panels/src/utils/test-utils.ts index 0082084b8..ad9a9070a 100644 --- a/packages/react-resizable-panels/src/utils/test-utils.ts +++ b/packages/react-resizable-panels/src/utils/test-utils.ts @@ -2,11 +2,16 @@ import { assert } from "./assert"; const util = require("util"); -export function dispatchPointerEvent(type: string, target: HTMLElement) { +interface IDispatchPointerEventOption { + clientX?: number; + clientY?: number; +} +export function dispatchPointerEvent(type: string, target: HTMLElement, options: IDispatchPointerEventOption = {}) { const rect = target.getBoundingClientRect(); - const clientX = rect.left + rect.width / 2; - const clientY = rect.top + rect.height / 2; + const { clientX: initClientX, clientY: initClientY } = options; + const clientX = initClientX || rect.left + rect.width / 2; + const clientY = initClientY || rect.top + rect.height / 2; const event = new MouseEvent(type, { bubbles: true,