mirror of
https://github.com/mathuo/dockview
synced 2025-05-08 12:38:24 +00:00
Merge pull request #822 from mathuo/610-feature-request-dropdown-menu-to-handle-overflow-tabs-4
tabs overflow menu
This commit is contained in:
commit
d3d57b62b2
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
- run: npm run build
|
- run: npm run build
|
||||||
- run: npm run test:cov
|
- run: npm run test:cov
|
||||||
- name: SonarCloud Scan
|
- name: SonarCloud Scan
|
||||||
uses: sonarsource/sonarqube-scan-action@v4.1.0
|
uses: sonarsource/sonarqube-scan-action@v5
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||||
import {
|
import {
|
||||||
GroupPanelPartInitParameters,
|
TabPartInitParameters,
|
||||||
IContentRenderer,
|
IContentRenderer,
|
||||||
ITabRenderer,
|
ITabRenderer,
|
||||||
} from '../../dockview/types';
|
} from '../../dockview/types';
|
||||||
import { PanelUpdateEvent } from '../../panel/types';
|
import { PanelUpdateEvent } from '../../panel/types';
|
||||||
|
import { TabLocation } from '../../dockview/framework';
|
||||||
|
|
||||||
export class DockviewPanelModelMock implements IDockviewPanelModel {
|
export class DockviewPanelModelMock implements IDockviewPanelModel {
|
||||||
constructor(
|
constructor(
|
||||||
@ -17,8 +18,11 @@ export class DockviewPanelModelMock implements IDockviewPanelModel {
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
|
||||||
|
return this.tab;
|
||||||
|
}
|
||||||
|
|
||||||
init(params: GroupPanelPartInitParameters): void {
|
init(params: TabPartInitParameters): void {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ describe('groupPanelApi', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const panelMock = jest.fn<DockviewPanel, []>(() => {
|
const panelMock = jest.fn<DockviewPanel, []>(() => {
|
||||||
@ -50,6 +51,7 @@ describe('groupPanelApi', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupViewPanel = new DockviewGroupPanel(
|
const groupViewPanel = new DockviewGroupPanel(
|
||||||
@ -82,6 +84,7 @@ describe('groupPanelApi', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupViewPanel = new DockviewGroupPanel(
|
const groupViewPanel = new DockviewGroupPanel(
|
||||||
|
@ -18,6 +18,7 @@ describe('tabsContainer', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||||
@ -71,6 +72,7 @@ describe('tabsContainer', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const dropTargetContainer = document.createElement('div');
|
const dropTargetContainer = document.createElement('div');
|
||||||
@ -140,6 +142,7 @@ describe('tabsContainer', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||||
@ -203,6 +206,7 @@ describe('tabsContainer', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||||
@ -266,6 +270,7 @@ describe('tabsContainer', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||||
@ -334,6 +339,7 @@ describe('tabsContainer', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
@ -398,6 +404,7 @@ describe('tabsContainer', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
@ -464,6 +471,7 @@ describe('tabsContainer', () => {
|
|||||||
element: document.createElement('div'),
|
element: document.createElement('div'),
|
||||||
addFloatingGroup: jest.fn(),
|
addFloatingGroup: jest.fn(),
|
||||||
doSetGroupActive: jest.fn(),
|
doSetGroupActive: jest.fn(),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
@ -520,6 +528,7 @@ describe('tabsContainer', () => {
|
|||||||
element: document.createElement('div'),
|
element: document.createElement('div'),
|
||||||
addFloatingGroup: jest.fn(),
|
addFloatingGroup: jest.fn(),
|
||||||
doSetGroupActive: jest.fn(),
|
doSetGroupActive: jest.fn(),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
@ -571,6 +580,7 @@ describe('tabsContainer', () => {
|
|||||||
element: document.createElement('div'),
|
element: document.createElement('div'),
|
||||||
addFloatingGroup: jest.fn(),
|
addFloatingGroup: jest.fn(),
|
||||||
getGroupPanel: jest.fn(),
|
getGroupPanel: jest.fn(),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
@ -627,6 +637,7 @@ describe('tabsContainer', () => {
|
|||||||
element: document.createElement('div'),
|
element: document.createElement('div'),
|
||||||
addFloatingGroup: jest.fn(),
|
addFloatingGroup: jest.fn(),
|
||||||
getGroupPanel: jest.fn(),
|
getGroupPanel: jest.fn(),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
@ -694,6 +705,7 @@ describe('tabsContainer', () => {
|
|||||||
element: document.createElement('div'),
|
element: document.createElement('div'),
|
||||||
addFloatingGroup: jest.fn(),
|
addFloatingGroup: jest.fn(),
|
||||||
getGroupPanel: jest.fn(),
|
getGroupPanel: jest.fn(),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
@ -761,6 +773,7 @@ describe('tabsContainer', () => {
|
|||||||
element: document.createElement('div'),
|
element: document.createElement('div'),
|
||||||
addFloatingGroup: jest.fn(),
|
addFloatingGroup: jest.fn(),
|
||||||
getGroupPanel: jest.fn(),
|
getGroupPanel: jest.fn(),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||||
@ -824,6 +837,7 @@ describe('tabsContainer', () => {
|
|||||||
const cut = new TabsContainer(
|
const cut = new TabsContainer(
|
||||||
fromPartial<DockviewComponent>({
|
fromPartial<DockviewComponent>({
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
}),
|
}),
|
||||||
fromPartial<DockviewGroupPanel>({})
|
fromPartial<DockviewGroupPanel>({})
|
||||||
);
|
);
|
||||||
|
@ -133,11 +133,15 @@ describe('dockviewComponent', () => {
|
|||||||
},
|
},
|
||||||
className: 'test-a test-b',
|
className: 'test-a test-b',
|
||||||
});
|
});
|
||||||
expect(dockview.element.className).toBe('test-a test-b dockview-theme-abyss');
|
expect(dockview.element.className).toBe(
|
||||||
|
'test-a test-b dockview-theme-abyss'
|
||||||
|
);
|
||||||
|
|
||||||
dockview.updateOptions({ className: 'test-b test-c' });
|
dockview.updateOptions({ className: 'test-b test-c' });
|
||||||
|
|
||||||
expect(dockview.element.className).toBe('dockview-theme-abyss test-b test-c');
|
expect(dockview.element.className).toBe(
|
||||||
|
'dockview-theme-abyss test-b test-c'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('memory leakage', () => {
|
describe('memory leakage', () => {
|
||||||
@ -2453,17 +2457,17 @@ describe('dockviewComponent', () => {
|
|||||||
const group = dockview.getGroupPanel('panel2')!.api.group;
|
const group = dockview.getGroupPanel('panel2')!.api.group;
|
||||||
|
|
||||||
const viewQuery = group.element.querySelectorAll(
|
const viewQuery = group.element.querySelectorAll(
|
||||||
'.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab'
|
'.dv-groupview > .dv-tabs-and-actions-container > .dv-scrollable > .dv-tabs-container > .dv-tab'
|
||||||
);
|
);
|
||||||
expect(viewQuery.length).toBe(2);
|
expect(viewQuery.length).toBe(2);
|
||||||
|
|
||||||
const viewQuery2 = group.element.querySelectorAll(
|
const viewQuery2 = group.element.querySelectorAll(
|
||||||
'.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab > .dv-default-tab'
|
'.dv-groupview > .dv-tabs-and-actions-container > .dv-scrollable > .dv-tabs-container > .dv-tab > .dv-default-tab'
|
||||||
);
|
);
|
||||||
expect(viewQuery2.length).toBe(1);
|
expect(viewQuery2.length).toBe(1);
|
||||||
|
|
||||||
const viewQuery3 = group.element.querySelectorAll(
|
const viewQuery3 = group.element.querySelectorAll(
|
||||||
'.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2'
|
'.dv-groupview > .dv-tabs-and-actions-container > .dv-scrollable > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2'
|
||||||
);
|
);
|
||||||
expect(viewQuery3.length).toBe(1);
|
expect(viewQuery3.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,7 @@ describe('dockviewGroupPanel', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
const options = fromPartial<GroupOptions>({});
|
const options = fromPartial<GroupOptions>({});
|
||||||
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
||||||
@ -39,6 +40,7 @@ describe('dockviewGroupPanel', () => {
|
|||||||
detatch: jest.fn(),
|
detatch: jest.fn(),
|
||||||
},
|
},
|
||||||
doSetGroupActive: jest.fn(),
|
doSetGroupActive: jest.fn(),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
const options = fromPartial<GroupOptions>({});
|
const options = fromPartial<GroupOptions>({});
|
||||||
|
|
||||||
@ -81,6 +83,7 @@ describe('dockviewGroupPanel', () => {
|
|||||||
detatch: jest.fn(),
|
detatch: jest.fn(),
|
||||||
}),
|
}),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
const options = fromPartial<GroupOptions>({});
|
const options = fromPartial<GroupOptions>({});
|
||||||
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
||||||
|
@ -24,6 +24,7 @@ import { createOffsetDragOverEvent } from '../__test_utils__/utils';
|
|||||||
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
|
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
|
||||||
import { Emitter } from '../../events';
|
import { Emitter } from '../../events';
|
||||||
import { fromPartial } from '@total-typescript/shoehorn';
|
import { fromPartial } from '@total-typescript/shoehorn';
|
||||||
|
import { TabLocation } from '../../dockview/framework';
|
||||||
|
|
||||||
enum GroupChangeKind2 {
|
enum GroupChangeKind2 {
|
||||||
ADD_PANEL,
|
ADD_PANEL,
|
||||||
@ -36,12 +37,16 @@ class TestModel implements IDockviewPanelModel {
|
|||||||
readonly contentComponent: string;
|
readonly contentComponent: string;
|
||||||
readonly tab: ITabRenderer;
|
readonly tab: ITabRenderer;
|
||||||
|
|
||||||
constructor(id: string) {
|
constructor(readonly id: string) {
|
||||||
this.content = new TestHeaderPart(id);
|
this.content = new TestHeaderPart(id);
|
||||||
this.contentComponent = id;
|
this.contentComponent = id;
|
||||||
this.tab = new TestContentPart(id);
|
this.tab = new TestContentPart(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
|
||||||
|
return new TestHeaderPart(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
update(event: PanelUpdateEvent): void {
|
update(event: PanelUpdateEvent): void {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
@ -265,6 +270,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
document.createElement('div'),
|
document.createElement('div'),
|
||||||
fromPartial<DockviewComponent>({})
|
fromPartial<DockviewComponent>({})
|
||||||
),
|
),
|
||||||
|
onDidOptionsChange: () => ({ dispose: jest.fn() }),
|
||||||
});
|
});
|
||||||
|
|
||||||
groupview = new DockviewGroupPanel(dockview, 'groupview-1', options);
|
groupview = new DockviewGroupPanel(dockview, 'groupview-1', options);
|
||||||
@ -646,6 +652,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
getPanel: jest.fn(),
|
getPanel: jest.fn(),
|
||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||||
@ -708,6 +715,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
getPanel: jest.fn(),
|
getPanel: jest.fn(),
|
||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||||
@ -804,6 +812,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
document.createElement('div'),
|
document.createElement('div'),
|
||||||
fromPartial<DockviewComponent>({})
|
fromPartial<DockviewComponent>({})
|
||||||
),
|
),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||||
@ -870,6 +879,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
document.createElement('div'),
|
document.createElement('div'),
|
||||||
fromPartial<DockviewComponent>({})
|
fromPartial<DockviewComponent>({})
|
||||||
),
|
),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||||
@ -943,6 +953,7 @@ describe('dockviewGroupPanelModel', () => {
|
|||||||
document.createElement('div'),
|
document.createElement('div'),
|
||||||
fromPartial<DockviewComponent>({})
|
fromPartial<DockviewComponent>({})
|
||||||
),
|
),
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||||
|
@ -8,6 +8,7 @@ describe('gridviewPanel', () => {
|
|||||||
onDidAddPanel: jest.fn(),
|
onDidAddPanel: jest.fn(),
|
||||||
onDidRemovePanel: jest.fn(),
|
onDidRemovePanel: jest.fn(),
|
||||||
options: {},
|
options: {},
|
||||||
|
onDidOptionsChange: jest.fn(),
|
||||||
} as any;
|
} as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
import { addDisposableWindowListener } from '../../events';
|
||||||
|
import {
|
||||||
|
CompositeDisposable,
|
||||||
|
Disposable,
|
||||||
|
MutableDisposable,
|
||||||
|
} from '../../lifecycle';
|
||||||
|
|
||||||
|
export class PopupService extends CompositeDisposable {
|
||||||
|
private readonly _element: HTMLElement;
|
||||||
|
private _active: HTMLElement | null = null;
|
||||||
|
private readonly _activeDisposable = new MutableDisposable();
|
||||||
|
|
||||||
|
constructor(private readonly root: HTMLElement) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._element = document.createElement('div');
|
||||||
|
this._element.className = 'dv-popover-anchor';
|
||||||
|
this._element.style.position = 'relative';
|
||||||
|
|
||||||
|
this.root.prepend(this._element);
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
Disposable.from(() => {
|
||||||
|
this.close();
|
||||||
|
}),
|
||||||
|
this._activeDisposable
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
openPopover(
|
||||||
|
element: HTMLElement,
|
||||||
|
position: { x: number; y: number }
|
||||||
|
): void {
|
||||||
|
this.close();
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.style.position = 'absolute';
|
||||||
|
wrapper.style.zIndex = '99';
|
||||||
|
wrapper.appendChild(element);
|
||||||
|
|
||||||
|
const anchorBox = this._element.getBoundingClientRect();
|
||||||
|
const offsetX = anchorBox.left;
|
||||||
|
const offsetY = anchorBox.top;
|
||||||
|
|
||||||
|
wrapper.style.top = `${position.y - offsetY}px`;
|
||||||
|
wrapper.style.left = `${position.x - offsetX}px`;
|
||||||
|
|
||||||
|
this._element.appendChild(wrapper);
|
||||||
|
|
||||||
|
this._active = wrapper;
|
||||||
|
|
||||||
|
this._activeDisposable.value = new CompositeDisposable(
|
||||||
|
addDisposableWindowListener(window, 'pointerdown', (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let el: HTMLElement | null = target;
|
||||||
|
|
||||||
|
while (el && el !== wrapper) {
|
||||||
|
el = el?.parentElement ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el) {
|
||||||
|
return; // clicked within popover
|
||||||
|
}
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
if (this._active) {
|
||||||
|
this._active.remove();
|
||||||
|
this._activeDisposable.dispose();
|
||||||
|
this._active = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
.dv-tabs-overflow-dropdown-default {
|
||||||
|
height: 100%;
|
||||||
|
color: var(--dv-activegroup-hiddenpanel-tab-color);
|
||||||
|
|
||||||
|
margin: var(--dv-tab-margin);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { createChevronRightButton } from '../../../svg';
|
||||||
|
|
||||||
|
export type DropdownElement = {
|
||||||
|
element: HTMLElement;
|
||||||
|
update: (params: { tabs: number }) => void;
|
||||||
|
dispose?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createDropdownElementHandle(): DropdownElement {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'dv-tabs-overflow-dropdown-default';
|
||||||
|
|
||||||
|
const text = document.createElement('span');
|
||||||
|
text.textContent = ``;
|
||||||
|
const icon = createChevronRightButton();
|
||||||
|
el.appendChild(icon);
|
||||||
|
el.appendChild(text);
|
||||||
|
|
||||||
|
return {
|
||||||
|
element: el,
|
||||||
|
update: (params: { tabs: number }) => {
|
||||||
|
text.textContent = `${params.tabs}`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
.dv-tabs-container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
scrollbar-width: thin; // firefox
|
||||||
|
|
||||||
|
&.dv-horizontal {
|
||||||
|
.dv-tabs-container {
|
||||||
|
.dv-tab {
|
||||||
|
&:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:nth-last-child(1)) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:first-child)::before {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 5;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: var(--dv-tab-divider-color);
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Track */
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle */
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--dv-tabs-container-scrollbar-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-tab {
|
||||||
|
-webkit-user-drag: element;
|
||||||
|
outline: none;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: var(--dv-tab-font-size);
|
||||||
|
margin: var(--dv-tab-margin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-tabs-overflow-container {
|
||||||
|
flex-direction: column;
|
||||||
|
height: unset;
|
||||||
|
border: 1px solid var(--dv-tab-divider-color);
|
||||||
|
background-color: var(--dv-group-view-background-color);
|
||||||
|
|
||||||
|
.dv-tab {
|
||||||
|
-webkit-user-drag: element;
|
||||||
|
outline: none;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: var(--dv-tab-font-size);
|
||||||
|
margin: var(--dv-tab-margin);
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid var(--dv-tab-divider-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv-active-tab {
|
||||||
|
background-color: var(
|
||||||
|
--dv-activegroup-visiblepanel-tab-background-color
|
||||||
|
);
|
||||||
|
color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||||
|
}
|
||||||
|
.dv-inactive-tab {
|
||||||
|
background-color: var(
|
||||||
|
--dv-activegroup-hiddenpanel-tab-background-color
|
||||||
|
);
|
||||||
|
color: var(--dv-activegroup-hiddenpanel-tab-color);
|
||||||
|
}
|
||||||
|
}
|
297
packages/dockview-core/src/dockview/components/titlebar/tabs.ts
Normal file
297
packages/dockview-core/src/dockview/components/titlebar/tabs.ts
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||||
|
import {
|
||||||
|
isChildEntirelyVisibleWithinParent,
|
||||||
|
OverflowObserver,
|
||||||
|
} from '../../../dom';
|
||||||
|
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||||
|
import {
|
||||||
|
CompositeDisposable,
|
||||||
|
Disposable,
|
||||||
|
IValueDisposable,
|
||||||
|
MutableDisposable,
|
||||||
|
} from '../../../lifecycle';
|
||||||
|
import { Scrollbar } from '../../../scrollbar';
|
||||||
|
import { DockviewComponent } from '../../dockviewComponent';
|
||||||
|
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||||
|
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
||||||
|
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
||||||
|
import { Tab } from '../tab/tab';
|
||||||
|
import { TabDragEvent, TabDropIndexEvent } from './tabsContainer';
|
||||||
|
|
||||||
|
export class Tabs extends CompositeDisposable {
|
||||||
|
private readonly _element: HTMLElement;
|
||||||
|
private readonly _tabsList: HTMLElement;
|
||||||
|
private readonly _observerDisposable = new MutableDisposable();
|
||||||
|
|
||||||
|
private _tabs: IValueDisposable<Tab>[] = [];
|
||||||
|
private selectedIndex = -1;
|
||||||
|
private _showTabsOverflowControl = false;
|
||||||
|
|
||||||
|
private readonly _onTabDragStart = new Emitter<TabDragEvent>();
|
||||||
|
readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event;
|
||||||
|
|
||||||
|
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
||||||
|
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
||||||
|
|
||||||
|
private readonly _onWillShowOverlay =
|
||||||
|
new Emitter<WillShowOverlayLocationEvent>();
|
||||||
|
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> =
|
||||||
|
this._onWillShowOverlay.event;
|
||||||
|
|
||||||
|
private readonly _onOverflowTabsChange = new Emitter<{
|
||||||
|
tabs: string[];
|
||||||
|
reset: boolean;
|
||||||
|
}>();
|
||||||
|
readonly onOverflowTabsChange = this._onOverflowTabsChange.event;
|
||||||
|
|
||||||
|
get showTabsOverflowControl(): boolean {
|
||||||
|
return this._showTabsOverflowControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
set showTabsOverflowControl(value: boolean) {
|
||||||
|
if (this._showTabsOverflowControl == value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._showTabsOverflowControl = value;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
const observer = new OverflowObserver(this._tabsList);
|
||||||
|
|
||||||
|
this._observerDisposable.value = new CompositeDisposable(
|
||||||
|
observer,
|
||||||
|
observer.onDidChange((event) => {
|
||||||
|
const hasOverflow = event.hasScrollX || event.hasScrollY;
|
||||||
|
this.toggleDropdown({ reset: !hasOverflow });
|
||||||
|
}),
|
||||||
|
addDisposableListener(this._tabsList, 'scroll', () => {
|
||||||
|
this.toggleDropdown({ reset: false });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get element(): HTMLElement {
|
||||||
|
return this._element;
|
||||||
|
}
|
||||||
|
|
||||||
|
get panels(): string[] {
|
||||||
|
return this._tabs.map((_) => _.value.panel.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
get size(): number {
|
||||||
|
return this._tabs.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
get tabs(): Tab[] {
|
||||||
|
return this._tabs.map((_) => _.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly group: DockviewGroupPanel,
|
||||||
|
private readonly accessor: DockviewComponent,
|
||||||
|
options: {
|
||||||
|
showTabsOverflowControl: boolean;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._tabsList = document.createElement('div');
|
||||||
|
this._tabsList.className = 'dv-tabs-container dv-horizontal';
|
||||||
|
|
||||||
|
this.showTabsOverflowControl = options.showTabsOverflowControl;
|
||||||
|
|
||||||
|
const scrollbar = new Scrollbar(this._tabsList);
|
||||||
|
this._element = scrollbar.element;
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
this._onOverflowTabsChange,
|
||||||
|
this._observerDisposable,
|
||||||
|
scrollbar,
|
||||||
|
this._onWillShowOverlay,
|
||||||
|
this._onDrop,
|
||||||
|
this._onTabDragStart,
|
||||||
|
addDisposableListener(this.element, 'pointerdown', (event) => {
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLeftClick = event.button === 0;
|
||||||
|
|
||||||
|
if (isLeftClick) {
|
||||||
|
this.accessor.doSetGroupActive(this.group);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Disposable.from(() => {
|
||||||
|
for (const { value, disposable } of this._tabs) {
|
||||||
|
disposable.dispose();
|
||||||
|
value.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tabs = [];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOf(id: string): number {
|
||||||
|
return this._tabs.findIndex((tab) => tab.value.panel.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive(tab: Tab): boolean {
|
||||||
|
return (
|
||||||
|
this.selectedIndex > -1 &&
|
||||||
|
this._tabs[this.selectedIndex].value === tab
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setActivePanel(panel: IDockviewPanel): void {
|
||||||
|
let runningWidth = 0;
|
||||||
|
|
||||||
|
for (const tab of this._tabs) {
|
||||||
|
const isActivePanel = panel.id === tab.value.panel.id;
|
||||||
|
tab.value.setActive(isActivePanel);
|
||||||
|
|
||||||
|
if (isActivePanel) {
|
||||||
|
const element = tab.value.element;
|
||||||
|
const parentElement = element.parentElement!;
|
||||||
|
|
||||||
|
if (
|
||||||
|
runningWidth < parentElement.scrollLeft ||
|
||||||
|
runningWidth + element.clientWidth >
|
||||||
|
parentElement.scrollLeft + parentElement.clientWidth
|
||||||
|
) {
|
||||||
|
parentElement.scrollLeft = runningWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runningWidth += tab.value.element.clientWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openPanel(panel: IDockviewPanel, index: number = this._tabs.length): void {
|
||||||
|
if (this._tabs.find((tab) => tab.value.panel.id === panel.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tab = new Tab(panel, this.accessor, this.group);
|
||||||
|
tab.setContent(panel.view.tab);
|
||||||
|
|
||||||
|
const disposable = new CompositeDisposable(
|
||||||
|
tab.onDragStart((event) => {
|
||||||
|
this._onTabDragStart.fire({ nativeEvent: event, panel });
|
||||||
|
}),
|
||||||
|
tab.onPointerDown((event) => {
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFloatingGroupsEnabled =
|
||||||
|
!this.accessor.options.disableFloatingGroups;
|
||||||
|
|
||||||
|
const isFloatingWithOnePanel =
|
||||||
|
this.group.api.location.type === 'floating' &&
|
||||||
|
this.size === 1;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isFloatingGroupsEnabled &&
|
||||||
|
!isFloatingWithOnePanel &&
|
||||||
|
event.shiftKey
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const panel = this.accessor.getGroupPanel(tab.panel.id);
|
||||||
|
|
||||||
|
const { top, left } = tab.element.getBoundingClientRect();
|
||||||
|
const { top: rootTop, left: rootLeft } =
|
||||||
|
this.accessor.element.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.accessor.addFloatingGroup(panel as DockviewPanel, {
|
||||||
|
x: left - rootLeft,
|
||||||
|
y: top - rootTop,
|
||||||
|
inDragMode: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.button) {
|
||||||
|
case 0: // left click or touch
|
||||||
|
if (this.group.activePanel !== panel) {
|
||||||
|
this.group.model.openPanel(panel);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
tab.onDrop((event) => {
|
||||||
|
this._onDrop.fire({
|
||||||
|
event: event.nativeEvent,
|
||||||
|
index: this._tabs.findIndex((x) => x.value === tab),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
tab.onWillShowOverlay((event) => {
|
||||||
|
this._onWillShowOverlay.fire(
|
||||||
|
new WillShowOverlayLocationEvent(event, {
|
||||||
|
kind: 'tab',
|
||||||
|
panel: this.group.activePanel,
|
||||||
|
api: this.accessor.api,
|
||||||
|
group: this.group,
|
||||||
|
getData: getPanelData,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const value: IValueDisposable<Tab> = { value: tab, disposable };
|
||||||
|
|
||||||
|
this.addTab(value, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(id: string): void {
|
||||||
|
const index = this.indexOf(id);
|
||||||
|
const tabToRemove = this._tabs.splice(index, 1)[0];
|
||||||
|
|
||||||
|
const { value, disposable } = tabToRemove;
|
||||||
|
|
||||||
|
disposable.dispose();
|
||||||
|
value.dispose();
|
||||||
|
value.element.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private addTab(
|
||||||
|
tab: IValueDisposable<Tab>,
|
||||||
|
index: number = this._tabs.length
|
||||||
|
): void {
|
||||||
|
if (index < 0 || index > this._tabs.length) {
|
||||||
|
throw new Error('invalid location');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tabsList.insertBefore(
|
||||||
|
tab.value.element,
|
||||||
|
this._tabsList.children[index]
|
||||||
|
);
|
||||||
|
|
||||||
|
this._tabs = [
|
||||||
|
...this._tabs.slice(0, index),
|
||||||
|
tab,
|
||||||
|
...this._tabs.slice(index),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.selectedIndex < 0) {
|
||||||
|
this.selectedIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleDropdown(options: { reset: boolean }): void {
|
||||||
|
const tabs = options.reset
|
||||||
|
? []
|
||||||
|
: this._tabs
|
||||||
|
.filter(
|
||||||
|
(tab) =>
|
||||||
|
!isChildEntirelyVisibleWithinParent(
|
||||||
|
tab.value.element,
|
||||||
|
this._tabsList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((x) => x.value.panel.id);
|
||||||
|
|
||||||
|
this._onOverflowTabsChange.fire({ tabs, reset: options.reset });
|
||||||
|
}
|
||||||
|
}
|
@ -26,56 +26,7 @@
|
|||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-tabs-container {
|
.dv-right-actions-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-x: overlay;
|
|
||||||
overflow-y: hidden;
|
|
||||||
|
|
||||||
scrollbar-width: thin; // firefox
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
height: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Track */
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handle */
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--dv-tabs-container-scrollbar-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dv-tab {
|
|
||||||
-webkit-user-drag: element;
|
|
||||||
outline: none;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: var(-dv-tab-font-size);
|
|
||||||
margin: var(--dv-tab-margin);
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:nth-last-child(1)) {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:first-child)::before {
|
|
||||||
content: ' ';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 5;
|
|
||||||
pointer-events: none;
|
|
||||||
background-color: var(--dv-tab-divider-color);
|
|
||||||
width: 1px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
import {
|
import {
|
||||||
IDisposable,
|
IDisposable,
|
||||||
CompositeDisposable,
|
CompositeDisposable,
|
||||||
IValueDisposable,
|
Disposable,
|
||||||
|
MutableDisposable,
|
||||||
} from '../../../lifecycle';
|
} from '../../../lifecycle';
|
||||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||||
import { Tab } from '../tab/tab';
|
import { Tab } from '../tab/tab';
|
||||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||||
import { VoidContainer } from './voidContainer';
|
import { VoidContainer } from './voidContainer';
|
||||||
import { toggleClass } from '../../../dom';
|
import { toggleClass } from '../../../dom';
|
||||||
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
import { IDockviewPanel } from '../../dockviewPanel';
|
||||||
import { DockviewComponent } from '../../dockviewComponent';
|
import { DockviewComponent } from '../../dockviewComponent';
|
||||||
import {
|
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
||||||
DockviewGroupPanelModel,
|
|
||||||
WillShowOverlayLocationEvent,
|
|
||||||
} from '../../dockviewGroupPanelModel';
|
|
||||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||||
|
import { Tabs } from './tabs';
|
||||||
|
import {
|
||||||
|
createDropdownElementHandle,
|
||||||
|
DropdownElement,
|
||||||
|
} from './tabOverflowControl';
|
||||||
|
|
||||||
export interface TabDropIndexEvent {
|
export interface TabDropIndexEvent {
|
||||||
readonly event: DragEvent;
|
readonly event: DragEvent;
|
||||||
@ -59,25 +62,28 @@ export class TabsContainer
|
|||||||
implements ITabsContainer
|
implements ITabsContainer
|
||||||
{
|
{
|
||||||
private readonly _element: HTMLElement;
|
private readonly _element: HTMLElement;
|
||||||
private readonly tabContainer: HTMLElement;
|
private readonly tabs: Tabs;
|
||||||
private readonly rightActionsContainer: HTMLElement;
|
private readonly rightActionsContainer: HTMLElement;
|
||||||
private readonly leftActionsContainer: HTMLElement;
|
private readonly leftActionsContainer: HTMLElement;
|
||||||
private readonly preActionsContainer: HTMLElement;
|
private readonly preActionsContainer: HTMLElement;
|
||||||
private readonly voidContainer: VoidContainer;
|
private readonly voidContainer: VoidContainer;
|
||||||
|
|
||||||
private tabs: IValueDisposable<Tab>[] = [];
|
|
||||||
private selectedIndex = -1;
|
|
||||||
private rightActions: HTMLElement | undefined;
|
private rightActions: HTMLElement | undefined;
|
||||||
private leftActions: HTMLElement | undefined;
|
private leftActions: HTMLElement | undefined;
|
||||||
private preActions: HTMLElement | undefined;
|
private preActions: HTMLElement | undefined;
|
||||||
|
|
||||||
private _hidden = false;
|
private _hidden = false;
|
||||||
|
|
||||||
|
private dropdownPart: DropdownElement | null = null;
|
||||||
|
private _overflowTabs: string[] = [];
|
||||||
|
private readonly _dropdownDisposable = new MutableDisposable();
|
||||||
|
|
||||||
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
||||||
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
||||||
|
|
||||||
private readonly _onTabDragStart = new Emitter<TabDragEvent>();
|
get onTabDragStart(): Event<TabDragEvent> {
|
||||||
readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event;
|
return this.tabs.onTabDragStart;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly _onGroupDragStart = new Emitter<GroupDragEvent>();
|
private readonly _onGroupDragStart = new Emitter<GroupDragEvent>();
|
||||||
readonly onGroupDragStart: Event<GroupDragEvent> =
|
readonly onGroupDragStart: Event<GroupDragEvent> =
|
||||||
@ -89,11 +95,11 @@ export class TabsContainer
|
|||||||
this._onWillShowOverlay.event;
|
this._onWillShowOverlay.event;
|
||||||
|
|
||||||
get panels(): string[] {
|
get panels(): string[] {
|
||||||
return this.tabs.map((_) => _.value.panel.id);
|
return this.tabs.panels;
|
||||||
}
|
}
|
||||||
|
|
||||||
get size(): number {
|
get size(): number {
|
||||||
return this.tabs.length;
|
return this.tabs.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hidden(): boolean {
|
get hidden(): boolean {
|
||||||
@ -105,6 +111,116 @@ export class TabsContainer
|
|||||||
this.element.style.display = value ? 'none' : '';
|
this.element.style.display = value ? 'none' : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get element(): HTMLElement {
|
||||||
|
return this._element;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly accessor: DockviewComponent,
|
||||||
|
private readonly group: DockviewGroupPanel
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._element = document.createElement('div');
|
||||||
|
this._element.className = 'dv-tabs-and-actions-container';
|
||||||
|
|
||||||
|
toggleClass(
|
||||||
|
this._element,
|
||||||
|
'dv-full-width-single-tab',
|
||||||
|
this.accessor.options.singleTabMode === 'fullwidth'
|
||||||
|
);
|
||||||
|
|
||||||
|
this.rightActionsContainer = document.createElement('div');
|
||||||
|
this.rightActionsContainer.className = 'dv-right-actions-container';
|
||||||
|
|
||||||
|
this.leftActionsContainer = document.createElement('div');
|
||||||
|
this.leftActionsContainer.className = 'dv-left-actions-container';
|
||||||
|
|
||||||
|
this.preActionsContainer = document.createElement('div');
|
||||||
|
this.preActionsContainer.className = 'dv-pre-actions-container';
|
||||||
|
|
||||||
|
this.tabs = new Tabs(group, accessor, {
|
||||||
|
showTabsOverflowControl: !accessor.options.disableTabsOverflowList,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.voidContainer = new VoidContainer(this.accessor, this.group);
|
||||||
|
|
||||||
|
this._element.appendChild(this.preActionsContainer);
|
||||||
|
this._element.appendChild(this.tabs.element);
|
||||||
|
this._element.appendChild(this.leftActionsContainer);
|
||||||
|
this._element.appendChild(this.voidContainer.element);
|
||||||
|
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,
|
||||||
|
this._onGroupDragStart,
|
||||||
|
this.voidContainer,
|
||||||
|
this.voidContainer.onDragStart((event) => {
|
||||||
|
this._onGroupDragStart.fire({
|
||||||
|
nativeEvent: event,
|
||||||
|
group: this.group,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
this.voidContainer.onDrop((event) => {
|
||||||
|
this._onDrop.fire({
|
||||||
|
event: event.nativeEvent,
|
||||||
|
index: this.tabs.size,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
this.voidContainer.onWillShowOverlay((event) => {
|
||||||
|
this._onWillShowOverlay.fire(
|
||||||
|
new WillShowOverlayLocationEvent(event, {
|
||||||
|
kind: 'header_space',
|
||||||
|
panel: this.group.activePanel,
|
||||||
|
api: this.accessor.api,
|
||||||
|
group: this.group,
|
||||||
|
getData: getPanelData,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
addDisposableListener(
|
||||||
|
this.voidContainer.element,
|
||||||
|
'pointerdown',
|
||||||
|
(event) => {
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFloatingGroupsEnabled =
|
||||||
|
!this.accessor.options.disableFloatingGroups;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isFloatingGroupsEnabled &&
|
||||||
|
event.shiftKey &&
|
||||||
|
this.group.api.location.type !== 'floating'
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const { top, left } =
|
||||||
|
this.element.getBoundingClientRect();
|
||||||
|
const { top: rootTop, left: rootLeft } =
|
||||||
|
this.accessor.element.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.accessor.addFloatingGroup(this.group, {
|
||||||
|
x: left - rootLeft + 20,
|
||||||
|
y: top - rootTop + 20,
|
||||||
|
inDragMode: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
show(): void {
|
show(): void {
|
||||||
if (!this.hidden) {
|
if (!this.hidden) {
|
||||||
this.element.style.display = '';
|
this.element.style.display = '';
|
||||||
@ -157,276 +273,124 @@ export class TabsContainer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get element(): HTMLElement {
|
isActive(tab: Tab): boolean {
|
||||||
return this._element;
|
return this.tabs.isActive(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isActive(tab: Tab): boolean {
|
indexOf(id: string): number {
|
||||||
return (
|
return this.tabs.indexOf(id);
|
||||||
this.selectedIndex > -1 &&
|
|
||||||
this.tabs[this.selectedIndex].value === tab
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public indexOf(id: string): number {
|
setActive(_isGroupActive: boolean) {
|
||||||
return this.tabs.findIndex((tab) => tab.value.panel.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly accessor: DockviewComponent,
|
|
||||||
private readonly group: DockviewGroupPanel
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this._element = document.createElement('div');
|
|
||||||
this._element.className = 'dv-tabs-and-actions-container';
|
|
||||||
|
|
||||||
toggleClass(
|
|
||||||
this._element,
|
|
||||||
'dv-full-width-single-tab',
|
|
||||||
this.accessor.options.singleTabMode === 'fullwidth'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.rightActionsContainer = document.createElement('div');
|
|
||||||
this.rightActionsContainer.className = 'dv-right-actions-container';
|
|
||||||
|
|
||||||
this.leftActionsContainer = document.createElement('div');
|
|
||||||
this.leftActionsContainer.className = 'dv-left-actions-container';
|
|
||||||
|
|
||||||
this.preActionsContainer = document.createElement('div');
|
|
||||||
this.preActionsContainer.className = 'dv-pre-actions-container';
|
|
||||||
|
|
||||||
this.tabContainer = document.createElement('div');
|
|
||||||
this.tabContainer.className = 'dv-tabs-container';
|
|
||||||
|
|
||||||
this.voidContainer = new VoidContainer(this.accessor, this.group);
|
|
||||||
|
|
||||||
this._element.appendChild(this.preActionsContainer);
|
|
||||||
this._element.appendChild(this.tabContainer);
|
|
||||||
this._element.appendChild(this.leftActionsContainer);
|
|
||||||
this._element.appendChild(this.voidContainer.element);
|
|
||||||
this._element.appendChild(this.rightActionsContainer);
|
|
||||||
|
|
||||||
this.addDisposables(
|
|
||||||
this._onWillShowOverlay,
|
|
||||||
this._onDrop,
|
|
||||||
this._onTabDragStart,
|
|
||||||
this._onGroupDragStart,
|
|
||||||
this.voidContainer,
|
|
||||||
this.voidContainer.onDragStart((event) => {
|
|
||||||
this._onGroupDragStart.fire({
|
|
||||||
nativeEvent: event,
|
|
||||||
group: this.group,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
this.voidContainer.onDrop((event) => {
|
|
||||||
this._onDrop.fire({
|
|
||||||
event: event.nativeEvent,
|
|
||||||
index: this.tabs.length,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
this.voidContainer.onWillShowOverlay((event) => {
|
|
||||||
this._onWillShowOverlay.fire(
|
|
||||||
new WillShowOverlayLocationEvent(event, {
|
|
||||||
kind: 'header_space',
|
|
||||||
panel: this.group.activePanel,
|
|
||||||
api: this.accessor.api,
|
|
||||||
group: this.group,
|
|
||||||
getData: getPanelData,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
addDisposableListener(
|
|
||||||
this.voidContainer.element,
|
|
||||||
'pointerdown',
|
|
||||||
(event) => {
|
|
||||||
const isFloatingGroupsEnabled =
|
|
||||||
!this.accessor.options.disableFloatingGroups;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isFloatingGroupsEnabled &&
|
|
||||||
event.shiftKey &&
|
|
||||||
this.group.api.location.type !== 'floating'
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const { top, left } =
|
|
||||||
this.element.getBoundingClientRect();
|
|
||||||
const { top: rootTop, left: rootLeft } =
|
|
||||||
this.accessor.element.getBoundingClientRect();
|
|
||||||
|
|
||||||
this.accessor.addFloatingGroup(this.group, {
|
|
||||||
x: left - rootLeft + 20,
|
|
||||||
y: top - rootTop + 20,
|
|
||||||
inDragMode: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
addDisposableListener(this.tabContainer, 'pointerdown', (event) => {
|
|
||||||
if (event.defaultPrevented) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLeftClick = event.button === 0;
|
|
||||||
|
|
||||||
if (isLeftClick) {
|
|
||||||
this.accessor.doSetGroupActive(this.group);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setActive(_isGroupActive: boolean) {
|
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(id: string): void {
|
delete(id: string): void {
|
||||||
const index = this.tabs.findIndex((tab) => tab.value.panel.id === id);
|
this.tabs.delete(id);
|
||||||
|
|
||||||
const tabToRemove = this.tabs.splice(index, 1)[0];
|
|
||||||
|
|
||||||
if (!tabToRemove) {
|
|
||||||
throw new Error(`dockview: Tab not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { value, disposable } = tabToRemove;
|
|
||||||
|
|
||||||
disposable.dispose();
|
|
||||||
value.dispose();
|
|
||||||
value.element.remove();
|
|
||||||
|
|
||||||
this.updateClassnames();
|
this.updateClassnames();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setActivePanel(panel: IDockviewPanel): void {
|
setActivePanel(panel: IDockviewPanel): void {
|
||||||
this.tabs.forEach((tab) => {
|
this.tabs.setActivePanel(panel);
|
||||||
const isActivePanel = panel.id === tab.value.panel.id;
|
|
||||||
tab.value.setActive(isActivePanel);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openPanel(
|
openPanel(panel: IDockviewPanel, index: number = this.tabs.size): void {
|
||||||
panel: IDockviewPanel,
|
this.tabs.openPanel(panel, index);
|
||||||
index: number = this.tabs.length
|
this.updateClassnames();
|
||||||
): void {
|
|
||||||
if (this.tabs.find((tab) => tab.value.panel.id === panel.id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tab = new Tab(panel, this.accessor, this.group);
|
|
||||||
tab.setContent(panel.view.tab);
|
|
||||||
|
|
||||||
const disposable = new CompositeDisposable(
|
|
||||||
tab.onDragStart((event) => {
|
|
||||||
this._onTabDragStart.fire({ nativeEvent: event, panel });
|
|
||||||
}),
|
|
||||||
tab.onPointerDown((event) => {
|
|
||||||
if (event.defaultPrevented) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFloatingGroupsEnabled =
|
closePanel(panel: IDockviewPanel): void {
|
||||||
!this.accessor.options.disableFloatingGroups;
|
|
||||||
|
|
||||||
const isFloatingWithOnePanel =
|
|
||||||
this.group.api.location.type === 'floating' &&
|
|
||||||
this.size === 1;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isFloatingGroupsEnabled &&
|
|
||||||
!isFloatingWithOnePanel &&
|
|
||||||
event.shiftKey
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const panel = this.accessor.getGroupPanel(tab.panel.id);
|
|
||||||
|
|
||||||
const { top, left } = tab.element.getBoundingClientRect();
|
|
||||||
const { top: rootTop, left: rootLeft } =
|
|
||||||
this.accessor.element.getBoundingClientRect();
|
|
||||||
|
|
||||||
this.accessor.addFloatingGroup(panel as DockviewPanel, {
|
|
||||||
x: left - rootLeft,
|
|
||||||
y: top - rootTop,
|
|
||||||
inDragMode: true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.button) {
|
|
||||||
case 0: // left click or touch
|
|
||||||
if (this.group.activePanel !== panel) {
|
|
||||||
this.group.model.openPanel(panel);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
tab.onDrop((event) => {
|
|
||||||
this._onDrop.fire({
|
|
||||||
event: event.nativeEvent,
|
|
||||||
index: this.tabs.findIndex((x) => x.value === tab),
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
tab.onWillShowOverlay((event) => {
|
|
||||||
this._onWillShowOverlay.fire(
|
|
||||||
new WillShowOverlayLocationEvent(event, {
|
|
||||||
kind: 'tab',
|
|
||||||
panel: this.group.activePanel,
|
|
||||||
api: this.accessor.api,
|
|
||||||
group: this.group,
|
|
||||||
getData: getPanelData,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const value: IValueDisposable<Tab> = { value: tab, disposable };
|
|
||||||
|
|
||||||
this.addTab(value, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public closePanel(panel: IDockviewPanel): void {
|
|
||||||
this.delete(panel.id);
|
this.delete(panel.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
|
||||||
super.dispose();
|
|
||||||
|
|
||||||
for (const { value, disposable } of this.tabs) {
|
|
||||||
disposable.dispose();
|
|
||||||
value.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tabs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private addTab(
|
|
||||||
tab: IValueDisposable<Tab>,
|
|
||||||
index: number = this.tabs.length
|
|
||||||
): void {
|
|
||||||
if (index < 0 || index > this.tabs.length) {
|
|
||||||
throw new Error('invalid location');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tabContainer.insertBefore(
|
|
||||||
tab.value.element,
|
|
||||||
this.tabContainer.children[index]
|
|
||||||
);
|
|
||||||
|
|
||||||
this.tabs = [
|
|
||||||
...this.tabs.slice(0, index),
|
|
||||||
tab,
|
|
||||||
...this.tabs.slice(index),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (this.selectedIndex < 0) {
|
|
||||||
this.selectedIndex = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateClassnames();
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateClassnames(): void {
|
private updateClassnames(): void {
|
||||||
toggleClass(this._element, 'dv-single-tab', this.size === 1);
|
toggleClass(this._element, 'dv-single-tab', this.size === 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toggleDropdown(options: { tabs: string[]; reset: boolean }): void {
|
||||||
|
const tabs = options.reset ? [] : options.tabs;
|
||||||
|
this._overflowTabs = tabs;
|
||||||
|
|
||||||
|
if (this._overflowTabs.length > 0 && this.dropdownPart) {
|
||||||
|
this.dropdownPart.update({ tabs: tabs.length });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._overflowTabs.length === 0) {
|
||||||
|
this._dropdownDisposable.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div');
|
||||||
|
root.className = 'dv-tabs-overflow-dropdown-root';
|
||||||
|
|
||||||
|
const part = createDropdownElementHandle();
|
||||||
|
part.update({ tabs: tabs.length });
|
||||||
|
|
||||||
|
this.dropdownPart = part;
|
||||||
|
|
||||||
|
root.appendChild(part.element);
|
||||||
|
this.rightActionsContainer.prepend(root);
|
||||||
|
|
||||||
|
this._dropdownDisposable.value = new CompositeDisposable(
|
||||||
|
Disposable.from(() => {
|
||||||
|
root.remove();
|
||||||
|
this.dropdownPart?.dispose?.();
|
||||||
|
this.dropdownPart = null;
|
||||||
|
}),
|
||||||
|
addDisposableListener(
|
||||||
|
root,
|
||||||
|
'pointerdown',
|
||||||
|
(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
},
|
||||||
|
{ capture: true }
|
||||||
|
),
|
||||||
|
addDisposableListener(root, 'click', (event) => {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.style.overflow = 'auto';
|
||||||
|
el.className = 'dv-tabs-overflow-container';
|
||||||
|
|
||||||
|
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 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
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
y: event.clientY,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,10 @@
|
|||||||
|
|
||||||
.dv-groupview {
|
.dv-groupview {
|
||||||
&.dv-active-group {
|
&.dv-active-group {
|
||||||
> .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab {
|
> .dv-tabs-and-actions-container
|
||||||
|
> .dv-scrollable
|
||||||
|
> .dv-tabs-container
|
||||||
|
> .dv-tab {
|
||||||
&.dv-active-tab {
|
&.dv-active-tab {
|
||||||
background-color: var(
|
background-color: var(
|
||||||
--dv-activegroup-visiblepanel-tab-background-color
|
--dv-activegroup-visiblepanel-tab-background-color
|
||||||
@ -34,7 +37,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.dv-inactive-group {
|
&.dv-inactive-group {
|
||||||
> .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab {
|
> .dv-tabs-and-actions-container
|
||||||
|
> .dv-scrollable
|
||||||
|
> .dv-tabs-container
|
||||||
|
> .dv-tab {
|
||||||
&.dv-active-tab {
|
&.dv-active-tab {
|
||||||
background-color: var(
|
background-color: var(
|
||||||
--dv-inactivegroup-visiblepanel-tab-background-color
|
--dv-inactivegroup-visiblepanel-tab-background-color
|
||||||
|
@ -75,8 +75,9 @@ import {
|
|||||||
} from '../overlay/overlayRenderContainer';
|
} from '../overlay/overlayRenderContainer';
|
||||||
import { PopoutWindow } from '../popoutWindow';
|
import { PopoutWindow } from '../popoutWindow';
|
||||||
import { StrictEventsSequencing } from './strictEventsSequencing';
|
import { StrictEventsSequencing } from './strictEventsSequencing';
|
||||||
|
import { PopupService } from './components/popupService';
|
||||||
import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer';
|
import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer';
|
||||||
import { DockviewTheme, themeAbyss } from './theme';
|
import { themeAbyss } from './theme';
|
||||||
|
|
||||||
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
|
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
|
||||||
activationSize: { type: 'pixels', value: 10 },
|
activationSize: { type: 'pixels', value: 10 },
|
||||||
@ -263,6 +264,7 @@ export class DockviewComponent
|
|||||||
private readonly _themeClassnames: Classnames;
|
private readonly _themeClassnames: Classnames;
|
||||||
|
|
||||||
readonly overlayRenderContainer: OverlayRenderContainer;
|
readonly overlayRenderContainer: OverlayRenderContainer;
|
||||||
|
readonly popupService: PopupService;
|
||||||
readonly rootDropTargetContainer: DropTargetAnchorContainer;
|
readonly rootDropTargetContainer: DropTargetAnchorContainer;
|
||||||
|
|
||||||
private readonly _onWillDragPanel = new Emitter<TabDragEvent>();
|
private readonly _onWillDragPanel = new Emitter<TabDragEvent>();
|
||||||
@ -328,6 +330,9 @@ export class DockviewComponent
|
|||||||
readonly onDidAddGroup: Event<DockviewGroupPanel> =
|
readonly onDidAddGroup: Event<DockviewGroupPanel> =
|
||||||
this._onDidAddGroup.event;
|
this._onDidAddGroup.event;
|
||||||
|
|
||||||
|
private readonly _onDidOptionsChange = new Emitter<void>();
|
||||||
|
readonly onDidOptionsChange: Event<void> = this._onDidOptionsChange.event;
|
||||||
|
|
||||||
private readonly _onDidActiveGroupChange = new Emitter<
|
private readonly _onDidActiveGroupChange = new Emitter<
|
||||||
DockviewGroupPanel | undefined
|
DockviewGroupPanel | undefined
|
||||||
>();
|
>();
|
||||||
@ -392,6 +397,8 @@ export class DockviewComponent
|
|||||||
className: options.className,
|
className: options.className,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.popupService = new PopupService(this.element);
|
||||||
|
|
||||||
this.updateDropTargetModel(options);
|
this.updateDropTargetModel(options);
|
||||||
|
|
||||||
this._themeClassnames = new Classnames(this.element);
|
this._themeClassnames = new Classnames(this.element);
|
||||||
@ -430,6 +437,7 @@ export class DockviewComponent
|
|||||||
this._onDidActiveGroupChange,
|
this._onDidActiveGroupChange,
|
||||||
this._onUnhandledDragOverEvent,
|
this._onUnhandledDragOverEvent,
|
||||||
this._onDidMaximizedGroupChange,
|
this._onDidMaximizedGroupChange,
|
||||||
|
this._onDidOptionsChange,
|
||||||
this.onDidViewVisibilityChangeMicroTaskQueue(() => {
|
this.onDidViewVisibilityChangeMicroTaskQueue(() => {
|
||||||
this.updateWatermark();
|
this.updateWatermark();
|
||||||
}),
|
}),
|
||||||
|
@ -4,10 +4,10 @@ import {
|
|||||||
IContentRenderer,
|
IContentRenderer,
|
||||||
ITabRenderer,
|
ITabRenderer,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
|
||||||
import { IDisposable } from '../lifecycle';
|
import { IDisposable } from '../lifecycle';
|
||||||
import { IDockviewComponent } from './dockviewComponent';
|
import { IDockviewComponent } from './dockviewComponent';
|
||||||
import { PanelUpdateEvent } from '../panel/types';
|
import { PanelUpdateEvent } from '../panel/types';
|
||||||
|
import { TabLocation } from './framework';
|
||||||
|
|
||||||
export interface IDockviewPanelModel extends IDisposable {
|
export interface IDockviewPanelModel extends IDisposable {
|
||||||
readonly contentComponent: string;
|
readonly contentComponent: string;
|
||||||
@ -17,13 +17,16 @@ export interface IDockviewPanelModel extends IDisposable {
|
|||||||
update(event: PanelUpdateEvent): void;
|
update(event: PanelUpdateEvent): void;
|
||||||
layout(width: number, height: number): void;
|
layout(width: number, height: number): void;
|
||||||
init(params: GroupPanelPartInitParameters): void;
|
init(params: GroupPanelPartInitParameters): void;
|
||||||
updateParentGroup(group: DockviewGroupPanel, isPanelVisible: boolean): void;
|
createTabRenderer(tabLocation: TabLocation): ITabRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DockviewPanelModel implements IDockviewPanelModel {
|
export class DockviewPanelModel implements IDockviewPanelModel {
|
||||||
private readonly _content: IContentRenderer;
|
private readonly _content: IContentRenderer;
|
||||||
private readonly _tab: ITabRenderer;
|
private readonly _tab: ITabRenderer;
|
||||||
|
|
||||||
|
private _params: GroupPanelPartInitParameters | undefined;
|
||||||
|
private _updateEvent: PanelUpdateEvent | undefined;
|
||||||
|
|
||||||
get content(): IContentRenderer {
|
get content(): IContentRenderer {
|
||||||
return this._content;
|
return this._content;
|
||||||
}
|
}
|
||||||
@ -42,16 +45,23 @@ export class DockviewPanelModel implements IDockviewPanelModel {
|
|||||||
this._tab = this.createTabComponent(this.id, tabComponent);
|
this._tab = this.createTabComponent(this.id, tabComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
init(params: GroupPanelPartInitParameters): void {
|
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
|
||||||
this.content.init(params);
|
const cmp = this.createTabComponent(this.id, this.tabComponent);
|
||||||
this.tab.init(params);
|
if (this._params) {
|
||||||
|
cmp.init({ ...this._params, tabLocation });
|
||||||
|
}
|
||||||
|
if (this._updateEvent) {
|
||||||
|
cmp.update?.(this._updateEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateParentGroup(
|
return cmp;
|
||||||
_group: DockviewGroupPanel,
|
}
|
||||||
_isPanelVisible: boolean
|
|
||||||
): void {
|
init(params: GroupPanelPartInitParameters): void {
|
||||||
// noop
|
this._params = params;
|
||||||
|
|
||||||
|
this.content.init(params);
|
||||||
|
this.tab.init({ ...params, tabLocation: 'header' });
|
||||||
}
|
}
|
||||||
|
|
||||||
layout(width: number, height: number): void {
|
layout(width: number, height: number): void {
|
||||||
@ -59,6 +69,8 @@ export class DockviewPanelModel implements IDockviewPanelModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(event: PanelUpdateEvent): void {
|
update(event: PanelUpdateEvent): void {
|
||||||
|
this._updateEvent = event;
|
||||||
|
|
||||||
this.content.update?.(event);
|
this.content.update?.(event);
|
||||||
this.tab.update?.(event);
|
this.tab.update?.(event);
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,11 @@ export interface IGroupPanelBaseProps<T extends { [index: string]: any } = any>
|
|||||||
containerApi: DockviewApi;
|
containerApi: DockviewApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TabLocation = 'header' | 'headerOverflow';
|
||||||
|
|
||||||
export type IDockviewPanelHeaderProps<
|
export type IDockviewPanelHeaderProps<
|
||||||
T extends { [index: string]: any } = any
|
T extends { [index: string]: any } = any
|
||||||
> = IGroupPanelBaseProps<T>;
|
> = IGroupPanelBaseProps<T> & { tabLocation: TabLocation };
|
||||||
|
|
||||||
export type IDockviewPanelProps<T extends { [index: string]: any } = any> =
|
export type IDockviewPanelProps<T extends { [index: string]: any } = any> =
|
||||||
IGroupPanelBaseProps<T>;
|
IGroupPanelBaseProps<T>;
|
||||||
|
@ -72,6 +72,7 @@ export interface DockviewOptions {
|
|||||||
*/
|
*/
|
||||||
noPanelsOverlay?: 'emptyGroup' | 'watermark';
|
noPanelsOverlay?: 'emptyGroup' | 'watermark';
|
||||||
theme?: DockviewTheme;
|
theme?: DockviewTheme;
|
||||||
|
disableTabsOverflowList?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DockviewDndOverlayEvent extends IAcceptableEvent {
|
export interface DockviewDndOverlayEvent extends IAcceptableEvent {
|
||||||
@ -119,6 +120,7 @@ export const PROPERTY_KEYS_DOCKVIEW: (keyof DockviewOptions)[] = (() => {
|
|||||||
dndEdges: undefined,
|
dndEdges: undefined,
|
||||||
theme: undefined,
|
theme: undefined,
|
||||||
gap: undefined,
|
gap: undefined,
|
||||||
|
disableTabsOverflowList: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Object.keys(properties) as (keyof DockviewOptions)[];
|
return Object.keys(properties) as (keyof DockviewOptions)[];
|
||||||
|
@ -4,6 +4,7 @@ import { DockviewApi } from '../api/component.api';
|
|||||||
import { Optional } from '../types';
|
import { Optional } from '../types';
|
||||||
import { IDockviewGroupPanel } from './dockviewGroupPanel';
|
import { IDockviewGroupPanel } from './dockviewGroupPanel';
|
||||||
import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
|
import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
|
||||||
|
import { TabLocation } from './framework';
|
||||||
|
|
||||||
export interface HeaderPartInitParameters {
|
export interface HeaderPartInitParameters {
|
||||||
title: string;
|
title: string;
|
||||||
@ -34,10 +35,14 @@ export interface IWatermarkRenderer
|
|||||||
init: (params: WatermarkRendererInitParameters) => void;
|
init: (params: WatermarkRendererInitParameters) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TabPartInitParameters extends GroupPanelPartInitParameters {
|
||||||
|
tabLocation: TabLocation;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ITabRenderer
|
export interface ITabRenderer
|
||||||
extends Optional<Omit<IPanel, 'id'>, RendererMethodOptionalList> {
|
extends Optional<Omit<IPanel, 'id'>, RendererMethodOptionalList> {
|
||||||
readonly element: HTMLElement;
|
readonly element: HTMLElement;
|
||||||
init(parameters: GroupPanelPartInitParameters): void;
|
init(parameters: TabPartInitParameters): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IContentRenderer
|
export interface IContentRenderer
|
||||||
|
@ -357,3 +357,25 @@ export class Classnames {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isChildEntirelyVisibleWithinParent(
|
||||||
|
child: HTMLElement,
|
||||||
|
parent: HTMLElement
|
||||||
|
): boolean {
|
||||||
|
//
|
||||||
|
const childPosition = getDomNodePagePosition(child);
|
||||||
|
const parentPosition = getDomNodePagePosition(parent);
|
||||||
|
|
||||||
|
if (childPosition.left < parentPosition.left) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
childPosition.left + childPosition.width >
|
||||||
|
parentPosition.left + parentPosition.width
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
@ -11,6 +11,9 @@ import { Classnames } from '../dom';
|
|||||||
|
|
||||||
const nextLayoutId = sequentialNumberGenerator();
|
const nextLayoutId = sequentialNumberGenerator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A direction in which a panel can be moved or placed relative to another panel.
|
||||||
|
*/
|
||||||
export type Direction = 'left' | 'right' | 'above' | 'below' | 'within';
|
export type Direction = 'left' | 'right' | 'above' | 'below' | 'within';
|
||||||
|
|
||||||
export function toTarget(direction: Direction): Position {
|
export function toTarget(direction: Direction): Position {
|
||||||
|
28
packages/dockview-core/src/scrollbar.scss
Normal file
28
packages/dockview-core/src/scrollbar.scss
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
.dv-scrollable {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.dv-scrollbar-horizontal {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: transparent;
|
||||||
|
transition-property: background-color;
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
transition-duration: 1s;
|
||||||
|
transition-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.dv-scrollable-resizing,
|
||||||
|
&.dv-scrollable-scrolling {
|
||||||
|
.dv-scrollbar-horizontal {
|
||||||
|
background-color: var(
|
||||||
|
--dv-scrollbar-background-color,
|
||||||
|
rgba(255, 255, 255, 0.25)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
packages/dockview-core/src/scrollbar.ts
Normal file
131
packages/dockview-core/src/scrollbar.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { toggleClass, watchElementResize } from './dom';
|
||||||
|
import { addDisposableListener } from './events';
|
||||||
|
import { CompositeDisposable } from './lifecycle';
|
||||||
|
import { clamp } from './math';
|
||||||
|
|
||||||
|
export class Scrollbar extends CompositeDisposable {
|
||||||
|
private readonly _element: HTMLElement;
|
||||||
|
private readonly _horizontalScrollbar: HTMLElement;
|
||||||
|
private _scrollLeft: number = 0;
|
||||||
|
private _animationTimer: any;
|
||||||
|
public static MouseWheelSpeed = 1;
|
||||||
|
|
||||||
|
get element(): HTMLElement {
|
||||||
|
return this._element;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly scrollableElement: HTMLElement) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._element = document.createElement('div');
|
||||||
|
this._element.className = 'dv-scrollable';
|
||||||
|
|
||||||
|
this._horizontalScrollbar = document.createElement('div');
|
||||||
|
this._horizontalScrollbar.className = 'dv-scrollbar-horizontal';
|
||||||
|
|
||||||
|
this.element.appendChild(scrollableElement);
|
||||||
|
this.element.appendChild(this._horizontalScrollbar);
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
addDisposableListener(this.element, 'wheel', (event) => {
|
||||||
|
this._scrollLeft += event.deltaY * Scrollbar.MouseWheelSpeed;
|
||||||
|
|
||||||
|
this.calculateScrollbarStyles();
|
||||||
|
}),
|
||||||
|
addDisposableListener(
|
||||||
|
this._horizontalScrollbar,
|
||||||
|
'pointerdown',
|
||||||
|
(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
toggleClass(this.element, 'dv-scrollable-scrolling', true);
|
||||||
|
|
||||||
|
const originalClientX = event.clientX;
|
||||||
|
const originalScrollLeft = this._scrollLeft;
|
||||||
|
|
||||||
|
const onPointerMove = (event: PointerEvent) => {
|
||||||
|
const deltaX = event.clientX - originalClientX;
|
||||||
|
|
||||||
|
const { clientWidth } = this.element;
|
||||||
|
const { scrollWidth } = this.scrollableElement;
|
||||||
|
const p = clientWidth / scrollWidth;
|
||||||
|
|
||||||
|
this._scrollLeft = originalScrollLeft + deltaX / p;
|
||||||
|
this.calculateScrollbarStyles();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEnd = () => {
|
||||||
|
toggleClass(
|
||||||
|
this.element,
|
||||||
|
'dv-scrollable-scrolling',
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
document.removeEventListener(
|
||||||
|
'pointermove',
|
||||||
|
onPointerMove
|
||||||
|
);
|
||||||
|
document.removeEventListener('pointerup', onEnd);
|
||||||
|
document.removeEventListener('pointercancel', onEnd);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('pointermove', onPointerMove);
|
||||||
|
document.addEventListener('pointerup', onEnd);
|
||||||
|
document.addEventListener('pointercancel', onEnd);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
addDisposableListener(this.element, 'scroll', () => {
|
||||||
|
this.calculateScrollbarStyles();
|
||||||
|
}),
|
||||||
|
addDisposableListener(this.scrollableElement, 'scroll', () => {
|
||||||
|
this._scrollLeft = this.scrollableElement.scrollLeft;
|
||||||
|
this.calculateScrollbarStyles();
|
||||||
|
}),
|
||||||
|
watchElementResize(this.element, () => {
|
||||||
|
toggleClass(this.element, 'dv-scrollable-resizing', true);
|
||||||
|
|
||||||
|
if (this._animationTimer) {
|
||||||
|
clearTimeout(this._animationTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._animationTimer = setTimeout(() => {
|
||||||
|
clearTimeout(this._animationTimer);
|
||||||
|
toggleClass(this.element, 'dv-scrollable-resizing', false);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
this.calculateScrollbarStyles();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateScrollbarStyles(): void {
|
||||||
|
const { clientWidth } = this.element;
|
||||||
|
const { scrollWidth } = this.scrollableElement;
|
||||||
|
|
||||||
|
const hasScrollbar = scrollWidth > clientWidth;
|
||||||
|
|
||||||
|
if (hasScrollbar) {
|
||||||
|
const px = clientWidth * (clientWidth / scrollWidth);
|
||||||
|
this._horizontalScrollbar.style.width = `${px}px`;
|
||||||
|
|
||||||
|
this._scrollLeft = clamp(
|
||||||
|
this._scrollLeft,
|
||||||
|
0,
|
||||||
|
this.scrollableElement.scrollWidth - clientWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
this.scrollableElement.scrollLeft = this._scrollLeft;
|
||||||
|
|
||||||
|
const percentageComplete =
|
||||||
|
this._scrollLeft / (scrollWidth - clientWidth);
|
||||||
|
|
||||||
|
this._horizontalScrollbar.style.left = `${
|
||||||
|
(clientWidth - px) * percentageComplete
|
||||||
|
}px`;
|
||||||
|
} else {
|
||||||
|
this._horizontalScrollbar.style.width = `0px`;
|
||||||
|
this._horizontalScrollbar.style.left = `0px`;
|
||||||
|
this._scrollLeft = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,7 @@
|
|||||||
--dv-tab-margin: 0.5rem 0.25rem;
|
--dv-tab-margin: 0.5rem 0.25rem;
|
||||||
--dv-tabs-and-actions-container-height: 44px;
|
--dv-tabs-and-actions-container-height: 44px;
|
||||||
|
|
||||||
|
--dv-border-radius: 20px;
|
||||||
--dv-border-radius
|
|
||||||
|
|
||||||
.dv-resize-container:has(> .dv-groupview) {
|
.dv-resize-container:has(> .dv-groupview) {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -27,11 +26,11 @@
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-groupview {
|
.dv-tabs-overflow-container,
|
||||||
border-radius: var(--dv-border-radius);
|
.dv-tabs-overflow-dropdown-default {
|
||||||
|
border-radius: 8px;
|
||||||
.dv-tabs-and-actions-container {
|
height: unset !important;
|
||||||
padding: 0px calc(var(--dv-border-radius) / 2);
|
}
|
||||||
|
|
||||||
.dv-tab {
|
.dv-tab {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@ -41,6 +40,12 @@
|
|||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dv-groupview {
|
||||||
|
border-radius: var(--dv-border-radius);
|
||||||
|
|
||||||
|
.dv-tabs-and-actions-container {
|
||||||
|
padding: 0px calc(var(--dv-border-radius) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-content-container {
|
.dv-content-container {
|
||||||
|
@ -2,7 +2,6 @@ import type {
|
|||||||
DockviewApi,
|
DockviewApi,
|
||||||
DockviewGroupPanel,
|
DockviewGroupPanel,
|
||||||
DockviewPanelApi,
|
DockviewPanelApi,
|
||||||
GroupPanelPartInitParameters,
|
|
||||||
IContentRenderer,
|
IContentRenderer,
|
||||||
IDockviewPanelHeaderProps,
|
IDockviewPanelHeaderProps,
|
||||||
IGroupHeaderProps,
|
IGroupHeaderProps,
|
||||||
@ -12,6 +11,7 @@ import type {
|
|||||||
IWatermarkRenderer,
|
IWatermarkRenderer,
|
||||||
PanelUpdateEvent,
|
PanelUpdateEvent,
|
||||||
Parameters,
|
Parameters,
|
||||||
|
TabPartInitParameters,
|
||||||
WatermarkRendererInitParameters,
|
WatermarkRendererInitParameters,
|
||||||
} from 'dockview-core';
|
} from 'dockview-core';
|
||||||
import {
|
import {
|
||||||
@ -121,7 +121,7 @@ export class VueRenderer
|
|||||||
private _api: DockviewPanelApi | undefined;
|
private _api: DockviewPanelApi | undefined;
|
||||||
private _containerApi: DockviewApi | undefined;
|
private _containerApi: DockviewApi | undefined;
|
||||||
|
|
||||||
init(parameters: GroupPanelPartInitParameters): void {
|
init(parameters: TabPartInitParameters): void {
|
||||||
this._api = parameters.api;
|
this._api = parameters.api;
|
||||||
this._containerApi = parameters.containerApi;
|
this._containerApi = parameters.containerApi;
|
||||||
|
|
||||||
@ -129,6 +129,7 @@ export class VueRenderer
|
|||||||
params: parameters.params,
|
params: parameters.params,
|
||||||
api: parameters.api,
|
api: parameters.api,
|
||||||
containerApi: parameters.containerApi,
|
containerApi: parameters.containerApi,
|
||||||
|
tabLocation: parameters.tabLocation,
|
||||||
};
|
};
|
||||||
|
|
||||||
this._renderDisposable?.dispose();
|
this._renderDisposable?.dispose();
|
||||||
|
@ -19,6 +19,7 @@ describe('defaultTab', () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<DockviewDefaultTab
|
<DockviewDefaultTab
|
||||||
|
tabLocation="header"
|
||||||
api={api}
|
api={api}
|
||||||
containerApi={containerApi}
|
containerApi={containerApi}
|
||||||
params={params}
|
params={params}
|
||||||
@ -41,6 +42,7 @@ describe('defaultTab', () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<DockviewDefaultTab
|
<DockviewDefaultTab
|
||||||
|
tabLocation="header"
|
||||||
api={api}
|
api={api}
|
||||||
containerApi={containerApi}
|
containerApi={containerApi}
|
||||||
params={params}
|
params={params}
|
||||||
@ -65,6 +67,7 @@ describe('defaultTab', () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<DockviewDefaultTab
|
<DockviewDefaultTab
|
||||||
|
tabLocation="header"
|
||||||
api={api}
|
api={api}
|
||||||
containerApi={containerApi}
|
containerApi={containerApi}
|
||||||
params={params}
|
params={params}
|
||||||
@ -97,6 +100,7 @@ describe('defaultTab', () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<DockviewDefaultTab
|
<DockviewDefaultTab
|
||||||
|
tabLocation="header"
|
||||||
api={api}
|
api={api}
|
||||||
containerApi={containerApi}
|
containerApi={containerApi}
|
||||||
params={params}
|
params={params}
|
||||||
@ -122,6 +126,7 @@ describe('defaultTab', () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<DockviewDefaultTab
|
<DockviewDefaultTab
|
||||||
|
tabLocation="header"
|
||||||
api={api}
|
api={api}
|
||||||
containerApi={containerApi}
|
containerApi={containerApi}
|
||||||
params={params}
|
params={params}
|
||||||
@ -151,6 +156,7 @@ describe('defaultTab', () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<DockviewDefaultTab
|
<DockviewDefaultTab
|
||||||
|
tabLocation="header"
|
||||||
api={api}
|
api={api}
|
||||||
containerApi={containerApi}
|
containerApi={containerApi}
|
||||||
params={params}
|
params={params}
|
||||||
@ -177,6 +183,7 @@ describe('defaultTab', () => {
|
|||||||
|
|
||||||
render(
|
render(
|
||||||
<DockviewDefaultTab
|
<DockviewDefaultTab
|
||||||
|
tabLocation="header"
|
||||||
api={api}
|
api={api}
|
||||||
containerApi={containerApi}
|
containerApi={containerApi}
|
||||||
params={params}
|
params={params}
|
||||||
|
@ -35,6 +35,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
|
|||||||
onPointerDown,
|
onPointerDown,
|
||||||
onPointerUp,
|
onPointerUp,
|
||||||
onPointerLeave,
|
onPointerLeave,
|
||||||
|
tabLocation,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const title = useTitle(api);
|
const title = useTitle(api);
|
||||||
@ -96,7 +97,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
|
|||||||
className="dv-default-tab"
|
className="dv-default-tab"
|
||||||
>
|
>
|
||||||
<span className="dv-default-tab-content">{title}</span>
|
<span className="dv-default-tab-content">{title}</span>
|
||||||
{!hideClose && (
|
{!hideClose && tabLocation !== 'headerOverflow' && (
|
||||||
<div
|
<div
|
||||||
className="dv-default-tab-action"
|
className="dv-default-tab-action"
|
||||||
onPointerDown={onBtnPointerDown}
|
onPointerDown={onBtnPointerDown}
|
||||||
|
@ -3,13 +3,13 @@ import { ReactPart, ReactPortalStore } from '../react';
|
|||||||
import {
|
import {
|
||||||
PanelUpdateEvent,
|
PanelUpdateEvent,
|
||||||
ITabRenderer,
|
ITabRenderer,
|
||||||
GroupPanelPartInitParameters,
|
TabPartInitParameters,
|
||||||
IGroupPanelBaseProps,
|
IDockviewPanelHeaderProps,
|
||||||
} from 'dockview-core';
|
} from 'dockview-core';
|
||||||
|
|
||||||
export class ReactPanelHeaderPart implements ITabRenderer {
|
export class ReactPanelHeaderPart implements ITabRenderer {
|
||||||
private readonly _element: HTMLElement;
|
private readonly _element: HTMLElement;
|
||||||
private part?: ReactPart<IGroupPanelBaseProps>;
|
private part?: ReactPart<IDockviewPanelHeaderProps>;
|
||||||
|
|
||||||
get element(): HTMLElement {
|
get element(): HTMLElement {
|
||||||
return this._element;
|
return this._element;
|
||||||
@ -17,7 +17,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly id: string,
|
public readonly id: string,
|
||||||
private readonly component: React.FunctionComponent<IGroupPanelBaseProps>,
|
private readonly component: React.FunctionComponent<IDockviewPanelHeaderProps>,
|
||||||
private readonly reactPortalStore: ReactPortalStore
|
private readonly reactPortalStore: ReactPortalStore
|
||||||
) {
|
) {
|
||||||
this._element = document.createElement('div');
|
this._element = document.createElement('div');
|
||||||
@ -30,7 +30,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
|
|||||||
//noop
|
//noop
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(parameters: GroupPanelPartInitParameters): void {
|
public init(parameters: TabPartInitParameters): void {
|
||||||
this.part = new ReactPart(
|
this.part = new ReactPart(
|
||||||
this.element,
|
this.element,
|
||||||
this.reactPortalStore,
|
this.reactPortalStore,
|
||||||
@ -39,6 +39,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
|
|||||||
params: parameters.params,
|
params: parameters.params,
|
||||||
api: parameters.api,
|
api: parameters.api,
|
||||||
containerApi: parameters.containerApi,
|
containerApi: parameters.containerApi,
|
||||||
|
tabLocation: parameters.tabLocation,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user