refactor: extract scheduleSplitScrollRestore to stay under max-lines lint

Moves the two-phase scroll restoration schedule (double-rAF + 200ms
timeout) into pane-split-scroll.ts so both pane-manager.ts and
pane-tree-ops.ts stay under the 300-line oxlint limit.
This commit is contained in:
Jinwoo-H 2026-04-19 23:24:03 -04:00
parent 0072d409e6
commit fa88f76b3c
2 changed files with 44 additions and 35 deletions

View file

@ -33,9 +33,9 @@ import {
safeFit,
fitAllPanesInternal,
captureScrollState,
restoreScrollState,
refitPanesUnder
} from './pane-tree-ops'
import { scheduleSplitScrollRestore } from './pane-split-scroll'
export type { PaneManagerOptions, PaneStyleOptions, ManagedPane, DropZone }
@ -129,40 +129,12 @@ export class PaneManager {
void this.options.onPaneCreated?.(this.toPublic(newPane))
this.options.onLayoutChanged?.()
// Why: reparenting the container resets the viewport scroll position
// immediately (browser clears scrollTop on DOM move). The early
// double-rAF restore fires after the first fit settles (~2 frames /
// ~32ms) to minimise the visible flash. The lock stays in place so
// intermediate fits don't interfere. The later 200ms timeout is the
// authoritative final restore that also clears the lock — it catches
// any late async reflows (ResizeObserver 150ms debounce, WebGL
// context-loss rAFs) that might shift the position after the early
// restore.
const existingPaneId = existing.id
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (this.destroyed) {
return
}
const live = this.panes.get(existingPaneId)
if (live?.pendingSplitScrollState) {
restoreScrollState(live.terminal, scrollState)
}
})
})
setTimeout(() => {
if (this.destroyed) {
return
}
const live = this.panes.get(existingPaneId)
if (!live) {
return
}
live.pendingSplitScrollState = null
restoreScrollState(live.terminal, scrollState)
}, 200)
scheduleSplitScrollRestore(
(id) => this.panes.get(id),
existing.id,
scrollState,
() => this.destroyed
)
return this.toPublic(newPane)
}

View file

@ -0,0 +1,37 @@
import type { ManagedPaneInternal, ScrollState } from './pane-manager-types'
import { restoreScrollState } from './pane-tree-ops'
// Why: reparenting a terminal container during split resets the viewport
// scroll position (browser clears scrollTop on DOM move). This schedules a
// two-phase restore: an early double-rAF (~32ms) to minimise the visible
// flash, plus a 200ms authoritative restore that also clears the scroll lock.
export function scheduleSplitScrollRestore(
getPaneById: (id: number) => ManagedPaneInternal | undefined,
paneId: number,
scrollState: ScrollState,
isDestroyed: () => boolean
): void {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (isDestroyed()) {
return
}
const live = getPaneById(paneId)
if (live?.pendingSplitScrollState) {
restoreScrollState(live.terminal, scrollState)
}
})
})
setTimeout(() => {
if (isDestroyed()) {
return
}
const live = getPaneById(paneId)
if (!live) {
return
}
live.pendingSplitScrollState = null
restoreScrollState(live.terminal, scrollState)
}, 200)
}