mirror of
https://github.com/mathuo/dockview
synced 2025-02-21 15:45:46 +00:00
Merge branch '281-type-hints-for-panel-parameters' into mpearson/component-params-typing
This commit is contained in:
commit
853a5be569
@ -13,6 +13,7 @@
|
||||
"/packages/docs/sandboxes/externaldnd-dockview",
|
||||
"/packages/docs/sandboxes/fullwidthtab-dockview",
|
||||
"/packages/docs/sandboxes/groupcontol-dockview",
|
||||
"/packages/docs/sandboxes/iframe-dockview",
|
||||
"/packages/docs/sandboxes/layout-dockview",
|
||||
"/packages/docs/sandboxes/nativeapp-dockview",
|
||||
"/packages/docs/sandboxes/nested-dockview",
|
||||
@ -22,8 +23,11 @@
|
||||
"/packages/docs/sandboxes/simple-dockview",
|
||||
"/packages/docs/sandboxes/tabheight-dockview",
|
||||
"/packages/docs/sandboxes/updatetitle-dockview",
|
||||
"/packages/docs/sandboxes/vanilla-dockview",
|
||||
"/packages/docs/sandboxes/watermark-dockview"
|
||||
"/packages/docs/sandboxes/watermark-dockview",
|
||||
"/packages/docs/sandboxes/javascript/fullwidthtab-dockview",
|
||||
"/packages/docs/sandboxes/javascript/simple-dockview",
|
||||
"/packages/docs/sandboxes/javascript/tabheight-dockview",
|
||||
"/packages/docs/sandboxes/javascript/vanilla-dockview"
|
||||
],
|
||||
"node": "16"
|
||||
}
|
6
.github/workflows/deploy-docs.yml
vendored
6
.github/workflows/deploy-docs.yml
vendored
@ -33,8 +33,10 @@ jobs:
|
||||
working-directory: packages/dockview
|
||||
- run: npm run build
|
||||
working-directory: packages/docs
|
||||
- run: npm run deploy-docs
|
||||
working-directory: packages/docs
|
||||
- run: npm run docs
|
||||
working-directory: .
|
||||
- run: npm run package-docs
|
||||
working-directory: .
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,3 +13,4 @@ test-report.xml
|
||||
*.code-workspace
|
||||
yarn-error.log
|
||||
/build
|
||||
/docs/
|
||||
|
@ -3,7 +3,7 @@
|
||||
"packages/*"
|
||||
],
|
||||
"useWorkspaces": true,
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.5",
|
||||
"npmClient": "yarn",
|
||||
"command": {
|
||||
"publish": {
|
||||
|
15
package.json
15
package.json
@ -19,7 +19,9 @@
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"test:cov": "jest --coverage",
|
||||
"version-beta-build": "lerna version prerelease --preid beta",
|
||||
"publish-app": "lerna publish"
|
||||
"publish-app": "lerna publish",
|
||||
"docs": "typedoc",
|
||||
"package-docs": "node scripts/package-docs.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -33,6 +35,7 @@
|
||||
"homepage": "https://github.com/mathuo/dockview#readme",
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.20.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
||||
"@typescript-eslint/parser": "^5.52.0",
|
||||
@ -44,6 +47,7 @@
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-dart-sass": "^1.0.2",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.4.3",
|
||||
"jest-sonar-reporter": "^2.0.0",
|
||||
"jsdom": "^21.1.0",
|
||||
@ -55,14 +59,13 @@
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^29.0.5",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.5.0",
|
||||
"typedoc": "^0.24.7",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"jest": "^29.5.0",
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
}
|
||||
"dependencies": {}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-core",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.5",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
|
@ -2,10 +2,10 @@ import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import {
|
||||
GroupPanelPartInitParameters,
|
||||
GroupPanelUpdateEvent,
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
} from '../../dockview/types';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
|
||||
export class DockviewPanelModelMock implements IDockviewPanelModel {
|
||||
constructor(
|
||||
@ -21,7 +21,14 @@ export class DockviewPanelModelMock implements IDockviewPanelModel {
|
||||
//
|
||||
}
|
||||
|
||||
update(event: GroupPanelUpdateEvent): void {
|
||||
updateParentGroup(
|
||||
group: DockviewGroupPanel,
|
||||
isPanelVisible: boolean
|
||||
): void {
|
||||
//
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent): void {
|
||||
//
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PanelApiImpl } from '../../api/panelApi';
|
||||
import { IPanel } from '../../panel/types';
|
||||
|
||||
describe('api', () => {
|
||||
let api: PanelApiImpl;
|
||||
@ -7,7 +8,23 @@ describe('api', () => {
|
||||
api = new PanelApiImpl('dummy_id');
|
||||
});
|
||||
|
||||
it('should update isFcoused getter', () => {
|
||||
test('updateParameters', () => {
|
||||
const panel = {
|
||||
update: jest.fn(),
|
||||
} as Partial<IPanel>;
|
||||
|
||||
api.initialize(panel as IPanel);
|
||||
|
||||
expect(panel.update).toHaveBeenCalledTimes(0);
|
||||
|
||||
api.updateParameters({ keyA: 'valueA' });
|
||||
expect(panel.update).toHaveBeenCalledTimes(1);
|
||||
expect(panel.update).toHaveBeenCalledWith({
|
||||
params: { keyA: 'valueA' },
|
||||
});
|
||||
});
|
||||
|
||||
test('should update isFcoused getter', () => {
|
||||
expect(api.isFocused).toBeFalsy();
|
||||
|
||||
api._onDidChangeFocus.fire({ isFocused: true });
|
||||
@ -17,7 +34,7 @@ describe('api', () => {
|
||||
expect(api.isFocused).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should update isActive getter', () => {
|
||||
test('should update isActive getter', () => {
|
||||
expect(api.isFocused).toBeFalsy();
|
||||
|
||||
api._onDidActiveChange.fire({ isActive: true });
|
||||
@ -27,7 +44,7 @@ describe('api', () => {
|
||||
expect(api.isActive).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should update isActive getter', () => {
|
||||
test('should update isActive getter', () => {
|
||||
expect(api.isVisible).toBeTruthy();
|
||||
|
||||
api._onDidVisibilityChange.fire({ isVisible: false });
|
||||
@ -37,7 +54,7 @@ describe('api', () => {
|
||||
expect(api.isVisible).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should update width and height getter', () => {
|
||||
test('should update width and height getter', () => {
|
||||
expect(api.height).toBe(0);
|
||||
expect(api.width).toBe(0);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DockviewPanelApiImpl, TitleEvent } from '../../api/dockviewPanelApi';
|
||||
import { DockviewPanelApiImpl } from '../../api/dockviewPanelApi';
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
@ -8,6 +8,7 @@ describe('groupPanelApi', () => {
|
||||
const panelMock = jest.fn<DockviewPanel, []>(() => {
|
||||
return {
|
||||
update: jest.fn(),
|
||||
setTitle: jest.fn(),
|
||||
} as any;
|
||||
});
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
@ -20,11 +21,38 @@ describe('groupPanelApi', () => {
|
||||
const cut = new DockviewPanelApiImpl(panel, group);
|
||||
|
||||
cut.setTitle('test_title');
|
||||
expect(panel.setTitle).toBeCalledTimes(1);
|
||||
expect(panel.setTitle).toBeCalledWith('test_title');
|
||||
});
|
||||
|
||||
expect(panel.update).toBeCalledTimes(1);
|
||||
expect(panel.update).toBeCalledWith({
|
||||
params: { title: 'test_title' },
|
||||
test('updateParameters', () => {
|
||||
const groupPanel: Partial<IDockviewPanel> = {
|
||||
id: 'test_id',
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
const groupViewPanel = new DockviewGroupPanel(
|
||||
<DockviewComponent>accessor,
|
||||
'',
|
||||
{}
|
||||
);
|
||||
|
||||
const cut = new DockviewPanelApiImpl(
|
||||
<IDockviewPanel>groupPanel,
|
||||
<DockviewGroupPanel>groupViewPanel
|
||||
);
|
||||
|
||||
cut.updateParameters({ keyA: 'valueA' });
|
||||
|
||||
expect(groupPanel.update).toHaveBeenCalledWith({
|
||||
params: { keyA: 'valueA' },
|
||||
});
|
||||
expect(groupPanel.update).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('onDidGroupChange', () => {
|
||||
|
@ -20,10 +20,6 @@ describe('abstractDragHandler', () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
})(element);
|
||||
|
||||
expect(element.classList.contains('dv-dragged')).toBeFalsy();
|
||||
@ -62,10 +58,6 @@ describe('abstractDragHandler', () => {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
//
|
||||
}
|
||||
})(element);
|
||||
|
||||
expect(iframe.style.pointerEvents).toBeFalsy();
|
||||
@ -84,4 +76,46 @@ describe('abstractDragHandler', () => {
|
||||
|
||||
handler.dispose();
|
||||
});
|
||||
|
||||
test('that the disabling of pointerEvents is restored on a premature disposal of the handler', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const element = document.createElement('div');
|
||||
const iframe = document.createElement('iframe');
|
||||
const webview = document.createElement('webview');
|
||||
const span = document.createElement('span');
|
||||
|
||||
document.body.appendChild(element);
|
||||
document.body.appendChild(iframe);
|
||||
document.body.appendChild(webview);
|
||||
document.body.appendChild(span);
|
||||
|
||||
const handler = new (class TestClass extends DragHandler {
|
||||
constructor(el: HTMLElement) {
|
||||
super(el);
|
||||
}
|
||||
|
||||
getData(): IDisposable {
|
||||
return {
|
||||
dispose: () => {
|
||||
// /
|
||||
},
|
||||
};
|
||||
}
|
||||
})(element);
|
||||
|
||||
expect(iframe.style.pointerEvents).toBeFalsy();
|
||||
expect(webview.style.pointerEvents).toBeFalsy();
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
|
||||
fireEvent.dragStart(element);
|
||||
expect(iframe.style.pointerEvents).toBe('none');
|
||||
expect(webview.style.pointerEvents).toBe('none');
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
|
||||
handler.dispose();
|
||||
expect(iframe.style.pointerEvents).toBe('auto');
|
||||
expect(webview.style.pointerEvents).toBe('auto');
|
||||
expect(span.style.pointerEvents).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@ -46,6 +46,7 @@ class PanelContentPartTest implements IContentRenderer {
|
||||
dispose(): void {
|
||||
this.isDisposed = true;
|
||||
this._onDidDispose.fire();
|
||||
this._onDidDispose.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +81,7 @@ class PanelTabPartTest implements ITabRenderer {
|
||||
dispose(): void {
|
||||
this.isDisposed = true;
|
||||
this._onDidDispose.fire();
|
||||
this._onDidDispose.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,6 +100,68 @@ describe('dockviewComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('event leakage', () => {
|
||||
Emitter.setLeakageMonitorEnabled(true);
|
||||
|
||||
dockview = new DockviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
});
|
||||
|
||||
dockview.layout(500, 1000);
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
dockview.removePanel(panel2);
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'right',
|
||||
referencePanel: 'panel1',
|
||||
},
|
||||
});
|
||||
|
||||
const panel4 = dockview.addPanel({
|
||||
id: 'panel4',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'above',
|
||||
},
|
||||
});
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel4.group,
|
||||
panel3.group.id,
|
||||
panel3.id,
|
||||
'center'
|
||||
);
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
Emitter.setLeakageMonitorEnabled(false);
|
||||
});
|
||||
|
||||
test('duplicate panel', () => {
|
||||
dockview.layout(500, 1000);
|
||||
|
||||
@ -112,6 +176,8 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
}).toThrowError('panel with id panel1 already exists');
|
||||
|
||||
dockview.dispose();
|
||||
});
|
||||
|
||||
test('set active panel', () => {
|
||||
@ -1285,21 +1351,21 @@ describe('dockviewComponent', () => {
|
||||
tabComponent: 'default',
|
||||
});
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
});
|
||||
// const panel2 = dockview.addPanel({
|
||||
// id: 'panel2',
|
||||
// component: 'default',
|
||||
// tabComponent: 'default',
|
||||
// });
|
||||
|
||||
expect(panel1.group).toEqual(panel2.group);
|
||||
// expect(panel1.group).toEqual(panel2.group);
|
||||
|
||||
const panel1Spy = jest.spyOn(panel1, 'dispose');
|
||||
const panel2Spy = jest.spyOn(panel2, 'dispose');
|
||||
// const panel2Spy = jest.spyOn(panel2, 'dispose');
|
||||
|
||||
dockview.dispose();
|
||||
|
||||
expect(panel1Spy).toBeCalledTimes(1);
|
||||
expect(panel2Spy).toBeCalledTimes(1);
|
||||
// expect(panel2Spy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('panel is disposed of when from JSON is called', () => {
|
||||
@ -2295,4 +2361,160 @@ describe('dockviewComponent', () => {
|
||||
panels: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('that title and params.title do not conflict', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
|
||||
dockview.layout(100, 100);
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
params: {
|
||||
title: 'Panel 1',
|
||||
},
|
||||
});
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 3',
|
||||
},
|
||||
});
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1', 'panel2', 'panel3'],
|
||||
activeView: 'panel3',
|
||||
id: '1',
|
||||
},
|
||||
size: 100,
|
||||
},
|
||||
],
|
||||
size: 100,
|
||||
},
|
||||
width: 100,
|
||||
height: 100,
|
||||
orientation: 'HORIZONTAL',
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
contentComponent: 'default',
|
||||
params: {
|
||||
title: 'Panel 1',
|
||||
},
|
||||
title: 'Panel 1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
contentComponent: 'default',
|
||||
title: 'Panel 2',
|
||||
},
|
||||
panel3: {
|
||||
id: 'panel3',
|
||||
contentComponent: 'default',
|
||||
params: {
|
||||
title: 'Panel 3',
|
||||
},
|
||||
title: 'panel3',
|
||||
},
|
||||
},
|
||||
activeGroup: '1',
|
||||
});
|
||||
});
|
||||
|
||||
test('check dockview component is rendering to the DOM as expected', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
|
||||
dockview.layout(100, 100);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel3.group,
|
||||
panel3.group.id,
|
||||
panel3.id,
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(2);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel3.group,
|
||||
panel2.group.id,
|
||||
panel2.id,
|
||||
'bottom'
|
||||
);
|
||||
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(4);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel2.group,
|
||||
panel1.group.id,
|
||||
panel1.id,
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
|
||||
expect(dockview.element.querySelectorAll('.view').length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
@ -37,13 +37,13 @@ describe('dockviewPanel', () => {
|
||||
latestTitle = event.title;
|
||||
});
|
||||
|
||||
expect(cut.title).toBe('');
|
||||
expect(cut.title).toBeUndefined();
|
||||
|
||||
cut.init({ title: 'new title', params: {} });
|
||||
expect(latestTitle).toBe('new title');
|
||||
expect(cut.title).toBe('new title');
|
||||
|
||||
cut.update({ params: { title: 'another title' } });
|
||||
cut.setTitle('another title');
|
||||
expect(latestTitle).toBe('another title');
|
||||
expect(cut.title).toBe('another title');
|
||||
|
||||
@ -81,6 +81,9 @@ describe('dockviewPanel', () => {
|
||||
|
||||
cut.setTitle('newTitle');
|
||||
expect(cut.title).toBe('newTitle');
|
||||
|
||||
cut.api.setTitle('new title 2');
|
||||
expect(cut.title).toBe('new title 2');
|
||||
});
|
||||
|
||||
test('dispose cleanup', () => {
|
||||
@ -142,7 +145,7 @@ describe('dockviewPanel', () => {
|
||||
|
||||
expect(cut.params).toEqual(undefined);
|
||||
|
||||
cut.update({ params: { params: { variableA: 'A', variableB: 'B' } } });
|
||||
cut.update({ params: { variableA: 'A', variableB: 'B' } });
|
||||
|
||||
expect(cut.params).toEqual({ variableA: 'A', variableB: 'B' });
|
||||
});
|
||||
@ -181,4 +184,67 @@ describe('dockviewPanel', () => {
|
||||
expect(group.api.setSize).toBeCalledWith({ height: 123, width: 456 });
|
||||
expect(group.api.setSize).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('updateParameter', () => {
|
||||
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const panelModelMock = jest.fn<Partial<IDockviewPanelModel>, []>(() => {
|
||||
return {
|
||||
update: jest.fn(),
|
||||
init: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
|
||||
cut.init({ params: { a: '1', b: '2' }, title: 'A title' });
|
||||
expect(cut.params).toEqual({ a: '1', b: '2' });
|
||||
|
||||
// update 'a' and add 'c'
|
||||
cut.update({ params: { a: '-1', c: '3' } });
|
||||
expect(cut.params).toEqual({ a: '-1', b: '2', c: '3' });
|
||||
|
||||
cut.update({ params: { d: '4', e: '5', f: '6' } });
|
||||
expect(cut.params).toEqual({
|
||||
a: '-1',
|
||||
b: '2',
|
||||
c: '3',
|
||||
d: '4',
|
||||
e: '5',
|
||||
f: '6',
|
||||
});
|
||||
|
||||
cut.update({
|
||||
params: {
|
||||
d: '',
|
||||
e: null,
|
||||
f: undefined,
|
||||
g: '',
|
||||
h: null,
|
||||
i: undefined,
|
||||
},
|
||||
});
|
||||
expect(cut.params).toEqual({
|
||||
a: '-1',
|
||||
b: '2',
|
||||
c: '3',
|
||||
d: '',
|
||||
e: null,
|
||||
g: '',
|
||||
h: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,7 +1,16 @@
|
||||
import { Emitter, Event } from '../events';
|
||||
import {
|
||||
Emitter,
|
||||
Event,
|
||||
addDisposableListener,
|
||||
addDisposableWindowListener,
|
||||
} from '../events';
|
||||
|
||||
describe('events', () => {
|
||||
describe('emitter', () => {
|
||||
it('debug mode is off', () => {
|
||||
expect(Emitter.ENABLE_TRACKING).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should emit values', () => {
|
||||
const emitter = new Emitter<number>();
|
||||
let value: number | undefined = undefined;
|
||||
@ -97,4 +106,138 @@ describe('events', () => {
|
||||
emitter3.fire(3);
|
||||
expect(value).toBe(3);
|
||||
});
|
||||
|
||||
it('addDisposableWindowListener with capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableWindowListener(
|
||||
element as any,
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
expect(element.removeEventListener).toBeCalledTimes(0);
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('addDisposableWindowListener without capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableWindowListener(
|
||||
element as any,
|
||||
'mousedown',
|
||||
handler
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
expect(element.removeEventListener).toBeCalledTimes(0);
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('addDisposableListener with capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableListener(
|
||||
element as any,
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
expect(element.removeEventListener).toBeCalledTimes(0);
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('addDisposableListener without capture options', () => {
|
||||
const element = {
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
};
|
||||
|
||||
const handler = jest.fn();
|
||||
|
||||
const disposable = addDisposableListener(
|
||||
element as any,
|
||||
'mousedown',
|
||||
handler
|
||||
);
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.addEventListener).toHaveBeenCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
expect(element.removeEventListener).toBeCalledTimes(0);
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
expect(element.addEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledTimes(1);
|
||||
expect(element.removeEventListener).toBeCalledWith(
|
||||
'mousedown',
|
||||
handler,
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -18,6 +18,10 @@ class MockGridview implements IGridView {
|
||||
>().event;
|
||||
element: HTMLElement = document.createElement('div');
|
||||
|
||||
constructor() {
|
||||
this.element.className = 'mock-grid-view';
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
//
|
||||
}
|
||||
@ -116,4 +120,574 @@ describe('gridview', () => {
|
||||
|
||||
checkOrientationFlipsAtEachLevel((gridview as any).root as BranchNode);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf from branch where branch becomes leaf and parent is root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(3);
|
||||
|
||||
gridview.removeView([1, 0], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(2);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf from branch where branch remains branch and parent is root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 1]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 333,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 333,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 334,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(4);
|
||||
|
||||
gridview.removeView([1, 0], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(3);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf where parent is root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(3);
|
||||
|
||||
gridview.removeView([0], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'VERTICAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(2);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf from branch where branch becomes leaf and parent is not root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 0]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 250,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 250,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(4);
|
||||
|
||||
gridview.removeView([1, 0, 0], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(3);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf from branch where branch remains branch and parent is not root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 1]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 168,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(5);
|
||||
|
||||
gridview.removeView([1, 0, 1], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 250,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 250,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(4);
|
||||
});
|
||||
|
||||
test('removeView: remove leaf where parent is root', () => {
|
||||
const gridview = new Gridview(
|
||||
false,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
|
||||
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 0]);
|
||||
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 1]);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 168,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
type: 'branch',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(5);
|
||||
|
||||
gridview.removeView([1, 1], Sizing.Distribute);
|
||||
|
||||
expect(gridview.serialize()).toEqual({
|
||||
height: 1000,
|
||||
orientation: 'HORIZONTAL',
|
||||
root: {
|
||||
data: [
|
||||
{
|
||||
data: {},
|
||||
size: 500,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 166,
|
||||
type: 'leaf',
|
||||
},
|
||||
{
|
||||
data: {},
|
||||
size: 168,
|
||||
type: 'leaf',
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
type: 'branch',
|
||||
},
|
||||
width: 1000,
|
||||
});
|
||||
expect(
|
||||
gridview.element.querySelectorAll('.mock-grid-view').length
|
||||
).toBe(4);
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {DockviewComponent} from '../../dockview/dockviewComponent';
|
||||
import {
|
||||
GroupPanelUpdateEvent,
|
||||
GroupviewPanelState,
|
||||
IGroupPanelInitParameters,
|
||||
GroupPanelPartInitParameters,
|
||||
@ -39,7 +38,7 @@ class TestModel implements IDockviewPanelModel {
|
||||
this.tab = new TestContentPart(id);
|
||||
}
|
||||
|
||||
update(event: GroupPanelUpdateEvent): void {
|
||||
update(event: PanelUpdateEvent): void {
|
||||
//
|
||||
}
|
||||
|
||||
@ -203,6 +202,10 @@ export class TestPanel implements IDockviewPanel {
|
||||
//noop
|
||||
}
|
||||
|
||||
setTitle(title: string): void {
|
||||
//
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent) {
|
||||
//noop
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
Sizing,
|
||||
Splitview,
|
||||
} from '../../splitview/splitview';
|
||||
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
class Testview implements IView {
|
||||
private _element: HTMLElement = document.createElement('div');
|
||||
private _size = 0;
|
||||
@ -84,6 +84,8 @@ describe('splitview', () => {
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div');
|
||||
container.className = 'container';
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('vertical splitview', () => {
|
||||
@ -585,8 +587,149 @@ describe('splitview', () => {
|
||||
|
||||
expect(container.childNodes.length).toBeGreaterThan(0);
|
||||
|
||||
splitview.dispose();
|
||||
let anyEvents = false;
|
||||
const listener = splitview.onDidRemoveView((e) => {
|
||||
anyEvents = true; // disposing of the splitview shouldn't fire onDidRemoveView events
|
||||
});
|
||||
|
||||
splitview.dispose();
|
||||
listener.dispose();
|
||||
|
||||
expect(anyEvents).toBeFalsy();
|
||||
expect(container.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('dnd: mouse events to move sash', () => {
|
||||
const splitview = new Splitview(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: false,
|
||||
});
|
||||
splitview.layout(400, 500);
|
||||
|
||||
const view1 = new Testview(0, 1000);
|
||||
const view2 = new Testview(0, 1000);
|
||||
|
||||
splitview.addView(view1);
|
||||
splitview.addView(view2);
|
||||
|
||||
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
|
||||
const removeEventListenerSpy = jest.spyOn(
|
||||
document,
|
||||
'removeEventListener'
|
||||
);
|
||||
|
||||
const sashElement = container
|
||||
.getElementsByClassName('sash')
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
// validate the expected state before drag
|
||||
expect([view1.size, view2.size]).toEqual([200, 200]);
|
||||
expect(sashElement).toBeTruthy();
|
||||
expect(view1.element.parentElement!.style.pointerEvents).toBe('');
|
||||
expect(view2.element.parentElement!.style.pointerEvents).toBe('');
|
||||
|
||||
// start the drag event
|
||||
fireEvent.mouseDown(sashElement, { clientX: 50, clientY: 100 });
|
||||
|
||||
expect(addEventListenerSpy).toBeCalledTimes(5);
|
||||
|
||||
// during a sash drag the views should have pointer-events disabled
|
||||
expect(view1.element.parentElement!.style.pointerEvents).toBe('none');
|
||||
expect(view2.element.parentElement!.style.pointerEvents).toBe('none');
|
||||
|
||||
// expect a delta move of 70 - 50 = 20
|
||||
fireEvent.mouseMove(document, { clientX: 70, clientY: 110 });
|
||||
expect([view1.size, view2.size]).toEqual([220, 180]);
|
||||
|
||||
// expect a delta move of 75 - 70 = 5
|
||||
fireEvent.mouseMove(document, { clientX: 75, clientY: 110 });
|
||||
expect([view1.size, view2.size]).toEqual([225, 175]);
|
||||
|
||||
// end the drag event
|
||||
fireEvent.mouseUp(document);
|
||||
|
||||
expect(removeEventListenerSpy).toBeCalledTimes(5);
|
||||
|
||||
// expect pointer-eventes on views to be restored
|
||||
expect(view1.element.parentElement!.style.pointerEvents).toBe('');
|
||||
expect(view2.element.parentElement!.style.pointerEvents).toBe('');
|
||||
|
||||
fireEvent.mouseMove(document, { clientX: 100, clientY: 100 });
|
||||
// expect no additional resizes
|
||||
expect([view1.size, view2.size]).toEqual([225, 175]);
|
||||
// expect no additional document listeners
|
||||
expect(addEventListenerSpy).toBeCalledTimes(5);
|
||||
expect(removeEventListenerSpy).toBeCalledTimes(5);
|
||||
});
|
||||
|
||||
test('dnd: touch events to move sash', () => {
|
||||
const splitview = new Splitview(container, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
proportionalLayout: false,
|
||||
});
|
||||
splitview.layout(400, 500);
|
||||
|
||||
const view1 = new Testview(0, 1000);
|
||||
const view2 = new Testview(0, 1000);
|
||||
|
||||
splitview.addView(view1);
|
||||
splitview.addView(view2);
|
||||
|
||||
const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
|
||||
const removeEventListenerSpy = jest.spyOn(
|
||||
document,
|
||||
'removeEventListener'
|
||||
);
|
||||
|
||||
const sashElement = container
|
||||
.getElementsByClassName('sash')
|
||||
.item(0) as HTMLElement;
|
||||
|
||||
// validate the expected state before drag
|
||||
expect([view1.size, view2.size]).toEqual([200, 200]);
|
||||
expect(sashElement).toBeTruthy();
|
||||
expect(view1.element.parentElement!.style.pointerEvents).toBe('');
|
||||
expect(view2.element.parentElement!.style.pointerEvents).toBe('');
|
||||
|
||||
// start the drag event
|
||||
fireEvent.touchStart(sashElement, {
|
||||
touches: [{ clientX: 50, clientY: 100 }],
|
||||
});
|
||||
|
||||
expect(addEventListenerSpy).toBeCalledTimes(5);
|
||||
|
||||
// during a sash drag the views should have pointer-events disabled
|
||||
expect(view1.element.parentElement!.style.pointerEvents).toBe('none');
|
||||
expect(view2.element.parentElement!.style.pointerEvents).toBe('none');
|
||||
|
||||
// expect a delta move of 70 - 50 = 20
|
||||
fireEvent.touchMove(document, {
|
||||
touches: [{ clientX: 70, clientY: 110 }],
|
||||
});
|
||||
expect([view1.size, view2.size]).toEqual([220, 180]);
|
||||
|
||||
// expect a delta move of 75 - 70 = 5
|
||||
fireEvent.touchMove(document, {
|
||||
touches: [{ clientX: 75, clientY: 110 }],
|
||||
});
|
||||
expect([view1.size, view2.size]).toEqual([225, 175]);
|
||||
|
||||
// end the drag event
|
||||
fireEvent.touchEnd(document);
|
||||
|
||||
expect(removeEventListenerSpy).toBeCalledTimes(5);
|
||||
|
||||
// expect pointer-eventes on views to be restored
|
||||
expect(view1.element.parentElement!.style.pointerEvents).toBe('');
|
||||
expect(view2.element.parentElement!.style.pointerEvents).toBe('');
|
||||
|
||||
fireEvent.touchMove(document, {
|
||||
touches: [{ clientX: 100, clientY: 100 }],
|
||||
});
|
||||
// expect no additional resizes
|
||||
expect([view1.size, view2.size]).toEqual([225, 175]);
|
||||
// expect no additional document listeners
|
||||
expect(addEventListenerSpy).toBeCalledTimes(5);
|
||||
expect(removeEventListenerSpy).toBeCalledTimes(5);
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PanelDimensionChangeEvent } from '../../api/panelApi';
|
||||
import { Emitter } from '../../events';
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
import { Orientation } from '../../splitview/splitview';
|
||||
import { SplitviewComponent } from '../../splitview/splitviewComponent';
|
||||
@ -25,6 +26,45 @@ describe('componentSplitview', () => {
|
||||
container.className = 'container';
|
||||
});
|
||||
|
||||
test('event leakage', () => {
|
||||
Emitter.setLeakageMonitorEnabled(true);
|
||||
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
orientation: Orientation.VERTICAL,
|
||||
components: {
|
||||
testPanel: TestPanel,
|
||||
},
|
||||
});
|
||||
splitview.layout(600, 400);
|
||||
|
||||
const panel1 = splitview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'testPanel',
|
||||
});
|
||||
const panel2 = splitview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'testPanel',
|
||||
});
|
||||
|
||||
splitview.movePanel(0, 1);
|
||||
|
||||
splitview.removePanel(panel1);
|
||||
|
||||
splitview.dispose();
|
||||
|
||||
if (Emitter.MEMORY_LEAK_WATCHER.size > 0) {
|
||||
for (const entry of Array.from(
|
||||
Emitter.MEMORY_LEAK_WATCHER.events
|
||||
)) {
|
||||
console.log(entry[1]);
|
||||
}
|
||||
throw new Error('not all listeners disposed');
|
||||
}
|
||||
|
||||
Emitter.setLeakageMonitorEnabled(false);
|
||||
});
|
||||
|
||||
test('remove panel', () => {
|
||||
const splitview = new SplitviewComponent({
|
||||
parentElement: container,
|
||||
|
@ -19,7 +19,7 @@ export interface DockviewPanelApi
|
||||
> {
|
||||
readonly group: DockviewGroupPanel;
|
||||
readonly isGroupActive: boolean;
|
||||
readonly title: string;
|
||||
readonly title: string | undefined;
|
||||
readonly onDidActiveGroupChange: Event<void>;
|
||||
readonly onDidGroupChange: Event<void>;
|
||||
close(): void;
|
||||
@ -43,7 +43,7 @@ export class DockviewPanelApiImpl
|
||||
|
||||
private readonly disposable = new MutableDisposable();
|
||||
|
||||
get title(): string {
|
||||
get title(): string | undefined {
|
||||
return this.panel.title;
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ export class DockviewPanelApiImpl
|
||||
}
|
||||
|
||||
public setTitle(title: string): void {
|
||||
this.panel.update({ params: { title } });
|
||||
this.panel.setTitle(title);
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
|
@ -126,15 +126,6 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
super();
|
||||
|
||||
this.addDisposables(
|
||||
this.panelUpdatesDisposable,
|
||||
this._onDidDimensionChange,
|
||||
this._onDidChangeFocus,
|
||||
this._onDidVisibilityChange,
|
||||
this._onDidActiveChange,
|
||||
this._onFocusEvent,
|
||||
this._onActiveChange,
|
||||
this._onVisibilityChange,
|
||||
this._onUpdateParameters,
|
||||
this.onDidFocusChange((event) => {
|
||||
this._isFocused = event.isFocused;
|
||||
}),
|
||||
@ -147,7 +138,16 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
this.onDidDimensionsChange((event) => {
|
||||
this._width = event.width;
|
||||
this._height = event.height;
|
||||
})
|
||||
}),
|
||||
this.panelUpdatesDisposable,
|
||||
this._onDidDimensionChange,
|
||||
this._onDidChangeFocus,
|
||||
this._onDidVisibilityChange,
|
||||
this._onDidActiveChange,
|
||||
this._onFocusEvent,
|
||||
this._onActiveChange,
|
||||
this._onVisibilityChange,
|
||||
this._onUpdateParameters
|
||||
);
|
||||
}
|
||||
|
||||
@ -155,9 +155,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
this.panelUpdatesDisposable.value = this._onUpdateParameters.event(
|
||||
(parameters) => {
|
||||
panel.update({
|
||||
params: {
|
||||
params: parameters,
|
||||
},
|
||||
params: parameters,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -7,15 +7,21 @@ import {
|
||||
} from '../lifecycle';
|
||||
|
||||
export abstract class DragHandler extends CompositeDisposable {
|
||||
private readonly disposable = new MutableDisposable();
|
||||
private readonly dataDisposable = new MutableDisposable();
|
||||
private readonly pointerEventsDisposable = new MutableDisposable();
|
||||
|
||||
private readonly _onDragStart = new Emitter<void>();
|
||||
readonly onDragStart = this._onDragStart.event;
|
||||
|
||||
private iframes: HTMLElement[] = [];
|
||||
|
||||
constructor(protected readonly el: HTMLElement) {
|
||||
super();
|
||||
|
||||
this.addDisposables(
|
||||
this._onDragStart,
|
||||
this.dataDisposable,
|
||||
this.pointerEventsDisposable
|
||||
);
|
||||
|
||||
this.configure();
|
||||
}
|
||||
|
||||
@ -25,19 +31,27 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
this.addDisposables(
|
||||
this._onDragStart,
|
||||
addDisposableListener(this.el, 'dragstart', (event) => {
|
||||
this.iframes = [
|
||||
const iframes = [
|
||||
...getElementsByTagName('iframe'),
|
||||
...getElementsByTagName('webview'),
|
||||
];
|
||||
|
||||
for (const iframe of this.iframes) {
|
||||
this.pointerEventsDisposable.value = {
|
||||
dispose: () => {
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'auto';
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
for (const iframe of iframes) {
|
||||
iframe.style.pointerEvents = 'none';
|
||||
}
|
||||
|
||||
this.el.classList.add('dv-dragged');
|
||||
setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
|
||||
|
||||
this.disposable.value = this.getData(event.dataTransfer);
|
||||
this.dataDisposable.value = this.getData(event.dataTransfer);
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
@ -58,12 +72,8 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
}
|
||||
}),
|
||||
addDisposableListener(this.el, 'dragend', () => {
|
||||
for (const iframe of this.iframes) {
|
||||
iframe.style.pointerEvents = 'auto';
|
||||
}
|
||||
this.iframes = [];
|
||||
|
||||
this.disposable.dispose();
|
||||
this.pointerEventsDisposable.dispose();
|
||||
this.dataDisposable.dispose();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -177,6 +177,7 @@ export class Droptarget extends CompositeDisposable {
|
||||
|
||||
public dispose(): void {
|
||||
this.removeDropTarget();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private toggleClasses(
|
||||
|
@ -53,8 +53,4 @@ export class GroupDragHandler extends DragHandler {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
@ -77,11 +77,12 @@ export class ContentContainer
|
||||
const _onDidFocus = this.panel.view.content.onDidFocus;
|
||||
const _onDidBlur = this.panel.view.content.onDidBlur;
|
||||
|
||||
const { onDidFocus, onDidBlur } = trackFocus(this._element);
|
||||
const focusTracker = trackFocus(this._element);
|
||||
|
||||
disposable.addDisposables(
|
||||
onDidFocus(() => this._onDidFocus.fire()),
|
||||
onDidBlur(() => this._onDidBlur.fire())
|
||||
focusTracker,
|
||||
focusTracker.onDidFocus(() => this._onDidFocus.fire()),
|
||||
focusTracker.onDidBlur(() => this._onDidBlur.fire())
|
||||
);
|
||||
|
||||
if (_onDidFocus) {
|
||||
|
@ -12,7 +12,7 @@ import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget';
|
||||
import { DragHandler } from '../../../dnd/abstractDragHandler';
|
||||
|
||||
export interface ITab {
|
||||
export interface ITab extends IDisposable {
|
||||
readonly panelId: string;
|
||||
readonly element: HTMLElement;
|
||||
setContent: (element: ITabRenderer) => void;
|
||||
@ -43,8 +43,6 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
) {
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onChanged, this._onDropped);
|
||||
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'tab';
|
||||
this._element.tabIndex = 0;
|
||||
@ -53,6 +51,8 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
toggleClass(this.element, 'inactive-tab', true);
|
||||
|
||||
this.addDisposables(
|
||||
this._onChanged,
|
||||
this._onDropped,
|
||||
new (class Handler extends DragHandler {
|
||||
private readonly panelTransfer =
|
||||
LocalSelectionTransfer.getInstance<PanelTransfer>();
|
||||
@ -71,10 +71,6 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
//
|
||||
}
|
||||
})(this._element)
|
||||
);
|
||||
|
||||
@ -127,7 +123,8 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
this.addDisposables(
|
||||
this.droptarget.onDrop((event) => {
|
||||
this._onDropped.fire(event);
|
||||
})
|
||||
}),
|
||||
this.droptarget
|
||||
);
|
||||
}
|
||||
|
||||
@ -146,6 +143,5 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.droptarget.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -216,6 +216,7 @@ export class TabsContainer
|
||||
const { value, disposable } = tabToRemove;
|
||||
|
||||
disposable.dispose();
|
||||
value.dispose();
|
||||
value.element.remove();
|
||||
}
|
||||
|
||||
@ -275,9 +276,11 @@ export class TabsContainer
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.tabs.forEach((tab) => {
|
||||
tab.disposable.dispose();
|
||||
});
|
||||
for (const { value, disposable } of this.tabs) {
|
||||
disposable.dispose();
|
||||
value.dispose();
|
||||
}
|
||||
|
||||
this.tabs = [];
|
||||
}
|
||||
}
|
||||
|
@ -251,7 +251,6 @@ export class DockviewComponent
|
||||
});
|
||||
|
||||
this.addDisposables(
|
||||
dropTarget,
|
||||
dropTarget.onDrop((event) => {
|
||||
const data = getPanelData();
|
||||
|
||||
@ -270,7 +269,8 @@ export class DockviewComponent
|
||||
getData: getPanelData,
|
||||
});
|
||||
}
|
||||
})
|
||||
}),
|
||||
dropTarget
|
||||
);
|
||||
|
||||
this._api = new DockviewApi(this);
|
||||
@ -706,43 +706,49 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
moveGroupOrPanel(
|
||||
referenceGroup: DockviewGroupPanel,
|
||||
groupId: string,
|
||||
itemId: string | undefined,
|
||||
target: Position,
|
||||
index?: number
|
||||
destinationGroup: DockviewGroupPanel,
|
||||
sourceGroupId: string,
|
||||
sourceItemId: string | undefined,
|
||||
destinationTarget: Position,
|
||||
destinationIndex?: number
|
||||
): void {
|
||||
const sourceGroup = groupId
|
||||
? this._groups.get(groupId)?.value
|
||||
const sourceGroup = sourceGroupId
|
||||
? this._groups.get(sourceGroupId)?.value
|
||||
: undefined;
|
||||
|
||||
if (itemId === undefined) {
|
||||
if (sourceItemId === undefined) {
|
||||
if (sourceGroup) {
|
||||
this.moveGroup(sourceGroup, referenceGroup, target);
|
||||
this.moveGroup(
|
||||
sourceGroup,
|
||||
destinationGroup,
|
||||
destinationTarget
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!target || target === 'center') {
|
||||
if (!destinationTarget || destinationTarget === 'center') {
|
||||
const groupItem: IDockviewPanel | undefined =
|
||||
sourceGroup?.model.removePanel(itemId) ||
|
||||
this.panels.find((panel) => panel.id === itemId);
|
||||
sourceGroup?.model.removePanel(sourceItemId) ||
|
||||
this.panels.find((panel) => panel.id === sourceItemId);
|
||||
|
||||
if (!groupItem) {
|
||||
throw new Error(`No panel with id ${itemId}`);
|
||||
throw new Error(`No panel with id ${sourceItemId}`);
|
||||
}
|
||||
|
||||
if (sourceGroup?.model.size === 0) {
|
||||
this.doRemoveGroup(sourceGroup);
|
||||
}
|
||||
|
||||
referenceGroup.model.openPanel(groupItem, { index });
|
||||
destinationGroup.model.openPanel(groupItem, {
|
||||
index: destinationIndex,
|
||||
});
|
||||
} else {
|
||||
const referenceLocation = getGridLocation(referenceGroup.element);
|
||||
const referenceLocation = getGridLocation(destinationGroup.element);
|
||||
const targetLocation = getRelativeLocation(
|
||||
this.gridview.orientation,
|
||||
referenceLocation,
|
||||
target
|
||||
destinationTarget
|
||||
);
|
||||
|
||||
if (sourceGroup && sourceGroup.size < 2) {
|
||||
@ -766,28 +772,28 @@ export class DockviewComponent
|
||||
|
||||
// after deleting the group we need to re-evaulate the ref location
|
||||
const updatedReferenceLocation = getGridLocation(
|
||||
referenceGroup.element
|
||||
destinationGroup.element
|
||||
);
|
||||
const location = getRelativeLocation(
|
||||
this.gridview.orientation,
|
||||
updatedReferenceLocation,
|
||||
target
|
||||
destinationTarget
|
||||
);
|
||||
this.doAddGroup(targetGroup, location);
|
||||
}
|
||||
} else {
|
||||
const groupItem: IDockviewPanel | undefined =
|
||||
sourceGroup?.model.removePanel(itemId) ||
|
||||
this.panels.find((panel) => panel.id === itemId);
|
||||
sourceGroup?.model.removePanel(sourceItemId) ||
|
||||
this.panels.find((panel) => panel.id === sourceItemId);
|
||||
|
||||
if (!groupItem) {
|
||||
throw new Error(`No panel with id ${itemId}`);
|
||||
throw new Error(`No panel with id ${sourceItemId}`);
|
||||
}
|
||||
|
||||
const dropLocation = getRelativeLocation(
|
||||
this.gridview.orientation,
|
||||
referenceLocation,
|
||||
target
|
||||
destinationTarget
|
||||
);
|
||||
|
||||
const group = this.createGroupAtLocation(dropLocation);
|
||||
@ -953,11 +959,11 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this._onDidActivePanelChange.dispose();
|
||||
this._onDidAddPanel.dispose();
|
||||
this._onDidRemovePanel.dispose();
|
||||
this._onDidLayoutFromJSON.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -282,12 +282,6 @@ export class DockviewGroupPanelModel
|
||||
this.locked = !!options.locked;
|
||||
|
||||
this.addDisposables(
|
||||
this._onMove,
|
||||
this._onDidChange,
|
||||
this._onDidDrop,
|
||||
this._onDidAddPanel,
|
||||
this._onDidRemovePanel,
|
||||
this._onDidActivePanelChange,
|
||||
this.tabsContainer.onDrop((event) => {
|
||||
this.handleDropEvent(event.event, 'center', event.index);
|
||||
}),
|
||||
@ -299,7 +293,13 @@ export class DockviewGroupPanelModel
|
||||
}),
|
||||
this.dropTarget.onDrop((event) => {
|
||||
this.handleDropEvent(event.nativeEvent, event.position);
|
||||
})
|
||||
}),
|
||||
this._onMove,
|
||||
this._onDidChange,
|
||||
this._onDidDrop,
|
||||
this._onDidAddPanel,
|
||||
this._onDidRemovePanel,
|
||||
this._onDidActivePanelChange
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,10 @@ import {
|
||||
DockviewPanelApi,
|
||||
DockviewPanelApiImpl,
|
||||
} from '../api/dockviewPanelApi';
|
||||
import {
|
||||
GroupPanelUpdateEvent,
|
||||
GroupviewPanelState,
|
||||
IGroupPanelInitParameters,
|
||||
} from './types';
|
||||
import { GroupviewPanelState, IGroupPanelInitParameters } from './types';
|
||||
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
import { IPanel, Parameters } from '../panel/types';
|
||||
import { IPanel, PanelUpdateEvent, Parameters } from '../panel/types';
|
||||
import { IDockviewPanelModel } from './dockviewPanelModel';
|
||||
import { IDockviewComponent } from './dockviewComponent';
|
||||
|
||||
@ -23,7 +19,8 @@ export interface IDockviewPanel extends IDisposable, IPanel {
|
||||
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void;
|
||||
init(params: IGroupPanelInitParameters): void;
|
||||
toJSON(): GroupviewPanelState;
|
||||
update(event: GroupPanelUpdateEvent): void;
|
||||
setTitle(title: string): void;
|
||||
update(event: PanelUpdateEvent): void;
|
||||
}
|
||||
|
||||
export class DockviewPanel
|
||||
@ -34,13 +31,13 @@ export class DockviewPanel
|
||||
private _group: DockviewGroupPanel;
|
||||
private _params?: Parameters;
|
||||
|
||||
private _title: string;
|
||||
private _title: string | undefined;
|
||||
|
||||
get params(): Parameters | undefined {
|
||||
return this._params;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
get title(): string | undefined {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
@ -56,7 +53,6 @@ export class DockviewPanel
|
||||
readonly view: IDockviewPanelModel
|
||||
) {
|
||||
super();
|
||||
this._title = '';
|
||||
this._group = group;
|
||||
|
||||
this.api = new DockviewPanelApiImpl(this, this._group);
|
||||
@ -76,13 +72,13 @@ export class DockviewPanel
|
||||
public init(params: IGroupPanelInitParameters): void {
|
||||
this._params = params.params;
|
||||
|
||||
this.setTitle(params.title);
|
||||
|
||||
this.view.init({
|
||||
...params,
|
||||
api: this.api,
|
||||
containerApi: this.containerApi,
|
||||
});
|
||||
|
||||
this.setTitle(params.title);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
@ -103,12 +99,12 @@ export class DockviewPanel
|
||||
}
|
||||
|
||||
setTitle(title: string): void {
|
||||
const didTitleChange = title !== this._params?.title;
|
||||
const didTitleChange = title !== this.title;
|
||||
|
||||
if (didTitleChange) {
|
||||
this._title = title;
|
||||
|
||||
this.view?.update({
|
||||
this.view.update({
|
||||
params: {
|
||||
params: this._params,
|
||||
title: this.title,
|
||||
@ -118,20 +114,25 @@ export class DockviewPanel
|
||||
}
|
||||
}
|
||||
|
||||
public update(event: GroupPanelUpdateEvent): void {
|
||||
const params = event.params as IGroupPanelInitParameters;
|
||||
|
||||
public update(event: PanelUpdateEvent): void {
|
||||
// merge the new parameters with the existing parameters
|
||||
this._params = {
|
||||
...(this._params || {}),
|
||||
...event.params.params,
|
||||
...event.params,
|
||||
};
|
||||
|
||||
if (params.title !== this.title) {
|
||||
this._title = params.title;
|
||||
this.api._onDidTitleChange.fire({ title: this.title });
|
||||
/**
|
||||
* delete new keys that have a value of undefined,
|
||||
* allow values of null
|
||||
*/
|
||||
for (const key of Object.keys(event.params)) {
|
||||
if (event.params[key] === undefined) {
|
||||
delete this._params[key];
|
||||
}
|
||||
}
|
||||
|
||||
this.view?.update({
|
||||
// update the view with the updated props
|
||||
this.view.update({
|
||||
params: {
|
||||
params: this._params,
|
||||
title: this.title,
|
||||
|
@ -3,19 +3,19 @@ import {
|
||||
GroupPanelPartInitParameters,
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
GroupPanelUpdateEvent,
|
||||
} from './types';
|
||||
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { createComponent } from '../panel/componentFactory';
|
||||
import { IDockviewComponent } from './dockviewComponent';
|
||||
import { PanelUpdateEvent } from '../panel/types';
|
||||
|
||||
export interface IDockviewPanelModel extends IDisposable {
|
||||
readonly contentComponent: string;
|
||||
readonly tabComponent?: string;
|
||||
readonly content: IContentRenderer;
|
||||
readonly tab?: ITabRenderer;
|
||||
update(event: GroupPanelUpdateEvent): void;
|
||||
update(event: PanelUpdateEvent): void;
|
||||
layout(width: number, height: number): void;
|
||||
init(params: GroupPanelPartInitParameters): void;
|
||||
updateParentGroup(group: DockviewGroupPanel, isPanelVisible: boolean): void;
|
||||
@ -80,7 +80,7 @@ export class DockviewPanelModel implements IDockviewPanelModel {
|
||||
this.content.layout?.(width, height);
|
||||
}
|
||||
|
||||
update(event: GroupPanelUpdateEvent): void {
|
||||
update(event: PanelUpdateEvent): void {
|
||||
this.content.update?.(event);
|
||||
this.tab.update?.(event);
|
||||
}
|
||||
|
@ -1,11 +1,6 @@
|
||||
import { IDockviewComponent } from './dockviewComponent';
|
||||
import { DockviewPanelApi } from '../api/dockviewPanelApi';
|
||||
import {
|
||||
PanelInitParameters,
|
||||
IPanel,
|
||||
PanelUpdateEvent,
|
||||
Parameters,
|
||||
} from '../panel/types';
|
||||
import { PanelInitParameters, IPanel } from '../panel/types';
|
||||
import { DockviewApi } from '../api/component.api';
|
||||
import { Event } from '../events';
|
||||
import { Optional } from '../types';
|
||||
@ -91,11 +86,6 @@ export interface IGroupPanelInitParameters
|
||||
//
|
||||
}
|
||||
|
||||
export type GroupPanelUpdateEvent = PanelUpdateEvent<{
|
||||
params?: Parameters;
|
||||
title?: string;
|
||||
}>;
|
||||
|
||||
export interface GroupviewPanelState {
|
||||
id: string;
|
||||
contentComponent?: string;
|
||||
|
@ -111,6 +111,8 @@ class FocusTracker extends CompositeDisposable implements IFocusTracker {
|
||||
constructor(element: HTMLElement | Window) {
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onDidFocus, this._onDidBlur);
|
||||
|
||||
let hasFocus = isAncestor(document.activeElement, <HTMLElement>element);
|
||||
let loosingFocus = false;
|
||||
|
||||
@ -169,11 +171,4 @@ class FocusTracker extends CompositeDisposable implements IFocusTracker {
|
||||
refreshState(): void {
|
||||
this._refreshStateHandler();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this._onDidBlur.dispose();
|
||||
this._onDidFocus.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -24,24 +24,76 @@ export namespace Event {
|
||||
};
|
||||
}
|
||||
|
||||
// dumb event emitter with better typings than nodes event emitter
|
||||
// https://github.com/microsoft/vscode/blob/master/src/vs/base/common/event.ts
|
||||
class LeakageMonitor {
|
||||
readonly events = new Map<Event<any>, Stacktrace>();
|
||||
|
||||
get size(): number {
|
||||
return this.events.size;
|
||||
}
|
||||
|
||||
add<T>(event: Event<T>, stacktrace: Stacktrace): void {
|
||||
this.events.set(event, stacktrace);
|
||||
}
|
||||
|
||||
delete<T>(event: Event<T>): void {
|
||||
this.events.delete(event);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.events.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class Stacktrace {
|
||||
static create(): Stacktrace {
|
||||
return new Stacktrace(new Error().stack ?? '');
|
||||
}
|
||||
|
||||
private constructor(readonly value: string) {}
|
||||
|
||||
print(): void {
|
||||
console.warn(this.value);
|
||||
}
|
||||
}
|
||||
|
||||
class Listener<T> {
|
||||
constructor(
|
||||
readonly callback: (t: T) => void,
|
||||
readonly stacktrace: Stacktrace | undefined
|
||||
) {}
|
||||
}
|
||||
|
||||
// relatively simple event emitter taken from https://github.com/microsoft/vscode/blob/master/src/vs/base/common/event.ts
|
||||
export class Emitter<T> implements IDisposable {
|
||||
private _event?: Event<T>;
|
||||
|
||||
private _last?: T;
|
||||
private _listeners: Array<(e: T) => any> = [];
|
||||
private _listeners: Listener<any>[] = [];
|
||||
private _disposed = false;
|
||||
|
||||
static ENABLE_TRACKING = false;
|
||||
static readonly MEMORY_LEAK_WATCHER = new LeakageMonitor();
|
||||
|
||||
static setLeakageMonitorEnabled(isEnabled: boolean) {
|
||||
if (isEnabled !== Emitter.ENABLE_TRACKING) {
|
||||
Emitter.MEMORY_LEAK_WATCHER.clear();
|
||||
}
|
||||
Emitter.ENABLE_TRACKING = isEnabled;
|
||||
}
|
||||
|
||||
constructor(private readonly options?: EmitterOptions) {}
|
||||
|
||||
get event(): Event<T> {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (e: T) => void): IDisposable => {
|
||||
this._event = (callback: (e: T) => void): IDisposable => {
|
||||
if (this.options?.replay && this._last !== undefined) {
|
||||
listener(this._last);
|
||||
callback(this._last);
|
||||
}
|
||||
|
||||
const listener = new Listener(
|
||||
callback,
|
||||
Emitter.ENABLE_TRACKING ? Stacktrace.create() : undefined
|
||||
);
|
||||
this._listeners.push(listener);
|
||||
|
||||
return {
|
||||
@ -49,10 +101,22 @@ export class Emitter<T> implements IDisposable {
|
||||
const index = this._listeners.indexOf(listener);
|
||||
if (index > -1) {
|
||||
this._listeners.splice(index, 1);
|
||||
} else if (Emitter.ENABLE_TRACKING) {
|
||||
// console.warn(
|
||||
// `Listener already disposed`,
|
||||
// Stacktrace.create().print()
|
||||
// );
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
if (Emitter.ENABLE_TRACKING) {
|
||||
Emitter.MEMORY_LEAK_WATCHER.add(
|
||||
this._event,
|
||||
Stacktrace.create()
|
||||
);
|
||||
}
|
||||
}
|
||||
return this._event;
|
||||
}
|
||||
@ -60,13 +124,31 @@ export class Emitter<T> implements IDisposable {
|
||||
public fire(e: T): void {
|
||||
this._last = e;
|
||||
for (const listener of this._listeners) {
|
||||
listener(e);
|
||||
listener.callback(e);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._listeners = [];
|
||||
this._disposed = true;
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
|
||||
if (this._listeners.length > 0) {
|
||||
if (Emitter.ENABLE_TRACKING) {
|
||||
queueMicrotask(() => {
|
||||
// don't check until stack of execution is completed to allow for out-of-order disposals within the same execution block
|
||||
for (const listener of this._listeners) {
|
||||
console.warn(listener.stacktrace?.print());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
if (Emitter.ENABLE_TRACKING && this._event) {
|
||||
Emitter.MEMORY_LEAK_WATCHER.delete(this._event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +162,7 @@ export function addDisposableWindowListener<K extends keyof WindowEventMap>(
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
element.removeEventListener(type, listener);
|
||||
element.removeEventListener(type, listener, options);
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -95,7 +177,7 @@ export function addDisposableListener<K extends keyof HTMLElementEventMap>(
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
element.removeEventListener(type, listener);
|
||||
element.removeEventListener(type, listener, options);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -143,10 +143,7 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
this.addDisposables(
|
||||
this.gridview.onDidChange(() => {
|
||||
this._bufferOnDidLayoutChange.fire();
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
}),
|
||||
Event.any(
|
||||
this.onDidAddGroup,
|
||||
this.onDidRemoveGroup,
|
||||
@ -297,8 +294,6 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this._onDidActiveGroupChange.dispose();
|
||||
this._onDidAddGroup.dispose();
|
||||
this._onDidRemoveGroup.dispose();
|
||||
@ -309,5 +304,7 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
}
|
||||
|
||||
this.gridview.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -69,16 +69,17 @@ export abstract class BasePanelView<T extends PanelApiImpl>
|
||||
this._element.style.width = '100%';
|
||||
this._element.style.overflow = 'hidden';
|
||||
|
||||
const { onDidFocus, onDidBlur } = trackFocus(this._element);
|
||||
const focusTracker = trackFocus(this._element);
|
||||
|
||||
this.addDisposables(
|
||||
this.api,
|
||||
onDidFocus(() => {
|
||||
focusTracker.onDidFocus(() => {
|
||||
this.api._onDidChangeFocus.fire({ isFocused: true });
|
||||
}),
|
||||
onDidBlur(() => {
|
||||
focusTracker.onDidBlur(() => {
|
||||
this.api._onDidChangeFocus.fire({ isFocused: false });
|
||||
})
|
||||
}),
|
||||
focusTracker
|
||||
);
|
||||
}
|
||||
|
||||
@ -104,6 +105,7 @@ export abstract class BasePanelView<T extends PanelApiImpl>
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent): void {
|
||||
// merge the new parameters with the existing parameters
|
||||
this._params = {
|
||||
...this._params,
|
||||
params: {
|
||||
@ -111,6 +113,18 @@ export abstract class BasePanelView<T extends PanelApiImpl>
|
||||
...event.params,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* delete new keys that have a value of undefined,
|
||||
* allow values of null
|
||||
*/
|
||||
for (const key of Object.keys(event.params)) {
|
||||
if (event.params[key] === undefined) {
|
||||
delete this._params.params[key];
|
||||
}
|
||||
}
|
||||
|
||||
// update the view with the updated props
|
||||
this.part?.update({ params: this._params.params });
|
||||
}
|
||||
|
||||
@ -125,9 +139,9 @@ export abstract class BasePanelView<T extends PanelApiImpl>
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.api.dispose();
|
||||
this.part?.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -260,13 +260,13 @@ export class BranchNode extends CompositeDisposable implements IView {
|
||||
return this.splitview.getViewCachedVisibleSize(index);
|
||||
}
|
||||
|
||||
public removeChild(index: number, sizing?: Sizing): void {
|
||||
public removeChild(index: number, sizing?: Sizing): Node {
|
||||
if (index < 0 || index >= this.children.length) {
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
|
||||
this.splitview.removeView(index, sizing);
|
||||
this._removeChild(index);
|
||||
return this._removeChild(index);
|
||||
}
|
||||
|
||||
private _addChild(node: Node, index: number): void {
|
||||
@ -296,9 +296,10 @@ export class BranchNode extends CompositeDisposable implements IView {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this._childrenDisposable.dispose();
|
||||
this.children.forEach((child) => child.dispose());
|
||||
this.splitview.dispose();
|
||||
this.children.forEach((child) => child.dispose());
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -462,7 +462,8 @@ export class Gridview implements IDisposable {
|
||||
if (oldRoot.children.length === 1) {
|
||||
// can remove one level of redundant branching if there is only a single child
|
||||
const childReference = oldRoot.children[0];
|
||||
oldRoot.removeChild(0); // remove to prevent disposal when disposing of unwanted root
|
||||
const child = oldRoot.removeChild(0); // remove to prevent disposal when disposing of unwanted root
|
||||
child.dispose();
|
||||
oldRoot.dispose();
|
||||
|
||||
this._root.addChild(
|
||||
@ -632,7 +633,8 @@ export class Gridview implements IDisposable {
|
||||
newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize);
|
||||
}
|
||||
|
||||
grandParent.removeChild(parentIndex);
|
||||
const child = grandParent.removeChild(parentIndex);
|
||||
child.dispose();
|
||||
|
||||
const newParent = new BranchNode(
|
||||
parent.orientation,
|
||||
@ -676,60 +678,82 @@ export class Gridview implements IDisposable {
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
const node = parent.children[index];
|
||||
const nodeToRemove = parent.children[index];
|
||||
|
||||
if (!(node instanceof LeafNode)) {
|
||||
if (!(nodeToRemove instanceof LeafNode)) {
|
||||
throw new Error('Invalid location');
|
||||
}
|
||||
|
||||
parent.removeChild(index, sizing);
|
||||
nodeToRemove.dispose();
|
||||
|
||||
if (parent.children.length === 0) {
|
||||
return node.view;
|
||||
if (parent.children.length !== 1) {
|
||||
return nodeToRemove.view;
|
||||
}
|
||||
|
||||
if (parent.children.length > 1) {
|
||||
return node.view;
|
||||
}
|
||||
// if the parent has only one child and we know the parent is a BranchNode we can make the tree
|
||||
// more efficiently spaced by replacing the parent BranchNode with the child.
|
||||
// if that child is a LeafNode then we simply replace the BranchNode with the child otherwise if the child
|
||||
// is a BranchNode too we should spread it's children into the grandparent.
|
||||
|
||||
// refer to the remaining child as the sibling
|
||||
const sibling = parent.children[0];
|
||||
|
||||
if (pathToParent.length === 0) {
|
||||
// parent is root
|
||||
// if the parent is root
|
||||
|
||||
if (sibling instanceof LeafNode) {
|
||||
return node.view;
|
||||
// if the sibling is a leaf node no action is required
|
||||
return nodeToRemove.view;
|
||||
}
|
||||
|
||||
// we must promote sibling to be the new root
|
||||
// otherwise the sibling is a branch node. since the parent is the root and the root has only one child
|
||||
// which is a branch node we can just set this branch node to be the new root node
|
||||
|
||||
// for good housekeeping we'll removing the sibling from it's existing tree
|
||||
parent.removeChild(0, sizing);
|
||||
|
||||
// and set that sibling node to be root
|
||||
this.root = sibling;
|
||||
return node.view;
|
||||
|
||||
return nodeToRemove.view;
|
||||
}
|
||||
|
||||
// otherwise the parent is apart of a large sub-tree
|
||||
|
||||
const [grandParent, ..._] = [...pathToParent].reverse();
|
||||
const [parentIndex, ...__] = [...rest].reverse();
|
||||
|
||||
const isSiblingVisible = parent.isChildVisible(0);
|
||||
|
||||
// either way we need to remove the sibling from it's existing tree
|
||||
parent.removeChild(0, sizing);
|
||||
|
||||
// note the sizes of all of the grandparents children
|
||||
const sizes = grandParent.children.map((_size, i) =>
|
||||
grandParent.getChildSize(i)
|
||||
);
|
||||
grandParent.removeChild(parentIndex, sizing);
|
||||
|
||||
// remove the parent from the grandparent since we are moving the sibling to take the parents place
|
||||
// this parent is no longer used and can be disposed of
|
||||
grandParent.removeChild(parentIndex, sizing).dispose();
|
||||
|
||||
if (sibling instanceof BranchNode) {
|
||||
// replace the parent with the siblings children
|
||||
sizes.splice(
|
||||
parentIndex,
|
||||
1,
|
||||
...sibling.children.map((c) => c.size)
|
||||
);
|
||||
|
||||
// and add those siblings to the grandparent
|
||||
for (let i = 0; i < sibling.children.length; i++) {
|
||||
const child = sibling.children[i];
|
||||
grandParent.addChild(child, child.size, parentIndex + i);
|
||||
}
|
||||
} else {
|
||||
// otherwise create a new leaf node and add that to the grandparent
|
||||
|
||||
const newSibling = new LeafNode(
|
||||
sibling.view,
|
||||
orthogonal(sibling.orientation),
|
||||
@ -738,14 +762,19 @@ export class Gridview implements IDisposable {
|
||||
const siblingSizing = isSiblingVisible
|
||||
? sibling.orthogonalSize
|
||||
: Sizing.Invisible(sibling.orthogonalSize);
|
||||
|
||||
grandParent.addChild(newSibling, siblingSizing, parentIndex);
|
||||
}
|
||||
|
||||
// the containing node of the sibling is no longer required and can be disposed of
|
||||
sibling.dispose();
|
||||
|
||||
// resize everything
|
||||
for (let i = 0; i < sizes.length; i++) {
|
||||
grandParent.resizeChild(i, sizes[i]);
|
||||
}
|
||||
|
||||
return node.view;
|
||||
return nodeToRemove.view;
|
||||
}
|
||||
|
||||
public layout(width: number, height: number): void {
|
||||
|
@ -154,7 +154,6 @@ export abstract class GridviewPanel
|
||||
this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidChange,
|
||||
this.api.onVisibilityChange((event) => {
|
||||
const { isVisible } = event;
|
||||
const { accessor } = this._params as GridviewInitParameters;
|
||||
@ -195,7 +194,8 @@ export abstract class GridviewPanel
|
||||
height: event.height,
|
||||
width: event.width,
|
||||
});
|
||||
})
|
||||
}),
|
||||
this._onDidChange
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ export namespace Disposable {
|
||||
}
|
||||
|
||||
export class CompositeDisposable {
|
||||
private readonly disposables: IDisposable[];
|
||||
private readonly _disposables: IDisposable[];
|
||||
private _isDisposed = false;
|
||||
|
||||
protected get isDisposed(): boolean {
|
||||
@ -28,15 +28,15 @@ export class CompositeDisposable {
|
||||
}
|
||||
|
||||
constructor(...args: IDisposable[]) {
|
||||
this.disposables = args;
|
||||
this._disposables = args;
|
||||
}
|
||||
|
||||
public addDisposables(...args: IDisposable[]): void {
|
||||
args.forEach((arg) => this.disposables.push(arg));
|
||||
args.forEach((arg) => this._disposables.push(arg));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.disposables.forEach((arg) => arg.dispose());
|
||||
this._disposables.forEach((arg) => arg.dispose());
|
||||
|
||||
this._isDisposed = true;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import { Event, Emitter } from '../events';
|
||||
import { pushToStart, pushToEnd, firstIndex } from '../array';
|
||||
import { range, clamp } from '../math';
|
||||
import { ViewItem } from './viewItem';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
|
||||
export enum Orientation {
|
||||
HORIZONTAL = 'HORIZONTAL',
|
||||
@ -42,7 +43,7 @@ export enum LayoutPriority {
|
||||
Normal = 'normal',
|
||||
}
|
||||
|
||||
export interface IBaseView {
|
||||
export interface IBaseView extends IDisposable {
|
||||
minimumSize: number;
|
||||
maximumSize: number;
|
||||
snap?: boolean;
|
||||
@ -97,7 +98,7 @@ export class Splitview {
|
||||
private element: HTMLElement;
|
||||
private viewContainer: HTMLElement;
|
||||
private sashContainer: HTMLElement;
|
||||
private views: ViewItem[] = [];
|
||||
private viewItems: ViewItem[] = [];
|
||||
private sashes: ISashItem[] = [];
|
||||
private _orientation: Orientation;
|
||||
private _size = 0;
|
||||
@ -132,7 +133,7 @@ export class Splitview {
|
||||
}
|
||||
|
||||
public get length(): number {
|
||||
return this.views.length;
|
||||
return this.viewItems.length;
|
||||
}
|
||||
|
||||
public get proportions(): number[] | undefined {
|
||||
@ -159,13 +160,13 @@ export class Splitview {
|
||||
}
|
||||
|
||||
get minimumSize(): number {
|
||||
return this.views.reduce((r, item) => r + item.minimumSize, 0);
|
||||
return this.viewItems.reduce((r, item) => r + item.minimumSize, 0);
|
||||
}
|
||||
|
||||
get maximumSize(): number {
|
||||
return this.length === 0
|
||||
? Number.POSITIVE_INFINITY
|
||||
: this.views.reduce((r, item) => r + item.maximumSize, 0);
|
||||
: this.viewItems.reduce((r, item) => r + item.maximumSize, 0);
|
||||
}
|
||||
|
||||
get startSnappingEnabled(): boolean {
|
||||
@ -240,7 +241,7 @@ export class Splitview {
|
||||
});
|
||||
|
||||
// Initialize content size and proportions for first layout
|
||||
this.contentSize = this.views.reduce((r, i) => r + i.size, 0);
|
||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
this.saveProportions();
|
||||
}
|
||||
}
|
||||
@ -261,22 +262,22 @@ export class Splitview {
|
||||
}
|
||||
|
||||
isViewVisible(index: number): boolean {
|
||||
if (index < 0 || index >= this.views.length) {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
throw new Error('Index out of bounds');
|
||||
}
|
||||
|
||||
const viewItem = this.views[index];
|
||||
const viewItem = this.viewItems[index];
|
||||
return viewItem.visible;
|
||||
}
|
||||
|
||||
setViewVisible(index: number, visible: boolean): void {
|
||||
if (index < 0 || index >= this.views.length) {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
throw new Error('Index out of bounds');
|
||||
}
|
||||
|
||||
toggleClass(this.container, 'visible', visible);
|
||||
|
||||
const viewItem = this.views[index];
|
||||
const viewItem = this.viewItems[index];
|
||||
|
||||
toggleClass(this.container, 'visible', visible);
|
||||
|
||||
@ -288,30 +289,30 @@ export class Splitview {
|
||||
}
|
||||
|
||||
getViewSize(index: number): number {
|
||||
if (index < 0 || index >= this.views.length) {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.views[index].size;
|
||||
return this.viewItems[index].size;
|
||||
}
|
||||
|
||||
resizeView(index: number, size: number): void {
|
||||
if (index < 0 || index >= this.views.length) {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexes = range(this.views.length).filter((i) => i !== index);
|
||||
const indexes = range(this.viewItems.length).filter((i) => i !== index);
|
||||
const lowPriorityIndexes = [
|
||||
...indexes.filter(
|
||||
(i) => this.views[i].priority === LayoutPriority.Low
|
||||
(i) => this.viewItems[i].priority === LayoutPriority.Low
|
||||
),
|
||||
index,
|
||||
];
|
||||
const highPriorityIndexes = indexes.filter(
|
||||
(i) => this.views[i].priority === LayoutPriority.High
|
||||
(i) => this.viewItems[i].priority === LayoutPriority.High
|
||||
);
|
||||
|
||||
const item = this.views[index];
|
||||
const item = this.viewItems[index];
|
||||
size = Math.round(size);
|
||||
size = clamp(
|
||||
size,
|
||||
@ -324,13 +325,13 @@ export class Splitview {
|
||||
}
|
||||
|
||||
public getViews<T extends IView>(): T[] {
|
||||
return this.views.map((x) => x.view as T);
|
||||
return this.viewItems.map((x) => x.view as T);
|
||||
}
|
||||
|
||||
private onDidChange(item: ViewItem, size: number | undefined): void {
|
||||
const index = this.views.indexOf(item);
|
||||
const index = this.viewItems.indexOf(item);
|
||||
|
||||
if (index < 0 || index >= this.views.length) {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -345,7 +346,7 @@ export class Splitview {
|
||||
public addView(
|
||||
view: IView,
|
||||
size: number | Sizing = { type: 'distribute' },
|
||||
index: number = this.views.length,
|
||||
index: number = this.viewItems.length,
|
||||
skipLayout?: boolean
|
||||
): void {
|
||||
const container = document.createElement('div');
|
||||
@ -369,14 +370,14 @@ export class Splitview {
|
||||
this.onDidChange(viewItem, newSize.size)
|
||||
);
|
||||
|
||||
const dispose = () => {
|
||||
disposable?.dispose();
|
||||
this.viewContainer.removeChild(container);
|
||||
};
|
||||
const viewItem = new ViewItem(container, view, viewSize, {
|
||||
dispose: () => {
|
||||
disposable.dispose();
|
||||
this.viewContainer.removeChild(container);
|
||||
},
|
||||
});
|
||||
|
||||
const viewItem = new ViewItem(container, view, viewSize, { dispose });
|
||||
|
||||
if (index === this.views.length) {
|
||||
if (index === this.viewItems.length) {
|
||||
this.viewContainer.appendChild(container);
|
||||
} else {
|
||||
this.viewContainer.insertBefore(
|
||||
@ -385,15 +386,25 @@ export class Splitview {
|
||||
);
|
||||
}
|
||||
|
||||
this.views.splice(index, 0, viewItem);
|
||||
this.viewItems.splice(index, 0, viewItem);
|
||||
|
||||
if (this.views.length > 1) {
|
||||
if (this.viewItems.length > 1) {
|
||||
//add sash
|
||||
const sash = document.createElement('div');
|
||||
sash.className = 'sash';
|
||||
|
||||
const onStart = (event: MouseEvent) => {
|
||||
for (const item of this.views) {
|
||||
const onTouchStart = (event: TouchEvent) => {
|
||||
event.preventDefault();
|
||||
const touch = event.touches[0];
|
||||
onStart(touch);
|
||||
};
|
||||
|
||||
const onMouseDown = (event: MouseEvent) => {
|
||||
onStart(event);
|
||||
};
|
||||
|
||||
const onStart = (event: { clientX: number; clientY: number }) => {
|
||||
for (const item of this.viewItems) {
|
||||
item.enabled = false;
|
||||
}
|
||||
|
||||
@ -417,19 +428,20 @@ export class Splitview {
|
||||
);
|
||||
|
||||
//
|
||||
const sizes = this.views.map((x) => x.size);
|
||||
const sizes = this.viewItems.map((x) => x.size);
|
||||
|
||||
//
|
||||
let snapBefore: ISashDragSnapState | undefined;
|
||||
let snapAfter: ISashDragSnapState | undefined;
|
||||
const upIndexes = range(sashIndex, -1);
|
||||
const downIndexes = range(sashIndex + 1, this.views.length);
|
||||
const downIndexes = range(sashIndex + 1, this.viewItems.length);
|
||||
const minDeltaUp = upIndexes.reduce(
|
||||
(r, i) => r + (this.views[i].minimumSize - sizes[i]),
|
||||
(r, i) => r + (this.viewItems[i].minimumSize - sizes[i]),
|
||||
0
|
||||
);
|
||||
const maxDeltaUp = upIndexes.reduce(
|
||||
(r, i) => r + (this.views[i].viewMaximumSize - sizes[i]),
|
||||
(r, i) =>
|
||||
r + (this.viewItems[i].viewMaximumSize - sizes[i]),
|
||||
0
|
||||
);
|
||||
const maxDeltaDown =
|
||||
@ -437,7 +449,8 @@ export class Splitview {
|
||||
? Number.POSITIVE_INFINITY
|
||||
: downIndexes.reduce(
|
||||
(r, i) =>
|
||||
r + (sizes[i] - this.views[i].minimumSize),
|
||||
r +
|
||||
(sizes[i] - this.viewItems[i].minimumSize),
|
||||
0
|
||||
);
|
||||
const minDeltaDown =
|
||||
@ -446,7 +459,8 @@ export class Splitview {
|
||||
: downIndexes.reduce(
|
||||
(r, i) =>
|
||||
r +
|
||||
(sizes[i] - this.views[i].viewMaximumSize),
|
||||
(sizes[i] -
|
||||
this.viewItems[i].viewMaximumSize),
|
||||
0
|
||||
);
|
||||
const minDelta = Math.max(minDeltaUp, minDeltaDown);
|
||||
@ -454,7 +468,7 @@ export class Splitview {
|
||||
const snapBeforeIndex = this.findFirstSnapIndex(upIndexes);
|
||||
const snapAfterIndex = this.findFirstSnapIndex(downIndexes);
|
||||
if (typeof snapBeforeIndex === 'number') {
|
||||
const snappedViewItem = this.views[snapBeforeIndex];
|
||||
const snappedViewItem = this.viewItems[snapBeforeIndex];
|
||||
const halfSize = Math.floor(
|
||||
snappedViewItem.viewMinimumSize / 2
|
||||
);
|
||||
@ -469,7 +483,7 @@ export class Splitview {
|
||||
}
|
||||
|
||||
if (typeof snapAfterIndex === 'number') {
|
||||
const snappedViewItem = this.views[snapAfterIndex];
|
||||
const snappedViewItem = this.viewItems[snapAfterIndex];
|
||||
const halfSize = Math.floor(
|
||||
snappedViewItem.viewMinimumSize / 2
|
||||
);
|
||||
@ -482,13 +496,25 @@ export class Splitview {
|
||||
size: snappedViewItem.size,
|
||||
};
|
||||
}
|
||||
//
|
||||
|
||||
const mousemove = (mousemoveEvent: MouseEvent) => {
|
||||
const onMouseMove = (event: MouseEvent) => {
|
||||
reposition(event);
|
||||
};
|
||||
|
||||
const onTouchMove = (event: TouchEvent) => {
|
||||
event.preventDefault();
|
||||
const touch = event.touches[0];
|
||||
reposition(touch);
|
||||
};
|
||||
|
||||
const reposition = (event: {
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}) => {
|
||||
const current =
|
||||
this._orientation === Orientation.HORIZONTAL
|
||||
? mousemoveEvent.clientX
|
||||
: mousemoveEvent.clientY;
|
||||
? event.clientX
|
||||
: event.clientY;
|
||||
const delta = current - start;
|
||||
|
||||
this.resize(
|
||||
@ -507,7 +533,7 @@ export class Splitview {
|
||||
};
|
||||
|
||||
const end = () => {
|
||||
for (const item of this.views) {
|
||||
for (const item of this.viewItems) {
|
||||
item.enabled = true;
|
||||
}
|
||||
|
||||
@ -517,24 +543,30 @@ export class Splitview {
|
||||
|
||||
this.saveProportions();
|
||||
|
||||
document.removeEventListener('mousemove', mousemove);
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', end);
|
||||
document.removeEventListener('mouseend', end);
|
||||
document.removeEventListener('touchmove', onTouchMove);
|
||||
document.removeEventListener('touchend', end);
|
||||
document.removeEventListener('touchcancel', end);
|
||||
|
||||
this._onDidSashEnd.fire(undefined);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', mousemove);
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', end);
|
||||
document.addEventListener('mouseend', end);
|
||||
document.addEventListener('touchmove', onTouchMove);
|
||||
document.addEventListener('touchend', end);
|
||||
document.addEventListener('touchcancel', end);
|
||||
};
|
||||
|
||||
sash.addEventListener('mousedown', onStart);
|
||||
sash.addEventListener('mousedown', onMouseDown);
|
||||
sash.addEventListener('touchstart', onTouchStart);
|
||||
|
||||
const sashItem: ISashItem = {
|
||||
container: sash,
|
||||
disposable: () => {
|
||||
sash.removeEventListener('mousedown', onStart);
|
||||
sash.removeEventListener('touchstart', onTouchStart);
|
||||
this.sashContainer.removeChild(sash);
|
||||
},
|
||||
};
|
||||
@ -562,7 +594,7 @@ export class Splitview {
|
||||
const flexibleViewItems: ViewItem[] = [];
|
||||
let flexibleSize = 0;
|
||||
|
||||
for (const item of this.views) {
|
||||
for (const item of this.viewItems) {
|
||||
if (item.maximumSize - item.minimumSize > 0) {
|
||||
flexibleViewItems.push(item);
|
||||
flexibleSize += item.size;
|
||||
@ -575,12 +607,12 @@ export class Splitview {
|
||||
item.size = clamp(size, item.minimumSize, item.maximumSize);
|
||||
}
|
||||
|
||||
const indexes = range(this.views.length);
|
||||
const indexes = range(this.viewItems.length);
|
||||
const lowPriorityIndexes = indexes.filter(
|
||||
(i) => this.views[i].priority === LayoutPriority.Low
|
||||
(i) => this.viewItems[i].priority === LayoutPriority.Low
|
||||
);
|
||||
const highPriorityIndexes = indexes.filter(
|
||||
(i) => this.views[i].priority === LayoutPriority.High
|
||||
(i) => this.viewItems[i].priority === LayoutPriority.High
|
||||
);
|
||||
|
||||
this.relayout(lowPriorityIndexes, highPriorityIndexes);
|
||||
@ -592,11 +624,11 @@ export class Splitview {
|
||||
skipLayout = false
|
||||
): IView {
|
||||
// Remove view
|
||||
const viewItem = this.views.splice(index, 1)[0];
|
||||
const viewItem = this.viewItems.splice(index, 1)[0];
|
||||
viewItem.dispose();
|
||||
|
||||
// Remove sash
|
||||
if (this.views.length >= 1) {
|
||||
if (this.viewItems.length >= 1) {
|
||||
const sashIndex = Math.max(index - 1, 0);
|
||||
const sashItem = this.sashes.splice(sashIndex, 1)[0];
|
||||
sashItem.disposable();
|
||||
@ -616,11 +648,11 @@ export class Splitview {
|
||||
}
|
||||
|
||||
getViewCachedVisibleSize(index: number): number | undefined {
|
||||
if (index < 0 || index >= this.views.length) {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
throw new Error('Index out of bounds');
|
||||
}
|
||||
|
||||
const viewItem = this.views[index];
|
||||
const viewItem = this.viewItems[index];
|
||||
return viewItem.cachedVisibleSize;
|
||||
}
|
||||
|
||||
@ -640,24 +672,24 @@ export class Splitview {
|
||||
this.orthogonalSize = orthogonalSize;
|
||||
|
||||
if (!this.proportions) {
|
||||
const indexes = range(this.views.length);
|
||||
const indexes = range(this.viewItems.length);
|
||||
const lowPriorityIndexes = indexes.filter(
|
||||
(i) => this.views[i].priority === LayoutPriority.Low
|
||||
(i) => this.viewItems[i].priority === LayoutPriority.Low
|
||||
);
|
||||
const highPriorityIndexes = indexes.filter(
|
||||
(i) => this.views[i].priority === LayoutPriority.High
|
||||
(i) => this.viewItems[i].priority === LayoutPriority.High
|
||||
);
|
||||
|
||||
this.resize(
|
||||
this.views.length - 1,
|
||||
this.viewItems.length - 1,
|
||||
size - previousSize,
|
||||
undefined,
|
||||
lowPriorityIndexes,
|
||||
highPriorityIndexes
|
||||
);
|
||||
} else {
|
||||
for (let i = 0; i < this.views.length; i++) {
|
||||
const item = this.views[i];
|
||||
for (let i = 0; i < this.viewItems.length; i++) {
|
||||
const item = this.viewItems[i];
|
||||
|
||||
item.size = clamp(
|
||||
Math.round(this.proportions[i] * size),
|
||||
@ -675,10 +707,10 @@ export class Splitview {
|
||||
lowPriorityIndexes?: number[],
|
||||
highPriorityIndexes?: number[]
|
||||
): void {
|
||||
const contentSize = this.views.reduce((r, i) => r + i.size, 0);
|
||||
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
|
||||
this.resize(
|
||||
this.views.length - 1,
|
||||
this.viewItems.length - 1,
|
||||
this._size - contentSize,
|
||||
undefined,
|
||||
lowPriorityIndexes,
|
||||
@ -690,15 +722,15 @@ export class Splitview {
|
||||
}
|
||||
|
||||
private distributeEmptySpace(lowPriorityIndex?: number): void {
|
||||
const contentSize = this.views.reduce((r, i) => r + i.size, 0);
|
||||
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
let emptyDelta = this.size - contentSize;
|
||||
|
||||
const indexes = range(this.views.length - 1, -1);
|
||||
const indexes = range(this.viewItems.length - 1, -1);
|
||||
const lowPriorityIndexes = indexes.filter(
|
||||
(i) => this.views[i].priority === LayoutPriority.Low
|
||||
(i) => this.viewItems[i].priority === LayoutPriority.Low
|
||||
);
|
||||
const highPriorityIndexes = indexes.filter(
|
||||
(i) => this.views[i].priority === LayoutPriority.High
|
||||
(i) => this.viewItems[i].priority === LayoutPriority.High
|
||||
);
|
||||
|
||||
for (const index of highPriorityIndexes) {
|
||||
@ -714,7 +746,7 @@ export class Splitview {
|
||||
}
|
||||
|
||||
for (let i = 0; emptyDelta !== 0 && i < indexes.length; i++) {
|
||||
const item = this.views[indexes[i]];
|
||||
const item = this.viewItems[indexes[i]];
|
||||
const size = clamp(
|
||||
item.size + emptyDelta,
|
||||
item.minimumSize,
|
||||
@ -729,21 +761,21 @@ export class Splitview {
|
||||
|
||||
private saveProportions(): void {
|
||||
if (this.proportionalLayout && this.contentSize > 0) {
|
||||
this._proportions = this.views.map(
|
||||
this._proportions = this.viewItems.map(
|
||||
(i) => i.size / this.contentSize
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private layoutViews(): void {
|
||||
this.contentSize = this.views.reduce((r, i) => r + i.size, 0);
|
||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
let sum = 0;
|
||||
const x: number[] = [];
|
||||
|
||||
this.updateSashEnablement();
|
||||
|
||||
for (let i = 0; i < this.views.length - 1; i++) {
|
||||
sum += this.views[i].size;
|
||||
for (let i = 0; i < this.viewItems.length - 1; i++) {
|
||||
sum += this.viewItems[i].size;
|
||||
x.push(sum);
|
||||
|
||||
const offset = Math.min(Math.max(0, sum - 2), this.size - 4);
|
||||
@ -757,7 +789,7 @@ export class Splitview {
|
||||
this.sashes[i].container.style.top = `${offset}px`;
|
||||
}
|
||||
}
|
||||
this.views.forEach((view, i) => {
|
||||
this.viewItems.forEach((view, i) => {
|
||||
if (this._orientation === Orientation.HORIZONTAL) {
|
||||
view.container.style.width = `${view.size}px`;
|
||||
view.container.style.left = i == 0 ? '0px' : `${x[i - 1]}px`;
|
||||
@ -778,7 +810,7 @@ export class Splitview {
|
||||
private findFirstSnapIndex(indexes: number[]): number | undefined {
|
||||
// visible views first
|
||||
for (const index of indexes) {
|
||||
const viewItem = this.views[index];
|
||||
const viewItem = this.viewItems[index];
|
||||
|
||||
if (!viewItem.visible) {
|
||||
continue;
|
||||
@ -791,7 +823,7 @@ export class Splitview {
|
||||
|
||||
// then, hidden views
|
||||
for (const index of indexes) {
|
||||
const viewItem = this.views[index];
|
||||
const viewItem = this.viewItems[index];
|
||||
|
||||
if (
|
||||
viewItem.visible &&
|
||||
@ -810,16 +842,16 @@ export class Splitview {
|
||||
|
||||
private updateSashEnablement(): void {
|
||||
let previous = false;
|
||||
const collapsesDown = this.views.map(
|
||||
const collapsesDown = this.viewItems.map(
|
||||
(i) => (previous = i.size - i.minimumSize > 0 || previous)
|
||||
);
|
||||
|
||||
previous = false;
|
||||
const expandsDown = this.views.map(
|
||||
const expandsDown = this.viewItems.map(
|
||||
(i) => (previous = i.maximumSize - i.size > 0 || previous)
|
||||
);
|
||||
|
||||
const reverseViews = [...this.views].reverse();
|
||||
const reverseViews = [...this.viewItems].reverse();
|
||||
previous = false;
|
||||
const collapsesUp = reverseViews
|
||||
.map((i) => (previous = i.size - i.minimumSize > 0 || previous))
|
||||
@ -833,7 +865,7 @@ export class Splitview {
|
||||
let position = 0;
|
||||
for (let index = 0; index < this.sashes.length; index++) {
|
||||
const sash = this.sashes[index];
|
||||
const viewItem = this.views[index];
|
||||
const viewItem = this.viewItems[index];
|
||||
position += viewItem.size;
|
||||
|
||||
const min = !(collapsesDown[index] && expandsUp[index + 1]);
|
||||
@ -841,16 +873,16 @@ export class Splitview {
|
||||
|
||||
if (min && max) {
|
||||
const upIndexes = range(index, -1);
|
||||
const downIndexes = range(index + 1, this.views.length);
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const snapBeforeIndex = this.findFirstSnapIndex(upIndexes);
|
||||
const snapAfterIndex = this.findFirstSnapIndex(downIndexes);
|
||||
|
||||
const snappedBefore =
|
||||
typeof snapBeforeIndex === 'number' &&
|
||||
!this.views[snapBeforeIndex].visible;
|
||||
!this.viewItems[snapBeforeIndex].visible;
|
||||
const snappedAfter =
|
||||
typeof snapAfterIndex === 'number' &&
|
||||
!this.views[snapAfterIndex].visible;
|
||||
!this.viewItems[snapAfterIndex].visible;
|
||||
|
||||
if (
|
||||
snappedBefore &&
|
||||
@ -887,7 +919,7 @@ export class Splitview {
|
||||
private resize = (
|
||||
index: number,
|
||||
delta: number,
|
||||
sizes: number[] = this.views.map((x) => x.size),
|
||||
sizes: number[] = this.viewItems.map((x) => x.size),
|
||||
lowPriorityIndexes?: number[],
|
||||
highPriorityIndexes?: number[],
|
||||
overloadMinDelta: number = Number.NEGATIVE_INFINITY,
|
||||
@ -895,12 +927,12 @@ export class Splitview {
|
||||
snapBefore?: ISashDragSnapState,
|
||||
snapAfter?: ISashDragSnapState
|
||||
): number => {
|
||||
if (index < 0 || index > this.views.length) {
|
||||
if (index < 0 || index > this.viewItems.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const upIndexes = range(index, -1);
|
||||
const downIndexes = range(index + 1, this.views.length);
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
//
|
||||
if (highPriorityIndexes) {
|
||||
for (const i of highPriorityIndexes) {
|
||||
@ -916,18 +948,18 @@ export class Splitview {
|
||||
}
|
||||
}
|
||||
//
|
||||
const upItems = upIndexes.map((i) => this.views[i]);
|
||||
const upItems = upIndexes.map((i) => this.viewItems[i]);
|
||||
const upSizes = upIndexes.map((i) => sizes[i]);
|
||||
//
|
||||
const downItems = downIndexes.map((i) => this.views[i]);
|
||||
const downItems = downIndexes.map((i) => this.viewItems[i]);
|
||||
const downSizes = downIndexes.map((i) => sizes[i]);
|
||||
//
|
||||
const minDeltaUp = upIndexes.reduce(
|
||||
(_, i) => _ + this.views[i].minimumSize - sizes[i],
|
||||
(_, i) => _ + this.viewItems[i].minimumSize - sizes[i],
|
||||
0
|
||||
);
|
||||
const maxDeltaUp = upIndexes.reduce(
|
||||
(_, i) => _ + this.views[i].maximumSize - sizes[i],
|
||||
(_, i) => _ + this.viewItems[i].maximumSize - sizes[i],
|
||||
0
|
||||
);
|
||||
//
|
||||
@ -935,7 +967,7 @@ export class Splitview {
|
||||
downIndexes.length === 0
|
||||
? Number.POSITIVE_INFINITY
|
||||
: downIndexes.reduce(
|
||||
(_, i) => _ + sizes[i] - this.views[i].minimumSize,
|
||||
(_, i) => _ + sizes[i] - this.viewItems[i].minimumSize,
|
||||
|
||||
0
|
||||
);
|
||||
@ -943,7 +975,7 @@ export class Splitview {
|
||||
downIndexes.length === 0
|
||||
? Number.NEGATIVE_INFINITY
|
||||
: downIndexes.reduce(
|
||||
(_, i) => _ + sizes[i] - this.views[i].maximumSize,
|
||||
(_, i) => _ + sizes[i] - this.viewItems[i].maximumSize,
|
||||
0
|
||||
);
|
||||
//
|
||||
@ -952,14 +984,14 @@ export class Splitview {
|
||||
//
|
||||
let snapped = false;
|
||||
if (snapBefore) {
|
||||
const snapView = this.views[snapBefore.index];
|
||||
const snapView = this.viewItems[snapBefore.index];
|
||||
const visible = delta >= snapBefore.limitDelta;
|
||||
snapped = visible !== snapView.visible;
|
||||
snapView.setVisible(visible, snapBefore.size);
|
||||
}
|
||||
|
||||
if (!snapped && snapAfter) {
|
||||
const snapView = this.views[snapAfter.index];
|
||||
const snapView = this.viewItems[snapAfter.index];
|
||||
const visible = delta < snapAfter.limitDelta;
|
||||
snapped = visible !== snapView.visible;
|
||||
snapView.setVisible(visible, snapAfter.size);
|
||||
@ -1047,6 +1079,10 @@ export class Splitview {
|
||||
}
|
||||
}
|
||||
|
||||
for (const viewItem of this.viewItems) {
|
||||
viewItem.dispose();
|
||||
}
|
||||
|
||||
this.element.remove();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {
|
||||
CompositeDisposable,
|
||||
IDisposable,
|
||||
IValueDisposable,
|
||||
MutableDisposable,
|
||||
} from '../lifecycle';
|
||||
import {
|
||||
@ -83,10 +82,10 @@ export class SplitviewComponent
|
||||
extends Resizable
|
||||
implements ISplitviewComponent
|
||||
{
|
||||
private _disposable = new MutableDisposable();
|
||||
private _splitviewChangeDisposable = new MutableDisposable();
|
||||
private _splitview!: Splitview;
|
||||
private _activePanel: SplitviewPanel | undefined;
|
||||
private _panels = new Map<string, IValueDisposable<SplitviewPanel>>();
|
||||
private _panels = new Map<string, IDisposable>();
|
||||
private _options: SplitviewComponentOptions;
|
||||
|
||||
private readonly _onDidLayoutfromJSON = new Emitter<void>();
|
||||
@ -124,7 +123,7 @@ export class SplitviewComponent
|
||||
set splitview(value: Splitview) {
|
||||
this._splitview = value;
|
||||
|
||||
this._disposable.value = new CompositeDisposable(
|
||||
this._splitviewChangeDisposable.value = new CompositeDisposable(
|
||||
this._splitview.onDidSashEnd(() => {
|
||||
this._onDidLayoutChange.fire(undefined);
|
||||
}),
|
||||
@ -170,7 +169,6 @@ export class SplitviewComponent
|
||||
this.splitview = new Splitview(this.element, options);
|
||||
|
||||
this.addDisposables(
|
||||
this._disposable,
|
||||
this._onDidAddView,
|
||||
this._onDidLayoutfromJSON,
|
||||
this._onDidRemoveView,
|
||||
@ -226,19 +224,19 @@ export class SplitviewComponent
|
||||
}
|
||||
|
||||
removePanel(panel: SplitviewPanel, sizing?: Sizing): void {
|
||||
const disposable = this._panels.get(panel.id);
|
||||
const item = this._panels.get(panel.id);
|
||||
|
||||
if (!disposable) {
|
||||
if (!item) {
|
||||
throw new Error(`unknown splitview panel ${panel.id}`);
|
||||
}
|
||||
|
||||
disposable.disposable.dispose();
|
||||
disposable.value.dispose();
|
||||
item.dispose();
|
||||
|
||||
this._panels.delete(panel.id);
|
||||
|
||||
const index = this.panels.findIndex((_) => _ === panel);
|
||||
this.splitview.removeView(index, sizing);
|
||||
const removedView = this.splitview.removeView(index, sizing);
|
||||
removedView.dispose();
|
||||
|
||||
const panels = this.panels;
|
||||
if (panels.length > 0) {
|
||||
@ -250,7 +248,7 @@ export class SplitviewComponent
|
||||
return this.panels.find((view) => view.id === id);
|
||||
}
|
||||
|
||||
addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel {
|
||||
addPanel(options: AddSplitviewComponentOptions): SplitviewPanel {
|
||||
if (this._panels.has(options.id)) {
|
||||
throw new Error(`panel ${options.id} already exists`);
|
||||
}
|
||||
@ -308,7 +306,7 @@ export class SplitviewComponent
|
||||
this.setActive(view, true);
|
||||
});
|
||||
|
||||
this._panels.set(view.id, { disposable, value: view });
|
||||
this._panels.set(view.id, disposable);
|
||||
}
|
||||
|
||||
toJSON(): SerializedSplitview {
|
||||
@ -404,23 +402,34 @@ export class SplitviewComponent
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
for (const [_, value] of this._panels.entries()) {
|
||||
value.disposable.dispose();
|
||||
value.value.dispose();
|
||||
for (const disposable of this._panels.values()) {
|
||||
disposable.dispose();
|
||||
}
|
||||
|
||||
this._panels.clear();
|
||||
this.splitview.dispose();
|
||||
|
||||
while (this.splitview.length > 0) {
|
||||
const view = this.splitview.removeView(0, Sizing.Distribute, true);
|
||||
view.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
for (const [_, value] of this._panels.entries()) {
|
||||
value.disposable.dispose();
|
||||
value.value.dispose();
|
||||
for (const disposable of this._panels.values()) {
|
||||
disposable.dispose();
|
||||
}
|
||||
|
||||
this._panels.clear();
|
||||
|
||||
const views = this.splitview.getViews();
|
||||
|
||||
this._splitviewChangeDisposable.dispose();
|
||||
this.splitview.dispose();
|
||||
|
||||
for (const view of views) {
|
||||
view.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { SplitviewPanelApiImpl } from '../api/splitviewPanelApi';
|
||||
import { LayoutPriority, Orientation } from './splitview';
|
||||
import { FunctionOrValue } from '../types';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
|
||||
export interface ISplitviewPanel
|
||||
extends BasePanelViewExported<SplitviewPanelApiImpl> {
|
||||
|
@ -1,7 +1,4 @@
|
||||
{
|
||||
"out": "typedocs",
|
||||
"entryPoints": ["./src/index.ts"],
|
||||
"exclude": ["**/_test/**/*.*", "**/index.ts"],
|
||||
"excludeExternals": true,
|
||||
"excludePrivate": true
|
||||
"extends": ["../../typedoc.base.json"],
|
||||
"entryPoints": ["src/index.ts"]
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ const config: JestConfigWithTsJest = {
|
||||
setupFiles: [
|
||||
'<rootDir>/packages/dockview/src/__tests__/__mocks__/resizeObserver.js',
|
||||
],
|
||||
setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
|
||||
coveragePathIgnorePatterns: ['/node_modules/'],
|
||||
modulePathIgnorePatterns: [
|
||||
'<rootDir>/packages/dockview/src/__tests__/__mocks__',
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.5",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
@ -56,7 +56,7 @@
|
||||
"author": "https://github.com/mathuo",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dockview-core": "^1.7.2"
|
||||
"dockview-core": "^1.7.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
@ -71,7 +71,6 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"rimraf": "^4.1.2",
|
||||
"rollup": "^3.15.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"typedoc": "^0.23.25"
|
||||
"rollup-plugin-postcss": "^4.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { DockviewApi } from 'dockview-core';
|
||||
import { act, render, waitFor } from '@testing-library/react';
|
||||
import { DockviewApi, IDockviewPanel } from 'dockview-core';
|
||||
import {
|
||||
IDockviewPanelProps,
|
||||
DockviewReact,
|
||||
@ -15,7 +15,17 @@ describe('gridview react', () => {
|
||||
beforeEach(() => {
|
||||
components = {
|
||||
default: (props: IDockviewPanelProps) => {
|
||||
return <div>hello world</div>;
|
||||
return (
|
||||
<div>
|
||||
{Object.keys(props.params).map((key) => {
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
>{`key=${key},value=${props.params[key]}`}</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -51,4 +61,84 @@ describe('gridview react', () => {
|
||||
expect(api!.width).toBe(650);
|
||||
expect(api!.height).toBe(450);
|
||||
});
|
||||
|
||||
test('that the component can update parameters', async () => {
|
||||
let api: DockviewApi;
|
||||
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
const wrapper = render(
|
||||
<DockviewReact components={components} onReady={onReady} />
|
||||
);
|
||||
|
||||
let panel: IDockviewPanel;
|
||||
|
||||
act(() => {
|
||||
panel = api!.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
params: {
|
||||
keyA: 'valueA',
|
||||
keyB: 'valueB',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyA: 'valueAA', keyC: 'valueC' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueAA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=valueC/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyC: null });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueAA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=null/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyA: undefined });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(wrapper.queryByText(/key=keyA/i)).not.toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=null/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { GridviewApi, Orientation } from 'dockview-core';
|
||||
import { act, render, waitFor } from '@testing-library/react';
|
||||
import { GridviewApi, IGridviewPanel, Orientation } from 'dockview-core';
|
||||
import {
|
||||
IGridviewPanelProps,
|
||||
GridviewReact,
|
||||
@ -15,7 +15,17 @@ describe('gridview react', () => {
|
||||
beforeEach(() => {
|
||||
components = {
|
||||
default: (props: IGridviewPanelProps) => {
|
||||
return <div>hello world</div>;
|
||||
return (
|
||||
<div>
|
||||
{Object.keys(props.params).map((key) => {
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
>{`key=${key},value=${props.params[key]}`}</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -62,4 +72,88 @@ describe('gridview react', () => {
|
||||
expect(api!.width).toBe(650);
|
||||
expect(api!.height).toBe(450);
|
||||
});
|
||||
|
||||
test('that the component can update parameters', async () => {
|
||||
let api: GridviewApi;
|
||||
|
||||
const onReady = (event: GridviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
const wrapper = render(
|
||||
<GridviewReact
|
||||
orientation={Orientation.VERTICAL}
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
/>
|
||||
);
|
||||
|
||||
let panel: IGridviewPanel;
|
||||
|
||||
act(() => {
|
||||
panel = api!.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
params: {
|
||||
keyA: 'valueA',
|
||||
keyB: 'valueB',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyA: 'valueAA', keyC: 'valueC' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueAA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=valueC/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyC: null });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueAA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=null/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyA: undefined });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(wrapper.queryByText(/key=keyA/i)).not.toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=null/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { PaneviewApi } from 'dockview-core';
|
||||
import { act, render, waitFor } from '@testing-library/react';
|
||||
import { IPaneviewPanel, PaneviewApi } from 'dockview-core';
|
||||
import {
|
||||
IPaneviewPanelProps,
|
||||
PaneviewReact,
|
||||
@ -15,7 +15,17 @@ describe('gridview react', () => {
|
||||
beforeEach(() => {
|
||||
components = {
|
||||
default: (props: IPaneviewPanelProps) => {
|
||||
return <div>hello world</div>;
|
||||
return (
|
||||
<div>
|
||||
{Object.keys(props.params).map((key) => {
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
>{`key=${key},value=${props.params[key]}`}</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -49,4 +59,85 @@ describe('gridview react', () => {
|
||||
expect(api!.width).toBe(650);
|
||||
expect(api!.height).toBe(450);
|
||||
});
|
||||
|
||||
test('that the component can update parameters', async () => {
|
||||
let api: PaneviewApi;
|
||||
|
||||
const onReady = (event: PaneviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
const wrapper = render(
|
||||
<PaneviewReact components={components} onReady={onReady} />
|
||||
);
|
||||
|
||||
let panel: IPaneviewPanel;
|
||||
|
||||
act(() => {
|
||||
panel = api!.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
params: {
|
||||
keyA: 'valueA',
|
||||
keyB: 'valueB',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyA: 'valueAA', keyC: 'valueC' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueAA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=valueC/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyC: null });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueAA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=null/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyA: undefined });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(wrapper.queryByText(/key=keyA/i)).not.toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=null/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,53 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { DockviewApi } from 'dockview-core';
|
||||
import {
|
||||
IDockviewPanelProps,
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
} from '../../../dockview/dockview';
|
||||
import { PanelCollection } from '../../../types';
|
||||
import { setMockRefElement } from '../../__test_utils__/utils';
|
||||
|
||||
describe('dockview', () => {
|
||||
let components: PanelCollection<IDockviewPanelProps>;
|
||||
|
||||
beforeEach(() => {
|
||||
components = {
|
||||
default: (props: IDockviewPanelProps) => {
|
||||
return <div>hello world</div>;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
test('default', () => {
|
||||
let api: DockviewApi | undefined;
|
||||
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
render(<DockviewReact components={components} onReady={onReady} />);
|
||||
|
||||
expect(api).toBeTruthy();
|
||||
});
|
||||
|
||||
test('is sized to container', () => {
|
||||
const el = document.createElement('div') as any;
|
||||
jest.spyOn(el, 'clientHeight', 'get').mockReturnValue(450);
|
||||
jest.spyOn(el, 'clientWidth', 'get').mockReturnValue(650);
|
||||
|
||||
setMockRefElement(el);
|
||||
|
||||
let api: DockviewApi | undefined;
|
||||
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
render(<DockviewReact components={components} onReady={onReady} />);
|
||||
|
||||
expect(api!.width).toBe(650);
|
||||
expect(api!.height).toBe(450);
|
||||
});
|
||||
});
|
@ -1,58 +0,0 @@
|
||||
import {
|
||||
DockviewGroupPanel,
|
||||
DockviewGroupPanelApi,
|
||||
DockviewGroupPanelModel,
|
||||
} from 'dockview-core';
|
||||
import { ReactGroupControlsRendererPart } from '../../../dockview/groupControlsRenderer';
|
||||
|
||||
describe('groupControlsRenderer', () => {
|
||||
test('#1', () => {
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
() => {
|
||||
return {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
onDidActivePanelChange: jest.fn(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const groupview = new groupviewMock() as DockviewGroupPanelModel;
|
||||
|
||||
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
|
||||
return {
|
||||
api: {} as DockviewGroupPanelApi as any,
|
||||
model: groupview,
|
||||
};
|
||||
});
|
||||
|
||||
const groupPanel = new groupPanelMock() as DockviewGroupPanel;
|
||||
|
||||
const cut = new ReactGroupControlsRendererPart(
|
||||
jest.fn(),
|
||||
{
|
||||
addPortal: jest.fn(),
|
||||
},
|
||||
groupPanel
|
||||
);
|
||||
|
||||
expect(cut.element.childNodes.length).toBe(0);
|
||||
expect(cut.element.className).toBe('dockview-react-part');
|
||||
expect(cut.part).toBeUndefined();
|
||||
|
||||
cut.init({
|
||||
containerApi: <any>jest.fn(),
|
||||
api: <any>{
|
||||
onDidActiveChange: jest.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
const update = jest.fn();
|
||||
|
||||
jest.spyOn(cut.part!, 'update').mockImplementation(update);
|
||||
|
||||
cut.update({ params: { valueA: 'A' } });
|
||||
|
||||
expect(update).toBeCalledWith({ valueA: 'A' });
|
||||
});
|
||||
});
|
@ -1,64 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { GridviewApi, Orientation } from 'dockview-core';
|
||||
import {
|
||||
IGridviewPanelProps,
|
||||
GridviewReact,
|
||||
GridviewReadyEvent,
|
||||
} from '../../../gridview/gridview';
|
||||
import { PanelCollection } from '../../../types';
|
||||
import { setMockRefElement } from '../../__test_utils__/utils';
|
||||
|
||||
describe('gridview react', () => {
|
||||
let components: PanelCollection<IGridviewPanelProps>;
|
||||
|
||||
beforeEach(() => {
|
||||
components = {
|
||||
default: (props: IGridviewPanelProps) => {
|
||||
return <div>hello world</div>;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
test('default', () => {
|
||||
let api: GridviewApi | undefined;
|
||||
|
||||
const onReady = (event: GridviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
render(
|
||||
<GridviewReact
|
||||
orientation={Orientation.VERTICAL}
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(api).toBeTruthy();
|
||||
});
|
||||
|
||||
test('is sized to container', () => {
|
||||
setMockRefElement({
|
||||
clientHeight: 450,
|
||||
clientWidth: 650,
|
||||
appendChild: jest.fn(),
|
||||
});
|
||||
let api: GridviewApi | undefined;
|
||||
|
||||
const onReady = (event: GridviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
render(
|
||||
<GridviewReact
|
||||
orientation={Orientation.VERTICAL}
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(api!.width).toBe(650);
|
||||
expect(api!.height).toBe(450);
|
||||
});
|
||||
});
|
@ -1,52 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { PaneviewApi } from 'dockview-core';
|
||||
import {
|
||||
IPaneviewPanelProps,
|
||||
PaneviewReact,
|
||||
PaneviewReadyEvent,
|
||||
} from '../../../paneview/paneview';
|
||||
import { PanelCollection } from '../../../types';
|
||||
import { setMockRefElement } from '../../__test_utils__/utils';
|
||||
|
||||
describe('gridview react', () => {
|
||||
let components: PanelCollection<IPaneviewPanelProps>;
|
||||
|
||||
beforeEach(() => {
|
||||
components = {
|
||||
default: (props: IPaneviewPanelProps) => {
|
||||
return <div>hello world</div>;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
test('default', () => {
|
||||
let api: PaneviewApi | undefined;
|
||||
|
||||
const onReady = (event: PaneviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
render(<PaneviewReact components={components} onReady={onReady} />);
|
||||
|
||||
expect(api).toBeTruthy();
|
||||
});
|
||||
|
||||
test('is sized to container', () => {
|
||||
setMockRefElement({
|
||||
clientHeight: 450,
|
||||
clientWidth: 650,
|
||||
appendChild: jest.fn(),
|
||||
});
|
||||
let api: PaneviewApi | undefined;
|
||||
|
||||
const onReady = (event: PaneviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
render(<PaneviewReact components={components} onReady={onReady} />);
|
||||
|
||||
expect(api!.width).toBe(650);
|
||||
expect(api!.height).toBe(450);
|
||||
});
|
||||
});
|
@ -1,90 +0,0 @@
|
||||
import { ReactPart } from '../../react';
|
||||
import * as React from 'react';
|
||||
import { render, screen, act } from '@testing-library/react';
|
||||
|
||||
interface TestInterface {
|
||||
valueA: string;
|
||||
valueB: number;
|
||||
}
|
||||
|
||||
describe('react', () => {
|
||||
describe('ReactPart', () => {
|
||||
test('update underlying component via ReactPart class', () => {
|
||||
let api: ReactPart<TestInterface>;
|
||||
|
||||
const onReady = (_api: ReactPart<TestInterface>) => {
|
||||
api = _api;
|
||||
};
|
||||
|
||||
render(<TestWrapper onReady={onReady} component={Component} />);
|
||||
|
||||
expect(api!).toBeTruthy();
|
||||
|
||||
expect(screen.getByTestId('valueA').textContent).toBe('stringA');
|
||||
expect(screen.getByTestId('valueB').textContent).toBe('42');
|
||||
|
||||
act(() => {
|
||||
api.update({ valueB: '32' });
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('valueA').textContent).toBe('stringA');
|
||||
expect(screen.getByTestId('valueB').textContent).toBe('32');
|
||||
|
||||
act(() => {
|
||||
api.update({ valueA: 'anotherStringA', valueB: '22' });
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('valueA').textContent).toBe(
|
||||
'anotherStringA'
|
||||
);
|
||||
expect(screen.getByTestId('valueB').textContent).toBe('22');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const Component = (props: TestInterface) => {
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="valueA">{props.valueA}</div>
|
||||
<div data-testid="valueB">{props.valueB}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const TestWrapper = (props: {
|
||||
component: React.FunctionComponent<TestInterface>;
|
||||
onReady: (api: ReactPart<TestInterface>) => void;
|
||||
}) => {
|
||||
const [portal, setPortal] = React.useState<React.ReactPortal[]>([]);
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
const cut = new ReactPart<TestInterface>(
|
||||
ref.current!,
|
||||
{
|
||||
addPortal: (portal: React.ReactPortal) => {
|
||||
setPortal((_) => [..._, portal]);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
setPortal((_) => _.filter((_) => _ !== portal));
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
props.component,
|
||||
{
|
||||
valueA: 'stringA',
|
||||
valueB: 42,
|
||||
}
|
||||
);
|
||||
|
||||
props.onReady(cut);
|
||||
|
||||
return () => {
|
||||
cut.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div ref={ref}>{portal}</div>;
|
||||
};
|
@ -1,64 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { SplitviewApi, Orientation } from 'dockview-core';
|
||||
import {
|
||||
ISplitviewPanelProps,
|
||||
SplitviewReact,
|
||||
SplitviewReadyEvent,
|
||||
} from '../../../splitview/splitview';
|
||||
import { PanelCollection } from '../../../types';
|
||||
import { setMockRefElement } from '../../__test_utils__/utils';
|
||||
|
||||
describe('splitview react', () => {
|
||||
let components: PanelCollection<ISplitviewPanelProps>;
|
||||
|
||||
beforeEach(() => {
|
||||
components = {
|
||||
default: (props: ISplitviewPanelProps) => {
|
||||
return <div>hello world</div>;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
test('default', () => {
|
||||
let api: SplitviewApi | undefined;
|
||||
|
||||
const onReady = (event: SplitviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
render(
|
||||
<SplitviewReact
|
||||
orientation={Orientation.VERTICAL}
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(api).toBeTruthy();
|
||||
});
|
||||
|
||||
test('is sized to container', () => {
|
||||
setMockRefElement({
|
||||
clientHeight: 450,
|
||||
clientWidth: 650,
|
||||
appendChild: jest.fn(),
|
||||
});
|
||||
let api: SplitviewApi | undefined;
|
||||
|
||||
const onReady = (event: SplitviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
render(
|
||||
<SplitviewReact
|
||||
orientation={Orientation.VERTICAL}
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(api!.width).toBe(650);
|
||||
expect(api!.height).toBe(450);
|
||||
});
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { SplitviewApi, Orientation } from 'dockview-core';
|
||||
import { act, render, waitFor } from '@testing-library/react';
|
||||
import { SplitviewApi, Orientation, ISplitviewPanel } from 'dockview-core';
|
||||
import {
|
||||
ISplitviewPanelProps,
|
||||
SplitviewReact,
|
||||
@ -15,7 +15,17 @@ describe('splitview react', () => {
|
||||
beforeEach(() => {
|
||||
components = {
|
||||
default: (props: ISplitviewPanelProps) => {
|
||||
return <div>hello world</div>;
|
||||
return (
|
||||
<div>
|
||||
{Object.keys(props.params).map((key) => {
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
>{`key=${key},value=${props.params[key]}`}</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -61,4 +71,88 @@ describe('splitview react', () => {
|
||||
expect(api!.width).toBe(650);
|
||||
expect(api!.height).toBe(450);
|
||||
});
|
||||
|
||||
test('that the component can update parameters', async () => {
|
||||
let api: SplitviewApi;
|
||||
|
||||
const onReady = (event: SplitviewReadyEvent) => {
|
||||
api = event.api;
|
||||
};
|
||||
|
||||
const wrapper = render(
|
||||
<SplitviewReact
|
||||
orientation={Orientation.VERTICAL}
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
/>
|
||||
);
|
||||
|
||||
let panel: ISplitviewPanel;
|
||||
|
||||
act(() => {
|
||||
panel = api!.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
params: {
|
||||
keyA: 'valueA',
|
||||
keyB: 'valueB',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyA: 'valueAA', keyC: 'valueC' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueAA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=valueC/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyC: null });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyA,value=valueAA/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=null/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
panel.api.updateParameters({ keyA: undefined });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(wrapper.queryByText(/key=keyA/i)).not.toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyB,value=valueB/i)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
wrapper.queryByText(/key=keyC,value=null/i)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -6,9 +6,6 @@
|
||||
"jsx": "react",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"paths": {
|
||||
"dockview-core": "../dockview-core"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["**/node_modules", "src/__tests__"]
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
{
|
||||
"out": "typedocs",
|
||||
"entryPoints": ["./src/index.ts"],
|
||||
"exclude": ["**/_test/**/*.*", "**/index.ts"],
|
||||
"excludeExternals": true,
|
||||
"excludePrivate": true
|
||||
"extends": ["../../typedoc.base.json"],
|
||||
"entryPoints": ["src/index.ts"],
|
||||
"exclude": ["**/dist/**"]
|
||||
}
|
||||
|
17
packages/docs/blog/2023-06-03-dockview-1.7.3.md
Normal file
17
packages/docs/blog/2023-06-03-dockview-1.7.3.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
slug: dockview-1.7.3-release
|
||||
title: Dockview 1.7.3
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference to docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Fix bug custom params named 'title' conflicting with built-in tab 'title' object [#258](https://github.com/mathuo/dockview/issues/258)
|
||||
|
||||
## 🔥 Breaking changes
|
20
packages/docs/blog/2023-06-10-dockview-1.7.4.md
Normal file
20
packages/docs/blog/2023-06-10-dockview-1.7.4.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
slug: dockview-1.7.4-release
|
||||
title: Dockview 1.7.4
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference to docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Improvements and tests added to the panel `api.updateParameters(...)` method [#265](https://github.com/mathuo/dockview/pull/265)
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Fix bug associated with overidding panel titles when using `api.updateParameters(...)` [#265](https://github.com/mathuo/dockview/pull/265)
|
||||
- Cleanup listeners and disposables after use [#257](https://github.com/mathuo/dockview/pull/257)
|
||||
|
||||
## 🔥 Breaking changes
|
17
packages/docs/blog/2023-06-11-dockview-1.7.5.md
Normal file
17
packages/docs/blog/2023-06-11-dockview-1.7.5.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
slug: dockview-1.7.5-release
|
||||
title: Dockview 1.7.5
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference to docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Fix [#255](https://github.com/mathuo/dockview/issues/255)
|
||||
|
||||
## 🔥 Breaking changes
|
@ -2,7 +2,10 @@
|
||||
description: Dockview Documentation
|
||||
---
|
||||
|
||||
import { Container } from '@site/src/components/ui/container';
|
||||
import {
|
||||
Container,
|
||||
MultiFrameworkContainer,
|
||||
} from '@site/src/components/ui/container';
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
@ -24,7 +27,12 @@ import RenderingDockview from '@site/sandboxes/rendering-dockview/src/app';
|
||||
import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app';
|
||||
import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app';
|
||||
import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app';
|
||||
import { attach as attachDockviewVanilla } from '@site/sandboxes/vanilla-dockview/src/app';
|
||||
import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app';
|
||||
|
||||
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
|
||||
import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app';
|
||||
import { attach as attachTabHeightDockview } from '@site/sandboxes/javascript/tabheight-dockview/src/app';
|
||||
import { attach as attachNativeDockview } from '@site/sandboxes/javascript/fullwidthtab-dockview/src/app';
|
||||
|
||||
# Dockview
|
||||
|
||||
@ -32,12 +40,16 @@ import { attach as attachDockviewVanilla } from '@site/sandboxes/vanilla-dockvie
|
||||
|
||||
Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels.
|
||||
|
||||
<Container sandboxId="simple-dockview">
|
||||
<SimpleDockview />
|
||||
</Container>
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="simple-dockview"
|
||||
react={SimpleDockview}
|
||||
typescript={attachSimpleDockview}
|
||||
/>
|
||||
|
||||
You can access the panels associated group through the `panel.group` variable.
|
||||
The group will always be defined and will change if a panel is moved into another group.
|
||||
<br />
|
||||
|
||||
> You can access the panels associated group through the `panel.group` variable.
|
||||
> The group will always be defined and will change if a panel is moved into another group.
|
||||
|
||||
## DockviewReact Component
|
||||
|
||||
@ -424,6 +436,40 @@ const panel2 = api.addPanel({
|
||||
});
|
||||
```
|
||||
|
||||
### Update Panel
|
||||
|
||||
You can programatically update the `params` passed through to the panel through the panal api using `api.updateParameters`.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
params: {
|
||||
keyA: 'valueA',
|
||||
},
|
||||
});
|
||||
|
||||
// ...
|
||||
|
||||
panel.api.updateParameters({
|
||||
keyB: 'valueB',
|
||||
});
|
||||
|
||||
// ...
|
||||
|
||||
panel.api.updateParameters({
|
||||
keyA: 'anotherValueA',
|
||||
});
|
||||
```
|
||||
|
||||
To delete a parameter you should pass a value of `undefined` for the key.
|
||||
|
||||
```ts
|
||||
panel.api.updateParameters({
|
||||
keyA: undefined, // this will delete 'keyA'.
|
||||
});
|
||||
```
|
||||
|
||||
### Panel Rendering
|
||||
|
||||
By default `DockviewReact` only adds to the DOM those panels that are visible,
|
||||
@ -608,17 +654,21 @@ to the entire width of the group. For example:
|
||||
<DockviewReactComponent singleTabMode="fullwidth" {...otherProps} />
|
||||
```
|
||||
|
||||
<Container sandboxId="fullwidthtab-dockview">
|
||||
<DockviewNative />
|
||||
</Container>
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="fullwidthtab-dockview"
|
||||
react={DockviewNative}
|
||||
typescript={attachNativeDockview}
|
||||
/>
|
||||
|
||||
### Tab Height
|
||||
|
||||
Tab height can be controlled through CSS.
|
||||
|
||||
<Container sandboxId="tabheight-dockview">
|
||||
<DockviewTabheight />
|
||||
</Container>
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="tabheight-dockview"
|
||||
react={DockviewTabheight}
|
||||
typescript={attachTabHeightDockview}
|
||||
/>
|
||||
|
||||
## Groups
|
||||
|
||||
@ -690,6 +740,29 @@ api.group.api.setConstraints(...)
|
||||
<DockviewConstraints />
|
||||
</Container>
|
||||
|
||||
## iFrames
|
||||
|
||||
iFrames required special attention because of a particular behaviour in how iFrames render:
|
||||
|
||||
> Re-parenting an iFrame will reload the contents of the iFrame or the rephrase this, moving an iFrame within the DOM will cause a reload of its contents.
|
||||
|
||||
You can find many examples of discussions on this. Two reputable forums for example are linked [here](https://bugzilla.mozilla.org/show_bug.cgi?id=254144) and [here](https://github.com/whatwg/html/issues/5484).
|
||||
|
||||
The problem with iFrames and `dockview` is that when you hide or move a panel that panels DOM element may be moved within the DOM or removed from the DOM completely.
|
||||
If your panel contains an iFrame then that iFrame will reload after being re-positioned within the DOM tree and all state in that iFrame will most likely be lost.
|
||||
|
||||
`dockview` does not provide a built-in solution to this because it's too specific of a problem to include in the library.
|
||||
However the below example does show an implementation of a higher-order component `HoistedDockviewPanel`that you could use to work around this problems and make iFrames behave in `dockview`.
|
||||
|
||||
What the higher-order component is doing is to hoist the panels contents into a DOM element that is always present and then `position: absolute` that element to match the dimensions of it's linked panel.
|
||||
The visibility of these hoisted elements is then controlled through some exposed api methods to hide elements that shouldn't be currently shown.
|
||||
|
||||
You should open this example in CodeSandbox using the provided link to understand the code and make use of this implemention if required.
|
||||
|
||||
<Container sandboxId="iframe-dockview" height={600}>
|
||||
<DockviewWithIFrames />
|
||||
</Container>
|
||||
|
||||
## Events
|
||||
|
||||
A simple example showing events fired by `dockviewz that can be interacted with.
|
||||
@ -709,19 +782,11 @@ If you wish to interact with the drop event from one dockview instance in anothe
|
||||
<NestedDockview />
|
||||
</Container>
|
||||
|
||||
### Example
|
||||
|
||||
hello
|
||||
### Window-like mananger with tabs
|
||||
|
||||
<DockviewNative2 />
|
||||
|
||||
hello 2
|
||||
|
||||
<div style={{ height: '400px', width: '100%' }}>
|
||||
<App />
|
||||
</div>
|
||||
|
||||
## VanillaJS
|
||||
## Vanilla JS
|
||||
|
||||
> Note: This section is experimental and support for Vanilla JS is a work in progress.
|
||||
|
||||
@ -732,6 +797,6 @@ The core library is published as an independant package under the name `dockview
|
||||
> `dockview-core` is a dependency of `dockview` and automatically installed during the installation process of `dockview` via `npm install dockview`.
|
||||
|
||||
<Container
|
||||
sandboxId="vanilla-dockview"
|
||||
sandboxId="typescript/vanilla-dockview"
|
||||
injectVanillaJS={attachDockviewVanilla}
|
||||
/>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-docs",
|
||||
"version": "1.7.2",
|
||||
"version": "1.7.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
@ -12,8 +12,7 @@
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"typecheck": "tsc",
|
||||
"deploy-docs": "node scripts/package-docs.js"
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.4.0",
|
||||
@ -23,7 +22,7 @@
|
||||
"@minoru/react-dnd-treeview": "^3.4.3",
|
||||
"axios": "^1.3.3",
|
||||
"clsx": "^1.2.1",
|
||||
"dockview": "^1.7.2",
|
||||
"dockview": "^1.7.5",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
|
@ -14,7 +14,8 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,8 @@
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,8 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -1,20 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": true
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
||||
|
32
packages/docs/sandboxes/iframe-dockview/package.json
Normal file
32
packages/docs/sandboxes/iframe-dockview/package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "iframe-dockview",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"dockview"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.tsx",
|
||||
"dependencies": {
|
||||
"dockview": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
61
packages/docs/sandboxes/iframe-dockview/src/app.tsx
Normal file
61
packages/docs/sandboxes/iframe-dockview/src/app.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import {
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import { HoistedDockviewPanel } from './hoistedDockviewPanel';
|
||||
|
||||
const components = {
|
||||
iframeComponent: HoistedDockviewPanel(
|
||||
(props: IDockviewPanelProps<{ color: string }>) => {
|
||||
return (
|
||||
<iframe
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
src="https://dockview.dev"
|
||||
/>
|
||||
);
|
||||
}
|
||||
),
|
||||
basicComponent: () => {
|
||||
return (
|
||||
<div style={{ padding: '20px', color: 'white' }}>
|
||||
{'This panel is just a usual component '}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const App: React.FC = () => {
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'iframeComponent',
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'iframeComponent',
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'basicComponent',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DockviewReact
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
className="dockview-theme-abyss"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -0,0 +1,91 @@
|
||||
import { IDockviewPanelProps } from 'dockview';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
// get absolute position of element allowing for scroll position
|
||||
function getDomNodePagePosition(domNode: HTMLElement): {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
} {
|
||||
const { left, top, width, height } = domNode.getBoundingClientRect();
|
||||
return {
|
||||
left: left + window.scrollX,
|
||||
top: top + window.scrollY,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
}
|
||||
|
||||
function toggleVisibility(element: HTMLElement, isVisible: boolean) {
|
||||
element.style.visibility = isVisible ? 'visible' : 'hidden';
|
||||
}
|
||||
|
||||
export const HoistedDockviewPanel = <T extends object>(
|
||||
DockviewPanelComponent: React.FC<IDockviewPanelProps<T>>
|
||||
) => {
|
||||
return (props: IDockviewPanelProps<T>) => {
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const innerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
const positionHoistedPanel = () => {
|
||||
if (!ref.current || !innerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { left, top, height, width } = getDomNodePagePosition(
|
||||
ref.current.parentElement! // use the parent element to determine our size
|
||||
);
|
||||
|
||||
innerRef.current.style.left = `${left}px`;
|
||||
innerRef.current.style.top = `${top}px`;
|
||||
innerRef.current.style.height = `${height}px`;
|
||||
innerRef.current.style.width = `${width}px`;
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!innerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disposable1 = props.api.onDidVisibilityChange((event) => {
|
||||
if (!innerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
toggleVisibility(innerRef.current, event.isVisible); // subsequent checks of visibility
|
||||
});
|
||||
|
||||
const disposable2 = props.api.onDidDimensionsChange(() => {
|
||||
positionHoistedPanel();
|
||||
});
|
||||
|
||||
positionHoistedPanel();
|
||||
|
||||
return () => {
|
||||
disposable1.dispose(); // cleanup
|
||||
disposable2.dispose();
|
||||
};
|
||||
}, [props.api]);
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{ReactDOM.createPortal(
|
||||
<div
|
||||
/** you may want to mark these elements with some kind of attribute id */
|
||||
ref={innerRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
pointerEvents: 'none', // prevent this wrapper contain stealing events
|
||||
}}
|
||||
>
|
||||
<DockviewPanelComponent {...props} />
|
||||
</div>,
|
||||
document.body // <-- you may choose to mount these 'global' elements to anywhere you see suitable
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
20
packages/docs/sandboxes/iframe-dockview/src/index.tsx
Normal file
20
packages/docs/sandboxes/iframe-dockview/src/index.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { StrictMode } from 'react';
|
||||
import * as ReactDOMClient from 'react-dom/client';
|
||||
import './styles.css';
|
||||
import 'dockview/dist/styles/dockview.css';
|
||||
|
||||
import App from './app';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
if (rootElement) {
|
||||
const root = ReactDOMClient.createRoot(rootElement);
|
||||
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<div className="app">
|
||||
<App />
|
||||
</div>
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
15
packages/docs/sandboxes/iframe-dockview/src/styles.css
Normal file
15
packages/docs/sandboxes/iframe-dockview/src/styles.css
Normal file
@ -0,0 +1,15 @@
|
||||
body {
|
||||
margin: 0px;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.app {
|
||||
height: 100%;
|
||||
}
|
18
packages/docs/sandboxes/iframe-dockview/tsconfig.json
Normal file
18
packages/docs/sandboxes/iframe-dockview/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "javascript-fullwidthtab-dockview",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"dockview"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"dockview-core": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,17 @@
|
||||
.my-custom-tab {
|
||||
padding: 0px 8px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
background-color: var(--dv-tabs-and-actions-container-background-color);
|
||||
|
||||
.my-custom-tab-icon {
|
||||
font-size: 16px;
|
||||
|
||||
&:hover {
|
||||
border-radius: 2px;
|
||||
background-color: var(--dv-icon-hover-background-color);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
import {
|
||||
IGroupPanelInitParameters,
|
||||
IContentRenderer,
|
||||
PanelUpdateEvent,
|
||||
Parameters,
|
||||
ITabRenderer,
|
||||
DockviewComponent,
|
||||
} from 'dockview-core';
|
||||
import './app.scss';
|
||||
|
||||
class DefaultPanel implements IContentRenderer {
|
||||
private _element: HTMLElement;
|
||||
private _titleElement: HTMLElement;
|
||||
private _paramsElement: HTMLElement;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._element = document.createElement('div');
|
||||
this._element.style.display = 'flex';
|
||||
this._element.style.justifyContent = 'center';
|
||||
this._element.style.alignItems = 'center';
|
||||
this._element.style.color = 'white';
|
||||
this._element.style.height = '100%';
|
||||
|
||||
this._titleElement = document.createElement('span');
|
||||
this._paramsElement = document.createElement('span');
|
||||
|
||||
this._element.appendChild(this._titleElement);
|
||||
}
|
||||
|
||||
init(params: IGroupPanelInitParameters): void {
|
||||
this.render(params.params);
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent<Parameters>): void {
|
||||
this.render(event.params);
|
||||
}
|
||||
|
||||
private render(params: Record<string, any>) {
|
||||
this._titleElement.textContent = params.title;
|
||||
|
||||
if (params.x) {
|
||||
if (!this._paramsElement.parentElement) {
|
||||
this._element.appendChild(this._paramsElement);
|
||||
}
|
||||
this._paramsElement.textContent = params.x;
|
||||
} else {
|
||||
this._paramsElement.parentElement?.removeChild(this._paramsElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultTab implements ITabRenderer {
|
||||
private _element: HTMLElement;
|
||||
private _title: HTMLElement;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'my-custom-tab';
|
||||
|
||||
this._title = document.createElement('span');
|
||||
|
||||
const spacer = document.createElement('span');
|
||||
spacer.style.flexGrow = '1';
|
||||
|
||||
const btn1 = document.createElement('span');
|
||||
btn1.className = 'my-custom-tab-icon material-symbols-outlined';
|
||||
btn1.textContent = 'minimize';
|
||||
|
||||
const btn2 = document.createElement('span');
|
||||
btn2.className = 'my-custom-tab-icon material-symbols-outlined';
|
||||
btn2.textContent = 'maximize';
|
||||
|
||||
const btn3 = document.createElement('span');
|
||||
btn3.className = 'my-custom-tab-icon material-symbols-outlined';
|
||||
btn3.textContent = 'close';
|
||||
|
||||
this._element.appendChild(this._title);
|
||||
this._element.appendChild(spacer);
|
||||
this._element.appendChild(btn1);
|
||||
this._element.appendChild(btn2);
|
||||
this._element.appendChild(btn3);
|
||||
}
|
||||
|
||||
init(params: IGroupPanelInitParameters): void {
|
||||
this.render(params.params);
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent<Parameters>): void {
|
||||
this.render(event.params);
|
||||
}
|
||||
|
||||
private render(params: Record<string, any>) {
|
||||
this._title = params.title;
|
||||
}
|
||||
}
|
||||
|
||||
export function attach(parent: HTMLElement): {
|
||||
dispose: () => void;
|
||||
} {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'dockview-theme-abyss';
|
||||
element.style.height = '100%';
|
||||
element.style.width = '100%';
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
components: {
|
||||
default: DefaultPanel,
|
||||
},
|
||||
tabComponents: {
|
||||
default: DefaultTab,
|
||||
},
|
||||
singleTabMode: 'fullwidth',
|
||||
parentElement: element,
|
||||
});
|
||||
|
||||
parent.appendChild(element);
|
||||
|
||||
const { clientWidth, clientHeight } = parent;
|
||||
dockview.layout(clientWidth, clientHeight);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 1',
|
||||
},
|
||||
});
|
||||
panel1.group.locked = true;
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 2',
|
||||
},
|
||||
position: {
|
||||
direction: 'right',
|
||||
},
|
||||
});
|
||||
panel2.group.locked = true;
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 3',
|
||||
},
|
||||
position: {
|
||||
direction: 'below',
|
||||
},
|
||||
});
|
||||
panel3.group.locked = true;
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
dockview.dispose();
|
||||
element.remove();
|
||||
},
|
||||
};
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "vanilla-dockview",
|
||||
"name": "javascript-simple-dockview",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"dockview"
|
||||
@ -10,9 +10,15 @@
|
||||
"dockview-core": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.9.5"
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"scripts": {},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
120
packages/docs/sandboxes/javascript/simple-dockview/src/app.ts
Normal file
120
packages/docs/sandboxes/javascript/simple-dockview/src/app.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import {
|
||||
DockviewComponent,
|
||||
IContentRenderer,
|
||||
IGroupPanelInitParameters,
|
||||
PanelUpdateEvent,
|
||||
Parameters,
|
||||
} from 'dockview-core';
|
||||
|
||||
class DefaultPanel implements IContentRenderer {
|
||||
private _element: HTMLElement;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this._element = document.createElement('div');
|
||||
this._element.style.padding = '20px';
|
||||
this._element.style.color = 'white';
|
||||
}
|
||||
|
||||
init(params: IGroupPanelInitParameters): void {
|
||||
this._element.textContent = params.params.title;
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent<Parameters>): void {
|
||||
this._element.textContent = event.params.title;
|
||||
}
|
||||
}
|
||||
|
||||
export function attach(parent: HTMLElement): {
|
||||
dispose: () => void;
|
||||
} {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'dockview-theme-abyss';
|
||||
element.style.height = '100%';
|
||||
element.style.width = '100%';
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
components: {
|
||||
default: DefaultPanel,
|
||||
},
|
||||
parentElement: element,
|
||||
});
|
||||
|
||||
parent.appendChild(element);
|
||||
|
||||
const { clientWidth, clientHeight } = parent;
|
||||
dockview.layout(clientWidth, clientHeight);
|
||||
|
||||
const panel = dockview.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 1',
|
||||
},
|
||||
});
|
||||
|
||||
panel.group.locked = true;
|
||||
panel.group.header.hidden = true;
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 2',
|
||||
},
|
||||
});
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 3',
|
||||
},
|
||||
});
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 4',
|
||||
},
|
||||
position: { referencePanel: 'panel_1', direction: 'right' },
|
||||
});
|
||||
|
||||
const panel5 = dockview.addPanel({
|
||||
id: 'panel_5',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 5',
|
||||
},
|
||||
position: { referencePanel: 'panel_3', direction: 'right' },
|
||||
});
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel_6',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 6',
|
||||
},
|
||||
position: { referencePanel: 'panel_5', direction: 'below' },
|
||||
});
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel_7',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 7',
|
||||
},
|
||||
position: { referencePanel: 'panel_6', direction: 'right' },
|
||||
});
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
dockview.dispose();
|
||||
element.remove();
|
||||
},
|
||||
};
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import './styles.css';
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
|
||||
import { attach } from './app';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
if (rootElement) {
|
||||
attach(rootElement);
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
body {
|
||||
margin: 0px;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.app {
|
||||
height: 100%;
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "javascript-tabheight-dockview",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"dockview"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"dockview-core": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user