mirror of
https://github.com/mathuo/dockview
synced 2025-08-26 03:56:44 +00:00
Merge branch 'master' of https://github.com/mathuo/dockview into 774-drag-and-drop-not-working-for-iframe-panels-within-a-shadow-dom
This commit is contained in:
commit
d168356344
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
- run: npm run build
|
||||
- run: npm run test:cov
|
||||
- name: SonarCloud Scan
|
||||
uses: sonarsource/sonarqube-scan-action@v4.1.0
|
||||
uses: sonarsource/sonarqube-scan-action@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
@ -7,12 +7,12 @@
|
||||
|
||||
---
|
||||
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://www.npmjs.com/package/dockview)
|
||||
[](https://www.npmjs.com/package/dockview-core)
|
||||
[](https://www.npmjs.com/package/dockview-core)
|
||||
[](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
|
||||
[](https://bundlephobia.com/result?p=dockview)
|
||||
[](https://bundlephobia.com/result?p=dockview-core)
|
||||
|
||||
##
|
||||
|
||||
|
8
SECURITY.md
Normal file
8
SECURITY.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
- Dockview is an entirely open source project.
|
||||
- All build and publication scripts use public Github Action files found [here](https://github.com/mathuo/dockview/tree/master/.github/workflows).
|
||||
- All npm publications are verified through the use of [provenance statements](https://docs.npmjs.com/generating-provenance-statements/).
|
||||
- All builds are scanned with SonarCube and outputs can be found [here](https://sonarcloud.io/summary/overall?id=mathuo_dockview).
|
||||
|
||||
If you believe you have found a security or vulnerability issue please send a complete example to github.mathuo@gmail.com where it will be investigated.
|
@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "2.0.0",
|
||||
"version": "4.0.0",
|
||||
"npmClient": "yarn",
|
||||
"command": {
|
||||
"publish": {
|
||||
|
@ -77,8 +77,5 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ag-grid-vue3": "^31.1.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-angular",
|
||||
"version": "2.0.0",
|
||||
"version": "4.0.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -54,6 +54,6 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^2.0.0"
|
||||
"dockview-core": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-core",
|
||||
"version": "2.0.0",
|
||||
"version": "4.0.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import {
|
||||
GroupPanelPartInitParameters,
|
||||
TabPartInitParameters,
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
} from '../../dockview/types';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { TabLocation } from '../../dockview/framework';
|
||||
|
||||
export class DockviewPanelModelMock implements IDockviewPanelModel {
|
||||
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(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const panelMock = jest.fn<DockviewPanel, []>(() => {
|
||||
@ -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(
|
||||
|
@ -16,10 +16,10 @@ describe('droptarget', () => {
|
||||
beforeEach(() => {
|
||||
element = document.createElement('div');
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 200);
|
||||
});
|
||||
|
||||
test('that dragover events are marked', () => {
|
||||
|
@ -8,6 +8,7 @@ import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel';
|
||||
import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel';
|
||||
import { Tab } from '../../../dockview/components/tab/tab';
|
||||
import { IDockviewPanel } from '../../../dockview/dockviewPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
|
||||
describe('tab', () => {
|
||||
test('that empty tab has inactive-tab class', () => {
|
||||
@ -46,15 +47,10 @@ describe('tab', () => {
|
||||
id: 'testcomponentid',
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -72,38 +68,33 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
fireEvent.dragEnter(cut.element);
|
||||
fireEvent.dragOver(cut.element);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toBeCalled();
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that if you drag over yourself no drop target is shown', () => {
|
||||
test('that if you drag over yourself a drop target is shown', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -121,10 +112,10 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -136,11 +127,11 @@ describe('tab', () => {
|
||||
fireEvent.dragEnter(cut.element);
|
||||
fireEvent.dragOver(cut.element);
|
||||
|
||||
expect(groupView.canDisplayOverlay).toBeCalledTimes(0);
|
||||
expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that if you drag over another tab a drop target is shown', () => {
|
||||
@ -175,10 +166,10 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -229,10 +220,10 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -289,10 +280,10 @@ describe('tab', () => {
|
||||
groupPanel
|
||||
);
|
||||
|
||||
jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { fireEvent } from '@testing-library/dom';
|
||||
import { TestPanel } from '../../dockviewGroupPanelModel.spec';
|
||||
import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { DockviewPanelApi } from '../../../../api/dockviewPanelApi';
|
||||
|
||||
describe('tabsContainer', () => {
|
||||
test('that an external event does not render a drop target and calls through to the group mode', () => {
|
||||
@ -17,6 +18,7 @@ describe('tabsContainer', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -41,16 +43,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -70,17 +72,17 @@ describe('tabsContainer', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
const dropTargetContainer = document.createElement('div');
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
// dropTargetContainer: new DropTargetAnchorContainer(
|
||||
// dropTargetContainer
|
||||
// ),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -96,16 +98,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -128,6 +130,10 @@ describe('tabsContainer', () => {
|
||||
expect(
|
||||
cut.element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
// expect(
|
||||
// dropTargetContainer.getElementsByClassName('dv-drop-target-anchor')
|
||||
// .length
|
||||
// ).toBe(1);
|
||||
});
|
||||
|
||||
test('that dropping over the empty space should render a drop target', () => {
|
||||
@ -136,6 +142,7 @@ describe('tabsContainer', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -165,16 +172,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -199,6 +206,7 @@ describe('tabsContainer', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -228,16 +236,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -262,6 +270,7 @@ describe('tabsContainer', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -290,16 +299,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('dv-void-container')
|
||||
.item(0);
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
if (!emptySpace!) {
|
||||
fail('element not found');
|
||||
}
|
||||
|
||||
jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation(
|
||||
jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
|
||||
@ -330,6 +339,7 @@ describe('tabsContainer', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
@ -394,6 +404,7 @@ describe('tabsContainer', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
@ -460,6 +471,7 @@ describe('tabsContainer', () => {
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
@ -516,6 +528,7 @@ describe('tabsContainer', () => {
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
@ -567,6 +580,7 @@ describe('tabsContainer', () => {
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
@ -623,6 +637,7 @@ describe('tabsContainer', () => {
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
@ -690,6 +705,7 @@ describe('tabsContainer', () => {
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
@ -757,6 +773,7 @@ describe('tabsContainer', () => {
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
@ -815,4 +832,36 @@ describe('tabsContainer', () => {
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('class dv-single-tab is present when only one tab exists`', () => {
|
||||
const cut = new TabsContainer(
|
||||
fromPartial<DockviewComponent>({
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
}),
|
||||
fromPartial<DockviewGroupPanel>({})
|
||||
);
|
||||
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy();
|
||||
|
||||
const panel1 = new TestPanel(
|
||||
'panel_1',
|
||||
fromPartial<DockviewPanelApi>({})
|
||||
);
|
||||
cut.openPanel(panel1);
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy();
|
||||
|
||||
const panel2 = new TestPanel(
|
||||
'panel_2',
|
||||
fromPartial<DockviewPanelApi>({})
|
||||
);
|
||||
cut.openPanel(panel2);
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy();
|
||||
|
||||
cut.closePanel(panel1);
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy();
|
||||
|
||||
cut.closePanel(panel2);
|
||||
expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@ -133,116 +133,125 @@ describe('dockviewComponent', () => {
|
||||
},
|
||||
className: 'test-a test-b',
|
||||
});
|
||||
expect(dockview.element.className).toBe('test-a test-b');
|
||||
expect(dockview.element.className).toBe(
|
||||
'test-a test-b dockview-theme-abyss'
|
||||
);
|
||||
|
||||
dockview.updateOptions({ className: 'test-b test-c' });
|
||||
|
||||
expect(dockview.element.className).toBe('test-b test-c');
|
||||
expect(dockview.element.className).toBe(
|
||||
'dockview-theme-abyss test-b test-c'
|
||||
);
|
||||
});
|
||||
|
||||
// describe('memory leakage', () => {
|
||||
// beforeEach(() => {
|
||||
// window.open = () => fromPartial<Window>({
|
||||
// addEventListener: jest.fn(),
|
||||
// close: jest.fn(),
|
||||
// });
|
||||
// });
|
||||
describe('memory leakage', () => {
|
||||
beforeEach(() => {
|
||||
window.open = () => setupMockWindow();
|
||||
});
|
||||
|
||||
// test('event leakage', () => {
|
||||
// Emitter.setLeakageMonitorEnabled(true);
|
||||
test('event leakage', async () => {
|
||||
Emitter.setLeakageMonitorEnabled(true);
|
||||
|
||||
// dockview = new DockviewComponent({
|
||||
// parentElement: container,
|
||||
// components: {
|
||||
// default: PanelContentPartTest,
|
||||
// },
|
||||
// });
|
||||
dockview = new DockviewComponent(container, {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
className: 'test-a test-b',
|
||||
});
|
||||
|
||||
// dockview.layout(500, 1000);
|
||||
dockview.layout(500, 1000);
|
||||
|
||||
// const panel1 = dockview.addPanel({
|
||||
// id: 'panel1',
|
||||
// component: 'default',
|
||||
// });
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
// const panel2 = dockview.addPanel({
|
||||
// id: 'panel2',
|
||||
// component: 'default',
|
||||
// });
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
// dockview.removePanel(panel2);
|
||||
dockview.removePanel(panel2);
|
||||
|
||||
// const panel3 = dockview.addPanel({
|
||||
// id: 'panel3',
|
||||
// component: 'default',
|
||||
// position: {
|
||||
// direction: 'right',
|
||||
// referencePanel: 'panel1',
|
||||
// },
|
||||
// });
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'right',
|
||||
referencePanel: 'panel1',
|
||||
},
|
||||
});
|
||||
|
||||
// const panel4 = dockview.addPanel({
|
||||
// id: 'panel4',
|
||||
// component: 'default',
|
||||
// position: {
|
||||
// direction: 'above',
|
||||
// },
|
||||
// });
|
||||
const panel4 = dockview.addPanel({
|
||||
id: 'panel4',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'above',
|
||||
},
|
||||
});
|
||||
|
||||
// dockview.moveGroupOrPanel(
|
||||
// panel4.group,
|
||||
// panel3.group.id,
|
||||
// panel3.id,
|
||||
// 'center'
|
||||
// );
|
||||
panel4.api.group.api.moveTo({
|
||||
group: panel3.api.group,
|
||||
position: 'center',
|
||||
});
|
||||
|
||||
// dockview.addPanel({
|
||||
// id: 'panel5',
|
||||
// component: 'default',
|
||||
// floating: true,
|
||||
// });
|
||||
dockview.addPanel({
|
||||
id: 'panel5',
|
||||
component: 'default',
|
||||
floating: true,
|
||||
});
|
||||
|
||||
// const panel6 = dockview.addPanel({
|
||||
// id: 'panel6',
|
||||
// component: 'default',
|
||||
// position: {
|
||||
// referencePanel: 'panel5',
|
||||
// direction: 'within',
|
||||
// },
|
||||
// });
|
||||
const panel6 = dockview.addPanel({
|
||||
id: 'panel6',
|
||||
component: 'default',
|
||||
position: {
|
||||
referencePanel: 'panel5',
|
||||
direction: 'within',
|
||||
},
|
||||
});
|
||||
|
||||
// dockview.addFloatingGroup(panel4.api.group);
|
||||
dockview.addFloatingGroup(panel4.api.group);
|
||||
|
||||
// dockview.addPopoutGroup(panel6);
|
||||
await dockview.addPopoutGroup(panel2);
|
||||
|
||||
// dockview.moveGroupOrPanel(
|
||||
// panel1.group,
|
||||
// panel6.group.id,
|
||||
// panel6.id,
|
||||
// 'center'
|
||||
// );
|
||||
panel1.api.group.api.moveTo({
|
||||
group: panel6.api.group,
|
||||
position: 'center',
|
||||
});
|
||||
|
||||
// dockview.moveGroupOrPanel(
|
||||
// panel4.group,
|
||||
// panel6.group.id,
|
||||
// panel6.id,
|
||||
// 'center'
|
||||
// );
|
||||
panel4.api.group.api.moveTo({
|
||||
group: panel6.api.group,
|
||||
position: 'center',
|
||||
});
|
||||
|
||||
// dockview.dispose();
|
||||
dockview.dispose();
|
||||
|
||||
// if (Emitter.MEMORY_LEAK_WATCHER.size > 0) {
|
||||
// for (const entry of Array.from(
|
||||
// Emitter.MEMORY_LEAK_WATCHER.events
|
||||
// )) {
|
||||
// console.log('disposal', entry[1]);
|
||||
// }
|
||||
// throw new Error('not all listeners disposed');
|
||||
// }
|
||||
if (Emitter.MEMORY_LEAK_WATCHER.size > 0) {
|
||||
console.warn(
|
||||
`${Emitter.MEMORY_LEAK_WATCHER.size} undisposed resources`
|
||||
);
|
||||
|
||||
// Emitter.setLeakageMonitorEnabled(false);
|
||||
// });
|
||||
// });
|
||||
for (const entry of Array.from(
|
||||
Emitter.MEMORY_LEAK_WATCHER.events
|
||||
)) {
|
||||
console.log('disposal', entry[1]);
|
||||
}
|
||||
throw new Error(
|
||||
`${Emitter.MEMORY_LEAK_WATCHER.size} undisposed resources`
|
||||
);
|
||||
}
|
||||
|
||||
Emitter.setLeakageMonitorEnabled(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('duplicate panel', () => {
|
||||
dockview.layout(500, 1000);
|
||||
@ -1097,7 +1106,9 @@ describe('dockviewComponent', () => {
|
||||
disposable.dispose();
|
||||
});
|
||||
|
||||
test('events flow', () => {
|
||||
test('events flow', async () => {
|
||||
window.open = () => setupMockWindow();
|
||||
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
let events: {
|
||||
@ -1290,7 +1301,42 @@ describe('dockviewComponent', () => {
|
||||
expect(dockview.size).toBe(0);
|
||||
expect(dockview.totalPanels).toBe(0);
|
||||
|
||||
events = [];
|
||||
|
||||
const panel8 = dockview.addPanel({
|
||||
id: 'panel8',
|
||||
component: 'default',
|
||||
});
|
||||
const panel9 = dockview.addPanel({
|
||||
id: 'panel9',
|
||||
component: 'default',
|
||||
floating: true,
|
||||
});
|
||||
const panel10 = dockview.addPanel({
|
||||
id: 'panel10',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(await dockview.addPopoutGroup(panel10)).toBeTruthy();
|
||||
|
||||
expect(events).toEqual([
|
||||
{ type: 'ADD_GROUP', group: panel8.group },
|
||||
{ type: 'ADD_PANEL', panel: panel8 },
|
||||
{ type: 'ACTIVE_GROUP', group: panel8.group },
|
||||
{ type: 'ACTIVE_PANEL', panel: panel8 },
|
||||
{ type: 'ADD_GROUP', group: panel9.group },
|
||||
{ type: 'ADD_PANEL', panel: panel9 },
|
||||
{ type: 'ACTIVE_GROUP', group: panel9.group },
|
||||
{ type: 'ACTIVE_PANEL', panel: panel9 },
|
||||
{ type: 'ADD_PANEL', panel: panel10 },
|
||||
{ type: 'ACTIVE_PANEL', panel: panel10 },
|
||||
{ type: 'ADD_GROUP', group: panel10.group },
|
||||
]);
|
||||
|
||||
events = [];
|
||||
disposable.dispose();
|
||||
|
||||
expect(events.length).toBe(0);
|
||||
});
|
||||
|
||||
test('that removing a panel from a group reflects in the dockviewcomponent when searching for a panel', () => {
|
||||
@ -2411,17 +2457,17 @@ describe('dockviewComponent', () => {
|
||||
const group = dockview.getGroupPanel('panel2')!.api.group;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
});
|
||||
@ -3334,10 +3380,10 @@ describe('dockviewComponent', () => {
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
Object.defineProperty(dockview.element, 'clientWidth', {
|
||||
Object.defineProperty(dockview.element, 'offsetWidth', {
|
||||
get: () => 100,
|
||||
});
|
||||
Object.defineProperty(dockview.element, 'clientHeight', {
|
||||
Object.defineProperty(dockview.element, 'offsetHeight', {
|
||||
get: () => 100,
|
||||
});
|
||||
|
||||
@ -3707,16 +3753,16 @@ describe('dockviewComponent', () => {
|
||||
floatingGroups: [
|
||||
{
|
||||
data: {
|
||||
views: ['panelB'],
|
||||
activeView: 'panelB',
|
||||
views: ['panelC'],
|
||||
activeView: 'panelC',
|
||||
id: '3',
|
||||
},
|
||||
position: { left: 0, top: 0, height: 100, width: 100 },
|
||||
},
|
||||
{
|
||||
data: {
|
||||
views: ['panelC'],
|
||||
activeView: 'panelC',
|
||||
views: ['panelD'],
|
||||
activeView: 'panelD',
|
||||
id: '4',
|
||||
},
|
||||
position: { left: 0, top: 0, height: 100, width: 100 },
|
||||
@ -4867,6 +4913,335 @@ describe('dockviewComponent', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('deserailize popout with no reference group', async () => {
|
||||
jest.useRealTimers();
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
window.open = () => setupMockWindow();
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 1000,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.VERTICAL,
|
||||
},
|
||||
popoutGroups: [
|
||||
{
|
||||
data: {
|
||||
views: ['panel2'],
|
||||
id: 'group-2',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
position: null,
|
||||
},
|
||||
],
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
contentComponent: 'default',
|
||||
title: 'panel1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
contentComponent: 'default',
|
||||
title: 'panel2',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
const panel2 = dockview.api.getPanel('panel2');
|
||||
|
||||
const windowObject =
|
||||
panel2?.api.location.type === 'popout'
|
||||
? panel2?.api.location.getWindow()
|
||||
: undefined;
|
||||
|
||||
expect(windowObject).toBeTruthy();
|
||||
|
||||
windowObject!.close();
|
||||
});
|
||||
|
||||
test('grid -> floating -> popout -> popout closed', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
window.open = () => setupMockWindow();
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
dockview.layout(1000, 500);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
expect(panel1.api.location.type).toBe('grid');
|
||||
expect(panel2.api.location.type).toBe('grid');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
|
||||
dockview.addFloatingGroup(panel2);
|
||||
|
||||
expect(panel1.api.location.type).toBe('grid');
|
||||
expect(panel2.api.location.type).toBe('floating');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
|
||||
await dockview.addPopoutGroup(panel2);
|
||||
|
||||
expect(panel1.api.location.type).toBe('grid');
|
||||
expect(panel2.api.location.type).toBe('popout');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
|
||||
const windowObject =
|
||||
panel2.api.location.type === 'popout'
|
||||
? panel2.api.location.getWindow()
|
||||
: undefined;
|
||||
expect(windowObject).toBeTruthy();
|
||||
|
||||
windowObject!.close();
|
||||
|
||||
expect(panel1.api.location.type).toBe('grid');
|
||||
expect(panel2.api.location.type).toBe('floating');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
});
|
||||
|
||||
test('grid -> floating -> popout -> floating', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
window.open = () => setupMockWindow();
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
dockview.layout(1000, 500);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
expect(panel1.api.location.type).toBe('grid');
|
||||
expect(panel2.api.location.type).toBe('grid');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
|
||||
dockview.addFloatingGroup(panel2.group);
|
||||
|
||||
expect(panel1.api.location.type).toBe('floating');
|
||||
expect(panel2.api.location.type).toBe('floating');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
|
||||
await dockview.addPopoutGroup(panel2.group);
|
||||
|
||||
expect(panel1.api.location.type).toBe('popout');
|
||||
expect(panel2.api.location.type).toBe('popout');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
|
||||
dockview.addFloatingGroup(panel2.group);
|
||||
|
||||
expect(panel1.api.location.type).toBe('floating');
|
||||
expect(panel2.api.location.type).toBe('floating');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
|
||||
await dockview.addPopoutGroup(panel2.group);
|
||||
|
||||
expect(panel1.api.location.type).toBe('popout');
|
||||
expect(panel2.api.location.type).toBe('popout');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
|
||||
panel2.group.api.moveTo({ group: panel3.group });
|
||||
|
||||
expect(panel1.api.location.type).toBe('grid');
|
||||
expect(panel2.api.location.type).toBe('grid');
|
||||
expect(panel3.api.location.type).toBe('grid');
|
||||
});
|
||||
|
||||
test('that panel is rendered when moving from popout to new group', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
window.open = () => setupMockWindow();
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
dockview.layout(1000, 500);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
renderer: 'always',
|
||||
});
|
||||
|
||||
await dockview.addPopoutGroup(panel2);
|
||||
panel2.api.moveTo({ group: panel1.api.group, position: 'right' });
|
||||
|
||||
// confirm panel is rendered on DOM
|
||||
expect(
|
||||
panel2.group.element.querySelectorAll(
|
||||
'.dv-content-container > .testpanel-panel_2'
|
||||
).length
|
||||
).toBe(1);
|
||||
|
||||
await dockview.addPopoutGroup(panel3);
|
||||
panel3.api.moveTo({ group: panel1.api.group, position: 'right' });
|
||||
|
||||
// confirm panel is rendered to always overlay container
|
||||
expect(
|
||||
dockview.element.querySelectorAll(
|
||||
'.dv-render-overlay > .testpanel-panel_3'
|
||||
).length
|
||||
).toBe(1);
|
||||
expect(
|
||||
panel2.group.element.querySelectorAll(
|
||||
'.dv-content-container > .testpanel-panel_3'
|
||||
).length
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('move popout group of 1 panel inside grid', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
window.open = () => setupMockWindow();
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
dockview.layout(1000, 500);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
await dockview.addPopoutGroup(panel2);
|
||||
|
||||
panel2.api.moveTo({ position: 'top', group: panel3.group });
|
||||
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
});
|
||||
|
||||
test('add a popout group', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
@ -5071,7 +5446,7 @@ describe('dockviewComponent', () => {
|
||||
mockWindow.close();
|
||||
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('grid');
|
||||
|
||||
dockview.clear();
|
||||
@ -5362,6 +5737,42 @@ describe('dockviewComponent', () => {
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('dispose of dockview instance when popup is open', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
window.open = () => setupMockWindow();
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
dockview.layout(1000, 500);
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(await dockview.addPopoutGroup(panel2.group)).toBeTruthy();
|
||||
|
||||
dockview.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
describe('maximized group', () => {
|
||||
@ -6318,36 +6729,4 @@ describe('dockviewComponent', () => {
|
||||
expect(api.panels.length).toBe(3);
|
||||
expect(api.groups.length).toBe(3);
|
||||
});
|
||||
|
||||
describe('updateOptions', () => {
|
||||
test('gap', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
gap: 6,
|
||||
});
|
||||
|
||||
expect(dockview.gap).toBe(6);
|
||||
|
||||
dockview.updateOptions({ gap: 10 });
|
||||
expect(dockview.gap).toBe(10);
|
||||
|
||||
dockview.updateOptions({});
|
||||
expect(dockview.gap).toBe(10);
|
||||
|
||||
dockview.updateOptions({ gap: 15 });
|
||||
expect(dockview.gap).toBe(15);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2,10 +2,12 @@ import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { GroupOptions } from '../../dockview/dockviewGroupPanelModel';
|
||||
import { DockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { DockviewPanelModelMock } from '../__mocks__/mockDockviewPanelModel';
|
||||
import { IContentRenderer, ITabRenderer } from '../../dockview/types';
|
||||
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
|
||||
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||
import { ContentContainer } from '../../dockview/components/panel/content';
|
||||
|
||||
describe('dockviewGroupPanel', () => {
|
||||
test('default minimum/maximium width/height', () => {
|
||||
@ -14,6 +16,7 @@ describe('dockviewGroupPanel', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
const options = fromPartial<GroupOptions>({});
|
||||
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
||||
@ -24,6 +27,51 @@ describe('dockviewGroupPanel', () => {
|
||||
expect(cut.maximumWidth).toBe(Number.MAX_SAFE_INTEGER);
|
||||
});
|
||||
|
||||
test('that onDidActivePanelChange is configured at inline', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
onDidActivePanelChange: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
api: {},
|
||||
renderer: 'always',
|
||||
overlayRenderContainer: {
|
||||
attach: jest.fn(),
|
||||
detatch: jest.fn(),
|
||||
},
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
const options = fromPartial<GroupOptions>({});
|
||||
|
||||
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
||||
|
||||
let counter = 0;
|
||||
|
||||
cut.api.onDidActivePanelChange((event) => {
|
||||
counter++;
|
||||
});
|
||||
|
||||
cut.model.openPanel(
|
||||
fromPartial<IDockviewPanel>({
|
||||
updateParentGroup: jest.fn(),
|
||||
view: {
|
||||
tab: { element: document.createElement('div') },
|
||||
content: new ContentContainer(accessor, cut.model),
|
||||
},
|
||||
api: {
|
||||
renderer: 'onlyWhenVisible',
|
||||
onDidTitleChange: jest.fn(),
|
||||
onDidParametersChange: jest.fn(),
|
||||
},
|
||||
layout: jest.fn(),
|
||||
runEvents: jest.fn(),
|
||||
})
|
||||
);
|
||||
|
||||
expect(counter).toBe(1);
|
||||
});
|
||||
|
||||
test('group constraints', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
onDidActivePanelChange: jest.fn(),
|
||||
@ -35,6 +83,7 @@ describe('dockviewGroupPanel', () => {
|
||||
detatch: jest.fn(),
|
||||
}),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
const options = fromPartial<GroupOptions>({});
|
||||
const cut = new DockviewGroupPanel(accessor, 'test_id', options);
|
||||
|
@ -24,6 +24,7 @@ import { createOffsetDragOverEvent } from '../__test_utils__/utils';
|
||||
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
|
||||
import { Emitter } from '../../events';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { TabLocation } from '../../dockview/framework';
|
||||
|
||||
enum GroupChangeKind2 {
|
||||
ADD_PANEL,
|
||||
@ -36,12 +37,16 @@ class TestModel implements IDockviewPanelModel {
|
||||
readonly contentComponent: string;
|
||||
readonly tab: ITabRenderer;
|
||||
|
||||
constructor(id: string) {
|
||||
constructor(readonly id: string) {
|
||||
this.content = new TestHeaderPart(id);
|
||||
this.contentComponent = id;
|
||||
this.tab = new TestContentPart(id);
|
||||
}
|
||||
|
||||
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
|
||||
return new TestHeaderPart(this.id);
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent): void {
|
||||
//
|
||||
}
|
||||
@ -265,6 +270,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
document.createElement('div'),
|
||||
fromPartial<DockviewComponent>({})
|
||||
),
|
||||
onDidOptionsChange: () => ({ dispose: jest.fn() }),
|
||||
});
|
||||
|
||||
groupview = new DockviewGroupPanel(dockview, 'groupview-1', options);
|
||||
@ -646,6 +652,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
getPanel: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -684,12 +691,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0)! as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
fireEvent.dragEnter(element);
|
||||
fireEvent.dragOver(element);
|
||||
@ -708,6 +715,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
getPanel: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -744,12 +752,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0)! as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
function run(value: number) {
|
||||
fireEvent.dragEnter(element);
|
||||
@ -792,7 +800,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
fireEvent.dragEnd(element);
|
||||
});
|
||||
|
||||
test('that should not show drop target if dropping on self', () => {
|
||||
test('that should show drop target if dropping on self', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
options: {},
|
||||
@ -804,17 +812,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
document.createElement('div'),
|
||||
fromPartial<DockviewComponent>({})
|
||||
),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
canDisplayOverlay: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupView = new groupviewMock() as DockviewGroupPanelModel;
|
||||
const groupView = fromPartial<DockviewGroupPanelModel>({
|
||||
canDisplayOverlay: jest.fn(),
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
@ -842,12 +845,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0)! as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
|
||||
@ -861,10 +864,10 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that should not allow drop when dropping on self for same component id', () => {
|
||||
test('that should allow drop when dropping on self for same component id', () => {
|
||||
const accessor = fromPartial<DockviewComponent>({
|
||||
id: 'testcomponentid',
|
||||
options: {},
|
||||
@ -876,6 +879,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
document.createElement('div'),
|
||||
fromPartial<DockviewComponent>({})
|
||||
),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -915,12 +919,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')],
|
||||
@ -934,7 +938,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('dv-drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that should not allow drop when not dropping for different component id', () => {
|
||||
@ -949,6 +953,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
document.createElement('div'),
|
||||
fromPartial<DockviewComponent>({})
|
||||
),
|
||||
onDidOptionsChange: jest.fn(),
|
||||
});
|
||||
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -988,12 +993,12 @@ describe('dockviewGroupPanelModel', () => {
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('dv-content-container')
|
||||
.item(0)!;
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
jest.spyOn(element, 'clientHeight', 'get').mockImplementation(
|
||||
jest.spyOn(element, 'offsetHeight', 'get').mockImplementation(
|
||||
() => 100
|
||||
);
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100);
|
||||
jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100);
|
||||
|
||||
LocalSelectionTransfer.getInstance().setData(
|
||||
[new PanelTransfer('anothercomponentid', 'groupviewid', 'panel1')],
|
||||
|
@ -105,6 +105,21 @@ class ClassUnderTest extends BaseGrid<TestPanel> {
|
||||
}
|
||||
|
||||
describe('baseComponentGridview', () => {
|
||||
test('that the container is not removed when grid is disposed', () => {
|
||||
const root = document.createElement('div');
|
||||
const container = document.createElement('div');
|
||||
root.appendChild(container);
|
||||
|
||||
const cut = new ClassUnderTest(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: true,
|
||||
});
|
||||
|
||||
cut.dispose();
|
||||
|
||||
expect(container.parentElement).toBe(root);
|
||||
});
|
||||
|
||||
test('that .layout(...) force flag works', () => {
|
||||
const cut = new ClassUnderTest(document.createElement('div'), {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
|
@ -36,7 +36,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
className: 'test-a test-b',
|
||||
});
|
||||
|
||||
@ -51,7 +58,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -70,7 +84,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -100,7 +121,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -160,7 +188,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -287,7 +322,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(1000, 1000);
|
||||
@ -323,7 +365,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -446,7 +495,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -468,14 +524,21 @@ describe('gridview', () => {
|
||||
|
||||
gridview.dispose();
|
||||
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
expect(container.children.length).toBe(0);
|
||||
});
|
||||
|
||||
test('#1/VERTICAL', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -533,7 +596,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -591,7 +661,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -667,7 +744,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -761,7 +845,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -855,7 +946,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -949,7 +1047,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1073,7 +1178,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1197,7 +1309,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1323,7 +1442,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1447,7 +1573,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(800, 400);
|
||||
@ -1571,7 +1704,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.fromJSON({
|
||||
@ -1698,7 +1838,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(1000, 1000);
|
||||
@ -1728,7 +1875,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
@ -1757,7 +1911,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: false,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
@ -1795,7 +1956,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let addGroup: GridviewPanel[] = [];
|
||||
@ -1917,7 +2085,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(1600, 800);
|
||||
@ -2043,7 +2218,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(6000, 5000);
|
||||
@ -2318,7 +2500,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
gridview.layout(5000, 6000);
|
||||
@ -2591,7 +2780,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error(`unsupported panel '${options.name}'`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let el = gridview.element.querySelector('.dv-view-container');
|
||||
@ -2655,9 +2851,7 @@ describe('gridview', () => {
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
});
|
||||
}).toThrow(
|
||||
"Cannot create 'panel_1', no component 'somethingBad' provided"
|
||||
);
|
||||
}).toThrow("unsupported panel 'somethingBad'");
|
||||
|
||||
expect(gridview.groups.length).toBe(0);
|
||||
|
||||
@ -2670,7 +2864,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(gridview.disableResizing).toBeFalsy();
|
||||
@ -2680,7 +2881,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
|
||||
@ -2691,7 +2899,14 @@ describe('gridview', () => {
|
||||
const gridview = new GridviewComponent(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: { default: TestGridview },
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestGridview(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
gridview.layout(1000, 1000);
|
||||
|
@ -8,6 +8,7 @@ describe('gridviewPanel', () => {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
onDidOptionsChange: jest.fn(),
|
||||
} as any;
|
||||
});
|
||||
|
||||
|
@ -1,102 +0,0 @@
|
||||
import { createComponent } from '../../panel/componentFactory';
|
||||
|
||||
describe('componentFactory', () => {
|
||||
describe('createComponent', () => {
|
||||
test('valid component and framework component', () => {
|
||||
const mock = jest.fn();
|
||||
const mock2 = jest.fn();
|
||||
|
||||
expect(() =>
|
||||
createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{ 'component-1': mock },
|
||||
{ 'component-1': mock2 }
|
||||
)
|
||||
).toThrow(
|
||||
"Cannot create 'id-1'. component 'component-1' registered as both a component and frameworkComponent"
|
||||
);
|
||||
});
|
||||
|
||||
test('valid framework component but no factory', () => {
|
||||
const mock = jest.fn();
|
||||
|
||||
expect(() =>
|
||||
createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{},
|
||||
{ 'component-1': mock }
|
||||
)
|
||||
).toThrow(
|
||||
"Cannot create 'id-1' for framework component 'component-1'. you must register a frameworkPanelWrapper to use framework components"
|
||||
);
|
||||
});
|
||||
|
||||
test('valid framework component', () => {
|
||||
const component = jest.fn();
|
||||
const createComponentFn = jest
|
||||
.fn()
|
||||
.mockImplementation(() => component);
|
||||
const frameworkComponent = jest.fn();
|
||||
|
||||
expect(
|
||||
createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{},
|
||||
{ 'component-1': frameworkComponent },
|
||||
{
|
||||
createComponent: createComponentFn,
|
||||
}
|
||||
)
|
||||
).toBe(component);
|
||||
|
||||
expect(createComponentFn).toHaveBeenCalledWith(
|
||||
'id-1',
|
||||
'component-1',
|
||||
frameworkComponent
|
||||
);
|
||||
});
|
||||
|
||||
test('no valid component with fallback', () => {
|
||||
const mock = jest.fn();
|
||||
|
||||
expect(
|
||||
createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{},
|
||||
{},
|
||||
{
|
||||
createComponent: () => null,
|
||||
},
|
||||
() => mock
|
||||
)
|
||||
).toBe(mock);
|
||||
});
|
||||
|
||||
test('no valid component', () => {
|
||||
expect(() =>
|
||||
createComponent('id-1', 'component-1', {}, {})
|
||||
).toThrow(
|
||||
"Cannot create 'id-1', no component 'component-1' provided"
|
||||
);
|
||||
});
|
||||
|
||||
test('valid component', () => {
|
||||
const component = jest.fn();
|
||||
|
||||
const componentResult = createComponent(
|
||||
'id-1',
|
||||
'component-1',
|
||||
{ 'component-1': component },
|
||||
{}
|
||||
);
|
||||
|
||||
expect(component).toHaveBeenCalled();
|
||||
|
||||
expect(componentResult instanceof component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,14 +1,10 @@
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
import { Paneview } from '../../paneview/paneview';
|
||||
import {
|
||||
IPaneBodyPart,
|
||||
IPaneHeaderPart,
|
||||
PaneviewPanel,
|
||||
} from '../../paneview/paneviewPanel';
|
||||
import { IPanePart, PaneviewPanel } from '../../paneview/paneviewPanel';
|
||||
import { Orientation } from '../../splitview/splitview';
|
||||
|
||||
class TestPanel extends PaneviewPanel {
|
||||
protected getBodyComponent(): IPaneBodyPart {
|
||||
protected getBodyComponent(): IPanePart {
|
||||
return {
|
||||
element: document.createElement('div'),
|
||||
update: () => {
|
||||
@ -23,7 +19,7 @@ class TestPanel extends PaneviewPanel {
|
||||
};
|
||||
}
|
||||
|
||||
protected getHeaderComponent(): IPaneHeaderPart {
|
||||
protected getHeaderComponent(): IPanePart {
|
||||
return {
|
||||
element: document.createElement('div'),
|
||||
update: () => {
|
||||
|
@ -4,8 +4,7 @@ import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { PaneviewComponent } from '../../paneview/paneviewComponent';
|
||||
import {
|
||||
PaneviewPanel,
|
||||
IPaneBodyPart,
|
||||
IPaneHeaderPart,
|
||||
IPanePart,
|
||||
PanePanelComponentInitParameter,
|
||||
} from '../../paneview/paneviewPanel';
|
||||
import { Orientation } from '../../splitview/splitview';
|
||||
@ -16,7 +15,7 @@ class TestPanel extends PaneviewPanel {
|
||||
}
|
||||
|
||||
getHeaderComponent() {
|
||||
return new (class Header implements IPaneHeaderPart {
|
||||
return new (class Header implements IPanePart {
|
||||
private _element: HTMLElement = document.createElement('div');
|
||||
|
||||
get element() {
|
||||
@ -38,7 +37,7 @@ class TestPanel extends PaneviewPanel {
|
||||
}
|
||||
|
||||
getBodyComponent() {
|
||||
return new (class Header implements IPaneBodyPart {
|
||||
return new (class Header implements IPanePart {
|
||||
private _element: HTMLElement = document.createElement('div');
|
||||
|
||||
get element() {
|
||||
@ -68,12 +67,39 @@ describe('componentPaneview', () => {
|
||||
container.className = 'container';
|
||||
});
|
||||
|
||||
test('that the container is not removed when grid is disposed', () => {
|
||||
const root = document.createElement('div');
|
||||
const container = document.createElement('div');
|
||||
root.appendChild(container);
|
||||
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
paneview.dispose();
|
||||
|
||||
expect(container.parentElement).toBe(root);
|
||||
expect(container.children.length).toBe(0);
|
||||
});
|
||||
|
||||
test('vertical panels', () => {
|
||||
const disposables = new CompositeDisposable();
|
||||
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -81,12 +107,12 @@ describe('componentPaneview', () => {
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel2',
|
||||
});
|
||||
|
||||
@ -144,8 +170,13 @@ describe('componentPaneview', () => {
|
||||
|
||||
test('serialization', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -156,7 +187,7 @@ describe('componentPaneview', () => {
|
||||
size: 1,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
expanded: true,
|
||||
@ -165,7 +196,7 @@ describe('componentPaneview', () => {
|
||||
size: 2,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: false,
|
||||
@ -174,7 +205,7 @@ describe('componentPaneview', () => {
|
||||
size: 3,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
},
|
||||
@ -220,7 +251,7 @@ describe('componentPaneview', () => {
|
||||
size: 756,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
expanded: true,
|
||||
@ -230,7 +261,7 @@ describe('componentPaneview', () => {
|
||||
size: 22,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: false,
|
||||
@ -240,7 +271,7 @@ describe('componentPaneview', () => {
|
||||
size: 22,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
expanded: false,
|
||||
@ -252,20 +283,25 @@ describe('componentPaneview', () => {
|
||||
|
||||
test('toJSON shouldnt fire any layout events', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
paneview.layout(1000, 1000);
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
@ -279,39 +315,15 @@ describe('componentPaneview', () => {
|
||||
disposable.dispose();
|
||||
});
|
||||
|
||||
test('dispose of paneviewComponent', () => {
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
paneview.layout(1000, 1000);
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
expect(container.childNodes.length).toBeGreaterThan(0);
|
||||
|
||||
paneview.dispose();
|
||||
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('panel is disposed of when component is disposed', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -319,12 +331,12 @@ describe('componentPaneview', () => {
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
@ -342,8 +354,13 @@ describe('componentPaneview', () => {
|
||||
|
||||
test('panel is disposed of when removed', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -351,12 +368,12 @@ describe('componentPaneview', () => {
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
@ -374,8 +391,13 @@ describe('componentPaneview', () => {
|
||||
|
||||
test('panel is disposed of when fromJSON is called', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -383,12 +405,12 @@ describe('componentPaneview', () => {
|
||||
|
||||
paneview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
paneview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
@ -406,8 +428,13 @@ describe('componentPaneview', () => {
|
||||
|
||||
test('that fromJSON layouts are resized to the current dimensions', async () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -420,7 +447,7 @@ describe('componentPaneview', () => {
|
||||
size: 1,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
expanded: true,
|
||||
@ -429,7 +456,7 @@ describe('componentPaneview', () => {
|
||||
size: 2,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: true,
|
||||
@ -438,7 +465,7 @@ describe('componentPaneview', () => {
|
||||
size: 3,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
expanded: true,
|
||||
@ -454,7 +481,7 @@ describe('componentPaneview', () => {
|
||||
size: 122,
|
||||
data: {
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
},
|
||||
expanded: true,
|
||||
@ -464,7 +491,7 @@ describe('componentPaneview', () => {
|
||||
size: 122,
|
||||
data: {
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
expanded: true,
|
||||
@ -474,7 +501,7 @@ describe('componentPaneview', () => {
|
||||
size: 356,
|
||||
data: {
|
||||
id: 'panel3',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
title: 'Panel 3',
|
||||
},
|
||||
expanded: true,
|
||||
@ -486,8 +513,13 @@ describe('componentPaneview', () => {
|
||||
|
||||
test('that disableAutoResizing is false by default', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -496,8 +528,13 @@ describe('componentPaneview', () => {
|
||||
|
||||
test('that disableAutoResizing can be enabled', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
@ -507,8 +544,13 @@ describe('componentPaneview', () => {
|
||||
|
||||
test('that setVisible toggles visiblity', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
default: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
@ -540,17 +582,22 @@ describe('componentPaneview', () => {
|
||||
|
||||
test('update className', () => {
|
||||
const paneview = new PaneviewComponent(container, {
|
||||
components: {
|
||||
default: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
className: 'test-a test-b',
|
||||
});
|
||||
|
||||
expect(paneview.element.className).toBe('container test-a test-b');
|
||||
expect(paneview.element.className).toBe('test-a test-b');
|
||||
|
||||
paneview.updateOptions({ className: 'test-b test-c' });
|
||||
|
||||
expect(paneview.element.className).toBe('container test-b test-c');
|
||||
expect(paneview.element.className).toBe('test-b test-c');
|
||||
});
|
||||
});
|
||||
|
@ -26,24 +26,52 @@ describe('componentSplitview', () => {
|
||||
container.className = 'container';
|
||||
});
|
||||
|
||||
test('that the container is not removed when grid is disposed', () => {
|
||||
const root = document.createElement('div');
|
||||
const container = document.createElement('div');
|
||||
root.appendChild(container);
|
||||
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
splitview.dispose();
|
||||
|
||||
expect(container.parentElement).toBe(root);
|
||||
expect(container.children.length).toBe(0);
|
||||
});
|
||||
|
||||
test('event leakage', () => {
|
||||
Emitter.setLeakageMonitorEnabled(true);
|
||||
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
|
||||
const panel1 = splitview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
});
|
||||
const panel2 = splitview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
splitview.movePanel(0, 1);
|
||||
@ -67,15 +95,20 @@ describe('componentSplitview', () => {
|
||||
test('remove panel', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel3', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel3', component: 'default' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1')!;
|
||||
const panel2 = splitview.getPanel('panel2')!;
|
||||
@ -102,8 +135,13 @@ describe('componentSplitview', () => {
|
||||
test('horizontal dimensions', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
@ -115,8 +153,13 @@ describe('componentSplitview', () => {
|
||||
test('vertical dimensions', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
@ -128,15 +171,20 @@ describe('componentSplitview', () => {
|
||||
test('api resize', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(400, 600);
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel3', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel3', component: 'default' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1')!;
|
||||
const panel2 = splitview.getPanel('panel2')!;
|
||||
@ -180,13 +228,18 @@ describe('componentSplitview', () => {
|
||||
test('api', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(600, 400);
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1');
|
||||
|
||||
@ -197,7 +250,7 @@ describe('componentSplitview', () => {
|
||||
// expect(panel1?.api.isFocused).toBeFalsy();
|
||||
expect(panel1!.api.isVisible).toBeTruthy();
|
||||
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
|
||||
const panel2 = splitview.getPanel('panel2');
|
||||
|
||||
@ -221,15 +274,20 @@ describe('componentSplitview', () => {
|
||||
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(300, 200);
|
||||
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1') as SplitviewPanel;
|
||||
const panel2 = splitview.getPanel('panel2') as SplitviewPanel;
|
||||
@ -272,15 +330,20 @@ describe('componentSplitview', () => {
|
||||
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(300, 200);
|
||||
|
||||
splitview.addPanel({ id: 'panel1', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'testPanel' });
|
||||
splitview.addPanel({ id: 'panel1', component: 'default' });
|
||||
splitview.addPanel({ id: 'panel2', component: 'default' });
|
||||
|
||||
const panel1 = splitview.getPanel('panel1') as SplitviewPanel;
|
||||
const panel2 = splitview.getPanel('panel2') as SplitviewPanel;
|
||||
@ -321,8 +384,13 @@ describe('componentSplitview', () => {
|
||||
test('serialization', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
splitview.layout(400, 6);
|
||||
@ -331,15 +399,15 @@ describe('componentSplitview', () => {
|
||||
views: [
|
||||
{
|
||||
size: 1,
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
data: { id: 'panel1', component: 'default' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 2,
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
data: { id: 'panel2', component: 'default' },
|
||||
snap: true,
|
||||
},
|
||||
{ size: 3, data: { id: 'panel3', component: 'testPanel' } },
|
||||
{ size: 3, data: { id: 'panel3', component: 'default' } },
|
||||
],
|
||||
size: 6,
|
||||
orientation: Orientation.VERTICAL,
|
||||
@ -352,17 +420,17 @@ describe('componentSplitview', () => {
|
||||
views: [
|
||||
{
|
||||
size: 1,
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
data: { id: 'panel1', component: 'default' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 2,
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
data: { id: 'panel2', component: 'default' },
|
||||
snap: true,
|
||||
},
|
||||
{
|
||||
size: 3,
|
||||
data: { id: 'panel3', component: 'testPanel' },
|
||||
data: { id: 'panel3', component: 'default' },
|
||||
snap: false,
|
||||
},
|
||||
],
|
||||
@ -375,8 +443,13 @@ describe('componentSplitview', () => {
|
||||
test('toJSON shouldnt fire any layout events', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -384,11 +457,11 @@ describe('componentSplitview', () => {
|
||||
|
||||
splitview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
});
|
||||
splitview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const disposable = splitview.onDidLayoutChange(() => {
|
||||
@ -401,39 +474,16 @@ describe('componentSplitview', () => {
|
||||
disposable.dispose();
|
||||
});
|
||||
|
||||
test('dispose of splitviewComponent', () => {
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
|
||||
splitview.layout(1000, 1000);
|
||||
|
||||
splitview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
});
|
||||
splitview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
});
|
||||
|
||||
expect(container.childNodes.length).toBeGreaterThan(0);
|
||||
|
||||
splitview.dispose();
|
||||
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('panel is disposed of when component is disposed', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
default: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -463,8 +513,13 @@ describe('componentSplitview', () => {
|
||||
test('panel is disposed of when removed', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
default: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -494,8 +549,13 @@ describe('componentSplitview', () => {
|
||||
test('panel is disposed of when fromJSON is called', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
default: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -529,8 +589,13 @@ describe('componentSplitview', () => {
|
||||
test('that fromJSON layouts are resized to the current dimensions', async () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
splitview.layout(400, 600);
|
||||
@ -539,15 +604,15 @@ describe('componentSplitview', () => {
|
||||
views: [
|
||||
{
|
||||
size: 1,
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
data: { id: 'panel1', component: 'default' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 2,
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
data: { id: 'panel2', component: 'default' },
|
||||
snap: true,
|
||||
},
|
||||
{ size: 3, data: { id: 'panel3', component: 'testPanel' } },
|
||||
{ size: 3, data: { id: 'panel3', component: 'default' } },
|
||||
],
|
||||
size: 6,
|
||||
orientation: Orientation.VERTICAL,
|
||||
@ -558,17 +623,17 @@ describe('componentSplitview', () => {
|
||||
views: [
|
||||
{
|
||||
size: 100,
|
||||
data: { id: 'panel1', component: 'testPanel' },
|
||||
data: { id: 'panel1', component: 'default' },
|
||||
snap: false,
|
||||
},
|
||||
{
|
||||
size: 200,
|
||||
data: { id: 'panel2', component: 'testPanel' },
|
||||
data: { id: 'panel2', component: 'default' },
|
||||
snap: true,
|
||||
},
|
||||
{
|
||||
size: 300,
|
||||
data: { id: 'panel3', component: 'testPanel' },
|
||||
data: { id: 'panel3', component: 'default' },
|
||||
snap: false,
|
||||
},
|
||||
],
|
||||
@ -581,8 +646,13 @@ describe('componentSplitview', () => {
|
||||
test('that disableAutoResizing is false by default', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -592,8 +662,13 @@ describe('componentSplitview', () => {
|
||||
test('that disableAutoResizing can be enabled', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
disableAutoResizing: true,
|
||||
});
|
||||
@ -604,8 +679,13 @@ describe('componentSplitview', () => {
|
||||
test('that setVisible toggles visiblity', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
default: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -635,16 +715,21 @@ describe('componentSplitview', () => {
|
||||
test('update className', () => {
|
||||
const splitview = new SplitviewComponent(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
components: {
|
||||
default: TestPanel,
|
||||
createComponent: (options) => {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new TestPanel(options.id, options.name);
|
||||
default:
|
||||
throw new Error('unsupported');
|
||||
}
|
||||
},
|
||||
className: 'test-a test-b',
|
||||
});
|
||||
|
||||
expect(splitview.element.className).toBe('container test-a test-b');
|
||||
expect(splitview.element.className).toBe('test-a test-b');
|
||||
|
||||
splitview.updateOptions({ className: 'test-b test-c' });
|
||||
|
||||
expect(splitview.element.className).toBe('container test-b test-c');
|
||||
expect(splitview.element.className).toBe('test-b test-c');
|
||||
});
|
||||
});
|
||||
|
@ -38,9 +38,9 @@ import {
|
||||
DockviewGroupPanel,
|
||||
IDockviewGroupPanel,
|
||||
} from '../dockview/dockviewGroupPanel';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { Event } from '../events';
|
||||
import { IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { PaneviewDropEvent } from '../paneview/draggablePaneviewPanel';
|
||||
import { PaneviewDidDropEvent } from '../paneview/draggablePaneviewPanel';
|
||||
import {
|
||||
GroupDragEvent,
|
||||
TabDragEvent,
|
||||
@ -51,7 +51,10 @@ import {
|
||||
DockviewWillDropEvent,
|
||||
WillShowOverlayLocationEvent,
|
||||
} from '../dockview/dockviewGroupPanelModel';
|
||||
import { PaneviewComponentOptions } from '../paneview/options';
|
||||
import {
|
||||
PaneviewComponentOptions,
|
||||
PaneviewDndOverlayEvent,
|
||||
} from '../paneview/options';
|
||||
import { SplitviewComponentOptions } from '../splitview/options';
|
||||
import { GridviewComponentOptions } from '../gridview/options';
|
||||
|
||||
@ -294,19 +297,12 @@ export class PaneviewApi implements CommonApi<SerializedPaneview> {
|
||||
/**
|
||||
* Invoked when a Drag'n'Drop event occurs that the component was unable to handle. Exposed for custom Drag'n'Drop functionality.
|
||||
*/
|
||||
get onDidDrop(): Event<PaneviewDropEvent> {
|
||||
const emitter = new Emitter<PaneviewDropEvent>();
|
||||
get onDidDrop(): Event<PaneviewDidDropEvent> {
|
||||
return this.component.onDidDrop;
|
||||
}
|
||||
|
||||
const disposable = this.component.onDidDrop((e) => {
|
||||
emitter.fire({ ...e, api: this });
|
||||
});
|
||||
|
||||
emitter.dispose = () => {
|
||||
disposable.dispose();
|
||||
emitter.dispose();
|
||||
};
|
||||
|
||||
return emitter.event;
|
||||
get onUnhandledDragOverEvent(): Event<PaneviewDndOverlayEvent> {
|
||||
return this.component.onUnhandledDragOverEvent;
|
||||
}
|
||||
|
||||
constructor(private readonly component: IPaneviewComponent) {}
|
||||
@ -633,9 +629,6 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
return this.component.totalPanels;
|
||||
}
|
||||
|
||||
get gap(): number {
|
||||
return this.component.gap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the active group changes. May be undefined if no group is active.
|
||||
@ -918,10 +911,6 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
return this.component.addPopoutGroup(item, options);
|
||||
}
|
||||
|
||||
setGap(gap: number | undefined): void {
|
||||
this.component.updateOptions({ gap });
|
||||
}
|
||||
|
||||
updateOptions(options: Partial<DockviewComponentOptions>) {
|
||||
this.component.updateOptions(options);
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import {
|
||||
DockviewGroupLocation,
|
||||
} from '../dockview/dockviewGroupPanelModel';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { MutableDisposable } from '../lifecycle';
|
||||
import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi';
|
||||
|
||||
export interface DockviewGroupMoveParams {
|
||||
@ -41,8 +40,6 @@ const NOT_INITIALIZED_MESSAGE =
|
||||
'dockview: DockviewGroupPanelApiImpl not initialized';
|
||||
|
||||
export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
||||
private readonly _mutableDisposable = new MutableDisposable();
|
||||
|
||||
private _group: DockviewGroupPanel | undefined;
|
||||
|
||||
readonly _onDidLocationChange =
|
||||
@ -50,8 +47,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
||||
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent> =
|
||||
this._onDidLocationChange.event;
|
||||
|
||||
private readonly _onDidActivePanelChange =
|
||||
new Emitter<DockviewGroupChangeEvent>();
|
||||
readonly _onDidActivePanelChange = new Emitter<DockviewGroupChangeEvent>();
|
||||
readonly onDidActivePanelChange = this._onDidActivePanelChange.event;
|
||||
|
||||
get location(): DockviewGroupLocation {
|
||||
@ -66,8 +62,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidLocationChange,
|
||||
this._onDidActivePanelChange,
|
||||
this._mutableDisposable
|
||||
this._onDidActivePanelChange
|
||||
);
|
||||
}
|
||||
|
||||
@ -140,21 +135,6 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
||||
}
|
||||
|
||||
initialize(group: DockviewGroupPanel): void {
|
||||
/**
|
||||
* TODO: Annoying initialization order caveat, find a better way to initialize and avoid needing null checks
|
||||
*
|
||||
* Due to the order on initialization we know that the model isn't defined until later in the same stack-frame of setup.
|
||||
* By queuing a microtask we can ensure the setup is completed within the same stack-frame, but after everything else has
|
||||
* finished ensuring the `model` is defined.
|
||||
*/
|
||||
|
||||
this._group = group;
|
||||
|
||||
queueMicrotask(() => {
|
||||
this._mutableDisposable.value =
|
||||
this._group!.model.onDidActivePanelChange((event) => {
|
||||
this._onDidActivePanelChange.fire(event);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
* For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled
|
||||
* through .preventDefault(). Since this is applied globally to all drag events this would break dockviews
|
||||
* dnd logic. You can see the code at
|
||||
* https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
|
||||
P * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542
|
||||
*/
|
||||
event.dataTransfer.setData('text/plain', '');
|
||||
}
|
||||
@ -75,7 +75,9 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
}),
|
||||
addDisposableListener(this.el, 'dragend', () => {
|
||||
this.pointerEventsDisposable.dispose();
|
||||
this.dataDisposable.dispose();
|
||||
setTimeout(() => {
|
||||
this.dataDisposable.dispose(); // allow the data to be read by other handlers before disposing
|
||||
}, 0);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
.dv-drop-target-container {
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
--dv-transition-duration: 300ms;
|
||||
|
||||
.dv-drop-target-anchor {
|
||||
position: relative;
|
||||
border: var(--dv-drag-over-border);
|
||||
transition: opacity var(--dv-transition-duration) ease-in,
|
||||
top var(--dv-transition-duration) ease-out,
|
||||
left var(--dv-transition-duration) ease-out,
|
||||
width var(--dv-transition-duration) ease-out,
|
||||
height var(--dv-transition-duration) ease-out;
|
||||
background-color: var(--dv-drag-over-background-color);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
102
packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts
Normal file
102
packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { CompositeDisposable, Disposable } from '../lifecycle';
|
||||
import { DropTargetTargetModel } from './droptarget';
|
||||
|
||||
export class DropTargetAnchorContainer extends CompositeDisposable {
|
||||
private _model:
|
||||
| { root: HTMLElement; overlay: HTMLElement; changed: boolean }
|
||||
| undefined;
|
||||
|
||||
private _outline: HTMLElement | undefined;
|
||||
|
||||
private _disabled = false;
|
||||
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
if (this.disabled === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._disabled = value;
|
||||
|
||||
if (value) {
|
||||
this.model?.clear();
|
||||
}
|
||||
}
|
||||
|
||||
get model(): DropTargetTargetModel | undefined {
|
||||
if (this.disabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
clear: () => {
|
||||
if (this._model) {
|
||||
this._model.root.parentElement?.removeChild(
|
||||
this._model.root
|
||||
);
|
||||
}
|
||||
this._model = undefined;
|
||||
},
|
||||
exists: () => {
|
||||
return !!this._model;
|
||||
},
|
||||
getElements: (event?: DragEvent, outline?: HTMLElement) => {
|
||||
const changed = this._outline !== outline;
|
||||
this._outline = outline;
|
||||
|
||||
if (this._model) {
|
||||
this._model.changed = changed;
|
||||
return this._model;
|
||||
}
|
||||
|
||||
const container = this.createContainer();
|
||||
const anchor = this.createAnchor();
|
||||
|
||||
this._model = { root: container, overlay: anchor, changed };
|
||||
|
||||
container.appendChild(anchor);
|
||||
this.element.appendChild(container);
|
||||
|
||||
if (event?.target instanceof HTMLElement) {
|
||||
const targetBox = event.target.getBoundingClientRect();
|
||||
const box = this.element.getBoundingClientRect();
|
||||
|
||||
anchor.style.left = `${targetBox.left - box.left}px`;
|
||||
anchor.style.top = `${targetBox.top - box.top}px`;
|
||||
}
|
||||
|
||||
return this._model;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor(readonly element: HTMLElement, options: { disabled: boolean }) {
|
||||
super();
|
||||
|
||||
this._disabled = options.disabled;
|
||||
|
||||
this.addDisposables(
|
||||
Disposable.from(() => {
|
||||
this.model?.clear();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private createContainer(): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'dv-drop-target-container';
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
private createAnchor(): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'dv-drop-target-anchor';
|
||||
el.style.visibility = 'hidden';
|
||||
|
||||
return el;
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
.dv-drop-target {
|
||||
position: relative;
|
||||
--dv-transition-duration: 70ms;
|
||||
|
||||
> .dv-drop-target-dropzone {
|
||||
position: absolute;
|
||||
@ -15,10 +16,13 @@
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: var(--dv-drag-over-border);
|
||||
background-color: var(--dv-drag-over-background-color);
|
||||
transition: top 70ms ease-out, left 70ms ease-out,
|
||||
width 70ms ease-out, height 70ms ease-out,
|
||||
opacity 0.15s ease-out;
|
||||
transition: top var(--dv-transition-duration) ease-out,
|
||||
left var(--dv-transition-duration) ease-out,
|
||||
width var(--dv-transition-duration) ease-out,
|
||||
height var(--dv-transition-duration) ease-out,
|
||||
opacity var(--dv-transition-duration) ease-out;
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
|
||||
|
@ -93,10 +93,26 @@ const DEFAULT_SIZE: MeasuredValue = {
|
||||
const SMALL_WIDTH_BOUNDARY = 100;
|
||||
const SMALL_HEIGHT_BOUNDARY = 100;
|
||||
|
||||
export interface DropTargetTargetModel {
|
||||
getElements(
|
||||
event?: DragEvent,
|
||||
outline?: HTMLElement
|
||||
): {
|
||||
root: HTMLElement;
|
||||
overlay: HTMLElement;
|
||||
changed: boolean;
|
||||
};
|
||||
exists(): boolean;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
export interface DroptargetOptions {
|
||||
canDisplayOverlay: CanDisplayOverlay;
|
||||
acceptedTargetZones: Position[];
|
||||
overlayModel?: DroptargetOverlayModel;
|
||||
getOverrideTarget?: () => DropTargetTargetModel | undefined;
|
||||
className?: string;
|
||||
getOverlayOutline?: () => HTMLElement | null;
|
||||
}
|
||||
|
||||
export class Droptarget extends CompositeDisposable {
|
||||
@ -116,6 +132,18 @@ export class Droptarget extends CompositeDisposable {
|
||||
|
||||
private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__';
|
||||
|
||||
private static ACTUAL_TARGET: Droptarget | undefined;
|
||||
|
||||
private _disabled: boolean;
|
||||
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
this._disabled = value;
|
||||
}
|
||||
|
||||
get state(): Position | undefined {
|
||||
return this._state;
|
||||
}
|
||||
@ -126,21 +154,35 @@ export class Droptarget extends CompositeDisposable {
|
||||
) {
|
||||
super();
|
||||
|
||||
this._disabled = false;
|
||||
|
||||
// use a set to take advantage of #<set>.has
|
||||
this._acceptedTargetZonesSet = new Set(
|
||||
this.options.acceptedTargetZones
|
||||
);
|
||||
|
||||
this.dnd = new DragAndDropObserver(this.element, {
|
||||
onDragEnter: () => undefined,
|
||||
onDragEnter: () => {
|
||||
this.options.getOverrideTarget?.()?.getElements();
|
||||
},
|
||||
onDragOver: (e) => {
|
||||
Droptarget.ACTUAL_TARGET = this;
|
||||
|
||||
const overrideTraget = this.options.getOverrideTarget?.();
|
||||
|
||||
if (this._acceptedTargetZonesSet.size === 0) {
|
||||
if (overrideTraget) {
|
||||
return;
|
||||
}
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
const width = this.element.clientWidth;
|
||||
const height = this.element.clientHeight;
|
||||
const target =
|
||||
this.options.getOverlayOutline?.() ?? this.element;
|
||||
|
||||
const width = target.offsetWidth;
|
||||
const height = target.offsetHeight;
|
||||
|
||||
if (width === 0 || height === 0) {
|
||||
return; // avoid div!0
|
||||
@ -149,8 +191,8 @@ export class Droptarget extends CompositeDisposable {
|
||||
const rect = (
|
||||
e.currentTarget as HTMLElement
|
||||
).getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
const x = (e.clientX ?? 0) - rect.left;
|
||||
const y = (e.clientY ?? 0) - rect.top;
|
||||
|
||||
const quadrant = this.calculateQuadrant(
|
||||
this._acceptedTargetZonesSet,
|
||||
@ -172,6 +214,9 @@ export class Droptarget extends CompositeDisposable {
|
||||
}
|
||||
|
||||
if (!this.options.canDisplayOverlay(e, quadrant)) {
|
||||
if (overrideTraget) {
|
||||
return;
|
||||
}
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
@ -194,7 +239,9 @@ export class Droptarget extends CompositeDisposable {
|
||||
|
||||
this.markAsUsed(e);
|
||||
|
||||
if (!this.targetElement) {
|
||||
if (overrideTraget) {
|
||||
//
|
||||
} else if (!this.targetElement) {
|
||||
this.targetElement = document.createElement('div');
|
||||
this.targetElement.className = 'dv-drop-target-dropzone';
|
||||
this.overlayElement = document.createElement('div');
|
||||
@ -202,8 +249,16 @@ export class Droptarget extends CompositeDisposable {
|
||||
this._state = 'center';
|
||||
this.targetElement.appendChild(this.overlayElement);
|
||||
|
||||
this.element.classList.add('dv-drop-target');
|
||||
this.element.append(this.targetElement);
|
||||
target.classList.add('dv-drop-target');
|
||||
target.append(this.targetElement);
|
||||
|
||||
// this.overlayElement.style.opacity = '0';
|
||||
|
||||
// requestAnimationFrame(() => {
|
||||
// if (this.overlayElement) {
|
||||
// this.overlayElement.style.opacity = '';
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
this.toggleClasses(quadrant, width, height);
|
||||
@ -211,10 +266,32 @@ export class Droptarget extends CompositeDisposable {
|
||||
this._state = quadrant;
|
||||
},
|
||||
onDragLeave: () => {
|
||||
const target = this.options.getOverrideTarget?.();
|
||||
|
||||
if (target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeDropTarget();
|
||||
},
|
||||
onDragEnd: () => {
|
||||
onDragEnd: (e) => {
|
||||
const target = this.options.getOverrideTarget?.();
|
||||
|
||||
if (target && Droptarget.ACTUAL_TARGET === this) {
|
||||
if (this._state) {
|
||||
// only stop the propagation of the event if we are dealing with it
|
||||
// which is only when the target has state
|
||||
e.stopPropagation();
|
||||
this._onDrop.fire({
|
||||
position: this._state,
|
||||
nativeEvent: e,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.removeDropTarget();
|
||||
|
||||
target?.clear();
|
||||
},
|
||||
onDrop: (e) => {
|
||||
e.preventDefault();
|
||||
@ -223,6 +300,8 @@ export class Droptarget extends CompositeDisposable {
|
||||
|
||||
this.removeDropTarget();
|
||||
|
||||
this.options.getOverrideTarget?.()?.clear();
|
||||
|
||||
if (state) {
|
||||
// only stop the propagation of the event if we are dealing with it
|
||||
// which is only when the target has state
|
||||
@ -268,7 +347,9 @@ export class Droptarget extends CompositeDisposable {
|
||||
width: number,
|
||||
height: number
|
||||
): void {
|
||||
if (!this.overlayElement) {
|
||||
const target = this.options.getOverrideTarget?.();
|
||||
|
||||
if (!target && !this.overlayElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -300,6 +381,103 @@ export class Droptarget extends CompositeDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
if (target) {
|
||||
const outlineEl =
|
||||
this.options.getOverlayOutline?.() ?? this.element;
|
||||
const elBox = outlineEl.getBoundingClientRect();
|
||||
|
||||
const ta = target.getElements(undefined, outlineEl);
|
||||
const el = ta.root;
|
||||
const overlay = ta.overlay;
|
||||
|
||||
const bigbox = el.getBoundingClientRect();
|
||||
|
||||
const rootTop = elBox.top - bigbox.top;
|
||||
const rootLeft = elBox.left - bigbox.left;
|
||||
|
||||
const box = {
|
||||
top: rootTop,
|
||||
left: rootLeft,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
|
||||
if (rightClass) {
|
||||
box.left = rootLeft + width * (1 - size);
|
||||
box.width = width * size;
|
||||
} else if (leftClass) {
|
||||
box.width = width * size;
|
||||
} else if (topClass) {
|
||||
box.height = height * size;
|
||||
} else if (bottomClass) {
|
||||
box.top = rootTop + height * (1 - size);
|
||||
box.height = height * size;
|
||||
}
|
||||
|
||||
if (isSmallX && isLeft) {
|
||||
box.width = 4;
|
||||
}
|
||||
if (isSmallX && isRight) {
|
||||
box.left = rootLeft + width - 4;
|
||||
box.width = 4;
|
||||
}
|
||||
|
||||
const topPx = `${Math.round(box.top)}px`;
|
||||
const leftPx = `${Math.round(box.left)}px`;
|
||||
const widthPx = `${Math.round(box.width)}px`;
|
||||
const heightPx = `${Math.round(box.height)}px`;
|
||||
|
||||
if (
|
||||
overlay.style.top === topPx &&
|
||||
overlay.style.left === leftPx &&
|
||||
overlay.style.width === widthPx &&
|
||||
overlay.style.height === heightPx
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
overlay.style.top = topPx;
|
||||
overlay.style.left = leftPx;
|
||||
overlay.style.width = widthPx;
|
||||
overlay.style.height = heightPx;
|
||||
overlay.style.visibility = 'visible';
|
||||
|
||||
overlay.className = `dv-drop-target-anchor${
|
||||
this.options.className ? ` ${this.options.className}` : ''
|
||||
}`;
|
||||
|
||||
toggleClass(overlay, 'dv-drop-target-left', isLeft);
|
||||
toggleClass(overlay, 'dv-drop-target-right', isRight);
|
||||
toggleClass(overlay, 'dv-drop-target-top', isTop);
|
||||
toggleClass(overlay, 'dv-drop-target-bottom', isBottom);
|
||||
toggleClass(
|
||||
overlay,
|
||||
'dv-drop-target-center',
|
||||
quadrant === 'center'
|
||||
);
|
||||
|
||||
if (ta.changed) {
|
||||
toggleClass(
|
||||
overlay,
|
||||
'dv-drop-target-anchor-container-changed',
|
||||
true
|
||||
);
|
||||
setTimeout(() => {
|
||||
toggleClass(
|
||||
overlay,
|
||||
'dv-drop-target-anchor-container-changed',
|
||||
false
|
||||
);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.overlayElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const box = { top: '0px', left: '0px', width: '100%', height: '100%' };
|
||||
|
||||
/**
|
||||
@ -396,10 +574,12 @@ export class Droptarget extends CompositeDisposable {
|
||||
private removeDropTarget(): void {
|
||||
if (this.targetElement) {
|
||||
this._state = undefined;
|
||||
this.element.removeChild(this.targetElement);
|
||||
this.targetElement.parentElement?.classList.remove(
|
||||
'dv-drop-target'
|
||||
);
|
||||
this.targetElement.remove();
|
||||
this.targetElement = undefined;
|
||||
this.overlayElement = undefined;
|
||||
this.element.classList.remove('dv-drop-target');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ import { addClasses, removeClasses } from '../dom';
|
||||
|
||||
export function addGhostImage(
|
||||
dataTransfer: DataTransfer,
|
||||
ghostElement: HTMLElement
|
||||
ghostElement: HTMLElement,
|
||||
options?: { x?: number; y?: number }
|
||||
): void {
|
||||
// class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
|
||||
addClasses(ghostElement, 'dv-dragged');
|
||||
|
||||
document.body.appendChild(ghostElement);
|
||||
dataTransfer.setDragImage(ghostElement, 0, 0);
|
||||
dataTransfer.setDragImage(ghostElement, options?.x ?? 0, options?.y ?? 0);
|
||||
|
||||
setTimeout(() => {
|
||||
removeClasses(ghostElement, 'dv-dragged');
|
||||
|
@ -72,9 +72,11 @@ export class GroupDragHandler extends DragHandler {
|
||||
ghostElement.style.lineHeight = '20px';
|
||||
ghostElement.style.borderRadius = '12px';
|
||||
ghostElement.style.position = 'absolute';
|
||||
ghostElement.style.pointerEvents = 'none';
|
||||
ghostElement.style.top = '-9999px';
|
||||
ghostElement.textContent = `Multiple Panels (${this.group.size})`;
|
||||
|
||||
addGhostImage(dataTransfer, ghostElement);
|
||||
addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 });
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -55,7 +55,15 @@ export class ContentContainer
|
||||
|
||||
this.addDisposables(this._onDidFocus, this._onDidBlur);
|
||||
|
||||
const target = group.dropTargetContainer;
|
||||
|
||||
this.dropTarget = new Droptarget(this.element, {
|
||||
getOverlayOutline: () => {
|
||||
return accessor.options.theme?.dndPanelOverlay === 'group'
|
||||
? this.element.parentElement
|
||||
: null;
|
||||
},
|
||||
className: 'dv-drop-target-content',
|
||||
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (
|
||||
@ -76,26 +84,12 @@ export class ContentContainer
|
||||
}
|
||||
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
if (data.groupId === this.group.id) {
|
||||
if (position === 'center') {
|
||||
// don't allow to drop on self for center position
|
||||
return false;
|
||||
}
|
||||
if (data.panelId === null) {
|
||||
// don't allow group move to drop anywhere on self
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const groupHasOnePanelAndIsActiveDragElement =
|
||||
this.group.panels.length === 1 &&
|
||||
data.groupId === this.group.id;
|
||||
|
||||
return !groupHasOnePanelAndIsActiveDragElement;
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.group.canDisplayOverlay(event, position, 'content');
|
||||
},
|
||||
getOverrideTarget: target ? () => target.model : undefined,
|
||||
});
|
||||
|
||||
this.addDisposables(this.dropTarget);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -58,15 +58,13 @@
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
min-width: 80px;
|
||||
align-items: center;
|
||||
padding: 0px 8px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.dv-default-tab-content {
|
||||
padding: 0px 8px;
|
||||
flex-grow: 1;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.dv-default-tab-action {
|
||||
|
@ -29,12 +29,6 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
this._element.appendChild(this._content);
|
||||
this._element.appendChild(this.action);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.action, 'pointerdown', (ev) => {
|
||||
ev.preventDefault();
|
||||
})
|
||||
);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
} from '../../../dnd/droptarget';
|
||||
import { DragHandler } from '../../../dnd/abstractDragHandler';
|
||||
import { IDockviewPanel } from '../../dockviewPanel';
|
||||
import { addGhostImage } from '../../../dnd/ghost';
|
||||
|
||||
class TabDragHandler extends DragHandler {
|
||||
private readonly panelTransfer =
|
||||
@ -49,8 +50,8 @@ export class Tab extends CompositeDisposable {
|
||||
private readonly dropTarget: Droptarget;
|
||||
private content: ITabRenderer | undefined = undefined;
|
||||
|
||||
private readonly _onChanged = new Emitter<MouseEvent>();
|
||||
readonly onChanged: Event<MouseEvent> = this._onChanged.event;
|
||||
private readonly _onPointDown = new Emitter<MouseEvent>();
|
||||
readonly onPointerDown: Event<MouseEvent> = this._onPointDown.event;
|
||||
|
||||
private readonly _onDropped = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDropped.event;
|
||||
@ -86,7 +87,8 @@ export class Tab extends CompositeDisposable {
|
||||
);
|
||||
|
||||
this.dropTarget = new Droptarget(this._element, {
|
||||
acceptedTargetZones: ['center'],
|
||||
acceptedTargetZones: ['left', 'right'],
|
||||
overlayModel: { activationSize: { value: 50, type: 'percentage' } },
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (this.group.locked) {
|
||||
return false;
|
||||
@ -95,15 +97,7 @@ export class Tab extends CompositeDisposable {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.panel.id !== data.panelId;
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.group.model.canDisplayOverlay(
|
||||
@ -112,24 +106,38 @@ export class Tab extends CompositeDisposable {
|
||||
'tab'
|
||||
);
|
||||
},
|
||||
getOverrideTarget: () => group.model.dropTargetContainer?.model,
|
||||
});
|
||||
|
||||
this.onWillShowOverlay = this.dropTarget.onWillShowOverlay;
|
||||
|
||||
this.addDisposables(
|
||||
this._onChanged,
|
||||
this._onPointDown,
|
||||
this._onDropped,
|
||||
this._onDragStart,
|
||||
dragHandler.onDragStart((event) => {
|
||||
if (event.dataTransfer) {
|
||||
const style = getComputedStyle(this.element);
|
||||
const newNode = this.element.cloneNode(true) as HTMLElement;
|
||||
Array.from(style).forEach((key) =>
|
||||
newNode.style.setProperty(
|
||||
key,
|
||||
style.getPropertyValue(key),
|
||||
style.getPropertyPriority(key)
|
||||
)
|
||||
);
|
||||
newNode.style.position = 'absolute';
|
||||
|
||||
addGhostImage(event.dataTransfer, newNode, {
|
||||
y: -10,
|
||||
x: 30,
|
||||
});
|
||||
}
|
||||
this._onDragStart.fire(event);
|
||||
}),
|
||||
dragHandler,
|
||||
addDisposableListener(this._element, 'pointerdown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._onChanged.fire(event);
|
||||
this._onPointDown.fire(event);
|
||||
}),
|
||||
this.dropTarget.onDrop((event) => {
|
||||
this._onDropped.fire(event);
|
||||
|
@ -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,83 @@
|
||||
.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 {
|
||||
&: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 });
|
||||
}
|
||||
}
|
@ -7,17 +7,17 @@
|
||||
font-size: var(--dv-tabs-and-actions-container-font-size);
|
||||
|
||||
&.dv-single-tab.dv-full-width-single-tab {
|
||||
.dv-tabs-container {
|
||||
flex-grow: 1;
|
||||
|
||||
.dv-tab {
|
||||
.dv-tabs-container {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-void-container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
.dv-tab {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-void-container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-void-container {
|
||||
@ -26,46 +26,7 @@
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.dv-tabs-container {
|
||||
.dv-right-actions-container {
|
||||
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;
|
||||
min-width: 75px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
&: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,17 +1,23 @@
|
||||
import {
|
||||
IDisposable,
|
||||
CompositeDisposable,
|
||||
IValueDisposable,
|
||||
Disposable,
|
||||
MutableDisposable,
|
||||
} from '../../../lifecycle';
|
||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||
import { Tab } from '../tab/tab';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { VoidContainer } from './voidContainer';
|
||||
import { toggleClass } from '../../../dom';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
||||
import { IDockviewPanel } from '../../dockviewPanel';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
import { Tabs } from './tabs';
|
||||
import {
|
||||
createDropdownElementHandle,
|
||||
DropdownElement,
|
||||
} from './tabOverflowControl';
|
||||
|
||||
export interface TabDropIndexEvent {
|
||||
readonly event: DragEvent;
|
||||
@ -56,25 +62,28 @@ export class TabsContainer
|
||||
implements ITabsContainer
|
||||
{
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly tabContainer: HTMLElement;
|
||||
private readonly tabs: Tabs;
|
||||
private readonly rightActionsContainer: HTMLElement;
|
||||
private readonly leftActionsContainer: HTMLElement;
|
||||
private readonly preActionsContainer: HTMLElement;
|
||||
private readonly voidContainer: VoidContainer;
|
||||
|
||||
private tabs: IValueDisposable<Tab>[] = [];
|
||||
private selectedIndex = -1;
|
||||
private rightActions: HTMLElement | undefined;
|
||||
private leftActions: HTMLElement | undefined;
|
||||
private preActions: HTMLElement | undefined;
|
||||
|
||||
private _hidden = false;
|
||||
|
||||
private dropdownPart: DropdownElement | null = null;
|
||||
private _overflowTabs: string[] = [];
|
||||
private readonly _dropdownDisposable = new MutableDisposable();
|
||||
|
||||
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
||||
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
||||
|
||||
private readonly _onTabDragStart = new Emitter<TabDragEvent>();
|
||||
readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event;
|
||||
get onTabDragStart(): Event<TabDragEvent> {
|
||||
return this.tabs.onTabDragStart;
|
||||
}
|
||||
|
||||
private readonly _onGroupDragStart = new Emitter<GroupDragEvent>();
|
||||
readonly onGroupDragStart: Event<GroupDragEvent> =
|
||||
@ -86,11 +95,11 @@ export class TabsContainer
|
||||
this._onWillShowOverlay.event;
|
||||
|
||||
get panels(): string[] {
|
||||
return this.tabs.map((_) => _.value.panel.id);
|
||||
return this.tabs.panels;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.tabs.length;
|
||||
return this.tabs.size;
|
||||
}
|
||||
|
||||
get hidden(): boolean {
|
||||
@ -102,6 +111,116 @@ export class TabsContainer
|
||||
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 {
|
||||
if (!this.hidden) {
|
||||
this.element.style.display = '';
|
||||
@ -154,280 +273,124 @@ export class TabsContainer
|
||||
}
|
||||
}
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
isActive(tab: Tab): boolean {
|
||||
return this.tabs.isActive(tab);
|
||||
}
|
||||
|
||||
public isActive(tab: Tab): boolean {
|
||||
return (
|
||||
this.selectedIndex > -1 &&
|
||||
this.tabs[this.selectedIndex].value === tab
|
||||
);
|
||||
indexOf(id: string): number {
|
||||
return this.tabs.indexOf(id);
|
||||
}
|
||||
|
||||
public indexOf(id: string): number {
|
||||
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.accessor.onDidAddPanel((e) => {
|
||||
if (e.api.group === this.group) {
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-single-tab',
|
||||
this.size === 1
|
||||
);
|
||||
}
|
||||
}),
|
||||
this.accessor.onDidRemovePanel((e) => {
|
||||
if (e.api.group === this.group) {
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-single-tab',
|
||||
this.size === 1
|
||||
);
|
||||
}
|
||||
}),
|
||||
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) {
|
||||
setActive(_isGroupActive: boolean) {
|
||||
// noop
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
delete(id: string): void {
|
||||
this.tabs.delete(id);
|
||||
this.updateClassnames();
|
||||
}
|
||||
|
||||
public delete(id: string): void {
|
||||
const index = this.tabs.findIndex((tab) => tab.value.panel.id === id);
|
||||
|
||||
const tabToRemove = this.tabs.splice(index, 1)[0];
|
||||
|
||||
const { value, disposable } = tabToRemove;
|
||||
|
||||
disposable.dispose();
|
||||
value.dispose();
|
||||
value.element.remove();
|
||||
setActivePanel(panel: IDockviewPanel): void {
|
||||
this.tabs.setActivePanel(panel);
|
||||
}
|
||||
|
||||
public setActivePanel(panel: IDockviewPanel): void {
|
||||
this.tabs.forEach((tab) => {
|
||||
const isActivePanel = panel.id === tab.value.panel.id;
|
||||
tab.value.setActive(isActivePanel);
|
||||
});
|
||||
openPanel(panel: IDockviewPanel, index: number = this.tabs.size): void {
|
||||
this.tabs.openPanel(panel, index);
|
||||
this.updateClassnames();
|
||||
}
|
||||
|
||||
public 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.onChanged((event) => {
|
||||
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;
|
||||
}
|
||||
|
||||
const isLeftClick = event.button === 0;
|
||||
|
||||
if (!isLeftClick || event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.group.activePanel !== panel) {
|
||||
this.group.model.openPanel(panel);
|
||||
}
|
||||
}),
|
||||
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 {
|
||||
closePanel(panel: IDockviewPanel): void {
|
||||
this.delete(panel.id);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
private updateClassnames(): void {
|
||||
toggleClass(this._element, 'dv-single-tab', this.size === 1);
|
||||
}
|
||||
|
||||
for (const { value, disposable } of this.tabs) {
|
||||
disposable.dispose();
|
||||
value.dispose();
|
||||
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;
|
||||
}
|
||||
|
||||
this.tabs = [];
|
||||
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,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { last } from '../../../array';
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
import {
|
||||
Droptarget,
|
||||
@ -10,6 +9,7 @@ import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel';
|
||||
|
||||
export class VoidContainer extends CompositeDisposable {
|
||||
private readonly _element: HTMLElement;
|
||||
@ -54,16 +54,7 @@ export class VoidContainer extends CompositeDisposable {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't show the overlay if the tab being dragged is the last panel of this group
|
||||
return last(this.group.panels)?.id !== data.panelId;
|
||||
return true;
|
||||
}
|
||||
|
||||
return group.model.canDisplayOverlay(
|
||||
@ -72,6 +63,7 @@ export class VoidContainer extends CompositeDisposable {
|
||||
'header_space'
|
||||
);
|
||||
},
|
||||
getOverrideTarget: () => group.model.dropTargetContainer?.model,
|
||||
});
|
||||
|
||||
this.onWillShowOverlay = this.dropTraget.onWillShowOverlay;
|
||||
|
@ -18,7 +18,10 @@
|
||||
|
||||
.dv-groupview {
|
||||
&.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 {
|
||||
background-color: var(
|
||||
--dv-activegroup-visiblepanel-tab-background-color
|
||||
@ -34,7 +37,10 @@
|
||||
}
|
||||
}
|
||||
&.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 {
|
||||
background-color: var(
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color
|
||||
|
@ -54,6 +54,7 @@ import { Parameters } from '../panel/types';
|
||||
import { Overlay } from '../overlay/overlay';
|
||||
import {
|
||||
addTestId,
|
||||
Classnames,
|
||||
getDockviewTheme,
|
||||
toggleClass,
|
||||
watchElementResize,
|
||||
@ -73,6 +74,10 @@ import {
|
||||
OverlayRenderContainer,
|
||||
} from '../overlay/overlayRenderContainer';
|
||||
import { PopoutWindow } from '../popoutWindow';
|
||||
import { StrictEventsSequencing } from './strictEventsSequencing';
|
||||
import { PopupService } from './components/popupService';
|
||||
import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer';
|
||||
import { themeAbyss } from './theme';
|
||||
|
||||
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
|
||||
activationSize: { type: 'pixels', value: 10 },
|
||||
@ -190,7 +195,6 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
readonly totalPanels: number;
|
||||
readonly panels: IDockviewPanel[];
|
||||
readonly orientation: Orientation;
|
||||
readonly gap: number;
|
||||
readonly onDidDrop: Event<DockviewDidDropEvent>;
|
||||
readonly onWillDrop: Event<DockviewWillDropEvent>;
|
||||
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent>;
|
||||
@ -252,9 +256,12 @@ export class DockviewComponent
|
||||
private readonly _deserializer = new DefaultDockviewDeserialzier(this);
|
||||
private readonly _api: DockviewApi;
|
||||
private _options: Exclude<DockviewComponentOptions, 'orientation'>;
|
||||
private watermark: IWatermarkRenderer | null = null;
|
||||
private _watermark: IWatermarkRenderer | null = null;
|
||||
private readonly _themeClassnames: Classnames;
|
||||
|
||||
readonly overlayRenderContainer: OverlayRenderContainer;
|
||||
readonly popupService: PopupService;
|
||||
readonly rootDropTargetContainer: DropTargetAnchorContainer;
|
||||
|
||||
private readonly _onWillDragPanel = new Emitter<TabDragEvent>();
|
||||
readonly onWillDragPanel: Event<TabDragEvent> = this._onWillDragPanel.event;
|
||||
@ -319,6 +326,9 @@ export class DockviewComponent
|
||||
readonly onDidAddGroup: Event<DockviewGroupPanel> =
|
||||
this._onDidAddGroup.event;
|
||||
|
||||
private readonly _onDidOptionsChange = new Emitter<void>();
|
||||
readonly onDidOptionsChange: Event<void> = this._onDidOptionsChange.event;
|
||||
|
||||
private readonly _onDidActiveGroupChange = new Emitter<
|
||||
DockviewGroupPanel | undefined
|
||||
>();
|
||||
@ -359,16 +369,12 @@ export class DockviewComponent
|
||||
return this._api;
|
||||
}
|
||||
|
||||
get gap(): number {
|
||||
return this.gridview.margin;
|
||||
}
|
||||
|
||||
get floatingGroups(): DockviewFloatingGroupPanel[] {
|
||||
return this._floatingGroups;
|
||||
}
|
||||
|
||||
constructor(parentElement: HTMLElement, options: DockviewComponentOptions) {
|
||||
super(parentElement, {
|
||||
constructor(container: HTMLElement, options: DockviewComponentOptions) {
|
||||
super(container, {
|
||||
proportionalLayout: true,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
styles: options.hideBorders
|
||||
@ -376,10 +382,20 @@ export class DockviewComponent
|
||||
: undefined,
|
||||
disableAutoResizing: options.disableAutoResizing,
|
||||
locked: options.locked,
|
||||
margin: options.gap,
|
||||
margin: options.theme?.gap ?? 0,
|
||||
className: options.className,
|
||||
});
|
||||
|
||||
this.popupService = new PopupService(this.element);
|
||||
|
||||
this.updateDropTargetModel(options);
|
||||
|
||||
this._themeClassnames = new Classnames(this.element);
|
||||
|
||||
this.rootDropTargetContainer = new DropTargetAnchorContainer(
|
||||
this.element,
|
||||
{ disabled: true }
|
||||
);
|
||||
this.overlayRenderContainer = new OverlayRenderContainer(
|
||||
this.gridview.element,
|
||||
this
|
||||
@ -388,7 +404,12 @@ export class DockviewComponent
|
||||
toggleClass(this.gridview.element, 'dv-dockview', true);
|
||||
toggleClass(this.element, 'dv-debug', !!options.debug);
|
||||
|
||||
if (options.debug) {
|
||||
this.addDisposables(new StrictEventsSequencing(this));
|
||||
}
|
||||
|
||||
this.addDisposables(
|
||||
this.rootDropTargetContainer,
|
||||
this.overlayRenderContainer,
|
||||
this._onWillDragPanel,
|
||||
this._onWillDragGroup,
|
||||
@ -404,6 +425,8 @@ export class DockviewComponent
|
||||
this._onDidRemoveGroup,
|
||||
this._onDidActiveGroupChange,
|
||||
this._onUnhandledDragOverEvent,
|
||||
this._onDidMaximizedGroupChange,
|
||||
this._onDidOptionsChange,
|
||||
this.onDidViewVisibilityChangeMicroTaskQueue(() => {
|
||||
this.updateWatermark();
|
||||
}),
|
||||
@ -458,8 +481,10 @@ export class DockviewComponent
|
||||
);
|
||||
|
||||
this._options = options;
|
||||
this.updateTheme();
|
||||
|
||||
this._rootDropTarget = new Droptarget(this.element, {
|
||||
className: 'dv-drop-target-edge',
|
||||
canDisplayOverlay: (event, position) => {
|
||||
const data = getPanelData();
|
||||
|
||||
@ -500,6 +525,7 @@ export class DockviewComponent
|
||||
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
|
||||
overlayModel:
|
||||
this.options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL,
|
||||
getOverrideTarget: () => this.rootDropTargetContainer?.model,
|
||||
});
|
||||
|
||||
this.addDisposables(
|
||||
@ -706,6 +732,8 @@ export class DockviewComponent
|
||||
_window.window!.innerHeight
|
||||
);
|
||||
|
||||
let floatingBox: AnchoredBox | undefined;
|
||||
|
||||
if (!options?.overridePopoutGroup && isGroupAddedToDom) {
|
||||
if (itemToPopout instanceof DockviewPanel) {
|
||||
this.movingLock(() => {
|
||||
@ -727,7 +755,16 @@ export class DockviewComponent
|
||||
break;
|
||||
case 'floating':
|
||||
case 'popout':
|
||||
floatingBox = this._floatingGroups
|
||||
.find(
|
||||
(value) =>
|
||||
value.group.api.id ===
|
||||
itemToPopout.api.id
|
||||
)
|
||||
?.overlay.toJSON();
|
||||
|
||||
this.removeGroup(referenceGroup);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -739,6 +776,15 @@ export class DockviewComponent
|
||||
|
||||
popoutContainer.appendChild(group.element);
|
||||
|
||||
const anchor = document.createElement('div');
|
||||
const dropTargetContainer = new DropTargetAnchorContainer(
|
||||
anchor,
|
||||
{ disabled: this.rootDropTargetContainer.disabled }
|
||||
);
|
||||
popoutContainer.appendChild(anchor);
|
||||
|
||||
group.model.dropTargetContainer = dropTargetContainer;
|
||||
|
||||
group.model.location = {
|
||||
type: 'popout',
|
||||
getWindow: () => _window.window!,
|
||||
@ -804,6 +850,10 @@ export class DockviewComponent
|
||||
),
|
||||
overlayRenderContainer,
|
||||
Disposable.from(() => {
|
||||
if (this.isDisposed) {
|
||||
return; // cleanup may run after instance is disposed
|
||||
}
|
||||
|
||||
if (
|
||||
isGroupAddedToDom &&
|
||||
this.getPanel(referenceGroup.id)
|
||||
@ -825,21 +875,47 @@ export class DockviewComponent
|
||||
});
|
||||
}
|
||||
} else if (this.getPanel(group.id)) {
|
||||
this.doRemoveGroup(group, {
|
||||
skipDispose: true,
|
||||
skipActive: true,
|
||||
skipPopoutReturn: true,
|
||||
});
|
||||
|
||||
const removedGroup = group;
|
||||
|
||||
removedGroup.model.renderContainer =
|
||||
group.model.renderContainer =
|
||||
this.overlayRenderContainer;
|
||||
removedGroup.model.location = { type: 'grid' };
|
||||
returnedGroup = removedGroup;
|
||||
group.model.dropTargetContainer =
|
||||
this.rootDropTargetContainer;
|
||||
returnedGroup = group;
|
||||
|
||||
this.doAddGroup(removedGroup, [0]);
|
||||
this.doSetGroupAndPanelActive(removedGroup);
|
||||
const alreadyRemoved = !this._popoutGroups.find(
|
||||
(p) => p.popoutGroup === group
|
||||
);
|
||||
|
||||
if (alreadyRemoved) {
|
||||
/**
|
||||
* If this popout group was explicitly removed then we shouldn't run the additional
|
||||
* steps. To tell if the running of this disposable is the result of this popout group
|
||||
* being explicitly removed we can check if this popout group is still referenced in
|
||||
* the `this._popoutGroups` list.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
if (floatingBox) {
|
||||
this.addFloatingGroup(group, {
|
||||
height: floatingBox.height,
|
||||
width: floatingBox.width,
|
||||
position: floatingBox,
|
||||
});
|
||||
} else {
|
||||
this.doRemoveGroup(group, {
|
||||
skipDispose: true,
|
||||
skipActive: true,
|
||||
skipPopoutReturn: true,
|
||||
});
|
||||
|
||||
group.model.location = { type: 'grid' };
|
||||
|
||||
this.movingLock(() => {
|
||||
// suppress group add events since the group already exists
|
||||
this.doAddGroup(group, [0]);
|
||||
});
|
||||
}
|
||||
this.doSetGroupAndPanelActive(group);
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -1117,18 +1193,14 @@ export class DockviewComponent
|
||||
}
|
||||
}
|
||||
|
||||
if ('rootOverlayModel' in options) {
|
||||
this._rootDropTarget.setOverlayModel(
|
||||
options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL
|
||||
);
|
||||
}
|
||||
|
||||
if ('gap' in options) {
|
||||
this.gridview.margin = options.gap ?? 0;
|
||||
}
|
||||
this.updateDropTargetModel(options);
|
||||
|
||||
this._options = { ...this.options, ...options };
|
||||
|
||||
if ('theme' in options) {
|
||||
this.updateTheme();
|
||||
}
|
||||
|
||||
this.layout(this.gridview.width, this.gridview.height, true);
|
||||
}
|
||||
|
||||
@ -1290,6 +1362,7 @@ export class DockviewComponent
|
||||
locked: !!locked,
|
||||
hideHeader: !!hideHeader,
|
||||
});
|
||||
this._onDidAddGroup.fire(group);
|
||||
|
||||
const createdPanels: IDockviewPanel[] = [];
|
||||
|
||||
@ -1306,8 +1379,6 @@ export class DockviewComponent
|
||||
createdPanels.push(panel);
|
||||
}
|
||||
|
||||
this._onDidAddGroup.fire(group);
|
||||
|
||||
for (let i = 0; i < views.length; i++) {
|
||||
const panel = createdPanels[i];
|
||||
|
||||
@ -1394,6 +1465,7 @@ export class DockviewComponent
|
||||
'dockview: failed to deserialize layout. Reverting changes',
|
||||
err
|
||||
);
|
||||
|
||||
/**
|
||||
* Takes all the successfully created groups and remove all of their panels.
|
||||
*/
|
||||
@ -1704,24 +1776,24 @@ export class DockviewComponent
|
||||
(x) => x.api.location.type === 'grid' && x.api.isVisible
|
||||
).length === 0
|
||||
) {
|
||||
if (!this.watermark) {
|
||||
this.watermark = this.createWatermarkComponent();
|
||||
if (!this._watermark) {
|
||||
this._watermark = this.createWatermarkComponent();
|
||||
|
||||
this.watermark.init({
|
||||
this._watermark.init({
|
||||
containerApi: new DockviewApi(this),
|
||||
});
|
||||
|
||||
const watermarkContainer = document.createElement('div');
|
||||
watermarkContainer.className = 'dv-watermark-container';
|
||||
addTestId(watermarkContainer, 'watermark-component');
|
||||
watermarkContainer.appendChild(this.watermark.element);
|
||||
watermarkContainer.appendChild(this._watermark.element);
|
||||
|
||||
this.gridview.element.appendChild(watermarkContainer);
|
||||
}
|
||||
} else if (this.watermark) {
|
||||
this.watermark.element.parentElement!.remove();
|
||||
this.watermark.dispose?.();
|
||||
this.watermark = null;
|
||||
} else if (this._watermark) {
|
||||
this._watermark.element.parentElement!.remove();
|
||||
this._watermark.dispose?.();
|
||||
this._watermark = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1885,7 +1957,7 @@ export class DockviewComponent
|
||||
const refGroup = selectedGroup.referenceGroup
|
||||
? this.getPanel(selectedGroup.referenceGroup)
|
||||
: undefined;
|
||||
if (refGroup) {
|
||||
if (refGroup && refGroup.panels.length === 0) {
|
||||
this.removeGroup(refGroup);
|
||||
}
|
||||
}
|
||||
@ -2081,9 +2153,7 @@ export class DockviewComponent
|
||||
|
||||
const newGroup = this.createGroupAtLocation(targetLocation);
|
||||
this.movingLock(() =>
|
||||
newGroup.model.openPanel(removedPanel, {
|
||||
skipSetActive: true,
|
||||
})
|
||||
newGroup.model.openPanel(removedPanel)
|
||||
);
|
||||
this.doSetGroupAndPanelActive(newGroup);
|
||||
|
||||
@ -2365,9 +2435,11 @@ export class DockviewComponent
|
||||
if (this._moving) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.panel !== this.activePanel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._onDidActivePanelChange.value !== event.panel) {
|
||||
this._onDidActivePanelChange.fire(event.panel);
|
||||
}
|
||||
@ -2450,4 +2522,44 @@ export class DockviewComponent
|
||||
? rootOrientation
|
||||
: orthogonal(rootOrientation);
|
||||
}
|
||||
|
||||
private updateDropTargetModel(options: Partial<DockviewComponentOptions>) {
|
||||
if ('dndEdges' in options) {
|
||||
this._rootDropTarget.disabled =
|
||||
typeof options.dndEdges === 'boolean' &&
|
||||
options.dndEdges === false;
|
||||
|
||||
if (
|
||||
typeof options.dndEdges === 'object' &&
|
||||
options.dndEdges !== null
|
||||
) {
|
||||
this._rootDropTarget.setOverlayModel(options.dndEdges);
|
||||
} else {
|
||||
this._rootDropTarget.setOverlayModel(
|
||||
DEFAULT_ROOT_OVERLAY_MODEL
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ('rootOverlayModel' in options) {
|
||||
this.updateDropTargetModel({ dndEdges: options.dndEdges });
|
||||
}
|
||||
}
|
||||
|
||||
private updateTheme(): void {
|
||||
const theme = this._options.theme ?? themeAbyss;
|
||||
this._themeClassnames.setClassNames(theme.className);
|
||||
|
||||
this.gridview.margin = theme.gap ?? 0;
|
||||
|
||||
switch (theme.dndOverlayMounting) {
|
||||
case 'absolute':
|
||||
this.rootDropTargetContainer.disabled = false;
|
||||
break;
|
||||
case 'relative':
|
||||
default:
|
||||
this.rootDropTargetContainer.disabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +124,12 @@ export class DockviewGroupPanel
|
||||
options,
|
||||
this
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
this.model.onDidActivePanelChange((event) => {
|
||||
this.api._onDidActivePanelChange.fire(event);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override focus(): void {
|
||||
|
@ -39,6 +39,7 @@ import {
|
||||
import { OverlayRenderContainer } from '../overlay/overlayRenderContainer';
|
||||
import { TitleEvent } from '../api/dockviewPanelApi';
|
||||
import { Contraints } from '../gridview/gridviewPanel';
|
||||
import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer';
|
||||
|
||||
interface GroupMoveEvent {
|
||||
groupId: string;
|
||||
@ -265,6 +266,8 @@ export class DockviewGroupPanelModel
|
||||
|
||||
private mostRecentlyUsed: IDockviewPanel[] = [];
|
||||
private _overwriteRenderContainer: OverlayRenderContainer | null = null;
|
||||
private _overwriteDropTargetContainer: DropTargetAnchorContainer | null =
|
||||
null;
|
||||
|
||||
private readonly _onDidChange = new Emitter<IViewSize | undefined>();
|
||||
readonly onDidChange: Event<IViewSize | undefined> =
|
||||
@ -506,7 +509,9 @@ export class DockviewGroupPanelModel
|
||||
this._onDidAddPanel,
|
||||
this._onDidRemovePanel,
|
||||
this._onDidActivePanelChange,
|
||||
this._onUnhandledDragOverEvent
|
||||
this._onUnhandledDragOverEvent,
|
||||
this._onDidPanelTitleChange,
|
||||
this._onDidPanelParametersChange
|
||||
);
|
||||
}
|
||||
|
||||
@ -533,6 +538,17 @@ export class DockviewGroupPanelModel
|
||||
);
|
||||
}
|
||||
|
||||
set dropTargetContainer(value: DropTargetAnchorContainer | null) {
|
||||
this._overwriteDropTargetContainer = value;
|
||||
}
|
||||
|
||||
get dropTargetContainer(): DropTargetAnchorContainer | null {
|
||||
return (
|
||||
this._overwriteDropTargetContainer ??
|
||||
this.accessor.rootDropTargetContainer
|
||||
);
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
if (this.options.panels) {
|
||||
this.options.panels.forEach((panel) => {
|
||||
@ -1047,6 +1063,29 @@ export class DockviewGroupPanelModel
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
if (type === 'content') {
|
||||
if (data.groupId === this.id) {
|
||||
// don't allow to drop on self for center position
|
||||
|
||||
if (position === 'center') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.panelId === null) {
|
||||
// don't allow group move to drop anywhere on self
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'header') {
|
||||
if (data.groupId === this.id) {
|
||||
if (data.panelId === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.panelId === null) {
|
||||
// this is a group move dnd event
|
||||
const { groupId } = data;
|
||||
|
@ -4,10 +4,10 @@ import {
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
} from './types';
|
||||
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { IDockviewComponent } from './dockviewComponent';
|
||||
import { PanelUpdateEvent } from '../panel/types';
|
||||
import { TabLocation } from './framework';
|
||||
|
||||
export interface IDockviewPanelModel extends IDisposable {
|
||||
readonly contentComponent: string;
|
||||
@ -17,13 +17,16 @@ export interface IDockviewPanelModel extends IDisposable {
|
||||
update(event: PanelUpdateEvent): void;
|
||||
layout(width: number, height: number): void;
|
||||
init(params: GroupPanelPartInitParameters): void;
|
||||
updateParentGroup(group: DockviewGroupPanel, isPanelVisible: boolean): void;
|
||||
createTabRenderer(tabLocation: TabLocation): ITabRenderer;
|
||||
}
|
||||
|
||||
export class DockviewPanelModel implements IDockviewPanelModel {
|
||||
private readonly _content: IContentRenderer;
|
||||
private readonly _tab: ITabRenderer;
|
||||
|
||||
private _params: GroupPanelPartInitParameters | undefined;
|
||||
private _updateEvent: PanelUpdateEvent | undefined;
|
||||
|
||||
get content(): IContentRenderer {
|
||||
return this._content;
|
||||
}
|
||||
@ -42,16 +45,23 @@ export class DockviewPanelModel implements IDockviewPanelModel {
|
||||
this._tab = this.createTabComponent(this.id, tabComponent);
|
||||
}
|
||||
|
||||
init(params: GroupPanelPartInitParameters): void {
|
||||
this.content.init(params);
|
||||
this.tab.init(params);
|
||||
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
|
||||
const cmp = this.createTabComponent(this.id, this.tabComponent);
|
||||
if (this._params) {
|
||||
cmp.init({ ...this._params, tabLocation });
|
||||
}
|
||||
if (this._updateEvent) {
|
||||
cmp.update?.(this._updateEvent);
|
||||
}
|
||||
|
||||
return cmp;
|
||||
}
|
||||
|
||||
updateParentGroup(
|
||||
_group: DockviewGroupPanel,
|
||||
_isPanelVisible: boolean
|
||||
): void {
|
||||
// noop
|
||||
init(params: GroupPanelPartInitParameters): void {
|
||||
this._params = params;
|
||||
|
||||
this.content.init(params);
|
||||
this.tab.init({ ...params, tabLocation: 'header' });
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
@ -59,6 +69,8 @@ export class DockviewPanelModel implements IDockviewPanelModel {
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent): void {
|
||||
this._updateEvent = event;
|
||||
|
||||
this.content.update?.(event);
|
||||
this.tab.update?.(event);
|
||||
}
|
||||
|
@ -11,9 +11,11 @@ export interface IGroupPanelBaseProps<T extends { [index: string]: any } = any>
|
||||
containerApi: DockviewApi;
|
||||
}
|
||||
|
||||
export type TabLocation = 'header' | 'headerOverflow';
|
||||
|
||||
export type IDockviewPanelHeaderProps<
|
||||
T extends { [index: string]: any } = any
|
||||
> = IGroupPanelBaseProps<T>;
|
||||
> = IGroupPanelBaseProps<T> & { tabLocation: TabLocation };
|
||||
|
||||
export type IDockviewPanelProps<T extends { [index: string]: any } = any> =
|
||||
IGroupPanelBaseProps<T>;
|
||||
|
@ -16,6 +16,8 @@ import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
|
||||
import { IGroupHeaderProps } from './framework';
|
||||
import { FloatingGroupOptions } from './dockviewComponent';
|
||||
import { Contraints } from '../gridview/gridviewPanel';
|
||||
import { AcceptableEvent, IAcceptableEvent } from '../events';
|
||||
import { DockviewTheme } from './theme';
|
||||
|
||||
export interface IHeaderActionsRenderer extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
@ -51,52 +53,48 @@ export interface DockviewOptions {
|
||||
popoutUrl?: string;
|
||||
defaultRenderer?: DockviewPanelRenderer;
|
||||
debug?: boolean;
|
||||
rootOverlayModel?: DroptargetOverlayModel;
|
||||
locked?: boolean;
|
||||
disableDnd?: boolean;
|
||||
className?: string;
|
||||
// #start dnd
|
||||
dndEdges?: false | DroptargetOverlayModel;
|
||||
/**
|
||||
* Pixel gap between groups
|
||||
*/
|
||||
gap?: number;
|
||||
* @deprecated use `dndEdges` instead. To be removed in a future version.
|
||||
* */
|
||||
rootOverlayModel?: DroptargetOverlayModel;
|
||||
disableDnd?: boolean;
|
||||
// #end dnd
|
||||
locked?: boolean;
|
||||
className?: string;
|
||||
/**
|
||||
* Define the behaviour of the dock when there are no panels to display. Defaults to `watermark`.
|
||||
*/
|
||||
noPanelsOverlay?: 'emptyGroup' | 'watermark';
|
||||
theme?: DockviewTheme;
|
||||
disableTabsOverflowList?: boolean;
|
||||
}
|
||||
|
||||
export interface DockviewDndOverlayEvent {
|
||||
export interface DockviewDndOverlayEvent extends IAcceptableEvent {
|
||||
nativeEvent: DragEvent;
|
||||
target: DockviewGroupDropLocation;
|
||||
position: Position;
|
||||
group?: DockviewGroupPanel;
|
||||
getData: () => PanelTransfer | undefined;
|
||||
//
|
||||
isAccepted: boolean;
|
||||
accept(): void;
|
||||
}
|
||||
|
||||
export class DockviewUnhandledDragOverEvent implements DockviewDndOverlayEvent {
|
||||
private _isAccepted = false;
|
||||
|
||||
get isAccepted(): boolean {
|
||||
return this._isAccepted;
|
||||
}
|
||||
|
||||
export class DockviewUnhandledDragOverEvent
|
||||
extends AcceptableEvent
|
||||
implements DockviewDndOverlayEvent
|
||||
{
|
||||
constructor(
|
||||
readonly nativeEvent: DragEvent,
|
||||
readonly target: DockviewGroupDropLocation,
|
||||
readonly position: Position,
|
||||
readonly getData: () => PanelTransfer | undefined,
|
||||
readonly group?: DockviewGroupPanel
|
||||
) {}
|
||||
|
||||
accept(): void {
|
||||
this._isAccepted = true;
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export const PROPERTY_KEYS: (keyof DockviewOptions)[] = (() => {
|
||||
export const PROPERTY_KEYS_DOCKVIEW: (keyof DockviewOptions)[] = (() => {
|
||||
/**
|
||||
* by readong the keys from an empty value object TypeScript will error
|
||||
* when we add or remove new properties to `DockviewOptions`
|
||||
@ -113,9 +111,11 @@ export const PROPERTY_KEYS: (keyof DockviewOptions)[] = (() => {
|
||||
rootOverlayModel: undefined,
|
||||
locked: undefined,
|
||||
disableDnd: undefined,
|
||||
gap: undefined,
|
||||
className: undefined,
|
||||
noPanelsOverlay: undefined,
|
||||
dndEdges: undefined,
|
||||
theme: undefined,
|
||||
disableTabsOverflowList: undefined,
|
||||
};
|
||||
|
||||
return Object.keys(properties) as (keyof DockviewOptions)[];
|
||||
|
@ -0,0 +1,54 @@
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { DockviewComponent } from './dockviewComponent';
|
||||
|
||||
export class StrictEventsSequencing extends CompositeDisposable {
|
||||
constructor(private readonly accessor: DockviewComponent) {
|
||||
super();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
const panels = new Set<string>();
|
||||
const groups = new Set<string>();
|
||||
|
||||
this.addDisposables(
|
||||
this.accessor.onDidAddPanel((panel) => {
|
||||
if (panels.has(panel.api.id)) {
|
||||
throw new Error(
|
||||
`dockview: Invalid event sequence. [onDidAddPanel] called for panel ${panel.api.id} but panel already exists`
|
||||
);
|
||||
} else {
|
||||
panels.add(panel.api.id);
|
||||
}
|
||||
}),
|
||||
this.accessor.onDidRemovePanel((panel) => {
|
||||
if (!panels.has(panel.api.id)) {
|
||||
throw new Error(
|
||||
`dockview: Invalid event sequence. [onDidRemovePanel] called for panel ${panel.api.id} but panel does not exists`
|
||||
);
|
||||
} else {
|
||||
panels.delete(panel.api.id);
|
||||
}
|
||||
}),
|
||||
this.accessor.onDidAddGroup((group) => {
|
||||
if (groups.has(group.api.id)) {
|
||||
throw new Error(
|
||||
`dockview: Invalid event sequence. [onDidAddGroup] called for group ${group.api.id} but group already exists`
|
||||
);
|
||||
} else {
|
||||
groups.add(group.api.id);
|
||||
}
|
||||
}),
|
||||
this.accessor.onDidRemoveGroup((group) => {
|
||||
if (!groups.has(group.api.id)) {
|
||||
throw new Error(
|
||||
`dockview: Invalid event sequence. [onDidRemoveGroup] called for group ${group.api.id} but group does not exists`
|
||||
);
|
||||
} else {
|
||||
groups.delete(group.api.id);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
70
packages/dockview-core/src/dockview/theme.ts
Normal file
70
packages/dockview-core/src/dockview/theme.ts
Normal file
@ -0,0 +1,70 @@
|
||||
export interface DockviewTheme {
|
||||
/**
|
||||
* The name of the theme
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* The class name to apply to the theme containing the CSS variables settings.
|
||||
*/
|
||||
className: string;
|
||||
/**
|
||||
* The gap between the groups
|
||||
*/
|
||||
gap?: number;
|
||||
/**
|
||||
* The mouting position of the overlay shown when dragging a panel. `absolute`
|
||||
* will mount the overlay to root of the dockview component whereas `relative` will mount the overlay to the group container.
|
||||
*/
|
||||
dndOverlayMounting?: 'absolute' | 'relative';
|
||||
/**
|
||||
* When dragging a panel, the overlay can either encompass the panel contents or the entire group including the tab header space.
|
||||
*/
|
||||
dndPanelOverlay?: 'content' | 'group';
|
||||
}
|
||||
|
||||
export const themeDark: DockviewTheme = {
|
||||
name: 'dark',
|
||||
className: 'dockview-theme-dark',
|
||||
};
|
||||
|
||||
export const themeLight: DockviewTheme = {
|
||||
name: 'light',
|
||||
className: 'dockview-theme-light',
|
||||
};
|
||||
|
||||
export const themeVisualStudio: DockviewTheme = {
|
||||
name: 'visualStudio',
|
||||
className: 'dockview-theme-vs',
|
||||
};
|
||||
|
||||
export const themeAbyss: DockviewTheme = {
|
||||
name: 'abyss',
|
||||
className: 'dockview-theme-abyss',
|
||||
};
|
||||
|
||||
export const themeDracula: DockviewTheme = {
|
||||
name: 'dracula',
|
||||
className: 'dockview-theme-dracula',
|
||||
};
|
||||
|
||||
export const themeReplit: DockviewTheme = {
|
||||
name: 'replit',
|
||||
className: 'dockview-theme-replit',
|
||||
gap: 10,
|
||||
};
|
||||
|
||||
export const themeAbyssSpaced: DockviewTheme = {
|
||||
name: 'abyssSpaced',
|
||||
className: 'dockview-theme-abyss-spaced',
|
||||
gap: 10,
|
||||
dndOverlayMounting: 'absolute',
|
||||
dndPanelOverlay: 'group',
|
||||
};
|
||||
|
||||
export const themeLightSpaced: DockviewTheme = {
|
||||
name: 'lightSpaced',
|
||||
className: 'dockview-theme-light-spaced',
|
||||
gap: 10,
|
||||
dndOverlayMounting: 'absolute',
|
||||
dndPanelOverlay: 'group',
|
||||
};
|
@ -4,6 +4,7 @@ import { DockviewApi } from '../api/component.api';
|
||||
import { Optional } from '../types';
|
||||
import { IDockviewGroupPanel } from './dockviewGroupPanel';
|
||||
import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
|
||||
import { TabLocation } from './framework';
|
||||
|
||||
export interface HeaderPartInitParameters {
|
||||
title: string;
|
||||
@ -34,10 +35,14 @@ export interface IWatermarkRenderer
|
||||
init: (params: WatermarkRendererInitParameters) => void;
|
||||
}
|
||||
|
||||
export interface TabPartInitParameters extends GroupPanelPartInitParameters {
|
||||
tabLocation: TabLocation;
|
||||
}
|
||||
|
||||
export interface ITabRenderer
|
||||
extends Optional<Omit<IPanel, 'id'>, RendererMethodOptionalList> {
|
||||
readonly element: HTMLElement;
|
||||
init(parameters: GroupPanelPartInitParameters): void;
|
||||
init(parameters: TabPartInitParameters): void;
|
||||
}
|
||||
|
||||
export interface IContentRenderer
|
||||
|
@ -379,3 +379,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;
|
||||
}
|
||||
|
@ -41,6 +41,23 @@ export class DockviewEvent implements IDockviewEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IAcceptableEvent {
|
||||
readonly isAccepted: boolean;
|
||||
accept(): void;
|
||||
}
|
||||
|
||||
export class AcceptableEvent implements IAcceptableEvent {
|
||||
private _isAccepted = false;
|
||||
|
||||
get isAccepted(): boolean {
|
||||
return this._isAccepted;
|
||||
}
|
||||
|
||||
accept(): void {
|
||||
this._isAccepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
class LeakageMonitor {
|
||||
readonly events = new Map<Event<any>, Stacktrace>();
|
||||
|
||||
|
@ -11,6 +11,9 @@ import { Classnames } from '../dom';
|
||||
|
||||
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 function toTarget(direction: Direction): Position {
|
||||
@ -155,7 +158,7 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
this.gridview.locked = value;
|
||||
}
|
||||
|
||||
constructor(parentElement: HTMLElement, options: BaseGridOptions) {
|
||||
constructor(container: HTMLElement, options: BaseGridOptions) {
|
||||
super(document.createElement('div'), options.disableAutoResizing);
|
||||
this.element.style.height = '100%';
|
||||
this.element.style.width = '100%';
|
||||
@ -163,7 +166,8 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
this._classNames = new Classnames(this.element);
|
||||
this._classNames.setClassNames(options.className ?? '');
|
||||
|
||||
parentElement.appendChild(this.element);
|
||||
// the container is owned by the third-party, do not modify/delete it
|
||||
container.appendChild(this.element);
|
||||
|
||||
this.gridview = new Gridview(
|
||||
!!options.proportionalLayout,
|
||||
@ -205,6 +209,8 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
)(() => {
|
||||
this._bufferOnDidLayoutChange.fire();
|
||||
}),
|
||||
this._onDidMaximizedChange,
|
||||
this._onDidViewVisibilityChangeMicroTaskQueue,
|
||||
this._bufferOnDidLayoutChange
|
||||
);
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import {
|
||||
} from './gridviewPanel';
|
||||
import { BaseComponentOptions, Parameters } from '../panel/types';
|
||||
import { Orientation, Sizing } from '../splitview/splitview';
|
||||
import { createComponent } from '../panel/componentFactory';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
|
||||
@ -114,11 +113,13 @@ export class GridviewComponent
|
||||
this._deserializer = value;
|
||||
}
|
||||
|
||||
constructor(parentElement: HTMLElement, options: GridviewComponentOptions) {
|
||||
super(parentElement, {
|
||||
proportionalLayout: options.proportionalLayout,
|
||||
constructor(container: HTMLElement, options: GridviewComponentOptions) {
|
||||
super(container, {
|
||||
proportionalLayout: options.proportionalLayout ?? true,
|
||||
orientation: options.orientation,
|
||||
styles: options.styles,
|
||||
styles: options.hideBorders
|
||||
? { separatorBorder: 'transparent' }
|
||||
: undefined,
|
||||
disableAutoResizing: options.disableAutoResizing,
|
||||
className: options.className,
|
||||
});
|
||||
@ -139,13 +140,6 @@ export class GridviewComponent
|
||||
this._onDidActiveGroupChange.fire(event);
|
||||
})
|
||||
);
|
||||
|
||||
if (!this.options.components) {
|
||||
this.options.components = {};
|
||||
}
|
||||
if (!this.options.frameworkComponents) {
|
||||
this.options.frameworkComponents = {};
|
||||
}
|
||||
}
|
||||
|
||||
override updateOptions(options: Partial<GridviewComponentOptions>): void {
|
||||
@ -216,19 +210,11 @@ export class GridviewComponent
|
||||
this.gridview.deserialize(grid, {
|
||||
fromJSON: (node) => {
|
||||
const { data } = node;
|
||||
const view = createComponent(
|
||||
data.id,
|
||||
data.component,
|
||||
this.options.components ?? {},
|
||||
this.options.frameworkComponents ?? {},
|
||||
this.options.frameworkComponentFactory
|
||||
? {
|
||||
createComponent:
|
||||
this.options.frameworkComponentFactory
|
||||
.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
|
||||
const view = this.options.createComponent({
|
||||
id: data.id,
|
||||
name: data.component,
|
||||
});
|
||||
|
||||
queue.push(() =>
|
||||
view.init({
|
||||
@ -363,19 +349,10 @@ export class GridviewComponent
|
||||
}
|
||||
}
|
||||
|
||||
const view = createComponent(
|
||||
options.id,
|
||||
options.component,
|
||||
this.options.components ?? {},
|
||||
this.options.frameworkComponents ?? {},
|
||||
this.options.frameworkComponentFactory
|
||||
? {
|
||||
createComponent:
|
||||
this.options.frameworkComponentFactory
|
||||
.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
const view = this.options.createComponent({
|
||||
id: options.id,
|
||||
name: options.component,
|
||||
});
|
||||
|
||||
view.init({
|
||||
params: options.params ?? {},
|
||||
|
@ -1,21 +1,34 @@
|
||||
import { GridviewPanel } from './gridviewPanel';
|
||||
import { ISplitviewStyles, Orientation } from '../splitview/splitview';
|
||||
import {
|
||||
ComponentConstructor,
|
||||
FrameworkFactory,
|
||||
} from '../panel/componentFactory';
|
||||
import { Orientation } from '../splitview/splitview';
|
||||
import { CreateComponentOptions } from '../dockview/options';
|
||||
|
||||
export interface GridviewComponentOptions {
|
||||
export interface GridviewOptions {
|
||||
disableAutoResizing?: boolean;
|
||||
proportionalLayout: boolean;
|
||||
proportionalLayout?: boolean;
|
||||
orientation: Orientation;
|
||||
components?: {
|
||||
[componentName: string]: ComponentConstructor<GridviewPanel>;
|
||||
};
|
||||
frameworkComponents?: {
|
||||
[componentName: string]: any;
|
||||
};
|
||||
frameworkComponentFactory?: FrameworkFactory<GridviewPanel>;
|
||||
styles?: ISplitviewStyles;
|
||||
className?: string;
|
||||
hideBorders?: boolean;
|
||||
}
|
||||
|
||||
export interface GridviewFrameworkOptions {
|
||||
createComponent: (options: CreateComponentOptions) => GridviewPanel;
|
||||
}
|
||||
|
||||
export type GridviewComponentOptions = GridviewOptions &
|
||||
GridviewFrameworkOptions;
|
||||
|
||||
export const PROPERTY_KEYS_GRIDVIEW: (keyof GridviewOptions)[] = (() => {
|
||||
/**
|
||||
* by readong the keys from an empty value object TypeScript will error
|
||||
* when we add or remove new properties to `DockviewOptions`
|
||||
*/
|
||||
const properties: Record<keyof GridviewOptions, undefined> = {
|
||||
disableAutoResizing: undefined,
|
||||
proportionalLayout: undefined,
|
||||
orientation: undefined,
|
||||
hideBorders: undefined,
|
||||
className: undefined,
|
||||
};
|
||||
|
||||
return Object.keys(properties) as (keyof GridviewOptions)[];
|
||||
})();
|
||||
|
@ -24,16 +24,24 @@ export * from './splitview/splitview';
|
||||
export {
|
||||
SplitviewComponentOptions,
|
||||
PanelViewInitParameters,
|
||||
SplitviewOptions,
|
||||
SplitviewFrameworkOptions,
|
||||
PROPERTY_KEYS_SPLITVIEW,
|
||||
} from './splitview/options';
|
||||
|
||||
export * from './paneview/paneview';
|
||||
export * from './gridview/gridview';
|
||||
export { GridviewComponentOptions } from './gridview/options';
|
||||
export {
|
||||
GridviewComponentOptions,
|
||||
GridviewOptions,
|
||||
GridviewFrameworkOptions,
|
||||
PROPERTY_KEYS_GRIDVIEW,
|
||||
} from './gridview/options';
|
||||
export * from './gridview/baseComponentGridview';
|
||||
|
||||
export {
|
||||
DraggablePaneviewPanel,
|
||||
PaneviewDropEvent,
|
||||
PaneviewDidDropEvent as PaneviewDropEvent,
|
||||
} from './paneview/draggablePaneviewPanel';
|
||||
|
||||
export * from './dockview/components/panel/content';
|
||||
@ -56,6 +64,7 @@ export {
|
||||
} from './dockview/framework';
|
||||
|
||||
export * from './dockview/options';
|
||||
export * from './dockview/theme';
|
||||
export * from './dockview/dockviewPanel';
|
||||
export { DefaultTab } from './dockview/components/tab/defaultTab';
|
||||
export {
|
||||
@ -67,7 +76,14 @@ export * from './dockview/dockviewComponent';
|
||||
export * from './gridview/gridviewComponent';
|
||||
export * from './splitview/splitviewComponent';
|
||||
export * from './paneview/paneviewComponent';
|
||||
export { PaneviewComponentOptions } from './paneview/options';
|
||||
export {
|
||||
PaneviewComponentOptions,
|
||||
PaneviewOptions,
|
||||
PaneviewFrameworkOptions,
|
||||
PROPERTY_KEYS_PANEVIEW,
|
||||
PaneviewUnhandledDragOverEvent,
|
||||
PaneviewDndOverlayEvent,
|
||||
} from './paneview/options';
|
||||
|
||||
export * from './gridview/gridviewPanel';
|
||||
export { SplitviewPanel, ISplitviewPanel } from './splitview/splitviewPanel';
|
||||
|
@ -1,58 +0,0 @@
|
||||
export interface FrameworkFactory<T> {
|
||||
createComponent: (id: string, componentId: string, component: any) => T;
|
||||
}
|
||||
|
||||
export type ComponentConstructor<T> = {
|
||||
new (id: string, component: string): T;
|
||||
};
|
||||
|
||||
export function createComponent<T>(
|
||||
id: string,
|
||||
componentName?: string,
|
||||
components: {
|
||||
[componentName: string]: ComponentConstructor<T>;
|
||||
} = {},
|
||||
frameworkComponents: {
|
||||
[componentName: string]: any;
|
||||
} = {},
|
||||
createFrameworkComponent?: FrameworkFactory<T>,
|
||||
fallback?: () => T
|
||||
): T {
|
||||
const Component =
|
||||
typeof componentName === 'string'
|
||||
? components[componentName]
|
||||
: undefined;
|
||||
const FrameworkComponent =
|
||||
typeof componentName === 'string'
|
||||
? frameworkComponents[componentName]
|
||||
: undefined;
|
||||
|
||||
if (Component && FrameworkComponent) {
|
||||
throw new Error(
|
||||
`Cannot create '${id}'. component '${componentName}' registered as both a component and frameworkComponent`
|
||||
);
|
||||
}
|
||||
if (FrameworkComponent) {
|
||||
if (!createFrameworkComponent) {
|
||||
throw new Error(
|
||||
`Cannot create '${id}' for framework component '${componentName}'. you must register a frameworkPanelWrapper to use framework components`
|
||||
);
|
||||
}
|
||||
return createFrameworkComponent.createComponent(
|
||||
id,
|
||||
componentName!,
|
||||
FrameworkComponent
|
||||
);
|
||||
}
|
||||
|
||||
if (!Component) {
|
||||
if (fallback) {
|
||||
return fallback();
|
||||
}
|
||||
throw new Error(
|
||||
`Cannot create '${id}', no component '${componentName}' provided`
|
||||
);
|
||||
}
|
||||
|
||||
return new Component(id, componentName!);
|
||||
}
|
@ -2,14 +2,11 @@ import { addDisposableListener } from '../events';
|
||||
import { PaneviewPanelApiImpl } from '../api/paneviewPanelApi';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { PanelUpdateEvent } from '../panel/types';
|
||||
import { IPaneHeaderPart, PanePanelInitParameter } from './paneviewPanel';
|
||||
import { IPanePart, PanePanelInitParameter } from './paneviewPanel';
|
||||
import { toggleClass } from '../dom';
|
||||
import { createChevronRightButton, createExpandMoreButton } from '../svg';
|
||||
|
||||
export class DefaultHeader
|
||||
extends CompositeDisposable
|
||||
implements IPaneHeaderPart
|
||||
{
|
||||
export class DefaultHeader extends CompositeDisposable implements IPanePart {
|
||||
private readonly _expandedIcon = createExpandMoreButton();
|
||||
private readonly _collapsedIcon = createChevronRightButton();
|
||||
private readonly disposable = new MutableDisposable();
|
||||
|
@ -6,9 +6,13 @@ import {
|
||||
PaneTransfer,
|
||||
} from '../dnd/dataTransfer';
|
||||
import { Droptarget, DroptargetEvent } from '../dnd/droptarget';
|
||||
import { Emitter } from '../events';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { Orientation } from '../splitview/splitview';
|
||||
import {
|
||||
PaneviewDndOverlayEvent,
|
||||
PaneviewUnhandledDragOverEvent,
|
||||
} from './options';
|
||||
import { IPaneviewComponent } from './paneviewComponent';
|
||||
import {
|
||||
IPaneviewPanel,
|
||||
@ -16,7 +20,7 @@ import {
|
||||
PaneviewPanel,
|
||||
} from './paneviewPanel';
|
||||
|
||||
export interface PaneviewDropEvent extends DroptargetEvent {
|
||||
export interface PaneviewDidDropEvent extends DroptargetEvent {
|
||||
panel: IPaneviewPanel;
|
||||
getData: () => PaneTransfer | undefined;
|
||||
api: PaneviewApi;
|
||||
@ -26,9 +30,14 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
private handler: DragHandler | undefined;
|
||||
private target: Droptarget | undefined;
|
||||
|
||||
private readonly _onDidDrop = new Emitter<PaneviewDropEvent>();
|
||||
private readonly _onDidDrop = new Emitter<PaneviewDidDropEvent>();
|
||||
readonly onDidDrop = this._onDidDrop.event;
|
||||
|
||||
private readonly _onUnhandledDragOverEvent =
|
||||
new Emitter<PaneviewDndOverlayEvent>();
|
||||
readonly onUnhandledDragOverEvent: Event<PaneviewDndOverlayEvent> =
|
||||
this._onUnhandledDragOverEvent.event;
|
||||
|
||||
constructor(
|
||||
private readonly accessor: IPaneviewComponent,
|
||||
id: string,
|
||||
@ -40,6 +49,8 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
) {
|
||||
super(id, component, headerComponent, orientation, isExpanded, true);
|
||||
|
||||
this.addDisposables(this._onDidDrop, this._onUnhandledDragOverEvent);
|
||||
|
||||
if (!disableDnd) {
|
||||
this.initDragFeatures();
|
||||
}
|
||||
@ -76,7 +87,7 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
overlayModel: {
|
||||
activationSize: { type: 'percentage', value: 50 },
|
||||
},
|
||||
canDisplayOverlay: (event) => {
|
||||
canDisplayOverlay: (event, position) => {
|
||||
const data = getPaneData();
|
||||
|
||||
if (data) {
|
||||
@ -88,15 +99,16 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.accessor.options.showDndOverlay) {
|
||||
return this.accessor.options.showDndOverlay({
|
||||
nativeEvent: event,
|
||||
getData: getPaneData,
|
||||
panel: this,
|
||||
});
|
||||
}
|
||||
const firedEvent = new PaneviewUnhandledDragOverEvent(
|
||||
event,
|
||||
position,
|
||||
getPaneData,
|
||||
this
|
||||
);
|
||||
|
||||
return false;
|
||||
this._onUnhandledDragOverEvent.fire(firedEvent);
|
||||
|
||||
return firedEvent.isAccepted;
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,29 +1,56 @@
|
||||
import {
|
||||
ComponentConstructor,
|
||||
FrameworkFactory,
|
||||
} from '../panel/componentFactory';
|
||||
import { PaneviewDndOverlayEvent } from './paneviewComponent';
|
||||
import { IPaneBodyPart, IPaneHeaderPart, PaneviewPanel } from './paneviewPanel';
|
||||
import { PaneTransfer } from '../dnd/dataTransfer';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { CreateComponentOptions } from '../dockview/options';
|
||||
import { AcceptableEvent, IAcceptableEvent } from '../events';
|
||||
import { IPanePart, IPaneviewPanel } from './paneviewPanel';
|
||||
|
||||
export interface PaneviewComponentOptions {
|
||||
export interface PaneviewOptions {
|
||||
disableAutoResizing?: boolean;
|
||||
components?: {
|
||||
[componentName: string]: ComponentConstructor<PaneviewPanel>;
|
||||
};
|
||||
frameworkComponents?: {
|
||||
[componentName: string]: any;
|
||||
};
|
||||
headerComponents?: {
|
||||
[componentName: string]: ComponentConstructor<PaneviewPanel>;
|
||||
};
|
||||
headerframeworkComponents?: {
|
||||
[componentName: string]: any;
|
||||
};
|
||||
frameworkWrapper?: {
|
||||
header: FrameworkFactory<IPaneHeaderPart>;
|
||||
body: FrameworkFactory<IPaneBodyPart>;
|
||||
};
|
||||
disableDnd?: boolean;
|
||||
showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface PaneviewFrameworkOptions {
|
||||
createComponent: (options: CreateComponentOptions) => IPanePart;
|
||||
createHeaderComponent?: (
|
||||
options: CreateComponentOptions
|
||||
) => IPanePart | undefined;
|
||||
}
|
||||
|
||||
export type PaneviewComponentOptions = PaneviewOptions &
|
||||
PaneviewFrameworkOptions;
|
||||
|
||||
export const PROPERTY_KEYS_PANEVIEW: (keyof PaneviewOptions)[] = (() => {
|
||||
/**
|
||||
* by readong the keys from an empty value object TypeScript will error
|
||||
* when we add or remove new properties to `DockviewOptions`
|
||||
*/
|
||||
const properties: Record<keyof PaneviewOptions, undefined> = {
|
||||
disableAutoResizing: undefined,
|
||||
disableDnd: undefined,
|
||||
className: undefined,
|
||||
};
|
||||
|
||||
return Object.keys(properties) as (keyof PaneviewOptions)[];
|
||||
})();
|
||||
|
||||
export interface PaneviewDndOverlayEvent extends IAcceptableEvent {
|
||||
nativeEvent: DragEvent;
|
||||
position: Position;
|
||||
panel: IPaneviewPanel;
|
||||
getData: () => PaneTransfer | undefined;
|
||||
}
|
||||
|
||||
export class PaneviewUnhandledDragOverEvent
|
||||
extends AcceptableEvent
|
||||
implements PaneviewDndOverlayEvent
|
||||
{
|
||||
constructor(
|
||||
readonly nativeEvent: DragEvent,
|
||||
readonly position: Position,
|
||||
readonly getData: () => PaneTransfer | undefined,
|
||||
readonly panel: IPaneviewPanel
|
||||
) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { PaneviewApi } from '../api/component.api';
|
||||
import { createComponent } from '../panel/componentFactory';
|
||||
import { Emitter, Event } from '../events';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
@ -7,33 +6,21 @@ import {
|
||||
MutableDisposable,
|
||||
} from '../lifecycle';
|
||||
import { LayoutPriority, Orientation, Sizing } from '../splitview/splitview';
|
||||
import { PaneviewComponentOptions } from './options';
|
||||
import { PaneviewComponentOptions, PaneviewDndOverlayEvent } from './options';
|
||||
import { Paneview } from './paneview';
|
||||
import {
|
||||
IPaneBodyPart,
|
||||
IPaneHeaderPart,
|
||||
PaneviewPanel,
|
||||
IPaneviewPanel,
|
||||
} from './paneviewPanel';
|
||||
import { IPanePart, PaneviewPanel, IPaneviewPanel } from './paneviewPanel';
|
||||
import {
|
||||
DraggablePaneviewPanel,
|
||||
PaneviewDropEvent,
|
||||
PaneviewDidDropEvent,
|
||||
} from './draggablePaneviewPanel';
|
||||
import { DefaultHeader } from './defaultPaneviewHeader';
|
||||
import { sequentialNumberGenerator } from '../math';
|
||||
import { PaneTransfer } from '../dnd/dataTransfer';
|
||||
import { Resizable } from '../resizable';
|
||||
import { Parameters } from '../panel/types';
|
||||
import { Classnames } from '../dom';
|
||||
|
||||
const nextLayoutId = sequentialNumberGenerator();
|
||||
|
||||
export interface PaneviewDndOverlayEvent {
|
||||
nativeEvent: DragEvent;
|
||||
panel: IPaneviewPanel;
|
||||
getData: () => PaneTransfer | undefined;
|
||||
}
|
||||
|
||||
export interface SerializedPaneviewPanel {
|
||||
snap?: boolean;
|
||||
priority?: LayoutPriority;
|
||||
@ -61,8 +48,8 @@ export class PaneFramework extends DraggablePaneviewPanel {
|
||||
id: string;
|
||||
component: string;
|
||||
headerComponent: string | undefined;
|
||||
body: IPaneBodyPart;
|
||||
header: IPaneHeaderPart;
|
||||
body: IPanePart;
|
||||
header: IPanePart;
|
||||
orientation: Orientation;
|
||||
isExpanded: boolean;
|
||||
disableDnd: boolean;
|
||||
@ -112,9 +99,10 @@ export interface IPaneviewComponent extends IDisposable {
|
||||
readonly options: PaneviewComponentOptions;
|
||||
readonly onDidAddView: Event<PaneviewPanel>;
|
||||
readonly onDidRemoveView: Event<PaneviewPanel>;
|
||||
readonly onDidDrop: Event<PaneviewDropEvent>;
|
||||
readonly onDidDrop: Event<PaneviewDidDropEvent>;
|
||||
readonly onDidLayoutChange: Event<void>;
|
||||
readonly onDidLayoutFromJSON: Event<void>;
|
||||
readonly onUnhandledDragOverEvent: Event<PaneviewDndOverlayEvent>;
|
||||
addPanel<T extends object = Parameters>(
|
||||
options: AddPaneviewComponentOptions<T>
|
||||
): IPaneviewPanel;
|
||||
@ -143,8 +131,8 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
|
||||
private readonly _onDidLayoutChange = new Emitter<void>();
|
||||
readonly onDidLayoutChange: Event<void> = this._onDidLayoutChange.event;
|
||||
|
||||
private readonly _onDidDrop = new Emitter<PaneviewDropEvent>();
|
||||
readonly onDidDrop: Event<PaneviewDropEvent> = this._onDidDrop.event;
|
||||
private readonly _onDidDrop = new Emitter<PaneviewDidDropEvent>();
|
||||
readonly onDidDrop: Event<PaneviewDidDropEvent> = this._onDidDrop.event;
|
||||
|
||||
private readonly _onDidAddView = new Emitter<PaneviewPanel>();
|
||||
readonly onDidAddView = this._onDidAddView.event;
|
||||
@ -152,6 +140,11 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
|
||||
private readonly _onDidRemoveView = new Emitter<PaneviewPanel>();
|
||||
readonly onDidRemoveView = this._onDidRemoveView.event;
|
||||
|
||||
private readonly _onUnhandledDragOverEvent =
|
||||
new Emitter<PaneviewDndOverlayEvent>();
|
||||
readonly onUnhandledDragOverEvent: Event<PaneviewDndOverlayEvent> =
|
||||
this._onUnhandledDragOverEvent.event;
|
||||
|
||||
private readonly _classNames: Classnames;
|
||||
|
||||
get id(): string {
|
||||
@ -202,28 +195,27 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
constructor(parentElement: HTMLElement, options: PaneviewComponentOptions) {
|
||||
super(parentElement, options.disableAutoResizing);
|
||||
constructor(container: HTMLElement, options: PaneviewComponentOptions) {
|
||||
super(document.createElement('div'), options.disableAutoResizing);
|
||||
this.element.style.height = '100%';
|
||||
this.element.style.width = '100%';
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidLayoutChange,
|
||||
this._onDidLayoutfromJSON,
|
||||
this._onDidDrop,
|
||||
this._onDidAddView,
|
||||
this._onDidRemoveView
|
||||
this._onDidRemoveView,
|
||||
this._onUnhandledDragOverEvent
|
||||
);
|
||||
|
||||
this._classNames = new Classnames(this.element);
|
||||
this._classNames.setClassNames(options.className ?? '');
|
||||
|
||||
this._options = options;
|
||||
// the container is owned by the third-party, do not modify/delete it
|
||||
container.appendChild(this.element);
|
||||
|
||||
if (!options.components) {
|
||||
options.components = {};
|
||||
}
|
||||
if (!options.frameworkComponents) {
|
||||
options.frameworkComponents = {};
|
||||
}
|
||||
this._options = options;
|
||||
|
||||
this.paneview = new Paneview(this.element, {
|
||||
// only allow paneview in the vertical orientation for now
|
||||
@ -257,36 +249,21 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
|
||||
addPanel<T extends object = Parameters>(
|
||||
options: AddPaneviewComponentOptions<T>
|
||||
): IPaneviewPanel {
|
||||
const body = createComponent(
|
||||
options.id,
|
||||
options.component,
|
||||
this.options.components ?? {},
|
||||
this.options.frameworkComponents ?? {},
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent:
|
||||
this.options.frameworkWrapper.body.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
const body = this.options.createComponent({
|
||||
id: options.id,
|
||||
name: options.component,
|
||||
});
|
||||
|
||||
let header: IPaneHeaderPart;
|
||||
let header: IPanePart | undefined;
|
||||
|
||||
if (options.headerComponent) {
|
||||
header = createComponent(
|
||||
options.id,
|
||||
options.headerComponent,
|
||||
this.options.headerComponents ?? {},
|
||||
this.options.headerframeworkComponents,
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent:
|
||||
this.options.frameworkWrapper.header
|
||||
.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
} else {
|
||||
if (options.headerComponent && this.options.createHeaderComponent) {
|
||||
header = this.options.createHeaderComponent({
|
||||
id: options.id,
|
||||
name: options.headerComponent,
|
||||
});
|
||||
}
|
||||
|
||||
if (!header) {
|
||||
header = new DefaultHeader();
|
||||
}
|
||||
|
||||
@ -395,37 +372,24 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
|
||||
views: views.map((view) => {
|
||||
const data = view.data;
|
||||
|
||||
const body = createComponent(
|
||||
data.id,
|
||||
data.component,
|
||||
this.options.components ?? {},
|
||||
this.options.frameworkComponents ?? {},
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent:
|
||||
this.options.frameworkWrapper.body
|
||||
.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
const body = this.options.createComponent({
|
||||
id: data.id,
|
||||
name: data.component,
|
||||
});
|
||||
|
||||
let header: IPaneHeaderPart;
|
||||
let header: IPanePart | undefined;
|
||||
|
||||
if (data.headerComponent) {
|
||||
header = createComponent(
|
||||
data.id,
|
||||
data.headerComponent,
|
||||
this.options.headerComponents ?? {},
|
||||
this.options.headerframeworkComponents ?? {},
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent:
|
||||
this.options.frameworkWrapper.header
|
||||
.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
} else {
|
||||
if (
|
||||
data.headerComponent &&
|
||||
this.options.createHeaderComponent
|
||||
) {
|
||||
header = this.options.createHeaderComponent({
|
||||
id: data.id,
|
||||
name: data.headerComponent,
|
||||
});
|
||||
}
|
||||
|
||||
if (!header) {
|
||||
header = new DefaultHeader();
|
||||
}
|
||||
|
||||
@ -483,9 +447,14 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
|
||||
}
|
||||
|
||||
private doAddPanel(panel: PaneFramework): void {
|
||||
const disposable = panel.onDidDrop((event) => {
|
||||
this._onDidDrop.fire(event);
|
||||
});
|
||||
const disposable = new CompositeDisposable(
|
||||
panel.onDidDrop((event) => {
|
||||
this._onDidDrop.fire(event);
|
||||
}),
|
||||
panel.onUnhandledDragOverEvent((event) => {
|
||||
this._onUnhandledDragOverEvent.fire(event);
|
||||
})
|
||||
);
|
||||
|
||||
this._viewDisposables.set(panel.id, disposable);
|
||||
}
|
||||
@ -507,6 +476,8 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
|
||||
}
|
||||
this._viewDisposables.clear();
|
||||
|
||||
this.element.remove();
|
||||
|
||||
this.paneview.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -36,13 +36,7 @@ export interface PanePanelComponentInitParameter
|
||||
api: PaneviewPanelApiImpl;
|
||||
}
|
||||
|
||||
export interface IPaneBodyPart extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
update(params: PanelUpdateEvent): void;
|
||||
init(parameters: PanePanelComponentInitParameter): void;
|
||||
}
|
||||
|
||||
export interface IPaneHeaderPart extends IDisposable {
|
||||
export interface IPanePart extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
update(params: PanelUpdateEvent): void;
|
||||
init(parameters: PanePanelComponentInitParameter): void;
|
||||
@ -85,8 +79,8 @@ export abstract class PaneviewPanel
|
||||
private _isExpanded = false;
|
||||
protected header?: HTMLElement;
|
||||
protected body?: HTMLElement;
|
||||
private bodyPart?: IPaneHeaderPart;
|
||||
private headerPart?: IPaneBodyPart;
|
||||
private bodyPart?: IPanePart;
|
||||
private headerPart?: IPanePart;
|
||||
private expandedSize = 0;
|
||||
private animationTimer: any;
|
||||
private _orientation: Orientation;
|
||||
@ -338,6 +332,6 @@ export abstract class PaneviewPanel
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract getBodyComponent(): IPaneBodyPart;
|
||||
protected abstract getHeaderComponent(): IPaneHeaderPart;
|
||||
protected abstract getBodyComponent(): IPanePart;
|
||||
protected abstract getHeaderComponent(): IPanePart;
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,7 @@ import { PanelInitParameters } from '../panel/types';
|
||||
import { SplitViewOptions, LayoutPriority } from './splitview';
|
||||
import { SplitviewPanel } from './splitviewPanel';
|
||||
import { SplitviewComponent } from './splitviewComponent';
|
||||
import {
|
||||
ComponentConstructor,
|
||||
FrameworkFactory,
|
||||
} from '../panel/componentFactory';
|
||||
import { CreateComponentOptions } from '../dockview/options';
|
||||
|
||||
export interface PanelViewInitParameters extends PanelInitParameters {
|
||||
minimumSize?: number;
|
||||
@ -15,14 +12,32 @@ export interface PanelViewInitParameters extends PanelInitParameters {
|
||||
accessor: SplitviewComponent;
|
||||
}
|
||||
|
||||
export interface SplitviewComponentOptions extends SplitViewOptions {
|
||||
export interface SplitviewOptions extends SplitViewOptions {
|
||||
disableAutoResizing?: boolean;
|
||||
components?: {
|
||||
[componentName: string]: ComponentConstructor<SplitviewPanel>;
|
||||
};
|
||||
frameworkComponents?: {
|
||||
[componentName: string]: any;
|
||||
};
|
||||
frameworkWrapper?: FrameworkFactory<SplitviewPanel>;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface SplitviewFrameworkOptions {
|
||||
createComponent: (options: CreateComponentOptions) => SplitviewPanel;
|
||||
}
|
||||
|
||||
export type SplitviewComponentOptions = SplitviewOptions &
|
||||
SplitviewFrameworkOptions;
|
||||
|
||||
export const PROPERTY_KEYS_SPLITVIEW: (keyof SplitviewOptions)[] = (() => {
|
||||
/**
|
||||
* by readong the keys from an empty value object TypeScript will error
|
||||
* when we add or remove new properties to `DockviewOptions`
|
||||
*/
|
||||
const properties: Record<keyof SplitviewOptions, undefined> = {
|
||||
orientation: undefined,
|
||||
descriptor: undefined,
|
||||
proportionalLayout: undefined,
|
||||
styles: undefined,
|
||||
margin: undefined,
|
||||
disableAutoResizing: undefined,
|
||||
className: undefined,
|
||||
};
|
||||
|
||||
return Object.keys(properties) as (keyof SplitviewOptions)[];
|
||||
})();
|
||||
|
@ -116,16 +116,18 @@
|
||||
-moz-user-select: none; // Firefox
|
||||
-ms-user-select: none; // IE 10 and IE 11
|
||||
touch-action: none;
|
||||
background-color: var(--dv-sash-color, transparent);
|
||||
|
||||
&:not(.disabled):active {
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
background-color: var(--dv-active-sash-color, transparent);
|
||||
}
|
||||
|
||||
&:not(.disabled):active,
|
||||
&:not(.disabled):hover {
|
||||
background-color: var(--dv-active-sash-color, transparent);
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
transition-delay: 0.5s;
|
||||
transition-property: background-color;
|
||||
transition-timing-function: ease-in-out;
|
||||
transition-duration: var(
|
||||
--dv-active-sash-transition-duration,
|
||||
0.1s
|
||||
);
|
||||
transition-delay: var(--dv-active-sash-transition-delay, 0.5s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,11 +32,11 @@ export interface ISplitviewStyles {
|
||||
}
|
||||
|
||||
export interface SplitViewOptions {
|
||||
readonly orientation: Orientation;
|
||||
readonly descriptor?: ISplitViewDescriptor;
|
||||
readonly proportionalLayout?: boolean;
|
||||
readonly styles?: ISplitviewStyles;
|
||||
readonly margin?: number;
|
||||
orientation?: Orientation;
|
||||
descriptor?: ISplitViewDescriptor;
|
||||
proportionalLayout?: boolean;
|
||||
styles?: ISplitviewStyles;
|
||||
margin?: number;
|
||||
}
|
||||
|
||||
export enum LayoutPriority {
|
||||
@ -219,13 +219,15 @@ export class Splitview {
|
||||
|
||||
set margin(value: number) {
|
||||
this._margin = value;
|
||||
|
||||
toggleClass(this.element, 'dv-splitview-has-margin', value !== 0);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly container: HTMLElement,
|
||||
options: SplitViewOptions
|
||||
) {
|
||||
this._orientation = options.orientation;
|
||||
this._orientation = options.orientation ?? Orientation.VERTICAL;
|
||||
this.element = this.createContainer();
|
||||
|
||||
this.margin = options.margin ?? 0;
|
||||
|
@ -15,7 +15,6 @@ import { SplitviewComponentOptions } from './options';
|
||||
import { BaseComponentOptions, Parameters } from '../panel/types';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { SplitviewPanel, ISplitviewPanel } from './splitviewPanel';
|
||||
import { createComponent } from '../panel/componentFactory';
|
||||
import { Resizable } from '../resizable';
|
||||
import { Classnames } from '../dom';
|
||||
|
||||
@ -156,23 +155,18 @@ export class SplitviewComponent
|
||||
: this.splitview.orthogonalSize;
|
||||
}
|
||||
|
||||
constructor(
|
||||
parentElement: HTMLElement,
|
||||
options: SplitviewComponentOptions
|
||||
) {
|
||||
super(parentElement, options.disableAutoResizing);
|
||||
constructor(container: HTMLElement, options: SplitviewComponentOptions) {
|
||||
super(document.createElement('div'), options.disableAutoResizing);
|
||||
this.element.style.height = '100%';
|
||||
this.element.style.width = '100%';
|
||||
|
||||
this._classNames = new Classnames(this.element);
|
||||
this._classNames.setClassNames(options.className ?? '');
|
||||
|
||||
this._options = options;
|
||||
// the container is owned by the third-party, do not modify/delete it
|
||||
container.appendChild(this.element);
|
||||
|
||||
if (!options.components) {
|
||||
options.components = {};
|
||||
}
|
||||
if (!options.frameworkComponents) {
|
||||
options.frameworkComponents = {};
|
||||
}
|
||||
this._options = options;
|
||||
|
||||
this.splitview = new Splitview(this.element, options);
|
||||
|
||||
@ -267,18 +261,10 @@ export class SplitviewComponent
|
||||
throw new Error(`panel ${options.id} already exists`);
|
||||
}
|
||||
|
||||
const view = createComponent(
|
||||
options.id,
|
||||
options.component,
|
||||
this.options.components ?? {},
|
||||
this.options.frameworkComponents ?? {},
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent:
|
||||
this.options.frameworkWrapper.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
const view = this.options.createComponent({
|
||||
id: options.id,
|
||||
name: options.component,
|
||||
});
|
||||
|
||||
view.orientation = this.splitview.orientation;
|
||||
|
||||
@ -367,19 +353,10 @@ export class SplitviewComponent
|
||||
throw new Error(`panel ${data.id} already exists`);
|
||||
}
|
||||
|
||||
const panel = createComponent(
|
||||
data.id,
|
||||
data.component,
|
||||
this.options.components ?? {},
|
||||
this.options.frameworkComponents ?? {},
|
||||
this.options.frameworkWrapper
|
||||
? {
|
||||
createComponent:
|
||||
this.options.frameworkWrapper
|
||||
.createComponent,
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
const panel = this.options.createComponent({
|
||||
id: data.id,
|
||||
name: data.component,
|
||||
});
|
||||
|
||||
queue.push(() => {
|
||||
panel.init({
|
||||
@ -448,6 +425,8 @@ export class SplitviewComponent
|
||||
view.dispose();
|
||||
}
|
||||
|
||||
this.element.remove();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,31 @@
|
||||
@import 'theme/_sash-handle-mixin';
|
||||
@import 'theme/_drop-target-static-mixin';
|
||||
@import 'theme/_space-mixin';
|
||||
|
||||
@mixin dockview-theme-core-mixin {
|
||||
--dv-paneview-active-outline-color: dodgerblue;
|
||||
--dv-tabs-and-actions-container-font-size: 13px;
|
||||
--dv-tabs-and-actions-container-height: 35px;
|
||||
--dv-drag-over-background-color: rgba(83, 89, 93, 0.5);
|
||||
--dv-drag-over-border-color: white;
|
||||
--dv-drag-over-border-color: transparent;
|
||||
--dv-tabs-container-scrollbar-color: #888;
|
||||
--dv-icon-hover-background-color: rgba(90, 93, 94, 0.31);
|
||||
--dv-floating-box-shadow: 8px 8px 8px 0px rgba(83, 89, 93, 0.5);
|
||||
--dv-overlay-z-index: 999;
|
||||
//
|
||||
|
||||
--dv-tab-font-size: inherit;
|
||||
--dv-border-radius: 0px;
|
||||
--dv-tab-margin: 0;
|
||||
--dv-sash-color: transparent;
|
||||
--dv-active-sash-color: transparent;
|
||||
--dv-active-sash-transition-duration: 0.1s;
|
||||
--dv-active-sash-transition-delay: 0.5s;
|
||||
}
|
||||
|
||||
@mixin dockview-theme-dark-mixin {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #1e1e1e;
|
||||
@ -35,6 +49,8 @@
|
||||
|
||||
@mixin dockview-theme-light-mixin {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: white;
|
||||
//
|
||||
@ -53,6 +69,8 @@
|
||||
//
|
||||
--dv-separator-border: rgba(128, 128, 128, 0.35);
|
||||
--dv-paneview-header-border-color: rgb(51, 51, 51);
|
||||
|
||||
--dv-scrollbar-background-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.dockview-theme-dark {
|
||||
@ -131,30 +149,49 @@
|
||||
|
||||
@mixin dockview-theme-abyss-mixin {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
--dv-color-abyss-dark: #000c18;
|
||||
--dv-color-abyss: #10192c;
|
||||
--dv-color-abyss-light: #1c1c2a;
|
||||
--dv-color-abyss-lighter: #2b2b4a;
|
||||
--dv-color-abyss-accent: rgb(91, 30, 207);
|
||||
|
||||
--dv-color-abyss-primary-text: white;
|
||||
--dv-color-abyss-secondary-text: rgb(148, 151, 169);
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #000c18;
|
||||
--dv-group-view-background-color: var(--dv-color-abyss-dark);
|
||||
//
|
||||
--dv-tabs-and-actions-container-background-color: #1c1c2a;
|
||||
--dv-tabs-and-actions-container-background-color: var(
|
||||
--dv-color-abyss-light
|
||||
);
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-background-color: #000c18;
|
||||
--dv-activegroup-hiddenpanel-tab-background-color: #10192c;
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color: #000c18;
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color: #10192c;
|
||||
--dv-tab-divider-color: #2b2b4a;
|
||||
--dv-activegroup-visiblepanel-tab-background-color: var(
|
||||
--dv-color-abyss-dark
|
||||
);
|
||||
--dv-activegroup-hiddenpanel-tab-background-color: var(--dv-color-abyss);
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color: var(
|
||||
--dv-color-abyss-dark
|
||||
);
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color: var(--dv-color-abyss);
|
||||
--dv-tab-divider-color: var(--dv-color-abyss-lighter);
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-color: white;
|
||||
--dv-activegroup-hiddenpanel-tab-color: rgba(255, 255, 255, 0.5);
|
||||
--dv-inactivegroup-visiblepanel-tab-color: rgba(255, 255, 255, 0.5);
|
||||
--dv-inactivegroup-hiddenpanel-tab-color: rgba(255, 255, 255, 0.25);
|
||||
//
|
||||
--dv-separator-border: #2b2b4a;
|
||||
--dv-paneview-header-border-color: #2b2b4a;
|
||||
--dv-separator-border: var(--dv-color-abyss-lighter);
|
||||
--dv-paneview-header-border-color: var(--dv-color-abyss-lighter);
|
||||
|
||||
--dv-paneview-active-outline-color: #596f99;
|
||||
}
|
||||
|
||||
@mixin dockview-theme-dracula-mixin {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #282a36;
|
||||
//
|
||||
@ -179,19 +216,21 @@
|
||||
.dv-groupview {
|
||||
&.dv-active-group {
|
||||
> .dv-tabs-and-actions-container {
|
||||
> .dv-tabs-container {
|
||||
> .dv-tab.dv-active-tab {
|
||||
position: relative;
|
||||
> .dv-scrollable {
|
||||
> .dv-tabs-container {
|
||||
> .dv-tab.dv-active-tab {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #94527e;
|
||||
z-index: 999;
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #94527e;
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,19 +238,21 @@
|
||||
}
|
||||
&.dv-inactive-group {
|
||||
> .dv-tabs-and-actions-container {
|
||||
> .dv-tabs-container {
|
||||
> .dv-tab.dv-active-tab {
|
||||
position: relative;
|
||||
> .dv-scrollable {
|
||||
> .dv-tabs-container {
|
||||
> .dv-tab.dv-active-tab {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #5e3d5a;
|
||||
z-index: 999;
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: #5e3d5a;
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -229,10 +270,17 @@
|
||||
}
|
||||
|
||||
@mixin dockview-design-replit-mixin {
|
||||
@include dockview-drop-target-no-travel();
|
||||
|
||||
.dv-resize-container:has(> .dv-groupview) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dv-resize-container {
|
||||
border-radius: 10px !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dv-groupview {
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
@ -266,59 +314,16 @@
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical > .sash-container > .sash {
|
||||
&:not(.disabled) {
|
||||
&::after {
|
||||
content: '';
|
||||
height: 4px;
|
||||
width: 40px;
|
||||
border-radius: 2px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--dv-separator-handle-background-color);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
background-color: var(
|
||||
--dv-separator-handle-hover-background-color
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dv-horizontal > .dv-sash-container > .dv-sash {
|
||||
&:not(.disabled) {
|
||||
&::after {
|
||||
content: '';
|
||||
height: 40px;
|
||||
width: 4px;
|
||||
border-radius: 2px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--dv-separator-handle-background-color);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
background-color: var(
|
||||
--dv-separator-handle-hover-background-color
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dockview-theme-replit {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-design-replit-mixin();
|
||||
@include dockview-design-handle-mixin();
|
||||
|
||||
padding: 10px;
|
||||
background-color: #ebeced;
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #ebeced;
|
||||
//
|
||||
@ -339,6 +344,117 @@
|
||||
--dv-paneview-header-border-color: rgb(51, 51, 51);
|
||||
|
||||
/////
|
||||
--dv-separator-handle-background-color: #cfd1d3;
|
||||
--dv-separator-handle-hover-background-color: #babbbb;
|
||||
--dv-sash-color: #cfd1d3;
|
||||
--dv-active-sash-color: #babbbb;
|
||||
}
|
||||
|
||||
.dockview-theme-abyss-spaced {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-design-space-mixin();
|
||||
|
||||
// stylesheet
|
||||
--dv-color-abyss-dark: rgb(11, 6, 17);
|
||||
--dv-color-abyss: #16121f;
|
||||
--dv-color-abyss-light: #201d2b;
|
||||
--dv-color-abyss-lighter: #2a2837;
|
||||
--dv-color-abyss-accent: rgb(91, 30, 207);
|
||||
--dv-color-abyss-primary-text: white;
|
||||
--dv-color-abyss-secondary-text: rgb(148, 151, 169);
|
||||
|
||||
//
|
||||
--dv-drag-over-border: 2px solid var(--dv-color-abyss-accent);
|
||||
--dv-drag-over-background-color: '';
|
||||
//
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: var(--dv-color-abyss-dark);
|
||||
//
|
||||
--dv-tabs-and-actions-container-background-color: var(--dv-color-abyss);
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-background-color: var(
|
||||
--dv-color-abyss-lighter
|
||||
);
|
||||
--dv-activegroup-hiddenpanel-tab-background-color: var(
|
||||
--dv-color-abyss-light
|
||||
);
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color: var(
|
||||
--dv-color-abyss-lighter
|
||||
);
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color: var(
|
||||
--dv-color-abyss-light
|
||||
);
|
||||
--dv-tab-divider-color: transparent;
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-color: var(--dv-color-abyss-primary-text);
|
||||
--dv-activegroup-hiddenpanel-tab-color: var(
|
||||
--dv-color-abyss-secondary-text
|
||||
);
|
||||
--dv-inactivegroup-visiblepanel-tab-color: var(
|
||||
--dv-color-abyss-primary-text
|
||||
);
|
||||
--dv-inactivegroup-hiddenpanel-tab-color: var(
|
||||
--dv-color-abyss-secondary-text
|
||||
);
|
||||
//
|
||||
--dv-separator-border: transparent;
|
||||
--dv-paneview-header-border-color: rgb(51, 51, 51);
|
||||
|
||||
/////
|
||||
--dv-active-sash-color: var(--dv-color-abyss-accent);
|
||||
//
|
||||
--dv-floating-box-shadow: 8px 8px 8px 0px rgba(0, 0, 0, 0.5);
|
||||
|
||||
padding: 10px;
|
||||
background-color: var(--dv-color-abyss-dark);
|
||||
|
||||
.dv-resize-container {
|
||||
.dv-groupview {
|
||||
border: 2px solid var(--dv-color-abyss-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dockview-theme-light-spaced {
|
||||
@include dockview-theme-core-mixin();
|
||||
@include dockview-design-space-mixin();
|
||||
|
||||
//
|
||||
--dv-drag-over-border: 2px solid rgb(91, 30, 207);
|
||||
--dv-drag-over-background-color: '';
|
||||
//
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #f6f5f9;
|
||||
//
|
||||
--dv-tabs-and-actions-container-background-color: white;
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-background-color: #ededf0;
|
||||
--dv-activegroup-hiddenpanel-tab-background-color: #f9f9fa;
|
||||
--dv-inactivegroup-visiblepanel-tab-background-color: #ededf0;
|
||||
--dv-inactivegroup-hiddenpanel-tab-background-color: #f9f9fa;
|
||||
--dv-tab-divider-color: transparent;
|
||||
//
|
||||
--dv-activegroup-visiblepanel-tab-color: rgb(104, 107, 130);
|
||||
--dv-activegroup-hiddenpanel-tab-color: rgb(148, 151, 169);
|
||||
--dv-inactivegroup-visiblepanel-tab-color: rgb(104, 107, 130);
|
||||
--dv-inactivegroup-hiddenpanel-tab-color: rgb(148, 151, 169);
|
||||
//
|
||||
--dv-separator-border: transparent;
|
||||
--dv-paneview-header-border-color: rgb(51, 51, 51);
|
||||
|
||||
/////
|
||||
--dv-active-sash-color: rgb(91, 30, 207);
|
||||
//
|
||||
--dv-floating-box-shadow: 8px 8px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
padding: 10px;
|
||||
background-color: #f6f5f9;
|
||||
|
||||
--dv-scrollbar-background-color: rgba(0, 0, 0, 0.25);
|
||||
|
||||
.dv-resize-container {
|
||||
.dv-groupview {
|
||||
border: 2px solid rgb(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
@mixin dockview-drop-target-no-travel {
|
||||
.dv-drop-target-container {
|
||||
.dv-drop-target-anchor {
|
||||
&.dv-drop-target-anchor-container-changed {
|
||||
opacity: 0;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
packages/dockview-core/src/theme/_sash-handle-mixin.scss
Normal file
53
packages/dockview-core/src/theme/_sash-handle-mixin.scss
Normal file
@ -0,0 +1,53 @@
|
||||
@mixin dockview-design-handle-mixin {
|
||||
.dv-vertical > .dv-sash-container > .dv-sash {
|
||||
background-color: transparent;
|
||||
|
||||
&:not(.disabled) {
|
||||
&::after {
|
||||
content: '';
|
||||
height: 4px;
|
||||
width: 40px;
|
||||
border-radius: 2px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--dv-sash-color);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: transparent;
|
||||
&::after {
|
||||
background-color: var(--dv-active-sash-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dv-horizontal > .dv-sash-container > .dv-sash {
|
||||
background-color: transparent;
|
||||
|
||||
&:not(.disabled) {
|
||||
&::after {
|
||||
content: '';
|
||||
height: 40px;
|
||||
width: 4px;
|
||||
border-radius: 2px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--dv-sash-color);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: transparent;
|
||||
&::after {
|
||||
background-color: var(--dv-active-sash-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
57
packages/dockview-core/src/theme/_space-mixin.scss
Normal file
57
packages/dockview-core/src/theme/_space-mixin.scss
Normal file
@ -0,0 +1,57 @@
|
||||
@mixin dockview-design-space-mixin {
|
||||
--dv-tab-font-size: 12px;
|
||||
--dv-border-radius: 20px;
|
||||
--dv-tab-margin: 0.5rem 0.25rem;
|
||||
--dv-tabs-and-actions-container-height: 44px;
|
||||
|
||||
--dv-border-radius: 20px;
|
||||
|
||||
.dv-resize-container:has(> .dv-groupview) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dv-sash {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dv-drop-target-anchor {
|
||||
border-radius: calc(var(--dv-border-radius) / 4);
|
||||
&.dv-drop-target-content {
|
||||
border-radius: var(--dv-border-radius);
|
||||
}
|
||||
}
|
||||
|
||||
.dv-resize-container {
|
||||
border-radius: var(--dv-border-radius) !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dv-tabs-overflow-container,
|
||||
.dv-tabs-overflow-dropdown-default {
|
||||
border-radius: 8px;
|
||||
height: unset !important;
|
||||
}
|
||||
|
||||
.dv-tab {
|
||||
border-radius: 8px;
|
||||
|
||||
.dv-svg {
|
||||
height: 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 {
|
||||
background-color: var(
|
||||
--dv-tabs-and-actions-container-background-color
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-react",
|
||||
"version": "2.0.0",
|
||||
"version": "4.0.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -54,6 +54,6 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview": "^2.0.0"
|
||||
"dockview": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-vue",
|
||||
"version": "2.0.0",
|
||||
"version": "4.0.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -52,6 +52,9 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^2.0.0"
|
||||
"dockview-core": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.4.0"
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
import {
|
||||
DockviewApi,
|
||||
type DockviewOptions,
|
||||
PROPERTY_KEYS,
|
||||
PROPERTY_KEYS_DOCKVIEW,
|
||||
type DockviewFrameworkOptions,
|
||||
createDockview,
|
||||
} from 'dockview-core';
|
||||
@ -25,7 +25,7 @@ import {
|
||||
import type { IDockviewVueProps, VueEvents } from './types';
|
||||
|
||||
function extractCoreOptions(props: IDockviewVueProps): DockviewOptions {
|
||||
const coreOptions = (PROPERTY_KEYS as (keyof DockviewOptions)[]).reduce(
|
||||
const coreOptions = (PROPERTY_KEYS_DOCKVIEW as (keyof DockviewOptions)[]).reduce(
|
||||
(obj, key) => {
|
||||
(obj as any)[key] = props[key];
|
||||
return obj;
|
||||
@ -43,7 +43,7 @@ const props = defineProps<IDockviewVueProps>();
|
||||
const el = ref<HTMLElement | null>(null);
|
||||
const instance = ref<DockviewApi | null>(null);
|
||||
|
||||
PROPERTY_KEYS.forEach((coreOptionKey) => {
|
||||
PROPERTY_KEYS_DOCKVIEW.forEach((coreOptionKey) => {
|
||||
watch(
|
||||
() => props[coreOptionKey],
|
||||
(newValue, oldValue) => {
|
||||
|
@ -2,7 +2,6 @@ import type {
|
||||
DockviewApi,
|
||||
DockviewGroupPanel,
|
||||
DockviewPanelApi,
|
||||
GroupPanelPartInitParameters,
|
||||
IContentRenderer,
|
||||
IDockviewPanelHeaderProps,
|
||||
IGroupHeaderProps,
|
||||
@ -12,6 +11,7 @@ import type {
|
||||
IWatermarkRenderer,
|
||||
PanelUpdateEvent,
|
||||
Parameters,
|
||||
TabPartInitParameters,
|
||||
WatermarkRendererInitParameters,
|
||||
} from 'dockview-core';
|
||||
import {
|
||||
@ -121,7 +121,7 @@ export class VueRenderer
|
||||
private _api: DockviewPanelApi | undefined;
|
||||
private _containerApi: DockviewApi | undefined;
|
||||
|
||||
init(parameters: GroupPanelPartInitParameters): void {
|
||||
init(parameters: TabPartInitParameters): void {
|
||||
this._api = parameters.api;
|
||||
this._containerApi = parameters.containerApi;
|
||||
|
||||
@ -129,6 +129,7 @@ export class VueRenderer
|
||||
params: parameters.params,
|
||||
api: parameters.api,
|
||||
containerApi: parameters.containerApi,
|
||||
tabLocation: parameters.tabLocation,
|
||||
};
|
||||
|
||||
this._renderDisposable?.dispose();
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview",
|
||||
"version": "2.0.0",
|
||||
"version": "4.0.0",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -54,7 +54,7 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^2.0.0"
|
||||
"dockview-core": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
|
@ -10,13 +10,16 @@ import { Disposable } from 'dockview-core/dist/cjs/lifecycle';
|
||||
describe('defaultTab', () => {
|
||||
test('has close button by default', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
onDidTitleChange: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
render(
|
||||
<DockviewDefaultTab
|
||||
tabLocation="header"
|
||||
api={api}
|
||||
containerApi={containerApi}
|
||||
params={params}
|
||||
@ -30,13 +33,16 @@ describe('defaultTab', () => {
|
||||
test('that title is displayed', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
title: 'test_title',
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
onDidTitleChange: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
render(
|
||||
<DockviewDefaultTab
|
||||
tabLocation="header"
|
||||
api={api}
|
||||
containerApi={containerApi}
|
||||
params={params}
|
||||
@ -61,6 +67,7 @@ describe('defaultTab', () => {
|
||||
|
||||
render(
|
||||
<DockviewDefaultTab
|
||||
tabLocation="header"
|
||||
api={api}
|
||||
containerApi={containerApi}
|
||||
params={params}
|
||||
@ -84,13 +91,16 @@ describe('defaultTab', () => {
|
||||
|
||||
test('has no close button when hideClose=true', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
onDidTitleChange: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
render(
|
||||
<DockviewDefaultTab
|
||||
tabLocation="header"
|
||||
api={api}
|
||||
containerApi={containerApi}
|
||||
params={params}
|
||||
@ -105,7 +115,9 @@ describe('defaultTab', () => {
|
||||
test('that settings closeActionOverride skips api.close()', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
close: jest.fn(),
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
onDidTitleChange: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
@ -114,6 +126,7 @@ describe('defaultTab', () => {
|
||||
|
||||
render(
|
||||
<DockviewDefaultTab
|
||||
tabLocation="header"
|
||||
api={api}
|
||||
containerApi={containerApi}
|
||||
params={params}
|
||||
@ -134,13 +147,16 @@ describe('defaultTab', () => {
|
||||
test('that clicking close calls api.close()', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
close: jest.fn(),
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
onDidTitleChange: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
render(
|
||||
<DockviewDefaultTab
|
||||
tabLocation="header"
|
||||
api={api}
|
||||
containerApi={containerApi}
|
||||
params={params}
|
||||
@ -158,13 +174,16 @@ describe('defaultTab', () => {
|
||||
|
||||
test('has close button when hideClose=false', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
onDidTitleChange: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
render(
|
||||
<DockviewDefaultTab
|
||||
tabLocation="header"
|
||||
api={api}
|
||||
containerApi={containerApi}
|
||||
params={params}
|
||||
@ -175,32 +194,4 @@ describe('defaultTab', () => {
|
||||
const element = await screen.getByTestId('dockview-dv-default-tab');
|
||||
expect(element.querySelector('.dv-default-tab-action')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('that pointerDown on close button prevents panel becoming active', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
setActive: jest.fn(),
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
render(
|
||||
<DockviewDefaultTab
|
||||
api={api}
|
||||
containerApi={containerApi}
|
||||
params={params}
|
||||
/>
|
||||
);
|
||||
|
||||
const element = await screen.getByTestId('dockview-dv-default-tab');
|
||||
const btn = element.querySelector(
|
||||
'.dv-default-tab-action'
|
||||
) as HTMLElement;
|
||||
|
||||
fireEvent.pointerDown(btn);
|
||||
expect(api.setActive).toHaveBeenCalledTimes(0);
|
||||
|
||||
fireEvent.click(element);
|
||||
expect(api.setActive).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -37,7 +37,7 @@ describe('headerActionsRenderer', () => {
|
||||
);
|
||||
|
||||
expect(cut.element.childNodes.length).toBe(0);
|
||||
expect(cut.element.className).toBe('dockview-react-part');
|
||||
expect(cut.element.className).toBe('dv-react-part');
|
||||
expect(cut.part).toBeUndefined();
|
||||
|
||||
cut.init({
|
||||
|
@ -32,10 +32,16 @@ export const DockviewDefaultTab: React.FunctionComponent<
|
||||
params: _params,
|
||||
hideClose,
|
||||
closeActionOverride,
|
||||
onPointerDown,
|
||||
onPointerUp,
|
||||
onPointerLeave,
|
||||
tabLocation,
|
||||
...rest
|
||||
}) => {
|
||||
const title = useTitle(api);
|
||||
|
||||
const isMiddleMouseButton = React.useRef<boolean>(false);
|
||||
|
||||
const onClose = React.useCallback(
|
||||
(event: React.MouseEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault();
|
||||
@ -49,37 +55,52 @@ export const DockviewDefaultTab: React.FunctionComponent<
|
||||
[api, closeActionOverride]
|
||||
);
|
||||
|
||||
const onPointerDown = React.useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const onBtnPointerDown = React.useCallback((event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
}, []);
|
||||
|
||||
const onClick = React.useCallback(
|
||||
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
api.setActive();
|
||||
|
||||
if (rest.onClick) {
|
||||
rest.onClick(event);
|
||||
}
|
||||
const _onPointerDown = React.useCallback(
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
isMiddleMouseButton.current = event.button === 1;
|
||||
onPointerDown?.(event);
|
||||
},
|
||||
[api, rest.onClick]
|
||||
[onPointerDown]
|
||||
);
|
||||
|
||||
const _onPointerUp = React.useCallback(
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (isMiddleMouseButton && event.button === 1 && !hideClose) {
|
||||
isMiddleMouseButton.current = false;
|
||||
onClose(event);
|
||||
}
|
||||
|
||||
onPointerUp?.(event);
|
||||
},
|
||||
[onPointerUp, onClose, hideClose]
|
||||
);
|
||||
|
||||
const _onPointerLeave = React.useCallback(
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
isMiddleMouseButton.current = false;
|
||||
onPointerLeave?.(event);
|
||||
},
|
||||
[onPointerLeave]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="dockview-dv-default-tab"
|
||||
{...rest}
|
||||
onClick={onClick}
|
||||
onPointerDown={_onPointerDown}
|
||||
onPointerUp={_onPointerUp}
|
||||
onPointerLeave={_onPointerLeave}
|
||||
className="dv-default-tab"
|
||||
>
|
||||
<span className="dv-default-tab-content">{title}</span>
|
||||
{!hideClose && (
|
||||
{!hideClose && tabLocation !== 'headerOverflow' && (
|
||||
<div
|
||||
className="dv-default-tab-action"
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerDown={onBtnPointerDown}
|
||||
onClick={onClose}
|
||||
>
|
||||
<CloseButton />
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
IDockviewPanelHeaderProps,
|
||||
IDockviewPanelProps,
|
||||
DockviewOptions,
|
||||
PROPERTY_KEYS,
|
||||
PROPERTY_KEYS_DOCKVIEW,
|
||||
DockviewComponentOptions,
|
||||
DockviewFrameworkOptions,
|
||||
DockviewReadyEvent,
|
||||
@ -40,7 +40,6 @@ function createGroupControlElement(
|
||||
const DEFAULT_REACT_TAB = 'props.defaultTabComponent';
|
||||
|
||||
export interface IDockviewReactProps extends DockviewOptions {
|
||||
className?: string;
|
||||
tabComponents?: Record<
|
||||
string,
|
||||
React.FunctionComponent<IDockviewPanelHeaderProps>
|
||||
@ -58,7 +57,7 @@ export interface IDockviewReactProps extends DockviewOptions {
|
||||
}
|
||||
|
||||
function extractCoreOptions(props: IDockviewReactProps): DockviewOptions {
|
||||
const coreOptions = PROPERTY_KEYS.reduce((obj, key) => {
|
||||
const coreOptions = PROPERTY_KEYS_DOCKVIEW.reduce((obj, key) => {
|
||||
if (key in props) {
|
||||
obj[key] = props[key] as any;
|
||||
}
|
||||
@ -82,7 +81,7 @@ export const DockviewReact = React.forwardRef(
|
||||
() => {
|
||||
const changes: Partial<DockviewOptions> = {};
|
||||
|
||||
PROPERTY_KEYS.forEach((propKey) => {
|
||||
PROPERTY_KEYS_DOCKVIEW.forEach((propKey) => {
|
||||
const key = propKey;
|
||||
const propValue = props[key];
|
||||
|
||||
@ -99,7 +98,7 @@ export const DockviewReact = React.forwardRef(
|
||||
|
||||
prevProps.current = props;
|
||||
},
|
||||
PROPERTY_KEYS.map((key) => props[key])
|
||||
PROPERTY_KEYS_DOCKVIEW.map((key) => props[key])
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -319,11 +318,7 @@ export const DockviewReact = React.forwardRef(
|
||||
}, [props.prefixHeaderActionsComponent]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={props.className}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
ref={domRef}
|
||||
>
|
||||
<div style={{ height: '100%', width: '100%' }} ref={domRef}>
|
||||
{portals}
|
||||
</div>
|
||||
);
|
||||
|
@ -30,7 +30,7 @@ export class ReactHeaderActionsRendererPart implements IHeaderActionsRenderer {
|
||||
private readonly _group: DockviewGroupPanel
|
||||
) {
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dockview-react-part';
|
||||
this._element.className = 'dv-react-part';
|
||||
this._element.style.height = '100%';
|
||||
this._element.style.width = '100%';
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export class ReactPanelContentPart implements IContentRenderer {
|
||||
private readonly reactPortalStore: ReactPortalStore
|
||||
) {
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dockview-react-part';
|
||||
this._element.className = 'dv-react-part';
|
||||
this._element.style.height = '100%';
|
||||
this._element.style.width = '100%';
|
||||
}
|
||||
|
@ -3,13 +3,13 @@ import { ReactPart, ReactPortalStore } from '../react';
|
||||
import {
|
||||
PanelUpdateEvent,
|
||||
ITabRenderer,
|
||||
GroupPanelPartInitParameters,
|
||||
IGroupPanelBaseProps,
|
||||
TabPartInitParameters,
|
||||
IDockviewPanelHeaderProps,
|
||||
} from 'dockview-core';
|
||||
|
||||
export class ReactPanelHeaderPart implements ITabRenderer {
|
||||
private readonly _element: HTMLElement;
|
||||
private part?: ReactPart<IGroupPanelBaseProps>;
|
||||
private part?: ReactPart<IDockviewPanelHeaderProps>;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
@ -17,11 +17,11 @@ export class ReactPanelHeaderPart implements ITabRenderer {
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
private readonly component: React.FunctionComponent<IGroupPanelBaseProps>,
|
||||
private readonly component: React.FunctionComponent<IDockviewPanelHeaderProps>,
|
||||
private readonly reactPortalStore: ReactPortalStore
|
||||
) {
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dockview-react-part';
|
||||
this._element.className = 'dv-react-part';
|
||||
this._element.style.height = '100%';
|
||||
this._element.style.width = '100%';
|
||||
}
|
||||
@ -30,7 +30,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
|
||||
//noop
|
||||
}
|
||||
|
||||
public init(parameters: GroupPanelPartInitParameters): void {
|
||||
public init(parameters: TabPartInitParameters): void {
|
||||
this.part = new ReactPart(
|
||||
this.element,
|
||||
this.reactPortalStore,
|
||||
@ -39,6 +39,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
|
||||
params: parameters.params,
|
||||
api: parameters.api,
|
||||
containerApi: parameters.containerApi,
|
||||
tabLocation: parameters.tabLocation,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export class ReactWatermarkPart implements IWatermarkRenderer {
|
||||
private readonly reactPortalStore: ReactPortalStore
|
||||
) {
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dockview-react-part';
|
||||
this._element.className = 'dv-react-part';
|
||||
this._element.style.height = '100%';
|
||||
this._element.style.width = '100%';
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
GridviewPanelApi,
|
||||
Orientation,
|
||||
GridviewApi,
|
||||
createGridview,
|
||||
GridviewOptions,
|
||||
PROPERTY_KEYS_GRIDVIEW,
|
||||
GridviewComponentOptions,
|
||||
GridviewFrameworkOptions,
|
||||
} from 'dockview-core';
|
||||
import { ReactGridPanelView } from './view';
|
||||
import { usePortalsLifecycle } from '../react';
|
||||
import { PanelParameters } from '../types';
|
||||
|
||||
export interface GridviewReadyEvent {
|
||||
api: GridviewApi;
|
||||
}
|
||||
@ -18,14 +22,20 @@ export interface IGridviewPanelProps<T extends { [index: string]: any } = any>
|
||||
containerApi: GridviewApi;
|
||||
}
|
||||
|
||||
export interface IGridviewReactProps {
|
||||
orientation?: Orientation;
|
||||
export interface IGridviewReactProps extends GridviewOptions {
|
||||
onReady: (event: GridviewReadyEvent) => void;
|
||||
components: Record<string, React.FunctionComponent<IGridviewPanelProps>>;
|
||||
hideBorders?: boolean;
|
||||
className?: string;
|
||||
proportionalLayout?: boolean;
|
||||
disableAutoResizing?: boolean;
|
||||
}
|
||||
|
||||
function extractCoreOptions(props: IGridviewReactProps): GridviewOptions {
|
||||
const coreOptions = PROPERTY_KEYS_GRIDVIEW.reduce((obj, key) => {
|
||||
if (key in props) {
|
||||
obj[key] = props[key] as any;
|
||||
}
|
||||
return obj;
|
||||
}, {} as Partial<GridviewComponentOptions>);
|
||||
|
||||
return coreOptions as GridviewOptions;
|
||||
}
|
||||
|
||||
export const GridviewReact = React.forwardRef(
|
||||
@ -36,6 +46,32 @@ export const GridviewReact = React.forwardRef(
|
||||
|
||||
React.useImperativeHandle(ref, () => domRef.current!, []);
|
||||
|
||||
const prevProps = React.useRef<Partial<IGridviewReactProps>>({});
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
const changes: Partial<GridviewOptions> = {};
|
||||
|
||||
PROPERTY_KEYS_GRIDVIEW.forEach((propKey) => {
|
||||
const key = propKey;
|
||||
const propValue = props[key];
|
||||
|
||||
if (key in props && propValue !== prevProps.current[key]) {
|
||||
changes[key] = propValue as any;
|
||||
}
|
||||
});
|
||||
|
||||
if (gridviewRef.current) {
|
||||
gridviewRef.current.updateOptions(changes);
|
||||
} else {
|
||||
// not yet fully initialized
|
||||
}
|
||||
|
||||
prevProps.current = props;
|
||||
},
|
||||
PROPERTY_KEYS_GRIDVIEW.map((key) => props[key])
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!domRef.current) {
|
||||
return () => {
|
||||
@ -43,29 +79,20 @@ export const GridviewReact = React.forwardRef(
|
||||
};
|
||||
}
|
||||
|
||||
const api = createGridview(domRef.current, {
|
||||
disableAutoResizing: props.disableAutoResizing,
|
||||
proportionalLayout:
|
||||
typeof props.proportionalLayout === 'boolean'
|
||||
? props.proportionalLayout
|
||||
: true,
|
||||
orientation: props.orientation ?? Orientation.HORIZONTAL,
|
||||
frameworkComponents: props.components,
|
||||
frameworkComponentFactory: {
|
||||
createComponent: (id: string, componentId, component) => {
|
||||
return new ReactGridPanelView(
|
||||
id,
|
||||
componentId,
|
||||
component,
|
||||
{
|
||||
addPortal,
|
||||
}
|
||||
);
|
||||
},
|
||||
const frameworkOptions: GridviewFrameworkOptions = {
|
||||
createComponent: (options) => {
|
||||
return new ReactGridPanelView(
|
||||
options.id,
|
||||
options.name,
|
||||
props.components[options.name],
|
||||
{ addPortal }
|
||||
);
|
||||
},
|
||||
styles: props.hideBorders
|
||||
? { separatorBorder: 'transparent' }
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const api = createGridview(domRef.current, {
|
||||
...extractCoreOptions(props),
|
||||
...frameworkOptions,
|
||||
});
|
||||
|
||||
const { clientWidth, clientHeight } = domRef.current;
|
||||
@ -87,16 +114,19 @@ export const GridviewReact = React.forwardRef(
|
||||
return;
|
||||
}
|
||||
gridviewRef.current.updateOptions({
|
||||
frameworkComponents: props.components,
|
||||
createComponent: (options) => {
|
||||
return new ReactGridPanelView(
|
||||
options.id,
|
||||
options.name,
|
||||
props.components[options.name],
|
||||
{ addPortal }
|
||||
);
|
||||
},
|
||||
});
|
||||
}, [props.components]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={props.className}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
ref={domRef}
|
||||
>
|
||||
<div style={{ height: '100%', width: '100%' }} ref={domRef}>
|
||||
{portals}
|
||||
</div>
|
||||
);
|
||||
|
@ -1,10 +1,13 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
PaneviewPanelApi,
|
||||
PaneviewDndOverlayEvent,
|
||||
PaneviewApi,
|
||||
PaneviewDropEvent,
|
||||
createPaneview,
|
||||
PaneviewOptions,
|
||||
PROPERTY_KEYS_PANEVIEW,
|
||||
PaneviewComponentOptions,
|
||||
PaneviewFrameworkOptions,
|
||||
} from 'dockview-core';
|
||||
import { usePortalsLifecycle } from '../react';
|
||||
import { PanePanelSection } from './view';
|
||||
@ -21,20 +24,27 @@ export interface IPaneviewPanelProps<T extends { [index: string]: any } = any>
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface IPaneviewReactProps {
|
||||
export interface IPaneviewReactProps extends PaneviewOptions {
|
||||
onReady: (event: PaneviewReadyEvent) => void;
|
||||
components: Record<string, React.FunctionComponent<IPaneviewPanelProps>>;
|
||||
headerComponents?: Record<
|
||||
string,
|
||||
React.FunctionComponent<IPaneviewPanelProps>
|
||||
>;
|
||||
className?: string;
|
||||
disableAutoResizing?: boolean;
|
||||
disableDnd?: boolean;
|
||||
showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean;
|
||||
onDidDrop?(event: PaneviewDropEvent): void;
|
||||
}
|
||||
|
||||
function extractCoreOptions(props: IPaneviewReactProps): PaneviewOptions {
|
||||
const coreOptions = PROPERTY_KEYS_PANEVIEW.reduce((obj, key) => {
|
||||
if (key in props) {
|
||||
obj[key] = props[key] as any;
|
||||
}
|
||||
return obj;
|
||||
}, {} as Partial<PaneviewComponentOptions>);
|
||||
|
||||
return coreOptions as PaneviewOptions;
|
||||
}
|
||||
|
||||
export const PaneviewReact = React.forwardRef(
|
||||
(props: IPaneviewReactProps, ref: React.ForwardedRef<HTMLDivElement>) => {
|
||||
const domRef = React.useRef<HTMLDivElement>(null);
|
||||
@ -43,35 +53,64 @@ export const PaneviewReact = React.forwardRef(
|
||||
|
||||
React.useImperativeHandle(ref, () => domRef.current!, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
const createComponent = (
|
||||
id: string,
|
||||
_componentId: string,
|
||||
component: any
|
||||
) =>
|
||||
new PanePanelSection(id, component, {
|
||||
addPortal,
|
||||
const prevProps = React.useRef<Partial<IPaneviewReactProps>>({});
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
const changes: Partial<PaneviewOptions> = {};
|
||||
|
||||
PROPERTY_KEYS_PANEVIEW.forEach((propKey) => {
|
||||
const key = propKey;
|
||||
const propValue = props[key];
|
||||
|
||||
if (key in props && propValue !== prevProps.current[key]) {
|
||||
changes[key] = propValue as any;
|
||||
}
|
||||
});
|
||||
|
||||
const api = createPaneview(domRef.current!, {
|
||||
disableAutoResizing: props.disableAutoResizing,
|
||||
frameworkComponents: props.components,
|
||||
components: {},
|
||||
headerComponents: {},
|
||||
disableDnd: props.disableDnd,
|
||||
headerframeworkComponents: props.headerComponents,
|
||||
frameworkWrapper: {
|
||||
header: {
|
||||
createComponent,
|
||||
},
|
||||
body: {
|
||||
createComponent,
|
||||
},
|
||||
if (paneviewRef.current) {
|
||||
paneviewRef.current.updateOptions(changes);
|
||||
} else {
|
||||
// not yet fully initialized
|
||||
}
|
||||
|
||||
prevProps.current = props;
|
||||
},
|
||||
PROPERTY_KEYS_PANEVIEW.map((key) => props[key])
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!domRef.current) {
|
||||
return () => {
|
||||
// noop
|
||||
};
|
||||
}
|
||||
|
||||
const headerComponents = props.headerComponents ?? {};
|
||||
|
||||
const frameworkOptions: PaneviewFrameworkOptions = {
|
||||
createComponent: (options) => {
|
||||
return new PanePanelSection(
|
||||
options.id,
|
||||
props.components[options.name],
|
||||
{ addPortal }
|
||||
);
|
||||
},
|
||||
showDndOverlay: props.showDndOverlay,
|
||||
createHeaderComponent: (options) => {
|
||||
return new PanePanelSection(
|
||||
options.id,
|
||||
headerComponents[options.name],
|
||||
{ addPortal }
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const api = createPaneview(domRef.current, {
|
||||
...extractCoreOptions(props),
|
||||
...frameworkOptions,
|
||||
});
|
||||
|
||||
const { clientWidth, clientHeight } = domRef.current!;
|
||||
const { clientWidth, clientHeight } = domRef.current;
|
||||
api.layout(clientWidth, clientHeight);
|
||||
|
||||
if (props.onReady) {
|
||||
@ -90,7 +129,13 @@ export const PaneviewReact = React.forwardRef(
|
||||
return;
|
||||
}
|
||||
paneviewRef.current.updateOptions({
|
||||
frameworkComponents: props.components,
|
||||
createComponent: (options) => {
|
||||
return new PanePanelSection(
|
||||
options.id,
|
||||
props.components[options.name],
|
||||
{ addPortal }
|
||||
);
|
||||
},
|
||||
});
|
||||
}, [props.components]);
|
||||
|
||||
@ -98,26 +143,30 @@ export const PaneviewReact = React.forwardRef(
|
||||
if (!paneviewRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const headerComponents = props.headerComponents ?? {};
|
||||
|
||||
paneviewRef.current.updateOptions({
|
||||
headerframeworkComponents: props.headerComponents,
|
||||
createHeaderComponent: (options) => {
|
||||
return new PanePanelSection(
|
||||
options.id,
|
||||
headerComponents[options.name],
|
||||
{ addPortal }
|
||||
);
|
||||
},
|
||||
});
|
||||
}, [props.headerComponents]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!paneviewRef.current) {
|
||||
return () => {
|
||||
//
|
||||
// noop
|
||||
};
|
||||
}
|
||||
|
||||
const api = paneviewRef.current;
|
||||
|
||||
const disposable = api.onDidDrop((event) => {
|
||||
const disposable = paneviewRef.current.onDidDrop((event) => {
|
||||
if (props.onDidDrop) {
|
||||
props.onDidDrop({
|
||||
...event,
|
||||
api,
|
||||
});
|
||||
props.onDidDrop(event);
|
||||
}
|
||||
});
|
||||
|
||||
@ -126,21 +175,8 @@ export const PaneviewReact = React.forwardRef(
|
||||
};
|
||||
}, [props.onDidDrop]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!paneviewRef.current) {
|
||||
return;
|
||||
}
|
||||
paneviewRef.current.updateOptions({
|
||||
showDndOverlay: props.showDndOverlay,
|
||||
});
|
||||
}, [props.showDndOverlay]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={props.className}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
ref={domRef}
|
||||
>
|
||||
<div style={{ height: '100%', width: '100%' }} ref={domRef}>
|
||||
{portals}
|
||||
</div>
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
PanelUpdateEvent,
|
||||
IPaneBodyPart,
|
||||
IPanePart,
|
||||
PanePanelComponentInitParameter,
|
||||
} from 'dockview-core';
|
||||
import { ReactPart, ReactPortalStore } from '../react';
|
||||
import { IPaneviewPanelProps } from './paneview';
|
||||
|
||||
export class PanePanelSection implements IPaneBodyPart {
|
||||
export class PanePanelSection implements IPanePart {
|
||||
private readonly _element: HTMLElement;
|
||||
private part?: ReactPart<IPaneviewPanelProps>;
|
||||
|
||||
|
@ -2,8 +2,11 @@ import React from 'react';
|
||||
import {
|
||||
SplitviewApi,
|
||||
SplitviewPanelApi,
|
||||
Orientation,
|
||||
createSplitview,
|
||||
SplitviewOptions,
|
||||
PROPERTY_KEYS_SPLITVIEW,
|
||||
SplitviewFrameworkOptions,
|
||||
SplitviewComponentOptions,
|
||||
} from 'dockview-core';
|
||||
import { usePortalsLifecycle } from '../react';
|
||||
import { PanelParameters } from '../types';
|
||||
@ -19,14 +22,20 @@ export interface ISplitviewPanelProps<T extends { [index: string]: any } = any>
|
||||
containerApi: SplitviewApi;
|
||||
}
|
||||
|
||||
export interface ISplitviewReactProps {
|
||||
orientation?: Orientation;
|
||||
export interface ISplitviewReactProps extends SplitviewOptions {
|
||||
onReady: (event: SplitviewReadyEvent) => void;
|
||||
components: Record<string, React.FunctionComponent<ISplitviewPanelProps>>;
|
||||
proportionalLayout?: boolean;
|
||||
hideBorders?: boolean;
|
||||
className?: string;
|
||||
disableAutoResizing?: boolean;
|
||||
}
|
||||
|
||||
function extractCoreOptions(props: ISplitviewReactProps): SplitviewOptions {
|
||||
const coreOptions = PROPERTY_KEYS_SPLITVIEW.reduce((obj, key) => {
|
||||
if (key in props) {
|
||||
obj[key] = props[key] as any;
|
||||
}
|
||||
return obj;
|
||||
}, {} as Partial<SplitviewComponentOptions>);
|
||||
|
||||
return coreOptions as SplitviewOptions;
|
||||
}
|
||||
|
||||
export const SplitviewReact = React.forwardRef(
|
||||
@ -37,32 +46,56 @@ export const SplitviewReact = React.forwardRef(
|
||||
|
||||
React.useImperativeHandle(ref, () => domRef.current!, []);
|
||||
|
||||
const prevProps = React.useRef<Partial<ISplitviewReactProps>>({});
|
||||
|
||||
React.useEffect(
|
||||
() => {
|
||||
const changes: Partial<SplitviewOptions> = {};
|
||||
|
||||
PROPERTY_KEYS_SPLITVIEW.forEach((propKey) => {
|
||||
const key = propKey;
|
||||
const propValue = props[key];
|
||||
|
||||
if (key in props && propValue !== prevProps.current[key]) {
|
||||
changes[key] = propValue as any;
|
||||
}
|
||||
});
|
||||
|
||||
if (splitviewRef.current) {
|
||||
splitviewRef.current.updateOptions(changes);
|
||||
} else {
|
||||
// not yet fully initialized
|
||||
}
|
||||
|
||||
prevProps.current = props;
|
||||
},
|
||||
PROPERTY_KEYS_SPLITVIEW.map((key) => props[key])
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const api = createSplitview(domRef.current!, {
|
||||
disableAutoResizing: props.disableAutoResizing,
|
||||
orientation: props.orientation ?? Orientation.HORIZONTAL,
|
||||
frameworkComponents: props.components,
|
||||
frameworkWrapper: {
|
||||
createComponent: (
|
||||
id: string,
|
||||
componentId,
|
||||
component: any
|
||||
) => {
|
||||
return new ReactPanelView(id, componentId, component, {
|
||||
addPortal,
|
||||
});
|
||||
},
|
||||
if (!domRef.current) {
|
||||
return () => {
|
||||
// noop
|
||||
};
|
||||
}
|
||||
|
||||
const frameworkOptions: SplitviewFrameworkOptions = {
|
||||
createComponent: (options) => {
|
||||
return new ReactPanelView(
|
||||
options.id,
|
||||
options.name,
|
||||
props.components[options.name],
|
||||
{ addPortal }
|
||||
);
|
||||
},
|
||||
proportionalLayout:
|
||||
typeof props.proportionalLayout === 'boolean'
|
||||
? props.proportionalLayout
|
||||
: true,
|
||||
styles: props.hideBorders
|
||||
? { separatorBorder: 'transparent' }
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const api = createSplitview(domRef.current, {
|
||||
...extractCoreOptions(props),
|
||||
...frameworkOptions,
|
||||
});
|
||||
|
||||
const { clientWidth, clientHeight } = domRef.current!;
|
||||
const { clientWidth, clientHeight } = domRef.current;
|
||||
api.layout(clientWidth, clientHeight);
|
||||
|
||||
if (props.onReady) {
|
||||
@ -81,16 +114,19 @@ export const SplitviewReact = React.forwardRef(
|
||||
return;
|
||||
}
|
||||
splitviewRef.current.updateOptions({
|
||||
frameworkComponents: props.components,
|
||||
createComponent: (options) => {
|
||||
return new ReactPanelView(
|
||||
options.id,
|
||||
options.name,
|
||||
props.components[options.name],
|
||||
{ addPortal }
|
||||
);
|
||||
},
|
||||
});
|
||||
}, [props.components]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={props.className}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
ref={domRef}
|
||||
>
|
||||
<div style={{ height: '100%', width: '100%' }} ref={domRef}>
|
||||
{portals}
|
||||
</div>
|
||||
);
|
||||
|
@ -7,7 +7,7 @@ export const CloseButton = () => (
|
||||
viewBox="0 0 28 28"
|
||||
aria-hidden={'false'}
|
||||
focusable={false}
|
||||
className="dockview-svg"
|
||||
className="dv-svg"
|
||||
>
|
||||
<path d="M2.1 27.3L0 25.2L11.55 13.65L0 2.1L2.1 0L13.65 11.55L25.2 0L27.3 2.1L15.75 13.65L27.3 25.2L25.2 27.3L13.65 15.75L2.1 27.3Z"></path>
|
||||
</svg>
|
||||
@ -21,7 +21,7 @@ export const ExpandMore = () => {
|
||||
viewBox="0 0 24 15"
|
||||
aria-hidden={'false'}
|
||||
focusable={false}
|
||||
className="dockview-svg"
|
||||
className="dv-svg"
|
||||
>
|
||||
<path d="M12 14.15L0 2.15L2.15 0L12 9.9L21.85 0.0499992L24 2.2L12 14.15Z" />
|
||||
</svg>
|
||||
|
28
packages/docs/blog/2024-12-17-dockview-2.1.0.md
Normal file
28
packages/docs/blog/2024-12-17-dockview-2.1.0.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
slug: dockview-2.1.0-release
|
||||
title: Dockview 2.1.0
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Persist custom popout urls in layouts [#769](https://github.com/mathuo/dockview/pull/769)
|
||||
- Ensure group always exists [#783](https://github.com/mathuo/dockview/pull/783)
|
||||
- Serialization of maximized views [#762](https://github.com/mathuo/dockview/pull/762)
|
||||
- Set `react` as an explicit peerDependency of the `dockview` package
|
||||
- Make tabs container non-focusable [#761](https://github.com/mathuo/dockview/pull/761)
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Bug: fix `setVisible` for floating groups [#755](https://github.com/mathuo/dockview/pull/755)
|
||||
- Bug: fix `onDidAddGroup` event firing when adding floating groups and panels [#785](https://github.com/mathuo/dockview/pull/785)
|
||||
- Documentation [#743](https://github.com/mathuo/dockview/pull/743) [#770](https://github.com/mathuo/dockview/pull/770)
|
||||
- Build tooling [#789](https://github.com/mathuo/dockview/pull/789) [#759](https://github.com/mathuo/dockview/pull/759)
|
||||
|
||||
## 🔥 Breaking changes
|
||||
|
||||
|
19
packages/docs/blog/2024-12-20-dockview-2.1.1.md
Normal file
19
packages/docs/blog/2024-12-20-dockview-2.1.1.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
slug: dockview-2.1.1-release
|
||||
title: Dockview 2.1.1
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Bug: Fix issue with moving tab from popout group into main grid [#795](https://github.com/mathuo/dockview/issues/795)
|
||||
|
||||
## 🔥 Breaking changes
|
||||
|
||||
|
19
packages/docs/blog/2024-12-21-dockview-2.1.2.md
Normal file
19
packages/docs/blog/2024-12-21-dockview-2.1.2.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
slug: dockview-2.1.2-release
|
||||
title: Dockview 2.1.2
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Bug: Fix issues with popout group location transitions [#797](https://github.com/mathuo/dockview/issues/797)
|
||||
|
||||
## 🔥 Breaking changes
|
||||
|
||||
|
19
packages/docs/blog/2024-12-22-dockview-2.1.3.md
Normal file
19
packages/docs/blog/2024-12-22-dockview-2.1.3.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
slug: dockview-2.1.3-release
|
||||
title: Dockview 2.1.3
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Bug: Fix rendering issue when popout group is moved into new grid group [#799](https://github.com/mathuo/dockview/issues/799)
|
||||
|
||||
## 🔥 Breaking changes
|
||||
|
||||
|
20
packages/docs/blog/2024-12-23-dockview-2.1.4.md
Normal file
20
packages/docs/blog/2024-12-23-dockview-2.1.4.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
slug: dockview-2.1.4-release
|
||||
title: Dockview 2.1.4
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Maintenance: Cleanup resource dispose [#801](https://github.com/mathuo/dockview/issues/801)
|
||||
- Bug: Fix for using popout groups with `always` rendering [#803](https://github.com/mathuo/dockview/issues/803)
|
||||
|
||||
## 🔥 Breaking changes
|
||||
|
||||
|
29
packages/docs/blog/2024-12-29-dockview-3.0.0.md
Normal file
29
packages/docs/blog/2024-12-29-dockview-3.0.0.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
slug: dockview-3.0.0-release
|
||||
title: Dockview 3.0.0
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
This is a major release version due to some breaking changes in the `dockview-core` package. If you use the react or vue versions of dockview you should not see any breaking changes when upgrading. There are no new features in this release.
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- `dockview-vue` vue3 peerDependency [#808](https://github.com/mathuo/dockview/issues/808)
|
||||
- Bug: correct enablement of `dv-single-tab` class [#811](https://github.com/mathuo/dockview/issues/811)
|
||||
|
||||
## 🔥 Breaking changes
|
||||
|
||||
- Standardize `dockview-core` components for generic framework extensions following the pattern in `DockviewComponent` [#810](https://github.com/mathuo/dockview/issues/810)
|
||||
- `SplitviewComponent`: Replace `components` and `frameworkComponents` with `createComponent`
|
||||
- `PaneviewComponent`: Replace `components` and `frameworkComponents` with `createComponent` and replace `headerComponents` and `headerFrameworkComponents` with `createHeaderComponent`
|
||||
- `GridviewComponent`: Replace `components` and `frameworkComponents` with `createComponent`
|
||||
- rename class `dockview-react-part` to `dv-react-part` [#806](https://github.com/mathuo/dockview/issues/806)
|
||||
- rename type `PaneviewDropEvent` to `PaneviewDidDropEvent` [#812](https://github.com/mathuo/dockview/issues/812)
|
||||
- remove `showDndOverlay` from `PaneviewComponent` in favour of `api.onUnhandledDragOverEvent` [#812](https://github.com/mathuo/dockview/issues/812)
|
||||
|
17
packages/docs/blog/2025-01-09-dockview-3.0.1.md
Normal file
17
packages/docs/blog/2025-01-09-dockview-3.0.1.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
slug: dockview-3.0.1-release
|
||||
title: Dockview 3.0.1
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
## 🔥 Breaking changes
|
||||
|
||||
- Fix duplicate HTML element [#810](https://github.com/mathuo/dockview/issues/818)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user