Skip to content

Commit c741b2e

Browse files
committed
InteractiveContainer -> ScrollBoundary
1 parent afc4e14 commit c741b2e

3 files changed

Lines changed: 60 additions & 39 deletions

File tree

src/pages/components/layout/MainLayout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { arcoLocale } from "@App/locales/arco";
3636
import { prepareScriptByCode } from "@App/pkg/utils/script";
3737
import { saveHandle } from "@App/pkg/utils/filehandle-db";
3838
import { makeBlobURL } from "@App/pkg/utils/utils";
39-
import InteractiveContainer from "@App/pages/utility/InteractiveContainer";
39+
import ScrollBoundary from "@App/pages/components/layout/ScrollBoundary";
4040

4141
// --- 工具函数移出组件外,避免每次 Render 重新定义 ---
4242

@@ -298,7 +298,7 @@ const MainLayout: React.FC<{
298298
};
299299

300300
return (
301-
<InteractiveContainer>
301+
<ScrollBoundary parentNodeSelector="#root">
302302
<ConfigProvider
303303
renderEmpty={() => {
304304
return <Empty description={t("no_data")} />;
@@ -508,7 +508,7 @@ const MainLayout: React.FC<{
508508
</Layout>
509509
</Layout>
510510
</ConfigProvider>
511-
</InteractiveContainer>
511+
</ScrollBoundary>
512512
);
513513
};
514514

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type { HTMLAttributes, ReactNode } from "react";
2+
3+
// Props extend native div attributes so callers can pass className, style, etc.
4+
type ScrollBoundaryProps = {
5+
children: ReactNode;
6+
parentNodeSelector: string;
7+
} & HTMLAttributes<HTMLDivElement>;
8+
9+
/**
10+
* Handles wheel events bubbling up to the scroll boundary.
11+
*
12+
* - Monaco editor: prevent default so the editor's own scroll logic runs
13+
* without interference from the browser or ancestor handlers.
14+
* - Everywhere else: stop propagation so parent/page-level handlers ignore
15+
* wheel gestures originating inside this boundary.
16+
* (preventDefault is intentionally left commented out to allow native
17+
* scrolling within non-editor children.)
18+
*/
19+
const handleScrollBoundaryWheel = (evt: Event) => {
20+
if ((evt.target as Element).closest(".monaco-editor")) {
21+
evt.preventDefault();
22+
} else {
23+
evt.stopImmediatePropagation();
24+
evt.stopPropagation();
25+
// evt.preventDefault();
26+
}
27+
};
28+
29+
/**
30+
* Registers the wheel handler on the given target node.
31+
* Removes any existing listener first to guarantee exactly one handler is
32+
* attached, even if called multiple times (e.g. on re-renders).
33+
*
34+
* Options: non-capturing, non-passive (required for preventDefault to work),
35+
* and persistent (once: false).
36+
*/
37+
const attachScrollBoundaryHandler = (target: Node | null) => {
38+
const o = { capture: false, passive: false, once: false };
39+
target?.removeEventListener("wheel", handleScrollBoundaryWheel, o);
40+
target?.addEventListener("wheel", handleScrollBoundaryWheel, o);
41+
};
42+
43+
/**
44+
* Establishes a wheel-event boundary at the root level.
45+
*
46+
* Wheel behavior within the boundary:
47+
* - Inside Monaco editor instances: prevents default so editor scrolling
48+
* stays isolated from browser and ancestor handlers.
49+
* - Outside Monaco: stops propagation so parent/page-level handlers do not
50+
* react to wheel gestures originating inside this boundary.
51+
*/
52+
export default function ScrollBoundary({ children, parentNodeSelector }: ScrollBoundaryProps) {
53+
// Attach once per render; the handler is idempotent due to the
54+
// remove-then-add pattern in attachScrollBoundaryHandler.
55+
attachScrollBoundaryHandler(document.querySelector(parentNodeSelector));
56+
return <>{children}</>;
57+
}

src/pages/utility/InteractiveContainer.tsx

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)