Skip to content

Commit 59f4d7c

Browse files
committed
feat: better drag drop support in bottom panel tabs
1 parent 91a5e82 commit 59f4d7c

1 file changed

Lines changed: 84 additions & 25 deletions

File tree

src/view/PanelView.js

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,55 @@ define(function (require, exports, module) {
277277
_$tabBar.find(".bottom-panel-tab").removeClass("drag-target");
278278
}
279279

280+
// Find the closest non-default, non-dragged tab to mouseX
281+
function findNearestDropTarget(mouseX) {
282+
let closest = null;
283+
let closestDist = Infinity;
284+
_$tabsOverflow.find(".bottom-panel-tab").each(function () {
285+
if (this === draggedTab) {
286+
return;
287+
}
288+
if ($(this).data("panel-id") === _defaultPanelId) {
289+
return;
290+
}
291+
const rect = this.getBoundingClientRect();
292+
const dist = Math.min(Math.abs(mouseX - rect.left), Math.abs(mouseX - rect.right));
293+
if (dist < closestDist) {
294+
closestDist = dist;
295+
closest = this;
296+
}
297+
});
298+
return closest;
299+
}
300+
301+
// Perform the reorder in _openIds and rebuild the tab bar
302+
function reorderTabs(targetEl, mouseX) {
303+
if (!draggedTab || targetEl === draggedTab) {
304+
return;
305+
}
306+
let draggedId = $(draggedTab).data("panel-id");
307+
let targetId = $(targetEl).data("panel-id");
308+
let fromIdx = _openIds.indexOf(draggedId);
309+
let toIdx = _openIds.indexOf(targetId);
310+
if (fromIdx === -1 || toIdx === -1) {
311+
return;
312+
}
313+
const insertBefore = getDropPosition(targetEl, mouseX);
314+
_openIds.splice(fromIdx, 1);
315+
let newIdx = _openIds.indexOf(targetId);
316+
if (!insertBefore) {
317+
newIdx++;
318+
}
319+
// Never place a tab before the pinned Quick Access tab at index 0
320+
if (newIdx < 1 && _defaultPanelId && _openIds[0] === _defaultPanelId) {
321+
newIdx = 1;
322+
}
323+
_openIds.splice(newIdx, 0, draggedId);
324+
_updateBottomPanelTabBar();
325+
_updateActiveTabHighlight();
326+
}
327+
280328
_$tabBar.on("dragstart", ".bottom-panel-tab", function (e) {
281-
// Default panel (Tools) tab is never draggable
282329
if ($(this).data("panel-id") === _defaultPanelId) {
283330
e.preventDefault();
284331
return;
@@ -297,21 +344,38 @@ define(function (require, exports, module) {
297344
if (!draggedTab || this === draggedTab) {
298345
return;
299346
}
300-
// Don't allow dropping onto the pinned Quick Access tab
301347
if ($(this).data("panel-id") === _defaultPanelId) {
302348
return;
303349
}
304350
e.preventDefault();
305351
e.originalEvent.dataTransfer.dropEffect = "move";
306352
_$tabBar.find(".bottom-panel-tab").removeClass("drag-target");
307353
$(this).addClass("drag-target");
308-
let insertBefore = getDropPosition(this, e.originalEvent.clientX);
309-
// Can't insert before the first non-default tab (would go ahead of pinned tab)
310-
const targetIdx = _openIds.indexOf($(this).data("panel-id"));
311-
if (insertBefore && targetIdx <= 1) {
312-
insertBefore = false;
354+
updateIndicator(this, getDropPosition(this, e.originalEvent.clientX));
355+
});
356+
357+
// Fallback dragover on the overflow container: handles gaps between
358+
// tabs, empty space after the last tab, and the Quick Access tab area
359+
// by snapping to the nearest valid drop target.
360+
_$tabsOverflow.on("dragover", function (e) {
361+
if (!draggedTab) {
362+
return;
363+
}
364+
// Skip if already over a valid tab (the tab handler above covers it)
365+
const $closestTab = $(e.target).closest(".bottom-panel-tab");
366+
if ($closestTab.length && $closestTab[0] !== draggedTab
367+
&& $closestTab.data("panel-id") !== _defaultPanelId) {
368+
return;
313369
}
314-
updateIndicator(this, insertBefore);
370+
const nearest = findNearestDropTarget(e.originalEvent.clientX);
371+
if (!nearest) {
372+
return;
373+
}
374+
e.preventDefault();
375+
e.originalEvent.dataTransfer.dropEffect = "move";
376+
_$tabBar.find(".bottom-panel-tab").removeClass("drag-target");
377+
$(nearest).addClass("drag-target");
378+
updateIndicator(nearest, getDropPosition(nearest, e.originalEvent.clientX));
315379
});
316380

317381
_$tabBar.on("dragleave", ".bottom-panel-tab", function (e) {
@@ -328,28 +392,23 @@ define(function (require, exports, module) {
328392
cleanup();
329393
return;
330394
}
331-
let draggedId = $(draggedTab).data("panel-id");
332-
let targetId = $(this).data("panel-id");
333-
let fromIdx = _openIds.indexOf(draggedId);
334-
let toIdx = _openIds.indexOf(targetId);
335-
if (fromIdx === -1 || toIdx === -1) {
395+
reorderTabs(this, e.originalEvent.clientX);
396+
cleanup();
397+
});
398+
399+
// Fallback drop on the overflow container
400+
_$tabsOverflow.on("drop", function (e) {
401+
if (!draggedTab) {
336402
cleanup();
337403
return;
338404
}
339-
const insertBefore = getDropPosition(this, e.originalEvent.clientX);
340-
_openIds.splice(fromIdx, 1);
341-
let newIdx = _openIds.indexOf(targetId);
342-
if (!insertBefore) {
343-
newIdx++;
344-
}
345-
// Never place a tab before the pinned Quick Access tab at index 0
346-
if (newIdx < 1 && _defaultPanelId && _openIds[0] === _defaultPanelId) {
347-
newIdx = 1;
405+
const nearest = findNearestDropTarget(e.originalEvent.clientX);
406+
if (nearest) {
407+
e.preventDefault();
408+
e.stopPropagation();
409+
reorderTabs(nearest, e.originalEvent.clientX);
348410
}
349-
_openIds.splice(newIdx, 0, draggedId);
350411
cleanup();
351-
_updateBottomPanelTabBar();
352-
_updateActiveTabHighlight();
353412
});
354413
}
355414

0 commit comments

Comments
 (0)