@@ -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