diff --git a/docs/API-Reference/view/PanelView.md b/docs/API-Reference/view/PanelView.md
index 7a4455bcc..544969a66 100644
--- a/docs/API-Reference/view/PanelView.md
+++ b/docs/API-Reference/view/PanelView.md
@@ -68,12 +68,6 @@ recomputeLayout callback from WorkspaceManager
## \_defaultPanelId : string \| null
The default/quick-access panel ID
-**Kind**: global variable
-
-
-## \_$addBtn : jQueryObject
-The "+" button inside the tab overflow area
-
**Kind**: global variable
diff --git a/docs/API-Reference/view/WorkspaceManager.md b/docs/API-Reference/view/WorkspaceManager.md
index 98a49cc6f..bb0c11ed1 100644
--- a/docs/API-Reference/view/WorkspaceManager.md
+++ b/docs/API-Reference/view/WorkspaceManager.md
@@ -190,7 +190,8 @@ Returns true if visible else false.
### view/WorkspaceManager.setPluginPanelWidth(width)
Programmatically sets the plugin panel content width to the given value in pixels.
The total toolbar width is adjusted to account for the plugin icons bar.
-Width is clamped to respect panel minWidth and max size (75% of window).
+If the requested width doesn't fit, the sidebar is progressively shrunk
+(and collapsed if necessary) before clamping.
No-op if no panel is currently visible.
**Kind**: inner method of [view/WorkspaceManager](#module_view/WorkspaceManager)
diff --git a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css
index ecb403319..8c72ac0fb 100644
--- a/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css
+++ b/src/extensionsIntegrated/Phoenix-live-preview/live-preview.css
@@ -5,7 +5,7 @@
.live-preview-browser-btn {
opacity: 0;
visibility: hidden;
- transition: opacity 1s, visibility 0s linear 1s; /* Fade-out effect */
+ transition: opacity 1s, visibility 0s linear 1s;
}
#live-preview-plugin-toolbar {
@@ -27,7 +27,7 @@
#panel-live-preview-frame,
#panel-md-preview-frame {
- background-color: transparent;
+ background-color: white;
position: relative;
}
@@ -50,6 +50,127 @@
height: calc(100% - var(--toolbar-height));
}
+.frame-container.responsive-viewport {
+ position: relative;
+ justify-content: center;
+ align-items: stretch;
+ background: #1a1a1e;
+}
+
+.frame-container.responsive-viewport > div:first-child:not(.responsive-handle) {
+ display: none;
+}
+
+.responsive-handle {
+ flex-shrink: 0;
+ z-index: 5;
+ background: transparent;
+ transition: background 0.15s;
+}
+
+.responsive-handle:hover,
+.responsive-handle.dragging {
+ background: rgba(255, 255, 255, 0.08);
+}
+
+.responsive-handle-horizontal {
+ width: 10px;
+ cursor: ew-resize;
+ position: relative;
+}
+
+.responsive-handle-horizontal::before,
+.responsive-handle-horizontal::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 1px;
+ height: 16px;
+ border-radius: 0.5px;
+ background: rgba(255, 255, 255, 0.3);
+}
+
+.responsive-handle-horizontal::before {
+ transform: translate(calc(-50% - 2px), -50%);
+}
+
+.responsive-handle-horizontal::after {
+ transform: translate(calc(-50% + 2px), -50%);
+}
+
+.responsive-handle-horizontal:hover::before,
+.responsive-handle-horizontal:hover::after,
+.responsive-handle-horizontal.dragging::before,
+.responsive-handle-horizontal.dragging::after {
+ background: rgba(255, 255, 255, 0.7);
+}
+
+.responsive-handle-left {
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+
+.responsive-handle-left:hover,
+.responsive-handle-left.dragging {
+ opacity: 1;
+}
+
+.responsive-handle-vertical {
+ position: absolute;
+ height: 10px;
+ cursor: ns-resize;
+ box-sizing: border-box;
+}
+
+.responsive-handle-vertical::before,
+.responsive-handle-vertical::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 16px;
+ height: 1px;
+ border-radius: 0.5px;
+ background: rgba(255, 255, 255, 0.3);
+}
+
+.responsive-handle-vertical::before {
+ transform: translate(-50%, calc(-50% - 2px));
+}
+
+.responsive-handle-vertical::after {
+ transform: translate(-50%, calc(-50% + 2px));
+}
+
+.responsive-handle-vertical:hover::before,
+.responsive-handle-vertical:hover::after,
+.responsive-handle-vertical.dragging::before,
+.responsive-handle-vertical.dragging::after {
+ background: rgba(255, 255, 255, 0.7);
+}
+
+.responsive-dimension-label {
+ position: absolute;
+ transform: translateX(-50%);
+ z-index: 10;
+ display: none;
+ padding: 6px 16px;
+ border-radius: 6px;
+ background: rgba(0, 0, 0, 0.88);
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ color: #fff;
+ font-size: 15px;
+ font-weight: 600;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
+ white-space: nowrap;
+ pointer-events: none;
+ letter-spacing: 0.5px;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.06);
+}
+
.plugin-toolbar {
height: var(--toolbar-height);
color: #a0a0a0;
@@ -61,10 +182,30 @@
.toolbar-button {
background-color: transparent;
- width:28px;
+ width: 28px;
height: 22px;
}
+#live-preview-plugin-toolbar .btn-alt-quiet:hover,
+#live-preview-plugin-toolbar .btn-alt-quiet:focus,
+#live-preview-plugin-toolbar .btn-alt-quiet:active {
+ border-color: rgba(255, 255, 255, 0.1) !important;
+ box-shadow: none !important;
+}
+
+#live-preview-plugin-toolbar .lp-device-size-icon:hover,
+#live-preview-plugin-toolbar .lp-device-size-icon:focus,
+#live-preview-plugin-toolbar .lp-device-size-icon:active {
+ border: none !important;
+}
+
+#live-preview-plugin-toolbar .lp-device-size-dropdown-chevron:hover,
+#live-preview-plugin-toolbar .lp-device-size-dropdown-chevron:focus,
+#live-preview-plugin-toolbar .lp-device-size-dropdown-chevron:active {
+ border: none !important;
+ border-left: 1px solid rgba(255, 255, 255, 0.1) !important;
+}
+
.open-icon {
background: url("./images/sprites.svg#open-icon") no-repeat 72.5%;
width: 30px;
@@ -101,7 +242,7 @@
opacity: 0;
color: #a0a0a0;
visibility: hidden;
- transition: opacity 1s, visibility 0s linear 1s; /* Fade-out effect */
+ transition: opacity 1s, visibility 0s linear 1s;
width: 30px;
height: 22px;
padding: 1px 6px;
@@ -109,37 +250,92 @@
margin-top: 0;
}
-.lp-device-size-icon {
- min-width: fit-content;
+.lp-fit-to-panel-btn {
+ color: #a0a0a0;
+ flex-shrink: 0;
+ margin: 0 2px;
+ background: transparent;
+ border: 1px solid transparent;
+ box-shadow: none;
+ font-size: 12px;
+}
+
+.lp-fit-to-panel-btn:hover,
+.lp-fit-to-panel-btn:focus,
+.lp-fit-to-panel-btn:active {
+ background: transparent !important;
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
+ box-shadow: none !important;
+}
+
+.lp-device-size-btn-group {
display: flex;
align-items: center;
+ flex-shrink: 0;
margin: 0 4px 0 3px;
- cursor: pointer;
- background: transparent;
- box-shadow: none;
border: 1px solid transparent;
+ border-radius: 3px;
box-sizing: border-box;
+}
+
+.lp-device-size-btn-group:hover {
+ border-color: rgba(255, 255, 255, 0.1);
+}
+
+.lp-device-size-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ cursor: pointer;
+ background: transparent;
+ box-shadow: none !important;
+ border: none;
color: #a0a0a0;
- padding: 0 0.35em;
+ padding: 0;
+ margin: 0;
}
.lp-device-size-icon:hover,
.lp-device-size-icon:focus,
.lp-device-size-icon:active {
background: transparent !important;
- border: 1px solid rgba(255, 255, 255, 0.1) !important;
+ border: none !important;
box-shadow: none !important;
}
-#deviceSizeBtn.btn-dropdown::after {
- position: static;
- margin-left: 5px;
+.lp-device-size-dropdown-chevron {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ background: transparent;
+ box-shadow: none !important;
+ border: none;
+ border-left: 1px solid transparent;
+ border-radius: 0 3px 3px 0;
+ color: #a0a0a0;
+ padding: 0 4px;
+ margin: 0;
+ height: 22px;
+ font-size: 10px;
+}
+
+.lp-device-size-btn-group:hover .lp-device-size-dropdown-chevron {
+ border-left-color: rgba(255, 255, 255, 0.1);
+}
+
+.lp-device-size-dropdown-chevron:hover,
+.lp-device-size-dropdown-chevron:focus,
+.lp-device-size-dropdown-chevron:active {
+ background: transparent !important;
+ border: none !important;
+ border-left: 1px solid rgba(255, 255, 255, 0.1) !important;
+ box-shadow: none !important;
}
.device-size-item-icon {
margin-right: 6px;
- width: 12px;
- text-align: center;
font-size: inherit;
}
@@ -152,17 +348,12 @@
.device-size-item-width {
margin-left: 10px;
- opacity: 0.5;
-}
-
-.device-size-item-disabled {
- opacity: 0.35;
+ color: #9a9a9a;
+ font-size: 12.5px;
}
.device-size-item-breakpoint-icon {
margin-right: 6px;
- width: 12px;
- text-align: center;
font-size: inherit;
color: rgba(100, 180, 255, 0.8);
}
@@ -259,7 +450,6 @@
color: #fff;
}
-/* Persistent cursor-sync highlight on CM line corresponding to md viewer cursor */
.cm-cursor-sync-highlight {
background-color: rgba(100, 150, 255, 0.18) !important;
}
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js
index 0bc4d69f2..5e797fc57 100644
--- a/src/nls/root/strings.js
+++ b/src/nls/root/strings.js
@@ -523,7 +523,11 @@ define({
"LIVE_DEV_IMAGE_FOLDER_DIALOG_HELP": "💡 Type folder path or leave empty to download in 'images' folder.",
"LIVE_DEV_IMAGE_FOLDER_DIALOG_REMEMBER": "Don't ask again for this project",
"AVAILABLE_IN_PRO_TITLE": "Available in Phoenix Pro",
- "DEVICE_SIZE_LIMIT_MESSAGE": "Phoenix Pro lets you preview your page at the screen sizes defined in your CSS.",
+ "DEVICE_SIZE_LIMIT_MESSAGE": "See how your page looks on phones, tablets, desktops, and your CSS breakpoints. Upgrade to Phoenix Pro.",
+ "DEVICE_SIZE_NOT_ENOUGH_SPACE": "Not enough space for this screen size - try zooming out",
+ "DEVICE_SIZE_CYCLE_TOOLTIP": "Switch between mobile, tablet, and desktop",
+ "DEVICE_SIZE_DROPDOWN_TOOLTIP": "All device sizes",
+ "DEVICE_SIZE_FIT_TO_PANEL": "Fit to panel",
"MD_EDIT_UPSELL_MESSAGE": "Write Markdown like a document. {APP_NAME} handles the formatting so you can stay focused on writing.",
"IMAGE_UPLOADING": "Uploading",
"IMAGE_UPLOAD_FAILED": "Failed to upload image",
@@ -1557,7 +1561,7 @@ define({
"BOTTOM_PANEL_MINIMIZE": "Minimize Panel",
"BOTTOM_PANEL_SHOW": "Show Bottom Panel",
"BOTTOM_PANEL_HIDE_TOGGLE": "Hide Bottom Panel",
- "BOTTOM_PANEL_DEFAULT_TITLE": "Tools",
+ "BOTTOM_PANEL_DEFAULT_TITLE": "Quick Access - Tools",
"BOTTOM_PANEL_DEFAULT_HEADING": "Open a Panel",
"BOTTOM_PANEL_OPEN_PANEL": "Open a Panel",
"BOTTOM_PANEL_MAXIMIZE": "Maximize Panel",
diff --git a/src/styles/Extn-BottomPanelTabs.less b/src/styles/Extn-BottomPanelTabs.less
index f81bd6988..18fb29ec9 100644
--- a/src/styles/Extn-BottomPanelTabs.less
+++ b/src/styles/Extn-BottomPanelTabs.less
@@ -166,11 +166,12 @@
display: none;
}
-/* Default panel (Tools) tab: always show the icon alongside the title,
- in both expanded and collapsed modes, active or inactive. */
+/* Pinned Quick Access tab: icon-only, always visible */
.bottom-panel-tab.bottom-panel-tab-default .bottom-panel-tab-icon {
display: inline-flex;
- margin-right: 0.4rem;
+ width: 1.1rem;
+ height: 1.1rem;
+ margin: 0;
}
.default-panel-btn .panel-titlebar-icon {
@@ -237,8 +238,7 @@
/* Fix all collapsed tabs to the same width so the UI doesn't shake
when switching active tab. The width matches the active tab's
natural content (icon + close button) using fixed px since the
- icon has a fixed size. The Tools button (.bottom-panel-add-btn)
- is a separate element and keeps its natural "Tools" text width. */
+ icon has a fixed size. */
.bottom-panel-tab {
min-width: 63px;
box-sizing: border-box;
@@ -247,14 +247,9 @@
.bottom-panel-tab:not(.active) .bottom-panel-tab-close-btn {
display: none;
}
- /* Default panel (Tools) tab: keep natural width and show its title
- even in collapsed mode (other tabs collapse to icon-only). */
+ /* Pinned Quick Access tab: icon-only, keeps its natural width */
.bottom-panel-tab.bottom-panel-tab-default {
min-width: auto;
- justify-content: flex-start;
- }
- .bottom-panel-tab.bottom-panel-tab-default .bottom-panel-tab-title {
- display: inline;
}
}
@@ -304,42 +299,18 @@
}
}
-.bottom-panel-add-btn {
- display: flex;
- align-items: center;
+/* Pinned Quick Access tab: icon-only with separator */
+.bottom-panel-tab.bottom-panel-tab-default {
+ padding: 0 0.6rem;
+ min-width: auto;
justify-content: center;
- padding: 0 10px;
- height: 2rem;
- min-width: 70px;
- line-height: 2rem;
- cursor: pointer;
- color: #888;
- font-size: 0.82rem;
- flex: 0 0 auto;
- white-space: nowrap;
- user-select: none;
- -webkit-user-drag: none;
- transition: color 0.12s ease, background-color 0.12s ease;
-
- img {
- -webkit-user-drag: none;
- pointer-events: none;
- }
+ border-right: 1px solid rgba(0, 0, 0, 0.15);
+ background-color: transparent;
.dark & {
- color: #777;
+ border-right: 1px solid rgba(255, 255, 255, 0.1);
+ background-color: transparent;
}
-
- &:hover {
- background-color: #e0e0e0;
- color: #333;
-
- .dark & {
- background-color: #333;
- color: #eee;
- }
- }
-
}
.bottom-panel-tab-bar-actions {
diff --git a/src/view/DefaultPanelView.js b/src/view/DefaultPanelView.js
index 07fc9d91b..da4acd794 100644
--- a/src/view/DefaultPanelView.js
+++ b/src/view/DefaultPanelView.js
@@ -198,11 +198,9 @@ define(function (require, exports, module) {
}
});
- // Auto-hide when any other panel is shown; update drawer button state.
+ // Update drawer button state when panels are shown.
PanelView.on(PanelView.EVENT_PANEL_SHOWN, function (event, panelID) {
- if (panelID !== WorkspaceManager.DEFAULT_PANEL_ID) {
- _panel.hide();
- } else {
+ if (panelID === WorkspaceManager.DEFAULT_PANEL_ID) {
_updateButtonVisibility();
}
$drawerBtn.toggleClass("selected-button", panelID === WorkspaceManager.DEFAULT_PANEL_ID);
diff --git a/src/view/PanelView.js b/src/view/PanelView.js
index 0c6f8e0dd..6e9dcf800 100644
--- a/src/view/PanelView.js
+++ b/src/view/PanelView.js
@@ -108,9 +108,6 @@ define(function (require, exports, module) {
/** @type {string|null} The default/quick-access panel ID */
let _defaultPanelId = null;
- /** @type {jQueryObject} The "+" button inside the tab overflow area */
- let _$addBtn = null;
-
// --- Tab helper functions ---
/**
@@ -149,7 +146,7 @@ define(function (require, exports, module) {
*/
function _buildTab(panel, isActive) {
let title = panel._tabTitle || _getPanelTitle(panel.panelID, panel.$panel);
- // Default panel (Tools) tab is not draggable — it's a fixed slot, not a user tab
+ // Default panel (Quick Access) tab is pinned — not draggable, not closable
const isDefault = panel.panelID === _defaultPanelId;
let $tab = $('
')
.toggleClass('bottom-panel-tab-default', isDefault)
@@ -162,8 +159,13 @@ define(function (require, exports, module) {
$icon[0].style.maskImage = maskUrl;
$icon[0].style.webkitMaskImage = maskUrl;
$tab.append($icon);
- $tab.append($('').text(title));
- $tab.append($('×').attr('title', Strings.CLOSE));
+ if (isDefault) {
+ // Icon-only tab with tooltip
+ $tab.attr('title', title);
+ } else {
+ $tab.append($('').text(title));
+ $tab.append($('×').attr('title', Strings.CLOSE));
+ }
return $tab;
}
@@ -171,10 +173,6 @@ define(function (require, exports, module) {
if (!_$tabsOverflow) {
return;
}
- // Detach the add button before emptying to preserve its event handlers
- if (_$addBtn) {
- _$addBtn.detach();
- }
_$tabsOverflow.empty();
_openIds.forEach(function (panelId) {
@@ -185,11 +183,6 @@ define(function (require, exports, module) {
_$tabsOverflow.append(_buildTab(panel, panelId === _activeId));
});
- // Re-append the Tools button at the end
- if (_$addBtn) {
- _$tabsOverflow.append(_$addBtn);
- }
- _updateAddButtonVisibility();
_checkTabOverflow();
}
@@ -226,13 +219,7 @@ define(function (require, exports, module) {
return;
}
let $tab = _buildTab(panel, panelId === _activeId);
- // Insert before the Tools button so it stays at the end
- if (_$addBtn && _$addBtn.parent().length) {
- _$addBtn.before($tab);
- } else {
- _$tabsOverflow.append($tab);
- }
- _updateAddButtonVisibility();
+ _$tabsOverflow.append($tab);
_checkTabOverflow();
}
@@ -247,7 +234,6 @@ define(function (require, exports, module) {
return;
}
_$tabsOverflow.find('.bottom-panel-tab[data-panel-id="' + panelId + '"]').remove();
- _updateAddButtonVisibility();
_checkTabOverflow();
}
@@ -291,8 +277,55 @@ define(function (require, exports, module) {
_$tabBar.find(".bottom-panel-tab").removeClass("drag-target");
}
+ // Find the closest non-default, non-dragged tab to mouseX
+ function findNearestDropTarget(mouseX) {
+ let closest = null;
+ let closestDist = Infinity;
+ _$tabsOverflow.find(".bottom-panel-tab").each(function () {
+ if (this === draggedTab) {
+ return;
+ }
+ if ($(this).data("panel-id") === _defaultPanelId) {
+ return;
+ }
+ const rect = this.getBoundingClientRect();
+ const dist = Math.min(Math.abs(mouseX - rect.left), Math.abs(mouseX - rect.right));
+ if (dist < closestDist) {
+ closestDist = dist;
+ closest = this;
+ }
+ });
+ return closest;
+ }
+
+ // Perform the reorder in _openIds and rebuild the tab bar
+ function reorderTabs(targetEl, mouseX) {
+ if (!draggedTab || targetEl === draggedTab) {
+ return;
+ }
+ let draggedId = $(draggedTab).data("panel-id");
+ let targetId = $(targetEl).data("panel-id");
+ let fromIdx = _openIds.indexOf(draggedId);
+ let toIdx = _openIds.indexOf(targetId);
+ if (fromIdx === -1 || toIdx === -1) {
+ return;
+ }
+ const insertBefore = getDropPosition(targetEl, mouseX);
+ _openIds.splice(fromIdx, 1);
+ let newIdx = _openIds.indexOf(targetId);
+ if (!insertBefore) {
+ newIdx++;
+ }
+ // Never place a tab before the pinned Quick Access tab at index 0
+ if (newIdx < 1 && _defaultPanelId && _openIds[0] === _defaultPanelId) {
+ newIdx = 1;
+ }
+ _openIds.splice(newIdx, 0, draggedId);
+ _updateBottomPanelTabBar();
+ _updateActiveTabHighlight();
+ }
+
_$tabBar.on("dragstart", ".bottom-panel-tab", function (e) {
- // Default panel (Tools) tab is never draggable
if ($(this).data("panel-id") === _defaultPanelId) {
e.preventDefault();
return;
@@ -311,7 +344,6 @@ define(function (require, exports, module) {
if (!draggedTab || this === draggedTab) {
return;
}
- // Don't allow dropping onto the default panel (Tools) tab
if ($(this).data("panel-id") === _defaultPanelId) {
return;
}
@@ -322,6 +354,30 @@ define(function (require, exports, module) {
updateIndicator(this, getDropPosition(this, e.originalEvent.clientX));
});
+ // Fallback dragover on the overflow container: handles gaps between
+ // tabs, empty space after the last tab, and the Quick Access tab area
+ // by snapping to the nearest valid drop target.
+ _$tabsOverflow.on("dragover", function (e) {
+ if (!draggedTab) {
+ return;
+ }
+ // Skip if already over a valid tab (the tab handler above covers it)
+ const $closestTab = $(e.target).closest(".bottom-panel-tab");
+ if ($closestTab.length && $closestTab[0] !== draggedTab
+ && $closestTab.data("panel-id") !== _defaultPanelId) {
+ return;
+ }
+ const nearest = findNearestDropTarget(e.originalEvent.clientX);
+ if (!nearest) {
+ return;
+ }
+ e.preventDefault();
+ e.originalEvent.dataTransfer.dropEffect = "move";
+ _$tabBar.find(".bottom-panel-tab").removeClass("drag-target");
+ $(nearest).addClass("drag-target");
+ updateIndicator(nearest, getDropPosition(nearest, e.originalEvent.clientX));
+ });
+
_$tabBar.on("dragleave", ".bottom-panel-tab", function (e) {
const related = e.originalEvent.relatedTarget;
if (!$(this).is(related) && !$(this).has(related).length) {
@@ -336,24 +392,23 @@ define(function (require, exports, module) {
cleanup();
return;
}
- let draggedId = $(draggedTab).data("panel-id");
- let targetId = $(this).data("panel-id");
- let fromIdx = _openIds.indexOf(draggedId);
- let toIdx = _openIds.indexOf(targetId);
- if (fromIdx === -1 || toIdx === -1) {
+ reorderTabs(this, e.originalEvent.clientX);
+ cleanup();
+ });
+
+ // Fallback drop on the overflow container
+ _$tabsOverflow.on("drop", function (e) {
+ if (!draggedTab) {
cleanup();
return;
}
- const insertBefore = getDropPosition(this, e.originalEvent.clientX);
- _openIds.splice(fromIdx, 1);
- let newIdx = _openIds.indexOf(targetId);
- if (!insertBefore) {
- newIdx++;
+ const nearest = findNearestDropTarget(e.originalEvent.clientX);
+ if (nearest) {
+ e.preventDefault();
+ e.stopPropagation();
+ reorderTabs(nearest, e.originalEvent.clientX);
}
- _openIds.splice(newIdx, 0, draggedId);
cleanup();
- _updateBottomPanelTabBar();
- _updateActiveTabHighlight();
});
}
@@ -398,9 +453,13 @@ define(function (require, exports, module) {
_$overflowBtn.toggle(stillOverflowing);
}
- // Show tooltip on hover only in collapsed mode (title text is hidden)
+ // Show tooltip on hover only in collapsed mode (title text is hidden).
+ // The pinned Quick Access tab always keeps its tooltip (icon-only).
_$tabBar.find(".bottom-panel-tab").each(function () {
const $tab = $(this);
+ if ($tab.data("panel-id") === _defaultPanelId) {
+ return;
+ }
if (isOverflowing) {
$tab.attr("title", $tab.find(".bottom-panel-tab-title").text());
} else {
@@ -503,23 +562,6 @@ define(function (require, exports, module) {
});
}
- /**
- * Show or hide the "+" button based on whether the default panel is active.
- * The button is hidden when the default panel is the active tab (since
- * clicking "+" would be a no-op) and shown otherwise.
- * @private
- */
- function _updateAddButtonVisibility() {
- if (!_$addBtn) {
- return;
- }
- if (_defaultPanelId && _activeId === _defaultPanelId) {
- _$addBtn.hide();
- } else {
- _$addBtn.show();
- }
- }
-
/**
* Switch the active tab to the given panel. Does not show/hide the container.
* @param {string} panelId
@@ -543,7 +585,6 @@ define(function (require, exports, module) {
newPanel.$panel.addClass("active-bottom-panel");
}
_updateActiveTabHighlight();
- _updateAddButtonVisibility();
}
@@ -567,6 +608,12 @@ define(function (require, exports, module) {
this._tabTitle = _getPanelTitle(id, $panel, title);
this._options = options || {};
_panelMap[id] = this;
+
+ // Quick Access panel is pinned: always at index 0, always in the tab bar
+ if (id === _defaultPanelId && _openIds.indexOf(id) === -1) {
+ _openIds.unshift(id);
+ _addTabToBar(id);
+ }
}
/**
@@ -666,8 +713,13 @@ define(function (require, exports, module) {
exports.trigger(EVENT_PANEL_SHOWN, panelId);
return;
}
- // Not open: add to open set
- _openIds.push(panelId);
+ // Not open: add to open set.
+ // Quick Access panel is always pinned at index 0.
+ if (panelId === _defaultPanelId) {
+ _openIds.unshift(panelId);
+ } else {
+ _openIds.push(panelId);
+ }
// Show container if it was hidden
if (!_$container.is(":visible")) {
@@ -684,9 +736,26 @@ define(function (require, exports, module) {
*/
Panel.prototype.hide = function () {
let panelId = this.panelID;
+
+ // Quick Access panel is pinned — it stays in _openIds and the tab bar.
+ // Hiding it collapses the bottom panel container entirely.
+ if (panelId === _defaultPanelId) {
+ if (_activeId !== panelId) {
+ return;
+ }
+ this.$panel.removeClass("active-bottom-panel");
+ _activeId = null;
+ _updateActiveTabHighlight();
+ if (_$container) {
+ restoreIfMaximized();
+ Resizer.hide(_$container[0]);
+ }
+ exports.trigger(EVENT_PANEL_HIDDEN, panelId);
+ return;
+ }
+
let idx = _openIds.indexOf(panelId);
if (idx === -1) {
- // Not open - no-op
return;
}
@@ -700,10 +769,9 @@ define(function (require, exports, module) {
if (wasActive && _openIds.length > 0) {
let nextIdx = Math.min(idx, _openIds.length - 1);
activatedId = _openIds[nextIdx];
- _activeId = null; // clear so _switchToTab runs
+ _activeId = null;
_switchToTab(activatedId);
} else if (wasActive) {
- // No more tabs - hide the container
_activeId = null;
if (_$container) {
restoreIfMaximized();
@@ -713,10 +781,8 @@ define(function (require, exports, module) {
_removeTabFromBar(panelId);
- // Always fire HIDDEN for the closed panel first
exports.trigger(EVENT_PANEL_HIDDEN, panelId);
- // Then fire SHOWN for the newly activated tab, if any
if (activatedId) {
exports.trigger(EVENT_PANEL_SHOWN, activatedId);
}
@@ -793,18 +859,11 @@ define(function (require, exports, module) {
_recomputeLayout = recomputeLayoutFn;
_defaultPanelId = defaultPanelId;
- // Create the "Tools" button inside the scrollable tabs area.
- _$addBtn = $(''
- + '
'
- + Strings.BOTTOM_PANEL_DEFAULT_TITLE + '');
- _$tabsOverflow.append(_$addBtn);
-
// Tab bar click handlers
_$tabBar.on("click", ".bottom-panel-tab-close-btn", function (e) {
e.stopPropagation();
let panelId = $(this).closest(".bottom-panel-tab").data("panel-id");
- if (panelId) {
+ if (panelId && panelId !== _defaultPanelId) {
let panel = _panelMap[panelId];
if (panel) {
panel.requestClose();
@@ -836,14 +895,6 @@ define(function (require, exports, module) {
_showOverflowMenu();
});
- // "+" button opens the default/quick-access panel
- _$addBtn.on("click", function (e) {
- e.stopPropagation();
- if (_defaultPanelId && _panelMap[_defaultPanelId]) {
- _panelMap[_defaultPanelId].show();
- }
- });
-
// Hide-panel button collapses the container but keeps tabs intact.
// Maximize state is preserved so the panel re-opens maximized.
_$tabBar.on("click", ".bottom-panel-hide-btn", function (e) {
@@ -860,9 +911,8 @@ define(function (require, exports, module) {
});
// Double-click on empty tab bar area toggles maximize.
- // Exclude tabs themselves, action buttons, and the add button.
_$tabBar.on("dblclick", function (e) {
- if ($(e.target).closest(".bottom-panel-tab, .bottom-panel-tab-close-btn, .bottom-panel-hide-btn, .bottom-panel-maximize-btn, .bottom-panel-add-btn").length) {
+ if ($(e.target).closest(".bottom-panel-tab, .bottom-panel-tab-close-btn, .bottom-panel-hide-btn, .bottom-panel-maximize-btn").length) {
return;
}
_toggleMaximize();
@@ -990,12 +1040,12 @@ define(function (require, exports, module) {
let $btn = _$tabBar.find(".bottom-panel-maximize-btn");
let $icon = $btn.find("i");
if (_isMaximized) {
- $icon.removeClass("fa-regular fa-square")
- .addClass("fa-regular fa-window-restore");
+ $icon.removeClass("fa-solid fa-expand")
+ .addClass("fa-solid fa-compress");
$btn.attr("title", Strings.BOTTOM_PANEL_RESTORE);
} else {
- $icon.removeClass("fa-regular fa-window-restore")
- .addClass("fa-regular fa-square");
+ $icon.removeClass("fa-solid fa-compress")
+ .addClass("fa-solid fa-expand");
$btn.attr("title", Strings.BOTTOM_PANEL_MAXIMIZE);
}
}
@@ -1088,7 +1138,12 @@ define(function (require, exports, module) {
// Clear internal state BEFORE hiding the container so the
// panelCollapsed handler sees an empty _openIds and doesn't
// redundantly update the stacks.
- _openIds = [];
+ // The Quick Access panel stays pinned at index 0.
+ if (_defaultPanelId) {
+ _openIds = [_defaultPanelId];
+ } else {
+ _openIds = [];
+ }
_activeId = null;
if (_$container && _$container.is(":visible")) {
@@ -1098,8 +1153,6 @@ define(function (require, exports, module) {
_updateBottomPanelTabBar();
- // Fire one EVENT_PANEL_HIDDEN per panel for stack tracking.
- // No intermediate EVENT_PANEL_SHOWN events are emitted.
for (let i = 0; i < closedIds.length; i++) {
exports.trigger(EVENT_PANEL_HIDDEN, closedIds[i]);
}
diff --git a/src/view/WorkspaceManager.js b/src/view/WorkspaceManager.js
index 77b223997..de4ca2b8c 100644
--- a/src/view/WorkspaceManager.js
+++ b/src/view/WorkspaceManager.js
@@ -204,7 +204,7 @@ define(function (require, exports, module) {
// Floor the toolbar's maxsize at its minimum width. Without the floor, a narrow
// window with a wide sidebar can drive the cap below 10px, and Resizer's drag
// logic would then squeeze the toolbar to zero and hide it.
- var rawMax = Math.min(window.innerWidth * 0.75, window.innerWidth - sidebarWidth - 100);
+ var rawMax = window.innerWidth - sidebarWidth - 100;
$mainToolbar.data("maxsize", Math.max(minToolbarWidth, rawMax));
}
@@ -420,7 +420,7 @@ define(function (require, exports, module) {
.attr('title', Strings.BOTTOM_PANEL_MINIMIZE)
);
$tabBarActions.append(
- $('')
+ $('')
.attr('title', Strings.BOTTOM_PANEL_MAXIMIZE)
);
$bottomPanelTabBar.append($bottomPanelTabsOverflow);
@@ -570,7 +570,7 @@ define(function (require, exports, module) {
let minToolbarWidth = (panelBeingShown.minWidth || 0) + pluginIconsBarWidth;
let maxToolbarWidth = Math.max(
minToolbarWidth,
- Math.min(window.innerWidth * 0.75, window.innerWidth - sidebarWidth - 100)
+ window.innerWidth - sidebarWidth - 100
);
let currentWidth = $mainToolbar.width();
if (currentWidth > maxToolbarWidth || currentWidth < minToolbarWidth) {
@@ -675,7 +675,8 @@ define(function (require, exports, module) {
/**
* Programmatically sets the plugin panel content width to the given value in pixels.
* The total toolbar width is adjusted to account for the plugin icons bar.
- * Width is clamped to respect panel minWidth and max size (75% of window).
+ * If the requested width doesn't fit, the sidebar is progressively shrunk
+ * (and collapsed if necessary) before clamping.
* No-op if no panel is currently visible.
* @param {number} width Desired content width in pixels
*/
@@ -686,11 +687,31 @@ define(function (require, exports, module) {
var pluginIconsBarWidth = $pluginIconsBar.outerWidth();
var newToolbarWidth = width + pluginIconsBarWidth;
- // Respect min/max constraints
var minSize = currentlyShownPanel.minWidth || 0;
var minToolbarWidth = minSize + pluginIconsBarWidth;
var sidebarWidth = _getSidebarWidth();
- var maxToolbarWidth = Math.min(window.innerWidth * 0.75, window.innerWidth - sidebarWidth - 100);
+ var maxToolbarWidth = window.innerWidth - sidebarWidth - MIN_EDITOR_WIDTH;
+
+ if (newToolbarWidth > maxToolbarWidth && sidebarWidth > 0) {
+ var $sb = $("#sidebar");
+ var deficit = newToolbarWidth - maxToolbarWidth;
+ var newSidebarWidth = sidebarWidth - deficit;
+
+ if (newSidebarWidth >= MIN_SIDEBAR_WIDTH) {
+ $sb.width(newSidebarWidth);
+ var resync = $sb.data("resyncSizer");
+ if (typeof resync === "function") {
+ resync();
+ }
+ $sb.trigger("panelResizeUpdate", [newSidebarWidth]);
+ $sb.trigger("panelResizeEnd", [newSidebarWidth]);
+ } else {
+ Resizer.hide($sb[0]);
+ }
+ sidebarWidth = _getSidebarWidth();
+ maxToolbarWidth = window.innerWidth - sidebarWidth - MIN_EDITOR_WIDTH;
+ }
+
newToolbarWidth = Math.max(newToolbarWidth, minToolbarWidth);
newToolbarWidth = Math.min(newToolbarWidth, maxToolbarWidth);
diff --git a/test/spec/CentralControlBar-integ-test.js b/test/spec/CentralControlBar-integ-test.js
index e2a10643d..104e3377f 100644
--- a/test/spec/CentralControlBar-integ-test.js
+++ b/test/spec/CentralControlBar-integ-test.js
@@ -887,30 +887,38 @@ define(function (require, exports, module) {
expect(Math.abs(mtRect.right - testWindow.innerWidth)).toBeLessThan(2);
});
- it("should, in normal mode, resize only the plugin-panel (sidebar untouched) and respect the 75%/sidebar clamp", async function () {
+ it("should, in normal mode, progressively shrink the sidebar when an oversized plugin-panel width is requested", async function () {
await openLivePreview();
const iconsW = _$("#plugin-icons-bar").outerWidth();
const sidebarBefore = _$("#sidebar")[0].offsetWidth;
- // Request a very wide panel — should be clamped against 75% window
- // and (window - sidebar - 100). Either way sidebar must stay put.
+ // Request a very wide panel — the sidebar should shrink (or
+ // collapse) to make room, then the toolbar is clamped to
+ // (window - finalSidebarWidth - MIN_EDITOR_WIDTH).
const requested = testWindow.innerWidth; // intentionally over-large
WorkspaceManager.setPluginPanelWidth(requested);
await awaits(0);
- expect(_$("#sidebar")[0].offsetWidth).toBe(sidebarBefore);
+ // Sidebar must have shrunk or collapsed to accommodate the request.
+ const sidebarAfter = SidebarView.isVisible() ? _$("#sidebar")[0].offsetWidth : 0;
+ expect(sidebarAfter).toBeLessThan(sidebarBefore);
const toolbar = _$("#main-toolbar").outerWidth();
- const maxAllowed = Math.min(
- testWindow.innerWidth * 0.75,
- testWindow.innerWidth - sidebarBefore - 100
- );
- // Toolbar must honour the clamp (+iconsBar = the WSM math).
+ // Toolbar is clamped to (window - sidebar - MIN_EDITOR_WIDTH).
+ const maxAllowed = testWindow.innerWidth - sidebarAfter - 100;
expect(toolbar).toBeLessThanOrEqual(maxAllowed + 3);
// And at minimum it's the icons-bar + LP's minWidth.
const lp = livePanel();
const minToolbar = (lp && lp.minWidth ? lp.minWidth : 0) + iconsW;
expect(toolbar).toBeGreaterThanOrEqual(minToolbar);
+
+ // Restore sidebar for subsequent tests.
+ if (!SidebarView.isVisible()) {
+ SidebarView.show();
+ await awaitsFor(function () { return SidebarView.isVisible(); },
+ "sidebar to come back", 2000);
+ }
+ SidebarView.resize(200);
});
});
diff --git a/tracking-repos.json b/tracking-repos.json
index e555dba24..2f81c3ed3 100644
--- a/tracking-repos.json
+++ b/tracking-repos.json
@@ -1,5 +1,5 @@
{
"phoenixPro": {
- "commitID": "59e1d73841f94f546ad9b62a76217eb0a1e450e8"
+ "commitID": "0bb0ed293e70501363fa3cd9916319ea1dbf38bb"
}
}