From cee53fc3b95b48a5956155a44fa9ba4383760c19 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 22 May 2025 19:38:40 +0530 Subject: [PATCH 1/6] fix: unable to drag and drop tabs over the editor area --- docs/API-Reference/command/Commands.md | 12 ++++ src/extensionsIntegrated/TabBar/drag-drop.js | 61 +++++++++++++++----- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/docs/API-Reference/command/Commands.md b/docs/API-Reference/command/Commands.md index 783376af39..ef97165764 100644 --- a/docs/API-Reference/command/Commands.md +++ b/docs/API-Reference/command/Commands.md @@ -824,6 +824,18 @@ Sorts working set by file type ## CMD\_WORKING\_SORT\_TOGGLE\_AUTO Toggles automatic working set sorting +**Kind**: global variable + + +## CMD\_TOGGLE\_SHOW\_WORKING\_SET +Toggles working set visibility + +**Kind**: global variable + + +## CMD\_TOGGLE\_SHOW\_FILE\_TABS +Toggles file tabs visibility + **Kind**: global variable diff --git a/src/extensionsIntegrated/TabBar/drag-drop.js b/src/extensionsIntegrated/TabBar/drag-drop.js index ef0d912653..0c6ed74eb4 100644 --- a/src/extensionsIntegrated/TabBar/drag-drop.js +++ b/src/extensionsIntegrated/TabBar/drag-drop.js @@ -671,11 +671,7 @@ define(function (require, exports, module) { // Handle drag over empty pane $paneHolder.on("dragover dragenter", function (e) { - // we only want to process if this pane is empty (has no tab bar or has hidden tab bar) - const $tabBar = paneId === "first-pane" ? $("#phoenix-tab-bar") : $("#phoenix-tab-bar-2"); - const isEmptyPane = !$tabBar.length || $tabBar.is(":hidden") || $tabBar.children(".tab").length === 0; - - if (isEmptyPane && draggedTab) { + if (draggedTab) { e.preventDefault(); e.stopPropagation(); @@ -697,7 +693,7 @@ define(function (require, exports, module) { const $tabBar = paneId === "first-pane" ? $("#phoenix-tab-bar") : $("#phoenix-tab-bar-2"); const isEmptyPane = !$tabBar.length || $tabBar.is(":hidden") || $tabBar.children(".tab").length === 0; - if (isEmptyPane && draggedTab) { + if (draggedTab) { e.preventDefault(); e.stopPropagation(); @@ -711,9 +707,11 @@ define(function (require, exports, module) { const sourcePaneId = $(draggedTab).closest("#phoenix-tab-bar-2").length > 0 ? "second-pane" : "first-pane"; - // we don't want to do anything if dropping in the same pane - if (sourcePaneId !== paneId) { + // we only want to proceed if we're not dropping in the same pane or, + // allow if it's the same pane with existing tabs + if (sourcePaneId !== paneId || !isEmptyPane) { const sourceWorkingSet = MainViewManager.getWorkingSet(sourcePaneId); + const targetWorkingSet = MainViewManager.getWorkingSet(paneId); let draggedFile = null; // Find the dragged file in the source pane @@ -725,12 +723,47 @@ define(function (require, exports, module) { } if (draggedFile) { - // close in the source pane - CommandManager.execute(Commands.FILE_CLOSE, { file: draggedFile, paneId: sourcePaneId }); - - // and open in the target pane - MainViewManager.addToWorkingSet(paneId, draggedFile); - CommandManager.execute(Commands.FILE_OPEN, { fullPath: draggedPath, paneId: paneId }); + if (sourcePaneId !== paneId) { + // If different panes, close in source pane + CommandManager.execute(Commands.FILE_CLOSE, { file: draggedFile, paneId: sourcePaneId }); + + // For non-empty panes, find current active file to place tab after it + if (!isEmptyPane && targetWorkingSet.length > 0) { + const currentActiveFile = MainViewManager.getCurrentlyViewedFile(paneId); + + if (currentActiveFile) { + // Find index of current active file + let targetIndex = -1; + for (let i = 0; i < targetWorkingSet.length; i++) { + if (targetWorkingSet[i].fullPath === currentActiveFile.fullPath) { + targetIndex = i; + break; + } + } + + if (targetIndex !== -1) { + // Add after current active file + MainViewManager.addToWorkingSet(paneId, draggedFile, targetIndex + 1); + } else { + // Fallback to adding at the end + MainViewManager.addToWorkingSet(paneId, draggedFile); + } + } else { + // No active file, add to the end + MainViewManager.addToWorkingSet(paneId, draggedFile); + } + } else { + // Empty pane, just add it + MainViewManager.addToWorkingSet(paneId, draggedFile); + } + + // Open file in target pane + CommandManager.execute(Commands.FILE_OPEN, { fullPath: draggedPath, paneId: paneId }); + } else if (isEmptyPane) { + // Same pane, empty pane case (should never happen but kept for safety) + MainViewManager.addToWorkingSet(paneId, draggedFile); + CommandManager.execute(Commands.FILE_OPEN, { fullPath: draggedPath, paneId: paneId }); + } } } From b2d467d8c9e499d55ce7f4e63b7e6db0d8d1ec8f Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 22 May 2025 23:54:09 +0530 Subject: [PATCH 2/6] fix: dragging tabs between panes is not making the dragged tab active --- src/extensionsIntegrated/TabBar/drag-drop.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/extensionsIntegrated/TabBar/drag-drop.js b/src/extensionsIntegrated/TabBar/drag-drop.js index 0c6ed74eb4..3e0b42796a 100644 --- a/src/extensionsIntegrated/TabBar/drag-drop.js +++ b/src/extensionsIntegrated/TabBar/drag-drop.js @@ -635,13 +635,8 @@ define(function (require, exports, module) { // Add to the target pane at the calculated position MainViewManager.addToWorkingSet(targetPaneId, draggedFile, targetInsertIndex); - // If the tab was the active one in the source pane, - // make it active in the target pane too - const activeFile = MainViewManager.getCurrentlyViewedFile(sourcePaneId); - if (activeFile && activeFile.fullPath === draggedPath) { - // Open the file in the target pane and make it active - CommandManager.execute(Commands.FILE_OPEN, { fullPath: draggedPath, paneId: targetPaneId }); - } + // we always need to make the dragged tab active in the target pane when moving between panes + CommandManager.execute(Commands.FILE_OPEN, { fullPath: draggedPath, paneId: targetPaneId }); } } From bdc068334f1b19bbe3ebccd181ee156d8f3aa3a4 Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 23 May 2025 00:14:09 +0530 Subject: [PATCH 3/6] fix: tab bar becomes unresponsive sometimes after drag and drop --- src/extensionsIntegrated/TabBar/drag-drop.js | 55 +++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/src/extensionsIntegrated/TabBar/drag-drop.js b/src/extensionsIntegrated/TabBar/drag-drop.js index 3e0b42796a..f3b342233d 100644 --- a/src/extensionsIntegrated/TabBar/drag-drop.js +++ b/src/extensionsIntegrated/TabBar/drag-drop.js @@ -39,6 +39,27 @@ define(function (require, exports, module) { let scrollInterval = null; let dragSourcePane = null; + /** + * this function is responsible to make sure that all the drag state is properly cleaned up + * it is needed to make sure that the tab bar doesn't get unresponsive + * because of handlers not being attached properly + */ + function cleanupDragState() { + $(".tab").removeClass("dragging drag-target"); + $(".empty-pane-drop-target").removeClass("empty-pane-drop-target"); + updateDragIndicator(null); + draggedTab = null; + dragOverTab = null; + dragSourcePane = null; + + if (scrollInterval) { + clearInterval(scrollInterval); + scrollInterval = null; + } + + $("#tab-drag-extended-zone").remove(); + } + /** * Initialize drag and drop functionality for tab bars * This is called from `main.js` @@ -202,9 +223,6 @@ define(function (require, exports, module) { if (e.preventDefault) { e.preventDefault(); } - // hide the drag indicator - updateDragIndicator(null); - removeOuterDropZone(); // get container dimensions to determine drop position const containerRect = this.getBoundingClientRect(); @@ -235,6 +253,9 @@ define(function (require, exports, module) { } } } + + // ensure all drag state is cleaned up + cleanupDragState(); }); /** @@ -311,9 +332,7 @@ define(function (require, exports, module) { } } - // Clean up - updateDragIndicator(null); - removeOuterDropZone(); + cleanupDragState(); } } @@ -449,7 +468,6 @@ define(function (require, exports, module) { if (e.stopPropagation) { e.stopPropagation(); // Stops browser from redirecting } - updateDragIndicator(null); // Only process the drop if the dragged tab is different from the drop target if (draggedTab !== this) { @@ -474,6 +492,8 @@ define(function (require, exports, module) { moveWorkingSetItem(targetPaneId, draggedPath, targetPath, onLeftSide); } } + + cleanupDragState(); return false; } @@ -484,20 +504,7 @@ define(function (require, exports, module) { * @param {Event} e - The event object */ function handleDragEnd(e) { - $(".tab").removeClass("dragging drag-target"); - updateDragIndicator(null); - draggedTab = null; - dragOverTab = null; - dragSourcePane = null; - - // Clear scroll interval if it exists - if (scrollInterval) { - clearInterval(scrollInterval); - scrollInterval = null; - } - - // Remove the extended drop zone if it exists - $("#tab-drag-extended-zone").remove(); + cleanupDragState(); } /** @@ -762,11 +769,7 @@ define(function (require, exports, module) { } } - // reset all drag state stuff - updateDragIndicator(null); - draggedTab = null; - dragOverTab = null; - dragSourcePane = null; + cleanupDragState(); } }); } From f2fd882cced14507320a7353e3164a7152033506 Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 23 May 2025 16:51:51 +0530 Subject: [PATCH 4/6] refactor: empty pane border styling --- src/styles/Extn-TabBar.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/Extn-TabBar.less b/src/styles/Extn-TabBar.less index 6f17f686d4..71ae2fb27c 100644 --- a/src/styles/Extn-TabBar.less +++ b/src/styles/Extn-TabBar.less @@ -409,7 +409,7 @@ } .empty-pane-drop-target { - border: 2px dashed #6db6ff !important; + border: 1px solid #6db6ff !important; } .dropdown-tab-item.placeholder-item .tab-name-container, From 0cef6ea2b58f22b672157e6413a81d71cfb2d39f Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 23 May 2025 17:12:59 +0530 Subject: [PATCH 5/6] fix: sometimes placeholder tabs appear when dragging dropping tabs in between panes --- src/extensionsIntegrated/TabBar/drag-drop.js | 289 ++++++++++++++----- 1 file changed, 213 insertions(+), 76 deletions(-) diff --git a/src/extensionsIntegrated/TabBar/drag-drop.js b/src/extensionsIntegrated/TabBar/drag-drop.js index f3b342233d..0a4dadf2ef 100644 --- a/src/extensionsIntegrated/TabBar/drag-drop.js +++ b/src/extensionsIntegrated/TabBar/drag-drop.js @@ -47,7 +47,17 @@ define(function (require, exports, module) { function cleanupDragState() { $(".tab").removeClass("dragging drag-target"); $(".empty-pane-drop-target").removeClass("empty-pane-drop-target"); - updateDragIndicator(null); + + // this is to make sure that the drag indicator is hidden and remove any inline styles + if (dragIndicator) { + dragIndicator.hide().css({ + top: '', + left: '', + height: '' + }); + } + + // Reset all drag state variables draggedTab = null; dragOverTab = null; dragSourcePane = null; @@ -58,6 +68,17 @@ define(function (require, exports, module) { } $("#tab-drag-extended-zone").remove(); + + // this is needed to make sure that all the drag-active styling are properly hidden + // it is required because noticed a bug where sometimes some styles remain when drop fails + $(".phoenix-tab-bar").removeClass("drag-active"); + + setTimeout(() => { + // a double check just to make sure that the drag indicator is still hidden + if (dragIndicator && dragIndicator.is(':visible')) { + dragIndicator.hide(); + } + }, 5); } /** @@ -83,6 +104,9 @@ define(function (require, exports, module) { // add initialization for empty panes initEmptyPaneDropTargets(); + + // Set up global drag cleanup handlers to ensure drag state is always cleaned up + setupGlobalDragCleanup(); } /** @@ -165,10 +189,6 @@ define(function (require, exports, module) { } }; - const removeOuterDropZone = () => { - $("#tab-drag-extended-zone").remove(); - }; - // When dragging over the container but not directly over a tab element $container.on("dragover", function (e) { if (e.preventDefault) { @@ -276,14 +296,10 @@ define(function (require, exports, module) { if (mouseX > containerRect.right) { targetTab = $tabs.last()[0]; onLeftSide = false; - } - // If beyond the left edge, use the first tab - else if (mouseX < containerRect.left) { + } else if (mouseX < containerRect.left) { // If beyond the left edge, use the first tab targetTab = $tabs.first()[0]; onLeftSide = true; - } - // If within bounds, find the closest tab - else { + } else { // If within bounds, find the closest tab onLeftSide = mouseX < containerRect.left + containerRect.width / 2; targetTab = onLeftSide ? $tabs.first()[0] : $tabs.last()[0]; } @@ -384,6 +400,10 @@ define(function (require, exports, module) { * @param {Event} e - The event object */ function handleDragStart(e) { + if (draggedTab) { + cleanupDragState(); + } + // store reference to the dragged tab draggedTab = this; @@ -401,7 +421,10 @@ define(function (require, exports, module) { // Use a timeout to let the dragging class apply before taking measurements // This ensures visual updates are applied before we calculate positions setTimeout(() => { - updateDragIndicator(null); + // Ensure the drag indicator is properly hidden at the start + if (dragIndicator) { + dragIndicator.hide(); + } }, 0); } @@ -465,36 +488,43 @@ define(function (require, exports, module) { * @param {Event} e - The event object */ function handleDrop(e) { - if (e.stopPropagation) { - e.stopPropagation(); // Stops browser from redirecting - } + try { + if (e.stopPropagation) { + e.stopPropagation(); // Stops browser from redirecting + } + + // Only process the drop if the dragged tab is different from the drop target + if (draggedTab !== this) { + // Determine which pane the drop target belongs to + const isSecondPane = $(this).closest("#phoenix-tab-bar-2").length > 0; + const targetPaneId = isSecondPane ? "second-pane" : "first-pane"; + const draggedPath = $(draggedTab).attr("data-path"); + const targetPath = $(this).attr("data-path"); - // Only process the drop if the dragged tab is different from the drop target - if (draggedTab !== this) { - // Determine which pane the drop target belongs to - const isSecondPane = $(this).closest("#phoenix-tab-bar-2").length > 0; - const targetPaneId = isSecondPane ? "second-pane" : "first-pane"; - const draggedPath = $(draggedTab).attr("data-path"); - const targetPath = $(this).attr("data-path"); + // Determine if we're dropping to the left or right of the target + const targetRect = this.getBoundingClientRect(); + const mouseX = e.originalEvent.clientX; + const midPoint = targetRect.left + targetRect.width / 2; + const onLeftSide = mouseX < midPoint; - // Determine if we're dropping to the left or right of the target - const targetRect = this.getBoundingClientRect(); - const mouseX = e.originalEvent.clientX; - const midPoint = targetRect.left + targetRect.width / 2; - const onLeftSide = mouseX < midPoint; - - // Check if dragging between different panes - if (dragSourcePane !== targetPaneId) { - // Move the tab between panes - moveTabBetweenPanes(dragSourcePane, targetPaneId, draggedPath, targetPath, onLeftSide); - } else { - // Move within the same pane - moveWorkingSetItem(targetPaneId, draggedPath, targetPath, onLeftSide); + // Check if dragging between different panes + if (dragSourcePane !== targetPaneId) { + // Move the tab between panes + moveTabBetweenPanes(dragSourcePane, targetPaneId, draggedPath, targetPath, onLeftSide); + } else { + // Move within the same pane + moveWorkingSetItem(targetPaneId, draggedPath, targetPath, onLeftSide); + } } - } - cleanupDragState(); - return false; + cleanupDragState(); + return false; + } catch (error) { + console.error("Error during tab drop operation:", error); + // Ensure cleanup happens even if there's an error + cleanupDragState(); + return false; + } } /** @@ -504,7 +534,50 @@ define(function (require, exports, module) { * @param {Event} e - The event object */ function handleDragEnd(e) { - cleanupDragState(); + setTimeout(() => { + cleanupDragState(); + }, 10); + } + + /** + * Global document event listeners to ensure drag state is always cleaned up + * This handles cases where drag operations fail or are cancelled outside + * the normal tab bar drop zones + */ + function setupGlobalDragCleanup() { + // Listen for drags ending anywhere on the document + $(document).on('dragend', function(e) { + // Only clean up if we were tracking a drag operation + if (draggedTab) { + setTimeout(() => { + cleanupDragState(); + }, 10); + } + }); + + // Listen for global mouse up events to catch cancelled drags + $(document).on('mouseup', function(e) { + // If we have an active drag but mouse is released, clean up + if (draggedTab && !e.originalEvent.dataTransfer) { + setTimeout(() => { + cleanupDragState(); + }, 10); + } + }); + + // Listen for ESC key to cancel drag operations + $(document).on('keydown', function(e) { + if (e.key === 'Escape' && draggedTab) { + cleanupDragState(); + } + }); + + // Listen for page visibility changes (like alt-tab) to clean up + $(document).on('visibilitychange', function() { + if (document.hidden && draggedTab) { + cleanupDragState(); + } + }); } /** @@ -599,51 +672,81 @@ define(function (require, exports, module) { * @param {Boolean} beforeTarget - Whether to place before or after the target */ function moveTabBetweenPanes(sourcePaneId, targetPaneId, draggedPath, targetPath, beforeTarget) { - const sourceWorkingSet = MainViewManager.getWorkingSet(sourcePaneId); - const targetWorkingSet = MainViewManager.getWorkingSet(targetPaneId); - - let draggedIndex = -1; - let targetIndex = -1; - let draggedFile = null; - - // Find the dragged file and its index in the source pane - for (let i = 0; i < sourceWorkingSet.length; i++) { - if (sourceWorkingSet[i].fullPath === draggedPath) { - draggedIndex = i; - draggedFile = sourceWorkingSet[i]; - break; + try { + const sourceWorkingSet = MainViewManager.getWorkingSet(sourcePaneId); + const targetWorkingSet = MainViewManager.getWorkingSet(targetPaneId); + + let draggedIndex = -1; + let targetIndex = -1; + let draggedFile = null; + + // Find the dragged file and its index in the source pane + for (let i = 0; i < sourceWorkingSet.length; i++) { + if (sourceWorkingSet[i].fullPath === draggedPath) { + draggedIndex = i; + draggedFile = sourceWorkingSet[i]; + break; + } } - } - // Find the target index in the target pane - for (let i = 0; i < targetWorkingSet.length; i++) { - if (targetWorkingSet[i].fullPath === targetPath) { - targetIndex = i; - break; + // Find the target index in the target pane + for (let i = 0; i < targetWorkingSet.length; i++) { + if (targetWorkingSet[i].fullPath === targetPath) { + targetIndex = i; + break; + } } - } - // Only continue if we found the dragged file - if (draggedIndex !== -1 && draggedFile) { - // Remove the file from source pane - CommandManager.execute(Commands.FILE_CLOSE, { file: draggedFile, paneId: sourcePaneId }); + // Only continue if we found the dragged file + if (draggedIndex !== -1 && draggedFile) { + // Check if the dragged file is currently active in the source pane + const currentActiveFileInSource = MainViewManager.getCurrentlyViewedFile(sourcePaneId); + const isActiveFileBeingMoved = currentActiveFileInSource && + currentActiveFileInSource.fullPath === draggedPath; + + // If the active file is being moved and there are other files in the source pane, + // switch to another file first to prevent placeholder creation + if (isActiveFileBeingMoved && sourceWorkingSet.length > 1) { + // Find another file to make active (prefer the next file, or previous if this is the last) + let newActiveIndex = draggedIndex + 1; + if (newActiveIndex >= sourceWorkingSet.length) { + newActiveIndex = draggedIndex - 1; + } - // Calculate where to add it in the target pane - let targetInsertIndex; + if (newActiveIndex >= 0 && newActiveIndex < sourceWorkingSet.length) { + const newActiveFile = sourceWorkingSet[newActiveIndex]; + // Open the new active file in the source pane before removing the dragged file + CommandManager.execute(Commands.FILE_OPEN, { + fullPath: newActiveFile.fullPath, + paneId: sourcePaneId + }); + } + } - if (targetIndex !== -1) { - // We have a specific target index to aim for - targetInsertIndex = beforeTarget ? targetIndex : targetIndex + 1; - } else { - // No specific target, add to end of the working set - targetInsertIndex = targetWorkingSet.length; - } + // Remove the file from source pane + CommandManager.execute(Commands.FILE_CLOSE, { file: draggedFile, paneId: sourcePaneId }); - // Add to the target pane at the calculated position - MainViewManager.addToWorkingSet(targetPaneId, draggedFile, targetInsertIndex); + // Calculate where to add it in the target pane + let targetInsertIndex; - // we always need to make the dragged tab active in the target pane when moving between panes - CommandManager.execute(Commands.FILE_OPEN, { fullPath: draggedPath, paneId: targetPaneId }); + if (targetIndex !== -1) { + // We have a specific target index to aim for + targetInsertIndex = beforeTarget ? targetIndex : targetIndex + 1; + } else { + // No specific target, add to end of the working set + targetInsertIndex = targetWorkingSet.length; + } + + // Add to the target pane at the calculated position + MainViewManager.addToWorkingSet(targetPaneId, draggedFile, targetInsertIndex); + + // we always need to make the dragged tab active in the target pane when moving between panes + CommandManager.execute(Commands.FILE_OPEN, { fullPath: draggedPath, paneId: targetPaneId }); + } + } catch (error) { + console.error("Error during cross-pane tab move:", error); + // Even if there's an error, ensure the drag state is cleaned up + cleanupDragState(); } } @@ -726,6 +829,40 @@ define(function (require, exports, module) { if (draggedFile) { if (sourcePaneId !== paneId) { + // Check if the dragged file is currently active in the source pane + const currentActiveFileInSource = MainViewManager.getCurrentlyViewedFile(sourcePaneId); + const isActiveFileBeingMoved = currentActiveFileInSource && + currentActiveFileInSource.fullPath === draggedPath; + + // If the active file is being moved and there are other files in the source pane, + // switch to another file first to prevent placeholder creation + if (isActiveFileBeingMoved && sourceWorkingSet.length > 1) { + // Find another file to make active + let draggedIndex = -1; + for (let i = 0; i < sourceWorkingSet.length; i++) { + if (sourceWorkingSet[i].fullPath === draggedPath) { + draggedIndex = i; + break; + } + } + + if (draggedIndex !== -1) { + let newActiveIndex = draggedIndex + 1; + if (newActiveIndex >= sourceWorkingSet.length) { + newActiveIndex = draggedIndex - 1; + } + + if (newActiveIndex >= 0 && newActiveIndex < sourceWorkingSet.length) { + const newActiveFile = sourceWorkingSet[newActiveIndex]; + // Open the new active file in the source pane before removing the dragged file + CommandManager.execute(Commands.FILE_OPEN, { + fullPath: newActiveFile.fullPath, + paneId: sourcePaneId + }); + } + } + } + // If different panes, close in source pane CommandManager.execute(Commands.FILE_CLOSE, { file: draggedFile, paneId: sourcePaneId }); From c6e7bf75a182cba247a28e233eb9f584d085751b Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 23 May 2025 17:18:23 +0530 Subject: [PATCH 6/6] refactor: aligned the icons in the tab --- src/styles/Extn-TabBar.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/Extn-TabBar.less b/src/styles/Extn-TabBar.less index 71ae2fb27c..3f3ba370df 100644 --- a/src/styles/Extn-TabBar.less +++ b/src/styles/Extn-TabBar.less @@ -138,7 +138,7 @@ opacity: 0.7; font-weight: normal; position: relative; - top: 0.1rem; + top: 0.05rem; } .tab.active { @@ -200,7 +200,7 @@ font-size: 1.5rem; position: absolute; left: 0.4rem; - top: 0.33rem; + top: 0.28rem; } .tab.dirty::before {