返回 presentation-ai
plate-dnd-overlay.tsx
根目录 / src / components / plate / plugins / plate-dnd-overlay.tsx
1 "use client";
2
3 import { DndPlugin, DndScroller } from "@platejs/dnd";
4 import { useEditorRef, usePluginOption } from "platejs/react";
5 import { useEffect, useRef, type ReactNode } from "react";
6
7 const SCROLL_CLASS_NAMES = [
8 "overflow-auto",
9 "overflow-scroll",
10 "overflow-y-auto",
11 "overflow-y-scroll",
12 ] as const;
13
14 const SCROLL_OVERFLOW_VALUES = new Set(["auto", "overlay", "scroll"]);
15
16 function hasScrollableClassName(element: HTMLElement): boolean {
17 const className =
18 typeof element.className === "string"
19 ? element.className
20 : (element.getAttribute("class") ?? "");
21
22 return SCROLL_CLASS_NAMES.some((name) => className.includes(name));
23 }
24
25 function isScrollableAncestor(element: HTMLElement): boolean {
26 const styles = window.getComputedStyle(element);
27 const overflowY = styles.overflowY.toLowerCase();
28 const overflow = styles.overflow.toLowerCase();
29 const allowsScroll =
30 hasScrollableClassName(element) ||
31 SCROLL_OVERFLOW_VALUES.has(overflowY) ||
32 SCROLL_OVERFLOW_VALUES.has(overflow);
33
34 return allowsScroll && element.scrollHeight > element.clientHeight + 1;
35 }
36
37 function findScrollableAncestor(
38 startElement: HTMLElement | null,
39 ): HTMLElement | null {
40 let current = startElement;
41
42 while (current) {
43 if (
44 current !== document.body &&
45 current !== document.documentElement &&
46 isScrollableAncestor(current)
47 ) {
48 return current;
49 }
50
51 current = current.parentElement;
52 }
53
54 const rootScroller = document.scrollingElement;
55
56 return rootScroller instanceof HTMLElement ? rootScroller : null;
57 }
58
59 export function PlateDndOverlay({ children }: { children: ReactNode }) {
60 const editor = useEditorRef();
61 const isDragging = Boolean(usePluginOption(DndPlugin, "isDragging"));
62 const containerRef = useRef<HTMLElement | null>(null);
63 const editorElementRef = useRef<HTMLElement | null>(null);
64
65 useEffect(() => {
66 if (!isDragging) {
67 return;
68 }
69
70 try {
71 const editorElement = editor.api.toDOMNode(editor);
72
73 if (!(editorElement instanceof HTMLElement)) {
74 containerRef.current = null;
75 editorElementRef.current = null;
76 return;
77 }
78
79 if (
80 editorElementRef.current === editorElement &&
81 containerRef.current?.isConnected
82 ) {
83 return;
84 }
85
86 if (
87 containerRef.current?.isConnected &&
88 containerRef.current.contains(editorElement)
89 ) {
90 editorElementRef.current = editorElement;
91 return;
92 }
93
94 containerRef.current = findScrollableAncestor(editorElement);
95 editorElementRef.current = editorElement;
96 } catch {
97 containerRef.current = null;
98 editorElementRef.current = null;
99 }
100 }, [editor, isDragging]);
101
102 return (
103 <>
104 {children}
105 <DndScroller
106 containerRef={containerRef}
107 height={32}
108 minStrength={0.05}
109 strengthMultiplier={16}
110 />
111 </>
112 );
113 }
114
114 lines Plain Text