Skip to content

Commit 0cef6ea

Browse files
committed
fix: sometimes placeholder tabs appear when dragging dropping tabs in between panes
1 parent f2fd882 commit 0cef6ea

File tree

1 file changed

+213
-76
lines changed

1 file changed

+213
-76
lines changed

src/extensionsIntegrated/TabBar/drag-drop.js

Lines changed: 213 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)