Skip to content

Commit

Permalink
Stashing WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
bvaughn committed Feb 10, 2024
1 parent 73897f3 commit 7511d51
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ export default function NestedRoute() {

function Content() {
return (
<div className={styles.PanelGroupWrapper}>
<div
className={styles.PanelGroupWrapper}
style={{
position: "relative",
}}
>
<PanelGroup className={styles.PanelGroup} direction="horizontal">
<Panel className={styles.PanelRow} defaultSize={20} minSize={10}>
<div className={styles.Centered}>left</div>
Expand Down Expand Up @@ -53,6 +58,24 @@ function Content() {
<div className={styles.Centered}>right</div>
</Panel>
</PanelGroup>

<div
style={{
position: "absolute",
left: "calc(50% - 50px)",
top: "calc(50% - 25px)",
width: 100,
height: 50,
backgroundColor: "rgba(255,255,255,.5)",
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: "1rem",
border: "2px solid black",
}}
>
Modal Overlay
</div>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,11 @@ test.describe("cursor style", () => {
);
});

test("should update cursor when dragging intersecting panels (like tmux)", async ({
test.only("should update cursor when dragging intersecting panels (like tmux)", async ({
page,
}) => {
page.on("console", (msg) => console.log("PAGE LOG:", msg.text()));

await goToUrl(
page,
createElement(
Expand Down
8 changes: 6 additions & 2 deletions packages/react-resizable-panels-website/tests/utils/panels.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Locator, Page, expect } from "@playwright/test";
import { assert } from "react-resizable-panels";
import {
assert,
getIntersectingRectangle,
intersects,
} from "react-resizable-panels";
import { getBodyCursorStyle } from "./cursor";
import { verifyFuzzySizes } from "./verify";
import { getIntersectingRectangle, intersects } from "./rect";

type Operation = {
expectedCursor?: string;
Expand Down Expand Up @@ -71,6 +74,7 @@ export async function dragResizeIntersecting(
const panelGroupRect = (await panelGroup.boundingBox())!;

await page.mouse.move(centerPageX, centerPageY);
await new Promise((resolve) => setTimeout(resolve, 5_000));
await expect(
await dragHandleOne.getAttribute("data-resize-handle-state")
).toBe("hover");
Expand Down
55 changes: 49 additions & 6 deletions packages/react-resizable-panels/src/PanelResizeHandleRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { compare } from "stacking-order";
import { Direction, ResizeEvent } from "./types";
import { resetGlobalCursorStyle, setGlobalCursorStyle } from "./utils/cursor";
import { getResizeEventCoordinates } from "./utils/events/getResizeEventCoordinates";
import { getInputType } from "./utils/getInputType";
import { intersects } from "./utils/rects/intersects";

export type ResizeHandlerAction = "down" | "move" | "up";
export type SetResizeHandlerState = (
Expand Down Expand Up @@ -75,11 +77,12 @@ export function registerResizeHandle(
}

function handlePointerDown(event: ResizeEvent) {
const { target } = event;
const { x, y } = getResizeEventCoordinates(event);

isPointerDown = true;

recalculateIntersectingHandles({ x, y });
recalculateIntersectingHandles({ target, x, y });
updateListeners();

if (intersectingHandles.length > 0) {
Expand All @@ -93,10 +96,12 @@ function handlePointerMove(event: ResizeEvent) {
const { x, y } = getResizeEventCoordinates(event);

if (!isPointerDown) {
const { target } = event;

// Recalculate intersecting handles whenever the pointer moves, except if it has already been pressed
// at that point, the handles may not move with the pointer (depending on constraints)
// but the same set of active handles should be locked until the pointer is released
recalculateIntersectingHandles({ x, y });
recalculateIntersectingHandles({ target, x, y });
}

updateResizeHandlerStates("move", event);
Expand All @@ -110,6 +115,7 @@ function handlePointerMove(event: ResizeEvent) {
}

function handlePointerUp(event: ResizeEvent) {
const { target } = event;
const { x, y } = getResizeEventCoordinates(event);

panelConstraintFlags.clear();
Expand All @@ -119,16 +125,29 @@ function handlePointerUp(event: ResizeEvent) {
event.preventDefault();
}

recalculateIntersectingHandles({ x, y });
updateResizeHandlerStates("up", event);
recalculateIntersectingHandles({ target, x, y });
updateCursor();

updateListeners();
}

function recalculateIntersectingHandles({ x, y }: { x: number; y: number }) {
function recalculateIntersectingHandles({
target,
x,
y,
}: {
target: EventTarget | null;
x: number;
y: number;
}) {
intersectingHandles.splice(0);

let targetElement: HTMLElement | null = null;
if (target instanceof HTMLElement) {
targetElement = target;
}

registeredResizeHandlers.forEach((data) => {
const { element, hitAreaMargins } = data;
const { bottom, left, right, top } = element.getBoundingClientRect();
Expand All @@ -137,13 +156,37 @@ function recalculateIntersectingHandles({ x, y }: { x: number; y: number }) {
? hitAreaMargins.coarse
: hitAreaMargins.fine;

const intersects =
const eventIntersects =
x >= left - margin &&
x <= right + margin &&
y >= top - margin &&
y <= bottom + margin;

if (intersects) {
if (eventIntersects) {
// TRICKY
// We listen for pointers events at the root in order to support hit area margins
// (determining when the pointer is close enough to an element to be considered a "hit")
// Clicking on an element "above" a handle (e.g. a modal) should prevent a hit though
// so at this point we need to compare stacking order of a potentially intersecting drag handle,
// and the element that was actually clicked/touched
//
// Calculating stacking order has a cost, so we should avoid it if possible
// That is why we only check potentially intersecting handles,
// and why we skip if the event target is within the handle's DOM
if (
targetElement !== null &&
element !== targetElement &&
!element.contains(targetElement) &&
!targetElement.contains(element) &&
compare(targetElement, element) > 0 &&
intersects(
targetElement.getBoundingClientRect(),
element.getBoundingClientRect()
)
) {
return;
}

intersectingHandles.push(data);
}
});
Expand Down
4 changes: 4 additions & 0 deletions packages/react-resizable-panels/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement";
import { getResizeHandleElementIndex } from "./utils/dom/getResizeHandleElementIndex";
import { getResizeHandleElementsForGroup } from "./utils/dom/getResizeHandleElementsForGroup";
import { getResizeHandlePanelIds } from "./utils/dom/getResizeHandlePanelIds";
import { getIntersectingRectangle } from "./utils/rects/getIntersectingRectangle";
import { intersects } from "./utils/rects/intersects";

import type {
ImperativePanelHandle,
Expand Down Expand Up @@ -49,6 +51,8 @@ export {

// Utility methods
assert,
getIntersectingRectangle,
intersects,

// DOM helpers
getPanelElement,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
export interface Rectangle {
x: number;
y: number;
width: number;
height: number;
}
import { intersects } from "./intersects";
import { Rectangle } from "./types";

export function getIntersectingRectangle(
rectOne: Rectangle,
Expand All @@ -29,12 +25,3 @@ export function getIntersectingRectangle(
Math.max(rectOne.y, rectTwo.y),
};
}

export function intersects(rectOne: Rectangle, rectTwo: Rectangle): boolean {
return (
rectOne.x <= rectTwo.x + rectTwo.width &&
rectOne.x + rectOne.width >= rectTwo.x &&
rectOne.y <= rectTwo.y + rectTwo.height &&
rectOne.y + rectOne.height >= rectTwo.y
);
}
10 changes: 10 additions & 0 deletions packages/react-resizable-panels/src/utils/rects/intersects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Rectangle } from "./types";

export function intersects(rectOne: Rectangle, rectTwo: Rectangle): boolean {
return (
rectOne.x <= rectTwo.x + rectTwo.width &&
rectOne.x + rectOne.width >= rectTwo.x &&
rectOne.y <= rectTwo.y + rectTwo.height &&
rectOne.y + rectOne.height >= rectTwo.y
);
}
6 changes: 6 additions & 0 deletions packages/react-resizable-panels/src/utils/rects/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Rectangle {
x: number;
y: number;
width: number;
height: number;
}

0 comments on commit 7511d51

Please sign in to comment.