Skip to content

Commit 876791b

Browse files
committed
Panel expand() API should restore pre-collapse size
1 parent f682f6e commit 876791b

File tree

7 files changed

+84
-52
lines changed

7 files changed

+84
-52
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 4.0.16
4+
5+
- [563](https://github.com/bvaughn/react-resizable-panels/pull/563): Panel `expand()` API should restore pre-collapse size
6+
37
## 4.0.15
48

59
- [556](https://github.com/bvaughn/react-resizable-panels/pull/556): Ignore `defaultLayout` when keys don't match Panel ids

lib/components/panel/Panel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export function Panel({
7070
if (element !== null) {
7171
return registerPanel({
7272
element,
73+
expandToSizeRef: { current: undefined },
7374
id,
7475
idIsStable,
7576
onResize: hasOnResize ? onResizeStable : undefined,

lib/components/panel/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CSSProperties, HTMLAttributes, Ref } from "react";
1+
import type { CSSProperties, HTMLAttributes, Ref, RefObject } from "react";
22

33
export type PanelSize = {
44
asPercentage: number;
@@ -22,6 +22,7 @@ export type SizeUnit = "px" | "%" | "em" | "rem" | "vh" | "vw";
2222

2323
export type RegisteredPanel = {
2424
element: HTMLDivElement;
25+
expandToSizeRef: RefObject<number | undefined>;
2526
id: string;
2627
idIsStable: boolean;
2728
onResize: OnPanelResize | undefined;

lib/global/test/mockGroup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export function mockGroup(
9292

9393
const panel: RegisteredPanel = {
9494
element,
95+
expandToSizeRef: { current: undefined },
9596
id: panelId,
9697
idIsStable: true,
9798
panelConstraints: constraints,

lib/global/utils/getImperativePanelMethods.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,26 @@ describe("getImperativePanelMethods", () => {
172172
expect(onLayoutChange).not.toHaveBeenCalled();
173173
});
174174

175-
test("expands panel to the minimum size", () => {
175+
test("expands the panel to the previous pre-collapse size", () => {
176+
const { panelApis } = init([
177+
{ collapsible: true, defaultSize: 50, minSize: 25 },
178+
{}
179+
]);
180+
181+
panelApis[0].resize("35");
182+
expect(onLayoutChange).toHaveBeenCalledTimes(1);
183+
expect(onLayoutChange).toHaveBeenLastCalledWith([35, 65]);
184+
185+
panelApis[0].collapse();
186+
expect(onLayoutChange).toHaveBeenCalledTimes(2);
187+
expect(onLayoutChange).toHaveBeenLastCalledWith([0, 100]);
188+
189+
panelApis[0].expand();
190+
expect(onLayoutChange).toHaveBeenCalledTimes(3);
191+
expect(onLayoutChange).toHaveBeenLastCalledWith([35, 65]);
192+
});
193+
194+
test("expands panel to the minimum size as a fallback", () => {
176195
const { panelApis } = init([
177196
{ collapsible: true, defaultSize: 0, minSize: 25 },
178197
{}
@@ -182,6 +201,18 @@ describe("getImperativePanelMethods", () => {
182201
expect(onLayoutChange).toHaveBeenCalledTimes(1);
183202
expect(onLayoutChange).toHaveBeenCalledWith([25, 75]);
184203
});
204+
205+
// See github.com/bvaughn/react-resizable-panels/issues/561
206+
test("edge case: expands panel to a non-zero size if minSize is 0", () => {
207+
const { panelApis } = init([
208+
{ collapsible: true, defaultSize: 0, minSize: 0 },
209+
{}
210+
]);
211+
panelApis[0].expand();
212+
213+
expect(onLayoutChange).toHaveBeenCalledTimes(1);
214+
expect(onLayoutChange).toHaveBeenCalledWith([5, 95]);
215+
});
185216
});
186217

187218
describe("getSize", () => {

lib/global/utils/getImperativePanelMethods.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,31 @@ export function getImperativePanelMethods({
113113
return {
114114
collapse: () => {
115115
const { collapsible, collapsedSize } = getPanelConstraints();
116+
const { expandToSizeRef } = getPanel();
116117
const size = getPanelSize();
117118

118119
if (collapsible && size !== collapsedSize) {
120+
// Store previous size in to restore if expand() is called
121+
expandToSizeRef.current = size;
122+
119123
setPanelSize(collapsedSize);
120124
}
121125
},
122126
expand: () => {
123127
const { collapsible, collapsedSize, minSize } = getPanelConstraints();
128+
const { expandToSizeRef } = getPanel();
124129
const size = getPanelSize();
125130

126131
if (collapsible && size === collapsedSize) {
127-
setPanelSize(minSize);
132+
// Restore pre-collapse size, fallback to minSize
133+
let nextSize = expandToSizeRef.current ?? minSize;
134+
135+
// Edge case: if minSize is 0, pick something meaningful to expand the panel to
136+
if (nextSize === 0) {
137+
nextSize = 5;
138+
}
139+
140+
setPanelSize(nextSize);
128141
}
129142
},
130143
getSize: () => {

src/routes/LayoutBasicsRoute.tsx

Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,39 @@
1-
import { Box, Callout, Code, ExternalLink, Header } from "react-lib-tools";
2-
import { html as HorizontalHTML } from "../../public/generated/examples/LayoutBasicsHorizontal.json";
3-
import { html as SeparatorHTML } from "../../public/generated/examples/LayoutBasicsSeparator.json";
4-
import { html as VerticalHTML } from "../../public/generated/examples/LayoutBasicsVertical.json";
1+
import { useDefaultLayout, usePanelRef } from "react-resizable-panels";
52
import { Group } from "../components/styled-panels/Group";
63
import { Panel } from "../components/styled-panels/Panel";
74
import { Separator } from "../components/styled-panels/Separator";
85

96
export default function LayoutBasicsRoute() {
7+
const { defaultLayout, onLayoutChange } = useDefaultLayout({
8+
id: "1",
9+
storage: localStorage
10+
});
11+
12+
const panelRef = usePanelRef();
13+
14+
const togglePanel = () => {
15+
const panel = panelRef.current;
16+
if (panel) {
17+
if (panel.isCollapsed()) {
18+
panel.expand();
19+
} else {
20+
panel.collapse();
21+
}
22+
}
23+
};
24+
1025
return (
11-
<Box direction="column" gap={4}>
12-
<Header section="Examples" title="The basics" />
13-
<div>
14-
The simplest resizable panel configuration is two panels within a group.
15-
</div>
16-
<Code html={HorizontalHTML} />
17-
<Group className="h-15">
18-
<Panel>left</Panel>
19-
<Panel>right</Panel>
20-
</Group>
21-
<div>
22-
Panel groups use a{" "}
23-
<ExternalLink href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Flexible_box_layout/Basic_concepts">
24-
flexbox
25-
</ExternalLink>{" "}
26-
layout with a default orientation of <em>horizontal</em> but the{" "}
27-
<code>orientation</code> prop can be used to specify a <em>vertical</em>{" "}
28-
layout.
29-
</div>
30-
<Code html={VerticalHTML} />
31-
<Callout intent="warning">
32-
Vertical groups require an explicit height to be set using either{" "}
33-
<code>className</code> or <code>style</code> props.
34-
</Callout>
35-
<Group className="h-30" orientation="vertical">
36-
<Panel>top</Panel>
37-
<Panel>bottom</Panel>
38-
</Group>
39-
<div>
40-
Panels can be resized by clicking on their borders but explicit
41-
separators can be rendered to improve UX.
42-
</div>
43-
<Group className="h-15">
44-
<Panel>left</Panel>
45-
<Separator orientation="horizontal" />
46-
<Panel>right</Panel>
26+
<div>
27+
<button onClick={togglePanel}>Toggle Panel</button>
28+
<Group defaultLayout={defaultLayout} onLayoutChange={onLayoutChange}>
29+
<Panel id="panel1" defaultSize={30}>
30+
Panel 1
31+
</Panel>
32+
<Separator />
33+
<Panel id="panel2" defaultSize={70} collapsible panelRef={panelRef}>
34+
Panel 2
35+
</Panel>
4736
</Group>
48-
<Code html={SeparatorHTML} />
49-
<Callout intent="primary">
50-
Separators improve keyboard accessibility by providing a tab-focusable{" "}
51-
<ExternalLink href="https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/">
52-
window splitter
53-
</ExternalLink>{" "}
54-
element.
55-
</Callout>
56-
</Box>
37+
</div>
5738
);
5839
}

0 commit comments

Comments
 (0)