Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/components/SplitPane.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,47 @@ describe('SplitPane initial size calculation', () => {
// Second pane: remaining 624px
expect(panes[1]).toHaveStyle({ width: `${CONTAINER_WIDTH - 400}px` });
});

it('updates pane sizes when controlled size prop changes', async () => {
const ControlledComponent = ({ sizes }: { sizes: number[] }) => (
<SplitPane direction="horizontal">
<Pane size={sizes[0]}>Pane 1</Pane>
<Pane size={sizes[1]}>Pane 2</Pane>
</SplitPane>
);

const { container, rerender } = render(
<ControlledComponent sizes={[200, 400]} />
);

await act(async () => {
await vi.runAllTimersAsync();
});

const panes = container.querySelectorAll('[data-pane="true"]');
expect(panes).toHaveLength(2);
expect(panes[0]).toHaveStyle({ width: '200px' });
expect(panes[1]).toHaveStyle({ width: '400px' });

// Change sizes (simulates parent state update after drag)
rerender(<ControlledComponent sizes={[300, 300]} />);

await act(async () => {
await vi.runAllTimersAsync();
});

expect(panes[0]).toHaveStyle({ width: '300px' });
expect(panes[1]).toHaveStyle({ width: '300px' });

// Reset to initial values (simulates clicking "Reset" button)
rerender(<ControlledComponent sizes={[200, 400]} />);

await act(async () => {
await vi.runAllTimersAsync();
});

// This is the bug: sizes should update to [200, 400]
expect(panes[0]).toHaveStyle({ width: '200px' });
expect(panes[1]).toHaveStyle({ width: '400px' });
});
});
24 changes: 24 additions & 0 deletions src/components/SplitPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,30 @@ export function SplitPane(props: SplitPaneProps) {
calculateInitialSizes(containerSize)
);

// Sync paneSizes with controlled size props when they change
// This handles the case where parent state is reset (e.g., clicking a "Reset" button)
useEffect(() => {
if (containerSize === 0) return;

// Check if any pane has a controlled size prop
const hasControlledSizes = paneConfigs.some(
(config) => config.size !== undefined
);
if (!hasControlledSizes) return;

// Calculate what sizes should be based on current props
const expectedSizes = calculateInitialSizes(containerSize);

// Only update if sizes actually differ (avoid unnecessary re-renders)
setPaneSizes((currentSizes) => {
const sizesMatch =
currentSizes.length === expectedSizes.length &&
currentSizes.every((size, i) => size === expectedSizes[i]);

return sizesMatch ? currentSizes : expectedSizes;
});
}, [containerSize, paneConfigs, calculateInitialSizes]);

// Handle container size changes - update sizes proportionally
// Using a ref comparison to avoid effect dependency issues
const handleContainerSizeChange = useCallback(
Expand Down