mimic native nested scroll behaviour (#3325)
This commit is contained in:
@@ -112,7 +112,7 @@ function BlockCodeEditor({
|
|||||||
<div className="h-full flex-1 overflow-y-hidden">
|
<div className="h-full flex-1 overflow-y-hidden">
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
key="static"
|
key="static"
|
||||||
className="nopan nowheel h-full overflow-y-scroll"
|
className="nopan h-full overflow-y-scroll"
|
||||||
language="python"
|
language="python"
|
||||||
value={script}
|
value={script}
|
||||||
lineWrap={false}
|
lineWrap={false}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ import { json } from "@codemirror/lang-json";
|
|||||||
import { python } from "@codemirror/lang-python";
|
import { python } from "@codemirror/lang-python";
|
||||||
import { html } from "@codemirror/lang-html";
|
import { html } from "@codemirror/lang-html";
|
||||||
import { tokyoNightStorm } from "@uiw/codemirror-theme-tokyo-night-storm";
|
import { tokyoNightStorm } from "@uiw/codemirror-theme-tokyo-night-storm";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
import { cn } from "@/util/utils";
|
import { cn } from "@/util/utils";
|
||||||
|
|
||||||
|
import "./code-mirror-overrides.css";
|
||||||
|
|
||||||
function getLanguageExtension(language: "python" | "json" | "html") {
|
function getLanguageExtension(language: "python" | "json" | "html") {
|
||||||
switch (language) {
|
switch (language) {
|
||||||
case "python":
|
case "python":
|
||||||
@@ -30,8 +33,8 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fullHeightExtension = EditorView.theme({
|
const fullHeightExtension = EditorView.theme({
|
||||||
"&": { height: "100%" }, // the root
|
"&": { height: "100%" },
|
||||||
".cm-scroller": { flex: 1 }, // makes the scrollable area expand
|
".cm-scroller": { flex: 1 },
|
||||||
});
|
});
|
||||||
|
|
||||||
function CodeEditor({
|
function CodeEditor({
|
||||||
@@ -46,17 +49,58 @@ function CodeEditor({
|
|||||||
fontSize = 12,
|
fontSize = 12,
|
||||||
fullHeight = false,
|
fullHeight = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const viewRef = useRef<EditorView | null>(null);
|
||||||
|
|
||||||
const extensions = language
|
const extensions = language
|
||||||
? [getLanguageExtension(language), lineWrap ? EditorView.lineWrapping : []]
|
? [getLanguageExtension(language), lineWrap ? EditorView.lineWrapping : []]
|
||||||
: [lineWrap ? EditorView.lineWrapping : []];
|
: [lineWrap ? EditorView.lineWrapping : []];
|
||||||
|
|
||||||
const style: React.CSSProperties = { fontSize };
|
const style: React.CSSProperties = { fontSize };
|
||||||
|
|
||||||
if (fullHeight) {
|
if (fullHeight) {
|
||||||
extensions.push(fullHeightExtension);
|
extensions.push(fullHeightExtension);
|
||||||
style.height = "100%";
|
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 (
|
return (
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
value={value}
|
value={value}
|
||||||
@@ -68,6 +112,12 @@ function CodeEditor({
|
|||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
className={cn("cursor-auto", className)}
|
className={cn("cursor-auto", className)}
|
||||||
style={style}
|
style={style}
|
||||||
|
onCreateEditor={(view) => {
|
||||||
|
viewRef.current = view;
|
||||||
|
}}
|
||||||
|
onUpdate={(viewUpdate) => {
|
||||||
|
if (!viewRef.current) viewRef.current = viewUpdate.view;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
.cm-editor {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-scroller {
|
||||||
|
overflow: auto !important;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user