Skip to content

Commit da7c580

Browse files
committed
fix(layout): prevent scroll resets and jumps during panel activation and dragging
1 parent 831e490 commit da7c580

1 file changed

Lines changed: 52 additions & 23 deletions

File tree

anycode/components/layout/Layout.tsx

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export type PanelId = 'files' | 'search' | 'changes' | 'editor' | 'agent' | 'ter
250250
type PanelParams = {
251251
panelKey: string;
252252
panelId: PanelId;
253-
content: React.ReactNode;
253+
content?: React.ReactNode;
254254
};
255255

256256
type PanelPlacementDirection = 'above' | 'right' | 'below' | 'within';
@@ -288,6 +288,7 @@ type ScrollSnapshot = {
288288

289289
type LayoutViewStateRegistry = {
290290
registerPanelViewState: (panelKey: string, handlers: PanelViewStateHandlers) => () => void;
291+
savePanelViewState: (panelKey: string, state: unknown) => void;
291292
};
292293

293294
type LayoutProps = {
@@ -309,6 +310,7 @@ export type LayoutActions = {
309310
};
310311

311312
const LayoutViewStateContext = React.createContext<LayoutViewStateRegistry | null>(null);
313+
const LayoutRenderContext = React.createContext<((panelId: PanelId, panelKey: string) => React.ReactNode) | null>(null);
312314

313315
export const useLayoutPanelViewState = (
314316
panelKey: string,
@@ -587,10 +589,15 @@ const getSplitPanelSizes = (
587589

588590
const LayoutPanel: React.FC<IDockviewPanelProps<PanelParams>> = ({ params }) => {
589591
const rootRef = useRef<HTMLDivElement | null>(null);
592+
const renderPanel = useContext(LayoutRenderContext);
593+
const registry = useContext(LayoutViewStateContext);
590594

591-
const captureViewState = useCallback((): ScrollSnapshot[] => {
595+
const captureViewState = useCallback((): ScrollSnapshot[] | null => {
592596
const root = rootRef.current;
593-
return root ? getPanelScrollSnapshots(root) : [];
597+
if (!root || !document.body.contains(root) || (root.offsetWidth === 0 && root.offsetHeight === 0)) {
598+
return null;
599+
}
600+
return getPanelScrollSnapshots(root);
594601
}, []);
595602

596603
const restoreViewState = useCallback((state: unknown) => {
@@ -607,9 +614,24 @@ const LayoutPanel: React.FC<IDockviewPanelProps<PanelParams>> = ({ params }) =>
607614
restoreViewState,
608615
});
609616

617+
useEffect(() => {
618+
const element = rootRef.current;
619+
if (!element || !registry) return;
620+
621+
const handleScroll = () => {
622+
const snapshot = getPanelScrollSnapshots(element);
623+
registry.savePanelViewState(params.panelKey, snapshot);
624+
};
625+
626+
element.addEventListener('scroll', handleScroll, { capture: true, passive: true });
627+
return () => element.removeEventListener('scroll', handleScroll, { capture: true });
628+
}, [params.panelKey, registry]);
629+
630+
const content = renderPanel ? renderPanel(params.panelId, params.panelKey) : params.content;
631+
610632
return (
611633
<div ref={rootRef} className={`layout-dock-panel layout-dock-panel--${params.panelId}`}>
612-
{params.content}
634+
{content}
613635
</div>
614636
);
615637
};
@@ -660,6 +682,11 @@ const PanelPicker: React.FC<IDockviewPanelProps<PanelPickerParams>> = ({ params
660682
</div>
661683
);
662684

685+
const DOCKVIEW_COMPONENTS = {
686+
layoutPanel: LayoutPanel,
687+
panelPicker: PanelPicker,
688+
};
689+
663690
const LayoutHeaderActions: React.FC<IDockviewHeaderActionsProps & {
664691
onSplitRight: (api: DockviewApi, referencePanelId: string) => void;
665692
onSplitDown: (api: DockviewApi, referencePanelId: string) => void;
@@ -791,12 +818,11 @@ const addPanel = (
791818
api: DockviewApi,
792819
panelKey: string,
793820
panelId: PanelId,
794-
content: React.ReactNode,
821+
content?: React.ReactNode,
795822
): IDockviewPanel => {
796823
const definition = panelDefinitionById[panelId];
797824
const existing = api.getPanel(panelKey);
798825
if (existing) {
799-
existing.api.updateParameters({ panelId, panelKey, content });
800826
return existing;
801827
}
802828

@@ -1015,15 +1041,23 @@ export const Layout: React.FC<LayoutProps> = ({
10151041
};
10161042
}, []);
10171043

1044+
const savePanelViewState = useCallback((panelKey: string, state: unknown) => {
1045+
panelViewStatesRef.current[panelKey] = state;
1046+
}, []);
1047+
10181048
const viewStateRegistry = useMemo<LayoutViewStateRegistry>(() => ({
10191049
registerPanelViewState,
1020-
}), [registerPanelViewState]);
1050+
savePanelViewState,
1051+
}), [registerPanelViewState, savePanelViewState]);
10211052

10221053
const capturePanelViewStates = useCallback(() => {
10231054
const nextStates: Record<string, unknown> = {};
10241055

10251056
panelViewStateHandlersRef.current.forEach((handlers, panelKey) => {
1026-
nextStates[panelKey] = handlers.captureViewState();
1057+
const state = handlers.captureViewState();
1058+
if (state !== null && state !== undefined) {
1059+
nextStates[panelKey] = state;
1060+
}
10271061
});
10281062

10291063
panelViewStatesRef.current = {
@@ -1102,13 +1136,6 @@ export const Layout: React.FC<LayoutProps> = ({
11021136
continue;
11031137
}
11041138

1105-
for (const existing of existingPanels) {
1106-
existing.api.updateParameters({
1107-
panelId: panel.id,
1108-
panelKey: existing.id,
1109-
content: resolvePanelContent(panel.id, existing.id),
1110-
});
1111-
}
11121139
continue;
11131140
}
11141141

@@ -1151,10 +1178,9 @@ export const Layout: React.FC<LayoutProps> = ({
11511178
panel.api.updateParameters({
11521179
panelId,
11531180
panelKey: panel.id,
1154-
content: resolvePanelContent(panelId, panel.id),
11551181
});
11561182
}
1157-
}, [resolvePanelContent]);
1183+
}, []);
11581184

11591185
const handleSelectPanelFromPicker = useCallback((panelId: PanelId, pickerPanelId: string) => {
11601186
const api = apiRef.current;
@@ -1474,6 +1500,7 @@ export const Layout: React.FC<LayoutProps> = ({
14741500
const baseId = getPanelBaseId(panel.id);
14751501
if (!baseId) return;
14761502
onPanelActivated?.(baseId, panel.id);
1503+
restorePanelViewStates();
14771504
}),
14781505
api.onDidLayoutChange(() => {
14791506
if (isRestoringLayoutRef.current) {
@@ -1558,12 +1585,14 @@ export const Layout: React.FC<LayoutProps> = ({
15581585
<div className="layout dockview-theme-dark">
15591586
<div className="layout-main">
15601587
<LayoutViewStateContext.Provider value={viewStateRegistry}>
1561-
<DockviewReact
1562-
components={{ layoutPanel: LayoutPanel, panelPicker: PanelPicker }}
1563-
className="layout-root"
1564-
onReady={handleReady}
1565-
rightHeaderActionsComponent={renderRightHeaderActions}
1566-
/>
1588+
<LayoutRenderContext.Provider value={renderPanel}>
1589+
<DockviewReact
1590+
components={DOCKVIEW_COMPONENTS}
1591+
className="layout-root"
1592+
onReady={handleReady}
1593+
rightHeaderActionsComponent={renderRightHeaderActions}
1594+
/>
1595+
</LayoutRenderContext.Provider>
15671596
</LayoutViewStateContext.Provider>
15681597
</div>
15691598
<div className="layout-toolbar">

0 commit comments

Comments
 (0)