diff --git a/src/project/FileTreeView.js b/src/project/FileTreeView.js
index 6e366f726..11992ff87 100644
--- a/src/project/FileTreeView.js
+++ b/src/project/FileTreeView.js
@@ -1213,8 +1213,14 @@ define(function (require, exports, module) {
},
handleDrop: function(e) {
- var data = JSON.parse(e.dataTransfer.getData("text"));
- this.props.actions.moveItem(data.path, this.props.parentPath);
+ try {
+ var data = JSON.parse(e.dataTransfer.getData("text"));
+ if (data && data.path) {
+ this.props.actions.moveItem(data.path, this.props.parentPath);
+ }
+ } catch (err) {
+ console.error("FileTreeView: drop handler error:", err);
+ }
e.stopPropagation();
},
diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js
index 87757e15a..b78a2d83c 100644
--- a/src/project/ProjectManager.js
+++ b/src/project/ProjectManager.js
@@ -2096,8 +2096,14 @@ define(function (require, exports, module) {
// Add support for moving items to root directory
$projectTreeContainer.on("drop", function(e) {
- var data = JSON.parse(e.originalEvent.dataTransfer.getData("text"));
- actionCreator.moveItem(data.path, getProjectRoot().fullPath);
+ try {
+ var data = JSON.parse(e.originalEvent.dataTransfer.getData("text"));
+ if (data && data.path) {
+ actionCreator.moveItem(data.path, getProjectRoot().fullPath);
+ }
+ } catch (err) {
+ console.error("ProjectManager: drop handler error:", err);
+ }
e.stopPropagation();
});
diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less
index afe16fabf..c9dc05160 100644
--- a/src/styles/Extn-BottomPanelTabs.less
+++ b/src/styles/Extn-BottomPanelTabs.less
@@ -180,6 +180,35 @@ img.panel-titlebar-icon {
pointer-events: none;
}
+/* Overflow button: shown when tabs overflow even after collapsing to icons */
+.bottom-panel-overflow-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.9rem;
+ height: 2rem;
+ cursor: pointer;
+ color: #666;
+ font-size: 0.9rem;
+ flex: 0 0 auto;
+ transition: color 0.12s ease, background-color 0.12s ease;
+
+ .dark & {
+ color: #aaa;
+ }
+
+ &:hover {
+ background-color: #e0e0e0;
+ color: #333;
+
+ .dark & {
+ background-color: #333;
+ color: #eee;
+ }
+ }
+
+}
+
/* Drag-and-drop tab reordering */
.bottom-panel-tab-dragging {
opacity: 0.5;
@@ -280,6 +309,7 @@ img.panel-titlebar-icon {
color: #eee;
}
}
+
}
.bottom-panel-tab-bar-actions {
diff --git a/src/view/PanelView.js b/src/view/PanelView.js
index 3b3134454..aeedd540e 100644
--- a/src/view/PanelView.js
+++ b/src/view/PanelView.js
@@ -28,6 +28,7 @@ define(function (require, exports, module) {
const EventDispatcher = require("utils/EventDispatcher"),
PreferencesManager = require("preferences/PreferencesManager"),
Resizer = require("utils/Resizer"),
+ DropdownButton = require("widgets/DropdownButton"),
Strings = require("strings");
/**
@@ -185,11 +186,11 @@ define(function (require, exports, module) {
_$tabsOverflow.append(_buildTab(panel, panelId === _activeId));
});
- // Re-append the "+" button at the end (after all tabs)
+ // Re-append the Tools button at the end
if (_$addBtn) {
_$tabsOverflow.append(_$addBtn);
- _updateAddButtonVisibility();
}
+ _updateAddButtonVisibility();
_checkTabOverflow();
}
@@ -226,8 +227,7 @@ define(function (require, exports, module) {
return;
}
let $tab = _buildTab(panel, panelId === _activeId);
-
- // Insert before the "+" button so it stays at the end
+ // Insert before the Tools button so it stays at the end
if (_$addBtn && _$addBtn.parent().length) {
_$addBtn.before($tab);
} else {
@@ -295,7 +295,7 @@ define(function (require, exports, module) {
_$tabBar.on("dragstart", ".bottom-panel-tab", function (e) {
draggedTab = this;
e.originalEvent.dataTransfer.effectAllowed = "move";
- e.originalEvent.dataTransfer.setData("text/plain", "panel-tab");
+ e.originalEvent.dataTransfer.setData("application/x-phoenix-panel-tab", "1");
$(this).addClass("bottom-panel-tab-dragging");
});
@@ -354,6 +354,9 @@ define(function (require, exports, module) {
* Only collapses tabs that have an icon available.
* @private
*/
+ /** @type {jQueryObject} Overflow dropdown button */
+ let _$overflowBtn = null;
+
function _checkTabOverflow() {
if (!_$tabBar) {
return;
@@ -362,6 +365,16 @@ define(function (require, exports, module) {
_$tabBar.removeClass("bottom-panel-tabs-collapsed");
const isOverflowing = _$tabsOverflow[0].scrollWidth > _$tabsOverflow[0].clientWidth;
_$tabBar.toggleClass("bottom-panel-tabs-collapsed", isOverflowing);
+
+ // Check if still overflowing after collapse
+ const stillOverflowing = isOverflowing &&
+ _$tabsOverflow[0].scrollWidth > _$tabsOverflow[0].clientWidth;
+
+ // Show/hide overflow button
+ if (_$overflowBtn) {
+ _$overflowBtn.toggle(stillOverflowing);
+ }
+
// Show tooltip on hover only in collapsed mode (title text is hidden)
_$tabBar.find(".bottom-panel-tab").each(function () {
const $tab = $(this);
@@ -373,6 +386,105 @@ define(function (require, exports, module) {
});
}
+ /**
+ * Get the list of hidden (not fully visible) panel tabs.
+ * @return {Array<{panelId: string, title: string}>}
+ * @private
+ */
+ function _getHiddenTabs() {
+ const hidden = [];
+ const barRect = _$tabsOverflow[0].getBoundingClientRect();
+ _$tabsOverflow.find(".bottom-panel-tab").each(function () {
+ const tabRect = this.getBoundingClientRect();
+ const isVisible = tabRect.left >= barRect.left &&
+ tabRect.right <= (barRect.right + 2);
+ if (!isVisible) {
+ const $tab = $(this);
+ hidden.push({
+ panelId: $tab.data("panel-id"),
+ title: $tab.find(".bottom-panel-tab-title").text()
+ });
+ }
+ });
+ return hidden;
+ }
+
+ /** @type {DropdownButton.DropdownButton} */
+ let _overflowDropdown = null;
+
+ /**
+ * Show a dropdown menu listing hidden panel tabs.
+ * Uses the same DropdownButton widget as the file tab bar overflow.
+ * @private
+ */
+ function _showOverflowMenu() {
+ // If dropdown is already open, close it (toggle behavior)
+ if (_overflowDropdown) {
+ _overflowDropdown.closeDropdown();
+ _overflowDropdown = null;
+ return;
+ }
+
+ const hidden = _getHiddenTabs();
+ if (!hidden.length) {
+ return;
+ }
+
+ _overflowDropdown = new DropdownButton.DropdownButton("", hidden, function (item) {
+ const panel = _panelMap[item.panelId];
+ let iconHtml = "";
+ if (panel && panel._options) {
+ if (panel._options.iconClass) {
+ iconHtml = '';
+ } else if (panel._options.iconSvg) {
+ iconHtml = '';
+ }
+ }
+ const activeClass = item.panelId === _activeId ? ' style="font-weight:600"' : '';
+ return {
+ html: '