mirror of
https://github.com/mathuo/dockview
synced 2025-03-12 17:02:04 +00:00
Merge branch 'master' of https://github.com/mathuo/dockview into 172-zero-dependency-vanilla-js
This commit is contained in:
commit
d09b2e8ff4
@ -3,7 +3,7 @@
|
||||
"packages/*"
|
||||
],
|
||||
"useWorkspaces": true,
|
||||
"version": "1.5.2",
|
||||
"version": "1.6.0",
|
||||
"npmClient": "yarn",
|
||||
"command": {
|
||||
"publish": {
|
||||
|
42
package.json
42
package.json
@ -32,34 +32,34 @@
|
||||
},
|
||||
"homepage": "https://github.com/mathuo/dockview#readme",
|
||||
"devDependencies": {
|
||||
"@testing-library/dom": "^8.13.0",
|
||||
"@types/jest": "^27.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
||||
"@typescript-eslint/parser": "^5.22.0",
|
||||
"@testing-library/dom": "^8.20.0",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
||||
"@typescript-eslint/parser": "^5.52.0",
|
||||
"codecov": "^3.8.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.1",
|
||||
"eslint": "^8.15.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"eslint": "^8.34.0",
|
||||
"fs-extra": "^11.1.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-dart-sass": "^1.0.2",
|
||||
"jest": "^28.1.0",
|
||||
"jest-environment-jsdom": "^28.1.0",
|
||||
"jest": "^29.4.3",
|
||||
"jest-environment-jsdom": "^29.4.3",
|
||||
"jest-sonar-reporter": "^2.0.0",
|
||||
"jsdom": "^19.0.0",
|
||||
"lerna": "^4.0.0",
|
||||
"jsdom": "^21.1.0",
|
||||
"lerna": "^6.5.1",
|
||||
"merge2": "^1.4.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.51.0",
|
||||
"sass-loader": "^12.6.0",
|
||||
"rimraf": "^4.1.2",
|
||||
"sass": "^1.58.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^28.0.2",
|
||||
"ts-loader": "^9.3.0",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.6.4",
|
||||
"webpack": "^5.72.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.9.0"
|
||||
"ts-jest": "^29.0.5",
|
||||
"ts-loader": "^9.4.2",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.5",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview",
|
||||
"version": "1.5.2",
|
||||
"version": "1.6.0",
|
||||
"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,22 +56,18 @@
|
||||
"author": "https://github.com/mathuo",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-typescript": "^8.3.2",
|
||||
"@testing-library/react": "^13.2.0",
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/react-dom": "^18.0.3",
|
||||
"@rollup/plugin-typescript": "^11.0.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"cross-env": "^7.0.3",
|
||||
"postcss": "^8.4.13",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.72.1",
|
||||
"postcss": "^8.4.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"rimraf": "^4.1.2",
|
||||
"rollup": "^3.15.0",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"typedoc": "^0.22.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
"typedoc": "^0.23.25"
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import { join } from 'path';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import postcss from 'rollup-plugin-postcss';
|
||||
const { join } = require('path');
|
||||
const typescript = require('@rollup/plugin-typescript');
|
||||
const { terser } = require('rollup-plugin-terser');
|
||||
const postcss = require('rollup-plugin-postcss');
|
||||
|
||||
const { name, version, homepage, license } = require('./package.json');
|
||||
const reactMain = join(__dirname, './scripts/rollupEntryTarget-react.ts');
|
||||
@ -108,7 +108,7 @@ function createBundle(format, options) {
|
||||
};
|
||||
}
|
||||
|
||||
export default [
|
||||
module.exports = [
|
||||
// amd
|
||||
createBundle('amd', {
|
||||
withStyles: false,
|
||||
|
@ -1,42 +1,30 @@
|
||||
import { DockviewPanelApiImpl, TitleEvent } from '../../api/dockviewPanelApi';
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { DockviewPanelApiImpl, TitleEvent } from '../../api/groupPanelApi';
|
||||
import { IDockviewPanel } from '../../groupview/groupPanel';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
|
||||
describe('groupPanelApi', () => {
|
||||
test('title', () => {
|
||||
const groupPanel: Partial<IDockviewPanel> = {
|
||||
id: 'test_id',
|
||||
title: 'test_title',
|
||||
};
|
||||
|
||||
const accessor: Partial<DockviewComponent> = {};
|
||||
const groupViewPanel = new GroupPanel(
|
||||
<DockviewComponent>accessor,
|
||||
'',
|
||||
{}
|
||||
);
|
||||
|
||||
const cut = new DockviewPanelApiImpl(
|
||||
<IDockviewPanel>groupPanel,
|
||||
<GroupPanel>groupViewPanel
|
||||
);
|
||||
|
||||
let events: TitleEvent[] = [];
|
||||
|
||||
const disposable = cut.onDidTitleChange((event) => {
|
||||
events.push(event);
|
||||
const panelMock = jest.fn<DockviewPanel, []>(() => {
|
||||
return {
|
||||
update: jest.fn(),
|
||||
} as any;
|
||||
});
|
||||
const groupMock = jest.fn<GroupPanel, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
|
||||
expect(events.length).toBe(0);
|
||||
expect(cut.title).toBe('test_title');
|
||||
const panel = new panelMock();
|
||||
const group = new groupMock();
|
||||
|
||||
cut.setTitle('test_title_2');
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0]).toEqual({ title: 'test_title_2' });
|
||||
expect(cut.title).toBe('test_title'); // title should remain unchanged
|
||||
const cut = new DockviewPanelApiImpl(panel, group);
|
||||
|
||||
disposable.dispose();
|
||||
cut.setTitle('test_title');
|
||||
|
||||
expect(panel.update).toBeCalledTimes(1);
|
||||
expect(panel.update).toBeCalledWith({
|
||||
params: { title: 'test_title' },
|
||||
});
|
||||
});
|
||||
|
||||
test('onDidGroupChange', () => {
|
||||
@ -44,7 +32,11 @@ describe('groupPanelApi', () => {
|
||||
id: 'test_id',
|
||||
};
|
||||
|
||||
const accessor: Partial<DockviewComponent> = {};
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
const groupViewPanel = new GroupPanel(
|
||||
<DockviewComponent>accessor,
|
||||
'',
|
@ -3,7 +3,7 @@ import { DragHandler } from '../../dnd/abstractDragHandler';
|
||||
import { IDisposable } from '../../lifecycle';
|
||||
|
||||
describe('abstractDragHandler', () => {
|
||||
test('that className dragged is added to element after dragstart event', () => {
|
||||
test('that className dv-dragged is added to element after dragstart event', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const element = document.createElement('div');
|
||||
@ -26,13 +26,13 @@ describe('abstractDragHandler', () => {
|
||||
}
|
||||
})(element);
|
||||
|
||||
expect(element.classList.contains('dragged')).toBeFalsy();
|
||||
expect(element.classList.contains('dv-dragged')).toBeFalsy();
|
||||
|
||||
fireEvent.dragStart(element);
|
||||
expect(element.classList.contains('dragged')).toBeTruthy();
|
||||
expect(element.classList.contains('dv-dragged')).toBeTruthy();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(element.classList.contains('dragged')).toBeFalsy();
|
||||
expect(element.classList.contains('dv-dragged')).toBeFalsy();
|
||||
|
||||
handler.dispose();
|
||||
});
|
||||
|
@ -1,16 +1,23 @@
|
||||
import { Droptarget, Position } from '../../dnd/droptarget';
|
||||
import {
|
||||
calculateQuadrantAsPercentage,
|
||||
calculateQuadrantAsPixels,
|
||||
directionToPosition,
|
||||
Droptarget,
|
||||
Position,
|
||||
positionToDirection,
|
||||
} from '../../dnd/droptarget';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
|
||||
function createOffsetDragOverEvent(params: {
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
clientX: number;
|
||||
clientY: number;
|
||||
}): Event {
|
||||
const event = new Event('dragover', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
Object.defineProperty(event, 'offsetX', { get: () => params.offsetX });
|
||||
Object.defineProperty(event, 'offsetY', { get: () => params.offsetY });
|
||||
Object.defineProperty(event, 'clientX', { get: () => params.clientX });
|
||||
Object.defineProperty(event, 'clientY', { get: () => params.clientY });
|
||||
return event;
|
||||
}
|
||||
|
||||
@ -27,12 +34,34 @@ describe('droptarget', () => {
|
||||
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200);
|
||||
});
|
||||
|
||||
test('directionToPosition', () => {
|
||||
expect(directionToPosition('above')).toBe('top');
|
||||
expect(directionToPosition('below')).toBe('bottom');
|
||||
expect(directionToPosition('left')).toBe('left');
|
||||
expect(directionToPosition('right')).toBe('right');
|
||||
expect(directionToPosition('within')).toBe('center');
|
||||
expect(() => directionToPosition('bad_input' as any)).toThrow(
|
||||
"invalid direction 'bad_input'"
|
||||
);
|
||||
});
|
||||
|
||||
test('positionToDirection', () => {
|
||||
expect(positionToDirection('top')).toBe('above');
|
||||
expect(positionToDirection('bottom')).toBe('below');
|
||||
expect(positionToDirection('left')).toBe('left');
|
||||
expect(positionToDirection('right')).toBe('right');
|
||||
expect(positionToDirection('center')).toBe('within');
|
||||
expect(() => positionToDirection('bad_input' as any)).toThrow(
|
||||
"invalid position 'bad_input'"
|
||||
);
|
||||
});
|
||||
|
||||
test('non-directional', () => {
|
||||
let position: Position | undefined = undefined;
|
||||
|
||||
droptarget = new Droptarget(element, {
|
||||
canDisplayOverlay: () => true,
|
||||
validOverlays: 'none',
|
||||
acceptedTargetZones: ['center'],
|
||||
});
|
||||
|
||||
droptarget.onDrop((event) => {
|
||||
@ -46,7 +75,7 @@ describe('droptarget', () => {
|
||||
'.drop-target-dropzone'
|
||||
) as HTMLElement;
|
||||
fireEvent.drop(target);
|
||||
expect(position).toBe(Position.Center);
|
||||
expect(position).toBe('center');
|
||||
});
|
||||
|
||||
test('drop', () => {
|
||||
@ -54,7 +83,7 @@ describe('droptarget', () => {
|
||||
|
||||
droptarget = new Droptarget(element, {
|
||||
canDisplayOverlay: () => true,
|
||||
validOverlays: 'all',
|
||||
acceptedTargetZones: ['top', 'left', 'right', 'bottom', 'center'],
|
||||
});
|
||||
|
||||
droptarget.onDrop((event) => {
|
||||
@ -73,18 +102,21 @@ describe('droptarget', () => {
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 19, offsetY: 0 })
|
||||
createOffsetDragOverEvent({
|
||||
clientX: 19,
|
||||
clientY: 0,
|
||||
})
|
||||
);
|
||||
|
||||
expect(position).toBeUndefined();
|
||||
fireEvent.drop(target);
|
||||
expect(position).toBe(Position.Left);
|
||||
expect(position).toBe('left');
|
||||
});
|
||||
|
||||
test('default', () => {
|
||||
droptarget = new Droptarget(element, {
|
||||
canDisplayOverlay: () => true,
|
||||
validOverlays: 'all',
|
||||
acceptedTargetZones: ['top', 'left', 'right', 'bottom', 'center'],
|
||||
});
|
||||
|
||||
expect(droptarget.state).toBeUndefined();
|
||||
@ -106,57 +138,204 @@ describe('droptarget', () => {
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 19, offsetY: 0 })
|
||||
createOffsetDragOverEvent({ clientX: 19, clientY: 0 })
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection.left'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe(Position.Left);
|
||||
expect(droptarget.state).toBe('left');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateX(-25%) scaleX(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 40, offsetY: 19 })
|
||||
createOffsetDragOverEvent({ clientX: 40, clientY: 19 })
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection.top'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe(Position.Top);
|
||||
expect(droptarget.state).toBe('top');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateY(-25%) scaleY(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 160, offsetY: 81 })
|
||||
createOffsetDragOverEvent({ clientX: 160, clientY: 81 })
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection.bottom'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe(Position.Bottom);
|
||||
expect(droptarget.state).toBe('bottom');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateY(25%) scaleY(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 161, offsetY: 0 })
|
||||
createOffsetDragOverEvent({ clientX: 161, clientY: 0 })
|
||||
);
|
||||
|
||||
viewQuery = element.querySelectorAll(
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection.right'
|
||||
'.drop-target > .drop-target-dropzone > .drop-target-selection'
|
||||
);
|
||||
expect(viewQuery.length).toBe(1);
|
||||
expect(droptarget.state).toBe(Position.Right);
|
||||
expect(droptarget.state).toBe('right');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('translateX(25%) scaleX(0.5)');
|
||||
|
||||
fireEvent(
|
||||
target,
|
||||
createOffsetDragOverEvent({ offsetX: 100, offsetY: 50 })
|
||||
createOffsetDragOverEvent({ clientX: 100, clientY: 50 })
|
||||
);
|
||||
expect(droptarget.state).toBe(Position.Center);
|
||||
expect(droptarget.state).toBe('center');
|
||||
expect(
|
||||
(
|
||||
element
|
||||
.getElementsByClassName('drop-target-selection')
|
||||
.item(0) as HTMLDivElement
|
||||
).style.transform
|
||||
).toBe('');
|
||||
|
||||
fireEvent.dragLeave(target);
|
||||
expect(droptarget.state).toBeUndefined();
|
||||
expect(droptarget.state).toBe('center');
|
||||
viewQuery = element.querySelectorAll('.drop-target');
|
||||
expect(viewQuery.length).toBe(0);
|
||||
});
|
||||
|
||||
describe('calculateQuadrantAsPercentage', () => {
|
||||
test('variety of cases', () => {
|
||||
const inputs: Array<{
|
||||
directions: Position[];
|
||||
x: number;
|
||||
y: number;
|
||||
result: Position | null;
|
||||
}> = [
|
||||
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
|
||||
{
|
||||
directions: ['left', 'right'],
|
||||
x: 81,
|
||||
y: 50,
|
||||
result: 'right',
|
||||
},
|
||||
{
|
||||
directions: ['top', 'bottom'],
|
||||
x: 50,
|
||||
y: 19,
|
||||
result: 'top',
|
||||
},
|
||||
{
|
||||
directions: ['top', 'bottom'],
|
||||
x: 50,
|
||||
y: 81,
|
||||
result: 'bottom',
|
||||
},
|
||||
{
|
||||
directions: ['left', 'right', 'top', 'bottom', 'center'],
|
||||
x: 50,
|
||||
y: 50,
|
||||
result: 'center',
|
||||
},
|
||||
{
|
||||
directions: ['left', 'right', 'top', 'bottom'],
|
||||
x: 50,
|
||||
y: 50,
|
||||
result: null,
|
||||
},
|
||||
];
|
||||
|
||||
for (const input of inputs) {
|
||||
expect(
|
||||
calculateQuadrantAsPercentage(
|
||||
new Set(input.directions),
|
||||
input.x,
|
||||
input.y,
|
||||
100,
|
||||
100,
|
||||
20
|
||||
)
|
||||
).toBe(input.result);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateQuadrantAsPixels', () => {
|
||||
test('variety of cases', () => {
|
||||
const inputs: Array<{
|
||||
directions: Position[];
|
||||
x: number;
|
||||
y: number;
|
||||
result: Position | null;
|
||||
}> = [
|
||||
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
|
||||
{
|
||||
directions: ['left', 'right'],
|
||||
x: 81,
|
||||
y: 50,
|
||||
result: 'right',
|
||||
},
|
||||
{
|
||||
directions: ['top', 'bottom'],
|
||||
x: 50,
|
||||
y: 19,
|
||||
result: 'top',
|
||||
},
|
||||
{
|
||||
directions: ['top', 'bottom'],
|
||||
x: 50,
|
||||
y: 81,
|
||||
result: 'bottom',
|
||||
},
|
||||
{
|
||||
directions: ['left', 'right', 'top', 'bottom', 'center'],
|
||||
x: 50,
|
||||
y: 50,
|
||||
result: 'center',
|
||||
},
|
||||
{
|
||||
directions: ['left', 'right', 'top', 'bottom'],
|
||||
x: 50,
|
||||
y: 50,
|
||||
result: null,
|
||||
},
|
||||
];
|
||||
|
||||
for (const input of inputs) {
|
||||
expect(
|
||||
calculateQuadrantAsPixels(
|
||||
new Set(input.directions),
|
||||
input.x,
|
||||
input.y,
|
||||
100,
|
||||
100,
|
||||
20
|
||||
)
|
||||
).toBe(input.result);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -7,22 +7,21 @@ import {
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { Orientation } from '../../splitview/core/splitview';
|
||||
import { ReactPanelDeserialzier } from '../../react/deserializer';
|
||||
import { Position } from '../../dnd/droptarget';
|
||||
import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
import {
|
||||
GroupPanelUpdateEvent,
|
||||
GroupviewPanelState,
|
||||
IDockviewPanel,
|
||||
IGroupPanelInitParameters,
|
||||
} from '../../groupview/groupPanel';
|
||||
} from '../../groupview/types';
|
||||
import { IGroupPanelView } from '../../dockview/defaultGroupPanelView';
|
||||
import { DefaultTab } from '../../dockview/components/tab/defaultTab';
|
||||
import { Emitter } from '../../events';
|
||||
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import {
|
||||
DockviewPanelApi,
|
||||
DockviewPanelApiImpl,
|
||||
} from '../../api/groupPanelApi';
|
||||
import { DefaultTab } from '../../dockview/components/tab/defaultTab';
|
||||
import { Emitter } from '../../events';
|
||||
} from '../../api/dockviewPanelApi';
|
||||
|
||||
class PanelContentPartTest implements IContentRenderer {
|
||||
element: HTMLElement = document.createElement('div');
|
||||
@ -32,7 +31,7 @@ class PanelContentPartTest implements IContentRenderer {
|
||||
|
||||
isDisposed: boolean = false;
|
||||
|
||||
constructor(public readonly id: string, component: string) {
|
||||
constructor(public readonly id: string, public readonly component: string) {
|
||||
this.element.classList.add(`testpanel-${id}`);
|
||||
}
|
||||
|
||||
@ -53,7 +52,7 @@ class PanelContentPartTest implements IContentRenderer {
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
return { id: this.id };
|
||||
return { id: this.component };
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
@ -253,9 +252,9 @@ describe('dockviewComponent', () => {
|
||||
const panel4 = dockview.getGroupPanel('panel4');
|
||||
|
||||
const group1 = panel1!.group;
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right);
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
|
||||
const group2 = panel1!.group;
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center);
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
|
||||
|
||||
expect(dockview.activeGroup).toBe(group2);
|
||||
expect(dockview.activeGroup!.model.activePanel).toBe(panel3);
|
||||
@ -302,12 +301,12 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = dockview.getGroupPanel('panel1');
|
||||
const panel2 = dockview.getGroupPanel('panel2');
|
||||
const panel1 = dockview.getGroupPanel('panel1')!;
|
||||
const panel2 = dockview.getGroupPanel('panel2')!;
|
||||
const group1 = panel1.group;
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right);
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
|
||||
const group2 = panel1.group;
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center);
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
|
||||
|
||||
expect(dockview.size).toBe(2);
|
||||
expect(dockview.totalPanels).toBe(4);
|
||||
@ -345,10 +344,10 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = dockview.getGroupPanel('panel1');
|
||||
const panel2 = dockview.getGroupPanel('panel2');
|
||||
const panel3 = dockview.getGroupPanel('panel3');
|
||||
const panel4 = dockview.getGroupPanel('panel4');
|
||||
const panel1 = dockview.getGroupPanel('panel1')!;
|
||||
const panel2 = dockview.getGroupPanel('panel2')!;
|
||||
const panel3 = dockview.getGroupPanel('panel3')!;
|
||||
const panel4 = dockview.getGroupPanel('panel4')!;
|
||||
|
||||
expect(panel1.api.isActive).toBeFalsy();
|
||||
expect(panel2.api.isActive).toBeFalsy();
|
||||
@ -370,9 +369,9 @@ describe('dockviewComponent', () => {
|
||||
expect(panel4.api.isActive).toBeFalsy();
|
||||
|
||||
const group1 = panel1.group;
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right);
|
||||
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
|
||||
const group2 = panel1.group;
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center);
|
||||
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
|
||||
|
||||
expect(dockview.size).toBe(2);
|
||||
expect(panel1.group).toBe(panel3.group);
|
||||
@ -425,9 +424,8 @@ describe('dockviewComponent', () => {
|
||||
expect(dockview.size).toBe(1);
|
||||
expect(dockview.totalPanels).toBe(2);
|
||||
|
||||
const panel1 = dockview.getGroupPanel('panel1');
|
||||
const panel2 = dockview.getGroupPanel('panel2');
|
||||
|
||||
const panel1 = dockview.getGroupPanel('panel1')!;
|
||||
const panel2 = dockview.getGroupPanel('panel2')!;
|
||||
expect(panel1.group).toBe(panel2.group);
|
||||
|
||||
const group = panel1.group;
|
||||
@ -440,7 +438,7 @@ describe('dockviewComponent', () => {
|
||||
expect(group.model.indexOf(panel1)).toBe(0);
|
||||
expect(group.model.indexOf(panel2)).toBe(1);
|
||||
|
||||
dockview.moveGroupOrPanel(group, group.id, 'panel1', Position.Right);
|
||||
dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
|
||||
|
||||
expect(dockview.size).toBe(2);
|
||||
expect(dockview.totalPanels).toBe(2);
|
||||
@ -460,7 +458,7 @@ describe('dockviewComponent', () => {
|
||||
|
||||
await panel2.api.close();
|
||||
|
||||
expect(dockview.size).toBe(1); // watermark
|
||||
expect(dockview.size).toBe(0);
|
||||
expect(dockview.totalPanels).toBe(0);
|
||||
});
|
||||
|
||||
@ -489,8 +487,8 @@ describe('dockviewComponent', () => {
|
||||
|
||||
expect(viewQuery.length).toBe(1);
|
||||
|
||||
const group = dockview.getGroupPanel('panel1').group;
|
||||
dockview.moveGroupOrPanel(group, group.id, 'panel1', Position.Right);
|
||||
const group = dockview.getGroupPanel('panel1')!.group;
|
||||
dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
|
||||
|
||||
viewQuery = container.querySelectorAll(
|
||||
'.branch-node > .split-view-container > .view-container > .view'
|
||||
@ -975,7 +973,7 @@ describe('dockviewComponent', () => {
|
||||
panel2.group!,
|
||||
panel5.group!.id,
|
||||
panel5.id,
|
||||
Position.Center
|
||||
'center'
|
||||
);
|
||||
expect(events).toEqual([
|
||||
{ type: 'REMOVE_PANEL', panel: panel5 },
|
||||
@ -994,7 +992,7 @@ describe('dockviewComponent', () => {
|
||||
panel2.group!,
|
||||
panel4.group!.id,
|
||||
panel4.id,
|
||||
Position.Center
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(events).toEqual([
|
||||
@ -1207,55 +1205,6 @@ describe('dockviewComponent', () => {
|
||||
expect(dockview.totalPanels).toBe(0);
|
||||
});
|
||||
|
||||
test('last group is retained for watermark', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: { default: PanelContentPartTest },
|
||||
});
|
||||
|
||||
dockview.layout(500, 1000);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
});
|
||||
|
||||
expect(dockview.size).toBe(1);
|
||||
expect(dockview.totalPanels).toBe(1);
|
||||
|
||||
const group = panel1.group;
|
||||
|
||||
dockview.removePanel(panel1);
|
||||
|
||||
expect(group.model.hasWatermark).toBeTruthy();
|
||||
expect(dockview.size).toBe(1);
|
||||
expect(dockview.totalPanels).toBe(0);
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
});
|
||||
|
||||
expect(group.model.hasWatermark).toBeFalsy();
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
});
|
||||
|
||||
expect(dockview.size).toBe(1);
|
||||
expect(dockview.totalPanels).toBe(2);
|
||||
|
||||
panel2.api.close();
|
||||
expect(group.model.hasWatermark).toBeFalsy();
|
||||
panel3.api.close();
|
||||
expect(group.model.hasWatermark).toBeTruthy();
|
||||
});
|
||||
|
||||
test('panel is disposed of when removed', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
@ -1314,7 +1263,7 @@ describe('dockviewComponent', () => {
|
||||
panel1.group,
|
||||
panel2.group.id,
|
||||
'panel2',
|
||||
Position.Left
|
||||
'left'
|
||||
);
|
||||
|
||||
expect(panel1Spy).not.toHaveBeenCalled();
|
||||
@ -1355,7 +1304,7 @@ describe('dockviewComponent', () => {
|
||||
panel1.group,
|
||||
panel2.group.id,
|
||||
'panel2',
|
||||
Position.Center
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1Spy).not.toHaveBeenCalled();
|
||||
@ -1394,7 +1343,7 @@ describe('dockviewComponent', () => {
|
||||
panel1.group,
|
||||
panel1.group.id,
|
||||
'panel1',
|
||||
Position.Center,
|
||||
'center',
|
||||
0
|
||||
);
|
||||
|
||||
@ -1515,6 +1464,53 @@ describe('dockviewComponent', () => {
|
||||
expect(panel2Spy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('move entire group into another group', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: { default: PanelContentPartTest },
|
||||
});
|
||||
|
||||
dockview.layout(500, 1000);
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
});
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
position: {
|
||||
referencePanel: panel1,
|
||||
},
|
||||
});
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
position: {
|
||||
referencePanel: panel1,
|
||||
direction: 'right',
|
||||
},
|
||||
});
|
||||
|
||||
const panel1Spy = jest.spyOn(panel1.group, 'dispose');
|
||||
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel3.group,
|
||||
panel1.group.id,
|
||||
undefined,
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(panel1Spy).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('fromJSON events should still fire', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
@ -1635,8 +1631,6 @@ describe('dockviewComponent', () => {
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
console.log(activePanel.map((_) => _?.id).join(' '));
|
||||
|
||||
expect(addGroup.length).toBe(4);
|
||||
expect(removeGroup.length).toBe(0);
|
||||
expect(activeGroup.length).toBe(1);
|
||||
@ -1973,4 +1967,466 @@ describe('dockviewComponent', () => {
|
||||
// load a layout with a default tab identifier when react default is present
|
||||
|
||||
// load a layout with invialid panel identifier
|
||||
|
||||
test('orthogonal realigment #1', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
dockview.deserializer = new ReactPanelDeserialzier(dockview);
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.VERTICAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.VERTICAL);
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'left',
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
activeGroup: '1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel2'],
|
||||
id: '1',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 1000,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel2',
|
||||
},
|
||||
},
|
||||
options: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('orthogonal realigment #2', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
dockview.deserializer = new ReactPanelDeserialzier(dockview);
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel2'],
|
||||
id: 'group-2',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.VERTICAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel2',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.VERTICAL);
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'left',
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
activeGroup: '1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel3'],
|
||||
id: '1',
|
||||
activeView: 'panel3',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
{
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel2'],
|
||||
id: 'group-2',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel2',
|
||||
},
|
||||
panel3: {
|
||||
id: 'panel3',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel3',
|
||||
},
|
||||
},
|
||||
options: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('orthogonal realigment #3', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
dockview.deserializer = new ReactPanelDeserialzier(dockview);
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
|
||||
|
||||
dockview.fromJSON({
|
||||
activeGroup: 'group-1',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 500,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.VERTICAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.VERTICAL);
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'above',
|
||||
},
|
||||
});
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'below',
|
||||
},
|
||||
});
|
||||
|
||||
expect(dockview.orientation).toBe(Orientation.VERTICAL);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
activeGroup: '2',
|
||||
grid: {
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel2'],
|
||||
id: '1',
|
||||
activeView: 'panel2',
|
||||
},
|
||||
size: 333,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel1'],
|
||||
id: 'group-1',
|
||||
activeView: 'panel1',
|
||||
},
|
||||
size: 333,
|
||||
},
|
||||
{
|
||||
type: 'leaf',
|
||||
data: {
|
||||
views: ['panel3'],
|
||||
id: '2',
|
||||
activeView: 'panel3',
|
||||
},
|
||||
size: 334,
|
||||
},
|
||||
],
|
||||
size: 1000,
|
||||
},
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
orientation: Orientation.VERTICAL,
|
||||
},
|
||||
panels: {
|
||||
panel1: {
|
||||
id: 'panel1',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel1',
|
||||
},
|
||||
panel2: {
|
||||
id: 'panel2',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel2',
|
||||
},
|
||||
panel3: {
|
||||
id: 'panel3',
|
||||
view: { content: { id: 'default' } },
|
||||
title: 'panel3',
|
||||
},
|
||||
},
|
||||
options: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('that a empty component has no groups', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
dockview.deserializer = new ReactPanelDeserialzier(dockview);
|
||||
|
||||
expect(dockview.groups.length).toBe(0);
|
||||
});
|
||||
|
||||
test('that deserializing an empty layout has zero groups and a watermark', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
dockview.deserializer = new ReactPanelDeserialzier(dockview);
|
||||
|
||||
expect(dockview.groups.length).toBe(0);
|
||||
|
||||
expect(
|
||||
dockview.element.querySelectorAll('.dv-watermark-container').length
|
||||
).toBe(1);
|
||||
|
||||
dockview.fromJSON({
|
||||
grid: {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
root: {
|
||||
type: 'branch',
|
||||
data: [],
|
||||
},
|
||||
height: 100,
|
||||
width: 100,
|
||||
},
|
||||
panels: {},
|
||||
});
|
||||
|
||||
expect(dockview.groups.length).toBe(0);
|
||||
|
||||
expect(
|
||||
dockview.element.querySelectorAll('.dv-watermark-container').length
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('empty', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent(container, {
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
tabComponents: {
|
||||
test_tab_id: PanelTabPartTest,
|
||||
},
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
});
|
||||
dockview.deserializer = new ReactPanelDeserialzier(dockview);
|
||||
|
||||
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
|
||||
grid: {
|
||||
height: 0,
|
||||
width: 0,
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
root: {
|
||||
data: [],
|
||||
type: 'branch',
|
||||
size: 0,
|
||||
},
|
||||
},
|
||||
options: {},
|
||||
panels: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { DockviewApi } from '../../api/component.api';
|
||||
import { IGroupPanelView } from '../../dockview/defaultGroupPanelView';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import { DockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
|
||||
describe('dockviewGroupPanel', () => {
|
||||
describe('dockviewPanel', () => {
|
||||
test('update title', () => {
|
||||
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
|
||||
return {
|
||||
@ -20,7 +20,7 @@ describe('dockviewGroupPanel', () => {
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const cut = new DockviewGroupPanel('fake-id', accessor, api, group);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group);
|
||||
|
||||
let latestTitle: string | undefined = undefined;
|
||||
|
||||
@ -55,7 +55,7 @@ describe('dockviewGroupPanel', () => {
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
|
||||
const cut = new DockviewGroupPanel('fake-id', accessor, api, group);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group);
|
||||
|
||||
const viewMock = jest.fn<IGroupPanelView, []>(() => {
|
||||
return {
|
||||
@ -86,7 +86,7 @@ describe('dockviewGroupPanel', () => {
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const cut = new DockviewGroupPanel('fake-id', accessor, api, group);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group);
|
||||
|
||||
expect(cut.params).toEqual(undefined);
|
||||
|
||||
@ -94,4 +94,29 @@ describe('dockviewGroupPanel', () => {
|
||||
|
||||
expect(cut.params).toEqual({ variableA: 'A', variableB: 'B' });
|
||||
});
|
||||
|
||||
test('setSize propagates to underlying group', () => {
|
||||
const dockviewApiMock = jest.fn<DockviewApi, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
});
|
||||
const groupMock = jest.fn<GroupPanel, []>(() => {
|
||||
return {
|
||||
api: {
|
||||
setSize: jest.fn(),
|
||||
},
|
||||
} as any;
|
||||
});
|
||||
const api = new dockviewApiMock();
|
||||
const accessor = new accessorMock();
|
||||
const group = new groupMock();
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group);
|
||||
|
||||
cut.api.setSize({ height: 123, width: 456 });
|
||||
|
||||
expect(group.api.setSize).toBeCalledWith({ height: 123, width: 456 });
|
||||
expect(group.api.setSize).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
import { GridviewPanelApiImpl } from '../../api/gridviewPanelApi';
|
||||
import { GridviewComponent } from '../../gridview/gridviewComponent';
|
||||
import { GridviewPanel } from '../../gridview/gridviewPanel';
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
@ -6,7 +7,9 @@ import { Orientation } from '../../splitview/core/splitview';
|
||||
|
||||
class TestGridview extends GridviewPanel {
|
||||
constructor(id: string, componentName: string) {
|
||||
super(id, componentName);
|
||||
super(id, componentName, new GridviewPanelApiImpl(id));
|
||||
|
||||
this.api.initialize(this);
|
||||
|
||||
this.element.id = id;
|
||||
}
|
||||
@ -65,7 +68,7 @@ describe('gridview', () => {
|
||||
|
||||
expect(gridview.size).toBe(1);
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
|
||||
gridview.removePanel(panel1);
|
||||
|
||||
@ -101,9 +104,9 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel2 = gridview.getPanel('panel2');
|
||||
const panel3 = gridview.getPanel('panel3');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
const panel2 = gridview.getPanel('panel2')!;
|
||||
const panel3 = gridview.getPanel('panel3')!;
|
||||
|
||||
expect(panel1.api.isActive).toBeFalsy();
|
||||
expect(panel2.api.isActive).toBeFalsy();
|
||||
@ -192,9 +195,9 @@ describe('gridview', () => {
|
||||
});
|
||||
gridview.layout(800, 400, true);
|
||||
|
||||
const panel1 = gridview.getPanel('panel_1');
|
||||
const panel2 = gridview.getPanel('panel_2');
|
||||
const panel3 = gridview.getPanel('panel_3');
|
||||
const panel1 = gridview.getPanel('panel_1')!;
|
||||
const panel2 = gridview.getPanel('panel_2')!;
|
||||
const panel3 = gridview.getPanel('panel_3')!;
|
||||
|
||||
expect(panel1?.api.isVisible).toBeTruthy();
|
||||
expect(panel1?.api.id).toBe('panel_1');
|
||||
@ -330,7 +333,7 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel_1');
|
||||
const panel1 = gridview.getPanel('panel_1')!;
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
@ -349,7 +352,7 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = gridview.getPanel('panel_2');
|
||||
const panel2 = gridview.getPanel('panel_2')!;
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
@ -368,7 +371,7 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel3 = gridview.getPanel('panel_3');
|
||||
const panel3 = gridview.getPanel('panel_3')!;
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
@ -1685,8 +1688,8 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel2 = gridview.getPanel('panel2');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
const panel2 = gridview.getPanel('panel2')!;
|
||||
|
||||
const panel1Spy = jest.spyOn(panel1, 'dispose');
|
||||
const panel2Spy = jest.spyOn(panel2, 'dispose');
|
||||
@ -1714,8 +1717,8 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel2 = gridview.getPanel('panel2');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
const panel2 = gridview.getPanel('panel2')!;
|
||||
|
||||
const panel1Spy = jest.spyOn(panel1, 'dispose');
|
||||
const panel2Spy = jest.spyOn(panel2, 'dispose');
|
||||
@ -1743,8 +1746,8 @@ describe('gridview', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel1 = gridview.getPanel('panel1');
|
||||
const panel2 = gridview.getPanel('panel2');
|
||||
const panel1 = gridview.getPanel('panel1')!;
|
||||
const panel2 = gridview.getPanel('panel2')!;
|
||||
|
||||
const panel1Spy = jest.spyOn(panel1, 'dispose');
|
||||
const panel2Spy = jest.spyOn(panel2, 'dispose');
|
||||
|
@ -4,7 +4,11 @@ import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
describe('gridviewPanel', () => {
|
||||
test('get panel', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return {} as any;
|
||||
return {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
} as any;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import {
|
||||
GroupviewPanelState,
|
||||
IDockviewPanel,
|
||||
IGroupPanelInitParameters,
|
||||
} from '../../groupview/groupPanel';
|
||||
} from '../../groupview/types';
|
||||
import {
|
||||
GroupPanelPartInitParameters,
|
||||
IContentRenderer,
|
||||
@ -12,7 +11,6 @@ import {
|
||||
} from '../../groupview/types';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { GroupOptions, Groupview } from '../../groupview/groupview';
|
||||
import { DockviewPanelApi } from '../../api/groupPanelApi';
|
||||
import {
|
||||
DefaultGroupPanelView,
|
||||
IGroupPanelView,
|
||||
@ -21,6 +19,8 @@ import { GroupPanel } from '../../groupview/groupviewPanel';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
import { DockviewPanelApi } from '../../api/dockviewPanelApi';
|
||||
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
|
||||
enum GroupChangeKind2 {
|
||||
ADD_PANEL,
|
||||
@ -225,6 +225,8 @@ describe('groupview', () => {
|
||||
id: 'dockview-1',
|
||||
removePanel: removePanelMock,
|
||||
removeGroup: removeGroupMock,
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
|
||||
options = {
|
||||
@ -616,6 +618,8 @@ describe('groupview', () => {
|
||||
showDndOverlay: jest.fn(),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
@ -671,6 +675,8 @@ describe('groupview', () => {
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
@ -724,7 +730,7 @@ describe('groupview', () => {
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that should allow drop when not dropping on self for same component id', () => {
|
||||
test('that should not allow drop when dropping on self for same component id', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
@ -733,6 +739,8 @@ describe('groupview', () => {
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
@ -784,7 +792,7 @@ describe('groupview', () => {
|
||||
|
||||
expect(
|
||||
element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(1);
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('that should not allow drop when not dropping for different component id', () => {
|
||||
@ -796,6 +804,8 @@ describe('groupview', () => {
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
};
|
||||
});
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
|
@ -8,8 +8,8 @@ import {
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
import { PanelUpdateEvent } from '../../../panel/types';
|
||||
import { IGroupPanelView } from '../../../dockview/defaultGroupPanelView';
|
||||
import { IDockviewPanel } from '../../../groupview/groupPanel';
|
||||
import { GroupPanel } from '../../../groupview/groupviewPanel';
|
||||
import { IDockviewPanel } from '../../../dockview/dockviewPanel';
|
||||
|
||||
class TestContentRenderer
|
||||
extends CompositeDisposable
|
||||
|
@ -12,7 +12,11 @@ import { TestPanel } from '../groupview.spec';
|
||||
describe('tabsContainer', () => {
|
||||
test('that an external event does not render a drop target and calls through to the group mode', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {};
|
||||
return {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
return {
|
||||
@ -31,7 +35,7 @@ describe('tabsContainer', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel, {});
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('void-container')
|
||||
@ -62,6 +66,9 @@ describe('tabsContainer', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
@ -76,13 +83,14 @@ describe('tabsContainer', () => {
|
||||
return {
|
||||
id: 'testgroupid',
|
||||
model: groupView,
|
||||
panels: [],
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel, {});
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const emptySpace = cut.element
|
||||
.getElementsByClassName('void-container')
|
||||
@ -120,10 +128,13 @@ describe('tabsContainer', () => {
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that dropping the last tab should render no drop target', () => {
|
||||
test('that dropping over the empty space should render a drop target', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
@ -138,13 +149,14 @@ describe('tabsContainer', () => {
|
||||
return {
|
||||
id: 'testgroupid',
|
||||
model: groupView,
|
||||
panels: [],
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel, {});
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
@ -176,13 +188,16 @@ describe('tabsContainer', () => {
|
||||
|
||||
expect(
|
||||
cut.element.getElementsByClassName('drop-target-dropzone').length
|
||||
).toBe(0);
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
test('that dropping the first tab should render a drop target', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
@ -197,13 +212,14 @@ describe('tabsContainer', () => {
|
||||
return {
|
||||
id: 'testgroupid',
|
||||
model: groupView,
|
||||
panels: [],
|
||||
};
|
||||
});
|
||||
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel, {});
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
@ -242,6 +258,9 @@ describe('tabsContainer', () => {
|
||||
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
|
||||
return {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
|
||||
@ -262,7 +281,7 @@ describe('tabsContainer', () => {
|
||||
const accessor = new accessorMock() as DockviewComponent;
|
||||
const groupPanel = new groupPanelMock() as GroupPanel;
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel, {});
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
SerializedGridview,
|
||||
} from '../gridview/gridviewComponent';
|
||||
import { IGridviewPanel } from '../gridview/gridviewPanel';
|
||||
import { IDockviewPanel } from '../groupview/groupPanel';
|
||||
|
||||
import {
|
||||
AddPaneviewComponentOptions,
|
||||
SerializedPaneview,
|
||||
@ -33,6 +33,7 @@ import { ISplitviewPanel } from '../splitview/splitviewPanel';
|
||||
import { GroupPanel, IGroupviewPanel } from '../groupview/groupviewPanel';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { PaneviewDropEvent } from '../react';
|
||||
import { IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
|
||||
export interface CommonApi<T = any> {
|
||||
readonly height: number;
|
||||
@ -325,6 +326,10 @@ export class GridviewApi implements CommonApi<SerializedGridview> {
|
||||
}
|
||||
|
||||
export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
get id(): string {
|
||||
return this.component.id;
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return this.component.width;
|
||||
}
|
||||
@ -435,8 +440,8 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
return this.component.addPanel(options);
|
||||
}
|
||||
|
||||
addEmptyGroup(options?: AddGroupOptions): void {
|
||||
this.component.addEmptyGroup(options);
|
||||
addGroup(options?: AddGroupOptions): IGroupviewPanel {
|
||||
return this.component.addGroup(options);
|
||||
}
|
||||
|
||||
moveToNext(options?: MovementOptions): void {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Emitter, Event } from '../events';
|
||||
import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi';
|
||||
import { IDockviewPanel } from '../groupview/groupPanel';
|
||||
import { GroupPanel } from '../groupview/groupviewPanel';
|
||||
import { MutableDisposable } from '../lifecycle';
|
||||
import { IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
|
||||
export interface TitleEvent {
|
||||
readonly title: string;
|
||||
@ -37,13 +37,13 @@ export class DockviewPanelApiImpl
|
||||
private readonly _onDidGroupChange = new Emitter<void>();
|
||||
readonly onDidGroupChange = this._onDidGroupChange.event;
|
||||
|
||||
private disposable = new MutableDisposable();
|
||||
private readonly disposable = new MutableDisposable();
|
||||
|
||||
get title() {
|
||||
get title(): string {
|
||||
return this.panel.title;
|
||||
}
|
||||
|
||||
get isGroupActive() {
|
||||
get isGroupActive(): boolean {
|
||||
return !!this.group?.isActive;
|
||||
}
|
||||
|
||||
@ -71,6 +71,9 @@ export class DockviewPanelApiImpl
|
||||
|
||||
constructor(private panel: IDockviewPanel, group: GroupPanel) {
|
||||
super(panel.id);
|
||||
|
||||
this.initialize(panel);
|
||||
|
||||
this._group = group;
|
||||
|
||||
this.addDisposables(
|
||||
@ -81,14 +84,11 @@ export class DockviewPanelApiImpl
|
||||
);
|
||||
}
|
||||
|
||||
public setTitle(title: string) {
|
||||
this._onDidTitleChange.fire({ title });
|
||||
public setTitle(title: string): void {
|
||||
this.panel.update({ params: { title } });
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
if (!this.group) {
|
||||
throw new Error(`panel ${this.id} has no group`);
|
||||
}
|
||||
return this.group.model.closePanel(this.panel);
|
||||
this.group.model.closePanel(this.panel);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IPanel } from '../panel/types';
|
||||
import { FunctionOrValue } from '../types';
|
||||
import { PanelApiImpl, PanelApi } from './panelApi';
|
||||
|
||||
@ -31,24 +32,21 @@ export class GridviewPanelApiImpl
|
||||
extends PanelApiImpl
|
||||
implements GridviewPanelApi
|
||||
{
|
||||
readonly _onDidConstraintsChangeInternal =
|
||||
private readonly _onDidConstraintsChangeInternal =
|
||||
new Emitter<GridConstraintChangeEvent2>();
|
||||
readonly onDidConstraintsChangeInternal: Event<GridConstraintChangeEvent2> =
|
||||
this._onDidConstraintsChangeInternal.event;
|
||||
//
|
||||
|
||||
readonly _onDidConstraintsChange = new Emitter<GridConstraintChangeEvent>({
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidConstraintsChange: Event<GridConstraintChangeEvent> =
|
||||
this._onDidConstraintsChange.event;
|
||||
//
|
||||
|
||||
readonly _onDidSizeChange = new Emitter<SizeEvent>();
|
||||
private readonly _onDidSizeChange = new Emitter<SizeEvent>();
|
||||
readonly onDidSizeChange: Event<SizeEvent> = this._onDidSizeChange.event;
|
||||
//
|
||||
|
||||
constructor(id: string) {
|
||||
constructor(id: string, panel?: IPanel) {
|
||||
super(id);
|
||||
|
||||
this.addDisposables(
|
||||
@ -56,13 +54,17 @@ export class GridviewPanelApiImpl
|
||||
this._onDidConstraintsChange,
|
||||
this._onDidSizeChange
|
||||
);
|
||||
|
||||
if (panel) {
|
||||
this.initialize(panel);
|
||||
}
|
||||
}
|
||||
|
||||
public setConstraints(value: GridConstraintChangeEvent) {
|
||||
public setConstraints(value: GridConstraintChangeEvent): void {
|
||||
this._onDidConstraintsChangeInternal.fire(value);
|
||||
}
|
||||
|
||||
public setSize(event: SizeEvent) {
|
||||
public setSize(event: SizeEvent): void {
|
||||
this._onDidSizeChange.fire(event);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Emitter, Event } from '../events';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { IPanel, Parameters } from '../panel/types';
|
||||
|
||||
export interface FocusEvent {
|
||||
readonly isFocused: boolean;
|
||||
@ -25,6 +26,7 @@ export interface PanelApi {
|
||||
readonly onDidActiveChange: Event<ActiveEvent>;
|
||||
setVisible(isVisible: boolean): void;
|
||||
setActive(): void;
|
||||
updateParameters(parameters: Parameters): void;
|
||||
/**
|
||||
* The id of the panel that would have been assigned when the panel was created
|
||||
*/
|
||||
@ -61,6 +63,8 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
private _width = 0;
|
||||
private _height = 0;
|
||||
|
||||
private readonly panelUpdatesDisposable = new MutableDisposable();
|
||||
|
||||
readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>({
|
||||
replay: true,
|
||||
});
|
||||
@ -94,6 +98,10 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
readonly _onActiveChange = new Emitter<void>();
|
||||
readonly onActiveChange: Event<void> = this._onActiveChange.event;
|
||||
//
|
||||
readonly _onUpdateParameters = new Emitter<Parameters>();
|
||||
readonly onUpdateParameters: Event<Parameters> =
|
||||
this._onUpdateParameters.event;
|
||||
//
|
||||
|
||||
get isFocused() {
|
||||
return this._isFocused;
|
||||
@ -118,6 +126,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
super();
|
||||
|
||||
this.addDisposables(
|
||||
this.panelUpdatesDisposable,
|
||||
this._onDidDimensionChange,
|
||||
this._onDidChangeFocus,
|
||||
this._onDidVisibilityChange,
|
||||
@ -125,6 +134,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
this._onFocusEvent,
|
||||
this._onActiveChange,
|
||||
this._onVisibilityChange,
|
||||
this._onUpdateParameters,
|
||||
this.onDidFocusChange((event) => {
|
||||
this._isFocused = event.isFocused;
|
||||
}),
|
||||
@ -141,6 +151,18 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
);
|
||||
}
|
||||
|
||||
public initialize(panel: IPanel): void {
|
||||
this.panelUpdatesDisposable.value = this._onUpdateParameters.event(
|
||||
(parameters) => {
|
||||
panel.update({
|
||||
params: {
|
||||
params: parameters,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
setVisible(isVisible: boolean) {
|
||||
this._onVisibilityChange.fire({ isVisible });
|
||||
}
|
||||
@ -149,6 +171,10 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
this._onActiveChange.fire();
|
||||
}
|
||||
|
||||
updateParameters(parameters: Parameters): void {
|
||||
this._onUpdateParameters.fire(parameters);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -21,11 +21,17 @@ export { PaneviewComponentOptions } from './paneview/options';
|
||||
export * from './gridview/gridviewPanel';
|
||||
export * from './splitview/splitviewPanel';
|
||||
export * from './paneview/paneviewPanel';
|
||||
export * from './groupview/groupPanel';
|
||||
export * from './groupview/types';
|
||||
|
||||
export * from './react'; // TODO: should be conditional on whether user wants the React wrappers
|
||||
|
||||
export { Event } from './events';
|
||||
export { IDisposable } from './lifecycle';
|
||||
export { Position, Droptarget } from './dnd/droptarget';
|
||||
export {
|
||||
Position,
|
||||
positionToDirection,
|
||||
directionToPosition,
|
||||
} from './dnd/droptarget';
|
||||
export {
|
||||
FocusEvent,
|
||||
PanelDimensionChangeEvent,
|
||||
@ -38,7 +44,7 @@ export {
|
||||
GridviewPanelApi,
|
||||
GridConstraintChangeEvent,
|
||||
} from './api/gridviewPanelApi';
|
||||
export { TitleEvent, DockviewPanelApi } from './api/groupPanelApi';
|
||||
export { TitleEvent, DockviewPanelApi } from './api/dockviewPanelApi';
|
||||
export {
|
||||
PanelSizeEvent,
|
||||
PanelConstraintChangeEvent,
|
||||
|
@ -14,12 +14,12 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
|
||||
private iframes: HTMLElement[] = [];
|
||||
|
||||
constructor(private readonly el: HTMLElement) {
|
||||
constructor(protected readonly el: HTMLElement) {
|
||||
super();
|
||||
this.configure();
|
||||
}
|
||||
|
||||
abstract getData(): IDisposable;
|
||||
abstract getData(dataTransfer?: DataTransfer | null): IDisposable;
|
||||
|
||||
private configure() {
|
||||
this.addDisposables(
|
||||
@ -34,10 +34,10 @@ export abstract class DragHandler extends CompositeDisposable {
|
||||
iframe.style.pointerEvents = 'none';
|
||||
}
|
||||
|
||||
this.el.classList.add('dragged');
|
||||
setTimeout(() => this.el.classList.remove('dragged'), 0);
|
||||
this.el.classList.add('dv-dragged');
|
||||
setTimeout(() => this.el.classList.remove('dv-dragged'), 0);
|
||||
|
||||
this.disposable.value = this.getData();
|
||||
this.disposable.value = this.getData(event.dataTransfer);
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
|
@ -8,7 +8,7 @@ export class PanelTransfer extends TransferObject {
|
||||
constructor(
|
||||
public readonly viewId: string,
|
||||
public readonly groupId: string,
|
||||
public readonly panelId: string
|
||||
public readonly panelId: string | null
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
@ -6,16 +6,11 @@ export interface IDragAndDropObserverCallbacks {
|
||||
onDragLeave: (e: DragEvent) => void;
|
||||
onDrop: (e: DragEvent) => void;
|
||||
onDragEnd: (e: DragEvent) => void;
|
||||
|
||||
onDragOver?: (e: DragEvent) => void;
|
||||
}
|
||||
|
||||
export class DragAndDropObserver extends CompositeDisposable {
|
||||
// A helper to fix issues with repeated DRAG_ENTER / DRAG_LEAVE
|
||||
// calls see https://github.com/microsoft/vscode/issues/14470
|
||||
// when the element has child elements where the events are fired
|
||||
// repeadedly.
|
||||
private counter = 0;
|
||||
private target: EventTarget | null = null;
|
||||
|
||||
constructor(
|
||||
private element: HTMLElement,
|
||||
@ -28,28 +23,37 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
|
||||
private registerListeners(): void {
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragenter', (e: DragEvent) => {
|
||||
this.counter++;
|
||||
|
||||
this.callbacks.onDragEnter(e);
|
||||
})
|
||||
addDisposableListener(
|
||||
this.element,
|
||||
'dragenter',
|
||||
(e: DragEvent) => {
|
||||
this.target = e.target;
|
||||
this.callbacks.onDragEnter(e);
|
||||
},
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragover', (e: DragEvent) => {
|
||||
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||
addDisposableListener(
|
||||
this.element,
|
||||
'dragover',
|
||||
(e: DragEvent) => {
|
||||
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||
|
||||
if (this.callbacks.onDragOver) {
|
||||
this.callbacks.onDragOver(e);
|
||||
}
|
||||
})
|
||||
if (this.callbacks.onDragOver) {
|
||||
this.callbacks.onDragOver(e);
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragleave', (e: DragEvent) => {
|
||||
this.counter--;
|
||||
if (this.target === e.target) {
|
||||
this.target = null;
|
||||
|
||||
if (this.counter === 0) {
|
||||
this.callbacks.onDragLeave(e);
|
||||
}
|
||||
})
|
||||
@ -57,14 +61,13 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragend', (e: DragEvent) => {
|
||||
this.counter = 0;
|
||||
this.target = null;
|
||||
this.callbacks.onDragEnd(e);
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'drop', (e: DragEvent) => {
|
||||
this.counter = 0;
|
||||
this.callbacks.onDrop(e);
|
||||
})
|
||||
);
|
||||
|
@ -19,22 +19,6 @@
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
|
||||
&.left {
|
||||
transform: translateX(-25%) scaleX(0.5)
|
||||
}
|
||||
|
||||
&.right {
|
||||
transform: translateX(25%) scaleX(0.5)
|
||||
}
|
||||
|
||||
&.top {
|
||||
transform: translateY(-25%) scaleY(0.5);
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
transform: translateY(25%) scaleY(0.5);
|
||||
}
|
||||
|
||||
&.small-top {
|
||||
border-top: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
|
@ -2,33 +2,58 @@ import { toggleClass } from '../dom';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { DragAndDropObserver } from './dnd';
|
||||
import { clamp } from '../math';
|
||||
import { Direction } from '../gridview/baseComponentGridview';
|
||||
import { isBooleanValue } from '../types';
|
||||
|
||||
export enum Position {
|
||||
Top = 'Top',
|
||||
Left = 'Left',
|
||||
Bottom = 'Bottom',
|
||||
Right = 'Right',
|
||||
Center = 'Center',
|
||||
function numberOrFallback(maybeNumber: any, fallback: number): number {
|
||||
return typeof maybeNumber === 'number' ? maybeNumber : fallback;
|
||||
}
|
||||
|
||||
export type Quadrant = 'top' | 'bottom' | 'left' | 'right';
|
||||
export function directionToPosition(direction: Direction): Position {
|
||||
switch (direction) {
|
||||
case 'above':
|
||||
return 'top';
|
||||
case 'below':
|
||||
return 'bottom';
|
||||
case 'left':
|
||||
return 'left';
|
||||
case 'right':
|
||||
return 'right';
|
||||
case 'within':
|
||||
return 'center';
|
||||
default:
|
||||
throw new Error(`invalid direction '${direction}'`);
|
||||
}
|
||||
}
|
||||
|
||||
export function positionToDirection(position: Position): Direction {
|
||||
switch (position) {
|
||||
case 'top':
|
||||
return 'above';
|
||||
case 'bottom':
|
||||
return 'below';
|
||||
case 'left':
|
||||
return 'left';
|
||||
case 'right':
|
||||
return 'right';
|
||||
case 'center':
|
||||
return 'within';
|
||||
default:
|
||||
throw new Error(`invalid position '${position}'`);
|
||||
}
|
||||
}
|
||||
|
||||
export interface DroptargetEvent {
|
||||
position: Position;
|
||||
nativeEvent: DragEvent;
|
||||
readonly position: Position;
|
||||
readonly nativeEvent: DragEvent;
|
||||
}
|
||||
|
||||
export type DropTargetDirections = 'vertical' | 'horizontal' | 'all' | 'none';
|
||||
|
||||
function isBooleanValue(
|
||||
canDisplayOverlay: CanDisplayOverlay
|
||||
): canDisplayOverlay is boolean {
|
||||
return typeof canDisplayOverlay === 'boolean';
|
||||
}
|
||||
export type Position = 'top' | 'bottom' | 'left' | 'right' | 'center';
|
||||
|
||||
export type CanDisplayOverlay =
|
||||
| boolean
|
||||
| ((dragEvent: DragEvent, state: Quadrant | null) => boolean);
|
||||
| ((dragEvent: DragEvent, state: Position) => boolean);
|
||||
|
||||
export class Droptarget extends CompositeDisposable {
|
||||
private target: HTMLElement | undefined;
|
||||
@ -38,27 +63,31 @@ export class Droptarget extends CompositeDisposable {
|
||||
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
||||
|
||||
get state() {
|
||||
get state(): Position | undefined {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
set validOverlays(value: DropTargetDirections) {
|
||||
this.options.validOverlays = value;
|
||||
}
|
||||
|
||||
set canDisplayOverlay(value: CanDisplayOverlay) {
|
||||
this.options.canDisplayOverlay = value;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly element: HTMLElement,
|
||||
private readonly options: {
|
||||
canDisplayOverlay: CanDisplayOverlay;
|
||||
validOverlays: DropTargetDirections;
|
||||
acceptedTargetZones: Position[];
|
||||
overlayModel?: {
|
||||
size?: { value: number; type: 'pixels' | 'percentage' };
|
||||
activationSize?: {
|
||||
value: number;
|
||||
type: 'pixels' | 'percentage';
|
||||
};
|
||||
};
|
||||
}
|
||||
) {
|
||||
super();
|
||||
|
||||
// use a set to take advantage of #<set>.has
|
||||
const acceptedTargetZonesSet = new Set(
|
||||
this.options.acceptedTargetZones
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
this._onDrop,
|
||||
new DragAndDropObserver(this.element, {
|
||||
@ -71,17 +100,26 @@ export class Droptarget extends CompositeDisposable {
|
||||
return; // avoid div!0
|
||||
}
|
||||
|
||||
const x = e.offsetX;
|
||||
const y = e.offsetY;
|
||||
const xp = (100 * x) / width;
|
||||
const yp = (100 * y) / height;
|
||||
const rect = (
|
||||
e.currentTarget as HTMLElement
|
||||
).getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
const quadrant = this.calculateQuadrant(
|
||||
this.options.validOverlays,
|
||||
xp,
|
||||
yp
|
||||
acceptedTargetZonesSet,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
);
|
||||
|
||||
if (quadrant === null) {
|
||||
// no drop target should be displayed
|
||||
this.removeDropTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBooleanValue(this.options.canDisplayOverlay)) {
|
||||
if (!this.options.canDisplayOverlay) {
|
||||
return;
|
||||
@ -95,14 +133,14 @@ export class Droptarget extends CompositeDisposable {
|
||||
this.target.className = 'drop-target-dropzone';
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.className = 'drop-target-selection';
|
||||
this._state = Position.Center;
|
||||
this._state = 'center';
|
||||
this.target.appendChild(this.overlay);
|
||||
|
||||
this.element.classList.add('drop-target');
|
||||
this.element.append(this.target);
|
||||
}
|
||||
|
||||
if (this.options.validOverlays === 'none') {
|
||||
if (this.options.acceptedTargetZones.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -110,10 +148,7 @@ export class Droptarget extends CompositeDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSmallX = width < 100;
|
||||
const isSmallY = height < 100;
|
||||
|
||||
this.toggleClasses(quadrant, isSmallX, isSmallY);
|
||||
this.toggleClasses(quadrant, width, height);
|
||||
|
||||
this.setState(quadrant);
|
||||
},
|
||||
@ -139,28 +174,69 @@ export class Droptarget extends CompositeDisposable {
|
||||
);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
public dispose(): void {
|
||||
this.removeDropTarget();
|
||||
}
|
||||
|
||||
private toggleClasses(
|
||||
quadrant: Quadrant | null,
|
||||
isSmallX: boolean,
|
||||
isSmallY: boolean
|
||||
) {
|
||||
quadrant: Position,
|
||||
width: number,
|
||||
height: number
|
||||
): void {
|
||||
if (!this.overlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSmallX = width < 100;
|
||||
const isSmallY = height < 100;
|
||||
|
||||
const isLeft = quadrant === 'left';
|
||||
const isRight = quadrant === 'right';
|
||||
const isTop = quadrant === 'top';
|
||||
const isBottom = quadrant === 'bottom';
|
||||
|
||||
toggleClass(this.overlay, 'right', !isSmallX && isRight);
|
||||
toggleClass(this.overlay, 'left', !isSmallX && isLeft);
|
||||
toggleClass(this.overlay, 'top', !isSmallY && isTop);
|
||||
toggleClass(this.overlay, 'bottom', !isSmallY && isBottom);
|
||||
const rightClass = !isSmallX && isRight;
|
||||
const leftClass = !isSmallX && isLeft;
|
||||
const topClass = !isSmallY && isTop;
|
||||
const bottomClass = !isSmallY && isBottom;
|
||||
|
||||
let size = 0.5;
|
||||
|
||||
if (this.options.overlayModel?.size?.type === 'percentage') {
|
||||
size = clamp(this.options.overlayModel.size.value, 0, 100) / 100;
|
||||
}
|
||||
|
||||
if (this.options.overlayModel?.size?.type === 'pixels') {
|
||||
if (rightClass || leftClass) {
|
||||
size =
|
||||
clamp(0, this.options.overlayModel.size.value, width) /
|
||||
width;
|
||||
}
|
||||
if (topClass || bottomClass) {
|
||||
size =
|
||||
clamp(0, this.options.overlayModel.size.value, height) /
|
||||
height;
|
||||
}
|
||||
}
|
||||
|
||||
const translate = (1 - size) / 2;
|
||||
const scale = size;
|
||||
|
||||
let transform: string;
|
||||
|
||||
if (rightClass) {
|
||||
transform = `translateX(${100 * translate}%) scaleX(${scale})`;
|
||||
} else if (leftClass) {
|
||||
transform = `translateX(-${100 * translate}%) scaleX(${scale})`;
|
||||
} else if (topClass) {
|
||||
transform = `translateY(-${100 * translate}%) scaleY(${scale})`;
|
||||
} else if (bottomClass) {
|
||||
transform = `translateY(${100 * translate}%) scaleY(${scale})`;
|
||||
} else {
|
||||
transform = '';
|
||||
}
|
||||
|
||||
this.overlay.style.transform = transform;
|
||||
|
||||
toggleClass(this.overlay, 'small-right', isSmallX && isRight);
|
||||
toggleClass(this.overlay, 'small-left', isSmallX && isLeft);
|
||||
@ -168,68 +244,129 @@ export class Droptarget extends CompositeDisposable {
|
||||
toggleClass(this.overlay, 'small-bottom', isSmallY && isBottom);
|
||||
}
|
||||
|
||||
private setState(quadrant: Quadrant | null) {
|
||||
private setState(quadrant: Position): void {
|
||||
switch (quadrant) {
|
||||
case 'top':
|
||||
this._state = Position.Top;
|
||||
this._state = 'top';
|
||||
break;
|
||||
case 'left':
|
||||
this._state = Position.Left;
|
||||
this._state = 'left';
|
||||
break;
|
||||
case 'bottom':
|
||||
this._state = Position.Bottom;
|
||||
this._state = 'bottom';
|
||||
break;
|
||||
case 'right':
|
||||
this._state = Position.Right;
|
||||
this._state = 'right';
|
||||
break;
|
||||
default:
|
||||
this._state = Position.Center;
|
||||
case 'center':
|
||||
this._state = 'center';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private calculateQuadrant(
|
||||
overlayType: DropTargetDirections,
|
||||
xp: number,
|
||||
yp: number
|
||||
): Quadrant | null {
|
||||
switch (overlayType) {
|
||||
case 'all':
|
||||
if (xp < 20) {
|
||||
return 'left';
|
||||
}
|
||||
if (xp > 80) {
|
||||
return 'right';
|
||||
}
|
||||
if (yp < 20) {
|
||||
return 'top';
|
||||
}
|
||||
if (yp > 80) {
|
||||
return 'bottom';
|
||||
}
|
||||
break;
|
||||
case 'vertical':
|
||||
if (yp < 50) {
|
||||
return 'top';
|
||||
}
|
||||
return 'bottom';
|
||||
overlayType: Set<Position>,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
): Position | null {
|
||||
const isPercentage =
|
||||
this.options.overlayModel?.activationSize === undefined ||
|
||||
this.options.overlayModel?.activationSize?.type === 'percentage';
|
||||
|
||||
case 'horizontal':
|
||||
if (xp < 50) {
|
||||
return 'left';
|
||||
}
|
||||
return 'right';
|
||||
const value = numberOrFallback(
|
||||
this.options?.overlayModel?.activationSize?.value,
|
||||
20
|
||||
);
|
||||
|
||||
if (isPercentage) {
|
||||
return calculateQuadrantAsPercentage(
|
||||
overlayType,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
return calculateQuadrantAsPixels(
|
||||
overlayType,
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
private removeDropTarget() {
|
||||
private removeDropTarget(): void {
|
||||
if (this.target) {
|
||||
this._state = undefined;
|
||||
this.element.removeChild(this.target);
|
||||
this.target = undefined;
|
||||
this.overlay = undefined;
|
||||
this.element.classList.remove('drop-target');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateQuadrantAsPercentage(
|
||||
overlayType: Set<Position>,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
threshold: number
|
||||
): Position | null {
|
||||
const xp = (100 * x) / width;
|
||||
const yp = (100 * y) / height;
|
||||
|
||||
if (overlayType.has('left') && xp < threshold) {
|
||||
return 'left';
|
||||
}
|
||||
if (overlayType.has('right') && xp > 100 - threshold) {
|
||||
return 'right';
|
||||
}
|
||||
if (overlayType.has('top') && yp < threshold) {
|
||||
return 'top';
|
||||
}
|
||||
if (overlayType.has('bottom') && yp > 100 - threshold) {
|
||||
return 'bottom';
|
||||
}
|
||||
|
||||
if (!overlayType.has('center')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'center';
|
||||
}
|
||||
|
||||
export function calculateQuadrantAsPixels(
|
||||
overlayType: Set<Position>,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
threshold: number
|
||||
): Position | null {
|
||||
if (overlayType.has('left') && x < threshold) {
|
||||
return 'left';
|
||||
}
|
||||
if (overlayType.has('right') && x > width - threshold) {
|
||||
return 'right';
|
||||
}
|
||||
if (overlayType.has('top') && y < threshold) {
|
||||
return 'top';
|
||||
}
|
||||
if (overlayType.has('bottom') && y > height - threshold) {
|
||||
return 'bottom';
|
||||
}
|
||||
|
||||
if (!overlayType.has('center')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'center';
|
||||
}
|
||||
|
16
packages/dockview/src/dnd/ghost.ts
Normal file
16
packages/dockview/src/dnd/ghost.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { addClasses } from '../dom';
|
||||
|
||||
export function addGhostImage(
|
||||
dataTransfer: DataTransfer,
|
||||
ghostElement: HTMLElement
|
||||
) {
|
||||
// class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues
|
||||
addClasses(ghostElement, 'dv-dragged');
|
||||
|
||||
document.body.appendChild(ghostElement);
|
||||
dataTransfer.setDragImage(ghostElement, 0, 0);
|
||||
|
||||
setTimeout(() => {
|
||||
ghostElement.remove();
|
||||
}, 0);
|
||||
}
|
60
packages/dockview/src/dnd/groupDragHandler.ts
Normal file
60
packages/dockview/src/dnd/groupDragHandler.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { GroupPanel } from '../groupview/groupviewPanel';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { DragHandler } from './abstractDragHandler';
|
||||
import { LocalSelectionTransfer, PanelTransfer } from './dataTransfer';
|
||||
import { addGhostImage } from './ghost';
|
||||
|
||||
export class GroupDragHandler extends DragHandler {
|
||||
private readonly panelTransfer =
|
||||
LocalSelectionTransfer.getInstance<PanelTransfer>();
|
||||
|
||||
constructor(
|
||||
element: HTMLElement,
|
||||
private readonly accessorId: string,
|
||||
private readonly group: GroupPanel
|
||||
) {
|
||||
super(element);
|
||||
}
|
||||
|
||||
getData(dataTransfer: DataTransfer | null): IDisposable {
|
||||
this.panelTransfer.setData(
|
||||
[new PanelTransfer(this.accessorId, this.group.id, null)],
|
||||
PanelTransfer.prototype
|
||||
);
|
||||
|
||||
const style = window.getComputedStyle(this.el);
|
||||
|
||||
const bgColor = style.getPropertyValue(
|
||||
'--dv-activegroup-visiblepanel-tab-background-color'
|
||||
);
|
||||
const color = style.getPropertyValue(
|
||||
'--dv-activegroup-visiblepanel-tab-color'
|
||||
);
|
||||
|
||||
if (dataTransfer) {
|
||||
const ghostElement = document.createElement('div');
|
||||
|
||||
ghostElement.style.backgroundColor = bgColor;
|
||||
ghostElement.style.color = color;
|
||||
ghostElement.style.padding = '2px 8px';
|
||||
ghostElement.style.height = '24px';
|
||||
ghostElement.style.fontSize = '11px';
|
||||
ghostElement.style.lineHeight = '20px';
|
||||
ghostElement.style.borderRadius = '12px';
|
||||
ghostElement.style.position = 'absolute';
|
||||
ghostElement.textContent = `Multiple Panels (${this.group.size})`;
|
||||
|
||||
addGhostImage(dataTransfer, ghostElement);
|
||||
}
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
this.panelTransfer.clearData(PanelTransfer.prototype);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
//
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
.dragged {
|
||||
.dv-dragged {
|
||||
transform: translate3d(
|
||||
0px,
|
||||
0px,
|
||||
@ -9,7 +9,7 @@
|
||||
.tab {
|
||||
flex-shrink: 0;
|
||||
|
||||
&.dragging {
|
||||
&.dv-tab-dragging {
|
||||
.tab-action {
|
||||
background-color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||
}
|
||||
|
@ -79,7 +79,8 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
|
||||
public init(params: GroupPanelPartInitParameters) {
|
||||
this.params = params;
|
||||
this._content.textContent = params.title;
|
||||
this._content.textContent =
|
||||
typeof params.title === 'string' ? params.title : this.id;
|
||||
|
||||
addDisposableListener(this.action, 'click', (ev) => {
|
||||
ev.preventDefault(); //
|
||||
@ -106,7 +107,10 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
|
||||
private render() {
|
||||
if (this._content.textContent !== this.params.title) {
|
||||
this._content.textContent = this.params.title;
|
||||
this._content.textContent =
|
||||
typeof this.params.title === 'string'
|
||||
? this.params.title
|
||||
: this.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
} from '../groupview/types';
|
||||
import { GroupPanel } from '../groupview/groupviewPanel';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { GroupPanelUpdateEvent } from '../groupview/groupPanel';
|
||||
import { GroupPanelUpdateEvent } from '../groupview/types';
|
||||
|
||||
export interface IGroupPanelView extends IDisposable {
|
||||
readonly content: IContentRenderer;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { GroupviewPanelState, IDockviewPanel } from '../groupview/groupPanel';
|
||||
import { GroupviewPanelState } from '../groupview/types';
|
||||
import { GroupPanel } from '../groupview/groupviewPanel';
|
||||
import { IDockviewPanel } from './dockviewPanel';
|
||||
|
||||
export interface IPanelDeserializer {
|
||||
fromJSON(panelData: GroupviewPanelState, group: GroupPanel): IDockviewPanel;
|
||||
|
@ -1,13 +1,14 @@
|
||||
.custom-dragging {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
font-size: 11px;
|
||||
width: 100px;
|
||||
background-color: dodgerblue;
|
||||
color: ghostwhite;
|
||||
border-radius: 11px;
|
||||
.dv-dockview {
|
||||
position: relative;
|
||||
background-color: var(--dv-group-view-background-color);
|
||||
|
||||
.dv-watermark-container {
|
||||
position: absolute;
|
||||
padding-left: 10px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.groupview {
|
||||
@ -50,7 +51,7 @@
|
||||
* therefore we also set some stylings for the dragging event
|
||||
**/
|
||||
.tab {
|
||||
&.dragging {
|
||||
&.dv-tab-dragging {
|
||||
background-color: var(
|
||||
--dv-activegroup-visiblepanel-tab-background-color
|
||||
);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,23 +1,35 @@
|
||||
import { DockviewApi } from '../api/component.api';
|
||||
import { DockviewPanelApiImpl } from '../api/groupPanelApi';
|
||||
import {
|
||||
DockviewPanelApi,
|
||||
DockviewPanelApiImpl,
|
||||
} from '../api/dockviewPanelApi';
|
||||
import {
|
||||
GroupPanelUpdateEvent,
|
||||
GroupviewPanelState,
|
||||
IDockviewPanel,
|
||||
IGroupPanelInitParameters,
|
||||
} from '../groupview/groupPanel';
|
||||
} from '../groupview/types';
|
||||
import { GroupPanel } from '../groupview/groupviewPanel';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { Parameters } from '../panel/types';
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
import { IPanel, Parameters } from '../panel/types';
|
||||
import { IGroupPanelView } from './defaultGroupPanelView';
|
||||
import { DockviewComponent } from './dockviewComponent';
|
||||
|
||||
export class DockviewGroupPanel
|
||||
export interface IDockviewPanel extends IDisposable, IPanel {
|
||||
readonly view?: IGroupPanelView;
|
||||
readonly group: GroupPanel;
|
||||
readonly api: DockviewPanelApi;
|
||||
readonly title: string;
|
||||
readonly params: Record<string, any> | undefined;
|
||||
updateParentGroup(group: GroupPanel, isGroupActive: boolean): void;
|
||||
init(params: IGroupPanelInitParameters): void;
|
||||
toJSON(): GroupviewPanelState;
|
||||
update(event: GroupPanelUpdateEvent): void;
|
||||
}
|
||||
|
||||
export class DockviewPanel
|
||||
extends CompositeDisposable
|
||||
implements IDockviewPanel
|
||||
{
|
||||
private readonly mutableDisposable = new MutableDisposable();
|
||||
|
||||
readonly api: DockviewPanelApiImpl;
|
||||
private _group: GroupPanel;
|
||||
private _params?: Parameters;
|
||||
@ -26,11 +38,11 @@ export class DockviewGroupPanel
|
||||
|
||||
private _title: string;
|
||||
|
||||
get params() {
|
||||
get params(): Parameters | undefined {
|
||||
return this._params;
|
||||
}
|
||||
|
||||
get title() {
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
@ -38,7 +50,7 @@ export class DockviewGroupPanel
|
||||
return this._group;
|
||||
}
|
||||
|
||||
get view() {
|
||||
get view(): IGroupPanelView | undefined {
|
||||
return this._view;
|
||||
}
|
||||
|
||||
@ -57,6 +69,11 @@ export class DockviewGroupPanel
|
||||
this.addDisposables(
|
||||
this.api.onActiveChange(() => {
|
||||
accessor.setActivePanel(this);
|
||||
}),
|
||||
this.api.onDidSizeChange((event) => {
|
||||
// forward the resize event to the group since if you want to resize a panel
|
||||
// you are actually just resizing the panels parent which is the group
|
||||
this.group.api.setSize(event);
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -65,7 +82,9 @@ export class DockviewGroupPanel
|
||||
this._params = params.params;
|
||||
this._view = params.view;
|
||||
|
||||
this.setTitle(params.title);
|
||||
if (typeof params.title === 'string') {
|
||||
this.setTitle(params.title);
|
||||
}
|
||||
|
||||
this.view?.init({
|
||||
...params,
|
||||
@ -74,7 +93,7 @@ export class DockviewGroupPanel
|
||||
});
|
||||
}
|
||||
|
||||
focus() {
|
||||
focus(): void {
|
||||
this.api._onFocusEvent.fire();
|
||||
}
|
||||
|
||||
@ -90,7 +109,7 @@ export class DockviewGroupPanel
|
||||
};
|
||||
}
|
||||
|
||||
setTitle(title: string) {
|
||||
setTitle(title: string): void {
|
||||
const didTitleChange = title !== this._params?.title;
|
||||
|
||||
if (didTitleChange) {
|
||||
@ -129,7 +148,7 @@ export class DockviewGroupPanel
|
||||
});
|
||||
}
|
||||
|
||||
public updateParentGroup(group: GroupPanel, isGroupActive: boolean) {
|
||||
public updateParentGroup(group: GroupPanel, isGroupActive: boolean): void {
|
||||
this._group = group;
|
||||
this.api.group = group;
|
||||
|
||||
@ -148,7 +167,7 @@ export class DockviewGroupPanel
|
||||
);
|
||||
}
|
||||
|
||||
public layout(width: number, height: number) {
|
||||
public layout(width: number, height: number): void {
|
||||
// the obtain the correct dimensions of the content panel we must deduct the tab height
|
||||
this.api._onDidDimensionChange.fire({
|
||||
width,
|
||||
@ -158,9 +177,8 @@ export class DockviewGroupPanel
|
||||
this.view?.layout(width, height);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
public dispose(): void {
|
||||
this.api.dispose();
|
||||
this.mutableDisposable.dispose();
|
||||
|
||||
this.view?.dispose();
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { DockviewApi } from '../api/component.api';
|
||||
import { Direction } from '../gridview/baseComponentGridview';
|
||||
import { IGridView } from '../gridview/gridview';
|
||||
import { IDockviewPanel } from '../groupview/groupPanel';
|
||||
import {
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
@ -14,6 +13,8 @@ import { FrameworkFactory } from '../types';
|
||||
import { DockviewDropTargets } from '../groupview/dnd';
|
||||
import { PanelTransfer } from '../dnd/dataTransfer';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { IDockviewPanel } from './dockviewPanel';
|
||||
|
||||
export interface IGroupControlRenderer extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
@ -59,7 +60,8 @@ export interface ViewFactoryData {
|
||||
export interface DockviewDndOverlayEvent {
|
||||
nativeEvent: DragEvent;
|
||||
target: DockviewDropTargets;
|
||||
group: GroupPanel;
|
||||
position: Position;
|
||||
group?: GroupPanel;
|
||||
getData: () => PanelTransfer | undefined;
|
||||
}
|
||||
|
||||
@ -73,6 +75,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
|
||||
defaultTabComponent?: string;
|
||||
showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean;
|
||||
createGroupControlElement?: (group: GroupPanel) => IGroupControlRenderer;
|
||||
singleTabMode?: 'fullwidth' | 'default';
|
||||
}
|
||||
|
||||
export interface PanelOptions {
|
||||
@ -83,19 +86,81 @@ export interface PanelOptions {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
type RelativePanel = {
|
||||
direction?: Direction;
|
||||
referencePanel: string | IDockviewPanel;
|
||||
};
|
||||
|
||||
type RelativeGroup = {
|
||||
direction?: Direction;
|
||||
referenceGroup: string | GroupPanel;
|
||||
};
|
||||
|
||||
type AbsolutePosition = {
|
||||
direction: Omit<Direction, 'within'>;
|
||||
};
|
||||
|
||||
export type AddPanelPositionOptions =
|
||||
| RelativePanel
|
||||
| RelativeGroup
|
||||
| AbsolutePosition;
|
||||
|
||||
export function isPanelOptionsWithPanel(
|
||||
data: AddPanelPositionOptions
|
||||
): data is RelativePanel {
|
||||
if ((data as RelativePanel).referencePanel) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isPanelOptionsWithGroup(
|
||||
data: AddPanelPositionOptions
|
||||
): data is RelativeGroup {
|
||||
if ((data as RelativeGroup).referenceGroup) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface AddPanelOptions
|
||||
extends Omit<PanelOptions, 'component' | 'tabComponent'> {
|
||||
component: string;
|
||||
tabComponent?: string;
|
||||
position?: {
|
||||
direction?: Direction;
|
||||
referencePanel?: string;
|
||||
};
|
||||
position?: AddPanelPositionOptions;
|
||||
}
|
||||
|
||||
export interface AddGroupOptions {
|
||||
direction?: 'left' | 'right' | 'above' | 'below';
|
||||
referencePanel: string;
|
||||
type AddGroupOptionsWithPanel = {
|
||||
referencePanel: string | IDockviewPanel;
|
||||
direction?: Omit<Direction, 'within'>;
|
||||
};
|
||||
|
||||
type AddGroupOptionsWithGroup = {
|
||||
referenceGroup: string | GroupPanel;
|
||||
direction?: Omit<Direction, 'within'>;
|
||||
};
|
||||
|
||||
export type AddGroupOptions =
|
||||
| AddGroupOptionsWithGroup
|
||||
| AddGroupOptionsWithPanel
|
||||
| AbsolutePosition;
|
||||
|
||||
export function isGroupOptionsWithPanel(
|
||||
data: AddGroupOptions
|
||||
): data is AddGroupOptionsWithPanel {
|
||||
if ((data as AddGroupOptionsWithPanel).referencePanel) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isGroupOptionsWithGroup(
|
||||
data: AddGroupOptions
|
||||
): data is AddGroupOptionsWithGroup {
|
||||
if ((data as AddGroupOptionsWithGroup).referenceGroup) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface MovementOptions2 {
|
||||
|
@ -15,19 +15,19 @@ const nextLayoutId = sequentialNumberGenerator();
|
||||
|
||||
export type Direction = 'left' | 'right' | 'above' | 'below' | 'within';
|
||||
|
||||
export function toTarget(direction: Direction) {
|
||||
export function toTarget(direction: Direction): Position {
|
||||
switch (direction) {
|
||||
case 'left':
|
||||
return Position.Left;
|
||||
return 'left';
|
||||
case 'right':
|
||||
return Position.Right;
|
||||
return 'right';
|
||||
case 'above':
|
||||
return Position.Top;
|
||||
return 'top';
|
||||
case 'below':
|
||||
return Position.Bottom;
|
||||
return 'bottom';
|
||||
case 'within':
|
||||
default:
|
||||
return Position.Center;
|
||||
return 'center';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
PanelInitParameters,
|
||||
IPanel,
|
||||
} from '../panel/types';
|
||||
import { PanelApiImpl } from '../api/panelApi';
|
||||
import { PanelApi, PanelApiImpl } from '../api/panelApi';
|
||||
|
||||
export interface BasePanelViewState {
|
||||
id: string;
|
||||
@ -14,7 +14,7 @@ export interface BasePanelViewState {
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface BasePanelViewExported<T extends PanelApiImpl> {
|
||||
export interface BasePanelViewExported<T extends PanelApi> {
|
||||
readonly id: string;
|
||||
readonly api: T;
|
||||
readonly width: number;
|
||||
|
@ -9,13 +9,13 @@ import {
|
||||
Orientation,
|
||||
Sizing,
|
||||
} from '../splitview/core/splitview';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { tail } from '../array';
|
||||
import { LeafNode } from './leafNode';
|
||||
import { BranchNode } from './branchNode';
|
||||
import { Node } from './types';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
|
||||
function findLeaf(candiateNode: Node, last: boolean): LeafNode {
|
||||
if (candiateNode instanceof LeafNode) {
|
||||
@ -132,22 +132,19 @@ export function getRelativeLocation(
|
||||
const [rest, _index] = tail(location);
|
||||
let index = _index;
|
||||
|
||||
if (direction === Position.Right || direction === Position.Bottom) {
|
||||
if (direction === 'right' || direction === 'bottom') {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return [...rest, index];
|
||||
} else {
|
||||
const index =
|
||||
direction === Position.Right || direction === Position.Bottom
|
||||
? 1
|
||||
: 0;
|
||||
const index = direction === 'right' || direction === 'bottom' ? 1 : 0;
|
||||
return [...location, index];
|
||||
}
|
||||
}
|
||||
|
||||
export function getDirectionOrientation(direction: Position): Orientation {
|
||||
return direction === Position.Top || direction === Position.Bottom
|
||||
return direction === 'top' || direction === 'bottom'
|
||||
? Orientation.VERTICAL
|
||||
: Orientation.HORIZONTAL;
|
||||
}
|
||||
@ -276,6 +273,10 @@ export class Gridview implements IDisposable {
|
||||
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
|
||||
this._onDidChange.event;
|
||||
|
||||
public get length(): number {
|
||||
return this._root ? this._root.children.length : 0;
|
||||
}
|
||||
|
||||
public serialize() {
|
||||
const root = serializeBranchNode(this.getView(), this.orientation);
|
||||
|
||||
@ -410,6 +411,43 @@ export class Gridview implements IDisposable {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the root is orientated as a VERTICAL node then nest the existing root within a new HORIZIONTAL root node
|
||||
* If the root is orientated as a HORIZONTAL node then nest the existing root within a new VERITCAL root node
|
||||
*/
|
||||
public insertOrthogonalSplitviewAtRoot(): void {
|
||||
if (!this._root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldRoot = this.root;
|
||||
oldRoot.element.remove();
|
||||
|
||||
this._root = new BranchNode(
|
||||
orthogonal(oldRoot.orientation),
|
||||
this.proportionalLayout,
|
||||
this.styles,
|
||||
this.root.orthogonalSize,
|
||||
this.root.size
|
||||
);
|
||||
|
||||
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
|
||||
oldRoot.dispose();
|
||||
this._root.addChild(childReference, Sizing.Distribute, 0);
|
||||
} else {
|
||||
this._root.addChild(oldRoot, Sizing.Distribute, 0);
|
||||
}
|
||||
|
||||
this.element.appendChild(this._root.element);
|
||||
|
||||
this.disposable.value = this._root.onDidChange((e) => {
|
||||
this._onDidChange.fire(e);
|
||||
});
|
||||
}
|
||||
|
||||
public next(location: number[]) {
|
||||
return this.progmaticSelect(location);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import {
|
||||
SerializedGridObject,
|
||||
getGridLocation,
|
||||
} from './gridview';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { tail, sequenceEquals } from '../array';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { IPanelDeserializer } from '../dockview/deserializer';
|
||||
@ -25,6 +24,7 @@ import { BaseComponentOptions } from '../panel/types';
|
||||
import { Orientation, Sizing } from '../splitview/core/splitview';
|
||||
import { createComponent } from '../panel/componentFactory';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
|
||||
export interface SerializedGridview {
|
||||
grid: {
|
||||
@ -265,7 +265,7 @@ export class GridviewComponent
|
||||
}
|
||||
|
||||
const target = toTarget(options.direction);
|
||||
if (target === Position.Center) {
|
||||
if (target === 'center') {
|
||||
throw new Error(`${target} not supported as an option`);
|
||||
} else {
|
||||
const location = getGridLocation(referenceGroup.element);
|
||||
@ -294,7 +294,7 @@ export class GridviewComponent
|
||||
}
|
||||
|
||||
const target = toTarget(options.position.direction);
|
||||
if (target === Position.Center) {
|
||||
if (target === 'center') {
|
||||
throw new Error(`${target} not supported as an option`);
|
||||
} else {
|
||||
const location = getGridLocation(referenceGroup.element);
|
||||
|
@ -9,7 +9,10 @@ import {
|
||||
BasePanelViewExported,
|
||||
BasePanelViewState,
|
||||
} from './basePanelView';
|
||||
import { GridviewPanelApiImpl } from '../api/gridviewPanelApi';
|
||||
import {
|
||||
GridviewPanelApi,
|
||||
GridviewPanelApiImpl,
|
||||
} from '../api/gridviewPanelApi';
|
||||
import { LayoutPriority } from '../splitview/core/splitview';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IViewSize } from './gridview';
|
||||
@ -26,7 +29,7 @@ export interface GridviewInitParameters extends PanelInitParameters {
|
||||
}
|
||||
|
||||
export interface IGridviewPanel
|
||||
extends BasePanelViewExported<GridviewPanelApiImpl> {
|
||||
extends BasePanelViewExported<GridviewPanelApi> {
|
||||
readonly minimumWidth: number;
|
||||
readonly maximumWidth: number;
|
||||
readonly minimumHeight: number;
|
||||
@ -123,13 +126,11 @@ export abstract class GridviewPanel
|
||||
return this.api.isActive;
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
component: string,
|
||||
api = new GridviewPanelApiImpl(id)
|
||||
) {
|
||||
constructor(id: string, component: string, api: GridviewPanelApiImpl) {
|
||||
super(id, component, api);
|
||||
|
||||
this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidChange,
|
||||
this.api.onVisibilityChange((event) => {
|
||||
|
@ -2,4 +2,5 @@ export enum DockviewDropTargets {
|
||||
Tab,
|
||||
Panel,
|
||||
TabContainer,
|
||||
Edge,
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
import { DockviewPanelApi } from '../api/groupPanelApi';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { HeaderPartInitParameters } from './types';
|
||||
import {
|
||||
IPanel,
|
||||
PanelInitParameters,
|
||||
PanelUpdateEvent,
|
||||
Parameters,
|
||||
} from '../panel/types';
|
||||
import { GroupPanel } from './groupviewPanel';
|
||||
import { IGroupPanelView } from '../dockview/defaultGroupPanelView';
|
||||
|
||||
export interface IGroupPanelInitParameters
|
||||
extends PanelInitParameters,
|
||||
HeaderPartInitParameters {
|
||||
view: IGroupPanelView;
|
||||
}
|
||||
|
||||
export type GroupPanelUpdateEvent = PanelUpdateEvent<{
|
||||
params?: Parameters;
|
||||
title?: string;
|
||||
}>;
|
||||
|
||||
export interface IDockviewPanel extends IDisposable, IPanel {
|
||||
readonly view?: IGroupPanelView;
|
||||
readonly group: GroupPanel;
|
||||
readonly api: DockviewPanelApi;
|
||||
readonly title: string;
|
||||
readonly params: Record<string, any> | undefined;
|
||||
updateParentGroup(group: GroupPanel, isGroupActive: boolean): void;
|
||||
init(params: IGroupPanelInitParameters): void;
|
||||
toJSON(): GroupviewPanelState;
|
||||
update(event: GroupPanelUpdateEvent): void;
|
||||
}
|
||||
|
||||
export interface GroupviewPanelState {
|
||||
id: string;
|
||||
view?: any;
|
||||
title: string;
|
||||
params?: { [key: string]: any };
|
||||
}
|
@ -8,12 +8,12 @@ import { IGridPanelView } from '../gridview/baseComponentGridview';
|
||||
import { IViewSize } from '../gridview/gridview';
|
||||
import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
import { PanelInitParameters, PanelUpdateEvent } from '../panel/types';
|
||||
import { IDockviewPanel } from './groupPanel';
|
||||
import { ContentContainer, IContentContainer } from './panel/content';
|
||||
import { ITabsContainer, TabsContainer } from './titlebar/tabsContainer';
|
||||
import { IWatermarkRenderer } from './types';
|
||||
import { GroupPanel } from './groupviewPanel';
|
||||
import { DockviewDropTargets } from './dnd';
|
||||
import { IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { IGroupControlRenderer } from '../core';
|
||||
|
||||
export interface DndService {
|
||||
@ -38,7 +38,7 @@ export interface IGroupItem {
|
||||
|
||||
interface GroupMoveEvent {
|
||||
groupId: string;
|
||||
itemId: string;
|
||||
itemId?: string;
|
||||
target: Position;
|
||||
index?: number;
|
||||
}
|
||||
@ -110,7 +110,11 @@ export interface IGroupview extends IDisposable, IGridPanelView {
|
||||
panel?: IDockviewPanel;
|
||||
suppressRoll?: boolean;
|
||||
}): void;
|
||||
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean;
|
||||
canDisplayOverlay(
|
||||
event: DragEvent,
|
||||
position: Position,
|
||||
target: DockviewDropTargets
|
||||
): boolean;
|
||||
}
|
||||
|
||||
export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
@ -167,6 +171,8 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
set locked(value: boolean) {
|
||||
this._locked = value;
|
||||
|
||||
toggleClass(this.container, 'locked-groupview', value);
|
||||
}
|
||||
|
||||
get isActive(): boolean {
|
||||
@ -226,35 +232,48 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
private accessor: DockviewComponent,
|
||||
public id: string,
|
||||
private readonly options: GroupOptions,
|
||||
private readonly parent: GroupPanel
|
||||
private readonly groupPanel: GroupPanel
|
||||
) {
|
||||
super();
|
||||
|
||||
this.container.classList.add('groupview');
|
||||
|
||||
this.tabsContainer = new TabsContainer(this.accessor, this.parent, {
|
||||
tabHeight: options.tabHeight,
|
||||
});
|
||||
this.tabsContainer = new TabsContainer(this.accessor, this.groupPanel);
|
||||
|
||||
this.contentContainer = new ContentContainer();
|
||||
|
||||
this.dropTarget = new Droptarget(this.contentContainer.element, {
|
||||
validOverlays: 'all',
|
||||
canDisplayOverlay: (event, quadrant) => {
|
||||
if (this.locked && !quadrant) {
|
||||
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (this.locked && position === 'center') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
if (data.groupId === this.id) {
|
||||
if (position === 'center') {
|
||||
// don't allow to drop on self for center position
|
||||
return false;
|
||||
}
|
||||
if (data.panelId === null) {
|
||||
// don't allow group move to drop anywhere on self
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const groupHasOnePanelAndIsActiveDragElement =
|
||||
this._panels.length === 1 && data.groupId === this.id;
|
||||
|
||||
return !groupHasOnePanelAndIsActiveDragElement;
|
||||
}
|
||||
|
||||
return this.canDisplayOverlay(event, DockviewDropTargets.Panel);
|
||||
return this.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
DockviewDropTargets.Panel
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@ -274,10 +293,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this._onDidRemovePanel,
|
||||
this._onDidActivePanelChange,
|
||||
this.tabsContainer.onDrop((event) => {
|
||||
this.handleDropEvent(event.event, Position.Center, event.index);
|
||||
this.handleDropEvent(event.event, 'center', event.index);
|
||||
}),
|
||||
this.contentContainer.onDidFocus(() => {
|
||||
this.accessor.doSetGroupActive(this.parent, true);
|
||||
this.accessor.doSetGroupActive(this.groupPanel, true);
|
||||
}),
|
||||
this.contentContainer.onDidBlur(() => {
|
||||
// noop
|
||||
@ -306,12 +325,12 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
if (this.accessor.options.createGroupControlElement) {
|
||||
this._control = this.accessor.options.createGroupControlElement(
|
||||
this.parent
|
||||
this.groupPanel
|
||||
);
|
||||
this.addDisposables(this._control);
|
||||
this._control.init({
|
||||
containerApi: new DockviewApi(this.accessor),
|
||||
api: this.parent.api,
|
||||
api: this.groupPanel.api,
|
||||
});
|
||||
this.tabsContainer.setActionElement(this._control.element);
|
||||
}
|
||||
@ -431,11 +450,11 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
const skipSetGroupActive = !!options.skipSetGroupActive;
|
||||
|
||||
// ensure the group is updated before we fire any events
|
||||
panel.updateParentGroup(this.parent, true);
|
||||
panel.updateParentGroup(this.groupPanel, true);
|
||||
|
||||
if (this._activePanel === panel) {
|
||||
if (!skipSetGroupActive) {
|
||||
this.accessor.doSetGroupActive(this.parent);
|
||||
this.accessor.doSetGroupActive(this.groupPanel);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -447,7 +466,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
|
||||
if (!skipSetGroupActive) {
|
||||
this.accessor.doSetGroupActive(this.parent, !!options.skipFocus);
|
||||
this.accessor.doSetGroupActive(
|
||||
this.groupPanel,
|
||||
!!options.skipFocus
|
||||
);
|
||||
}
|
||||
|
||||
this.updateContainer();
|
||||
@ -476,7 +498,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
this.doClose(panel);
|
||||
}
|
||||
} else {
|
||||
this.accessor.removeGroup(this.parent);
|
||||
this.accessor.removeGroup(this.groupPanel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -633,7 +655,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
toggleClass(this.container, 'empty', this.isEmpty);
|
||||
|
||||
this.panels.forEach((panel) =>
|
||||
panel.updateParentGroup(this.parent, this.isActive)
|
||||
panel.updateParentGroup(this.groupPanel, this.isActive)
|
||||
);
|
||||
|
||||
if (this.isEmpty && !this.watermark) {
|
||||
@ -648,14 +670,14 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
|
||||
addDisposableListener(this.watermark.element, 'click', () => {
|
||||
if (!this.isActive) {
|
||||
this.accessor.doSetGroupActive(this.parent);
|
||||
this.accessor.doSetGroupActive(this.groupPanel);
|
||||
}
|
||||
});
|
||||
|
||||
this.tabsContainer.hide();
|
||||
this.contentContainer.element.appendChild(this.watermark.element);
|
||||
|
||||
this.watermark.updateParentGroup(this.parent, true);
|
||||
this.watermark.updateParentGroup(this.groupPanel, true);
|
||||
}
|
||||
if (!this.isEmpty && this.watermark) {
|
||||
this.watermark.element.remove();
|
||||
@ -665,13 +687,18 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
}
|
||||
}
|
||||
|
||||
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean {
|
||||
canDisplayOverlay(
|
||||
event: DragEvent,
|
||||
position: Position,
|
||||
target: DockviewDropTargets
|
||||
): boolean {
|
||||
// custom overlay handler
|
||||
if (this.accessor.options.showDndOverlay) {
|
||||
return this.accessor.options.showDndOverlay({
|
||||
nativeEvent: event,
|
||||
target,
|
||||
group: this.accessor.getPanel(this.id)!,
|
||||
position,
|
||||
getData: getPanelData,
|
||||
});
|
||||
}
|
||||
@ -685,7 +712,19 @@ export class Groupview extends CompositeDisposable implements IGroupview {
|
||||
): void {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data) {
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
if (data.panelId === null) {
|
||||
// this is a group move dnd event
|
||||
const { groupId } = data;
|
||||
|
||||
this._onMove.fire({
|
||||
target: position,
|
||||
groupId: groupId,
|
||||
index,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const fromSameGroup =
|
||||
this.tabsContainer.indexOf(data.panelId) !== -1;
|
||||
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
} from '../api/gridviewPanelApi';
|
||||
import { Groupview, GroupOptions, IHeader } from './groupview';
|
||||
import { GridviewPanel, IGridviewPanel } from '../gridview/gridviewPanel';
|
||||
import { IDockviewPanel } from './groupPanel';
|
||||
import { IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
|
||||
export interface IGroupviewPanel extends IGridviewPanel {
|
||||
model: Groupview;
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
} from '../../lifecycle';
|
||||
import { Emitter, Event } from '../../events';
|
||||
import { trackFocus } from '../../dom';
|
||||
import { IDockviewPanel } from '../groupPanel';
|
||||
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
|
||||
export interface IContentContainer extends IDisposable {
|
||||
onDidFocus: Event<void>;
|
||||
|
@ -8,28 +8,16 @@ import {
|
||||
import { toggleClass } from '../dom';
|
||||
import { IDockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { ITabRenderer } from './types';
|
||||
import { IDockviewPanel } from './groupPanel';
|
||||
import { GroupPanel } from './groupviewPanel';
|
||||
import { DroptargetEvent, Droptarget } from '../dnd/droptarget';
|
||||
import { DockviewDropTargets } from './dnd';
|
||||
import { DragHandler } from '../dnd/abstractDragHandler';
|
||||
|
||||
export enum MouseEventKind {
|
||||
CLICK = 'CLICK',
|
||||
}
|
||||
|
||||
export interface LayoutMouseEvent {
|
||||
readonly kind: MouseEventKind;
|
||||
readonly event: MouseEvent;
|
||||
readonly panel?: IDockviewPanel;
|
||||
readonly tab?: boolean;
|
||||
}
|
||||
|
||||
export interface ITab {
|
||||
readonly panelId: string;
|
||||
readonly element: HTMLElement;
|
||||
setContent: (element: ITabRenderer) => void;
|
||||
onChanged: Event<LayoutMouseEvent>;
|
||||
onChanged: Event<MouseEvent>;
|
||||
onDrop: Event<DroptargetEvent>;
|
||||
setActive(isActive: boolean): void;
|
||||
}
|
||||
@ -39,13 +27,13 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
private readonly droptarget: Droptarget;
|
||||
private content?: ITabRenderer;
|
||||
|
||||
private readonly _onChanged = new Emitter<LayoutMouseEvent>();
|
||||
readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event;
|
||||
private readonly _onChanged = new Emitter<MouseEvent>();
|
||||
readonly onChanged: Event<MouseEvent> = this._onChanged.event;
|
||||
|
||||
private readonly _onDropped = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDropped.event;
|
||||
|
||||
public get element() {
|
||||
public get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
@ -104,20 +92,34 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
*/
|
||||
event.stopPropagation();
|
||||
|
||||
this._onChanged.fire({ kind: MouseEventKind.CLICK, event });
|
||||
this._onChanged.fire(event);
|
||||
})
|
||||
);
|
||||
|
||||
this.droptarget = new Droptarget(this._element, {
|
||||
validOverlays: 'none',
|
||||
canDisplayOverlay: (event) => {
|
||||
acceptedTargetZones: ['center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (this.group.locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.panelId !== data.panelId;
|
||||
}
|
||||
|
||||
return this.group.model.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
DockviewDropTargets.Tab
|
||||
);
|
||||
},
|
||||
@ -130,12 +132,12 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
);
|
||||
}
|
||||
|
||||
public setActive(isActive: boolean) {
|
||||
public setActive(isActive: boolean): void {
|
||||
toggleClass(this.element, 'active-tab', isActive);
|
||||
toggleClass(this.element, 'inactive-tab', !isActive);
|
||||
}
|
||||
|
||||
public setContent(part: ITabRenderer) {
|
||||
public setContent(part: ITabRenderer): void {
|
||||
if (this.content) {
|
||||
this._element.removeChild(this.content.element);
|
||||
}
|
||||
@ -143,7 +145,7 @@ export class Tab extends CompositeDisposable implements ITab {
|
||||
this._element.appendChild(this.content.element);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.droptarget.dispose();
|
||||
}
|
||||
|
@ -10,9 +10,24 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.dv-single-tab.dv-full-width-single-tab {
|
||||
.tabs-container {
|
||||
flex-grow: 1;
|
||||
|
||||
.tab {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.void-container {
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.void-container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
|
@ -4,14 +4,12 @@ import {
|
||||
IValueDisposable,
|
||||
} from '../../lifecycle';
|
||||
import { addDisposableListener, Emitter, Event } from '../../events';
|
||||
import { ITab, MouseEventKind, Tab } from '../tab';
|
||||
import { last } from '../../array';
|
||||
import { IDockviewPanel } from '../groupPanel';
|
||||
import { ITab, Tab } from '../tab';
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { getPanelData } from '../../dnd/dataTransfer';
|
||||
import { GroupPanel } from '../groupviewPanel';
|
||||
import { Droptarget } from '../../dnd/droptarget';
|
||||
import { DockviewDropTargets } from '../dnd';
|
||||
import { VoidContainer } from './voidContainer';
|
||||
import { toggleClass } from '../../dom';
|
||||
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
|
||||
export interface TabDropIndexEvent {
|
||||
event: DragEvent;
|
||||
@ -44,10 +42,8 @@ export class TabsContainer
|
||||
{
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly tabContainer: HTMLElement;
|
||||
private readonly voidContainer: HTMLElement;
|
||||
private readonly actionContainer: HTMLElement;
|
||||
|
||||
private readonly voidDropTarget: Droptarget;
|
||||
private readonly voidContainer: VoidContainer;
|
||||
|
||||
private tabs: IValueDisposable<ITab>[] = [];
|
||||
private selectedIndex = -1;
|
||||
@ -138,9 +134,8 @@ export class TabsContainer
|
||||
}
|
||||
|
||||
constructor(
|
||||
private accessor: DockviewComponent,
|
||||
private group: GroupPanel,
|
||||
options: { tabHeight?: number }
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly group: GroupPanel
|
||||
) {
|
||||
super();
|
||||
|
||||
@ -149,7 +144,34 @@ export class TabsContainer
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'tabs-and-actions-container';
|
||||
|
||||
this.height = options.tabHeight;
|
||||
this.height = accessor.options.tabHeight;
|
||||
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-full-width-single-tab',
|
||||
this.accessor.options.singleTabMode === 'fullwidth'
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
this.accessor.onDidAddPanel((e) => {
|
||||
if (e.api.group === this.group) {
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-single-tab',
|
||||
this.size === 1
|
||||
);
|
||||
}
|
||||
}),
|
||||
this.accessor.onDidRemovePanel((e) => {
|
||||
if (e.api.group === this.group) {
|
||||
toggleClass(
|
||||
this._element,
|
||||
'dv-single-tab',
|
||||
this.size === 1
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.actionContainer = document.createElement('div');
|
||||
this.actionContainer.className = 'action-container';
|
||||
@ -157,38 +179,20 @@ export class TabsContainer
|
||||
this.tabContainer = document.createElement('div');
|
||||
this.tabContainer.className = 'tabs-container';
|
||||
|
||||
this.voidContainer = document.createElement('div');
|
||||
this.voidContainer.className = 'void-container';
|
||||
this.voidContainer = new VoidContainer(this.accessor, this.group);
|
||||
|
||||
this._element.appendChild(this.tabContainer);
|
||||
this._element.appendChild(this.voidContainer);
|
||||
this._element.appendChild(this.voidContainer.element);
|
||||
this._element.appendChild(this.actionContainer);
|
||||
|
||||
this.voidDropTarget = new Droptarget(this.voidContainer, {
|
||||
validOverlays: 'none',
|
||||
canDisplayOverlay: (event) => {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
// don't show the overlay if the tab being dragged is the last panel of this group
|
||||
return last(this.tabs)?.value.panelId !== data.panelId;
|
||||
}
|
||||
|
||||
return group.model.canDisplayOverlay(
|
||||
event,
|
||||
DockviewDropTargets.Panel
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.addDisposables(
|
||||
this.voidDropTarget.onDrop((event) => {
|
||||
this.voidContainer,
|
||||
this.voidContainer.onDrop((event) => {
|
||||
this._onDrop.fire({
|
||||
event: event.nativeEvent,
|
||||
index: this.tabs.length,
|
||||
});
|
||||
}),
|
||||
this.voidDropTarget,
|
||||
addDisposableListener(this.tabContainer, 'mousedown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
@ -265,17 +269,15 @@ export class TabsContainer
|
||||
panel.id === this.group.model.activePanel?.id &&
|
||||
this.group.model.isContentFocused;
|
||||
|
||||
const isLeftClick = event.event.button === 0;
|
||||
const isLeftClick = event.button === 0;
|
||||
|
||||
if (!isLeftClick || event.event.defaultPrevented) {
|
||||
if (!isLeftClick || event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.kind === MouseEventKind.CLICK) {
|
||||
this.group.model.openPanel(panel, {
|
||||
skipFocus: alreadyFocused,
|
||||
});
|
||||
}
|
||||
this.group.model.openPanel(panel, {
|
||||
skipFocus: alreadyFocused,
|
||||
});
|
||||
}),
|
||||
tabToAdd.onDrop((event) => {
|
||||
this._onDrop.fire({
|
||||
|
77
packages/dockview/src/groupview/titlebar/voidContainer.ts
Normal file
77
packages/dockview/src/groupview/titlebar/voidContainer.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { last } from '../../array';
|
||||
import { getPanelData } from '../../dnd/dataTransfer';
|
||||
import { Droptarget, DroptargetEvent } from '../../dnd/droptarget';
|
||||
import { GroupDragHandler } from '../../dnd/groupDragHandler';
|
||||
import { DockviewComponent } from '../../dockview/dockviewComponent';
|
||||
import { addDisposableListener, Emitter, Event } from '../../events';
|
||||
import { CompositeDisposable } from '../../lifecycle';
|
||||
import { DockviewDropTargets } from '../dnd';
|
||||
import { GroupPanel } from '../groupviewPanel';
|
||||
|
||||
export class VoidContainer extends CompositeDisposable {
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly voidDropTarget: Droptarget;
|
||||
|
||||
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
||||
|
||||
get element() {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly group: GroupPanel
|
||||
) {
|
||||
super();
|
||||
|
||||
this._element = document.createElement('div');
|
||||
|
||||
this._element.className = 'void-container';
|
||||
this._element.tabIndex = 0;
|
||||
this._element.draggable = true;
|
||||
|
||||
this.addDisposables(
|
||||
this._onDrop,
|
||||
addDisposableListener(this._element, 'click', () => {
|
||||
this.accessor.doSetGroupActive(this.group);
|
||||
})
|
||||
);
|
||||
|
||||
const handler = new GroupDragHandler(this._element, accessor.id, group);
|
||||
|
||||
this.voidDropTarget = new Droptarget(this._element, {
|
||||
acceptedTargetZones: ['center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
const data = getPanelData();
|
||||
|
||||
if (data && this.accessor.id === data.viewId) {
|
||||
if (
|
||||
data.panelId === null &&
|
||||
data.groupId === this.group.id
|
||||
) {
|
||||
// don't allow group move to drop on self
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't show the overlay if the tab being dragged is the last panel of this group
|
||||
return last(this.group.panels)?.id !== data.panelId;
|
||||
}
|
||||
|
||||
return group.model.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
DockviewDropTargets.Panel
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.addDisposables(
|
||||
handler,
|
||||
this.voidDropTarget.onDrop((event) => {
|
||||
this._onDrop.fire(event);
|
||||
}),
|
||||
this.voidDropTarget
|
||||
);
|
||||
}
|
||||
}
|
@ -1,9 +1,15 @@
|
||||
import { IDockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { DockviewPanelApi } from '../api/groupPanelApi';
|
||||
import { PanelInitParameters, IPanel } from '../panel/types';
|
||||
import { DockviewPanelApi } from '../api/dockviewPanelApi';
|
||||
import {
|
||||
PanelInitParameters,
|
||||
IPanel,
|
||||
PanelUpdateEvent,
|
||||
Parameters,
|
||||
} from '../panel/types';
|
||||
import { DockviewApi } from '../api/component.api';
|
||||
import { GroupPanel } from './groupviewPanel';
|
||||
import { Event } from '../events';
|
||||
import { IGroupPanelView } from '../dockview/defaultGroupPanelView';
|
||||
|
||||
export interface IRenderable {
|
||||
id: string;
|
||||
@ -13,7 +19,7 @@ export interface IRenderable {
|
||||
}
|
||||
|
||||
export interface HeaderPartInitParameters {
|
||||
title: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface GroupPanelPartInitParameters
|
||||
@ -67,3 +73,21 @@ export interface PanelContentPartConstructor {
|
||||
export interface WatermarkConstructor {
|
||||
new (): IWatermarkRenderer;
|
||||
}
|
||||
|
||||
export interface IGroupPanelInitParameters
|
||||
extends PanelInitParameters,
|
||||
HeaderPartInitParameters {
|
||||
view: IGroupPanelView;
|
||||
}
|
||||
|
||||
export type GroupPanelUpdateEvent = PanelUpdateEvent<{
|
||||
params?: Parameters;
|
||||
title?: string;
|
||||
}>;
|
||||
|
||||
export interface GroupviewPanelState {
|
||||
id: string;
|
||||
view?: any;
|
||||
title?: string;
|
||||
params?: { [key: string]: any };
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
LocalSelectionTransfer,
|
||||
PaneTransfer,
|
||||
} from '../dnd/dataTransfer';
|
||||
import { Droptarget, DroptargetEvent, Position } from '../dnd/droptarget';
|
||||
import { Droptarget, DroptargetEvent } from '../dnd/droptarget';
|
||||
import { Emitter } from '../events';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { Orientation } from '../splitview/core/splitview';
|
||||
@ -70,7 +70,10 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
})(this.header);
|
||||
|
||||
this.target = new Droptarget(this.element, {
|
||||
validOverlays: 'vertical',
|
||||
acceptedTargetZones: ['top', 'bottom'],
|
||||
overlayModel: {
|
||||
activationSize: { type: 'percentage', value: 50 },
|
||||
},
|
||||
canDisplayOverlay: (event) => {
|
||||
const data = getPaneData();
|
||||
|
||||
@ -139,16 +142,10 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
|
||||
const fromIndex = allPanels.indexOf(existingPanel);
|
||||
let toIndex = containerApi.panels.indexOf(this);
|
||||
|
||||
if (
|
||||
event.position === Position.Left ||
|
||||
event.position === Position.Top
|
||||
) {
|
||||
if (event.position === 'left' || event.position === 'top') {
|
||||
toIndex = Math.max(0, toIndex - 1);
|
||||
}
|
||||
if (
|
||||
event.position === Position.Right ||
|
||||
event.position === Position.Bottom
|
||||
) {
|
||||
if (event.position === 'right' || event.position === 'bottom') {
|
||||
if (fromIndex > toIndex) {
|
||||
toIndex++;
|
||||
}
|
||||
|
@ -164,6 +164,8 @@ export abstract class PaneviewPanel
|
||||
) {
|
||||
super(id, component, new PaneviewPanelApiImpl(id));
|
||||
this.api.pane = this; // TODO cannot use 'this' before 'super'
|
||||
this.api.initialize(this);
|
||||
|
||||
this._isExpanded = isExpanded;
|
||||
this._headerVisible = isHeaderVisible;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { GroupviewPanelState, IDockviewPanel } from '../groupview/groupPanel';
|
||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||
import { GroupviewPanelState } from '../groupview/types';
|
||||
import { DockviewPanel, IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { IPanelDeserializer } from '../dockview/deserializer';
|
||||
import { createComponent } from '../panel/componentFactory';
|
||||
import { DockviewApi } from '../api/component.api';
|
||||
@ -56,7 +56,7 @@ export class ReactPanelDeserialzier implements IPanelDeserializer {
|
||||
tab,
|
||||
});
|
||||
|
||||
const panel = new DockviewGroupPanel(
|
||||
const panel = new DockviewPanel(
|
||||
panelId,
|
||||
this.layout,
|
||||
new DockviewApi(this.layout),
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
GroupPanelFrameworkComponentFactory,
|
||||
IGroupControlRenderer,
|
||||
} from '../../dockview/options';
|
||||
import { DockviewPanelApi } from '../../api/groupPanelApi';
|
||||
import { DockviewPanelApi } from '../../api/dockviewPanelApi';
|
||||
import { ReactPortalStore, usePortalsLifecycle } from '../react';
|
||||
import { DockviewApi } from '../../api/component.api';
|
||||
import { IWatermarkPanelProps, ReactWatermarkPart } from './reactWatermarkPart';
|
||||
@ -57,10 +57,10 @@ export interface DockviewReadyEvent {
|
||||
}
|
||||
|
||||
export interface IDockviewReactProps {
|
||||
onReady: (event: DockviewReadyEvent) => void;
|
||||
components: PanelCollection<IDockviewPanelProps>;
|
||||
tabComponents?: PanelCollection<IDockviewPanelHeaderProps>;
|
||||
watermarkComponent?: React.FunctionComponent<IWatermarkPanelProps>;
|
||||
onReady: (event: DockviewReadyEvent) => void;
|
||||
tabHeight?: number;
|
||||
onDidDrop?: (event: DockviewDropEvent) => void;
|
||||
showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean;
|
||||
@ -69,6 +69,7 @@ export interface IDockviewReactProps {
|
||||
disableAutoResizing?: boolean;
|
||||
defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>;
|
||||
groupControlComponent?: React.FunctionComponent<IDockviewGroupControlProps>;
|
||||
singleTabMode?: 'fullwidth' | 'default';
|
||||
}
|
||||
|
||||
export const DockviewReact = React.forwardRef(
|
||||
@ -161,6 +162,7 @@ export const DockviewReact = React.forwardRef(
|
||||
props.groupControlComponent,
|
||||
{ addPortal }
|
||||
),
|
||||
singleTabMode: props.singleTabMode,
|
||||
});
|
||||
|
||||
domRef.current?.appendChild(dockview.element);
|
||||
|
@ -3,12 +3,8 @@ import { ReactPart, ReactPortalStore } from '../react';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { GroupPanel, GroupviewPanelApi } from '../../groupview/groupviewPanel';
|
||||
import { DockviewApi } from '../../api/component.api';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
IDisposable,
|
||||
MutableDisposable,
|
||||
} from '../../lifecycle';
|
||||
import { IDockviewPanel } from '../../groupview/groupPanel';
|
||||
import { CompositeDisposable, MutableDisposable } from '../../lifecycle';
|
||||
import { IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
|
||||
export interface IDockviewGroupControlProps {
|
||||
api: GroupviewPanelApi;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { GridviewApi } from '../../api/component.api';
|
||||
import { GridviewPanelApiImpl } from '../../api/gridviewPanelApi';
|
||||
import {
|
||||
GridviewPanel,
|
||||
GridviewInitParameters,
|
||||
@ -14,7 +15,7 @@ export class ReactGridPanelView extends GridviewPanel {
|
||||
private readonly reactComponent: React.FunctionComponent<IGridviewPanelProps>,
|
||||
private readonly reactPortalStore: ReactPortalStore
|
||||
) {
|
||||
super(id, component);
|
||||
super(id, component, new GridviewPanelApiImpl(id));
|
||||
}
|
||||
|
||||
getComponent(): IFrameworkPart {
|
||||
|
@ -85,6 +85,8 @@ export abstract class SplitviewPanel
|
||||
constructor(id: string, componentName: string) {
|
||||
super(id, componentName, new SplitviewPanelApiImpl(id));
|
||||
|
||||
this.api.initialize(this);
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidChange,
|
||||
this.api.onVisibilityChange((event) => {
|
||||
|
@ -7,3 +7,7 @@ export interface FrameworkFactory<T> {
|
||||
}
|
||||
|
||||
export type FunctionOrValue<T> = (() => T) | T;
|
||||
|
||||
export function isBooleanValue(value: any): value is boolean {
|
||||
return typeof value === 'boolean';
|
||||
}
|
||||
|
37
packages/docs/blog/2023-02-26-dockview-1.6.0.mdx
Normal file
37
packages/docs/blog/2023-02-26-dockview-1.6.0.mdx
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
slug: dockview-1.6.0-release
|
||||
title: Dockview 1.6.0
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference to docs @ [dockview.dev](https://dockview.dev).
|
||||
If you feel anything is missing or unclear please let me know.
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Allow drag events to edge of dockview containers [#177](https://github.com/mathuo/dockview/pull/177)
|
||||
- group dnd [#171](https://github.com/mathuo/dockview/pull/171)
|
||||
- full width tabs [#171](https://github.com/mathuo/dockview/pull/177)
|
||||
- addPanel improvements
|
||||
- update parameters via panel.api.updateParameters
|
||||
- allow dnd on empty groups [#168](https://github.com/mathuo/dockview/pull/168)
|
||||
- Change watermark logic [#194](https://github.com/mathuo/dockview/pull/194)
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Fix dockview panel.api.setSize to work as expected [#184](https://github.com/mathuo/dockview/pull/184)
|
||||
- Fix dockview setTitle [#190](https://github.com/mathuo/dockview/pull/190)
|
||||
- Fix group dnd logic to filter for same dockview instance [#185](https://github.com/mathuo/dockview/pull/193)
|
||||
- Update dependencies including the dev dependencies for dockview and all dependencies for the docs website.
|
||||
[#180](https://github.com/mathuo/dockview/pull/180)
|
||||
- A variety of internal changes including file name changes
|
||||
- Improve internal dnd control logic to handle a wider variety of cases
|
||||
- Various doc enhancements @ [dockview.dev](https://dockview.dev)
|
||||
|
||||
## 🔥 Breaking changes
|
||||
|
||||
- addEmptyGroup renamed to addGroup
|
@ -13,7 +13,15 @@ import { ContextMenuDockview } from '@site/src/components/dockview/contextMenu';
|
||||
import { NestedDockview } from '@site/src/components/dockview/nested';
|
||||
import { CustomHeadersDockview } from '@site/src/components/dockview/customHeaders';
|
||||
import { ResizeDockview } from '@site/src/components/dockview/resize';
|
||||
import { DockviewGroupControl } from '@site/src/components/dockview/groupControl';
|
||||
import { DockviewWatermark } from '@site/src/components/dockview/watermark';
|
||||
import { DockviewPersistance } from '@site/src/components/dockview/persistance';
|
||||
import {
|
||||
DockviewNative,
|
||||
DockviewNative2,
|
||||
} from '@site/src/components/dockview/native';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
# Dockview
|
||||
|
||||
@ -43,17 +51,21 @@ You can create a Dockview through the use of the `ReactDockview` component.
|
||||
import { ReactDockview } from 'dockview';
|
||||
```
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| ------------------- | ------------------------------------ | -------- | ------- | ------------------------------------------------------------ |
|
||||
| onReady | (event: SplitviewReadyEvent) => void | No | | |
|
||||
| components | object | No | | |
|
||||
| tabComponents | object | Yes | | |
|
||||
| watermarkComponent | object | Yes | | |
|
||||
| hideBorders | boolean | Yes | false | |
|
||||
| className | string | Yes | '' | |
|
||||
| disableAutoResizing | boolean | Yes | false | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
| onDidDrop | Event | Yes | false | |
|
||||
| showDndOverlay | Event | Yes | false | |
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| --------------------- | ------------------------------------ | -------- | --------- | ------------------------------------------------------------ |
|
||||
| onReady | (event: SplitviewReadyEvent) => void | No | | |
|
||||
| components | object | No | | |
|
||||
| tabComponents | object | Yes | | |
|
||||
| watermarkComponent | object | Yes | | |
|
||||
| hideBorders | boolean | Yes | false | |
|
||||
| className | string | Yes | '' | |
|
||||
| disableAutoResizing | boolean | Yes | false | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
| onDidDrop | Event | Yes | false | |
|
||||
| showDndOverlay | Event | Yes | false | |
|
||||
| defaultTabComponent | object | Yes | | |
|
||||
| groupControlComponent | object | Yes | | |
|
||||
| tabHeight | number | Yes | | |
|
||||
| singleTabMode | 'fullwidth' \| 'default' | Yes | 'default' | |
|
||||
|
||||
## Dockview API
|
||||
|
||||
@ -101,13 +113,13 @@ const onReady = (event: DockviewReadyEvent) => {
|
||||
| | | |
|
||||
| addPanel | `addPanel(options: AddPanelOptions): IDockviewPanel` | |
|
||||
| getPanel | `(id: string) \| IDockviewPanel \| undefined` | |
|
||||
| addEmptyGroup | `(options? AddGroupOptions): void` | |
|
||||
| addGroup | `(options? AddGroupOptions): void` | |
|
||||
| closeAllGroups | `(): void` | |
|
||||
| removeGroup | `(group: GroupPanel): void` | |
|
||||
| getGroup | `(id: string): GroupPanel \| undefined` | |
|
||||
| | | |
|
||||
| getTabHeight | `(): number \| undefined` | |
|
||||
| setTabHeight | `(hegiht: number \| undefined): void` | |
|
||||
| setTabHeight | `(height: number \| undefined): void` | |
|
||||
| updateOptions | `(options:SplitviewComponentUpdateOptions): void` | |
|
||||
| focus | `(): void` | |
|
||||
| layout | `(width: number, height:number): void` | <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
@ -125,192 +137,155 @@ const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | ----------------------------------------------------------- | --------------- |
|
||||
| id | `string` | Panel id |
|
||||
| isFocused | `boolean` | Is panel focsed |
|
||||
| isActive | `boolean` | Is panel active |
|
||||
| width | `number` | Panel width |
|
||||
| height | `number` | Panel height |
|
||||
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | |
|
||||
| onDidFocusChange | `Event<FocusEvent>` | |
|
||||
| onDidVisibilityChange | `Event<VisibilityEvent>` | |
|
||||
| onDidActiveChange | `Event<ActiveEvent>` | |
|
||||
| setActive | `(): void` | |
|
||||
| | | |
|
||||
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | |
|
||||
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
|
||||
| setSize | `(event: SizeEvent): void` | |
|
||||
| | | |
|
||||
| group | `GroupPanel | undefined` |
|
||||
| isGroupActive | `boolean` | |
|
||||
| title | `string` | |
|
||||
| suppressClosable | `boolean` | |
|
||||
| close | `(): void` | |
|
||||
| setTitle | `(title: string): void` | |
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | ----------------------------------------------------------- | ---------------- |
|
||||
| id | `string` | Panel id |
|
||||
| isFocused | `boolean` | Is panel focused |
|
||||
| isActive | `boolean` | Is panel active |
|
||||
| width | `number` | Panel width |
|
||||
| height | `number` | Panel height |
|
||||
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | |
|
||||
| onDidFocusChange | `Event<FocusEvent>` | |
|
||||
| onDidVisibilityChange | `Event<VisibilityEvent>` | |
|
||||
| onDidActiveChange | `Event<ActiveEvent>` | |
|
||||
| setActive | `(): void` | |
|
||||
| | | |
|
||||
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | |
|
||||
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
|
||||
| setSize | `(event: SizeEvent): void` | |
|
||||
| | | |
|
||||
| group | `GroupPanel | undefined` |
|
||||
| isGroupActive | `boolean` | |
|
||||
| title | `string` | |
|
||||
| suppressClosable | `boolean` | |
|
||||
| close | `(): void` | |
|
||||
| setTitle | `(title: string): void` | |
|
||||
|
||||
## Advanced Features
|
||||
## Layout Persistance
|
||||
|
||||
### Resizing via API
|
||||
Layouts are loaded and saved via to `fromJSON` and `toJSON` methods on the Dockview api.
|
||||
The api also exposes an event `onDidLayoutChange` you can listen on to determine when the layout has changed.
|
||||
Below are some snippets showing how you might load from and save to localStorage.
|
||||
|
||||
Each Dockview is comprised of a number of groups, each of which have a number of panels.
|
||||
Logically most people would want to resize a panel but practically this really translates to resizing the group to which the panel belongs.
|
||||
```tsx title="Saving the layout state to localStorage"
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
From the api you can access the panels group object (`props.group`) which exposes it's own api object (`props.groups.api`).
|
||||
This api is largly similar to the <Link to="./gridview/#gridview-panel-api">Gridview API</Link>.
|
||||
const disposable = api.onDidLayoutChange(() => {
|
||||
const layout = api.toJSON();
|
||||
|
||||
To resize an individual panel you could create a snippet similar to below.
|
||||
|
||||
```tsx
|
||||
const onResizePanel = () => {
|
||||
props.api.group.api.setSize({
|
||||
height: 100,
|
||||
localStorage.setItem(
|
||||
'dockview_persistance_layout',
|
||||
JSON.stringify(layout)
|
||||
);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
```tsx
|
||||
const onResizePanel = () => {
|
||||
props.api.group.api.setSize({
|
||||
width: 100,
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
Here is a working example of resizing panels via these API methods.
|
||||
|
||||
<ResizeDockview />
|
||||
|
||||
### Locked group
|
||||
|
||||
Locking a group will disable all drop events for this group ensuring a user can not add additional panels to the group.
|
||||
You can still add groups to a locked panel programatically using the API.
|
||||
|
||||
```tsx
|
||||
panel.group.locked = true;
|
||||
```
|
||||
|
||||
### Group header
|
||||
|
||||
You may wish to hide the header section of a group. This can achieved through setting the `hidden` variable on `panel.group.header`.
|
||||
|
||||
```tsx
|
||||
panel.group.header.hidden = true;
|
||||
```
|
||||
|
||||
### Custom Tab Headers
|
||||
|
||||
You can provide custom renderers for your tab headers.
|
||||
A default implementation of `DockviewDefaultTab` is provided should you only wish to attach minor
|
||||
changes and events that do not alter the default behaviour, for example to add a custom context menu event
|
||||
handler.
|
||||
|
||||
You are also free to define a custom renderer entirely from scratch and not make use of the `DockviewDefaultTab` component.
|
||||
|
||||
```tsx title="Attaching a custom context menu event handlers to a custom header"
|
||||
import { IDockviewPanelHeaderProps, DockviewDefaultTab } from 'dockview';
|
||||
|
||||
const MyCustomheader = (props: IDockviewPanelHeaderProps) => {
|
||||
const onContextMenu = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
alert('context menu');
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
return <DockviewDefaultTab onContextMenu={onContextMenu} {...props} />;
|
||||
}, [api]);
|
||||
```
|
||||
|
||||
```tsx title="Loading a layout from localStorage"
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const layoutString = localStorage.getItem('dockview_persistance_layout');
|
||||
|
||||
let success = false;
|
||||
|
||||
if (layoutString) {
|
||||
try {
|
||||
const layout = JSON.parse(layoutString);
|
||||
event.api.fromJSON(layout);
|
||||
success = true;
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// do something if there is no layout or there was a loading error
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
To use a custom renderer you can must register a collection of tab components
|
||||
Here is an example using the above code loading from and saving to localStorage.
|
||||
If you refresh the page you should notice your layout is loaded as you left it.
|
||||
|
||||
<DockviewPersistance />
|
||||
|
||||
## Resizing
|
||||
|
||||
Each Dockview contains of a number of groups and each group has a number of panels.
|
||||
Logically a user may want to resize a panel, but this translates to resizing the group which contains that panel.
|
||||
|
||||
You can set the size of a panel using `props.api.setSize(...)`.
|
||||
You can also set the size of the group associated with the panel using `props.api.group.api.setSize(...)` although this isn't recommended
|
||||
due to the clunky syntax.
|
||||
|
||||
```tsx
|
||||
const tabComponents = {
|
||||
myCustomHeader: MyCustomHeader,
|
||||
};
|
||||
// it's mandatory to provide either a height or a width, providing both is optional
|
||||
props.api.setSize({
|
||||
height: 100,
|
||||
width: 200,
|
||||
});
|
||||
|
||||
return <DockviewReact tabComponents={tabComponents} ... />;
|
||||
```
|
||||
|
||||
```tsx
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
tabComponent: 'myCustomHeader', // <--
|
||||
title: 'Panel 1',
|
||||
// you could also resize the panels group, although not recommended it achieved the same result
|
||||
props.api.group.api.setSize({
|
||||
height: 100,
|
||||
width: 200,
|
||||
});
|
||||
```
|
||||
|
||||
You can also override the default tab renderer which will be used when no `tabComponent` is provided to the `addPanel` function.
|
||||
You can see an example invoking both approaches below.
|
||||
|
||||
```tsx
|
||||
<DockviewReact defaultTabComponent={MyCustomHeader} ... />;
|
||||
```
|
||||
<ResizeDockview />
|
||||
|
||||
As a simple example the below attachs a custom event handler for the context menu on all tabs as a default tab renderer
|
||||
## Watermark
|
||||
|
||||
<CustomHeadersDockview />
|
||||
When the dockview is empty you may want to display some fallback content, this is refered to as the `watermark`.
|
||||
By default there the watermark has no content but you can provide as a prop to `DockviewReact` a `watermarkComponent`
|
||||
which will be rendered when there are no panels or groups.
|
||||
|
||||
### Rendering
|
||||
<DockviewWatermark />
|
||||
|
||||
Although `DockviewReact` will only add those tabs that are visible to the DOM all associated React Components for each tab including those that
|
||||
are not initially visible will be created.
|
||||
This will mean that any hooks in those components will run and if you running expensive operations in the tabs you may end up doing a lot of initial
|
||||
work for what are hidden tabs.
|
||||
## Drag And Drop
|
||||
|
||||
This is the default behaviour to ensure the greatest flexibility for the user but you can create a Higher-Order component wrapping your components that
|
||||
will ensure the component is only created if the tab is visible as below:
|
||||
### Built-in behaviours
|
||||
|
||||
```tsx
|
||||
import { PanelApi } from 'dockview';
|
||||
import * as React from 'react';
|
||||
Dockview supports a wide variety of built-in Drag and Drop possibilities.
|
||||
Below are some examples of the operations you can perform.
|
||||
|
||||
function RenderWhenVisible<
|
||||
T extends { api: Pick<PanelApi, 'isVisible' | 'onDidVisibilityChange'> }
|
||||
>(component: React.FunctionComponent<T>) {
|
||||
const HigherOrderComponent = (props: T) => {
|
||||
const [visible, setVisible] = React.useState<boolean>(
|
||||
props.api.isVisible
|
||||
);
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_tab.svg')} />
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.api.onDidVisibilityChange((event) =>
|
||||
setVisible(event.isVisible)
|
||||
);
|
||||
> Drag a tab onto another tab to place it inbetween existing tabs.
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [props.api]);
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_empty_space.svg')} />
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
> Drag a tab to the right of the last tab to place it after the existing tabs.
|
||||
|
||||
return React.createElement(component, props);
|
||||
};
|
||||
return HigherOrderComponent;
|
||||
}
|
||||
```
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_group.svg')} />
|
||||
|
||||
```tsx
|
||||
const component = RenderWhenVisible(MyComponent);
|
||||
```
|
||||
> Drag a group onto an existing group to merge the two groups.
|
||||
|
||||
Through toggling the checkbox you can see that when you only render those panels which are visible the underling React component is destroyed when it becomes hidden and re-created when it becomes visible.
|
||||
|
||||
<Checkbox />
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<RenderingDockview renderVisibleOnly={false} />
|
||||
<div style={{ display: 'flex', justifyContent: 'space-around' }}>
|
||||
<img style={{ width: '40%' }} src={useBaseUrl('/img/drop_positions.svg')} />
|
||||
<img
|
||||
style={{ width: '40%' }}
|
||||
src={useBaseUrl('/img/magnet_drop_positions.svg')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
### Drag And Drop
|
||||
> Drag into the left/right/top/bottom target zone of a panel to create a new group in the selected direction.
|
||||
|
||||
The component exposes some method to help determine whether external drag events should be interacted with or not.
|
||||
> Drag into the center of a panel to add to that group.
|
||||
|
||||
> Drag to the edge of the dockview component to create a new group on the selected edge.
|
||||
|
||||
### Extended behaviours
|
||||
|
||||
For interaction with the Drag events directly the component exposes some method to help determine whether external drag events should be interacted with or not.
|
||||
|
||||
```tsx
|
||||
/**
|
||||
@ -352,16 +327,280 @@ return (
|
||||
|
||||
<DndDockview />
|
||||
|
||||
### Events
|
||||
## Panels
|
||||
|
||||
<EventsDockview />
|
||||
### Add Panel
|
||||
|
||||
### Nested Dockviews
|
||||
Using the dockview API you can access the `addPanel` method which returns an instance of the created panel.
|
||||
The minimum method signature is:
|
||||
|
||||
You can safely create multiple dockview instances within one page and nest dockviews within other dockviews.
|
||||
If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`.
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'my_unique_panel_id',
|
||||
component: 'my_component',
|
||||
});
|
||||
```
|
||||
|
||||
<NestedDockview />
|
||||
where `id` is the unique id of the panel and `component` is the implenentation which
|
||||
will be used to render the panel. You will have registered this using the `components` prop of the `DockviewReactComponent` component.
|
||||
|
||||
You can optionally provide a `tabComponent` parameters to the `addPanel` method which will render the tab using a custom renderer.
|
||||
You will have registered this using the `tabComponents` prop of the `DockviewReactComponent` component.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'my_unique_panel_id',
|
||||
component: 'my_component',
|
||||
tabComponent: 'my_tab_component',
|
||||
});
|
||||
```
|
||||
|
||||
You can pass properties to the panel using the `params` key.
|
||||
You can update these properties through the panels `api` object and its `updateParameters` method.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'my_unique_panel_id',
|
||||
component: 'my_component',
|
||||
params: {
|
||||
myCustomKey: 'my_custom_value',
|
||||
},
|
||||
});
|
||||
|
||||
panel.api.updateParameters({
|
||||
myCustomKey: 'my_custom_value',
|
||||
myOtherCustomKey: 'my_other_custom_key',
|
||||
});
|
||||
```
|
||||
|
||||
> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters
|
||||
> you want the panel to receive.
|
||||
|
||||
Finally `addPanel` accepts a `position` object which tells dockview where to place the panel.
|
||||
|
||||
- This object optionally accepts either a `referencePanel` or `referenceGroup` which can be the associated id as a string
|
||||
or the panel/group object reference.
|
||||
- This object accepts a `direction` property which dictates where,
|
||||
relative to the provided reference the new panel will be placed.
|
||||
|
||||
> If neither a `referencePanel` or `referenceGroup` then the provided `direction` will be treated as absolute.
|
||||
|
||||
> If no `direction` is provided the library will place the new panel in a pre-determined position.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
position: {
|
||||
referencePanel: panel1,
|
||||
direction: 'right',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Panel Rendering
|
||||
|
||||
By default `DockviewReact` only adds to the DOM those panels that are visible,
|
||||
if a panel is not the active tab and not shown the contents of the hidden panel will be removed from the DOM.
|
||||
|
||||
However the React Components associated with each panel are only created once and will always exist for as long as the panel exists, hidden or not.
|
||||
|
||||
> For example this means that any hooks in those components will run whether the panel is visible or not which may lead to excessive background work depending
|
||||
> on the panels implementation.
|
||||
|
||||
This is the default behaviour to ensure the greatest flexibility for the user but through the panels `props.api` you can listen to the visiblity state of the panel
|
||||
and write additional logic to optimize your application.
|
||||
|
||||
For example if you wanted to unmount the React Components when the panel is not visible you could create a Higher-Order-Component that listens to the panels
|
||||
visiblity state and only renders the panel when visible.
|
||||
|
||||
```tsx title="Only rendering the React Component when the panel is visible, otherwise rendering a null React Component"
|
||||
import { IDockviewPanelProps } from 'dockview';
|
||||
import * as React from 'react';
|
||||
|
||||
function RenderWhenVisible(
|
||||
component: React.FunctionComponent<IDockviewPanelProps>
|
||||
) {
|
||||
const HigherOrderComponent = (props: IDockviewPanelProps) => {
|
||||
const [visible, setVisible] = React.useState<boolean>(
|
||||
props.api.isVisible
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.api.onDidVisibilityChange((event) =>
|
||||
setVisible(event.isVisible)
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [props.api]);
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.createElement(component, props);
|
||||
};
|
||||
return HigherOrderComponent;
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
const components = { default: RenderWhenVisible(MyComponent) };
|
||||
```
|
||||
|
||||
Toggling the checkbox you can see that when you only render those panels which are visible the underling React component is destroyed when it becomes hidden and re-created when it becomes visible.
|
||||
|
||||
<Checkbox />
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<RenderingDockview renderVisibleOnly={false} />
|
||||
</div>
|
||||
|
||||
## Headers
|
||||
|
||||
### Custom Tab Headers
|
||||
|
||||
You can provide custom renderers for your tab headers for maximum customization.
|
||||
A default implementation of `DockviewDefaultTab` is provided should you only wish to attach minor
|
||||
changes and events that do not alter the default behaviour, for example to add a custom context menu event
|
||||
handler.
|
||||
|
||||
```tsx title="Attaching a custom context menu event handlers to a custom header"
|
||||
import { IDockviewPanelHeaderProps, DockviewDefaultTab } from 'dockview';
|
||||
|
||||
const MyCustomheader = (props: IDockviewPanelHeaderProps) => {
|
||||
const onContextMenu = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
alert('context menu');
|
||||
};
|
||||
return <DockviewDefaultTab onContextMenu={onContextMenu} {...props} />;
|
||||
};
|
||||
```
|
||||
|
||||
You are also free to define a custom renderer entirely from scratch and not make use of the `DockviewDefaultTab` component.
|
||||
To use a custom renderer you can must register a collection of tab components.
|
||||
|
||||
```tsx
|
||||
const tabComponents = {
|
||||
myCustomHeader: MyCustomHeader,
|
||||
};
|
||||
|
||||
return <DockviewReact tabComponents={tabComponents} ... />;
|
||||
```
|
||||
|
||||
```tsx
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
tabComponent: 'myCustomHeader', // <-- your registered renderers
|
||||
title: 'Panel 1',
|
||||
});
|
||||
```
|
||||
|
||||
You can also override the default tab renderer which will be used when no `tabComponent` is provided to the `addPanel` function.
|
||||
|
||||
```tsx
|
||||
<DockviewReact defaultTabComponent={MyCustomHeader} ... />;
|
||||
```
|
||||
|
||||
As a simple example the below attachs a custom event handler for the context menu on all tabs as a default tab renderer
|
||||
|
||||
The below example uses a custom tab renderer to reigster a popover when the user right clicked on a tab.
|
||||
This still makes use of the `DockviewDefaultTab` since it's only a minor change.
|
||||
|
||||
<CustomHeadersDockview />
|
||||
|
||||
### Default Tab Title
|
||||
|
||||
If you are using the default tab renderer you can set the title of a tab when creating it
|
||||
|
||||
```tsx
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'my_component',
|
||||
title: 'my_custom_title', // <-- special param for title
|
||||
});
|
||||
```
|
||||
|
||||
You can update the title through the panel api which can be accessed via `props.api` if you are inside the panel
|
||||
component or via `api.getPanel('panel1').api` if you are accessing from outside of the panel component.
|
||||
|
||||
```tsx
|
||||
api.updateTitle('my_new_custom_title');
|
||||
```
|
||||
|
||||
> Note this only works when using the default tab implementation.
|
||||
|
||||
### Custom Tab Title
|
||||
|
||||
If you are using a custom tab implementation you should pass variables through as a parameter and render them
|
||||
through your tab components implementation.
|
||||
|
||||
```tsx title="Add a panel with custom parameters"
|
||||
api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'my_component',
|
||||
tabComponent: 'my_tab',
|
||||
params: {
|
||||
myTitle: 'Window 2', // <-- passing a variable to use as a title
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```tsx title="Accessing custom parameters from a custom tab renderer"
|
||||
const tabComponents = {
|
||||
default: (props: IDockviewPanelHeaderProps<{ myTitle: string }>) => {
|
||||
const title = props.params.myTitle; // <-- accessing my custom varaible
|
||||
return <div>{/** tab implementation as chosen by developer */}</div>;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Hidden Headers
|
||||
|
||||
You may wish to hide the header section of a group. This can achieved through the `hidden` variable on `panel.group.header`.
|
||||
|
||||
```tsx
|
||||
panel.group.header.hidden = true;
|
||||
```
|
||||
|
||||
### Full width tabs
|
||||
|
||||
`DockviewReactComponent` accepts the prop `singleTabMode`. If set `singleTabMode=fullwidth` then when there is only one tab in a group this tab will expand
|
||||
to the entire width of the group. For example:
|
||||
|
||||
> This can be conmbined with <Link to="./dockview/#locked-group">Locked Groups</Link> to create an application that feels more like a Window Manager
|
||||
> rather than a collection of groups and tabs.
|
||||
|
||||
```tsx
|
||||
<DockviewReactComponent singleTabMode="fullwidth" {...otherProps} />
|
||||
```
|
||||
|
||||
<DockviewNative />
|
||||
|
||||
## Groups
|
||||
|
||||
### Locked group
|
||||
|
||||
Locking a group will disable all drop events for this group ensuring no additional panels can be added to the group through drop events.
|
||||
You can still add groups to a locked panel programatically using the API though.
|
||||
|
||||
```tsx
|
||||
panel.group.locked = true;
|
||||
```
|
||||
|
||||
### Group Controls Panel
|
||||
|
||||
@ -375,3 +614,50 @@ const Component: React.FunctionComponent<IDockviewGroupControlProps> = () => {
|
||||
|
||||
return <DockviewReact {...props} groupControlComponent={Component} />;
|
||||
```
|
||||
|
||||
As a simple example the below uses the `groupControlComponent` to render a small control that indicates whether the group
|
||||
is active and which panel is active in that group.
|
||||
|
||||
```tsx
|
||||
const GroupControlComponent = (props: IDockviewGroupControlProps) => {
|
||||
const isGroupActive = props.isGroupActive;
|
||||
const activePanel = props.activePanel;
|
||||
|
||||
return (
|
||||
<div className="dockview-groupcontrol-demo">
|
||||
<span
|
||||
className="dockview-groupcontrol-demo-group-active"
|
||||
style={{
|
||||
background: isGroupActive ? 'green' : 'red',
|
||||
}}
|
||||
>
|
||||
{isGroupActive ? 'Group Active' : 'Group Inactive'}
|
||||
</span>
|
||||
<span className="dockview-groupcontrol-demo-active-panel">{`activePanel: ${
|
||||
activePanel?.id || 'null'
|
||||
}`}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
<DockviewGroupControl />
|
||||
|
||||
## Events
|
||||
|
||||
<EventsDockview />
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Nested Dockviews
|
||||
|
||||
You can safely create multiple dockview instances within one page and nest dockviews within other dockviews.
|
||||
If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`.
|
||||
|
||||
<NestedDockview />
|
||||
|
||||
### Example
|
||||
|
||||
hello
|
||||
|
||||
<DockviewNative2 />
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-docs",
|
||||
"version": "1.5.2",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
@ -16,30 +16,30 @@
|
||||
"deploy-docs": "node scripts/package-docs.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "2.0.0-beta.20",
|
||||
"@docusaurus/preset-classic": "2.0.0-beta.20",
|
||||
"@docusaurus/core": "2.3.1",
|
||||
"@docusaurus/preset-classic": "2.3.1",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"axios": "^0.27.2",
|
||||
"clsx": "^1.1.1",
|
||||
"dockview": "^1.5.2",
|
||||
"prism-react-renderer": "^1.3.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"recoil": "^0.7.3-alpha.2",
|
||||
"uuid": "^8.3.2",
|
||||
"axios": "^1.3.3",
|
||||
"clsx": "^1.2.1",
|
||||
"dockview": "^1.6.0",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"recoil": "^0.7.6",
|
||||
"uuid": "^9.0.0",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.0.0-beta.20",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
"@types/react": "^17.0.35",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"docusaurus-plugin-sass": "^0.2.2",
|
||||
"fs-extra": "^10.1.0",
|
||||
"@docusaurus/module-type-aliases": "2.3.1",
|
||||
"@tsconfig/docusaurus": "^1.0.6",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"docusaurus-plugin-sass": "^0.2.3",
|
||||
"fs-extra": "^11.1.0",
|
||||
"install": "^0.13.0",
|
||||
"sass": "^1.52.1",
|
||||
"typescript": "^4.6.4"
|
||||
"sass": "^1.58.1",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"react": "17.0.2",
|
||||
|
@ -11,7 +11,7 @@ type FeatureItem = {
|
||||
const FeatureList: FeatureItem[] = [
|
||||
{
|
||||
title: '',
|
||||
Svg: require('@site/static/img/dockview_grid_2.svg').default,
|
||||
Svg: require('@site/static/img/dockview_grid_3.svg').default,
|
||||
description: (
|
||||
<>
|
||||
<div className="feature-banner">
|
||||
|
@ -84,7 +84,7 @@ export const CustomHeadersDockview = () => {
|
||||
position: { referencePanel: 'panel_7', direction: 'within' },
|
||||
});
|
||||
|
||||
event.api.addEmptyGroup();
|
||||
event.api.addGroup();
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -209,9 +209,8 @@ const Icon = (props: {
|
||||
};
|
||||
|
||||
const Button = () => {
|
||||
const [position, setPosition] = React.useState<
|
||||
{ x: number; y: number } | undefined
|
||||
>(undefined);
|
||||
const [position, setPosition] =
|
||||
React.useState<{ x: number; y: number } | undefined>(undefined);
|
||||
|
||||
const close = () => setPosition(undefined);
|
||||
|
||||
@ -316,19 +315,19 @@ export const DockviewDemo = () => {
|
||||
position: { referencePanel: 'panel_7', direction: 'within' },
|
||||
});
|
||||
|
||||
event.api.addEmptyGroup();
|
||||
event.api.addGroup();
|
||||
|
||||
event.api.getPanel('panel_1').api.setActive();
|
||||
|
||||
setInterval(() => {
|
||||
event.api.getPanel('panel_1').update({
|
||||
params: {
|
||||
params: {
|
||||
title: Date.now().toString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 1000);
|
||||
// setInterval(() => {
|
||||
// event.api.getPanel('panel_1').update({
|
||||
// params: {
|
||||
// params: {
|
||||
// title: Date.now().toString(),
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// }, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
positionToDirection,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
|
||||
@ -54,14 +55,12 @@ export const DndDockview = (props: { renderVisibleOnly: boolean }) => {
|
||||
};
|
||||
|
||||
const onDidDrop = (event: DockviewDropEvent) => {
|
||||
const { group } = event;
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'test',
|
||||
component: 'default',
|
||||
position: {
|
||||
referencePanel: group.activePanel.id,
|
||||
direction: 'within',
|
||||
direction: positionToDirection(event.position),
|
||||
referenceGroup: event.group,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
17
packages/docs/src/components/dockview/groupControl.scss
Normal file
17
packages/docs/src/components/dockview/groupControl.scss
Normal file
@ -0,0 +1,17 @@
|
||||
.dockview-groupcontrol-demo {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: white;
|
||||
background-color: black;
|
||||
padding-left: 8px;
|
||||
|
||||
.dockview-groupcontrol-demo-group-active {
|
||||
padding: 0px 8px;
|
||||
}
|
||||
|
||||
.dockview-groupcontrol-demo-active-panel {
|
||||
color: yellow;
|
||||
padding: 0px 8px;
|
||||
}
|
||||
}
|
102
packages/docs/src/components/dockview/groupControl.tsx
Normal file
102
packages/docs/src/components/dockview/groupControl.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import {
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewGroupControlProps,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import './groupControl.scss';
|
||||
|
||||
const components = {
|
||||
default: (props: IDockviewPanelProps<{ title: string; x?: number }>) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: 'white',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<span>{`${props.params.title}`}</span>
|
||||
{props.params.x && <span>{` ${props.params.x}`}</span>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const GroupControlComponent = (props: IDockviewGroupControlProps) => {
|
||||
const isGroupActive = props.isGroupActive;
|
||||
const activePanel = props.activePanel;
|
||||
|
||||
return (
|
||||
<div className="dockview-groupcontrol-demo">
|
||||
<span
|
||||
className="dockview-groupcontrol-demo-group-active"
|
||||
style={{
|
||||
background: isGroupActive ? 'green' : 'red',
|
||||
}}
|
||||
>
|
||||
{isGroupActive ? 'Group Active' : 'Group Inactive'}
|
||||
</span>
|
||||
<span className="dockview-groupcontrol-demo-active-panel">{`activePanel: ${
|
||||
activePanel?.id || 'null'
|
||||
}`}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DockviewGroupControl = () => {
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const panel1 = event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 1',
|
||||
},
|
||||
});
|
||||
|
||||
const panel2 = event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 2',
|
||||
},
|
||||
position: {
|
||||
direction: 'right',
|
||||
},
|
||||
});
|
||||
|
||||
const panel3 = event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 3',
|
||||
},
|
||||
position: {
|
||||
direction: 'below',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '500px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<DockviewReact
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
groupControlComponent={GroupControlComponent}
|
||||
className="dockview-theme-abyss"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
34
packages/docs/src/components/dockview/native.scss
Normal file
34
packages/docs/src/components/dockview/native.scss
Normal file
@ -0,0 +1,34 @@
|
||||
.nested-dockview {
|
||||
position: relative;
|
||||
::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: var(--dv-separator-border);
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
padding: 0px 8px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
201
packages/docs/src/components/dockview/native.tsx
Normal file
201
packages/docs/src/components/dockview/native.tsx
Normal file
@ -0,0 +1,201 @@
|
||||
import {
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
Position,
|
||||
Direction,
|
||||
IDockviewPanelHeaderProps,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import './native.scss';
|
||||
|
||||
const components = {
|
||||
default: (props: IDockviewPanelProps<{ title: string; x?: number }>) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: 'white',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<span>{`${props.params.title}`}</span>
|
||||
{props.params.x && <span>{` ${props.params.x}`}</span>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
isolatedApp: (
|
||||
props: IDockviewPanelProps<{ title: string; x?: number }>
|
||||
) => {
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const panel1 = event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Tab 1',
|
||||
},
|
||||
});
|
||||
const panel2 = event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Tab 2',
|
||||
},
|
||||
});
|
||||
const panel3 = event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Tab 3',
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<DockviewReact
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
tabComponents={tabComponents}
|
||||
className="dockview-theme-abyss"
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const tabComponents = {
|
||||
default: (props: IDockviewPanelHeaderProps<{ title: string }>) => {
|
||||
return (
|
||||
<div className="my-custom-tab">
|
||||
<span>{props.params.title}</span>
|
||||
<span style={{ flexGrow: 1 }} />
|
||||
|
||||
<span className="my-custom-tab-icon material-symbols-outlined">
|
||||
chrome_minimize
|
||||
</span>
|
||||
<span className="my-custom-tab-icon material-symbols-outlined">
|
||||
chrome_maximize
|
||||
</span>
|
||||
<span className="my-custom-tab-icon material-symbols-outlined">
|
||||
close
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DockviewNative = () => {
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const panel1 = event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 1',
|
||||
},
|
||||
});
|
||||
panel1.group.locked = true;
|
||||
|
||||
const panel2 = event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 2',
|
||||
},
|
||||
position: {
|
||||
direction: 'right',
|
||||
},
|
||||
});
|
||||
panel2.group.locked = true;
|
||||
|
||||
const panel3 = event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 3',
|
||||
},
|
||||
position: {
|
||||
direction: 'below',
|
||||
},
|
||||
});
|
||||
panel3.group.locked = true;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '500px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<DockviewReact
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
tabComponents={tabComponents}
|
||||
className="dockview-theme-abyss"
|
||||
singleTabMode="fullwidth"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DockviewNative2 = () => {
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const panel1 = event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'isolatedApp',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 1',
|
||||
},
|
||||
});
|
||||
panel1.group.locked = true;
|
||||
|
||||
const panel2 = event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'isolatedApp',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 2',
|
||||
},
|
||||
position: {
|
||||
direction: 'right',
|
||||
},
|
||||
});
|
||||
panel2.group.locked = true;
|
||||
|
||||
const panel3 = event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'isolatedApp',
|
||||
tabComponent: 'default',
|
||||
params: {
|
||||
title: 'Window 3',
|
||||
},
|
||||
position: {
|
||||
direction: 'below',
|
||||
},
|
||||
});
|
||||
panel3.group.locked = true;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '500px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<DockviewReact
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
tabComponents={tabComponents}
|
||||
className="dockview-theme-abyss"
|
||||
singleTabMode="fullwidth"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
126
packages/docs/src/components/dockview/persistance.tsx
Normal file
126
packages/docs/src/components/dockview/persistance.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
import {
|
||||
DockviewApi,
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
IWatermarkPanelProps,
|
||||
Orientation,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import './nested.scss';
|
||||
|
||||
const components = {
|
||||
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
padding: '20px',
|
||||
background: 'var(--dv-group-view-background-color)',
|
||||
}}
|
||||
>
|
||||
{props.params.title}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const counter = (() => {
|
||||
let i = 0;
|
||||
|
||||
return {
|
||||
next: () => ++i,
|
||||
};
|
||||
})();
|
||||
|
||||
function loadDefaultLayout(api: DockviewApi) {
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
});
|
||||
}
|
||||
|
||||
export const DockviewPersistance = () => {
|
||||
const [api, setApi] = React.useState<DockviewApi>();
|
||||
|
||||
const clearLayout = () => {
|
||||
localStorage.removeItem('dockview_persistance_layout');
|
||||
if (api) {
|
||||
api.clear();
|
||||
loadDefaultLayout(api);
|
||||
}
|
||||
};
|
||||
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const layoutString = localStorage.getItem(
|
||||
'dockview_persistance_layout'
|
||||
);
|
||||
|
||||
let success = false;
|
||||
|
||||
if (layoutString) {
|
||||
try {
|
||||
const layout = JSON.parse(layoutString);
|
||||
event.api.fromJSON(layout);
|
||||
success = true;
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
loadDefaultLayout(event.api);
|
||||
}
|
||||
|
||||
setApi(event.api);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
api.onDidLayoutChange(() => {
|
||||
const layout = api.toJSON();
|
||||
|
||||
localStorage.setItem(
|
||||
'dockview_persistance_layout',
|
||||
JSON.stringify(layout)
|
||||
);
|
||||
});
|
||||
}, [api]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '500px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<button onClick={clearLayout}>Reset Layout</button>
|
||||
</div>
|
||||
<DockviewReact
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
watermarkComponent={Watermark}
|
||||
className="dockview-theme-abyss"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Watermark = () => {
|
||||
return <div style={{ color: 'white', padding: '8px' }}>watermark</div>;
|
||||
};
|
@ -2,7 +2,6 @@ import {
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
PanelApi,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
|
||||
@ -13,10 +12,10 @@ const renderVisibleComponentsOnlyAtom = atom<boolean>({
|
||||
default: false,
|
||||
});
|
||||
|
||||
function RenderWhenVisible<
|
||||
T extends { api: Pick<PanelApi, 'isVisible' | 'onDidVisibilityChange'> }
|
||||
>(component: React.FunctionComponent<T>) {
|
||||
const HigherOrderComponent = (props: T) => {
|
||||
function RenderWhenVisible(
|
||||
component: React.FunctionComponent<IDockviewPanelProps>
|
||||
) {
|
||||
const HigherOrderComponent = (props: IDockviewPanelProps) => {
|
||||
const [visible, setVisible] = React.useState<boolean>(
|
||||
props.api.isVisible
|
||||
);
|
||||
|
@ -23,13 +23,24 @@ const Default = (props: IDockviewPanelProps) => {
|
||||
step={1}
|
||||
/>
|
||||
<button
|
||||
style={{ width: '100px' }}
|
||||
onClick={() => {
|
||||
props.api.group.api.setSize({
|
||||
width,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Set
|
||||
Resize Group
|
||||
</button>
|
||||
<button
|
||||
style={{ width: '100px' }}
|
||||
onClick={() => {
|
||||
props.api.setSize({
|
||||
width,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Resize panel
|
||||
</button>
|
||||
</div>
|
||||
<div className="resize-control">
|
||||
@ -42,13 +53,24 @@ const Default = (props: IDockviewPanelProps) => {
|
||||
step={1}
|
||||
/>
|
||||
<button
|
||||
style={{ width: '100px' }}
|
||||
onClick={() => {
|
||||
props.api.group.api.setSize({
|
||||
height,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Set
|
||||
Resize Group
|
||||
</button>
|
||||
<button
|
||||
style={{ width: '100px' }}
|
||||
onClick={() => {
|
||||
props.api.setSize({
|
||||
height,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Resize Panel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
134
packages/docs/src/components/dockview/watermark.tsx
Normal file
134
packages/docs/src/components/dockview/watermark.tsx
Normal file
@ -0,0 +1,134 @@
|
||||
import {
|
||||
DockviewApi,
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
IWatermarkPanelProps,
|
||||
Orientation,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import './nested.scss';
|
||||
|
||||
const components = {
|
||||
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
padding: '20px',
|
||||
background: 'var(--dv-group-view-background-color)',
|
||||
}}
|
||||
>
|
||||
{props.params.title}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const counter = (() => {
|
||||
let i = 0;
|
||||
|
||||
return {
|
||||
next: () => ++i,
|
||||
};
|
||||
})();
|
||||
|
||||
const Watermark = (props: IWatermarkPanelProps) => {
|
||||
const isGroup = props.containerApi.groups.length > 0;
|
||||
|
||||
const addPanel = () => {
|
||||
props.containerApi.addPanel({
|
||||
id: counter.next().toString(),
|
||||
component: 'default',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
This is a custom watermark. You can put whatever React
|
||||
component you want here
|
||||
</span>
|
||||
<span>
|
||||
<button onClick={addPanel}>Add New Panel</button>
|
||||
</span>
|
||||
{isGroup && (
|
||||
<span>
|
||||
<button
|
||||
onClick={() => {
|
||||
props.close();
|
||||
}}
|
||||
>
|
||||
Close Group
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DockviewWatermark = () => {
|
||||
const [api, setApi] = React.useState<DockviewApi>();
|
||||
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
// event.api.addPanel({
|
||||
// id: 'panel_1',
|
||||
// component: 'default',
|
||||
// });
|
||||
|
||||
event.api.fromJSON({
|
||||
grid: {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
root: { type: 'branch', data: [] },
|
||||
height: 100,
|
||||
width: 100,
|
||||
},
|
||||
panels: {},
|
||||
});
|
||||
|
||||
setApi(event.api);
|
||||
};
|
||||
|
||||
const onClick = () => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
api.addGroup();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '500px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<button onClick={onClick}>Add Empty Group</button>
|
||||
</div>
|
||||
<DockviewReact
|
||||
onReady={onReady}
|
||||
components={components}
|
||||
watermarkComponent={Watermark}
|
||||
className="dockview-theme-abyss nested-dockview"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
20
packages/docs/static/img/add_to_empty_space.svg
vendored
Normal file
20
packages/docs/static/img/add_to_empty_space.svg
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<svg width="156" height="18" viewBox="0 0 156 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="4" width="156" height="14" fill="#1C1C2A"/>
|
||||
<rect y="4" width="30" height="14" fill="#10192C"/>
|
||||
<rect x="31" y="4" width="30" height="14" fill="#10192C"/>
|
||||
<rect x="62" y="4" width="30" height="14" fill="#000C18"/>
|
||||
<rect x="30" y="4" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="61" y="4" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="92" y="4" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="66" y="9" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="76" y="9" width="12" height="4" rx="2" fill="white"/>
|
||||
<rect x="33" y="9" width="15" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="2" y="9" width="6" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="10" y="9" width="18" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="93" y="4" width="63" height="14" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="111.5" y="0.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="115" y="5" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="126" y="5" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.344 13.2654C128.52 13.1426 128.51 12.8799 128.327 12.77L124.18 10.2905C123.961 10.1595 123.691 10.3492 123.739 10.5997L124.651 15.344C124.691 15.5542 124.935 15.653 125.11 15.5302L125.772 15.067C125.867 15.0002 125.914 14.8836 125.892 14.7693L125.602 13.2612C125.554 13.0106 125.825 12.821 126.044 12.952L127.362 13.7401C127.462 13.7999 127.588 13.7954 127.683 13.7286L128.344 13.2654Z" fill="white"/>
|
||||
<rect x="127.5" y="14.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
25
packages/docs/static/img/add_to_group.svg
vendored
Normal file
25
packages/docs/static/img/add_to_group.svg
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<svg width="156" height="18" viewBox="0 0 156 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="4" width="156" height="14" fill="#1C1C2A"/>
|
||||
<rect y="4" width="30" height="14" fill="#10192C"/>
|
||||
<rect x="31" y="4" width="30" height="14" fill="#10192C"/>
|
||||
<rect x="62" y="4" width="30" height="14" fill="#000C18"/>
|
||||
<rect x="30" y="4" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="61" y="4" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="92" y="4" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="66" y="9" width="7" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="76" y="9" width="12" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="33" y="9" width="15" height="4" rx="2" fill="#282828"/>
|
||||
<rect x="2" y="9" width="6" height="4" rx="2" fill="#282828"/>
|
||||
<rect x="10" y="9" width="18" height="4" rx="2" fill="#282828"/>
|
||||
<rect x="59" width="91" height="10" fill="#2B2B4A"/>
|
||||
<rect x="60" y="1" width="89.1429" height="8" fill="#1C1C2A"/>
|
||||
<rect x="60" y="1" width="17.1429" height="8" fill="#10192C"/>
|
||||
<rect x="77.7142" y="1" width="17.1429" height="8" fill="#10192C"/>
|
||||
<rect x="77.1428" y="1" width="0.571429" height="8" fill="#2B2B4A"/>
|
||||
<rect x="94.8572" y="1" width="0.571429" height="8" fill="#2B2B4A"/>
|
||||
<rect x="78.8572" y="3.85718" width="8.57143" height="2.28571" rx="1.14286" fill="white"/>
|
||||
<rect x="89.1428" y="3.85718" width="2.85714" height="2.28571" rx="1.14286" fill="white"/>
|
||||
<rect x="61.1428" y="3.85718" width="10.8571" height="2.28571" rx="1.14286" fill="#777777"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M117.344 8.26544C117.52 8.14263 117.51 7.87986 117.327 7.76998L113.18 5.29049C112.961 5.15953 112.691 5.34916 112.739 5.59974L113.651 10.344C113.691 10.5542 113.935 10.653 114.11 10.5302L114.772 10.067C114.867 10.0002 114.914 9.88362 114.892 9.76929L114.602 8.26123C114.554 8.01064 114.825 7.82101 115.044 7.95197L116.362 8.74015C116.462 8.79989 116.588 8.79538 116.683 8.7286L117.344 8.26544Z" fill="white"/>
|
||||
<rect x="116.5" y="9.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
20
packages/docs/static/img/add_to_tab.svg
vendored
Normal file
20
packages/docs/static/img/add_to_tab.svg
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<svg width="156" height="18" viewBox="0 0 156 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect y="4" width="156" height="14" fill="#1C1C2A"/>
|
||||
<rect y="4" width="30" height="14" fill="#10192C"/>
|
||||
<rect x="31" y="4" width="30" height="14" fill="#10192C"/>
|
||||
<rect x="62" y="4" width="30" height="14" fill="#000C18"/>
|
||||
<rect x="30" y="4" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="61" y="4" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="92" y="4" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="66" y="9" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="76" y="9" width="12" height="4" rx="2" fill="white"/>
|
||||
<rect x="33" y="9" width="15" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="2" y="9" width="6" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="10" y="9" width="18" height="4" rx="2" fill="#777777"/>
|
||||
<path d="M31 4H61V18H31V4Z" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="49.5" y="0.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="53" y="5" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="64" y="5" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M66.3445 13.2654C66.5198 13.1426 66.5104 12.8799 66.3266 12.77L62.1804 10.2905C61.9614 10.1595 61.6906 10.3492 61.7388 10.5997L62.6506 15.344C62.691 15.5542 62.9347 15.653 63.1101 15.5302L63.7716 15.067C63.8669 15.0002 63.9142 14.8836 63.8922 14.7693L63.6024 13.2612C63.5542 13.0106 63.825 12.821 64.044 12.952L65.362 13.7401C65.4619 13.7999 65.5876 13.7954 65.683 13.7286L66.3445 13.2654Z" fill="white"/>
|
||||
<rect x="65.5" y="14.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
61
packages/docs/static/img/dockview_grid_3.svg
vendored
Normal file
61
packages/docs/static/img/dockview_grid_3.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 18 KiB |
45
packages/docs/static/img/drop_positions.svg
vendored
Normal file
45
packages/docs/static/img/drop_positions.svg
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
<svg width="156" height="121" viewBox="0 0 156 121" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="156" height="14" fill="#1C1C2A"/>
|
||||
<rect width="30" height="14" fill="#10192C"/>
|
||||
<rect x="31" width="30" height="14" fill="#10192C"/>
|
||||
<rect x="62" width="30" height="14" fill="#000C18"/>
|
||||
<rect x="30" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="61" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="92" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="66" y="5" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="76" y="5" width="12" height="4" rx="2" fill="white"/>
|
||||
<rect x="33" y="5" width="15" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="2" y="5" width="6" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="10" y="5" width="18" height="4" rx="2" fill="#777777"/>
|
||||
<rect y="14" width="156" height="107" fill="#000C18"/>
|
||||
<rect x="38" y="14" width="80" height="25" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="38" y="96" width="80" height="25" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect y="29" width="30" height="77" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="38" y="48" width="80" height="38" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="126" y="29" width="30" height="77" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="63.5" y="20.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="67" y="25" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="78" y="25" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M80.3445 33.2654C80.5198 33.1426 80.5104 32.8799 80.3266 32.77L76.1804 30.2905C75.9614 30.1595 75.6906 30.3492 75.7388 30.5997L76.6506 35.344C76.691 35.5542 76.9347 35.653 77.1101 35.5302L77.7716 35.067C77.8669 35.0002 77.9142 34.8836 77.8922 34.7693L77.6024 33.2612C77.5542 33.0106 77.825 32.821 78.044 32.952L79.362 33.7401C79.4619 33.7999 79.5876 33.7954 79.683 33.7286L80.3445 33.2654Z" fill="white"/>
|
||||
<rect x="79.5" y="34.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
<rect x="122.5" y="62.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="126" y="67" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="137" y="67" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M139.344 75.2654C139.52 75.1426 139.51 74.8799 139.327 74.77L135.18 72.2905C134.961 72.1595 134.691 72.3492 134.739 72.5997L135.651 77.344C135.691 77.5542 135.935 77.653 136.11 77.5302L136.772 77.067C136.867 77.0002 136.914 76.8836 136.892 76.7693L136.602 75.2612C136.554 75.0106 136.825 74.821 137.044 74.952L138.362 75.7401C138.462 75.7999 138.588 75.7954 138.683 75.7286L139.344 75.2654Z" fill="white"/>
|
||||
<rect x="138.5" y="76.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
<rect x="62.5" y="100.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="66" y="105" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="77" y="105" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M79.3445 113.265C79.5198 113.143 79.5104 112.88 79.3266 112.77L75.1804 110.29C74.9614 110.16 74.6906 110.349 74.7388 110.6L75.6506 115.344C75.691 115.554 75.9347 115.653 76.1101 115.53L76.7716 115.067C76.8669 115 76.9142 114.884 76.8922 114.769L76.6024 113.261C76.5542 113.011 76.825 112.821 77.044 112.952L78.362 113.74C78.4619 113.8 78.5876 113.795 78.683 113.729L79.3445 113.265Z" fill="white"/>
|
||||
<rect x="78.5" y="114.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
<rect x="4.5" y="62.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="8" y="67" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="19" y="67" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.3445 75.2654C21.5198 75.1426 21.5104 74.8799 21.3266 74.77L17.1804 72.2905C16.9614 72.1595 16.6906 72.3492 16.7388 72.5997L17.6506 77.344C17.691 77.5542 17.9347 77.653 18.1101 77.5302L18.7716 77.067C18.8669 77.0002 18.9142 76.8836 18.8922 76.7693L18.6024 75.2612C18.5542 75.0106 18.825 74.821 19.044 74.952L20.362 75.7401C20.4619 75.7999 20.5876 75.7954 20.683 75.7286L21.3445 75.2654Z" fill="white"/>
|
||||
<rect x="20.5" y="76.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
<rect x="63.5" y="62.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="67" y="67" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="78" y="67" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M80.3445 75.2654C80.5198 75.1426 80.5104 74.8799 80.3266 74.77L76.1804 72.2905C75.9614 72.1595 75.6906 72.3492 75.7388 72.5997L76.6506 77.344C76.691 77.5542 76.9347 77.653 77.1101 77.5302L77.7716 77.067C77.8669 77.0002 77.9142 76.8836 77.8922 76.7693L77.6024 75.2612C77.5542 75.0106 77.825 74.821 78.044 74.952L79.362 75.7401C79.4619 75.7999 79.5876 75.7954 79.683 75.7286L80.3445 75.2654Z" fill="white"/>
|
||||
<rect x="79.5" y="76.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.9 KiB |
53
packages/docs/static/img/magnet_drop_positions.svg
vendored
Normal file
53
packages/docs/static/img/magnet_drop_positions.svg
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<svg width="163" height="131" viewBox="0 0 163 131" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 5C4 2.23858 6.23858 0 9 0H154C156.761 0 159 2.23858 159 5V11H4V5Z" fill="#DCDCDC"/>
|
||||
<rect x="4" y="10" width="155" height="1" fill="#BABABA"/>
|
||||
<rect x="4" y="11" width="155" height="61" fill="#000C18"/>
|
||||
<rect x="81" y="73" width="78" height="58" fill="#000C18"/>
|
||||
<rect x="4" y="73" width="77" height="58" fill="#000C18"/>
|
||||
<rect x="4" y="72" width="155" height="1" fill="#2B2B4A"/>
|
||||
<path d="M81 73H80V131H81V73Z" fill="#2B2B4A"/>
|
||||
<rect x="4" y="11" width="155" height="14" fill="#1C1C2A"/>
|
||||
<rect x="4" y="11" width="30" height="14" fill="#10192C"/>
|
||||
<rect x="35" y="11" width="30" height="14" fill="#000C18"/>
|
||||
<rect x="34" y="11" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="65" y="11" width="1" height="14" fill="#2B2B4A"/>
|
||||
<rect x="81" y="73" width="78" height="14" fill="#1C1C2A"/>
|
||||
<rect x="81" y="73" width="24" height="14" fill="#10192C"/>
|
||||
<rect x="105" y="73" width="24" height="14" fill="#000C18"/>
|
||||
<rect x="105" y="73" width="0.503226" height="14" fill="#2B2B4A"/>
|
||||
<rect x="129" y="73" width="0.503226" height="14" fill="#2B2B4A"/>
|
||||
<rect x="38" y="16" width="12" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="107" y="78" width="7" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="115" y="78" width="11" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="53" y="16" width="4" height="4" rx="2" fill="#777777"/>
|
||||
<rect x="7" y="16" width="5" height="4" rx="2" fill="#282828"/>
|
||||
<rect x="13" y="16" width="16" height="4" rx="2" fill="#282828"/>
|
||||
<rect x="84" y="78" width="16" height="4" rx="2" fill="#282828"/>
|
||||
<rect x="8" y="3" width="4" height="4" rx="2" fill="#FD605E"/>
|
||||
<rect x="14" y="3" width="4" height="4" rx="2" fill="#FBBC3F"/>
|
||||
<rect x="20" y="3" width="4" height="4" rx="2" fill="#34C942"/>
|
||||
<rect x="149" y="34" width="10" height="77" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="4" y="34" width="10" height="77" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="18" y="11" width="124" height="10" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="18" y="121" width="124" height="10" fill="#E1E1E1" fill-opacity="0.25"/>
|
||||
<rect x="90.5" y="112.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="94" y="117" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="105" y="117" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M107.344 125.265C107.52 125.143 107.51 124.88 107.327 124.77L103.18 122.29C102.961 122.16 102.691 122.349 102.739 122.6L103.651 127.344C103.691 127.554 103.935 127.653 104.11 127.53L104.772 127.067C104.867 127 104.914 126.884 104.892 126.769L104.602 125.261C104.554 125.011 104.825 124.821 105.044 124.952L106.362 125.74C106.462 125.8 106.588 125.795 106.683 125.729L107.344 125.265Z" fill="white"/>
|
||||
<rect x="106.5" y="126.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
<rect x="0.5" y="67.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="4" y="72" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="15" y="72" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.3445 80.2654C17.5198 80.1426 17.5104 79.8799 17.3266 79.77L13.1804 77.2905C12.9614 77.1595 12.6906 77.3492 12.7388 77.5997L13.6506 82.344C13.691 82.5542 13.9347 82.653 14.1101 82.5302L14.7716 82.067C14.8669 82.0002 14.9142 81.8836 14.8922 81.7693L14.6024 80.2612C14.5542 80.0106 14.825 79.821 15.044 79.952L16.362 80.7401C16.4619 80.7999 16.5876 80.7954 16.683 80.7286L17.3445 80.2654Z" fill="white"/>
|
||||
<rect x="16.5" y="81.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
<rect x="133.5" y="50.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="137" y="55" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="148" y="55" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M150.344 63.2654C150.52 63.1426 150.51 62.8799 150.327 62.77L146.18 60.2905C145.961 60.1595 145.691 60.3492 145.739 60.5997L146.651 65.344C146.691 65.5542 146.935 65.653 147.11 65.5302L147.772 65.067C147.867 65.0002 147.914 64.8836 147.892 64.7693L147.602 63.2612C147.554 63.0106 147.825 62.821 148.044 62.952L149.362 63.7401C149.462 63.7999 149.588 63.7954 149.683 63.7286L150.344 63.2654Z" fill="white"/>
|
||||
<rect x="149.5" y="64.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
<rect x="84.5" y="14.5" width="29" height="13" fill="#000C18" stroke="#2B2B4A"/>
|
||||
<rect x="88" y="19" width="7" height="4" rx="2" fill="white"/>
|
||||
<rect x="99" y="19" width="12" height="4" rx="2" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M101.344 27.2654C101.52 27.1426 101.51 26.8799 101.327 26.77L97.1804 24.2905C96.9614 24.1595 96.6906 24.3492 96.7388 24.5997L97.6506 29.344C97.691 29.5542 97.9347 29.653 98.1101 29.5302L98.7716 29.067C98.8669 29.0002 98.9142 28.8836 98.8922 28.7693L98.6024 27.2612C98.5542 27.0106 98.825 26.821 99.044 26.952L100.362 27.7401C100.462 27.7999 100.588 27.7954 100.683 27.7286L101.344 27.2654Z" fill="white"/>
|
||||
<rect x="100.5" y="28.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
119
packages/docs/versioned_docs/version-1.6.0/basics.mdx
vendored
Normal file
119
packages/docs/versioned_docs/version-1.6.0/basics.mdx
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: How to get started with Dockview
|
||||
---
|
||||
|
||||
import { SimpleSplitview } from '@site/src/components/simpleSplitview';
|
||||
import { SimpleSplitview2 } from '@site/src/components/simpleSplitview2';
|
||||
|
||||
# Basics
|
||||
|
||||
asd
|
||||
This section will take you through a number of concepts that can be applied to all dockview components.
|
||||
|
||||
## Panels
|
||||
|
||||
The below examples use `ReactSplitview` but the logic holds for `ReactPaneview`, `ReactGridview` and `ReactDockview` using their respective implementations and interfaces.
|
||||
All components require you to provide an `onReady` prop which you can use to build and control your component.
|
||||
|
||||
### Adding a panel with parameters
|
||||
|
||||
You can pass parameters to a panel through the `params` object
|
||||
|
||||
```tsx
|
||||
const onReady = (event: SplitviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'myComponent',
|
||||
params: {
|
||||
title: 'My Title',
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
and you can access those properties through the `props.params` object. The TypeScript interface accepts an optional generic type `T` that corresponds to the params objects type.
|
||||
|
||||
```tsx
|
||||
const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => {
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
There are two types of API you will interact with using `dockview`.
|
||||
|
||||
- The `panel API` is accessible via `props.api` in user defined panels and via the `.api` variable found on panel instances. This API contains actions and variable related to the the individual panel.
|
||||
- The `container API` is accessible via `event.api` in the `onReady` events and `props.containerApi` in user defined panels. This API contains actions and variable related to the component as a whole.
|
||||
|
||||
```tsx
|
||||
const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => {
|
||||
React.useEffect(() => {
|
||||
const disposable = props.api.onDidActiveChange((event) => {
|
||||
console.log(`is panel active: ${event.isActive}`);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose(); // remember to dispose of any subscriptions
|
||||
};
|
||||
}, [props.api]);
|
||||
|
||||
const addAnotherPanel = React.useCallback(() => {
|
||||
props.containerApi.addPanel({
|
||||
id: 'another_id',
|
||||
component: 'anotherComponent',
|
||||
});
|
||||
}, [props.containerApi]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>{`My first panel has the title: ${props.params.title}`}</span>
|
||||
<button onClick={addAnotherPanel}>Add Panel</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Serialization
|
||||
|
||||
All components support `toJSON(): T` which returns a Typed object representation of the components state. This same Typed object can be used to deserialize a view using `fromJSON(object: T): void`.
|
||||
|
||||
## Auto resizing
|
||||
|
||||
`SplitviewReact`, `GridviewReact`, `PaneviewReact` and `DockviewReact` will all automatically resize to fill the size of their parent element.
|
||||
Internally this is achieved using a [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) which some users may need to polyfill.
|
||||
You can disable this by settings the `disableAutoResizing` prop to be `true`.
|
||||
|
||||
You can manually resize a component using the API method `layout(width: number, height: number): void`.
|
||||
An advanced case may use this in conjunction with `disableAutoResizing=true` to allow a parent component to have ultimate control over the dimensions of the component.
|
||||
|
||||
## Events
|
||||
|
||||
Many API properties can be listened on using the `Event` pattern. For example `api.onDidFocusChange(() => {...})`.
|
||||
You should dispose of any event listeners you create cleaning up any listeners you would have created.
|
||||
|
||||
```tsx
|
||||
React.useEffect(() => {
|
||||
const disposable = api.onDidFocusChange(() => {
|
||||
// write some code
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
|
||||
## Proportional layout
|
||||
|
||||
The `proportionalLayout` property indicates the expected behaviour of the component as it's container resizes, should all views resize equally or should just one view expand to fill the new space. `proportionalLayout` can be set as a property on `SplitviewReact` and `GridviewReact` components.
|
||||
Although not configurable on `DockviewReact` and `PaneviewReact` these both behave as if `proportionalLayout=true` was set for them.
|
||||
|
||||
<SimpleSplitview2 proportional={false} />
|
||||
|
||||
<SimpleSplitview2 proportional={true} />
|
||||
|
||||
## Browser support
|
||||
|
||||
dockview is intended to support all major browsers. Some users may require a polyfill for [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver).
|
9
packages/docs/versioned_docs/version-1.6.0/components/_category_.json
vendored
Normal file
9
packages/docs/versioned_docs/version-1.6.0/components/_category_.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"label": "Components",
|
||||
"collapsible": true,
|
||||
"collapsed": false,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"title": "Components"
|
||||
}
|
||||
}
|
663
packages/docs/versioned_docs/version-1.6.0/components/dockview.mdx
vendored
Normal file
663
packages/docs/versioned_docs/version-1.6.0/components/dockview.mdx
vendored
Normal file
@ -0,0 +1,663 @@
|
||||
---
|
||||
description: Dockview Documentation
|
||||
---
|
||||
|
||||
import { SimpleDockview } from '@site/src/components/simpleDockview';
|
||||
import {
|
||||
RenderingDockview,
|
||||
Checkbox,
|
||||
} from '@site/src/components/dockview/rendering';
|
||||
import { DndDockview } from '@site/src/components/dockview/dnd';
|
||||
import { EventsDockview } from '@site/src/components/dockview/events';
|
||||
import { ContextMenuDockview } from '@site/src/components/dockview/contextMenu';
|
||||
import { NestedDockview } from '@site/src/components/dockview/nested';
|
||||
import { CustomHeadersDockview } from '@site/src/components/dockview/customHeaders';
|
||||
import { ResizeDockview } from '@site/src/components/dockview/resize';
|
||||
import { DockviewGroupControl } from '@site/src/components/dockview/groupControl';
|
||||
import { DockviewWatermark } from '@site/src/components/dockview/watermark';
|
||||
import { DockviewPersistance } from '@site/src/components/dockview/persistance';
|
||||
import {
|
||||
DockviewNative,
|
||||
DockviewNative2,
|
||||
} from '@site/src/components/dockview/native';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
# Dockview
|
||||
|
||||
## Introduction
|
||||
|
||||
Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels.
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimpleDockview />
|
||||
</div>
|
||||
|
||||
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
|
||||
|
||||
You can create a Dockview through the use of the `ReactDockview` component.
|
||||
|
||||
```tsx
|
||||
import { ReactDockview } from 'dockview';
|
||||
```
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| --------------------- | ------------------------------------ | -------- | --------- | ------------------------------------------------------------ |
|
||||
| onReady | (event: SplitviewReadyEvent) => void | No | | |
|
||||
| components | object | No | | |
|
||||
| tabComponents | object | Yes | | |
|
||||
| watermarkComponent | object | Yes | | |
|
||||
| hideBorders | boolean | Yes | false | |
|
||||
| className | string | Yes | '' | |
|
||||
| disableAutoResizing | boolean | Yes | false | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
| onDidDrop | Event | Yes | false | |
|
||||
| showDndOverlay | Event | Yes | false | |
|
||||
| defaultTabComponent | object | Yes | | |
|
||||
| groupControlComponent | object | Yes | | |
|
||||
| tabHeight | number | Yes | | |
|
||||
| singleTabMode | 'fullwidth' \| 'default' | Yes | 'default' | |
|
||||
|
||||
## Dockview API
|
||||
|
||||
The Dockview API is exposed both at the `onReady` event and on each panel through `props.containerApi`.
|
||||
Through this API you can control general features of the component and access all added panels.
|
||||
|
||||
```tsx title="Dockview API via Panel component"
|
||||
const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
// props.containerApi...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
```tsx title="Dockview API via the onReady callback"
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
// event.api...
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | ---------------------------------------------------- | -------------------------------------------------------- |
|
||||
| height | `number` | Component pixel height |
|
||||
| width | `number` | Component pixel width |
|
||||
| minimumHeight | `number` | |
|
||||
| maximumHeight | `number` | |
|
||||
| maximumWidth | `number` | |
|
||||
| maximumWidth | `number` | |
|
||||
| length | `number` | Number of panels |
|
||||
| size | `number` | Number of Groups |
|
||||
| panels | `IDockviewPanel[]` | |
|
||||
| groups | `GroupPanel[]` | |
|
||||
| activePanel | `IDockviewPanel \| undefined` | |
|
||||
| activeGroup | `IDockviewPanel \| undefined` | |
|
||||
| | | |
|
||||
| onDidLayoutChange | `Event<void>` | |
|
||||
| onDidLayoutFromJSON | `Event<void>` | |
|
||||
| onDidAddGroup | `Event<GroupPanel>` | |
|
||||
| onDidRemoveGroup | `Event<GroupPanel>` | |
|
||||
| onDidActiveGroupChange | `Event<GroupPanel \| undefined>` | |
|
||||
| onDidAddPanel | `Event<IDockviewPanel>` | |
|
||||
| onDidRemovePanel | `Event<IDockviewPanel>` | |
|
||||
| onDidActivePanelChange | `Event<IDockviewPanel \| undefined>` | |
|
||||
| onDidDrop | `Event<DockviewDropEvent` | |
|
||||
| | | |
|
||||
| addPanel | `addPanel(options: AddPanelOptions): IDockviewPanel` | |
|
||||
| getPanel | `(id: string) \| IDockviewPanel \| undefined` | |
|
||||
| addGroup | `(options? AddGroupOptions): void` | |
|
||||
| closeAllGroups | `(): void` | |
|
||||
| removeGroup | `(group: GroupPanel): void` | |
|
||||
| getGroup | `(id: string): GroupPanel \| undefined` | |
|
||||
| | | |
|
||||
| getTabHeight | `(): number \| undefined` | |
|
||||
| setTabHeight | `(height: number \| undefined): void` | |
|
||||
| updateOptions | `(options:SplitviewComponentUpdateOptions): void` | |
|
||||
| focus | `(): void` | |
|
||||
| layout | `(width: number, height:number): void` | <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
| fromJSON | `(data: SerializedDockview): void` | <Link to="../basics/#serialization">Serialization</Link> |
|
||||
| toJSON | `(): SerializedDockview` | <Link to="../basics/#serialization">Serialization</Link> |
|
||||
| clear | `(): void` | Clears the current layout |
|
||||
|
||||
## Dockview Panel API
|
||||
|
||||
```tsx
|
||||
const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
// props.api...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | ----------------------------------------------------------- | ---------------- |
|
||||
| id | `string` | Panel id |
|
||||
| isFocused | `boolean` | Is panel focused |
|
||||
| isActive | `boolean` | Is panel active |
|
||||
| width | `number` | Panel width |
|
||||
| height | `number` | Panel height |
|
||||
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | |
|
||||
| onDidFocusChange | `Event<FocusEvent>` | |
|
||||
| onDidVisibilityChange | `Event<VisibilityEvent>` | |
|
||||
| onDidActiveChange | `Event<ActiveEvent>` | |
|
||||
| setActive | `(): void` | |
|
||||
| | | |
|
||||
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | |
|
||||
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
|
||||
| setSize | `(event: SizeEvent): void` | |
|
||||
| | | |
|
||||
| group | `GroupPanel | undefined` |
|
||||
| isGroupActive | `boolean` | |
|
||||
| title | `string` | |
|
||||
| suppressClosable | `boolean` | |
|
||||
| close | `(): void` | |
|
||||
| setTitle | `(title: string): void` | |
|
||||
|
||||
## Layout Persistance
|
||||
|
||||
Layouts are loaded and saved via to `fromJSON` and `toJSON` methods on the Dockview api.
|
||||
The api also exposes an event `onDidLayoutChange` you can listen on to determine when the layout has changed.
|
||||
Below are some snippets showing how you might load from and save to localStorage.
|
||||
|
||||
```tsx title="Saving the layout state to localStorage"
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disposable = api.onDidLayoutChange(() => {
|
||||
const layout = api.toJSON();
|
||||
|
||||
localStorage.setItem(
|
||||
'dockview_persistance_layout',
|
||||
JSON.stringify(layout)
|
||||
);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [api]);
|
||||
```
|
||||
|
||||
```tsx title="Loading a layout from localStorage"
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const layoutString = localStorage.getItem('dockview_persistance_layout');
|
||||
|
||||
let success = false;
|
||||
|
||||
if (layoutString) {
|
||||
try {
|
||||
const layout = JSON.parse(layoutString);
|
||||
event.api.fromJSON(layout);
|
||||
success = true;
|
||||
} catch (err) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// do something if there is no layout or there was a loading error
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Here is an example using the above code loading from and saving to localStorage.
|
||||
If you refresh the page you should notice your layout is loaded as you left it.
|
||||
|
||||
<DockviewPersistance />
|
||||
|
||||
## Resizing
|
||||
|
||||
Each Dockview contains of a number of groups and each group has a number of panels.
|
||||
Logically a user may want to resize a panel, but this translates to resizing the group which contains that panel.
|
||||
|
||||
You can set the size of a panel using `props.api.setSize(...)`.
|
||||
You can also set the size of the group associated with the panel using `props.api.group.api.setSize(...)` although this isn't recommended
|
||||
due to the clunky syntax.
|
||||
|
||||
```tsx
|
||||
// it's mandatory to provide either a height or a width, providing both is optional
|
||||
props.api.setSize({
|
||||
height: 100,
|
||||
width: 200,
|
||||
});
|
||||
|
||||
// you could also resize the panels group, although not recommended it achieved the same result
|
||||
props.api.group.api.setSize({
|
||||
height: 100,
|
||||
width: 200,
|
||||
});
|
||||
```
|
||||
|
||||
You can see an example invoking both approaches below.
|
||||
|
||||
<ResizeDockview />
|
||||
|
||||
## Watermark
|
||||
|
||||
When the dockview is empty you may want to display some fallback content, this is refered to as the `watermark`.
|
||||
By default there the watermark has no content but you can provide as a prop to `DockviewReact` a `watermarkComponent`
|
||||
which will be rendered when there are no panels or groups.
|
||||
|
||||
<DockviewWatermark />
|
||||
|
||||
## Drag And Drop
|
||||
|
||||
### Built-in behaviours
|
||||
|
||||
Dockview supports a wide variety of built-in Drag and Drop possibilities.
|
||||
Below are some examples of the operations you can perform.
|
||||
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_tab.svg')} />
|
||||
|
||||
> Drag a tab onto another tab to place it inbetween existing tabs.
|
||||
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_empty_space.svg')} />
|
||||
|
||||
> Drag a tab to the right of the last tab to place it after the existing tabs.
|
||||
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/add_to_group.svg')} />
|
||||
|
||||
> Drag a group onto an existing group to merge the two groups.
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'space-around' }}>
|
||||
<img style={{ width: '40%' }} src={useBaseUrl('/img/drop_positions.svg')} />
|
||||
<img
|
||||
style={{ width: '40%' }}
|
||||
src={useBaseUrl('/img/magnet_drop_positions.svg')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
> Drag into the left/right/top/bottom target zone of a panel to create a new group in the selected direction.
|
||||
|
||||
> Drag into the center of a panel to add to that group.
|
||||
|
||||
> Drag to the edge of the dockview component to create a new group on the selected edge.
|
||||
|
||||
### Extended behaviours
|
||||
|
||||
For interaction with the Drag events directly the component exposes some method to help determine whether external drag events should be interacted with or not.
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* called when an ondrop event which does not originate from the dockview libray and
|
||||
* passes the showDndOverlay condition occurs
|
||||
**/
|
||||
const onDidDrop = (event: DockviewDropEvent) => {
|
||||
const { group } = event;
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'test',
|
||||
component: 'default',
|
||||
position: {
|
||||
referencePanel: group.activePanel.id,
|
||||
direction: 'within',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* called for drag over events which do not originate from the dockview library
|
||||
* allowing the developer to decide where the overlay should be shown for a
|
||||
* particular drag event
|
||||
**/
|
||||
const showDndOverlay = (event: DockviewDndOverlayEvent) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<DockviewReact
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
className="dockview-theme-abyss"
|
||||
onDidDrop={onDidDrop}
|
||||
showDndOverlay={showDndOverlay}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
<DndDockview />
|
||||
|
||||
## Panels
|
||||
|
||||
### Add Panel
|
||||
|
||||
Using the dockview API you can access the `addPanel` method which returns an instance of the created panel.
|
||||
The minimum method signature is:
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'my_unique_panel_id',
|
||||
component: 'my_component',
|
||||
});
|
||||
```
|
||||
|
||||
where `id` is the unique id of the panel and `component` is the implenentation which
|
||||
will be used to render the panel. You will have registered this using the `components` prop of the `DockviewReactComponent` component.
|
||||
|
||||
You can optionally provide a `tabComponent` parameters to the `addPanel` method which will render the tab using a custom renderer.
|
||||
You will have registered this using the `tabComponents` prop of the `DockviewReactComponent` component.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'my_unique_panel_id',
|
||||
component: 'my_component',
|
||||
tabComponent: 'my_tab_component',
|
||||
});
|
||||
```
|
||||
|
||||
You can pass properties to the panel using the `params` key.
|
||||
You can update these properties through the panels `api` object and its `updateParameters` method.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'my_unique_panel_id',
|
||||
component: 'my_component',
|
||||
params: {
|
||||
myCustomKey: 'my_custom_value',
|
||||
},
|
||||
});
|
||||
|
||||
panel.api.updateParameters({
|
||||
myCustomKey: 'my_custom_value',
|
||||
myOtherCustomKey: 'my_other_custom_key',
|
||||
});
|
||||
```
|
||||
|
||||
> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters
|
||||
> you want the panel to receive.
|
||||
|
||||
Finally `addPanel` accepts a `position` object which tells dockview where to place the panel.
|
||||
|
||||
- This object optionally accepts either a `referencePanel` or `referenceGroup` which can be the associated id as a string
|
||||
or the panel/group object reference.
|
||||
- This object accepts a `direction` property which dictates where,
|
||||
relative to the provided reference the new panel will be placed.
|
||||
|
||||
> If neither a `referencePanel` or `referenceGroup` then the provided `direction` will be treated as absolute.
|
||||
|
||||
> If no `direction` is provided the library will place the new panel in a pre-determined position.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
const panel2 = api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
position: {
|
||||
referencePanel: panel1,
|
||||
direction: 'right',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Panel Rendering
|
||||
|
||||
By default `DockviewReact` only adds to the DOM those panels that are visible,
|
||||
if a panel is not the active tab and not shown the contents of the hidden panel will be removed from the DOM.
|
||||
|
||||
However the React Components associated with each panel are only created once and will always exist for as long as the panel exists, hidden or not.
|
||||
|
||||
> For example this means that any hooks in those components will run whether the panel is visible or not which may lead to excessive background work depending
|
||||
> on the panels implementation.
|
||||
|
||||
This is the default behaviour to ensure the greatest flexibility for the user but through the panels `props.api` you can listen to the visiblity state of the panel
|
||||
and write additional logic to optimize your application.
|
||||
|
||||
For example if you wanted to unmount the React Components when the panel is not visible you could create a Higher-Order-Component that listens to the panels
|
||||
visiblity state and only renders the panel when visible.
|
||||
|
||||
```tsx title="Only rendering the React Component when the panel is visible, otherwise rendering a null React Component"
|
||||
import { IDockviewPanelProps } from 'dockview';
|
||||
import * as React from 'react';
|
||||
|
||||
function RenderWhenVisible(
|
||||
component: React.FunctionComponent<IDockviewPanelProps>
|
||||
) {
|
||||
const HigherOrderComponent = (props: IDockviewPanelProps) => {
|
||||
const [visible, setVisible] = React.useState<boolean>(
|
||||
props.api.isVisible
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.api.onDidVisibilityChange((event) =>
|
||||
setVisible(event.isVisible)
|
||||
);
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [props.api]);
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return React.createElement(component, props);
|
||||
};
|
||||
return HigherOrderComponent;
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
const components = { default: RenderWhenVisible(MyComponent) };
|
||||
```
|
||||
|
||||
Toggling the checkbox you can see that when you only render those panels which are visible the underling React component is destroyed when it becomes hidden and re-created when it becomes visible.
|
||||
|
||||
<Checkbox />
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<RenderingDockview renderVisibleOnly={false} />
|
||||
</div>
|
||||
|
||||
## Headers
|
||||
|
||||
### Custom Tab Headers
|
||||
|
||||
You can provide custom renderers for your tab headers for maximum customization.
|
||||
A default implementation of `DockviewDefaultTab` is provided should you only wish to attach minor
|
||||
changes and events that do not alter the default behaviour, for example to add a custom context menu event
|
||||
handler.
|
||||
|
||||
```tsx title="Attaching a custom context menu event handlers to a custom header"
|
||||
import { IDockviewPanelHeaderProps, DockviewDefaultTab } from 'dockview';
|
||||
|
||||
const MyCustomheader = (props: IDockviewPanelHeaderProps) => {
|
||||
const onContextMenu = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
alert('context menu');
|
||||
};
|
||||
return <DockviewDefaultTab onContextMenu={onContextMenu} {...props} />;
|
||||
};
|
||||
```
|
||||
|
||||
You are also free to define a custom renderer entirely from scratch and not make use of the `DockviewDefaultTab` component.
|
||||
To use a custom renderer you can must register a collection of tab components.
|
||||
|
||||
```tsx
|
||||
const tabComponents = {
|
||||
myCustomHeader: MyCustomHeader,
|
||||
};
|
||||
|
||||
return <DockviewReact tabComponents={tabComponents} ... />;
|
||||
```
|
||||
|
||||
```tsx
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
tabComponent: 'myCustomHeader', // <-- your registered renderers
|
||||
title: 'Panel 1',
|
||||
});
|
||||
```
|
||||
|
||||
You can also override the default tab renderer which will be used when no `tabComponent` is provided to the `addPanel` function.
|
||||
|
||||
```tsx
|
||||
<DockviewReact defaultTabComponent={MyCustomHeader} ... />;
|
||||
```
|
||||
|
||||
As a simple example the below attachs a custom event handler for the context menu on all tabs as a default tab renderer
|
||||
|
||||
The below example uses a custom tab renderer to reigster a popover when the user right clicked on a tab.
|
||||
This still makes use of the `DockviewDefaultTab` since it's only a minor change.
|
||||
|
||||
<CustomHeadersDockview />
|
||||
|
||||
### Default Tab Title
|
||||
|
||||
If you are using the default tab renderer you can set the title of a tab when creating it
|
||||
|
||||
```tsx
|
||||
api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'my_component',
|
||||
title: 'my_custom_title', // <-- special param for title
|
||||
});
|
||||
```
|
||||
|
||||
You can update the title through the panel api which can be accessed via `props.api` if you are inside the panel
|
||||
component or via `api.getPanel('panel1').api` if you are accessing from outside of the panel component.
|
||||
|
||||
```tsx
|
||||
api.updateTitle('my_new_custom_title');
|
||||
```
|
||||
|
||||
> Note this only works when using the default tab implementation.
|
||||
|
||||
### Custom Tab Title
|
||||
|
||||
If you are using a custom tab implementation you should pass variables through as a parameter and render them
|
||||
through your tab components implementation.
|
||||
|
||||
```tsx title="Add a panel with custom parameters"
|
||||
api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'my_component',
|
||||
tabComponent: 'my_tab',
|
||||
params: {
|
||||
myTitle: 'Window 2', // <-- passing a variable to use as a title
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```tsx title="Accessing custom parameters from a custom tab renderer"
|
||||
const tabComponents = {
|
||||
default: (props: IDockviewPanelHeaderProps<{ myTitle: string }>) => {
|
||||
const title = props.params.myTitle; // <-- accessing my custom varaible
|
||||
return <div>{/** tab implementation as chosen by developer */}</div>;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Hidden Headers
|
||||
|
||||
You may wish to hide the header section of a group. This can achieved through the `hidden` variable on `panel.group.header`.
|
||||
|
||||
```tsx
|
||||
panel.group.header.hidden = true;
|
||||
```
|
||||
|
||||
### Full width tabs
|
||||
|
||||
`DockviewReactComponent` accepts the prop `singleTabMode`. If set `singleTabMode=fullwidth` then when there is only one tab in a group this tab will expand
|
||||
to the entire width of the group. For example:
|
||||
|
||||
> This can be conmbined with <Link to="./dockview/#locked-group">Locked Groups</Link> to create an application that feels more like a Window Manager
|
||||
> rather than a collection of groups and tabs.
|
||||
|
||||
```tsx
|
||||
<DockviewReactComponent singleTabMode="fullwidth" {...otherProps} />
|
||||
```
|
||||
|
||||
<DockviewNative />
|
||||
|
||||
## Groups
|
||||
|
||||
### Locked group
|
||||
|
||||
Locking a group will disable all drop events for this group ensuring no additional panels can be added to the group through drop events.
|
||||
You can still add groups to a locked panel programatically using the API though.
|
||||
|
||||
```tsx
|
||||
panel.group.locked = true;
|
||||
```
|
||||
|
||||
### Group Controls Panel
|
||||
|
||||
`DockviewReact` accepts a prop `groupControlComponent` which expects a React component whos props are `IDockviewGroupControlProps`.
|
||||
This control will be rendered inside the header bar on the right hand side for each group of tabs.
|
||||
|
||||
```tsx
|
||||
const Component: React.FunctionComponent<IDockviewGroupControlProps> = () => {
|
||||
return <div>{'...'}</div>;
|
||||
};
|
||||
|
||||
return <DockviewReact {...props} groupControlComponent={Component} />;
|
||||
```
|
||||
|
||||
As a simple example the below uses the `groupControlComponent` to render a small control that indicates whether the group
|
||||
is active and which panel is active in that group.
|
||||
|
||||
```tsx
|
||||
const GroupControlComponent = (props: IDockviewGroupControlProps) => {
|
||||
const isGroupActive = props.isGroupActive;
|
||||
const activePanel = props.activePanel;
|
||||
|
||||
return (
|
||||
<div className="dockview-groupcontrol-demo">
|
||||
<span
|
||||
className="dockview-groupcontrol-demo-group-active"
|
||||
style={{
|
||||
background: isGroupActive ? 'green' : 'red',
|
||||
}}
|
||||
>
|
||||
{isGroupActive ? 'Group Active' : 'Group Inactive'}
|
||||
</span>
|
||||
<span className="dockview-groupcontrol-demo-active-panel">{`activePanel: ${
|
||||
activePanel?.id || 'null'
|
||||
}`}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
<DockviewGroupControl />
|
||||
|
||||
## Events
|
||||
|
||||
<EventsDockview />
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Nested Dockviews
|
||||
|
||||
You can safely create multiple dockview instances within one page and nest dockviews within other dockviews.
|
||||
If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`.
|
||||
|
||||
<NestedDockview />
|
||||
|
||||
### Example
|
||||
|
||||
hello
|
||||
|
||||
<DockviewNative2 />
|
120
packages/docs/versioned_docs/version-1.6.0/components/gridview.mdx
vendored
Normal file
120
packages/docs/versioned_docs/version-1.6.0/components/gridview.mdx
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
---
|
||||
description: Gridview Documentation
|
||||
---
|
||||
|
||||
import { SimpleGridview } from '@site/src/components/simpleGridview';
|
||||
import { EventsGridview } from '@site/src/components/gridview/events';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Gridview
|
||||
|
||||
## Introduction
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimpleGridview />
|
||||
</div>
|
||||
|
||||
## GridviewReact Component
|
||||
|
||||
```tsx
|
||||
import { ReactGridview } from 'dockview';
|
||||
```
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| ------------------- | ------------------------------------ | -------- | ---------------------- | ------------------------------------------------------------------------ |
|
||||
| onReady | (event: SplitviewReadyEvent) => void | No | | |
|
||||
| components | object | No | | |
|
||||
| orientation | Orientation | Yes | Orientation.HORIZONTAL | |
|
||||
| proportionalLayout | boolean | Yes | true | See <Link to="../basics/#proportional-layout">Proportional layout</Link> |
|
||||
| hideBorders | boolean | Yes | false | |
|
||||
| className | string | Yes | '' | |
|
||||
| disableAutoResizing | boolean | Yes | false | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
|
||||
## Gridview API
|
||||
|
||||
```tsx
|
||||
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
|
||||
// props.containerApi...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
```tsx
|
||||
const onReady = (event: GridviewReadyEvent) => {
|
||||
// event.api...
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| height | `number` | Component pixel height |
|
||||
| width | `number` | Component pixel width |
|
||||
| minimumHeight | `number` | |
|
||||
| maximumHeight | `number` | |
|
||||
| maximumWidth | `number` | |
|
||||
| maximumWidth | `number` | |
|
||||
| length | `number` | Number of panels |
|
||||
| panels | `ISplitviewPanel[]` | all panels |
|
||||
| orientation | `Orientation` | |
|
||||
| | | |
|
||||
| onDidLayoutChange | `Event<void>` | Fires on layout change |
|
||||
| onDidLayoutFromJSON | `Event<void>` | Fires of layout change caused by a fromJSON deserialization call |
|
||||
| onDidAddPanel | `Event<IGridviewPanel>` | Fires when a view is added |
|
||||
| onDidRemovePanel | `Event<IGridviewPanel>` | Fires when a view is removed |
|
||||
| onDidActivePanelChange | `Event<IGridviewPanel \| undefined>` | Fires when the active group changes |
|
||||
| | | |
|
||||
| addPanel | `addPanel(options: AddComponentOptions): IGridviewPanel` | |
|
||||
| removePanel | `(panel: IGridviewPanel, sizing?: Sizing): void` | |
|
||||
| movePanel | `(panel: IGridviewPanel, options: {direction: Direction, refernece:string, size?: number}): void` | |
|
||||
| getPanel | `(id: string) \| IGridviewPanel \| undefined` | |
|
||||
| | | |
|
||||
| updateOptions | `(options:SplitviewComponentUpdateOptions): void` | |
|
||||
| focus | `(): void` | Focus the active panel, if exists |
|
||||
| layout | `(width: number, height:number): void` | <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
| fromJSON | `(data: SerializedGridview): void` | <Link to="../basics/#serialization">Serialization</Link> |
|
||||
| toJSON | `(): SerializedGridview` | <Link to="../basics/#serialization">Serialization</Link> |
|
||||
| clear | `(): void` | Clears the current layout |
|
||||
|
||||
## Gridview Panel API
|
||||
|
||||
```tsx
|
||||
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
|
||||
// props.api...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | ----------------------------------------------------------- | ---------------- |
|
||||
| id | `string` | Panel id |
|
||||
| isFocused | `boolean` | Is panel focsed |
|
||||
| isActive | `boolean` | Is panel active |
|
||||
| isVisible | `boolean` | Is panel visible |
|
||||
| width | `number` | Panel width |
|
||||
| height | `number` | Panel height |
|
||||
| | | |
|
||||
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | |
|
||||
| onDidFocusChange | `Event<FocusEvent>` | |
|
||||
| onDidVisibilityChange | `Event<VisibilityEvent>` | |
|
||||
| onDidActiveChange | `Event<ActiveEvent>` | |
|
||||
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | |
|
||||
| | | |
|
||||
| setVisible | `(isVisible: boolean): void` | |
|
||||
| setActive | `(): void` | |
|
||||
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
|
||||
| setSize | `(event: SizeEvent): void` | |
|
||||
|
||||
## Events
|
||||
|
||||
`GridviewReact` exposes a number of events that the developer can listen to and below is a simple example with a log panel showing those events that occur.
|
||||
|
||||
<EventsGridview />
|
285
packages/docs/versioned_docs/version-1.6.0/components/paneview.mdx
vendored
Normal file
285
packages/docs/versioned_docs/version-1.6.0/components/paneview.mdx
vendored
Normal file
@ -0,0 +1,285 @@
|
||||
---
|
||||
description: Paneview Documentation
|
||||
---
|
||||
|
||||
import { SimplePaneview } from '@site/src/components/simplePaneview';
|
||||
import { CustomHeaderPaneview } from '@site/src/components/paneview/customHeader';
|
||||
import { DragAndDropPaneview } from '@site/src/components/paneview/dragAndDrop';
|
||||
import { SideBySidePaneview } from '@site/src/components/paneview/sideBySide';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Paneview
|
||||
|
||||
A paneview is a collapsed collection of vertically stacked panels and panel headers.
|
||||
The panel header will always remain visible however the panel will only be visible when the panel is expanded.
|
||||
|
||||
:::info
|
||||
|
||||
Paneview panels can be re-ordered by dragging and dropping the panel headers.
|
||||
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '400px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimplePaneview />
|
||||
</div>
|
||||
|
||||
```tsx title="Simple Paneview example"
|
||||
import {
|
||||
IPaneviewPanelProps,
|
||||
PaneviewReact,
|
||||
PaneviewReadyEvent,
|
||||
} from 'dockview';
|
||||
|
||||
const components = {
|
||||
default: (props: IPaneviewPanelProps<{ title: string }>) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: '10px',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgb(60,60,60)',
|
||||
}}
|
||||
>
|
||||
{props.params.title}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
SimplePaneview = () => {
|
||||
const onReady = (event: PaneviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 1',
|
||||
},
|
||||
title: 'Panel 1',
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 2',
|
||||
},
|
||||
title: 'Panel 2',
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 3',
|
||||
},
|
||||
title: 'Panel 3',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PaneviewReact
|
||||
components={components}
|
||||
headerComponents={headerComponents}
|
||||
onReady={onReady}
|
||||
className="dockview-theme-abyss"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## PaneviewReact Component
|
||||
|
||||
You can create a Paneview through the use of the `ReactPaneview` component.
|
||||
|
||||
```tsx
|
||||
import { ReactPaneview } from 'dockview';
|
||||
```
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| ------------------- | ------------------------------------ | -------- | ------- | -------------------------------------------------------- |
|
||||
| onReady | (event: SplitviewReadyEvent) => void | No | | |
|
||||
| components | object | No | | |
|
||||
| headerComponents | object | Yes | | |
|
||||
| className | string | Yes | '' | |
|
||||
| disableAutoResizing | boolean | Yes | false | <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
| disableDnd | boolean | Yes | false | |
|
||||
| onDidDrop | Event | Yes | | |
|
||||
|
||||
## Paneview API
|
||||
|
||||
The Paneview API is exposed both at the `onReady` event and on each panel through `props.containerApi`.
|
||||
Through this API you can control general features of the component and access all added panels.
|
||||
|
||||
```tsx title="Paneview API via Panel component"
|
||||
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
|
||||
// props.containerApi...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
```tsx title="Paneview API via the onReady callback"
|
||||
const onReady = (event: GridviewReadyEvent) => {
|
||||
// event.api...
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| height | `number` | Component pixel height |
|
||||
| width | `number` | Component pixel width |
|
||||
| minimumSize | `number` | The sum of the `minimumSize` property for each panel |
|
||||
| maximumSize | `number` | The sum of the `maximumSize` property for each panel |
|
||||
| length | `number` | Number of panels |
|
||||
| panels | `IPaneviewPanel[]` | All panels |
|
||||
| | | |
|
||||
| onDidLayoutChange | `Event<void>` | Fires on layout change |
|
||||
| onDidLayoutFromJSON | `Event<void>` | Fires of layout change caused by a fromJSON deserialization call |
|
||||
| onDidAddView | `Event<IPaneviewPanel>` | Fires when a view is added |
|
||||
| onDidRemoveView | `Event<IPaneviewPanel>` | Fires when a view is removed |
|
||||
| onDidDrop | `Event<PaneviewDropEvent` | Fires on an external drop event (See <Link to="./paneview/#drag-and-drop">Drag and Drop</Link>) |
|
||||
| | | |
|
||||
| addPanel | `addPanel(options: AddPaneviewComponentOptions): IPaneviewPanel` | |
|
||||
| removePanel | `(panel: IPaneviewPanel): void` | |
|
||||
| movePanel | `(from: number, to: number): void` | |
|
||||
| getPanel | `(id:string): IPaneviewPanel \| undefined` | |
|
||||
| | | |
|
||||
| focus | `(): void` | Focus the active panel, if exists |
|
||||
| layout | `(width: number, height:number): void` | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
| fromJSON | `(data: SerializedPaneview): void` | <Link to="../basics/#serialization">Serialization</Link> |
|
||||
| toJSON | `(): SerializedPaneview` | <Link to="../basics/#serialization">Serialization</Link> |
|
||||
| clear | `(): void` | Clears the current layout |
|
||||
|
||||
## Paneview Panel API
|
||||
|
||||
```tsx
|
||||
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
|
||||
// props.api...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | ----------------------------------------------------------- | ---------------- |
|
||||
| id | `string` | Panel id |
|
||||
| isFocused | `boolean` | Is panel focsed |
|
||||
| isActive | `boolean` | Is panel active |
|
||||
| isVisible | `boolean` | Is panel visible |
|
||||
| width | `number` | Panel width |
|
||||
| height | `number` | Panel height |
|
||||
| | |
|
||||
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | |
|
||||
| onDidFocusChange | `Event<FocusEvent>` | |
|
||||
| onDidVisibilityChange | `Event<VisibilityEvent>` | |
|
||||
| onDidActiveChange | `Event<ActiveEvent>` | |
|
||||
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | |
|
||||
| | |
|
||||
| setVisible | `(isVisible: boolean): void` | |
|
||||
| setActive | `(): void` | |
|
||||
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
|
||||
| setSize | `(event: SizeEvent): void` | |
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Header
|
||||
|
||||
You can provide a custom component to render an alternative header.
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '400px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<CustomHeaderPaneview />
|
||||
</div>
|
||||
|
||||
You can provide a `headerComponent` option when creating a panel to tell the library to use a custom header component.
|
||||
|
||||
```tsx
|
||||
const onReady = (event: PaneviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
headerComponent: 'myHeaderComponent',
|
||||
params: {
|
||||
valueA: 'A',
|
||||
},
|
||||
title: 'Panel 1',
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
This header must be defined in the collection of components provided to the `headerComponents` props for `ReactPaneivew`
|
||||
|
||||
```tsx
|
||||
import { IPaneviewPanelProps } from 'dockview';
|
||||
|
||||
const MyHeaderComponent = (props: IPaneviewPanelProps<{ title: string }>) => {
|
||||
const [expanded, setExpanded] = React.useState<boolean>(
|
||||
props.api.isExpanded
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.api.onDidExpansionChange((event) => {
|
||||
setExpanded(event.isExpanded);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onClick = () => {
|
||||
props.api.setExpanded(!expanded);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: '10px',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgb(60,60,60)',
|
||||
}}
|
||||
>
|
||||
<a
|
||||
onClick={onClick}
|
||||
className={expanded ? 'expanded' : 'collapsed'}
|
||||
/>
|
||||
<span>{props.params.title}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const headerComponents = { myHeaderComponent: MyHeaderComponent };
|
||||
```
|
||||
|
||||
### Drag And Drop
|
||||
|
||||
If you provide the `PaneviewReact` component with the prop `onDidDrop` you will be able to interact with custom drop events.
|
||||
|
||||
<DragAndDropPaneview />
|
||||
|
||||
### Interactions
|
||||
|
||||
You can safely create multiple paneview instances within one page. They will not interact with each other by default.
|
||||
|
||||
If you wish to interact with the drop event from one paneview instance in another paneview instance you can implement the `showDndOverlay` and `onDidDrop` props on `PaneviewReact`.
|
||||
|
||||
As an example see how dragging a header from one control to another will only trigger an interactable event for the developer if the checkbox is enabled.
|
||||
|
||||
<SideBySidePaneview />
|
246
packages/docs/versioned_docs/version-1.6.0/components/splitview.mdx
vendored
Normal file
246
packages/docs/versioned_docs/version-1.6.0/components/splitview.mdx
vendored
Normal file
@ -0,0 +1,246 @@
|
||||
---
|
||||
description: Splitview Documentation
|
||||
---
|
||||
|
||||
import { SimpleSplitview } from '@site/src/components/simpleSplitview';
|
||||
import { SplitviewExample1 } from '@site/src/components/splitview/active';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Splitview
|
||||
|
||||
## Introduction
|
||||
|
||||
A Splitview is a collection of resizable horizontally or vertically stacked panels.
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '100px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimpleSplitview />
|
||||
</div>
|
||||
|
||||
```tsx title="Simple Splitview example"
|
||||
import {
|
||||
ISplitviewPanelProps,
|
||||
Orientation,
|
||||
SplitviewReact,
|
||||
SplitviewReadyEvent,
|
||||
} from 'dockview';
|
||||
|
||||
const components = {
|
||||
default: (props: ISplitviewPanelProps<{ title: string }>) => {
|
||||
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
|
||||
},
|
||||
};
|
||||
|
||||
export const SimpleSplitview = () => {
|
||||
const onReady = (event: SplitviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 1',
|
||||
},
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 2',
|
||||
},
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
params: {
|
||||
title: 'Panel 3',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SplitviewReact
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
orientation={Orientation.HORIZONTAL}
|
||||
className="dockview-theme-abyss"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## SplitviewReact Component
|
||||
|
||||
You can create a Splitview through the use of the `ReactSplitview` component.
|
||||
|
||||
```tsx
|
||||
import { ReactSplitview } from 'dockview';
|
||||
```
|
||||
|
||||
Using the `onReady` prop you can access to the component `api` and add panels either through deserialization or the individual addition of panels.
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| ------------------- | -------------------------------------- | -------- | ------------------------ | ------------------------------------------------------------------------ |
|
||||
| onReady | `(event: SplitviewReadyEvent) => void` | No | | Function |
|
||||
| components | `Record<string, ISplitviewPanelProps>` | No | | Panel renderers |
|
||||
| orientation | `Orientation` | Yes | `Orientation.HORIZONTAL` | Orientation of the Splitview |
|
||||
| proportionalLayout | `boolean` | Yes | `true` | See <Link to="../basics/#proportional-layout">Proportional layout</Link> |
|
||||
| hideBorders | `boolean` | Yes | `false` | Hide the borders between panels |
|
||||
| className | `string` | Yes | `''` | Attaches a classname |
|
||||
| disableAutoResizing | `boolean` | Yes | `false` | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
|
||||
## Splitview API
|
||||
|
||||
The Splitview API is exposed both at the `onReady` event and on each panel through `props.containerApi`.
|
||||
Through this API you can control general features of the component and access all added panels.
|
||||
|
||||
```tsx title="Splitview API via Panel component"
|
||||
const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => {
|
||||
// props.containerApi...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
```tsx title="Splitview API via the onReady callback"
|
||||
const onReady = (event: SplitviewReadyEvent) => {
|
||||
// event.api...
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------- |
|
||||
| height | `number` | Component pixel height |
|
||||
| width | `number` | Component pixel width |
|
||||
| minimumSize | `number` | The sum of the `minimumSize` property for each panel |
|
||||
| maximumSize | `number` | The sum of the `maximumSize` property for each panel |
|
||||
| length | `number` | Number of panels |
|
||||
| panels | `ISplitviewPanel[]` | All panels |
|
||||
| | | |
|
||||
| onDidLayoutChange | `Event<void>` | Fires on layout change |
|
||||
| onDidLayoutFromJSON | `Event<void>` | Fires of layout change caused by a fromJSON deserialization call |
|
||||
| onDidAddView | `Event<IView>` | Fires when a view is added |
|
||||
| onDidRemoveView | `Event<IView>` | Fires when a view is removed |
|
||||
| | | |
|
||||
| addPanel | `addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel` | |
|
||||
| removePanel | `(panel: ISplitviewPanel, sizing?: Sizing): void` | |
|
||||
| getPanel | `(id:string): ISplitviewPanel \| undefined` | |
|
||||
| movePanel | `(from: number, to: number): void` | |
|
||||
| | |
|
||||
| updateOptions | `(options: SplitviewComponentUpdateOptions): void` | |
|
||||
| focus | `(): void` | Focus the active panel, if exists |
|
||||
| layout | `(width: number, height:number): void` | See <Link to="../basics/#auto-resizing">Auto Resizing</Link> |
|
||||
| fromJSON | `(data: SerializedSplitview): void` | <Link to="../basics/#serialization">Serialization</Link> |
|
||||
| toJSON | `(): SerializedSplitview` | <Link to="../basics/#serialization">Serialization</Link> |
|
||||
| clear | `(): void` | Clears the current layout |
|
||||
|
||||
## Splitview Panel API
|
||||
|
||||
The Splitview panel API is exposed on each panel containing actions and variables specific to that panel.
|
||||
|
||||
```tsx title="Splitview panel API via Panel component"
|
||||
const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => {
|
||||
// props.api...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| ---------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| id | `string` | Panel id |
|
||||
| isFocused | `boolean` | Is panel focsed |
|
||||
| isActive | `boolean` | Is panel active |
|
||||
| isVisible | `boolean` | Is panel visible |
|
||||
| width | `number` | Panel width |
|
||||
| height | `number` | Panel height |
|
||||
| | | |
|
||||
| onDidDimensionsChange | `Event<PanelDimensionChangeEvent>` | Fires when panel dimensions change |
|
||||
| onDidFocusChange | `Event<FocusEvent>` | Fire when panel is focused and blurred |
|
||||
| onDidVisibilityChange | `Event<VisibilityEvent>` | Fires when the panels visiblity property is changed (see <Link to="./splitview/#visibility">Panel Visibility</Link>) |
|
||||
| onDidActiveChange | `Event<ActiveEvent>` | Fires when the panels active property is changed (see <Link to="./splitview/#active">Active Panel</Link>) |
|
||||
| onDidConstraintsChange | `onDidConstraintsChange: Event<PanelConstraintChangeEvent>` | Fires when the panels size contrainsts change (see <Link to="./splitview/#contraints">Panel Constraints</Link>) |
|
||||
| | | |
|
||||
| setVisible | `(isVisible: boolean): void` | |
|
||||
| setActive | `(): void` | |
|
||||
| | | |
|
||||
| setConstraints | `(value: PanelConstraintChangeEvent2): void;` | |
|
||||
| setSize | `(event: PanelSizeEvent): void` | |
|
||||
|
||||
## Advanced Features
|
||||
|
||||
Listed below are some functionalities avalaible through both the panel and component APIs. The live demo shows examples of these in real-time.
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '200px',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SplitviewExample1 />
|
||||
</div>
|
||||
|
||||
### Visibility
|
||||
|
||||
A panels visibility can be controlled and monitored through the following code.
|
||||
A panel with visibility set to `false` will remain as a part of the components list of panels but will not be rendered.
|
||||
|
||||
```tsx
|
||||
const disposable = props.api.onDidVisibilityChange(({ isVisible }) => {
|
||||
//
|
||||
});
|
||||
```
|
||||
|
||||
```tsx
|
||||
api.setVisible(true);
|
||||
```
|
||||
|
||||
### Active
|
||||
|
||||
Only one panel in the `splitview` can be the active panel at any one time.
|
||||
Setting a panel as active will set all the others as inactive.
|
||||
A focused panel is always the active panel but an active panel is not always focused.
|
||||
|
||||
```tsx
|
||||
const disposable = props.api.onDidActiveChange(({ isActive }) => {
|
||||
//
|
||||
});
|
||||
```
|
||||
|
||||
```tsx
|
||||
api.setActive();
|
||||
```
|
||||
|
||||
### Contraints
|
||||
|
||||
When adding a panel you can specify pixel size contraints
|
||||
|
||||
```tsx
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
minimumSize: 100,
|
||||
maximumSize: 1000,
|
||||
});
|
||||
```
|
||||
|
||||
These contraints can be updated throughout the lifecycle of the `splitview` using the panel API
|
||||
|
||||
```tsx
|
||||
props.api.onDidConstraintsChange(({ maximumSize, minimumSize }) => {
|
||||
//
|
||||
});
|
||||
```
|
||||
|
||||
```tsx
|
||||
api.setConstraints({
|
||||
maximumSize: 200,
|
||||
minimumSize: 400,
|
||||
});
|
||||
```
|
25
packages/docs/versioned_docs/version-1.6.0/examples.mdx
vendored
Normal file
25
packages/docs/versioned_docs/version-1.6.0/examples.mdx
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
description: Dockview examples
|
||||
---
|
||||
|
||||
import { SimpleSplitview } from '@site/src/components/simpleSplitview';
|
||||
import { SimpleGridview } from '@site/src/components/simpleGridview';
|
||||
import { SimplePaneview } from '@site/src/components/simplePaneview';
|
||||
import { SimpleDockview } from '@site/src/components/simpleDockview';
|
||||
|
||||
# Examples
|
||||
|
||||
<iframe
|
||||
src="https://codesandbox.io/embed/dockview-template-mdc9f7?fontsize=14&hidenavigation=1&theme=dark"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '500px',
|
||||
border: 0,
|
||||
borderRaduis: '4px',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="dockview-template"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
></iframe>
|
149
packages/docs/versioned_docs/version-1.6.0/index.mdx
vendored
Normal file
149
packages/docs/versioned_docs/version-1.6.0/index.mdx
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: A zero dependency layout manager built for React
|
||||
---
|
||||
|
||||
import { SimpleSplitview } from '@site/src/components/simpleSplitview';
|
||||
import { SimpleGridview } from '@site/src/components/simpleGridview';
|
||||
import { SimplePaneview } from '@site/src/components/simplePaneview';
|
||||
import { SimpleDockview } from '@site/src/components/simpleDockview';
|
||||
|
||||
# Introduction
|
||||
|
||||
**dockview** is a zero dependency layout manager that supports tab, grids and splitviews.
|
||||
|
||||
## Features
|
||||
|
||||
- Themable and customizable
|
||||
- Support for the serialization and deserialization of layouts
|
||||
- Drag and drop support
|
||||
|
||||
## Quick start
|
||||
|
||||
`dockview` has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. To install `dockview` you can run:
|
||||
|
||||
```shell
|
||||
npm install dockview
|
||||
```
|
||||
|
||||
You must also import the dockview stylesheet found under [`dockview/dict/styles/dockview.css`](https://unpkg.com/browse/dockview@latest/dist/styles/dockview.css),
|
||||
depending on your solution this might be:
|
||||
|
||||
```css
|
||||
@import './node_modules/dockview/dist/styles/dockview.css';
|
||||
```
|
||||
|
||||
A dark and light theme are provided, one of these classes (or a custom theme) must be attached at any point above your components in the HTML tree. To cover the entire web page you might attach the class to the `body` component:
|
||||
|
||||
```html
|
||||
<body classname="dockview-theme-abyss">
|
||||
...
|
||||
</body>
|
||||
<body classname="dockview-theme-light">
|
||||
...
|
||||
</body>
|
||||
```
|
||||
|
||||
There are 4 components you may want to use:
|
||||
|
||||
Splitview
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '100px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimpleSplitview />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimpleGridview />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimplePaneview />
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimpleDockview />
|
||||
</div>
|
||||
|
||||
```tsx
|
||||
import {
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
PanelCollection,
|
||||
IDockviewPanelProps,
|
||||
IDockviewPanelHeaderProps,
|
||||
} from 'dockview';
|
||||
|
||||
const components: PanelCollection<IDockviewPanelProps> = {
|
||||
default: (props: IDockviewPanelProps<{ someProps: string }>) => {
|
||||
return <div>{props.params.someProps}</div>;
|
||||
},
|
||||
};
|
||||
|
||||
const headers: PanelCollection<IDockviewPanelHeaderProps> = {
|
||||
customTab: (props: IDockviewPanelHeaderProps) => {
|
||||
return (
|
||||
<div>
|
||||
<span>{props.api.title}</span>
|
||||
<span onClick={() => props.api.close()}>{'[x]'}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const Component = () => {
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
tabComponent: 'customTab', // optional custom header
|
||||
params: {
|
||||
someProps: 'Hello',
|
||||
},
|
||||
});
|
||||
event.api.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
params: {
|
||||
someProps: 'World',
|
||||
},
|
||||
position: { referencePanel: 'panel1', direction: 'below' },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DockviewReact
|
||||
components={components}
|
||||
tabComponents={headers} // optional headers renderer
|
||||
onReady={onReady}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
89
packages/docs/versioned_docs/version-1.6.0/theme.mdx
vendored
Normal file
89
packages/docs/versioned_docs/version-1.6.0/theme.mdx
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
description: Theming Dockview Components
|
||||
---
|
||||
|
||||
import { CustomCSSDockview } from '@site/src/components/dockview/customCss';
|
||||
|
||||
# Theme
|
||||
|
||||
## Introduction
|
||||
|
||||
`dockview` requires some css to work correctly.
|
||||
The css is exported as one file under [`dockview/dict/styles/dockview.css`](https://unpkg.com/browse/dockview@latest/dist/styles/dockview.css)
|
||||
and depending can be imported
|
||||
|
||||
```css
|
||||
@import './node_modules/dockview/dist/styles/dockview.css';
|
||||
```
|
||||
|
||||
## Provided themes
|
||||
|
||||
The following are provided as classes that you can attached to your components for themeing
|
||||
|
||||
- `.dockview-theme-light`
|
||||
- `.dockview-theme-dark`
|
||||
- `.dockview-theme-abyss`
|
||||
|
||||
## Customizing Theme
|
||||
|
||||
`dockview` supports theming through the use of css properties.
|
||||
You can view the built-in themes at [`dockview/src/theme.scss`](https://github.com/mathuo/dockview/blob/master/packages/dockview/src/theme.scss)
|
||||
and are free to build your own themes based on these css properties.
|
||||
|
||||
| CSS Property | Description |
|
||||
| ---------------------------------------------------- | ----------- |
|
||||
| --dv-paneview-active-outline-color | |
|
||||
| --dv-tabs-and-actions-container-font-size | |
|
||||
| --dv-tabs-and-actions-container-height | |
|
||||
| --dv-tab-close-icon | |
|
||||
| --dv-drag-over-background-color | |
|
||||
| --dv-drag-over-border-color | |
|
||||
| --dv-tabs-container-scrollbar-color | |
|
||||
| | |
|
||||
| --dv-group-view-background-color | |
|
||||
| | |
|
||||
| --dv-tabs-and-actions-container-background-color | |
|
||||
| | |
|
||||
| --dv-activegroup-visiblepanel-tab-background-color | |
|
||||
| --dv-activegroup-hiddenpanel-tab-background-color | |
|
||||
| --dv-inactivegroup-visiblepanel-tab-background-color | |
|
||||
| --dv-inactivegroup-hiddenpanel-tab-background-color | |
|
||||
| --dv-tab-divider-color | |
|
||||
| | |
|
||||
| --dv-activegroup-visiblepanel-tab-color | |
|
||||
| --dv-activegroup-hiddenpanel-tab-color | |
|
||||
| --dv-inactivegroup-visiblepanel-tab-color | |
|
||||
| --dv-inactivegroup-hiddenpanel-tab-color | |
|
||||
| | |
|
||||
| --dv-separator-border | |
|
||||
| --dv-paneview-header-border-color | |
|
||||
|
||||
You can further customise the theme through adjusting class properties but this is up you.
|
||||
As an example if you wanted to add a bottom border to the tab container for an active group in the `DockviewReact` component you could write:
|
||||
|
||||
```css
|
||||
.groupview {
|
||||
&.active-group {
|
||||
> .tabs-and-actions-container {
|
||||
border-bottom: 2px solid var(--dv-activegroup-visiblepanel-tab-background-color);
|
||||
}
|
||||
}
|
||||
&.inactive-group {
|
||||
> .tabs-and-actions-container {
|
||||
border-bottom: 2px solid var(--dv-inactivegroup-visiblepanel-tab-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<CustomCSSDockview />
|
||||
</div>
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"tutorialSidebar": [
|
||||
{
|
||||
"type": "autogenerated",
|
||||
"dirName": "."
|
||||
}
|
||||
]
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
[
|
||||
"1.6.0",
|
||||
"1.5.2",
|
||||
"1.5.1"
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user