mimic native nested scroll behaviour (#3325)

This commit is contained in:
Jonathan Dobson
2025-08-29 19:35:42 -04:00
committed by GitHub
parent bc8e35954f
commit 716d41aeec
3 changed files with 61 additions and 4 deletions

View File

@@ -112,7 +112,7 @@ function BlockCodeEditor({
<div className="h-full flex-1 overflow-y-hidden">
<CodeEditor
key="static"
className="nopan nowheel h-full overflow-y-scroll"
className="nopan h-full overflow-y-scroll"
language="python"
value={script}
lineWrap={false}

View File

@@ -3,8 +3,11 @@ import { json } from "@codemirror/lang-json";
import { python } from "@codemirror/lang-python";
import { html } from "@codemirror/lang-html";
import { tokyoNightStorm } from "@uiw/codemirror-theme-tokyo-night-storm";
import { useEffect, useRef } from "react";
import { cn } from "@/util/utils";
import "./code-mirror-overrides.css";
function getLanguageExtension(language: "python" | "json" | "html") {
switch (language) {
case "python":
@@ -30,8 +33,8 @@ type Props = {
};
const fullHeightExtension = EditorView.theme({
"&": { height: "100%" }, // the root
".cm-scroller": { flex: 1 }, // makes the scrollable area expand
"&": { height: "100%" },
".cm-scroller": { flex: 1 },
});
function CodeEditor({
@@ -46,17 +49,58 @@ function CodeEditor({
fontSize = 12,
fullHeight = false,
}: Props) {
const viewRef = useRef<EditorView | null>(null);
const extensions = language
? [getLanguageExtension(language), lineWrap ? EditorView.lineWrapping : []]
: [lineWrap ? EditorView.lineWrapping : []];
const style: React.CSSProperties = { fontSize };
if (fullHeight) {
extensions.push(fullHeightExtension);
style.height = "100%";
}
useEffect(() => {
const view = viewRef.current;
if (!view) {
return;
}
const el = view.scrollDOM; // this is the .cm-scroller element
const onWheel = (e: WheelEvent) => {
if (e.ctrlKey || e.metaKey) return;
const factor =
e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? el.clientHeight : 1;
const dy = e.deltaY * factor;
const dx = e.deltaX * factor;
const top = el.scrollTop;
const left = el.scrollLeft;
const maxY = el.scrollHeight - el.clientHeight;
const maxX = el.scrollWidth - el.clientWidth;
const atTop = top <= 0;
const atBottom = top >= maxY - 1;
const atLeft = left <= 0;
const atRight = left >= maxX - 1;
const verticalWouldScroll = (dy < 0 && !atTop) || (dy > 0 && !atBottom);
const horizontalWouldScroll = (dx < 0 && !atLeft) || (dx > 0 && !atRight);
if (verticalWouldScroll || horizontalWouldScroll) {
e.stopPropagation();
}
};
el.addEventListener("wheel", onWheel, { passive: true, capture: true });
return () => el.removeEventListener("wheel", onWheel, { capture: true });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [viewRef.current]);
return (
<CodeMirror
value={value}
@@ -68,6 +112,12 @@ function CodeEditor({
readOnly={readOnly}
className={cn("cursor-auto", className)}
style={style}
onCreateEditor={(view) => {
viewRef.current = view;
}}
onUpdate={(viewUpdate) => {
if (!viewRef.current) viewRef.current = viewUpdate.view;
}}
/>
);
}

View File

@@ -0,0 +1,7 @@
.cm-editor {
height: 100% !important;
}
.cm-scroller {
overflow: auto !important;
}