Skip to content
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Changelog

## Unreleased
## 4.5.7

- [646](https://github.com/bvaughn/react-resizable-panels/pull/646): Re-enable the collapsible `Panel` from 4.5.3 that was disabled in 4.5.6
- [648](https://github.com/bvaughn/react-resizable-panels/pull/648): **Bugfix**: Reset `Separator` hover-state on `Document` "pointerout"

## 4.5.6

Expand Down
9 changes: 9 additions & 0 deletions integrations/tests/src/components/IFrame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { type PropsWithChildren } from "react";

export type IFrameProps = PropsWithChildren<{
className?: string | undefined;
}>;

export function IFrame({ className }: IFrameProps) {
return <iframe className={className} />;
}
13 changes: 13 additions & 0 deletions integrations/tests/src/utils/serializer/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import type {
EncodedDisplayModeToggleElement,
EncodedElement,
EncodedGroupElement,
EncodedIFrameElement,
EncodedPanelElement,
EncodedPopupWindowElement,
EncodedSeparatorElement,
EncodedTextElement,
TextProps
} from "./types";
import { IFrame } from "../../components/IFrame";

type Config = {
groupProps?: Partial<GroupProps>;
Expand Down Expand Up @@ -65,6 +67,10 @@ function decodeChildren(
elements.push(decodeGroup(current, config));
break;
}
case "IFrame": {
elements.push(decodeIFrame(current));
break;
}
case "Panel": {
elements.push(decodePanel(current, config));
break;
Expand Down Expand Up @@ -137,6 +143,13 @@ function decodeGroup(
});
}

function decodeIFrame(json: EncodedIFrameElement): ReactElement<PanelProps> {
return createElement(IFrame, {
key: ++key,
...json.props
});
}

function decodePanel(
json: EncodedPanelElement,
config: Config
Expand Down
15 changes: 15 additions & 0 deletions integrations/tests/src/utils/serializer/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ import type {
EncodedDisplayModeToggleElement,
EncodedElement,
EncodedGroupElement,
EncodedIFrameElement,
EncodedPanelElement,
EncodedPopupWindowElement,
EncodedSeparatorElement,
EncodedTextElement,
TextProps
} from "./types";
import { IFrame, type IFrameProps } from "../../components/IFrame";

export function encode(element: ReactElement<unknown>) {
const json = encodeChildren([element]);
Expand Down Expand Up @@ -72,6 +74,10 @@ function encodeChildren(children: ReactElement<unknown>[]): EncodedElement[] {
elements.push(encodeGroup(current as ReactElement<GroupProps>));
break;
}
case IFrame: {
elements.push(encodeIFrame(current as ReactElement<IFrameProps>));
break;
}
case Panel: {
elements.push(encodePanel(current as ReactElement<PanelProps>));
break;
Expand Down Expand Up @@ -163,6 +169,15 @@ function encodeGroup(element: ReactElement<GroupProps>): EncodedGroupElement {
};
}

function encodeIFrame(
element: ReactElement<IFrameProps>
): EncodedIFrameElement {
return {
props: element.props,
type: "IFrame"
};
}

function encodePanel(element: ReactElement<PanelProps>): EncodedPanelElement {
const { children, onResize: __, ...props } = element.props;

Expand Down
7 changes: 7 additions & 0 deletions integrations/tests/src/utils/serializer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ContainerProps } from "../../../src/components/Container";
import type { DisplayModeToggleProps } from "../../../src/components/DisplayModeToggle";
import type { PopupWindowProps } from "../../../src/components/PopupWindow";
import type { ClickableProps } from "../../../src/components/Clickable";
import type { IFrameProps } from "../../components/IFrame";

type EncodedElementWithChildren<Props extends object = object> = Omit<
Props,
Expand All @@ -33,6 +34,11 @@ export interface EncodedGroupElement {
type: "Group";
}

export interface EncodedIFrameElement {
props: IFrameProps;
type: "IFrame";
}

export interface EncodedPanelElement {
props: EncodedElementWithChildren<PanelProps>;
type: "Panel";
Expand Down Expand Up @@ -63,6 +69,7 @@ export type EncodedElement =
| EncodedContainerElement
| EncodedDisplayModeToggleElement
| EncodedGroupElement
| EncodedIFrameElement
| EncodedPanelElement
| EncodedPopupWindowElement
| EncodedSeparatorElement
Expand Down
30 changes: 30 additions & 0 deletions integrations/tests/tests/pointer-interactions.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect, test } from "@playwright/test";
import { Group, Panel, Separator } from "react-resizable-panels";
import { Clickable } from "../src/components/Clickable";
import { Container } from "../src/components/Container";
import { IFrame } from "../src/components/IFrame";
import { assertLayoutChangeCounts } from "../src/utils/assertLayoutChangeCounts";
import { calculateHitArea } from "../src/utils/calculateHitArea";
import { getCenterCoordinates } from "../src/utils/getCenterCoordinates";
Expand Down Expand Up @@ -532,6 +533,35 @@ test.describe("pointer interactions", () => {
}
});
});

// See github.com/bvaughn/react-resizable-panels/issues/645
test("should update separator state when the cursor mouses over an iframe", async ({
page: mainPage
}) => {
const page = await goToUrl(
mainPage,
<Group className="gap-0!">
<Panel className="p-0! *:p-0!" id="left">
<IFrame className="w-full h-full" />
</Panel>
<Separator id="separator" />
<Panel className="p-0! *:p-0!" id="right" />
</Group>,
{ usePopUpWindow }
);

const separator = page.getByTestId("separator");

const { x, y } = getCenterCoordinates((await separator.boundingBox())!);

await expect(separator).toHaveAttribute("data-separator", "inactive");

await page.mouse.move(x, y);
await expect(separator).toHaveAttribute("data-separator", "hover");

await page.mouse.move(x - 25, y);
await expect(separator).toHaveAttribute("data-separator", "inactive");
});
});
}
});
20 changes: 20 additions & 0 deletions lib/global/event-handlers/onDocumentPointerOut.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { read, update } from "../mutableState";

