diff --git a/src/browser/app/profile/features.inc b/src/browser/app/profile/features.inc index 49d79fab76..22c0512f33 100644 --- a/src/browser/app/profile/features.inc +++ b/src/browser/app/profile/features.inc @@ -11,6 +11,8 @@ pref('zen.tabs.vertical', true); pref('zen.tabs.vertical.right-side', false); pref('zen.tabs.rename-tabs', true); +pref('zen.tabs.essentials.max', 12); +pref('zen.tabs.essentials.height', 0); // In vh units pref('zen.tabs.show-newtab-vertical', true); pref('zen.ctrlTab.show-pending-tabs', false); diff --git a/src/zen/common/ZenUIManager.mjs b/src/zen/common/ZenUIManager.mjs index 7372377cd3..3dedf6873a 100644 --- a/src/zen/common/ZenUIManager.mjs +++ b/src/zen/common/ZenUIManager.mjs @@ -33,6 +33,13 @@ var gZenUIManager = { 'zen.urlbar.show-domain-only-in-sidebar', true ); + XPCOMUtils.defineLazyPreferenceGetter( + this, + 'essentialsHeight', + 'zen.tabs.essentials.height', + 0, + this._updateEssentialsHeight.bind(this) + ); document.addEventListener('mousedown', this.handleMouseDown.bind(this), true); @@ -62,6 +69,7 @@ var gZenUIManager = { gZenWorkspaces.promiseInitialized.finally(() => { this._hasLoadedDOM = true; this.updateTabsToolbar(); + this._updateEssentialsHeight(); }); window.addEventListener('TabClose', this.onTabClose.bind(this)); @@ -70,6 +78,19 @@ var gZenUIManager = { gZenVerticalTabsManager.init(); }, + _updateEssentialsHeight() { + const essentialsWrapper = document.getElementById('zen-essentials'); + if (!essentialsWrapper) return; + + essentialsWrapper.style.setProperty('--zen-essentials-height', `${this.essentialsHeight}vh`); + + if (this.essentialsHeight === 0) { + essentialsWrapper.setAttribute('data-essentials-height', '0'); + } else { + essentialsWrapper.removeAttribute('data-essentials-height'); + } + }, + handleMouseDown(event) { this._lastClickPosition = { clientX: event.clientX, diff --git a/src/zen/tabs/ZenPinnedTabManager.mjs b/src/zen/tabs/ZenPinnedTabManager.mjs index 0e46a678f9..2ca3428ad2 100644 --- a/src/zen/tabs/ZenPinnedTabManager.mjs +++ b/src/zen/tabs/ZenPinnedTabManager.mjs @@ -52,12 +52,18 @@ } class nsZenPinnedTabManager extends ZenDOMOperatedFeature { - MAX_ESSENTIALS_TABS = 12; - async init() { if (!this.enabled) { return; } + + XPCOMUtils.defineLazyPreferenceGetter( + lazy, + 'zenTabsEssentialsMax', + 'zen.tabs.essentials.max', + 12 + ); + this._canLog = Services.prefs.getBoolPref('zen.pinned-tab-manager.debug', false); this.observer = new ZenPinnedTabsObserver(); this._initClosePinnedTabShortcut(); @@ -69,6 +75,21 @@ gZenWorkspaces._resolvePinnedInitialized(); } + get MAX_ESSENTIALS_TABS() { + return lazy.zenTabsEssentialsMax; + } + + async onWorkspaceChange(newWorkspace, onInit) { + if (!this.enabled || PrivateBrowsingUtils.isWindowPrivate(window)) { + return; + } + + if (onInit) { + await this._refreshPinnedTabs({ init: onInit }); + this._hasFinishedLoading = true; + } + } + log(message) { if (this._canLog) { console.log(`[ZenPinnedTabManager] ${message}`); diff --git a/src/zen/tabs/zen-tabs/vertical-tabs.css b/src/zen/tabs/zen-tabs/vertical-tabs.css index 1117f1593d..c2fc904493 100644 --- a/src/zen/tabs/zen-tabs/vertical-tabs.css +++ b/src/zen/tabs/zen-tabs/vertical-tabs.css @@ -1283,6 +1283,11 @@ #zen-essentials { z-index: 1; + overflow: hidden; + + &:not([data-essentials-height="0"]) { + max-height: var(--zen-essentials-height); + } } /* Container for essential items (often pinned/favorite tabs) */ @@ -1290,12 +1295,19 @@ will-change: transform; padding-bottom: var(--zen-toolbox-padding); - overflow: hidden; + overflow-y: auto; + overflow-x: hidden; gap: 4px; transition: max-height 0.3s ease-out, grid-template-columns 0.3s ease-out; opacity: 1; + + #zen-essentials:not([data-essentials-height="0"]) & { + height: 100%; + max-height: var(--zen-essentials-height); + } + grid-template-columns: repeat(auto-fit, minmax(max(23.7%, 48px), 1fr)); &[data-hack-type='1'] { grid-template-columns: repeat(auto-fit, minmax(max(30%, 48px), auto)); @@ -1333,6 +1345,48 @@ &[cloned] { pointer-events: none; } + + &[overflowing] { + --zen-scrollbar-overflow-background: light-dark(rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.08)); + + &::before { + content: ''; + position: sticky; + top: 0; + left: 0; + right: 0; + width: 100%; + height: 1px; + opacity: 0; + pointer-events: none; + transition: opacity 0.1s; + background-color: var(--zen-scrollbar-overflow-background); + grid-column: 1 / -1; + } + + &::after { + content: ''; + position: sticky; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 1px; + opacity: 0; + pointer-events: none; + transition: opacity 0.1s; + background-color: var(--zen-scrollbar-overflow-background); + grid-column: 1 / -1; + } + } + + &[overflowing]:not([scrolledtostart])::before { + opacity: 1; + } + + &[overflowing]:not([scrolledtoend])::after { + opacity: 1; + } } /* Style tabs within the Essentials container (and a related welcome sidebar) */ diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index 37a0c3967c..7bdfc2f4b5 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -445,6 +445,17 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature { essentialsContainer.setAttribute('container', container); document.getElementById('zen-essentials').appendChild(essentialsContainer); + this._updateEssentialsOverflow(essentialsContainer); + + essentialsContainer.addEventListener('scroll', () => { + this._updateEssentialsOverflow(essentialsContainer); + }); + + const resizeObserver = new ResizeObserver(() => { + this._updateEssentialsOverflow(essentialsContainer); + }); + resizeObserver.observe(essentialsContainer); + // Set an initial hidden state if the essentials section is not supposed // to be shown on the current workspace if ( @@ -457,6 +468,30 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature { return essentialsContainer; } + _updateEssentialsOverflow(container) { + const isOverflowing = container.scrollHeight > container.clientHeight; + const isAtStart = container.scrollTop === 0; + const isAtEnd = (container.scrollTop + container.clientHeight) >= (container.scrollHeight - 1); + + if (isOverflowing) { + container.setAttribute('overflowing', 'true'); + } else { + container.removeAttribute('overflowing'); + } + + if (isAtStart) { + container.setAttribute('scrolledtostart', 'true'); + } else { + container.removeAttribute('scrolledtostart'); + } + + if (isAtEnd) { + container.setAttribute('scrolledtoend', 'true'); + } else { + container.removeAttribute('scrolledtoend'); + } + } + getCurrentEssentialsContainer() { const currentWorkspace = this.getActiveWorkspaceFromCache(); return this.getEssentialsSection(currentWorkspace?.containerTabId);