@@ -47,7 +47,17 @@ define(function (require, exports, module) {
4747 function cleanupDragState ( ) {
4848 $ ( ".tab" ) . removeClass ( "dragging drag-target" ) ;
4949 $ ( ".empty-pane-drop-target" ) . removeClass ( "empty-pane-drop-target" ) ;
50- updateDragIndicator ( null ) ;
50+
51+ // this is to make sure that the drag indicator is hidden and remove any inline styles
52+ if ( dragIndicator ) {
53+ dragIndicator . hide ( ) . css ( {
54+ top : '' ,
55+ left : '' ,
56+ height : ''
57+ } ) ;
58+ }
59+
60+ // Reset all drag state variables
5161 draggedTab = null ;
5262 dragOverTab = null ;
5363 dragSourcePane = null ;
@@ -58,6 +68,17 @@ define(function (require, exports, module) {
5868 }
5969
6070 $ ( "#tab-drag-extended-zone" ) . remove ( ) ;
71+
72+ // this is needed to make sure that all the drag-active styling are properly hidden
73+ // it is required because noticed a bug where sometimes some styles remain when drop fails
74+ $ ( ".phoenix-tab-bar" ) . removeClass ( "drag-active" ) ;
75+
76+ setTimeout ( ( ) => {
77+ // a double check just to make sure that the drag indicator is still hidden
78+ if ( dragIndicator && dragIndicator . is ( ':visible' ) ) {
79+ dragIndicator . hide ( ) ;
80+ }
81+ } , 5 ) ;
6182 }
6283
6384 /**
@@ -83,6 +104,9 @@ define(function (require, exports, module) {
83104
84105 // add initialization for empty panes
85106 initEmptyPaneDropTargets ( ) ;
107+
108+ // Set up global drag cleanup handlers to ensure drag state is always cleaned up
109+ setupGlobalDragCleanup ( ) ;
86110 }
87111
88112 /**
@@ -165,10 +189,6 @@ define(function (require, exports, module) {
165189 }
166190 } ;
167191
168- const removeOuterDropZone = ( ) => {
169- $ ( "#tab-drag-extended-zone" ) . remove ( ) ;
170- } ;
171-
172192 // When dragging over the container but not directly over a tab element
173193 $container . on ( "dragover" , function ( e ) {
174194 if ( e . preventDefault ) {
@@ -276,14 +296,10 @@ define(function (require, exports, module) {
276296 if ( mouseX > containerRect . right ) {
277297 targetTab = $tabs . last ( ) [ 0 ] ;
278298 onLeftSide = false ;
279- }
280- // If beyond the left edge, use the first tab
281- else if ( mouseX < containerRect . left ) {
299+ } else if ( mouseX < containerRect . left ) { // If beyond the left edge, use the first tab
282300 targetTab = $tabs . first ( ) [ 0 ] ;
283301 onLeftSide = true ;
284- }
285- // If within bounds, find the closest tab
286- else {
302+ } else { // If within bounds, find the closest tab
287303 onLeftSide = mouseX < containerRect . left + containerRect . width / 2 ;
288304 targetTab = onLeftSide ? $tabs . first ( ) [ 0 ] : $tabs . last ( ) [ 0 ] ;
289305 }
@@ -384,6 +400,10 @@ define(function (require, exports, module) {
384400 * @param {Event } e - The event object
385401 */
386402 function handleDragStart ( e ) {
403+ if ( draggedTab ) {
404+ cleanupDragState ( ) ;
405+ }
406+
387407 // store reference to the dragged tab
388408 draggedTab = this ;
389409
@@ -401,7 +421,10 @@ define(function (require, exports, module) {
401421 // Use a timeout to let the dragging class apply before taking measurements
402422 // This ensures visual updates are applied before we calculate positions
403423 setTimeout ( ( ) => {
404- updateDragIndicator ( null ) ;
424+ // Ensure the drag indicator is properly hidden at the start
425+ if ( dragIndicator ) {
426+ dragIndicator . hide ( ) ;
427+ }
405428 } , 0 ) ;
406429 }
407430
@@ -465,36 +488,43 @@ define(function (require, exports, module) {
465488 * @param {Event } e - The event object
466489 */
467490 function handleDrop ( e ) {
468- if ( e . stopPropagation ) {
469- e . stopPropagation ( ) ; // Stops browser from redirecting
470- }
491+ try {
492+ if ( e . stopPropagation ) {
493+ e . stopPropagation ( ) ; // Stops browser from redirecting
494+ }
495+
496+ // Only process the drop if the dragged tab is different from the drop target
497+ if ( draggedTab !== this ) {
498+ // Determine which pane the drop target belongs to
499+ const isSecondPane = $ ( this ) . closest ( "#phoenix-tab-bar-2" ) . length > 0 ;
500+ const targetPaneId = isSecondPane ? "second-pane" : "first-pane" ;
501+ const draggedPath = $ ( draggedTab ) . attr ( "data-path" ) ;
502+ const targetPath = $ ( this ) . attr ( "data-path" ) ;
471503
472- // Only process the drop if the dragged tab is different from the drop target
473- if ( draggedTab !== this ) {
474- // Determine which pane the drop target belongs to
475- const isSecondPane = $ ( this ) . closest ( "#phoenix-tab-bar-2" ) . length > 0 ;
476- const targetPaneId = isSecondPane ? "second-pane" : "first-pane" ;
477- const draggedPath = $ ( draggedTab ) . attr ( "data-path" ) ;
478- const targetPath = $ ( this ) . attr ( "data-path" ) ;
504+ // Determine if we're dropping to the left or right of the target
505+ const targetRect = this . getBoundingClientRect ( ) ;
506+ const mouseX = e . originalEvent . clientX ;
507+ const midPoint = targetRect . left + targetRect . width / 2 ;
508+ const onLeftSide = mouseX < midPoint ;
479509
480- // Determine if we're dropping to the left or right of the target
481- const targetRect = this . getBoundingClientRect ( ) ;
482- const mouseX = e . originalEvent . clientX ;
483- const midPoint = targetRect . left + targetRect . width / 2 ;
484- const onLeftSide = mouseX < midPoint ;
485-
486- // Check if dragging between different panes
487- if ( dragSourcePane !== targetPaneId ) {
488- // Move the tab between panes
489- moveTabBetweenPanes ( dragSourcePane , targetPaneId , draggedPath , targetPath , onLeftSide ) ;
490- } else {
491- // Move within the same pane
492- moveWorkingSetItem ( targetPaneId , draggedPath , targetPath , onLeftSide ) ;
510+ // Check if dragging between different panes
511+ if ( dragSourcePane !== targetPaneId ) {
512+ // Move the tab between panes
513+ moveTabBetweenPanes ( dragSourcePane , targetPaneId , draggedPath , targetPath , onLeftSide ) ;
514+ } else {
515+ // Move within the same pane
516+ moveWorkingSetItem ( targetPaneId , draggedPath , targetPath , onLeftSide ) ;
517+ }
493518 }
494- }
495519
496- cleanupDragState ( ) ;
497- return false ;
520+ cleanupDragState ( ) ;
521+ return false ;
522+ } catch ( error ) {
523+ console . error ( "Error during tab drop operation:" , error ) ;
524+ // Ensure cleanup happens even if there's an error
525+ cleanupDragState ( ) ;
526+ return false ;
527+ }
498528 }
499529
500530 /**
@@ -504,7 +534,50 @@ define(function (require, exports, module) {
504534 * @param {Event } e - The event object
505535 */
506536 function handleDragEnd ( e ) {
507- cleanupDragState ( ) ;
537+ setTimeout ( ( ) => {
538+ cleanupDragState ( ) ;
539+ } , 10 ) ;
540+ }
541+
542+ /**
543+ * Global document event listeners to ensure drag state is always cleaned up
544+ * This handles cases where drag operations fail or are cancelled outside
545+ * the normal tab bar drop zones
546+ */
547+ function setupGlobalDragCleanup ( ) {
548+ // Listen for drags ending anywhere on the document
549+ $ ( document ) . on ( 'dragend' , function ( e ) {
550+ // Only clean up if we were tracking a drag operation
551+ if ( draggedTab ) {
552+ setTimeout ( ( ) => {
553+ cleanupDragState ( ) ;
554+ } , 10 ) ;
555+ }
556+ } ) ;
557+
558+ // Listen for global mouse up events to catch cancelled drags
559+ $ ( document ) . on ( 'mouseup' , function ( e ) {
560+ // If we have an active drag but mouse is released, clean up
561+ if ( draggedTab && ! e . originalEvent . dataTransfer ) {
562+ setTimeout ( ( ) => {
563+ cleanupDragState ( ) ;
564+ } , 10 ) ;
565+ }
566+ } ) ;
567+
568+ // Listen for ESC key to cancel drag operations
569+ $ ( document ) . on ( 'keydown' , function ( e ) {
570+ if ( e . key === 'Escape' && draggedTab ) {
571+ cleanupDragState ( ) ;
572+ }
573+ } ) ;
574+
575+ // Listen for page visibility changes (like alt-tab) to clean up
576+ $ ( document ) . on ( 'visibilitychange' , function ( ) {
577+ if ( document . hidden && draggedTab ) {
578+ cleanupDragState ( ) ;
579+ }
580+ } ) ;
508581 }
509582
510583 /**
@@ -599,51 +672,81 @@ define(function (require, exports, module) {
599672 * @param {Boolean } beforeTarget - Whether to place before or after the target
600673 */
601674 function moveTabBetweenPanes ( sourcePaneId , targetPaneId , draggedPath , targetPath , beforeTarget ) {
602- const sourceWorkingSet = MainViewManager . getWorkingSet ( sourcePaneId ) ;
603- const targetWorkingSet = MainViewManager . getWorkingSet ( targetPaneId ) ;
604-
605- let draggedIndex = - 1 ;
606- let targetIndex = - 1 ;
607- let draggedFile = null ;
608-
609- // Find the dragged file and its index in the source pane
610- for ( let i = 0 ; i < sourceWorkingSet . length ; i ++ ) {
611- if ( sourceWorkingSet [ i ] . fullPath === draggedPath ) {
612- draggedIndex = i ;
613- draggedFile = sourceWorkingSet [ i ] ;
614- break ;
675+ try {
676+ const sourceWorkingSet = MainViewManager . getWorkingSet ( sourcePaneId ) ;
677+ const targetWorkingSet = MainViewManager . getWorkingSet ( targetPaneId ) ;
678+
679+ let draggedIndex = - 1 ;
680+ let targetIndex = - 1 ;
681+ let draggedFile = null ;
682+
683+ // Find the dragged file and its index in the source pane
684+ for ( let i = 0 ; i < sourceWorkingSet . length ; i ++ ) {
685+ if ( sourceWorkingSet [ i ] . fullPath === draggedPath ) {
686+ draggedIndex = i ;
687+ draggedFile = sourceWorkingSet [ i ] ;
688+ break ;
689+ }
615690 }
616- }
617691
618- // Find the target index in the target pane
619- for ( let i = 0 ; i < targetWorkingSet . length ; i ++ ) {
620- if ( targetWorkingSet [ i ] . fullPath === targetPath ) {
621- targetIndex = i ;
622- break ;
692+ // Find the target index in the target pane
693+ for ( let i = 0 ; i < targetWorkingSet . length ; i ++ ) {
694+ if ( targetWorkingSet [ i ] . fullPath === targetPath ) {
695+ targetIndex = i ;
696+ break ;
697+ }
623698 }
624- }
625699
626- // Only continue if we found the dragged file
627- if ( draggedIndex !== - 1 && draggedFile ) {
628- // Remove the file from source pane
629- CommandManager . execute ( Commands . FILE_CLOSE , { file : draggedFile , paneId : sourcePaneId } ) ;
700+ // Only continue if we found the dragged file
701+ if ( draggedIndex !== - 1 && draggedFile ) {
702+ // Check if the dragged file is currently active in the source pane
703+ const currentActiveFileInSource = MainViewManager . getCurrentlyViewedFile ( sourcePaneId ) ;
704+ const isActiveFileBeingMoved = currentActiveFileInSource &&
705+ currentActiveFileInSource . fullPath === draggedPath ;
706+
707+ // If the active file is being moved and there are other files in the source pane,
708+ // switch to another file first to prevent placeholder creation
709+ if ( isActiveFileBeingMoved && sourceWorkingSet . length > 1 ) {
710+ // Find another file to make active (prefer the next file, or previous if this is the last)
711+ let newActiveIndex = draggedIndex + 1 ;
712+ if ( newActiveIndex >= sourceWorkingSet . length ) {
713+ newActiveIndex = draggedIndex - 1 ;
714+ }
630715
631- // Calculate where to add it in the target pane
632- let targetInsertIndex ;
716+ if ( newActiveIndex >= 0 && newActiveIndex < sourceWorkingSet . length ) {
717+ const newActiveFile = sourceWorkingSet [ newActiveIndex ] ;
718+ // Open the new active file in the source pane before removing the dragged file
719+ CommandManager . execute ( Commands . FILE_OPEN , {
720+ fullPath : newActiveFile . fullPath ,
721+ paneId : sourcePaneId
722+ } ) ;
723+ }
724+ }
633725
634- if ( targetIndex !== - 1 ) {
635- // We have a specific target index to aim for
636- targetInsertIndex = beforeTarget ? targetIndex : targetIndex + 1 ;
637- } else {
638- // No specific target, add to end of the working set
639- targetInsertIndex = targetWorkingSet . length ;
640- }
726+ // Remove the file from source pane
727+ CommandManager . execute ( Commands . FILE_CLOSE , { file : draggedFile , paneId : sourcePaneId } ) ;
641728
642- // Add to the target pane at the calculated position
643- MainViewManager . addToWorkingSet ( targetPaneId , draggedFile , targetInsertIndex ) ;
729+ // Calculate where to add it in the target pane
730+ let targetInsertIndex ;
644731
645- // we always need to make the dragged tab active in the target pane when moving between panes
646- CommandManager . execute ( Commands . FILE_OPEN , { fullPath : draggedPath , paneId : targetPaneId } ) ;
732+ if ( targetIndex !== - 1 ) {
733+ // We have a specific target index to aim for
734+ targetInsertIndex = beforeTarget ? targetIndex : targetIndex + 1 ;
735+ } else {
736+ // No specific target, add to end of the working set
737+ targetInsertIndex = targetWorkingSet . length ;
738+ }
739+
740+ // Add to the target pane at the calculated position
741+ MainViewManager . addToWorkingSet ( targetPaneId , draggedFile , targetInsertIndex ) ;
742+
743+ // we always need to make the dragged tab active in the target pane when moving between panes
744+ CommandManager . execute ( Commands . FILE_OPEN , { fullPath : draggedPath , paneId : targetPaneId } ) ;
745+ }
746+ } catch ( error ) {
747+ console . error ( "Error during cross-pane tab move:" , error ) ;
748+ // Even if there's an error, ensure the drag state is cleaned up
749+ cleanupDragState ( ) ;
647750 }
648751 }
649752
@@ -726,6 +829,40 @@ define(function (require, exports, module) {
726829
727830 if ( draggedFile ) {
728831 if ( sourcePaneId !== paneId ) {
832+ // Check if the dragged file is currently active in the source pane
833+ const currentActiveFileInSource = MainViewManager . getCurrentlyViewedFile ( sourcePaneId ) ;
834+ const isActiveFileBeingMoved = currentActiveFileInSource &&
835+ currentActiveFileInSource . fullPath === draggedPath ;
836+
837+ // If the active file is being moved and there are other files in the source pane,
838+ // switch to another file first to prevent placeholder creation
839+ if ( isActiveFileBeingMoved && sourceWorkingSet . length > 1 ) {
840+ // Find another file to make active
841+ let draggedIndex = - 1 ;
842+ for ( let i = 0 ; i < sourceWorkingSet . length ; i ++ ) {
843+ if ( sourceWorkingSet [ i ] . fullPath === draggedPath ) {
844+ draggedIndex = i ;
845+ break ;
846+ }
847+ }
848+
849+ if ( draggedIndex !== - 1 ) {
850+ let newActiveIndex = draggedIndex + 1 ;
851+ if ( newActiveIndex >= sourceWorkingSet . length ) {
852+ newActiveIndex = draggedIndex - 1 ;
853+ }
854+
855+ if ( newActiveIndex >= 0 && newActiveIndex < sourceWorkingSet . length ) {
856+ const newActiveFile = sourceWorkingSet [ newActiveIndex ] ;
857+ // Open the new active file in the source pane before removing the dragged file
858+ CommandManager . execute ( Commands . FILE_OPEN , {
859+ fullPath : newActiveFile . fullPath ,
860+ paneId : sourcePaneId
861+ } ) ;
862+ }
863+ }
864+ }
865+
729866 // If different panes, close in source pane
730867 CommandManager . execute ( Commands . FILE_CLOSE , { file : draggedFile , paneId : sourcePaneId } ) ;
731868
0 commit comments