feat: implement auto-scroll to first <DiffEditor /> diff (#21967)

Closes #21962

This pull-request makes it so that we auto-scroll to our first diff
within the files when the page loads. It attempts to center it within
the inner viewports scroll.

| Old | New | 
| --- | --- |
| <img width="3516" height="2390" alt="CleanShot 2026-02-06 at 17 12
23@2x"
src="https://github.com/user-attachments/assets/2215178d-b887-4d3b-a5a2-882ad4b1f03c"
/> | <img width="3516" height="2390" alt="CleanShot 2026-02-06 at 17 11
53@2x"
src="https://github.com/user-attachments/assets/4b28c589-ebee-4e8c-ac44-22717f80023c"
/>
This commit is contained in:
Jake Howell
2026-02-07 18:50:28 +11:00
committed by GitHub
parent 43176a74a0
commit 1873687492
@@ -1,16 +1,30 @@
import { useTheme } from "@emotion/react";
import Editor, { DiffEditor, loader } from "@monaco-editor/react";
import type * as Monaco from "monaco-editor";
import * as monaco from "monaco-editor";
import type { ComponentProps, FC } from "react";
import { type ComponentProps, type FC, useCallback } from "react";
import { useCoderTheme } from "./coderTheme";
loader.config({ monaco });
// Shared editor props with onMount typed to accept either editor variant,
// so callers don't need to know which underlying component will render.
type CommonEditorProps = Omit<
ComponentProps<typeof Editor> & ComponentProps<typeof DiffEditor>,
"onMount"
> & {
onMount?: (
editor:
| Monaco.editor.IStandaloneCodeEditor
| Monaco.editor.IStandaloneDiffEditor,
monaco: typeof Monaco,
) => void;
};
interface SyntaxHighlighterProps {
value: string;
language?: string;
editorProps?: ComponentProps<typeof Editor> &
ComponentProps<typeof DiffEditor>;
editorProps?: CommonEditorProps;
compareWith?: string;
}
@@ -23,6 +37,41 @@ export const SyntaxHighlighter: FC<SyntaxHighlighterProps> = ({
const hasDiff = compareWith && value !== compareWith;
const theme = useTheme();
const coderTheme = useCoderTheme();
// Auto-scroll to first diff when the diff editor mounts and diffs are computed.
const handleDiffEditorMount = useCallback(
(
editor: Monaco.editor.IStandaloneDiffEditor,
monacoInstance: typeof Monaco,
) => {
// Call any existing onMount handler from editorProps.
editorProps?.onMount?.(editor, monacoInstance);
// Diffs may already be computed by the time onMount fires,
// so check immediately first. If not ready yet, fall back
// to waiting for the onDidUpdateDiff event.
const scrollToFirstDiff = () => {
editor.goToDiff("next");
};
const changes = editor.getLineChanges();
if (changes && changes.length > 0) {
scrollToFirstDiff();
return;
}
const disposable = editor.onDidUpdateDiff(() => {
const updatedChanges = editor.getLineChanges();
if (!updatedChanges || updatedChanges.length === 0) {
return;
}
disposable.dispose();
scrollToFirstDiff();
});
},
[editorProps],
);
const commonProps = {
language,
theme: coderTheme.name,
@@ -50,7 +99,12 @@ export const SyntaxHighlighter: FC<SyntaxHighlighterProps> = ({
}}
>
{hasDiff ? (
<DiffEditor original={compareWith} modified={value} {...commonProps} />
<DiffEditor
original={compareWith}
modified={value}
{...commonProps}
onMount={handleDiffEditorMount}
/>
) : (
<Editor value={value} {...commonProps} />
)}