Skip to content

Commit

Permalink
Add optional 'minSize' override param to panel 'expand' imperative API
Browse files Browse the repository at this point in the history
  • Loading branch information
bvaughn committed Apr 29, 2024
1 parent 634284b commit efb2bdc
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,11 @@ export default function ImperativePanelApiRoute() {
<li>
<Code
className={sharedStyles.InlineCode}
code="expand(): void"
code="expand(defaultSize?: number): void"
language="typescript"
/>
Expand the panel to its previous size
Expand the panel to its previous size (or the default size if
there is no previous size)
</li>
<li>
<Code
Expand Down
4 changes: 4 additions & 0 deletions packages/react-resizable-panels/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 2.0.19

- Add optional `minSize` override param to panel `expand` imperative API

## 2.0.18

- Inline object `hitAreaMargins` will not trigger re-initialization logic unless inner values change (#342)
Expand Down
112 changes: 111 additions & 1 deletion packages/react-resizable-panels/src/Panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@ describe("PanelGroup", () => {
<PanelGroup direction="horizontal" onLayout={onLayout}>
<Panel collapsible defaultSize={50} ref={leftPanelRef} />
<PanelResizeHandle />
<Panel collapsible defaultSize={50} ref={rightPanelRef} />
<Panel
collapsible
defaultSize={50}
minSize={0}
ref={rightPanelRef}
/>
</PanelGroup>
);
});
Expand Down Expand Up @@ -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<ImperativePanelHandle>();

root = createRoot(container);

function renderPanelGroup() {
act(() => {
root.render(
<PanelGroup direction="horizontal">
<Panel
collapsible
defaultSize={0}
minSize={5}
ref={panelRef}
/>
<PanelResizeHandle />
<Panel />
</PanelGroup>
);
});
}

// 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<ImperativePanelHandle>();

root = createRoot(container);

function renderPanelGroup() {
act(() => {
root.render(
<PanelGroup autoSaveId="test" direction="horizontal">
<Panel
collapsible
defaultSize={0}
minSize={0}
ref={panelRef}
/>
<PanelResizeHandle />
<Panel />
</PanelGroup>
);
});
}

// 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", () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/react-resizable-panels/src/Panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type PanelData = {

export type ImperativePanelHandle = {
collapse: () => void;
expand: () => void;
expand: (minSize?: number) => void;
getId(): string;
getSize(): number;
isCollapsed: () => boolean;
Expand Down Expand Up @@ -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;
Expand Down
101 changes: 54 additions & 47 deletions packages/react-resizable-panels/src/PanelGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-resizable-panels/src/PanelGroupContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit efb2bdc

Please sign in to comment.