import type { ChangeStop } from './diffView' import type { StickyScrollSettings } from './types' import { useCallback, useLayoutEffect, useRef, useState } from 'react' import { nextStop } from './diffView' import { pageLayout } from './pageLayout' import { scrollOwner } from './scrollOwner' import { flash } from './useReveal' import { stickyInset } from './useStickyScroll' /** 跳到的那处离筛选栏下沿留多少像素。 */ const MARGIN = 8 /** * 「上一处 / 下一处」和点总览条的刻度:跳到一处改动。它在折叠着的块里时先展开那几层(expand),渲染好了再把它滚到筛选栏下面 * (钉着外层卡片时在钉着的几行下面,sticky 是 VS Code 的 Sticky Scroll 设置)、闪几下。 * 跳过去以后由 [scrollOwner] 跟住那张卡片。接着走从哪算:跳完之后用户没滚过、也没跳到别处就从刚跳到的那处接着走, * 否则按视口顶上那块的行找(见 [nextStop]);页面长高时滚动所有者自己滚回去的不算用户滚过。 */ export function useChangeNav(stops: readonly ChangeStop[], collapsed: ReadonlySet, expand: (ids: string[]) => void, sticky: StickyScrollSettings | undefined): { move: (step: 1 | -1) => void goTo: (stop: ChangeStop) => void } { const [target, setTarget] = useState(null) /** 最近一次跳到的那处,和跳完时页面换过几次地方(见 [ScrollOwner.turn](vscode/webview-ui/src/codeReader/scrollOwner.ts#ScrollOwner.turn)) */ const jumped = useRef<{ id: string, turn: number } | null>(null) useLayoutEffect(() => { if (!target) return const hidden = target.ancestors.filter(id => collapsed.has(id)) if (hidden.length > 0) { expand(hidden) return } const locate = (): HTMLElement | null => document.querySelector(`.cr-block[data-id="${target.id}"]`) const card = locate() if (card) { pageLayout.settle(card) const inset = pageLayout.pinnedHeight() + stickyInset(card, sticky) + MARGIN scrollOwner.reveal(locate, box => box.top - inset) flash(card) } jumped.current = { id: target.id, turn: scrollOwner.turn } setTarget(null) }, [target, collapsed, expand, sticky]) const move = useCallback((step: 1 | -1) => { if (stops.length === 0) return const from = jumped.current?.turn === scrollOwner.turn ? jumped.current.id : undefined const index = from === undefined ? -1 : stops.findIndex(stop => stop.id === from) const at = index >= 0 ? { index } : { line: pageLayout.anchorAt(window.scrollY + pageLayout.pinnedHeight())?.line } setTarget(stops[nextStop(stops, at, step)]) }, [stops]) return { move, goTo: setTarget } }