From 96d6947aa623783af6bf150a976dc1eb81f4a8c1 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 3 Mar 2025 20:53:59 +0000 Subject: [PATCH] feat: scrollbars --- .../__tests__/api/dockviewPanelApi.spec.ts | 3 + .../components/titlebar/tabsContainer.spec.ts | 14 ++++ .../dockview/dockviewGroupPanel.spec.ts | 3 + .../dockview/dockviewGroupPanelModel.spec.ts | 6 ++ .../__tests__/gridview/gridviewPanel.spec.ts | 1 + .../src/dockview/components/popupService.ts | 2 +- .../dockview/components/titlebar/tabs.scss | 10 +-- .../components/titlebar/tabsContainer.ts | 73 ++++++++++--------- .../src/dockview/dockviewComponent.ts | 4 + .../dockview-core/src/dockview/options.ts | 2 + packages/dockview-core/src/scrollbar.ts | 6 +- 11 files changed, 78 insertions(+), 46 deletions(-) diff --git a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts index 0bc24eaa5..d4255ab22 100644 --- a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts +++ b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts @@ -10,6 +10,7 @@ describe('groupPanelApi', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const panelMock = jest.fn(() => { @@ -50,6 +51,7 @@ describe('groupPanelApi', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupViewPanel = new DockviewGroupPanel( @@ -82,6 +84,7 @@ describe('groupPanelApi', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupViewPanel = new DockviewGroupPanel( diff --git a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts index bae8daa8d..bb79949cf 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts @@ -18,6 +18,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -71,6 +72,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const dropTargetContainer = document.createElement('div'); @@ -140,6 +142,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -203,6 +206,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -266,6 +270,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -334,6 +339,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -398,6 +404,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -464,6 +471,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), doSetGroupActive: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -520,6 +528,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), doSetGroupActive: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -571,6 +580,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -627,6 +637,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -694,6 +705,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -761,6 +773,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -824,6 +837,7 @@ describe('tabsContainer', () => { const cut = new TabsContainer( fromPartial({ options: {}, + onDidOptionsChange: jest.fn(), }), fromPartial({}) ); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts index eb2ab5320..1a01b1d30 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts @@ -16,6 +16,7 @@ describe('dockviewGroupPanel', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const options = fromPartial({}); const cut = new DockviewGroupPanel(accessor, 'test_id', options); @@ -39,6 +40,7 @@ describe('dockviewGroupPanel', () => { detatch: jest.fn(), }, doSetGroupActive: jest.fn(), + onDidOptionsChange: jest.fn(), }); const options = fromPartial({}); @@ -81,6 +83,7 @@ describe('dockviewGroupPanel', () => { detatch: jest.fn(), }), options: {}, + onDidOptionsChange: jest.fn(), }); const options = fromPartial({}); const cut = new DockviewGroupPanel(accessor, 'test_id', options); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 719c26ca0..6e24cd53f 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -270,6 +270,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: () => ({ dispose: jest.fn() }), }); groupview = new DockviewGroupPanel(dockview, 'groupview-1', options); @@ -651,6 +652,7 @@ describe('dockviewGroupPanelModel', () => { getPanel: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -713,6 +715,7 @@ describe('dockviewGroupPanelModel', () => { getPanel: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -809,6 +812,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: jest.fn(), }); const groupView = fromPartial({ @@ -875,6 +879,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -948,6 +953,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( diff --git a/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts index cfae5ea6b..18ed7d1d6 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts @@ -8,6 +8,7 @@ describe('gridviewPanel', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), } as any; }); diff --git a/packages/dockview-core/src/dockview/components/popupService.ts b/packages/dockview-core/src/dockview/components/popupService.ts index 1fb5a0996..58f0b9853 100644 --- a/packages/dockview-core/src/dockview/components/popupService.ts +++ b/packages/dockview-core/src/dockview/components/popupService.ts @@ -8,7 +8,7 @@ import { export class PopupService extends CompositeDisposable { private readonly _element: HTMLElement; private _active: HTMLElement | null = null; - private _activeDisposable = new MutableDisposable(); + private readonly _activeDisposable = new MutableDisposable(); constructor(private readonly root: HTMLElement) { super(); diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss index 5b9e7487f..279659fac 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss @@ -1,5 +1,9 @@ .dv-tabs-container { + display: flex; + height: 100%; overflow: hidden; + scrollbar-width: thin; // firefox + &.dv-horizontal { .dv-tabs-container { .dv-tab { @@ -26,11 +30,6 @@ } } - display: flex; - height: 100%; - overflow: hidden; - scrollbar-width: thin; // firefox - &::-webkit-scrollbar { height: 3px; } @@ -57,7 +56,6 @@ } } - .dv-tabs-overflow-container { flex-direction: column; height: unset; diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 6a8b803e3..e02533611 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -140,11 +140,7 @@ export class TabsContainer this.preActionsContainer.className = 'dv-pre-actions-container'; this.tabs = new Tabs(group, accessor, { - showTabsOverflowControl: false, - }); - - this.tabs.onOverflowTabsChange((event) => { - this.toggleDropdown(event); + showTabsOverflowControl: !accessor.options.disableTabsOverflowList, }); this.voidContainer = new VoidContainer(this.accessor, this.group); @@ -156,6 +152,13 @@ export class TabsContainer this._element.appendChild(this.rightActionsContainer); this.addDisposables( + accessor.onDidOptionsChange(() => { + this.tabs.showTabsOverflowControl = + !accessor.options.disableTabsOverflowList; + }), + this.tabs.onOverflowTabsChange((event) => { + this.toggleDropdown(event); + }), this.tabs, this._onWillShowOverlay, this._onDrop, @@ -348,42 +351,40 @@ export class TabsContainer el.style.overflow = 'auto'; el.className = 'dv-tabs-overflow-container'; - this.tabs.tabs - .filter((tab) => this._overflowTabs.includes(tab.panel.id)) - .map((tab) => { - const panelObject = this.group.panels.find( - (panel) => panel === tab.panel - )!; + for (const tab of this.tabs.tabs.filter((tab) => + this._overflowTabs.includes(tab.panel.id) + )) { + const panelObject = this.group.panels.find( + (panel) => panel === tab.panel + )!; - const tabComponent = - panelObject.view.createTabRenderer( - 'headerOverflow' - ); + const tabComponent = + panelObject.view.createTabRenderer('headerOverflow'); - const child = tabComponent.element; + const child = tabComponent.element; - const wrapper = document.createElement('div'); - toggleClass(wrapper, 'dv-tab', true); - toggleClass( - wrapper, - 'dv-active-tab', - panelObject.api.isActive - ); - toggleClass( - wrapper, - 'dv-inactive-tab', - !panelObject.api.isActive - ); + const wrapper = document.createElement('div'); + toggleClass(wrapper, 'dv-tab', true); + toggleClass( + wrapper, + 'dv-active-tab', + panelObject.api.isActive + ); + toggleClass( + wrapper, + 'dv-inactive-tab', + !panelObject.api.isActive + ); - wrapper.addEventListener('mousedown', () => { - this.accessor.popupService.close(); - tab.element.scrollIntoView(); - tab.panel.api.setActive(); - }); - wrapper.appendChild(child); - - el.appendChild(wrapper); + wrapper.addEventListener('mousedown', () => { + this.accessor.popupService.close(); + tab.element.scrollIntoView(); + tab.panel.api.setActive(); }); + wrapper.appendChild(child); + + el.appendChild(wrapper); + } this.accessor.popupService.openPopover(el, { x: event.clientX, diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 348e7725c..e9abdf237 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -330,6 +330,9 @@ export class DockviewComponent readonly onDidAddGroup: Event = this._onDidAddGroup.event; + private readonly _onDidOptionsChange = new Emitter(); + readonly onDidOptionsChange: Event = this._onDidOptionsChange.event; + private readonly _onDidActiveGroupChange = new Emitter< DockviewGroupPanel | undefined >(); @@ -434,6 +437,7 @@ export class DockviewComponent this._onDidActiveGroupChange, this._onUnhandledDragOverEvent, this._onDidMaximizedGroupChange, + this._onDidOptionsChange, this.onDidViewVisibilityChangeMicroTaskQueue(() => { this.updateWatermark(); }), diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 4b096c941..bf0861006 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -72,6 +72,7 @@ export interface DockviewOptions { */ noPanelsOverlay?: 'emptyGroup' | 'watermark'; theme?: DockviewTheme; + disableTabsOverflowList?: boolean; } export interface DockviewDndOverlayEvent extends IAcceptableEvent { @@ -119,6 +120,7 @@ export const PROPERTY_KEYS_DOCKVIEW: (keyof DockviewOptions)[] = (() => { dndEdges: undefined, theme: undefined, gap: undefined, + disableTabsOverflowList: undefined, }; return Object.keys(properties) as (keyof DockviewOptions)[]; diff --git a/packages/dockview-core/src/scrollbar.ts b/packages/dockview-core/src/scrollbar.ts index 535ebd8e2..75db4e54a 100644 --- a/packages/dockview-core/src/scrollbar.ts +++ b/packages/dockview-core/src/scrollbar.ts @@ -4,11 +4,11 @@ import { CompositeDisposable } from './lifecycle'; import { clamp } from './math'; export class Scrollbar extends CompositeDisposable { - private _element: HTMLElement; - private _horizontalScrollbar: HTMLElement; + private readonly _element: HTMLElement; + private readonly _horizontalScrollbar: HTMLElement; private _scrollLeft: number = 0; private _animationTimer: any; - static MouseWheelSpeed = 1; + public static MouseWheelSpeed = 1; get element(): HTMLElement { return this._element;