@@ -94,23 +94,6 @@ const errorBaseTheme = EditorView.baseTheme({
9494 } ,
9595} ) ;
9696
97- const pasteHandler = EditorView . domEventHandlers ( {
98- paste ( event , view ) {
99- const text = event . clipboardData ?. getData ( 'text/plain' ) ;
100- if ( text ) {
101- const { state } = view ;
102- const { from, to } = state . selection . main ;
103- view . dispatch ( {
104- changes : { from, to, insert : text } ,
105- selection : { anchor : from + text . length } ,
106- } ) ;
107- event . preventDefault ( ) ;
108- return true ;
109- }
110- return false ;
111- } ,
112- } ) ;
113-
11497
11598const EDITOR_COLORS : Record < DaxEditorTheme , Record < 'light' | 'dark' , EditorColors > > = {
11699 default : {
@@ -370,7 +353,6 @@ export const DaxEditor = forwardRef<DaxEditorHandle, DaxEditorProps>(function Da
370353 colorPickerExtension ( swatchCallbackRef ) ,
371354 errorField ,
372355 errorBaseTheme ,
373- pasteHandler ,
374356 highlightCompartmentRef . current . of ( syntaxHighlighting ( buildHighlight ( daxEditorTheme , theme ) ) ) ,
375357 themeCompartmentRef . current . of ( buildTheme ( daxEditorTheme , theme ) ) ,
376358 fontCompartmentRef . current . of ( buildFontSizeTheme ( fontSize ) ) ,
@@ -405,18 +387,32 @@ export const DaxEditor = forwardRef<DaxEditorHandle, DaxEditorProps>(function Da
405387 const handleScroll = ( ) => setShowScrollTop ( scroller . scrollTop > 300 ) ;
406388 scroller . addEventListener ( 'scroll' , handleScroll , { passive : true } ) ;
407389
408- // Add global paste handler to ensure paste works even if editor loses focus
390+ // Global paste handler: routes paste to the editor whenever focus is NOT on
391+ // a native editable element. This covers two cases:
392+ // 1. Focus on .cm-scroller / gutters (user clicked line numbers) — CodeMirror
393+ // only registers paste on .cm-content so those events are otherwise lost.
394+ // 2. Focus on body/html — user returned from another app without clicking.
395+ // When .cm-content is focused, CodeMirror handles paste natively (contenteditable
396+ // === "true"), so we bail out to avoid double-inserting.
409397 const handleGlobalPaste = ( event : ClipboardEvent ) => {
410398 const text = event . clipboardData ?. getData ( 'text/plain' ) ;
411- if ( text && containerRef . current ?. contains ( document . activeElement ) ) {
412- const { state } = view ;
413- const { from, to } = state . selection . main ;
414- view . dispatch ( {
415- changes : { from, to, insert : text } ,
416- selection : { anchor : from + text . length } ,
417- } ) ;
418- event . preventDefault ( ) ;
419- }
399+ if ( ! text ) return ;
400+
401+ const active = document . activeElement as HTMLElement | null ;
402+
403+ // .cm-content or any other contenteditable: handled natively, skip.
404+ if ( active ?. getAttribute ( 'contenteditable' ) === 'true' ) return ;
405+
406+ // Another text input field: don't steal the paste.
407+ if ( active ?. tagName === 'INPUT' || active ?. tagName === 'TEXTAREA' ) return ;
408+
409+ // Everything else (body, .cm-scroller, gutters, buttons…): paste into editor.
410+ const { from, to } = view . state . selection . main ;
411+ view . dispatch ( {
412+ changes : { from, to, insert : text } ,
413+ selection : { anchor : from + text . length } ,
414+ } ) ;
415+ event . preventDefault ( ) ;
420416 } ;
421417
422418 document . addEventListener ( 'paste' , handleGlobalPaste , { capture : true } ) ;
0 commit comments