diff --git a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx index 409d3c023..080acd7e5 100644 --- a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx +++ b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx @@ -72,10 +72,11 @@ export default function ImperativePanelApiRoute() {
- Expand the panel to its previous size
+ Expand the panel to its previous size (or the default size if
+ there is no previous size)
{
-
+
);
});
@@ -193,6 +198,111 @@ describe("PanelGroup", () => {
expect(leftPanelRef.current?.isCollapsed()).toBe(false);
expect(leftPanelRef.current?.isExpanded()).toBe(true);
});
+
+ describe("when a panel is mounted in a collapsed state", () => {
+ beforeEach(() => {
+ act(() => {
+ root.unmount();
+ });
+ });
+
+ it("should expand to the panel's minSize", () => {
+ const panelRef = createRef();
+
+ root = createRoot(container);
+
+ function renderPanelGroup() {
+ act(() => {
+ root.render(
+
+
+
+
+
+ );
+ });
+ }
+
+ // Re-render and confirmed collapsed by default
+ renderPanelGroup();
+ act(() => {
+ panelRef.current?.collapse();
+ });
+ expect(panelRef.current?.getSize()).toEqual(0);
+
+ // Toggling a panel should expand to the minSize (since there's no previous size to restore to)
+ act(() => {
+ panelRef.current?.expand();
+ });
+ expect(panelRef.current?.getSize()).toEqual(5);
+
+ // Collapse again
+ act(() => {
+ panelRef.current?.collapse();
+ });
+ expect(panelRef.current?.getSize()).toEqual(0);
+
+ // Toggling the panel should expand to the minSize override if one is specified
+ // Note this only works because the previous non-collapsed size is less than the minSize override
+ act(() => {
+ panelRef.current?.expand(15);
+ });
+ expect(panelRef.current?.getSize()).toEqual(15);
+ });
+
+ it("should support the (optional) default size", () => {
+ const panelRef = createRef();
+
+ root = createRoot(container);
+
+ function renderPanelGroup() {
+ act(() => {
+ root.render(
+
+
+
+
+
+ );
+ });
+ }
+
+ // Re-render and confirmed collapsed by default
+ renderPanelGroup();
+ act(() => {
+ panelRef.current?.collapse();
+ });
+ expect(panelRef.current?.getSize()).toEqual(0);
+
+ // In this case, toggling the panel to expanded will not change its size
+ act(() => {
+ panelRef.current?.expand();
+ });
+ expect(panelRef.current?.getSize()).toEqual(0);
+
+ // But we can override the toggle behavior by passing an explicit min size
+ act(() => {
+ panelRef.current?.expand(10);
+ });
+ expect(panelRef.current?.getSize()).toEqual(10);
+
+ // Toggling an already-expanded panel should not do anything even if we pass a default size
+ act(() => {
+ panelRef.current?.expand(15);
+ });
+ expect(panelRef.current?.getSize()).toEqual(10);
+ });
+ });
});
describe("resize", () => {
diff --git a/packages/react-resizable-panels/src/Panel.ts b/packages/react-resizable-panels/src/Panel.ts
index 68a1f21de..b71eb6f2e 100644
--- a/packages/react-resizable-panels/src/Panel.ts
+++ b/packages/react-resizable-panels/src/Panel.ts
@@ -46,7 +46,7 @@ export type PanelData = {
export type ImperativePanelHandle = {
collapse: () => void;
- expand: () => void;
+ expand: (minSize?: number) => void;
getId(): string;
getSize(): number;
isCollapsed: () => boolean;
@@ -200,8 +200,8 @@ export function PanelWithForwardedRef({
collapse: () => {
collapsePanel(panelDataRef.current);
},
- expand: () => {
- expandPanel(panelDataRef.current);
+ expand: (minSize?: number) => {
+ expandPanel(panelDataRef.current, minSize);
},
getId() {
return panelId;
diff --git a/packages/react-resizable-panels/src/PanelGroup.ts b/packages/react-resizable-panels/src/PanelGroup.ts
index b51ff3a12..48c203b01 100644
--- a/packages/react-resizable-panels/src/PanelGroup.ts
+++ b/packages/react-resizable-panels/src/PanelGroup.ts
@@ -386,65 +386,72 @@ function PanelGroupWithForwardedRef({
}, []);
// External APIs are safe to memoize via committed values ref
- const expandPanel = useCallback((panelData: PanelData) => {
- const { onLayout } = committedValuesRef.current;
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
+ const expandPanel = useCallback(
+ (panelData: PanelData, minSizeOverride?: number) => {
+ const { onLayout } = committedValuesRef.current;
+ const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
- if (panelData.constraints.collapsible) {
- const panelConstraintsArray = panelDataArray.map(
- (panelData) => panelData.constraints
- );
+ if (panelData.constraints.collapsible) {
+ const panelConstraintsArray = panelDataArray.map(
+ (panelData) => panelData.constraints
+ );
- const {
- collapsedSize = 0,
- panelSize = 0,
- minSize = 0,
- pivotIndices,
- } = panelDataHelper(panelDataArray, panelData, prevLayout);
+ const {
+ collapsedSize = 0,
+ panelSize = 0,
+ minSize: minSizeFromProps = 0,
+ pivotIndices,
+ } = panelDataHelper(panelDataArray, panelData, prevLayout);
- if (fuzzyNumbersEqual(panelSize, collapsedSize)) {
- // Restore this panel to the size it was before it was collapsed, if possible.
- const prevPanelSize = panelSizeBeforeCollapseRef.current.get(
- panelData.id
- );
+ const minSize = minSizeOverride ?? minSizeFromProps;
- const baseSize =
- prevPanelSize != null && prevPanelSize >= minSize
- ? prevPanelSize
- : minSize;
+ if (fuzzyNumbersEqual(panelSize, collapsedSize)) {
+ // Restore this panel to the size it was before it was collapsed, if possible.
+ const prevPanelSize = panelSizeBeforeCollapseRef.current.get(
+ panelData.id
+ );
- const isLastPanel =
- findPanelDataIndex(panelDataArray, panelData) ===
- panelDataArray.length - 1;
- const delta = isLastPanel ? panelSize - baseSize : baseSize - panelSize;
+ const baseSize =
+ prevPanelSize != null && prevPanelSize >= minSize
+ ? prevPanelSize
+ : minSize;
+
+ const isLastPanel =
+ findPanelDataIndex(panelDataArray, panelData) ===
+ panelDataArray.length - 1;
+ const delta = isLastPanel
+ ? panelSize - baseSize
+ : baseSize - panelSize;
+
+ const nextLayout = adjustLayoutByDelta({
+ delta,
+ initialLayout: prevLayout,
+ panelConstraints: panelConstraintsArray,
+ pivotIndices,
+ prevLayout,
+ trigger: "imperative-api",
+ });
- const nextLayout = adjustLayoutByDelta({
- delta,
- initialLayout: prevLayout,
- panelConstraints: panelConstraintsArray,
- pivotIndices,
- prevLayout,
- trigger: "imperative-api",
- });
+ if (!compareLayouts(prevLayout, nextLayout)) {
+ setLayout(nextLayout);
- if (!compareLayouts(prevLayout, nextLayout)) {
- setLayout(nextLayout);
+ eagerValuesRef.current.layout = nextLayout;
- eagerValuesRef.current.layout = nextLayout;
+ if (onLayout) {
+ onLayout(nextLayout);
+ }
- if (onLayout) {
- onLayout(nextLayout);
+ callPanelCallbacks(
+ panelDataArray,
+ nextLayout,
+ panelIdToLastNotifiedSizeMapRef.current
+ );
}
-
- callPanelCallbacks(
- panelDataArray,
- nextLayout,
- panelIdToLastNotifiedSizeMapRef.current
- );
}
}
- }
- }, []);
+ },
+ []
+ );
// External APIs are safe to memoize via committed values ref
const getPanelSize = useCallback((panelData: PanelData) => {
diff --git a/packages/react-resizable-panels/src/PanelGroupContext.ts b/packages/react-resizable-panels/src/PanelGroupContext.ts
index d810ee083..02eef1abd 100644
--- a/packages/react-resizable-panels/src/PanelGroupContext.ts
+++ b/packages/react-resizable-panels/src/PanelGroupContext.ts
@@ -16,7 +16,7 @@ export type TPanelGroupContext = {
collapsePanel: (panelData: PanelData) => void;
direction: "horizontal" | "vertical";
dragState: DragState | null;
- expandPanel: (panelData: PanelData) => void;
+ expandPanel: (panelData: PanelData, minSizeOverride?: number) => void;
getPanelSize: (panelData: PanelData) => number;
getPanelStyle: (
panelData: PanelData,