export function onDocumentPointerOut(event: PointerEvent) {
// For some reason, "pointerout" events don't fire if the `relatedTarget` is an iframe
// This can leave the `data-separator` attribute in an invalid state ("hover") which in turn might break styles
// The easiest fix for this case is to reset the interaction state in this specific circumstance
// See issues/645
if (event.relatedTarget instanceof HTMLIFrameElement) {
const { interactionState } = read();
switch (interactionState.state) {
case "hover": {
update({
interactionState: {
state: "inactive"
}
});
}
}
}
}
3 changes: 3 additions & 0 deletions lib/global/mountGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { onDocumentKeyDown } from "./event-handlers/onDocumentKeyDown";
import { onDocumentPointerDown } from "./event-handlers/onDocumentPointerDown";
import { onDocumentPointerLeave } from "./event-handlers/onDocumentPointerLeave";
import { onDocumentPointerMove } from "./event-handlers/onDocumentPointerMove";
import { onDocumentPointerOut } from "./event-handlers/onDocumentPointerOut";
import { onDocumentPointerUp } from "./event-handlers/onDocumentPointerUp";
import { update, type SeparatorToPanelsMap } from "./mutableState";
import { calculateDefaultLayout } from "./utils/calculateDefaultLayout";
Expand Down Expand Up @@ -175,6 +176,7 @@ export function mountGroup(group: RegisteredGroup) {
ownerDocument.addEventListener("pointerdown", onDocumentPointerDown, true);
ownerDocument.addEventListener("pointerleave", onDocumentPointerLeave);
ownerDocument.addEventListener("pointermove", onDocumentPointerMove);
ownerDocument.addEventListener("pointerout", onDocumentPointerOut);
ownerDocument.addEventListener("pointerup", onDocumentPointerUp, true);
}

Expand Down Expand Up @@ -211,6 +213,7 @@ export function mountGroup(group: RegisteredGroup) {
);
ownerDocument.removeEventListener("pointerleave", onDocumentPointerLeave);
ownerDocument.removeEventListener("pointermove", onDocumentPointerMove);
ownerDocument.removeEventListener("pointerout", onDocumentPointerOut);
ownerDocument.removeEventListener("pointerup", onDocumentPointerUp, true);
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-resizable-panels",
"version": "4.5.6",
"version": "4.5.7",
"type": "module",
"author": "Brian Vaughn <brian.david.vaughn@gmail.com> (https://github.com/bvaughn/)",
"contributors": [
Expand Down
Loading