@@ -148,7 +148,7 @@ define(function (require, exports, module) {
148148 */
149149 function _buildTab ( panel , isActive ) {
150150 let title = panel . _tabTitle || _getPanelTitle ( panel . panelID , panel . $panel ) ;
151- let $tab = $ ( '<div class="bottom-panel-tab"></div>' )
151+ let $tab = $ ( '<div class="bottom-panel-tab" draggable="true" ></div>' )
152152 . toggleClass ( 'active' , isActive )
153153 . attr ( 'data-panel-id' , panel . panelID ) ;
154154 const opts = panel . _options ;
@@ -171,6 +171,10 @@ define(function (require, exports, module) {
171171 if ( ! _$tabsOverflow ) {
172172 return ;
173173 }
174+ // Detach the add button before emptying to preserve its event handlers
175+ if ( _$addBtn ) {
176+ _$addBtn . detach ( ) ;
177+ }
174178 _$tabsOverflow . empty ( ) ;
175179
176180 _openIds . forEach ( function ( panelId ) {
@@ -248,6 +252,103 @@ define(function (require, exports, module) {
248252 _checkTabOverflow ( ) ;
249253 }
250254
255+ /**
256+ * Set up drag-and-drop tab reordering on the bottom panel tab bar.
257+ * Uses a vertical line indicator matching the file tab bar UX.
258+ * @private
259+ */
260+ function _initDragAndDrop ( ) {
261+ let draggedTab = null ;
262+ let $indicator = $ ( '<div class="tab-drag-indicator"></div>' ) ;
263+ $ ( "body" ) . append ( $indicator ) ;
264+
265+ function getDropPosition ( targetTab , mouseX ) {
266+ const rect = targetTab . getBoundingClientRect ( ) ;
267+ return mouseX < rect . left + rect . width / 2 ;
268+ }
269+
270+ function updateIndicator ( targetTab , insertBefore ) {
271+ if ( ! targetTab ) {
272+ $indicator . hide ( ) ;
273+ return ;
274+ }
275+ const rect = targetTab . getBoundingClientRect ( ) ;
276+ $indicator . css ( {
277+ position : "fixed" ,
278+ left : ( insertBefore ? rect . left : rect . right ) + "px" ,
279+ top : rect . top + "px" ,
280+ height : rect . height + "px" ,
281+ width : "2px" ,
282+ zIndex : 10001
283+ } ) . show ( ) ;
284+ }
285+
286+ function cleanup ( ) {
287+ if ( draggedTab ) {
288+ $ ( draggedTab ) . removeClass ( "bottom-panel-tab-dragging" ) ;
289+ }
290+ draggedTab = null ;
291+ $indicator . hide ( ) ;
292+ _$tabBar . find ( ".bottom-panel-tab" ) . removeClass ( "drag-target" ) ;
293+ }
294+
295+ _$tabBar . on ( "dragstart" , ".bottom-panel-tab" , function ( e ) {
296+ draggedTab = this ;
297+ e . originalEvent . dataTransfer . effectAllowed = "move" ;
298+ e . originalEvent . dataTransfer . setData ( "text/plain" , "panel-tab" ) ;
299+ $ ( this ) . addClass ( "bottom-panel-tab-dragging" ) ;
300+ } ) ;
301+
302+ _$tabBar . on ( "dragend" , ".bottom-panel-tab" , function ( ) {
303+ setTimeout ( cleanup , 50 ) ;
304+ } ) ;
305+
306+ _$tabBar . on ( "dragover" , ".bottom-panel-tab" , function ( e ) {
307+ if ( ! draggedTab || this === draggedTab ) {
308+ return ;
309+ }
310+ e . preventDefault ( ) ;
311+ e . originalEvent . dataTransfer . dropEffect = "move" ;
312+ _$tabBar . find ( ".bottom-panel-tab" ) . removeClass ( "drag-target" ) ;
313+ $ ( this ) . addClass ( "drag-target" ) ;
314+ updateIndicator ( this , getDropPosition ( this , e . originalEvent . clientX ) ) ;
315+ } ) ;
316+
317+ _$tabBar . on ( "dragleave" , ".bottom-panel-tab" , function ( e ) {
318+ const related = e . originalEvent . relatedTarget ;
319+ if ( ! $ ( this ) . is ( related ) && ! $ ( this ) . has ( related ) . length ) {
320+ $ ( this ) . removeClass ( "drag-target" ) ;
321+ }
322+ } ) ;
323+
324+ _$tabBar . on ( "drop" , ".bottom-panel-tab" , function ( e ) {
325+ e . preventDefault ( ) ;
326+ e . stopPropagation ( ) ;
327+ if ( ! draggedTab || this === draggedTab ) {
328+ cleanup ( ) ;
329+ return ;
330+ }
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 ) {
336+ cleanup ( ) ;
337+ return ;
338+ }
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+ _openIds . splice ( newIdx , 0 , draggedId ) ;
346+ cleanup ( ) ;
347+ _updateBottomPanelTabBar ( ) ;
348+ _updateActiveTabHighlight ( ) ;
349+ } ) ;
350+ }
351+
251352 /**
252353 * Check if the tab bar is overflowing and collapse tabs to icons if so.
253354 * Only collapses tabs that have an icon available.
@@ -261,6 +362,15 @@ define(function (require, exports, module) {
261362 _$tabBar . removeClass ( "bottom-panel-tabs-collapsed" ) ;
262363 const isOverflowing = _$tabsOverflow [ 0 ] . scrollWidth > _$tabsOverflow [ 0 ] . clientWidth ;
263364 _$tabBar . toggleClass ( "bottom-panel-tabs-collapsed" , isOverflowing ) ;
365+ // Show tooltip on hover only in collapsed mode (title text is hidden)
366+ _$tabBar . find ( ".bottom-panel-tab" ) . each ( function ( ) {
367+ const $tab = $ ( this ) ;
368+ if ( isOverflowing ) {
369+ $tab . attr ( "title" , $tab . find ( ".bottom-panel-tab-title" ) . text ( ) ) ;
370+ } else {
371+ $tab . removeAttr ( "title" ) ;
372+ }
373+ } ) ;
264374 }
265375
266376 /**
@@ -584,6 +694,8 @@ define(function (require, exports, module) {
584694 }
585695 } ) ;
586696
697+ _initDragAndDrop ( ) ;
698+
587699 // "+" button opens the default/quick-access panel
588700 _$addBtn . on ( "click" , function ( e ) {
589701 e . stopPropagation ( ) ;
0 commit comments