Skip to content

Commit ff05812

Browse files
committed
Add onLayoutChanged prop to Group
1 parent 77976d3 commit ff05812

27 files changed

+993
-338
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,16 @@ Use this prop to disable that behavior for Panels and Separators in this group.<
120120
</tr>
121121
<tr>
122122
<td>onLayoutChange</td>
123-
<td><p>Called when panel sizes change; receives a map of Panel id to size.</p>
123+
<td><p>Called when the Group&#39;s layout is changing.</p>
124+
<p>⚠️ For layout changes caused by pointer events, this method is called each time the pointer is moved.
125+
For most cases, it is recommended to use the <code>onLayoutChanged</code> callback instead.</p>
126+
</td>
127+
</tr>
128+
<tr>
129+
<td>onLayoutChanged</td>
130+
<td><p>Called after the Group&#39;s layout has been changed.</p>
131+
<p>ℹ️ For layout changes caused by pointer events, this method is not called until the pointer has been released.
132+
This method is recommended when saving layouts to some storage api.</p>
124133
</td>
125134
</tr>
126135
<tr>

integrations/tests/src/components/Decoder.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ export function Decoder({
6464
imperativeGroupApiLayout: Layout | undefined;
6565
imperativePanelApiSize: PanelSize | undefined;
6666
layout: Layout;
67-
onLayoutCount: number;
67+
onLayoutChangeCount: number;
68+
onLayoutChangedCount: number;
6869
panels: {
6970
[id: number | string]: {
7071
onResizeCount: number;
@@ -75,7 +76,8 @@ export function Decoder({
7576
imperativeGroupApiLayout: undefined,
7677
imperativePanelApiSize: undefined,
7778
layout: {},
78-
onLayoutCount: 0,
79+
onLayoutChangeCount: 0,
80+
onLayoutChangedCount: 0,
7981
panels: {}
8082
});
8183

@@ -94,7 +96,14 @@ export function Decoder({
9496

9597
setState((prev) => ({
9698
...prev,
97-
onLayoutCount: prev.onLayoutCount + 1,
99+
onLayoutChangeCount: prev.onLayoutChangeCount + 1,
100+
layout
101+
}));
102+
},
103+
onLayoutChanged: (layout) => {
104+
setState((prev) => ({
105+
...prev,
106+
onLayoutChangedCount: prev.onLayoutChangedCount + 1,
98107
layout
99108
}));
100109
}
@@ -153,7 +162,8 @@ export function Decoder({
153162
<DebugData
154163
data={{
155164
layout: state.layout,
156-
onLayoutCount: state.onLayoutCount
165+
onLayoutChangeCount: state.onLayoutChangeCount,
166+
onLayoutChangedCount: state.onLayoutChangedCount
157167
}}
158168
/>
159169
{Array.from(Object.keys(state.panels)).map((panelId) => (
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { expect, type Page } from "@playwright/test";
2+
3+
export async function assertLayoutChangeCounts(
4+
page: Page,
5+
layoutChangeCount: number,
6+
layoutChangedCount: number | undefined = layoutChangeCount
7+
) {
8+
await expect(
9+
page.getByText(`"onLayoutChangeCount": ${layoutChangeCount}`)
10+
).toBeVisible();
11+
await expect(
12+
page.getByText(`"onLayoutChangedCount": ${layoutChangedCount}`)
13+
).toBeVisible();
14+
}

integrations/tests/src/utils/expectLayout.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@ import type { Layout } from "react-resizable-panels";
44
export async function expectLayout({
55
layout,
66
mainPage,
7-
onLayoutCount
7+
onLayoutChangeCount,
8+
onLayoutChangedCount
89
}: {
910
layout: Layout;
1011
mainPage: Page;
11-
onLayoutCount: number;
12+
onLayoutChangeCount: number;
13+
onLayoutChangedCount: number;
1214
}) {
1315
await expect(mainPage.getByText('"layout"')).toHaveText(
1416
JSON.stringify(
1517
{
1618
layout,
17-
onLayoutCount
19+
onLayoutChangeCount,
20+
onLayoutChangedCount
1821
},
1922
null,
2023
2

integrations/tests/tests/fixed-size-elements.spec.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { expect, test } from "@playwright/test";
1+
import { test } from "@playwright/test";
22
import { Group, Panel, Separator } from "react-resizable-panels";
3+
import { assertLayoutChangeCounts } from "../src/utils/assertLayoutChangeCounts";
34
import { goToUrl } from "../src/utils/goToUrl";
45

56
test.describe("fixed size elements", () => {
@@ -23,7 +24,7 @@ test.describe("fixed size elements", () => {
2324
{ usePopUpWindow }
2425
);
2526

26-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
27+
await assertLayoutChangeCounts(mainPage, 1);
2728

2829
for (const text of ["foo+bar", "bar+baz", "baz+qux"]) {
2930
const box = (await page.getByText(text).boundingBox())!;
@@ -34,7 +35,7 @@ test.describe("fixed size elements", () => {
3435
await page.mouse.up();
3536
}
3637

37-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
38+
await assertLayoutChangeCounts(mainPage, 1);
3839
});
3940

4041
test("should work without an explicit separator", async ({
@@ -57,7 +58,7 @@ test.describe("fixed size elements", () => {
5758
{ usePopUpWindow }
5859
);
5960

60-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
61+
await assertLayoutChangeCounts(mainPage, 1);
6162

6263
const boxFoo = (await page.getByText("id: foo").boundingBox())!;
6364

@@ -66,7 +67,7 @@ test.describe("fixed size elements", () => {
6667
await page.mouse.move(0, 0);
6768
await page.mouse.up();
6869

69-
await expect(mainPage.getByText('"onLayoutCount": 2')).toBeVisible();
70+
await assertLayoutChangeCounts(mainPage, 2);
7071

7172
const boxBar = (await page.getByText("id: bar").boundingBox())!;
7273

@@ -75,7 +76,7 @@ test.describe("fixed size elements", () => {
7576
await page.mouse.move(1000, 0);
7677
await page.mouse.up();
7778

78-
await expect(mainPage.getByText('"onLayoutCount": 3')).toBeVisible();
79+
await assertLayoutChangeCounts(mainPage, 3);
7980
});
8081

8182
test("should work with an explicit separator", async ({
@@ -105,7 +106,7 @@ test.describe("fixed size elements", () => {
105106
await page.mouse.move(0, 0);
106107
await page.mouse.up();
107108

108-
await expect(mainPage.getByText('"onLayoutCount": 2')).toBeVisible();
109+
await assertLayoutChangeCounts(mainPage, 2);
109110

110111
const boxBaz = (await page.getByText("id: baz").boundingBox())!;
111112

@@ -114,7 +115,7 @@ test.describe("fixed size elements", () => {
114115
await page.mouse.move(1000, 0);
115116
await page.mouse.up();
116117

117-
await expect(mainPage.getByText('"onLayoutCount": 3')).toBeVisible();
118+
await assertLayoutChangeCounts(mainPage, 3);
118119
});
119120

120121
test("should work with two explicit separators", async ({
@@ -146,7 +147,7 @@ test.describe("fixed size elements", () => {
146147
await page.mouse.move(0, 0);
147148
await page.mouse.up();
148149

149-
await expect(mainPage.getByText('"onLayoutCount": 2')).toBeVisible();
150+
await assertLayoutChangeCounts(mainPage, 2);
150151

151152
separatorBox = (await page.getByTestId("baz+qux+right").boundingBox())!;
152153

@@ -155,7 +156,7 @@ test.describe("fixed size elements", () => {
155156
await page.mouse.move(1000, 0);
156157
await page.mouse.up();
157158

158-
await expect(mainPage.getByText('"onLayoutCount": 3')).toBeVisible();
159+
await assertLayoutChangeCounts(mainPage, 3);
159160
});
160161
});
161162
}

integrations/tests/tests/keyboard-interactions.spec.tsx

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { expect, test } from "@playwright/test";
22
import { Group, Panel, Separator } from "react-resizable-panels";
3+
import { assertLayoutChangeCounts } from "../src/utils/assertLayoutChangeCounts";
34
import { getSeparatorAriaAttributes } from "../src/utils/getSeparatorAriaAttributes";
45
import { goToUrl } from "../src/utils/goToUrl";
56

@@ -21,7 +22,7 @@ test.describe("keyboard interactions: window splitter api", () => {
2122
{ usePopUpWindow }
2223
);
2324

24-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
25+
await assertLayoutChangeCounts(mainPage, 1);
2526
await expect(mainPage.getByText('"left": 30')).toBeVisible();
2627

2728
await expect(await getSeparatorAriaAttributes(page)).toEqual({
@@ -36,7 +37,7 @@ test.describe("keyboard interactions: window splitter api", () => {
3637
await separator.focus();
3738
await page.keyboard.press("ArrowLeft");
3839

39-
await expect(mainPage.getByText('"onLayoutCount": 2')).toBeVisible();
40+
await assertLayoutChangeCounts(mainPage, 2);
4041
await expect(mainPage.getByText('"left": 25')).toBeVisible();
4142

4243
await expect(await getSeparatorAriaAttributes(page)).toEqual({
@@ -48,7 +49,7 @@ test.describe("keyboard interactions: window splitter api", () => {
4849

4950
await page.keyboard.press("ArrowRight");
5051

51-
await expect(mainPage.getByText('"onLayoutCount": 3')).toBeVisible();
52+
await assertLayoutChangeCounts(mainPage, 3);
5253
await expect(mainPage.getByText('"left": 30')).toBeVisible();
5354

5455
await expect(await getSeparatorAriaAttributes(page)).toEqual({
@@ -61,7 +62,7 @@ test.describe("keyboard interactions: window splitter api", () => {
6162
// Up/down are no-ops
6263
await page.keyboard.press("ArrowUp");
6364
await page.keyboard.press("ArrowDown");
64-
await expect(mainPage.getByText('"onLayoutCount": 3')).toBeVisible();
65+
await assertLayoutChangeCounts(mainPage, 3);
6566
});
6667

6768
test("vertical: arrow keys", async ({ page: mainPage }) => {
@@ -75,7 +76,7 @@ test.describe("keyboard interactions: window splitter api", () => {
7576
{ usePopUpWindow }
7677
);
7778

78-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
79+
await assertLayoutChangeCounts(mainPage, 1);
7980
await expect(mainPage.getByText('"top": 30')).toBeVisible();
8081

8182
await expect(await getSeparatorAriaAttributes(page)).toEqual({
@@ -90,7 +91,7 @@ test.describe("keyboard interactions: window splitter api", () => {
9091
await separator.focus();
9192
await page.keyboard.press("ArrowDown");
9293

93-
await expect(mainPage.getByText('"onLayoutCount": 2')).toBeVisible();
94+
await assertLayoutChangeCounts(mainPage, 2);
9495
await expect(mainPage.getByText('"top": 35')).toBeVisible();
9596

9697
await expect(await getSeparatorAriaAttributes(page)).toEqual({
@@ -102,7 +103,7 @@ test.describe("keyboard interactions: window splitter api", () => {
102103

103104
await page.keyboard.press("ArrowUp");
104105

105-
await expect(mainPage.getByText('"onLayoutCount": 3')).toBeVisible();
106+
await assertLayoutChangeCounts(mainPage, 3);
106107
await expect(mainPage.getByText('"top": 30')).toBeVisible();
107108

108109
await expect(await getSeparatorAriaAttributes(page)).toEqual({
@@ -115,7 +116,7 @@ test.describe("keyboard interactions: window splitter api", () => {
115116
// Left/right are no-ops
116117
await page.keyboard.press("ArrowLeft");
117118
await page.keyboard.press("ArrowRight");
118-
await expect(mainPage.getByText('"onLayoutCount": 3')).toBeVisible();
119+
await assertLayoutChangeCounts(mainPage, 3);
119120
});
120121

121122
test("enter key and collapsible panel", async ({ page: mainPage }) => {
@@ -129,7 +130,7 @@ test.describe("keyboard interactions: window splitter api", () => {
129130
{ usePopUpWindow }
130131
);
131132

132-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
133+
await assertLayoutChangeCounts(mainPage, 1);
133134
await expect(mainPage.getByText('"left": 50')).toBeVisible();
134135
await expect(mainPage.getByText('"right": 50')).toBeVisible();
135136

@@ -144,7 +145,7 @@ test.describe("keyboard interactions: window splitter api", () => {
144145
await separator.focus();
145146
await page.keyboard.press("Enter");
146147

147-
await expect(mainPage.getByText('"onLayoutCount": 2')).toBeVisible();
148+
await assertLayoutChangeCounts(mainPage, 2);
148149
await expect(mainPage.getByText('"left": 5')).toBeVisible();
149150
await expect(mainPage.getByText('"right": 95')).toBeVisible();
150151

@@ -157,7 +158,7 @@ test.describe("keyboard interactions: window splitter api", () => {
157158

158159
await page.keyboard.press("Enter");
159160

160-
await expect(mainPage.getByText('"onLayoutCount": 3')).toBeVisible();
161+
await assertLayoutChangeCounts(mainPage, 3);
161162
await expect(mainPage.getByText('"left": 50')).toBeVisible();
162163
await expect(mainPage.getByText('"right": 50')).toBeVisible();
163164

@@ -170,7 +171,7 @@ test.describe("keyboard interactions: window splitter api", () => {
170171

171172
await page.keyboard.press("ArrowLeft");
172173

173-
await expect(mainPage.getByText('"onLayoutCount": 4')).toBeVisible();
174+
await assertLayoutChangeCounts(mainPage, 4);
174175
await expect(mainPage.getByText('"left": 45')).toBeVisible();
175176
await expect(mainPage.getByText('"right": 55')).toBeVisible();
176177

@@ -183,7 +184,7 @@ test.describe("keyboard interactions: window splitter api", () => {
183184

184185
await page.keyboard.press("Enter");
185186

186-
await expect(mainPage.getByText('"onLayoutCount": 5')).toBeVisible();
187+
await assertLayoutChangeCounts(mainPage, 5);
187188
await expect(mainPage.getByText('"left": 5')).toBeVisible();
188189
await expect(mainPage.getByText('"right": 95')).toBeVisible();
189190

@@ -196,7 +197,7 @@ test.describe("keyboard interactions: window splitter api", () => {
196197

197198
await page.keyboard.press("Enter");
198199

199-
await expect(mainPage.getByText('"onLayoutCount": 6')).toBeVisible();
200+
await assertLayoutChangeCounts(mainPage, 6);
200201
await expect(mainPage.getByText('"left": 45')).toBeVisible();
201202
await expect(mainPage.getByText('"right": 55')).toBeVisible();
202203

@@ -221,15 +222,15 @@ test.describe("keyboard interactions: window splitter api", () => {
221222
{ usePopUpWindow }
222223
);
223224

224-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
225+
await assertLayoutChangeCounts(mainPage, 1);
225226
await expect(mainPage.getByText('"left": 50')).toBeVisible();
226227
await expect(mainPage.getByText('"right": 50')).toBeVisible();
227228

228229
const separator = page.getByRole("separator");
229230
await separator.focus();
230231
await page.keyboard.press("Enter");
231232

232-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
233+
await assertLayoutChangeCounts(mainPage, 1);
233234
});
234235

235236
test("home and end keys", async ({ page: mainPage }) => {
@@ -243,7 +244,7 @@ test.describe("keyboard interactions: window splitter api", () => {
243244
{ usePopUpWindow }
244245
);
245246

246-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
247+
await assertLayoutChangeCounts(mainPage, 1);
247248
await expect(mainPage.getByText('"left": 50')).toBeVisible();
248249
await expect(mainPage.getByText('"right": 50')).toBeVisible();
249250

@@ -252,13 +253,13 @@ test.describe("keyboard interactions: window splitter api", () => {
252253
await separator.focus();
253254
await page.keyboard.press("Home");
254255

255-
await expect(mainPage.getByText('"onLayoutCount": 2')).toBeVisible();
256+
await assertLayoutChangeCounts(mainPage, 2);
256257
await expect(mainPage.getByText('"left": 20')).toBeVisible();
257258
await expect(mainPage.getByText('"right": 80')).toBeVisible();
258259

259260
await page.keyboard.press("End");
260261

261-
await expect(mainPage.getByText('"onLayoutCount": 3')).toBeVisible();
262+
await assertLayoutChangeCounts(mainPage, 3);
262263
await expect(mainPage.getByText('"left": 95')).toBeVisible();
263264
await expect(mainPage.getByText('"right": 5')).toBeVisible();
264265
});
@@ -307,7 +308,7 @@ test.describe("keyboard interactions: window splitter api", () => {
307308
{ usePopUpWindow }
308309
);
309310

310-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
311+
await assertLayoutChangeCounts(mainPage, 1);
311312
await expect(mainPage.getByText('"left": 30')).toBeVisible();
312313

313314
const separator = page.getByRole("separator");
@@ -320,7 +321,7 @@ test.describe("keyboard interactions: window splitter api", () => {
320321
await page.keyboard.press("Enter");
321322
await page.keyboard.press("Home");
322323

323-
await expect(mainPage.getByText('"onLayoutCount": 1')).toBeVisible();
324+
await assertLayoutChangeCounts(mainPage, 1);
324325
await expect(mainPage.getByText('"left": 30')).toBeVisible();
325326
});
326327
});

0 commit comments

Comments
 (0)