@@ -71,12 +65,43 @@ export function SourcePreview({ path, line, highlights = [], load, version = 0,
if (error && !current) return
{error}
;
if (!current) return
;
+ return
;
+}
+
+/**
+ * The rendered lines. A separate component so the selection boundary mounts
+ * with the scroll element: the pane's own `path` may go null → set, after
+ * this component's effects already ran, and the ref would stay empty.
+ */
+function PreviewBody({
+ path,
+ line,
+ lines,
+ highlights,
+ className,
+}: {
+ path: string;
+ line: number;
+ lines: string[];
+ highlights: readonly SourceHighlight[];
+ className?: string;
+}) {
+ const scrollRef = useRef
(null);
+ useSelectionBoundary(scrollRef);
+ // Re-run on the loaded lines (not the parent's `current` object): the body
+ // remounts per text, so recentring must fire when the new lines arrive.
+ useLayoutEffect(() => {
+ const box = scrollRef.current;
+ const el = box?.querySelector(`[data-line="${line}"]`);
+ if (box && el) box.scrollTop = el.offsetTop - box.clientHeight / 2 + el.offsetHeight / 2;
+ }, [line, path, lines]);
+
const byLine = new Map(highlights.map((h) => [h.line, h.ranges]));
const start = Math.max(0, line - WINDOW);
- const end = Math.min(current.lines.length, line + WINDOW);
+ const end = Math.min(lines.length, line + WINDOW);
const rows: ReactNode[] = [];
for (let n = start; n < end; n++) {
- const content = current.lines[n] ?? "";
+ const content = lines[n] ?? "";
const ranges = byLine.get(n);
rows.push(