@@ -15,6 +15,7 @@ import StarterKit from "@tiptap/starter-kit";
1515import { useEffect , useMemo , useRef } from "react" ;
1616
1717import "@/components/editor/tiptap-node/node-focus/node-focus.scss" ;
18+ import "./drag-cursor.css" ;
1819import { useEditingDisabled } from "@/hooks/useEditingDisabled" ;
1920import { useEditor } from "@/providers/EditorContext" ;
2021import { cn } from "@/utils/utils" ;
@@ -205,28 +206,59 @@ export default function TiptapEditor({
205206 // Block drops from other editors (e.g., outer → nested or vice versa) by comparing editor IDs
206207 drop : ( view , event ) => {
207208 const e = event as unknown as DragEvent ;
209+ const isOurDnD = e . dataTransfer ?. types ?. includes ( "application/x-tiptap-dnd" ) ;
210+ document . body . classList . remove ( "fern-dragging-blocked" ) ;
211+ if ( ! isOurDnD ) {
212+ return false ; // Not our drag, don't process
213+ }
208214 const fromId = e . dataTransfer ?. getData ( "editor-id" ) || "" ;
209215 const toId = ( view . dom as HTMLElement ) ?. getAttribute ( "data-editor-id" ) || "" ;
210216 if ( fromId && toId && fromId !== toId ) {
211217 e . preventDefault ( ) ;
212218 e . stopPropagation ( ) ;
219+ // Clear global state on drop
220+ ( window as any ) . __fernDraggingEditorId = undefined ;
213221 return true ;
214222 }
223+ // Clear global state on successful drop
224+ ( window as any ) . __fernDraggingEditorId = undefined ;
215225 return false ;
216226 } ,
217- // Show "not-allowed" cursor when dragging between different editors
227+ // When dragging across different editors, block the drop and show a global "not-allowed" cursor.
228+ // We set a class on document.body (not the editor element) so the cursor wins over any child-level
229+ // cursor styles (e.g. .ProseMirror { cursor: text } or cursor: pointer on nodes).
230+ // We keep the class until drop/dragend to avoid flicker while moving between DOM children.
218231 dragover : ( view , event ) => {
219232 const e = event as unknown as DragEvent ;
220- const fromId = e . dataTransfer ?. getData ( "editor-id" ) || "" ;
233+ // Read from global store since dataTransfer.getData doesn't work in dragover
234+ const fromId = ( window as any ) . __fernDraggingEditorId || "" ;
221235 const toId = ( view . dom as HTMLElement ) ?. getAttribute ( "data-editor-id" ) || "" ;
222- if ( fromId && toId && fromId !== toId ) {
236+ // Only apply this logic to handle-initiated drags. This avoids changing the cursor for unrelated drags (e.g., text or external files).
237+ const isOurDnD = e . dataTransfer ?. types ?. includes ( "application/x-tiptap-dnd" ) ;
238+ if ( isOurDnD && fromId && toId && fromId !== toId ) {
239+ // Call preventDefault so browser respects dropEffect
240+ e . preventDefault ( ) ;
223241 try {
224242 if ( e . dataTransfer ) e . dataTransfer . dropEffect = "none" ;
225243 } catch { }
226- e . preventDefault ( ) ;
227244 e . stopPropagation ( ) ;
245+ // Add CSS class to body for cursor display
246+ document . body . classList . add ( "fern-dragging-blocked" ) ;
228247 return true ;
229248 }
249+ // If this drag isn't blocked, ensure the global cursor class is removed.
250+ document . body . classList . remove ( "fern-dragging-blocked" ) ;
251+ return false ;
252+ } ,
253+ dragleave : ( view , event ) => {
254+ // Do not remove the global cursor class on dragleave. Moving between children fires dragleave
255+ // continuously and would cause cursor flicker. Cleanup happens on dragover (when unblocked), drop, or dragend.
256+ return false ;
257+ } ,
258+ dragend : ( view , event ) => {
259+ // Clean up cursor class and global state when drag ends
260+ document . body . classList . remove ( "fern-dragging-blocked" ) ;
261+ ( window as any ) . __fernDraggingEditorId = undefined ;
230262 return false ;
231263 }
232264 }
0 commit comments