Skip to content

Commit 1f48a4d

Browse files
authored
fix: sync controlled pane sizes when props change (#862)
Fixes #861 When using SplitPane in controlled mode with `size` props on Pane components, resetting the sizes (e.g., via a "Reset" button) did not update the UI. The internal `paneSizes` state was not syncing with the controlled `size` props after changes. Added an effect to sync `paneSizes` with controlled size props when they change, ensuring the UI reflects parent state updates.
1 parent 48ebdbe commit 1f48a4d

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

src/components/SplitPane.test.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,47 @@ describe('SplitPane initial size calculation', () => {
235235
// Second pane: remaining 624px
236236
expect(panes[1]).toHaveStyle({ width: `${CONTAINER_WIDTH - 400}px` });
237237
});
238+
239+
it('updates pane sizes when controlled size prop changes', async () => {
240+
const ControlledComponent = ({ sizes }: { sizes: number[] }) => (
241+
<SplitPane direction="horizontal">
242+
<Pane size={sizes[0]}>Pane 1</Pane>
243+
<Pane size={sizes[1]}>Pane 2</Pane>
244+
</SplitPane>
245+
);
246+
247+
const { container, rerender } = render(
248+
<ControlledComponent sizes={[200, 400]} />
249+
);
250+
251+
await act(async () => {
252+
await vi.runAllTimersAsync();
253+
});
254+
255+
const panes = container.querySelectorAll('[data-pane="true"]');
256+
expect(panes).toHaveLength(2);
257+
expect(panes[0]).toHaveStyle({ width: '200px' });
258+
expect(panes[1]).toHaveStyle({ width: '400px' });
259+
260+
// Change sizes (simulates parent state update after drag)
261+
rerender(<ControlledComponent sizes={[300, 300]} />);
262+
263+
await act(async () => {
264+
await vi.runAllTimersAsync();
265+
});
266+
267+
expect(panes[0]).toHaveStyle({ width: '300px' });
268+
expect(panes[1]).toHaveStyle({ width: '300px' });
269+
270+
// Reset to initial values (simulates clicking "Reset" button)
271+
rerender(<ControlledComponent sizes={[200, 400]} />);
272+
273+
await act(async () => {
274+
await vi.runAllTimersAsync();
275+
});
276+
277+
// This is the bug: sizes should update to [200, 400]
278+
expect(panes[0]).toHaveStyle({ width: '200px' });
279+
expect(panes[1]).toHaveStyle({ width: '400px' });
280+
});
238281
});

src/components/SplitPane.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,30 @@ export function SplitPane(props: SplitPaneProps) {
152152
calculateInitialSizes(containerSize)
153153
);
154154

155+
// Sync paneSizes with controlled size props when they change
156+
// This handles the case where parent state is reset (e.g., clicking a "Reset" button)
157+
useEffect(() => {
158+
if (containerSize === 0) return;
159+
160+
// Check if any pane has a controlled size prop
161+
const hasControlledSizes = paneConfigs.some(
162+
(config) => config.size !== undefined
163+
);
164+
if (!hasControlledSizes) return;
165+
166+
// Calculate what sizes should be based on current props
167+
const expectedSizes = calculateInitialSizes(containerSize);
168+
169+
// Only update if sizes actually differ (avoid unnecessary re-renders)
170+
setPaneSizes((currentSizes) => {
171+
const sizesMatch =
172+
currentSizes.length === expectedSizes.length &&
173+
currentSizes.every((size, i) => size === expectedSizes[i]);
174+
175+
return sizesMatch ? currentSizes : expectedSizes;
176+
});
177+
}, [containerSize, paneConfigs, calculateInitialSizes]);
178+
155179
// Handle container size changes - update sizes proportionally
156180
// Using a ref comparison to avoid effect dependency issues
157181
const handleContainerSizeChange = useCallback(

0 commit comments

Comments
 (0)