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 f35c2b162..409d3c023 100644
--- a/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx
+++ b/packages/react-resizable-panels-website/src/routes/examples/ImperativePanelApi.tsx
@@ -221,6 +221,7 @@ function Content({
{
@@ -64,8 +58,7 @@ test.describe("Storage", () => {
now: 80,
});
- // Wait for localStorage write debounce
- await new Promise((resolve) => setTimeout(resolve, 250));
+ await waitForLocalStorageWrite();
// Values should be remembered after a page reload
await page.reload();
@@ -80,6 +73,22 @@ test.describe("Storage", () => {
test("should store layouts separately per panel combination", async ({
page,
}) => {
+ const panelGroupBC = createElement(
+ PanelGroup,
+ { autoSaveId: "test-group", direction: "horizontal" },
+ createElement(Panel, { minSize: 10, order: 2 }),
+ createElement(PanelResizeHandle),
+ createElement(Panel, { minSize: 10, order: 3 })
+ );
+
+ const panelGroupAB = createElement(
+ PanelGroup,
+ { autoSaveId: "test-group", direction: "horizontal" },
+ createElement(Panel, { minSize: 10, order: 1 }),
+ createElement(PanelResizeHandle),
+ createElement(Panel, { minSize: 10, order: 2 })
+ );
+
await goToUrl(page, panelGroupABC);
const resizeHandles = page.locator("[data-panel-resize-handle-id]");
@@ -93,8 +102,7 @@ test.describe("Storage", () => {
now: 33,
});
- // Wait for localStorage write debounce
- await new Promise((resolve) => setTimeout(resolve, 250));
+ await waitForLocalStorageWrite();
// Hide the first panel and then resize things
await goToUrl(page, panelGroupBC);
@@ -104,8 +112,7 @@ test.describe("Storage", () => {
now: 10,
});
- // Wait for localStorage write debounce
- await new Promise((resolve) => setTimeout(resolve, 250));
+ await waitForLocalStorageWrite();
// Hide the last panel and then resize things
await goToUrl(page, panelGroupAB);
@@ -115,8 +122,7 @@ test.describe("Storage", () => {
now: 90,
});
- // Wait for localStorage write debounce
- await new Promise((resolve) => setTimeout(resolve, 250));
+ await waitForLocalStorageWrite();
// Reload and verify all of the different layouts are remembered individually
await goToUrl(page, panelGroupABC);
@@ -137,5 +143,105 @@ test.describe("Storage", () => {
now: 90,
});
});
+
+ test("should remember the most recent expanded size for collapsed panels", async ({
+ page,
+ }) => {
+ const panelGroup = createElement(
+ PanelGroup,
+ { autoSaveId: "test-group", direction: "horizontal" },
+ createElement(Panel, {
+ collapsible: true,
+ id: "left",
+ minSize: 10,
+ order: 1,
+ }),
+ createElement(PanelResizeHandle),
+ createElement(Panel, {
+ collapsible: true,
+ id: "middle",
+ minSize: 10,
+ order: 2,
+ }),
+ createElement(PanelResizeHandle),
+ createElement(Panel, {
+ collapsible: true,
+ id: "right",
+ minSize: 10,
+ order: 3,
+ })
+ );
+
+ await goToUrl(page, panelGroup);
+
+ const resizeHandles = page.locator("[data-panel-resize-handle-id]");
+ const first = resizeHandles.first();
+ const last = resizeHandles.last();
+
+ await verifyAriaValues(first, {
+ now: 33,
+ });
+ await verifyAriaValues(last, {
+ now: 33,
+ });
+
+ // Change panel sizes
+ await first.focus();
+ await page.keyboard.press("ArrowLeft");
+ await last.focus();
+ await page.keyboard.press("ArrowRight");
+
+ // Verify sizes
+ await verifyAriaValues(first, {
+ now: 23,
+ });
+ await verifyAriaValues(last, {
+ now: 53,
+ });
+
+ await waitForLocalStorageWrite();
+
+ // Collapse panels
+ await imperativeCollapsePanel(page, "left");
+ await imperativeCollapsePanel(page, "right");
+
+ // Verify sizes
+ await verifyAriaValues(first, {
+ now: 0,
+ });
+ await verifyAriaValues(last, {
+ now: 100,
+ });
+
+ await waitForLocalStorageWrite();
+
+ // Reload page
+ await page.reload();
+
+ // Verify collapsed sizes resized
+ await verifyAriaValues(first, {
+ now: 0,
+ });
+ await verifyAriaValues(last, {
+ now: 100,
+ });
+
+ // Expand panels
+ await imperativeExpandPanel(page, "left");
+ await imperativeExpandPanel(page, "right");
+
+ // Verify sizes
+ await verifyAriaValues(first, {
+ now: 23,
+ });
+ await verifyAriaValues(last, {
+ now: 53,
+ });
+ });
});
});
+
+// Wait for localStorage write debounce
+async function waitForLocalStorageWrite() {
+ await new Promise((resolve) => setTimeout(resolve, 250));
+}
diff --git a/packages/react-resizable-panels-website/tests/utils/panels.ts b/packages/react-resizable-panels-website/tests/utils/panels.ts
index f50c4f18e..1d41e1c2d 100644
--- a/packages/react-resizable-panels-website/tests/utils/panels.ts
+++ b/packages/react-resizable-panels-website/tests/utils/panels.ts
@@ -149,6 +149,22 @@ export async function dragResizeTo(
await page.mouse.up();
}
+export async function imperativeCollapsePanel(page: Page, panelId: string) {
+ const panelIdSelect = page.locator("#panelIdSelect");
+ await panelIdSelect.selectOption(panelId);
+
+ const button = page.locator("#collapseButton");
+ await button.click();
+}
+
+export async function imperativeExpandPanel(page: Page, panelId: string) {
+ const panelIdSelect = page.locator("#panelIdSelect");
+ await panelIdSelect.selectOption(panelId);
+
+ const button = page.locator("#expandButton");
+ await button.click();
+}
+
export async function imperativeResizePanel(
page: Page,
panelId: string,
@@ -161,8 +177,8 @@ export async function imperativeResizePanel(
await sizeInput.focus();
await sizeInput.fill(`${size}%`);
- const resizeButton = page.locator("#resizeButton");
- await resizeButton.click();
+ const button = page.locator("#resizeButton");
+ await button.click();
}
export async function imperativeResizePanelGroup(
diff --git a/packages/react-resizable-panels/CHANGELOG.md b/packages/react-resizable-panels/CHANGELOG.md
index 763405abb..4875311f0 100644
--- a/packages/react-resizable-panels/CHANGELOG.md
+++ b/packages/react-resizable-panels/CHANGELOG.md
@@ -1,8 +1,12 @@
# Changelog
+## Unreleased
+
+- Remember most recently expanded panel size in local storage (#234)
+
## 1.0.2
-- Change local storage key for persisted sizes to avoid restoring pixel-based sizes (see #233)
+- Change local storage key for persisted sizes to avoid restoring pixel-based sizes (#233)
## 1.0.1
diff --git a/packages/react-resizable-panels/src/PanelGroup.ts b/packages/react-resizable-panels/src/PanelGroup.ts
index b9c03de4b..e78ec4f55 100644
--- a/packages/react-resizable-panels/src/PanelGroup.ts
+++ b/packages/react-resizable-panels/src/PanelGroup.ts
@@ -20,7 +20,10 @@ import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement";
import { isKeyDown, isMouseEvent, isTouchEvent } from "./utils/events";
import { getResizeEventCursorPosition } from "./utils/getResizeEventCursorPosition";
import { initializeDefaultStorage } from "./utils/initializeDefaultStorage";
-import { loadPanelLayout, savePanelGroupLayout } from "./utils/serialization";
+import {
+ loadPanelGroupState,
+ savePanelGroupState,
+} from "./utils/serialization";
import { validatePanelConstraints } from "./utils/validatePanelConstraints";
import { validatePanelGroupLayout } from "./utils/validatePanelGroupLayout";
import {
@@ -79,7 +82,7 @@ export type PanelGroupProps = Omit, "id"> &
}>;
const debounceMap: {
- [key: string]: typeof savePanelGroupLayout;
+ [key: string]: typeof savePanelGroupState;
} = {};
function PanelGroupWithForwardedRef({
@@ -102,7 +105,6 @@ function PanelGroupWithForwardedRef({
const [dragState, setDragState] = useState(null);
const [layout, setLayout] = useState([]);
- const [panelDataArray, setPanelDataArray] = useState([]);
const panelIdToLastNotifiedSizeMapRef = useRef>({});
const panelSizeBeforeCollapseRef = useRef