mirror of
https://github.com/mathuo/dockview
synced 2025-01-22 17:35:57 +00:00
Merge branch 'master' of https://github.com/mathuo/dockview into 460-locked-mode-prevent-all-mouse-resizing
This commit is contained in:
commit
e28c76626f
@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.2",
|
||||
"npmClient": "yarn",
|
||||
"command": {
|
||||
"publish": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-core",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.2",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
|
@ -8,7 +8,7 @@ describe('groupPanelApi', () => {
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
};
|
||||
|
||||
const panelMock = jest.fn<DockviewPanel, []>(() => {
|
||||
@ -44,7 +44,7 @@ describe('groupPanelApi', () => {
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
};
|
||||
const groupViewPanel = new DockviewGroupPanel(
|
||||
<DockviewComponent>accessor,
|
||||
@ -74,7 +74,7 @@ describe('groupPanelApi', () => {
|
||||
const accessor: Partial<DockviewComponent> = {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
};
|
||||
const groupViewPanel = new DockviewGroupPanel(
|
||||
<DockviewComponent>accessor,
|
||||
|
@ -11,7 +11,7 @@ describe('groupDragHandler', () => {
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
const partial: Partial<DockviewGroupPanel> = {
|
||||
id: 'test_group_id',
|
||||
api: { location: 'grid' } as any,
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
};
|
||||
return partial as DockviewGroupPanel;
|
||||
});
|
||||
@ -53,7 +53,7 @@ describe('groupDragHandler', () => {
|
||||
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
const partial: Partial<DockviewGroupPanel> = {
|
||||
api: { location: 'floating' } as any,
|
||||
api: { location: { type: 'floating' } } as any,
|
||||
};
|
||||
return partial as DockviewGroupPanel;
|
||||
});
|
||||
@ -85,7 +85,7 @@ describe('groupDragHandler', () => {
|
||||
|
||||
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
const partial: Partial<DockviewGroupPanel> = {
|
||||
api: { location: 'grid' } as any,
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
};
|
||||
return partial as DockviewGroupPanel;
|
||||
});
|
||||
|
@ -11,6 +11,8 @@ import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
|
||||
import { IDockviewPanelModel } from '../../../../dockview/dockviewPanelModel';
|
||||
import { DockviewComponent } from '../../../../dockview/dockviewComponent';
|
||||
import { OverlayRenderContainer } from '../../../../overlayRenderContainer';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanelModel';
|
||||
|
||||
class TestContentRenderer
|
||||
extends CompositeDisposable
|
||||
@ -26,6 +28,7 @@ class TestContentRenderer
|
||||
constructor(public id: string) {
|
||||
super();
|
||||
this.element = document.createElement('div');
|
||||
this.element.id = id;
|
||||
}
|
||||
|
||||
init(parameters: GroupPanelContentPartInitParameters): void {
|
||||
@ -146,4 +149,40 @@ describe('contentContainer', () => {
|
||||
|
||||
disposable.dispose();
|
||||
});
|
||||
|
||||
test("that panels renderered as 'onlyWhenVisibile' are removed when closed", () => {
|
||||
const cut = new ContentContainer(
|
||||
fromPartial<DockviewComponent>({
|
||||
overlayRenderContainer: {
|
||||
detatch: jest.fn(),
|
||||
},
|
||||
}),
|
||||
fromPartial<DockviewGroupPanelModel>({})
|
||||
);
|
||||
|
||||
const panel1 = fromPartial<IDockviewPanel>({
|
||||
api: {
|
||||
renderer: 'onlyWhenVisibile',
|
||||
},
|
||||
view: { content: new TestContentRenderer('panel_1') },
|
||||
});
|
||||
|
||||
const panel2 = fromPartial<IDockviewPanel>({
|
||||
api: {
|
||||
renderer: 'onlyWhenVisibile',
|
||||
},
|
||||
view: { content: new TestContentRenderer('panel_2') },
|
||||
});
|
||||
|
||||
cut.openPanel(panel1);
|
||||
|
||||
expect(panel1.view.content.element.parentElement).toBe(cut.element);
|
||||
expect(cut.element.childNodes.length).toBe(1);
|
||||
|
||||
cut.openPanel(panel2);
|
||||
|
||||
expect(panel1.view.content.element.parentElement).toBeNull();
|
||||
expect(panel2.view.content.element.parentElement).toBe(cut.element);
|
||||
expect(cut.element.childNodes.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
@ -9,6 +9,7 @@ import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanel
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import { TestPanel } from '../../dockviewGroupPanelModel.spec';
|
||||
import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
|
||||
describe('tabsContainer', () => {
|
||||
test('that an external event does not render a drop target and calls through to the group mode', () => {
|
||||
@ -16,7 +17,7 @@ describe('tabsContainer', () => {
|
||||
return {
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -71,7 +72,7 @@ describe('tabsContainer', () => {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -139,7 +140,7 @@ describe('tabsContainer', () => {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -204,7 +205,7 @@ describe('tabsContainer', () => {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -269,7 +270,7 @@ describe('tabsContainer', () => {
|
||||
id: 'testcomponentid',
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
};
|
||||
});
|
||||
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
|
||||
@ -336,7 +337,7 @@ describe('tabsContainer', () => {
|
||||
test('left actions', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
@ -402,7 +403,7 @@ describe('tabsContainer', () => {
|
||||
test('right actions', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
@ -468,7 +469,7 @@ describe('tabsContainer', () => {
|
||||
test('that a tab will become floating when clicked if not floating and shift is selected', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
@ -478,7 +479,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: 'grid' } as any,
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
@ -514,21 +515,21 @@ describe('tabsContainer', () => {
|
||||
},
|
||||
{ inDragMode: true }
|
||||
);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
|
||||
expect(eventPreventDefaultSpy).toBeCalledTimes(1);
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
|
||||
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
const event2 = new KeyboardEvent('mousedown', { shiftKey: false });
|
||||
const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault');
|
||||
fireEvent(container, event2);
|
||||
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
|
||||
expect(eventPreventDefaultSpy2).toBeCalledTimes(0);
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
|
||||
expect(eventPreventDefaultSpy2).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('that a tab that is already floating cannot be floated again', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
@ -538,7 +539,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: 'floating' } as any,
|
||||
api: { location: { type: 'floating' } } as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
@ -580,7 +581,7 @@ describe('tabsContainer', () => {
|
||||
test('that selecting a tab with shift down will move that tab into a new floating group', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
@ -591,7 +592,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: 'floating' } as any,
|
||||
api: { location: { type: 'floating' } } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
@ -601,23 +602,20 @@ describe('tabsContainer', () => {
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
|
||||
const partial: Partial<IDockviewPanel> = {
|
||||
const createPanel = (id: string) =>
|
||||
fromPartial<IDockviewPanel>({
|
||||
id,
|
||||
|
||||
view: {
|
||||
tab: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
},
|
||||
content: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
} as any,
|
||||
};
|
||||
return partial as IDockviewPanel;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const panel = new panelMock('test_id');
|
||||
const panel = createPanel('test_id');
|
||||
cut.openPanel(panel);
|
||||
|
||||
const el = cut.element.querySelector('.tab')!;
|
||||
@ -628,21 +626,21 @@ describe('tabsContainer', () => {
|
||||
fireEvent(el, event);
|
||||
|
||||
// a floating group with a single tab shouldn't be eligible
|
||||
expect(preventDefaultSpy).toBeCalledTimes(0);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(0);
|
||||
expect(preventDefaultSpy).toHaveBeenCalledTimes(0);
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0);
|
||||
|
||||
const panel2 = new panelMock('test_id_2');
|
||||
const panel2 = createPanel('test_id_2');
|
||||
cut.openPanel(panel2);
|
||||
fireEvent(el, event);
|
||||
|
||||
expect(preventDefaultSpy).toBeCalledTimes(1);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
|
||||
expect(preventDefaultSpy).toHaveBeenCalledTimes(1);
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('pre header actions', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
@ -653,7 +651,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: 'grid' } as any,
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
@ -712,7 +710,7 @@ describe('tabsContainer', () => {
|
||||
test('left header actions', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
@ -723,7 +721,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: 'grid' } as any,
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
@ -782,7 +780,7 @@ describe('tabsContainer', () => {
|
||||
test('right header actions', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
@ -793,7 +791,7 @@ describe('tabsContainer', () => {
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { location: 'grid' } as any,
|
||||
api: { location: { type: 'grid' } } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
@ -110,102 +110,109 @@ describe('dockviewComponent', () => {
|
||||
window.open = jest.fn(); // not implemented by jest
|
||||
});
|
||||
|
||||
describe('memory leakage', () => {
|
||||
test('event leakage', () => {
|
||||
Emitter.setLeakageMonitorEnabled(true);
|
||||
// describe('memory leakage', () => {
|
||||
// beforeEach(() => {
|
||||
// window.open = () => fromPartial<Window>({
|
||||
// addEventListener: jest.fn(),
|
||||
// close: jest.fn(),
|
||||
// });
|
||||
// });
|
||||
|
||||
dockview = new DockviewComponent({
|
||||
parentElement: container,
|
||||
components: {
|
||||
default: PanelContentPartTest,
|
||||
},
|
||||
});
|
||||
// test('event leakage', () => {
|
||||
// Emitter.setLeakageMonitorEnabled(true);
|
||||
|
||||
dockview.layout(500, 1000);
|
||||
// dockview = new DockviewComponent({
|
||||
// parentElement: container,
|
||||
// components: {
|
||||
// default: PanelContentPartTest,
|
||||
// },
|
||||
// });
|
||||
|
||||
const panel1 = dockview.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
});
|
||||
// dockview.layout(500, 1000);
|
||||
|
||||
const panel2 = dockview.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
});
|
||||
// const panel1 = dockview.addPanel({
|
||||
// id: 'panel1',
|
||||
// component: 'default',
|
||||
// });
|
||||
|
||||
dockview.removePanel(panel2);
|
||||
// const panel2 = dockview.addPanel({
|
||||
// id: 'panel2',
|
||||
// component: 'default',
|
||||
// });
|
||||
|
||||
const panel3 = dockview.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'right',
|
||||
referencePanel: 'panel1',
|
||||
},
|
||||
});
|
||||
// dockview.removePanel(panel2);
|
||||
|
||||
const panel4 = dockview.addPanel({
|
||||
id: 'panel4',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'above',
|
||||
},
|
||||
});
|
||||
// const panel3 = dockview.addPanel({
|
||||
// id: 'panel3',
|
||||
// component: 'default',
|
||||
// position: {
|
||||
// direction: 'right',
|
||||
// referencePanel: 'panel1',
|
||||
// },
|
||||
// });
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel4.group,
|
||||
panel3.group.id,
|
||||
panel3.id,
|
||||
'center'
|
||||
);
|
||||
// const panel4 = dockview.addPanel({
|
||||
// id: 'panel4',
|
||||
// component: 'default',
|
||||
// position: {
|
||||
// direction: 'above',
|
||||
// },
|
||||
// });
|
||||
|
||||
dockview.addPanel({
|
||||
id: 'panel5',
|
||||
component: 'default',
|
||||
floating: true,
|
||||
});
|
||||
// dockview.moveGroupOrPanel(
|
||||
// panel4.group,
|
||||
// panel3.group.id,
|
||||
// panel3.id,
|
||||
// 'center'
|
||||
// );
|
||||
|
||||
const panel6 = dockview.addPanel({
|
||||
id: 'panel6',
|
||||
component: 'default',
|
||||
position: {
|
||||
referencePanel: 'panel5',
|
||||
direction: 'within',
|
||||
},
|
||||
});
|
||||
// dockview.addPanel({
|
||||
// id: 'panel5',
|
||||
// component: 'default',
|
||||
// floating: true,
|
||||
// });
|
||||
|
||||
dockview.addFloatingGroup(panel4.api.group);
|
||||
// const panel6 = dockview.addPanel({
|
||||
// id: 'panel6',
|
||||
// component: 'default',
|
||||
// position: {
|
||||
// referencePanel: 'panel5',
|
||||
// direction: 'within',
|
||||
// },
|
||||
// });
|
||||
|
||||
dockview.addPopoutGroup(panel6);
|
||||
// dockview.addFloatingGroup(panel4.api.group);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel1.group,
|
||||
panel6.group.id,
|
||||
panel6.id,
|
||||
'center'
|
||||
);
|
||||
// dockview.addPopoutGroup(panel6);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel4.group,
|
||||
panel6.group.id,
|
||||
panel6.id,
|
||||
'center'
|
||||
);
|
||||
// dockview.moveGroupOrPanel(
|
||||
// panel1.group,
|
||||
// panel6.group.id,
|
||||
// panel6.id,
|
||||
// 'center'
|
||||
// );
|
||||
|
||||
dockview.dispose();
|
||||
// dockview.moveGroupOrPanel(
|
||||
// panel4.group,
|
||||
// panel6.group.id,
|
||||
// panel6.id,
|
||||
// 'center'
|
||||
// );
|
||||
|
||||
if (Emitter.MEMORY_LEAK_WATCHER.size > 0) {
|
||||
for (const entry of Array.from(
|
||||
Emitter.MEMORY_LEAK_WATCHER.events
|
||||
)) {
|
||||
console.log('disposal', entry[1]);
|
||||
}
|
||||
throw new Error('not all listeners disposed');
|
||||
}
|
||||
// dockview.dispose();
|
||||
|
||||
Emitter.setLeakageMonitorEnabled(false);
|
||||
});
|
||||
});
|
||||
// if (Emitter.MEMORY_LEAK_WATCHER.size > 0) {
|
||||
// for (const entry of Array.from(
|
||||
// Emitter.MEMORY_LEAK_WATCHER.events
|
||||
// )) {
|
||||
// console.log('disposal', entry[1]);
|
||||
// }
|
||||
// throw new Error('not all listeners disposed');
|
||||
// }
|
||||
|
||||
// Emitter.setLeakageMonitorEnabled(false);
|
||||
// });
|
||||
// });
|
||||
|
||||
test('duplicate panel', () => {
|
||||
dockview.layout(500, 1000);
|
||||
@ -3452,8 +3459,8 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
@ -3464,8 +3471,8 @@ describe('dockviewComponent', () => {
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -3497,8 +3504,8 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
@ -3509,8 +3516,8 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -3548,9 +3555,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3561,9 +3568,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3601,9 +3608,9 @@ describe('dockviewComponent', () => {
|
||||
position: { referencePanel: panel2 },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3614,9 +3621,9 @@ describe('dockviewComponent', () => {
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3654,9 +3661,9 @@ describe('dockviewComponent', () => {
|
||||
position: { referencePanel: panel2 },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3667,9 +3674,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3713,10 +3720,10 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel4.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(panel4.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(4);
|
||||
|
||||
@ -3727,10 +3734,10 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel4.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(panel4.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(4);
|
||||
});
|
||||
@ -3762,8 +3769,8 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
@ -3774,8 +3781,8 @@ describe('dockviewComponent', () => {
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -3807,8 +3814,8 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
@ -3819,8 +3826,8 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -3858,9 +3865,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3871,9 +3878,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3911,9 +3918,9 @@ describe('dockviewComponent', () => {
|
||||
position: { referencePanel: panel2 },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3924,9 +3931,9 @@ describe('dockviewComponent', () => {
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -3964,9 +3971,9 @@ describe('dockviewComponent', () => {
|
||||
position: { referencePanel: panel2 },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -3977,9 +3984,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -4023,10 +4030,10 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel4.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(panel4.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(4);
|
||||
|
||||
@ -4037,10 +4044,10 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel4.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(panel4.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(4);
|
||||
});
|
||||
@ -4078,9 +4085,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -4091,9 +4098,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('floating');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -4130,9 +4137,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -4143,9 +4150,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('floating');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -4183,9 +4190,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -4196,9 +4203,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('floating');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -4235,9 +4242,9 @@ describe('dockviewComponent', () => {
|
||||
floating: true,
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
@ -4248,9 +4255,9 @@ describe('dockviewComponent', () => {
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel3.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('floating');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(panel3.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
@ -4282,15 +4289,15 @@ describe('dockviewComponent', () => {
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
dockview.addFloatingGroup(panel2);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -4321,15 +4328,15 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
dockview.addFloatingGroup(panel2);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -4361,15 +4368,15 @@ describe('dockviewComponent', () => {
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
dockview.addFloatingGroup(panel2.group);
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
@ -4400,22 +4407,40 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
dockview.addFloatingGroup(panel2.group);
|
||||
|
||||
expect(panel1.group.api.location).toBe('floating');
|
||||
expect(panel2.group.api.location).toBe('floating');
|
||||
expect(panel1.group.api.location.type).toBe('floating');
|
||||
expect(panel2.group.api.location.type).toBe('floating');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('popout group', () => {
|
||||
test('that can remove a popout group', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window, 'open').mockReturnValue(
|
||||
fromPartial<Window>({
|
||||
document: fromPartial<Document>({
|
||||
body: document.createElement('body'),
|
||||
}),
|
||||
addEventListener: jest
|
||||
.fn()
|
||||
.mockImplementation((name, cb) => {
|
||||
if (name === 'load') {
|
||||
cb();
|
||||
}
|
||||
}),
|
||||
close: jest.fn(),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('that can remove a popout group', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
@ -4436,11 +4461,11 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
dockview.addPopoutGroup(panel1);
|
||||
await dockview.addPopoutGroup(panel1);
|
||||
|
||||
expect(dockview.panels.length).toBe(1);
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(panel1.api.group.api.location).toBe('popout');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(panel1.api.group.api.location.type).toBe('popout');
|
||||
|
||||
dockview.removePanel(panel1);
|
||||
|
||||
@ -4448,7 +4473,7 @@ describe('dockviewComponent', () => {
|
||||
expect(dockview.groups.length).toBe(0);
|
||||
});
|
||||
|
||||
test('add a popout group', () => {
|
||||
test('add a popout group', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
@ -4474,20 +4499,20 @@ describe('dockviewComponent', () => {
|
||||
component: 'default',
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
|
||||
dockview.addPopoutGroup(panel2.group);
|
||||
await dockview.addPopoutGroup(panel2.group);
|
||||
|
||||
expect(panel1.group.api.location).toBe('popout');
|
||||
expect(panel2.group.api.location).toBe('popout');
|
||||
expect(dockview.groups.length).toBe(1);
|
||||
expect(panel1.group.api.location.type).toBe('popout');
|
||||
expect(panel2.group.api.location.type).toBe('popout');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(2);
|
||||
});
|
||||
|
||||
test('move from fixed to popout group and back', () => {
|
||||
test('move from fixed to popout group and back', async () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
@ -4521,18 +4546,18 @@ describe('dockviewComponent', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(panel1.group.api.location).toBe('grid');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('grid');
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
dockview.addPopoutGroup(panel2.group);
|
||||
await dockview.addPopoutGroup(panel2.group);
|
||||
|
||||
expect(panel1.group.api.location).toBe('popout');
|
||||
expect(panel2.group.api.location).toBe('popout');
|
||||
expect(panel3.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(panel1.group.api.location.type).toBe('popout');
|
||||
expect(panel2.group.api.location.type).toBe('popout');
|
||||
expect(panel3.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
@ -4542,10 +4567,23 @@ describe('dockviewComponent', () => {
|
||||
'right'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location).toBe('popout');
|
||||
expect(panel2.group.api.location).toBe('grid');
|
||||
expect(panel3.group.api.location).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(3);
|
||||
expect(panel1.group.api.location.type).toBe('popout');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(4);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
|
||||
dockview.moveGroupOrPanel(
|
||||
panel3.api.group,
|
||||
panel1.api.group.id,
|
||||
panel1.api.id,
|
||||
'center'
|
||||
);
|
||||
|
||||
expect(panel1.group.api.location.type).toBe('grid');
|
||||
expect(panel2.group.api.location.type).toBe('grid');
|
||||
expect(panel3.group.api.location.type).toBe('grid');
|
||||
expect(dockview.groups.length).toBe(2);
|
||||
expect(dockview.panels.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
@ -243,7 +243,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
options = {};
|
||||
|
||||
dockview = (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
options: { parentElement: document.createElement('div') },
|
||||
createWatermarkComponent: () => new Watermark(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
id: 'dockview-1',
|
||||
@ -639,6 +639,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
id: 'testcomponentid',
|
||||
options: {
|
||||
showDndOverlay: jest.fn(),
|
||||
parentElement: document.createElement('div'),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
@ -699,6 +700,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
id: 'testcomponentid',
|
||||
options: {
|
||||
showDndOverlay: () => true,
|
||||
parentElement: document.createElement('div'),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
onDidAddPanel: jest.fn(),
|
||||
@ -790,6 +792,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
id: 'testcomponentid',
|
||||
options: {
|
||||
showDndOverlay: jest.fn(),
|
||||
parentElement: document.createElement('div'),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
@ -863,6 +866,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
id: 'testcomponentid',
|
||||
options: {
|
||||
showDndOverlay: jest.fn(),
|
||||
parentElement: document.createElement('div'),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
@ -941,6 +945,7 @@ describe('dockviewGroupPanelModel', () => {
|
||||
id: 'testcomponentid',
|
||||
options: {
|
||||
showDndOverlay: jest.fn(),
|
||||
parentElement: document.createElement('div'),
|
||||
},
|
||||
getPanel: jest.fn(),
|
||||
doSetGroupActive: jest.fn(),
|
||||
|
@ -38,6 +38,7 @@ describe('dockviewGroupPanel', () => {
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
@ -131,6 +132,7 @@ describe('dockviewGroupPanel', () => {
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
@ -159,6 +161,7 @@ describe('dockviewGroupPanel', () => {
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
@ -190,6 +193,7 @@ describe('dockviewGroupPanel', () => {
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
@ -222,6 +226,7 @@ describe('dockviewGroupPanel', () => {
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
@ -244,6 +249,7 @@ describe('dockviewGroupPanel', () => {
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
parentElement: document.createElement('div'),
|
||||
components: {
|
||||
contentComponent: jest.fn().mockImplementation(() => {
|
||||
return contentMock;
|
||||
@ -271,6 +277,7 @@ describe('dockviewGroupPanel', () => {
|
||||
accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
const partial: Partial<DockviewComponent> = {
|
||||
options: {
|
||||
parentElement: document.createElement('div'),
|
||||
frameworkComponents: {
|
||||
contentComponent: contentMock,
|
||||
},
|
||||
|
@ -268,7 +268,7 @@ describe('gridview', () => {
|
||||
],
|
||||
},
|
||||
},
|
||||
activePanel: 'panel_1',
|
||||
activePanel: 'panel_2',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -41,7 +41,7 @@ describe('overlayRenderContainer', () => {
|
||||
},
|
||||
group: {
|
||||
api: {
|
||||
location: 'grid',
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -77,7 +77,7 @@ describe('overlayRenderContainer', () => {
|
||||
},
|
||||
group: {
|
||||
api: {
|
||||
location: 'grid',
|
||||
location: { type: 'grid' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -830,8 +830,10 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
options?: {
|
||||
position?: Box;
|
||||
popoutUrl?: string;
|
||||
onDidOpen?: (event: { id: string; window: Window }) => void;
|
||||
onWillClose?: (event: { id: string; window: Window }) => void;
|
||||
}
|
||||
): void {
|
||||
this.component.addPopoutGroup(item, options);
|
||||
): Promise<void> {
|
||||
return this.component.addPopoutGroup(item, options);
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,10 @@ import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi';
|
||||
export interface DockviewGroupPanelApi extends GridviewPanelApi {
|
||||
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
||||
readonly location: DockviewGroupLocation;
|
||||
/**
|
||||
* If you require the Window object
|
||||
*/
|
||||
getWindow(): Window;
|
||||
moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void;
|
||||
maximize(): void;
|
||||
isMaximized(): boolean;
|
||||
@ -42,6 +46,12 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
||||
this.addDisposables(this._onDidLocationChange);
|
||||
}
|
||||
|
||||
getWindow(): Window {
|
||||
return this.location.type === 'popout'
|
||||
? this.location.getWindow()
|
||||
: window;
|
||||
}
|
||||
|
||||
moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void {
|
||||
if (!this._group) {
|
||||
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||
@ -66,7 +76,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
|
||||
throw new Error(NOT_INITIALIZED_MESSAGE);
|
||||
}
|
||||
|
||||
if (this.location !== 'grid') {
|
||||
if (this.location.type !== 'grid') {
|
||||
// only grid groups can be maximized
|
||||
return;
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { Emitter, Event } from '../events';
|
||||
import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi';
|
||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||
import { MutableDisposable } from '../lifecycle';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { DockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { DockviewPanelRenderer } from '../overlayRenderContainer';
|
||||
import { DockviewGroupPanelFloatingChangeEvent } from './dockviewGroupPanelApi';
|
||||
import { DockviewGroupLocation } from '../dockview/dockviewGroupPanelModel';
|
||||
|
||||
export interface TitleEvent {
|
||||
readonly title: string;
|
||||
@ -28,6 +30,8 @@ export interface DockviewPanelApi
|
||||
readonly onDidActiveGroupChange: Event<void>;
|
||||
readonly onDidGroupChange: Event<void>;
|
||||
readonly onDidRendererChange: Event<RendererChangedEvent>;
|
||||
readonly location: DockviewGroupLocation;
|
||||
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
|
||||
close(): void;
|
||||
setTitle(title: string): void;
|
||||
setRenderer(renderer: DockviewPanelRenderer): void;
|
||||
@ -39,6 +43,10 @@ export interface DockviewPanelApi
|
||||
maximize(): void;
|
||||
isMaximized(): boolean;
|
||||
exitMaximized(): void;
|
||||
/**
|
||||
* If you require the Window object
|
||||
*/
|
||||
getWindow(): Window;
|
||||
}
|
||||
|
||||
export class DockviewPanelApiImpl
|
||||
@ -59,7 +67,16 @@ export class DockviewPanelApiImpl
|
||||
readonly _onDidRendererChange = new Emitter<RendererChangedEvent>();
|
||||
readonly onDidRendererChange = this._onDidRendererChange.event;
|
||||
|
||||
private readonly disposable = new MutableDisposable();
|
||||
private readonly _onDidLocationChange =
|
||||
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
|
||||
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent> =
|
||||
this._onDidLocationChange.event;
|
||||
|
||||
private readonly groupEventsDisposable = new MutableDisposable();
|
||||
|
||||
get location(): DockviewGroupLocation {
|
||||
return this.group.api.location;
|
||||
}
|
||||
|
||||
get title(): string | undefined {
|
||||
return this.panel.title;
|
||||
@ -81,13 +98,22 @@ export class DockviewPanelApiImpl
|
||||
this._onDidGroupChange.fire();
|
||||
|
||||
if (this._group) {
|
||||
this.disposable.value = this._group.api.onDidActiveChange(() => {
|
||||
this.groupEventsDisposable.value = new CompositeDisposable(
|
||||
this.group.api.onDidLocationChange((event) => {
|
||||
this._onDidLocationChange.fire(event);
|
||||
}),
|
||||
this.group.api.onDidActiveChange(() => {
|
||||
this._onDidActiveGroupChange.fire();
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
if (this.isGroupActive !== isOldGroupActive) {
|
||||
this._onDidActiveGroupChange.fire();
|
||||
}
|
||||
|
||||
this._onDidLocationChange.fire({
|
||||
location: this.group.api.location,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,14 +133,19 @@ export class DockviewPanelApiImpl
|
||||
this._group = group;
|
||||
|
||||
this.addDisposables(
|
||||
this.disposable,
|
||||
this.groupEventsDisposable,
|
||||
this._onDidRendererChange,
|
||||
this._onDidTitleChange,
|
||||
this._onDidGroupChange,
|
||||
this._onDidActiveGroupChange
|
||||
this._onDidActiveGroupChange,
|
||||
this._onDidLocationChange
|
||||
);
|
||||
}
|
||||
|
||||
getWindow(): Window {
|
||||
return this.group.api.getWindow();
|
||||
}
|
||||
|
||||
moveTo(options: {
|
||||
group: DockviewGroupPanel;
|
||||
position?: Position;
|
||||
|
@ -14,6 +14,10 @@ export interface VisibilityEvent {
|
||||
readonly isVisible: boolean;
|
||||
}
|
||||
|
||||
export interface HiddenEvent {
|
||||
readonly isHidden: boolean;
|
||||
}
|
||||
|
||||
export interface ActiveEvent {
|
||||
readonly isActive: boolean;
|
||||
}
|
||||
@ -24,7 +28,7 @@ export interface PanelApi {
|
||||
readonly onDidFocusChange: Event<FocusEvent>;
|
||||
readonly onDidVisibilityChange: Event<VisibilityEvent>;
|
||||
readonly onDidActiveChange: Event<ActiveEvent>;
|
||||
setVisible(isVisible: boolean): void;
|
||||
readonly onDidHiddenChange: Event<HiddenEvent>;
|
||||
setActive(): void;
|
||||
updateParameters(parameters: Parameters): void;
|
||||
/**
|
||||
@ -43,6 +47,10 @@ export interface PanelApi {
|
||||
* Whether the panel is visible
|
||||
*/
|
||||
readonly isVisible: boolean;
|
||||
/**
|
||||
* Whether the panel is hidden
|
||||
*/
|
||||
readonly isHidden: boolean;
|
||||
/**
|
||||
* The panel width in pixels
|
||||
*/
|
||||
@ -60,6 +68,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
private _isFocused = false;
|
||||
private _isActive = false;
|
||||
private _isVisible = true;
|
||||
private _isHidden = false;
|
||||
private _width = 0;
|
||||
private _height = 0;
|
||||
|
||||
@ -69,56 +78,59 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidDimensionsChange = this._onDidDimensionChange.event;
|
||||
//
|
||||
|
||||
readonly _onDidChangeFocus = new Emitter<FocusEvent>({
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidFocusChange: Event<FocusEvent> = this._onDidChangeFocus.event;
|
||||
//
|
||||
|
||||
readonly _onFocusEvent = new Emitter<void>();
|
||||
readonly onFocusEvent: Event<void> = this._onFocusEvent.event;
|
||||
//
|
||||
|
||||
readonly _onDidVisibilityChange = new Emitter<VisibilityEvent>({
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidVisibilityChange: Event<VisibilityEvent> =
|
||||
this._onDidVisibilityChange.event;
|
||||
//
|
||||
|
||||
readonly _onVisibilityChange = new Emitter<VisibilityEvent>();
|
||||
readonly onVisibilityChange: Event<VisibilityEvent> =
|
||||
this._onVisibilityChange.event;
|
||||
//
|
||||
readonly _onDidHiddenChange = new Emitter<HiddenEvent>();
|
||||
readonly onDidHiddenChange: Event<HiddenEvent> =
|
||||
this._onDidHiddenChange.event;
|
||||
|
||||
readonly _onDidActiveChange = new Emitter<ActiveEvent>({
|
||||
replay: true,
|
||||
});
|
||||
readonly onDidActiveChange: Event<ActiveEvent> =
|
||||
this._onDidActiveChange.event;
|
||||
//
|
||||
|
||||
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() {
|
||||
get isFocused(): boolean {
|
||||
return this._isFocused;
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
get isActive(): boolean {
|
||||
return this._isActive;
|
||||
}
|
||||
get isVisible() {
|
||||
|
||||
get isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
get width() {
|
||||
get isHidden(): boolean {
|
||||
return this._isHidden;
|
||||
}
|
||||
|
||||
get width(): number {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
get height() {
|
||||
get height(): number {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
@ -135,6 +147,9 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
this.onDidVisibilityChange((event) => {
|
||||
this._isVisible = event.isVisible;
|
||||
}),
|
||||
this.onDidHiddenChange((event) => {
|
||||
this._isHidden = event.isHidden;
|
||||
}),
|
||||
this.onDidDimensionsChange((event) => {
|
||||
this._width = event.width;
|
||||
this._height = event.height;
|
||||
@ -146,7 +161,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
this._onDidActiveChange,
|
||||
this._onFocusEvent,
|
||||
this._onActiveChange,
|
||||
this._onVisibilityChange,
|
||||
this._onDidHiddenChange,
|
||||
this._onUpdateParameters
|
||||
);
|
||||
}
|
||||
@ -161,8 +176,8 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
);
|
||||
}
|
||||
|
||||
setVisible(isVisible: boolean) {
|
||||
this._onVisibilityChange.fire({ isVisible });
|
||||
setHidden(isHidden: boolean): void {
|
||||
this._onDidHiddenChange.fire({ isHidden });
|
||||
}
|
||||
|
||||
setActive(): void {
|
||||
@ -172,8 +187,4 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
|
||||
updateParameters(parameters: Parameters): void {
|
||||
this._onUpdateParameters.fire(parameters);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -22,26 +22,26 @@
|
||||
will-change: transform;
|
||||
pointer-events: none;
|
||||
|
||||
&.dv-overlay-top {
|
||||
&.dv-overlay-small-vertical {
|
||||
&.dv-drop-target-top {
|
||||
&.dv-drop-target-small-vertical {
|
||||
border-top: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.dv-overlay-bottom {
|
||||
&.dv-overlay-small-vertical {
|
||||
&.dv-drop-target-bottom {
|
||||
&.dv-drop-target-small-vertical {
|
||||
border-bottom: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.dv-overlay-left {
|
||||
&.dv-overlay-small-horizontal {
|
||||
&.dv-drop-target-left {
|
||||
&.dv-drop-target-small-horizontal {
|
||||
border-left: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.dv-overlay-right {
|
||||
&.dv-overlay-small-horizontal {
|
||||
&.dv-drop-target-right {
|
||||
&.dv-drop-target-small-horizontal {
|
||||
border-right: 1px solid var(--dv-drag-over-border-color);
|
||||
}
|
||||
}
|
||||
|
@ -279,16 +279,25 @@ export class Droptarget extends CompositeDisposable {
|
||||
|
||||
this.overlayElement.style.transform = transform;
|
||||
|
||||
toggleClass(this.overlayElement, 'dv-overlay-small-vertical', isSmallY);
|
||||
toggleClass(
|
||||
this.overlayElement,
|
||||
'dv-overlay-small-horizontal',
|
||||
'dv-drop-target-small-vertical',
|
||||
isSmallY
|
||||
);
|
||||
toggleClass(
|
||||
this.overlayElement,
|
||||
'dv-drop-target-small-horizontal',
|
||||
isSmallX
|
||||
);
|
||||
toggleClass(this.overlayElement, 'dv-overlay-left', isLeft);
|
||||
toggleClass(this.overlayElement, 'dv-overlay-right', isRight);
|
||||
toggleClass(this.overlayElement, 'dv-overlay-top', isTop);
|
||||
toggleClass(this.overlayElement, 'dv-overlay-bottom', isBottom);
|
||||
toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft);
|
||||
toggleClass(this.overlayElement, 'dv-drop-target-right', isRight);
|
||||
toggleClass(this.overlayElement, 'dv-drop-target-top', isTop);
|
||||
toggleClass(this.overlayElement, 'dv-drop-target-bottom', isBottom);
|
||||
toggleClass(
|
||||
this.overlayElement,
|
||||
'dv-drop-target-center',
|
||||
quadrant === 'center'
|
||||
);
|
||||
}
|
||||
|
||||
private calculateQuadrant(
|
||||
|
@ -38,7 +38,7 @@ export class GroupDragHandler extends DragHandler {
|
||||
}
|
||||
|
||||
override isCancelled(_event: DragEvent): boolean {
|
||||
if (this.group.api.location === 'floating' && !_event.shiftKey) {
|
||||
if (this.group.api.location.type === 'floating' && !_event.shiftKey) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -71,7 +71,7 @@ export class ContentContainer
|
||||
if (
|
||||
!data &&
|
||||
event.shiftKey &&
|
||||
this.group.location !== 'floating'
|
||||
this.group.location.type !== 'floating'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -203,11 +203,11 @@ export class ContentContainer
|
||||
|
||||
public closePanel(): void {
|
||||
if (this.panel) {
|
||||
if (this.accessor.options.defaultRenderer === 'onlyWhenVisibile') {
|
||||
if (this.panel.api.renderer === 'onlyWhenVisibile') {
|
||||
this._element.removeChild(this.panel.view.content.element);
|
||||
}
|
||||
this.panel = undefined;
|
||||
}
|
||||
this.panel = undefined;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
@ -247,7 +247,7 @@ export class TabsContainer
|
||||
if (
|
||||
isFloatingGroupsEnabled &&
|
||||
event.shiftKey &&
|
||||
this.group.api.location !== 'floating'
|
||||
this.group.api.location.type !== 'floating'
|
||||
) {
|
||||
event.preventDefault();
|
||||
|
||||
@ -350,7 +350,8 @@ export class TabsContainer
|
||||
!this.accessor.options.disableFloatingGroups;
|
||||
|
||||
const isFloatingWithOnePanel =
|
||||
this.group.api.location === 'floating' && this.size === 1;
|
||||
this.group.api.location.type === 'floating' &&
|
||||
this.size === 1;
|
||||
|
||||
if (
|
||||
isFloatingGroupsEnabled &&
|
||||
|
@ -14,6 +14,32 @@
|
||||
.dv-overlay-render-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.split-view-container {
|
||||
&.horizontal {
|
||||
> .view-container > .view {
|
||||
&:not(:last-child) {
|
||||
border-right: var(--dv-group-gap-size) solid transparent;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
border-left: var(--dv-group-gap-size) solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
> .view-container > .view {
|
||||
&:not(:last-child) {
|
||||
border-bottom: var(--dv-group-gap-size) solid transparent;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: var(--dv-group-gap-size) solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.groupview {
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
} from '../dnd/droptarget';
|
||||
import { tail, sequenceEquals, remove } from '../array';
|
||||
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
|
||||
import { CompositeDisposable, Disposable } from '../lifecycle';
|
||||
import { CompositeDisposable, Disposable, IDisposable } from '../lifecycle';
|
||||
import { Event, Emitter } from '../events';
|
||||
import { Watermark } from './components/watermark/watermark';
|
||||
import {
|
||||
@ -58,7 +58,6 @@ import {
|
||||
TabDragEvent,
|
||||
} from './components/titlebar/tabsContainer';
|
||||
import { Box } from '../types';
|
||||
import { DockviewPopoutGroupPanel } from './dockviewPopoutGroupPanel';
|
||||
import {
|
||||
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
|
||||
DEFAULT_FLOATING_GROUP_POSITION,
|
||||
@ -67,13 +66,14 @@ import {
|
||||
DockviewPanelRenderer,
|
||||
OverlayRenderContainer,
|
||||
} from '../overlayRenderContainer';
|
||||
import { PopoutWindow } from '../popoutWindow';
|
||||
|
||||
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
|
||||
activationSize: { type: 'pixels', value: 10 },
|
||||
size: { type: 'pixels', value: 20 },
|
||||
};
|
||||
|
||||
function getTheme(element: HTMLElement): string | undefined {
|
||||
function getDockviewTheme(element: HTMLElement): string | undefined {
|
||||
function toClassList(element: HTMLElement) {
|
||||
const list: string[] = [];
|
||||
|
||||
@ -286,8 +286,10 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
options?: {
|
||||
position?: Box;
|
||||
popoutUrl?: string;
|
||||
onDidOpen?: (event: { id: string; window: Window }) => void;
|
||||
onWillClose?: (event: { id: string; window: Window }) => void;
|
||||
}
|
||||
): void;
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export class DockviewComponent
|
||||
@ -329,7 +331,12 @@ export class DockviewComponent
|
||||
this._onDidActivePanelChange.event;
|
||||
|
||||
private readonly _floatingGroups: DockviewFloatingGroupPanel[] = [];
|
||||
private readonly _popoutGroups: DockviewPopoutGroupPanel[] = [];
|
||||
private readonly _popoutGroups: {
|
||||
window: PopoutWindow;
|
||||
popoutGroup: DockviewGroupPanel;
|
||||
referenceGroup: DockviewGroupPanel;
|
||||
disposable: IDisposable;
|
||||
}[] = [];
|
||||
private readonly _rootDropTarget: Droptarget;
|
||||
|
||||
get orientation(): Orientation {
|
||||
@ -411,7 +418,7 @@ export class DockviewComponent
|
||||
|
||||
// iterate over a copy of the array since .dispose() mutates the original array
|
||||
for (const group of [...this._popoutGroups]) {
|
||||
group.dispose();
|
||||
group.disposable.dispose();
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -514,71 +521,144 @@ export class DockviewComponent
|
||||
skipRemoveGroup?: boolean;
|
||||
position?: Box;
|
||||
popoutUrl?: string;
|
||||
onDidOpen?: (event: { id: string; window: Window }) => void;
|
||||
onWillClose?: (event: { id: string; window: Window }) => void;
|
||||
}
|
||||
): Promise<void> {
|
||||
if (item instanceof DockviewPanel && item.group.size === 1) {
|
||||
return this.addPopoutGroup(item.group);
|
||||
}
|
||||
): void {
|
||||
let group: DockviewGroupPanel;
|
||||
let box: Box | undefined = options?.position;
|
||||
|
||||
if (item instanceof DockviewPanel) {
|
||||
group = this.createGroup();
|
||||
const theme = getDockviewTheme(this.gridview.element);
|
||||
const element = this.element;
|
||||
|
||||
this.removePanel(item, {
|
||||
removeEmptyGroup: true,
|
||||
skipDispose: true,
|
||||
function moveGroupWithoutDestroying(options: {
|
||||
from: DockviewGroupPanel;
|
||||
to: DockviewGroupPanel;
|
||||
}) {
|
||||
const panels = [...options.from.panels].map((panel) =>
|
||||
options.from.model.removePanel(panel)
|
||||
);
|
||||
|
||||
panels.forEach((panel) => {
|
||||
options.to.model.openPanel(panel);
|
||||
});
|
||||
|
||||
group.model.openPanel(item);
|
||||
|
||||
if (!box) {
|
||||
box = this.element.getBoundingClientRect();
|
||||
}
|
||||
} else {
|
||||
group = item;
|
||||
|
||||
if (!box) {
|
||||
box = group.element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
const skip =
|
||||
typeof options?.skipRemoveGroup === 'boolean' &&
|
||||
options.skipRemoveGroup;
|
||||
|
||||
if (!skip) {
|
||||
this.doRemoveGroup(item, { skipDispose: true });
|
||||
}
|
||||
function getBox(): Box {
|
||||
if (options?.position) {
|
||||
return options.position;
|
||||
}
|
||||
|
||||
const theme = getTheme(this.gridview.element);
|
||||
if (item instanceof DockviewGroupPanel) {
|
||||
return item.element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
const popoutWindow = new DockviewPopoutGroupPanel(
|
||||
`${this.id}-${group.id}`, // globally unique within dockview
|
||||
group,
|
||||
if (item.group) {
|
||||
return item.group.element.getBoundingClientRect();
|
||||
}
|
||||
return element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
const box: Box = getBox();
|
||||
|
||||
const groupId = this.getNextGroupId(); //item.id;
|
||||
|
||||
item.api.setHidden(true);
|
||||
|
||||
const _window = new PopoutWindow(
|
||||
`${this.id}-${groupId}`, // unique id
|
||||
theme ?? '',
|
||||
{
|
||||
className: theme ?? '',
|
||||
popoutUrl: options?.popoutUrl ?? '/popout.html',
|
||||
box: {
|
||||
url: options?.popoutUrl ?? '/popout.html',
|
||||
left: window.screenX + box.left,
|
||||
top: window.screenY + box.top,
|
||||
width: box.width,
|
||||
height: box.height,
|
||||
},
|
||||
onDidOpen: options?.onDidOpen,
|
||||
onWillClose: options?.onWillClose,
|
||||
}
|
||||
);
|
||||
|
||||
popoutWindow.addDisposables(
|
||||
{
|
||||
dispose: () => {
|
||||
remove(this._popoutGroups, popoutWindow);
|
||||
this.updateWatermark();
|
||||
},
|
||||
},
|
||||
popoutWindow.window.onDidClose(() => {
|
||||
this.doAddGroup(group, [0]);
|
||||
const popoutWindowDisposable = new CompositeDisposable(
|
||||
_window,
|
||||
_window.onDidClose(() => {
|
||||
popoutWindowDisposable.dispose();
|
||||
})
|
||||
);
|
||||
|
||||
this._popoutGroups.push(popoutWindow);
|
||||
return _window
|
||||
.open()
|
||||
.then((popoutContainer) => {
|
||||
if (_window.isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (popoutContainer === null) {
|
||||
popoutWindowDisposable.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
const referenceGroup =
|
||||
item instanceof DockviewPanel ? item.group : item;
|
||||
|
||||
const group = this.createGroup({ id: groupId });
|
||||
|
||||
if (item instanceof DockviewPanel) {
|
||||
const panel = referenceGroup.model.removePanel(item);
|
||||
group.model.openPanel(panel);
|
||||
} else {
|
||||
moveGroupWithoutDestroying({
|
||||
from: referenceGroup,
|
||||
to: group,
|
||||
});
|
||||
referenceGroup.api.setHidden(false);
|
||||
}
|
||||
|
||||
popoutContainer.appendChild(group.element);
|
||||
|
||||
group.model.location = {
|
||||
type: 'popout',
|
||||
getWindow: () => _window.window!,
|
||||
};
|
||||
|
||||
const value = {
|
||||
window: _window,
|
||||
popoutGroup: group,
|
||||
referenceGroup,
|
||||
disposable: popoutWindowDisposable,
|
||||
};
|
||||
|
||||
popoutWindowDisposable.addDisposables(
|
||||
Disposable.from(() => {
|
||||
if (this.getPanel(referenceGroup.id)) {
|
||||
moveGroupWithoutDestroying({
|
||||
from: group,
|
||||
to: referenceGroup,
|
||||
});
|
||||
|
||||
if (referenceGroup.api.isHidden) {
|
||||
referenceGroup.api.setHidden(false);
|
||||
}
|
||||
|
||||
this.doRemoveGroup(group);
|
||||
} else {
|
||||
const removedGroup = this.doRemoveGroup(group, {
|
||||
skipDispose: true,
|
||||
skipActive: true,
|
||||
});
|
||||
removedGroup.model.location = { type: 'grid' };
|
||||
this.doAddGroup(removedGroup, [0]);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._popoutGroups.push(value);
|
||||
this.updateWatermark();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
addFloatingGroup(
|
||||
@ -609,7 +689,7 @@ export class DockviewComponent
|
||||
}
|
||||
}
|
||||
|
||||
group.model.location = 'floating';
|
||||
group.model.location = { type: 'floating' };
|
||||
|
||||
const overlayLeft =
|
||||
typeof coord?.x === 'number'
|
||||
@ -684,7 +764,7 @@ export class DockviewComponent
|
||||
dispose: () => {
|
||||
disposable.dispose();
|
||||
|
||||
group.model.location = 'grid';
|
||||
group.model.location = { type: 'grid' };
|
||||
remove(this._floatingGroups, floatingGroupPanel);
|
||||
this.updateWatermark();
|
||||
},
|
||||
@ -877,7 +957,7 @@ export class DockviewComponent
|
||||
const popoutGroups: SerializedPopoutGroup[] = this._popoutGroups.map(
|
||||
(group) => {
|
||||
return {
|
||||
data: group.group.toJSON() as GroupPanelViewState,
|
||||
data: group.popoutGroup.toJSON() as GroupPanelViewState,
|
||||
position: group.window.dimensions(),
|
||||
};
|
||||
}
|
||||
@ -1174,7 +1254,7 @@ export class DockviewComponent
|
||||
group.model.openPanel(panel);
|
||||
this.doSetGroupAndPanelActive(group);
|
||||
} else if (
|
||||
referenceGroup.api.location === 'floating' ||
|
||||
referenceGroup.api.location.type === 'floating' ||
|
||||
target === 'center'
|
||||
) {
|
||||
panel = this.createPanel(options, referenceGroup);
|
||||
@ -1260,7 +1340,11 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
private updateWatermark(): void {
|
||||
if (this.groups.filter((x) => x.api.location === 'grid').length === 0) {
|
||||
if (
|
||||
this.groups.filter(
|
||||
(x) => x.api.location.type === 'grid' && !x.api.isHidden
|
||||
).length === 0
|
||||
) {
|
||||
if (!this.watermark) {
|
||||
this.watermark = this.createWatermarkComponent();
|
||||
|
||||
@ -1282,7 +1366,7 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
addGroup(options?: AddGroupOptions): DockviewGroupPanel {
|
||||
const group = this.createGroup();
|
||||
const group = this.createGroup(options);
|
||||
|
||||
if (options) {
|
||||
let referenceGroup: DockviewGroupPanel | undefined;
|
||||
@ -1378,7 +1462,7 @@ export class DockviewComponent
|
||||
}
|
||||
| undefined
|
||||
): DockviewGroupPanel {
|
||||
if (group.api.location === 'floating') {
|
||||
if (group.api.location.type === 'floating') {
|
||||
const floatingGroup = this._floatingGroups.find(
|
||||
(_) => _.group === group
|
||||
);
|
||||
@ -1407,19 +1491,21 @@ export class DockviewComponent
|
||||
throw new Error('failed to find floating group');
|
||||
}
|
||||
|
||||
if (group.api.location === 'popout') {
|
||||
if (group.api.location.type === 'popout') {
|
||||
const selectedGroup = this._popoutGroups.find(
|
||||
(_) => _.group === group
|
||||
(_) => _.popoutGroup === group
|
||||
);
|
||||
|
||||
if (selectedGroup) {
|
||||
if (!options?.skipDispose) {
|
||||
selectedGroup.group.dispose();
|
||||
this.doRemoveGroup(selectedGroup.referenceGroup);
|
||||
|
||||
selectedGroup.popoutGroup.dispose();
|
||||
this._groups.delete(group.id);
|
||||
this._onDidRemoveGroup.fire(group);
|
||||
}
|
||||
|
||||
selectedGroup.dispose();
|
||||
selectedGroup.disposable.dispose();
|
||||
|
||||
if (!options?.skipActive && this._activeGroup === group) {
|
||||
const groups = Array.from(this._groups.values());
|
||||
@ -1429,7 +1515,8 @@ export class DockviewComponent
|
||||
);
|
||||
}
|
||||
|
||||
return selectedGroup.group;
|
||||
this.updateWatermark();
|
||||
return selectedGroup.popoutGroup;
|
||||
}
|
||||
|
||||
throw new Error('failed to find popout group');
|
||||
@ -1487,7 +1574,7 @@ export class DockviewComponent
|
||||
if (sourceGroup && sourceGroup.size < 2) {
|
||||
const [targetParentLocation, to] = tail(targetLocation);
|
||||
|
||||
if (sourceGroup.api.location === 'grid') {
|
||||
if (sourceGroup.api.location.type === 'grid') {
|
||||
const sourceLocation = getGridLocation(sourceGroup.element);
|
||||
const [sourceParentLocation, from] = tail(sourceLocation);
|
||||
|
||||
@ -1563,7 +1650,7 @@ export class DockviewComponent
|
||||
});
|
||||
}
|
||||
} else {
|
||||
switch (sourceGroup.api.location) {
|
||||
switch (sourceGroup.api.location.type) {
|
||||
case 'grid':
|
||||
this.gridview.removeView(
|
||||
getGridLocation(sourceGroup.element)
|
||||
@ -1581,12 +1668,12 @@ export class DockviewComponent
|
||||
}
|
||||
case 'popout': {
|
||||
const selectedPopoutGroup = this._popoutGroups.find(
|
||||
(x) => x.group === sourceGroup
|
||||
(x) => x.popoutGroup === sourceGroup
|
||||
);
|
||||
if (!selectedPopoutGroup) {
|
||||
throw new Error('failed to find popout group');
|
||||
}
|
||||
selectedPopoutGroup.dispose();
|
||||
selectedPopoutGroup.disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1620,6 +1707,15 @@ export class DockviewComponent
|
||||
}
|
||||
}
|
||||
|
||||
private getNextGroupId(): string {
|
||||
let id = this.nextGroupId.next();
|
||||
while (this._groups.has(id)) {
|
||||
id = this.nextGroupId.next();
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
createGroup(options?: GroupOptions): DockviewGroupPanel {
|
||||
if (!options) {
|
||||
options = {};
|
||||
@ -1642,7 +1738,7 @@ export class DockviewComponent
|
||||
}
|
||||
|
||||
const view = new DockviewGroupPanel(this, id, options);
|
||||
view.init({ params: {}, accessor: <any>null }); // required to initialized .part and allow for correct disposal of group
|
||||
view.init({ params: {}, accessor: this });
|
||||
|
||||
if (!this._groups.has(view.id)) {
|
||||
const disposable = new CompositeDisposable(
|
||||
@ -1677,8 +1773,7 @@ export class DockviewComponent
|
||||
this._groups.set(view.id, { value: view, disposable });
|
||||
}
|
||||
|
||||
// TODO: must be called after the above listeners have been setup,
|
||||
// not an ideal pattern
|
||||
// TODO: must be called after the above listeners have been setup, not an ideal pattern
|
||||
view.initialize();
|
||||
|
||||
return view;
|
||||
|
@ -130,7 +130,10 @@ export interface IDockviewGroupPanelModel extends IPanel {
|
||||
): boolean;
|
||||
}
|
||||
|
||||
export type DockviewGroupLocation = 'grid' | 'floating' | 'popout';
|
||||
export type DockviewGroupLocation =
|
||||
| { type: 'grid' }
|
||||
| { type: 'floating' }
|
||||
| { type: 'popout'; getWindow: () => Window };
|
||||
|
||||
export class DockviewGroupPanelModel
|
||||
extends CompositeDisposable
|
||||
@ -138,7 +141,6 @@ export class DockviewGroupPanelModel
|
||||
{
|
||||
private readonly tabsContainer: ITabsContainer;
|
||||
private readonly contentContainer: IContentContainer;
|
||||
// private readonly dropTarget: Droptarget;
|
||||
private _activePanel: IDockviewPanel | undefined;
|
||||
private watermark?: IWatermarkRenderer;
|
||||
private _isGroupActive = false;
|
||||
@ -147,7 +149,7 @@ export class DockviewGroupPanelModel
|
||||
private _leftHeaderActions: IHeaderActionsRenderer | undefined;
|
||||
private _prefixHeaderActions: IHeaderActionsRenderer | undefined;
|
||||
|
||||
private _location: DockviewGroupLocation = 'grid';
|
||||
private _location: DockviewGroupLocation = { type: 'grid' };
|
||||
|
||||
private mostRecentlyUsed: IDockviewPanel[] = [];
|
||||
|
||||
@ -254,7 +256,7 @@ export class DockviewGroupPanelModel
|
||||
toggleClass(this.container, 'dv-groupview-floating', false);
|
||||
toggleClass(this.container, 'dv-groupview-popout', false);
|
||||
|
||||
switch (value) {
|
||||
switch (value.type) {
|
||||
case 'grid':
|
||||
this.contentContainer.dropTarget.setTargetZones([
|
||||
'top',
|
||||
@ -527,6 +529,7 @@ export class DockviewGroupPanelModel
|
||||
if (!skipSetGroupActive) {
|
||||
this.accessor.doSetGroupActive(this.groupPanel);
|
||||
}
|
||||
this.contentContainer.renderPanel(panel, { asActive: true });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -835,6 +838,7 @@ export class DockviewGroupPanelModel
|
||||
|
||||
this.watermark?.element.remove();
|
||||
this.watermark?.dispose?.();
|
||||
this.watermark = undefined;
|
||||
|
||||
for (const panel of this.panels) {
|
||||
panel.dispose();
|
||||
|
@ -1,44 +0,0 @@
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { PopoutWindow } from '../popoutWindow';
|
||||
import { Box } from '../types';
|
||||
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
||||
|
||||
export class DockviewPopoutGroupPanel extends CompositeDisposable {
|
||||
readonly window: PopoutWindow;
|
||||
|
||||
constructor(
|
||||
readonly id: string,
|
||||
readonly group: DockviewGroupPanel,
|
||||
private readonly options: {
|
||||
className: string;
|
||||
popoutUrl: string;
|
||||
box: Box;
|
||||
}
|
||||
) {
|
||||
super();
|
||||
|
||||
this.window = new PopoutWindow(id, options.className ?? '', {
|
||||
url: this.options.popoutUrl,
|
||||
left: this.options.box.left,
|
||||
top: this.options.box.top,
|
||||
width: this.options.box.width,
|
||||
height: this.options.box.height,
|
||||
});
|
||||
|
||||
group.model.location = 'popout';
|
||||
|
||||
this.addDisposables(
|
||||
this.window,
|
||||
{
|
||||
dispose: () => {
|
||||
group.model.location = 'grid';
|
||||
},
|
||||
},
|
||||
this.window.onDidClose(() => {
|
||||
this.dispose();
|
||||
})
|
||||
);
|
||||
|
||||
this.window.open(group.element);
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import { ISplitviewStyles, Orientation } from '../splitview/splitview';
|
||||
import { PanelTransfer } from '../dnd/dataTransfer';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { DroptargetOverlayModel, Position } from '../dnd/droptarget';
|
||||
import { GroupOptions } from './dockviewGroupPanelModel';
|
||||
import { IDockviewPanel } from './dockviewPanel';
|
||||
import {
|
||||
ComponentConstructor,
|
||||
@ -89,7 +90,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
|
||||
group: DockviewGroupPanel
|
||||
) => IHeaderActionsRenderer;
|
||||
singleTabMode?: 'fullwidth' | 'default';
|
||||
parentElement?: HTMLElement;
|
||||
parentElement: HTMLElement;
|
||||
disableFloatingGroups?: boolean;
|
||||
floatingGroupBounds?:
|
||||
| 'boundedWithinViewport'
|
||||
@ -187,10 +188,12 @@ type AddGroupOptionsWithGroup = {
|
||||
direction?: Omit<Direction, 'within'>;
|
||||
};
|
||||
|
||||
export type AddGroupOptions =
|
||||
export type AddGroupOptions = (
|
||||
| AddGroupOptionsWithGroup
|
||||
| AddGroupOptionsWithPanel
|
||||
| AbsolutePosition;
|
||||
| AbsolutePosition
|
||||
) &
|
||||
GroupOptions;
|
||||
|
||||
export function isGroupOptionsWithPanel(
|
||||
data: AddGroupOptions
|
||||
|
@ -0,0 +1,4 @@
|
||||
.dv-root-wrapper {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { Emitter, Event, TickDelayedEvent } from '../events';
|
||||
import { getGridLocation, Gridview, IGridView } from './gridview';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { IValueDisposable } from '../lifecycle';
|
||||
import { Disposable, IValueDisposable } from '../lifecycle';
|
||||
import { sequentialNumberGenerator } from '../math';
|
||||
import { ISplitviewStyles, Orientation, Sizing } from '../splitview/splitview';
|
||||
import { IPanel } from '../panel/types';
|
||||
@ -32,7 +32,7 @@ export interface BaseGridOptions {
|
||||
readonly proportionalLayout: boolean;
|
||||
readonly orientation: Orientation;
|
||||
readonly styles?: ISplitviewStyles;
|
||||
readonly parentElement?: HTMLElement;
|
||||
readonly parentElement: HTMLElement;
|
||||
readonly disableAutoResizing?: boolean;
|
||||
readonly locked?: boolean;
|
||||
}
|
||||
@ -143,7 +143,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
}
|
||||
|
||||
constructor(options: BaseGridOptions) {
|
||||
super(options.parentElement, options.disableAutoResizing);
|
||||
super(document.createElement('div'), options.disableAutoResizing);
|
||||
|
||||
options.parentElement.appendChild(this.element);
|
||||
|
||||
this.gridview = new Gridview(
|
||||
!!options.proportionalLayout,
|
||||
@ -158,6 +160,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
this.layout(0, 0, true); // set some elements height/widths
|
||||
|
||||
this.addDisposables(
|
||||
Disposable.from(() => {
|
||||
this.element.parentElement?.removeChild(this.element);
|
||||
}),
|
||||
this.gridview.onDidChange(() => {
|
||||
this._bufferOnDidLayoutChange.fire();
|
||||
}),
|
||||
|
@ -275,7 +275,9 @@ export class Gridview implements IDisposable {
|
||||
|
||||
private _root: BranchNode | undefined;
|
||||
private _locked = false;
|
||||
private _maximizedNode: LeafNode | undefined = undefined;
|
||||
private _maximizedNode:
|
||||
| { leaf: LeafNode; hiddenOnMaximize: LeafNode[] }
|
||||
| undefined = undefined;
|
||||
private readonly disposable: MutableDisposable = new MutableDisposable();
|
||||
|
||||
private readonly _onDidChange = new Emitter<{
|
||||
@ -355,7 +357,7 @@ export class Gridview implements IDisposable {
|
||||
}
|
||||
|
||||
maximizedView(): IGridView | undefined {
|
||||
return this._maximizedNode?.view;
|
||||
return this._maximizedNode?.leaf.view;
|
||||
}
|
||||
|
||||
hasMaximizedView(): boolean {
|
||||
@ -370,7 +372,7 @@ export class Gridview implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._maximizedNode === node) {
|
||||
if (this._maximizedNode?.leaf === node) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -378,12 +380,18 @@ export class Gridview implements IDisposable {
|
||||
this.exitMaximizedView();
|
||||
}
|
||||
|
||||
const hiddenOnMaximize: LeafNode[] = [];
|
||||
|
||||
function hideAllViewsBut(parent: BranchNode, exclude: LeafNode): void {
|
||||
for (let i = 0; i < parent.children.length; i++) {
|
||||
const child = parent.children[i];
|
||||
if (child instanceof LeafNode) {
|
||||
if (child !== exclude) {
|
||||
if (parent.isChildVisible(i)) {
|
||||
parent.setChildVisible(i, false);
|
||||
} else {
|
||||
hiddenOnMaximize.push(child);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hideAllViewsBut(child, exclude);
|
||||
@ -392,7 +400,7 @@ export class Gridview implements IDisposable {
|
||||
}
|
||||
|
||||
hideAllViewsBut(this.root, node);
|
||||
this._maximizedNode = node;
|
||||
this._maximizedNode = { leaf: node, hiddenOnMaximize };
|
||||
this._onDidMaxmizedNodeChange.fire();
|
||||
}
|
||||
|
||||
@ -401,11 +409,15 @@ export class Gridview implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const hiddenOnMaximize = this._maximizedNode.hiddenOnMaximize;
|
||||
|
||||
function showViewsInReverseOrder(parent: BranchNode): void {
|
||||
for (let index = parent.children.length - 1; index >= 0; index--) {
|
||||
const child = parent.children[index];
|
||||
if (child instanceof LeafNode) {
|
||||
if (!hiddenOnMaximize.includes(child)) {
|
||||
parent.setChildVisible(index, true);
|
||||
}
|
||||
} else {
|
||||
showViewsInReverseOrder(child);
|
||||
}
|
||||
@ -421,8 +433,8 @@ export class Gridview implements IDisposable {
|
||||
public serialize(): SerializedGridview<any> {
|
||||
if (this.hasMaximizedView()) {
|
||||
/**
|
||||
* do not persist maximized view state but we must first exit any maximized views
|
||||
* before serialization to ensure the correct dimensions are persisted
|
||||
* do not persist maximized view state
|
||||
* firstly exit any maximized views to ensure the correct dimensions are persisted
|
||||
*/
|
||||
this.exitMaximizedView();
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
import { LayoutPriority } from '../splitview/splitview';
|
||||
import { Emitter, Event } from '../events';
|
||||
import { IViewSize } from './gridview';
|
||||
import { BaseGrid, IGridPanelView } from './baseComponentGridview';
|
||||
|
||||
export interface GridviewInitParameters extends PanelInitParameters {
|
||||
minimumWidth?: number;
|
||||
@ -24,7 +25,7 @@ export interface GridviewInitParameters extends PanelInitParameters {
|
||||
maximumHeight?: number;
|
||||
priority?: LayoutPriority;
|
||||
snap?: boolean;
|
||||
accessor: GridviewComponent;
|
||||
accessor: BaseGrid<IGridPanelView>;
|
||||
isVisible?: boolean;
|
||||
}
|
||||
|
||||
@ -157,14 +158,16 @@ export abstract class GridviewPanel<
|
||||
this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement
|
||||
|
||||
this.addDisposables(
|
||||
this.api.onVisibilityChange((event) => {
|
||||
const { isVisible } = event;
|
||||
this.api.onDidHiddenChange((event) => {
|
||||
const { isHidden } = event;
|
||||
const { accessor } = this._params as GridviewInitParameters;
|
||||
accessor.setVisible(this, isVisible);
|
||||
|
||||
accessor.setVisible(this, !isHidden);
|
||||
}),
|
||||
this.api.onActiveChange(() => {
|
||||
const { accessor } = this._params as GridviewInitParameters;
|
||||
accessor.setActive(this);
|
||||
|
||||
accessor.doSetGroupActive(this);
|
||||
}),
|
||||
this.api.onDidConstraintsChangeInternal((event) => {
|
||||
if (
|
||||
|
@ -17,5 +17,5 @@ export interface GridviewComponentOptions {
|
||||
};
|
||||
frameworkComponentFactory?: FrameworkFactory<GridviewPanel>;
|
||||
styles?: ISplitviewStyles;
|
||||
parentElement?: HTMLElement;
|
||||
parentElement: HTMLElement;
|
||||
}
|
||||
|
@ -24,10 +24,10 @@ export namespace Disposable {
|
||||
}
|
||||
|
||||
export class CompositeDisposable {
|
||||
private readonly _disposables: IDisposable[];
|
||||
private _disposables: IDisposable[];
|
||||
private _isDisposed = false;
|
||||
|
||||
protected get isDisposed(): boolean {
|
||||
get isDisposed(): boolean {
|
||||
return this._isDisposed;
|
||||
}
|
||||
|
||||
@ -40,9 +40,13 @@ export class CompositeDisposable {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposables.forEach((arg) => arg.dispose());
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isDisposed = true;
|
||||
this._disposables.forEach((arg) => arg.dispose());
|
||||
this._disposables = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ export class OverlayRenderContainer extends CompositeDisposable {
|
||||
toggleClass(
|
||||
focusContainer,
|
||||
'dv-render-overlay-float',
|
||||
panel.group.api.location === 'floating'
|
||||
panel.group.api.location.type === 'floating'
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -25,5 +25,5 @@ export interface PaneviewComponentOptions {
|
||||
};
|
||||
disableDnd?: boolean;
|
||||
showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean;
|
||||
parentElement?: HTMLElement;
|
||||
parentElement: HTMLElement;
|
||||
}
|
||||
|
@ -5,22 +5,31 @@ import { Box } from './types';
|
||||
|
||||
export type PopoutWindowOptions = {
|
||||
url: string;
|
||||
onDidOpen?: (event: { id: string; window: Window }) => void;
|
||||
onWillClose?: (event: { id: string; window: Window }) => void;
|
||||
} & Box;
|
||||
|
||||
export class PopoutWindow extends CompositeDisposable {
|
||||
private readonly _onWillClose = new Emitter<void>();
|
||||
readonly onWillClose = this._onWillClose.event;
|
||||
|
||||
private readonly _onDidClose = new Emitter<void>();
|
||||
readonly onDidClose = this._onDidClose.event;
|
||||
|
||||
private _window: { value: Window; disposable: IDisposable } | null = null;
|
||||
|
||||
get window(): Window | null {
|
||||
return this._window?.value ?? null;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly id: string,
|
||||
private readonly target: string,
|
||||
private readonly className: string,
|
||||
private readonly options: PopoutWindowOptions
|
||||
) {
|
||||
super();
|
||||
|
||||
this.addDisposables(this._onDidClose, {
|
||||
this.addDisposables(this._onWillClose, this._onDidClose, {
|
||||
dispose: () => {
|
||||
this.close();
|
||||
},
|
||||
@ -42,13 +51,22 @@ export class PopoutWindow extends CompositeDisposable {
|
||||
|
||||
close(): void {
|
||||
if (this._window) {
|
||||
this._onWillClose.fire();
|
||||
|
||||
this.options.onWillClose?.({
|
||||
id: this.target,
|
||||
window: this._window.value,
|
||||
});
|
||||
|
||||
this._window.disposable.dispose();
|
||||
this._window.value.close();
|
||||
this._window = null;
|
||||
|
||||
this._onDidClose.fire();
|
||||
}
|
||||
}
|
||||
|
||||
open(content: HTMLElement): void {
|
||||
async open(): Promise<HTMLElement | null> {
|
||||
if (this._window) {
|
||||
throw new Error('instance of popout window is already open');
|
||||
}
|
||||
@ -64,55 +82,93 @@ export class PopoutWindow extends CompositeDisposable {
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(',');
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/open
|
||||
const externalWindow = window.open(url, this.id, features);
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/open
|
||||
*/
|
||||
const externalWindow = window.open(url, this.target, features);
|
||||
|
||||
if (!externalWindow) {
|
||||
return;
|
||||
/**
|
||||
* Popup blocked
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
const disposable = new CompositeDisposable();
|
||||
|
||||
this._window = { value: externalWindow, disposable };
|
||||
|
||||
const cleanUp = () => {
|
||||
this._onDidClose.fire();
|
||||
this._window = null;
|
||||
};
|
||||
|
||||
// prevent any default content from loading
|
||||
// externalWindow.document.body.replaceWith(document.createElement('div'));
|
||||
|
||||
disposable.addDisposables(
|
||||
addDisposableWindowListener(window, 'beforeunload', () => {
|
||||
cleanUp();
|
||||
/**
|
||||
* before the main window closes we should close this popup too
|
||||
* to be good citizens
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
|
||||
*/
|
||||
this.close();
|
||||
})
|
||||
);
|
||||
|
||||
const container = this.createPopoutWindowContainer();
|
||||
|
||||
if (this.className) {
|
||||
container.classList.add(this.className);
|
||||
}
|
||||
|
||||
this.options.onDidOpen?.({
|
||||
id: this.target,
|
||||
window: externalWindow,
|
||||
});
|
||||
|
||||
return new Promise<HTMLElement | null>((resolve) => {
|
||||
externalWindow.addEventListener('unload', (e) => {
|
||||
// if page fails to load before unloading
|
||||
// this.close();
|
||||
});
|
||||
|
||||
externalWindow.addEventListener('load', () => {
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
|
||||
*/
|
||||
|
||||
const externalDocument = externalWindow.document;
|
||||
externalDocument.title = document.title;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('dv-popout-window');
|
||||
div.style.position = 'absolute';
|
||||
div.style.width = '100%';
|
||||
div.style.height = '100%';
|
||||
div.style.top = '0px';
|
||||
div.style.left = '0px';
|
||||
div.classList.add(this.className);
|
||||
div.appendChild(content);
|
||||
|
||||
externalDocument.body.replaceChildren(div);
|
||||
externalDocument.body.classList.add(this.className);
|
||||
externalDocument.body.appendChild(container);
|
||||
|
||||
addStyles(externalDocument, window.document.styleSheets);
|
||||
|
||||
externalWindow.addEventListener('beforeunload', () => {
|
||||
// TODO: indicate external window is closing
|
||||
cleanUp();
|
||||
/**
|
||||
* beforeunload must be registered after load for reasons I could not determine
|
||||
* otherwise the beforeunload event will not fire when the window is closed
|
||||
*/
|
||||
addDisposableWindowListener(
|
||||
externalWindow,
|
||||
'beforeunload',
|
||||
() => {
|
||||
/**
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
|
||||
*/
|
||||
this.close();
|
||||
}
|
||||
);
|
||||
|
||||
resolve(container);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createPopoutWindowContainer(): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
el.classList.add('dv-popout-window');
|
||||
el.id = 'dv-popout-window';
|
||||
el.style.position = 'absolute';
|
||||
el.style.width = '100%';
|
||||
el.style.height = '100%';
|
||||
el.style.top = '0px';
|
||||
el.style.left = '0px';
|
||||
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
@ -17,19 +17,12 @@ export abstract class Resizable extends CompositeDisposable {
|
||||
this._disableResizing = value;
|
||||
}
|
||||
|
||||
constructor(parentElement?: HTMLElement, disableResizing = false) {
|
||||
constructor(parentElement: HTMLElement, disableResizing = false) {
|
||||
super();
|
||||
|
||||
this._disableResizing = disableResizing;
|
||||
|
||||
if (parentElement) {
|
||||
this._element = parentElement;
|
||||
} else {
|
||||
this._element = document.createElement('div');
|
||||
this._element.style.height = '100%';
|
||||
this._element.style.width = '100%';
|
||||
this._element.className = 'dv-resizable-container';
|
||||
}
|
||||
|
||||
this.addDisposables(
|
||||
watchElementResize(this._element, (entry) => {
|
||||
|
@ -28,5 +28,5 @@ export interface SplitviewComponentOptions extends SplitViewOptions {
|
||||
[componentName: string]: any;
|
||||
};
|
||||
frameworkWrapper?: FrameworkFactory<SplitviewPanel>;
|
||||
parentElement?: HTMLElement;
|
||||
parentElement: HTMLElement;
|
||||
}
|
||||
|
@ -89,10 +89,10 @@ export abstract class SplitviewPanel
|
||||
|
||||
this.addDisposables(
|
||||
this._onDidChange,
|
||||
this.api.onVisibilityChange((event) => {
|
||||
const { isVisible } = event;
|
||||
this.api.onDidHiddenChange((event) => {
|
||||
const { isHidden } = event;
|
||||
const { accessor } = this._params as PanelViewInitParameters;
|
||||
accessor.setVisible(this, isVisible);
|
||||
accessor.setVisible(this, !isHidden);
|
||||
}),
|
||||
this.api.onActiveChange(() => {
|
||||
const { accessor } = this._params as PanelViewInitParameters;
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
@mixin dockview-theme-dark-mixin {
|
||||
@include dockview-theme-core-mixin();
|
||||
|
||||
//
|
||||
--dv-group-view-background-color: #1e1e1e;
|
||||
//
|
||||
@ -228,13 +229,7 @@
|
||||
}
|
||||
|
||||
@mixin dockview-design-replit-mixin {
|
||||
&.dv-dockview {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.view:has(> .groupview) {
|
||||
padding: 3px;
|
||||
}
|
||||
--dv-group-gap-size: 3px;
|
||||
|
||||
.dv-resize-container:has(> .groupview) {
|
||||
border-radius: 8px;
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.2",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -54,6 +54,6 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^1.9.0"
|
||||
"dockview-core": "^1.9.2"
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
GridviewPanel,
|
||||
GridviewInitParameters,
|
||||
IFrameworkPart,
|
||||
GridviewComponent,
|
||||
} from 'dockview-core';
|
||||
import { ReactPart, ReactPortalStore } from '../react';
|
||||
import { IGridviewPanelProps } from './gridview';
|
||||
@ -25,8 +26,10 @@ export class ReactGridPanelView extends GridviewPanel {
|
||||
{
|
||||
params: this._params?.params ?? {},
|
||||
api: this.api,
|
||||
// TODO: fix casting hack
|
||||
containerApi: new GridviewApi(
|
||||
(this._params as GridviewInitParameters).accessor
|
||||
(this._params as GridviewInitParameters)
|
||||
.accessor as GridviewComponent
|
||||
),
|
||||
}
|
||||
);
|
||||
|
21
packages/docs/blog/2024-01-20-dockview-1.9.1.md
Normal file
21
packages/docs/blog/2024-01-20-dockview-1.9.1.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
slug: dockview-1.9.1-release
|
||||
title: Dockview 1.9.1
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference to docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Drop target overlay classnames [#452](https://github.com/mathuo/dockview/issues/452)
|
||||
|
||||
- Expose root drop target configuration options [#431](https://github.com/mathuo/dockview/issues/431)
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Bug: Floating groups position reset when display:none applied to component [#458](https://github.com/mathuo/dockview/issues/458)
|
||||
|
||||
## 🔥 Breaking changes
|
19
packages/docs/blog/2024-01-23-dockview-1.9.2.md
Normal file
19
packages/docs/blog/2024-01-23-dockview-1.9.2.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
slug: dockview-1.9.2-release
|
||||
title: Dockview 1.9.2
|
||||
tags: [release]
|
||||
---
|
||||
|
||||
# Release Notes
|
||||
|
||||
Please reference to docs @ [dockview.dev](https://dockview.dev).
|
||||
|
||||
## 🚀 Features
|
||||
|
||||
- Expose addGroup options [#465](https://github.com/mathuo/dockview/issues/465)
|
||||
|
||||
## 🛠 Miscs
|
||||
|
||||
- Bug: Panel rendering broken when closing adjacent tabs [#472](https://github.com/mathuo/dockview/issues/472)
|
||||
|
||||
## 🔥 Breaking changes
|
@ -7,7 +7,7 @@ import { MultiFrameworkContainer } from '@site/src/components/ui/container';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app';
|
||||
import DockviewPersistence from '@site/sandboxes/layout-dockview/src/app';
|
||||
import SimpleDockview from '@site/sandboxes/simple-dockview/src/app';
|
||||
import ResizeDockview from '@site/sandboxes/resize-dockview/src/app';
|
||||
import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app';
|
||||
@ -140,7 +140,7 @@ As well as importing the `dockview` stylesheet you must provide a class-based th
|
||||
|
||||
You can find more details on theming <Link to="../theme">here</Link>.
|
||||
|
||||
## Layout Persistance
|
||||
## Layout Persistence
|
||||
|
||||
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.
|
||||
@ -156,7 +156,7 @@ React.useEffect(() => {
|
||||
const layout = api.toJSON();
|
||||
|
||||
localStorage.setItem(
|
||||
'dockview_persistance_layout',
|
||||
'dockview_persistence_layout',
|
||||
JSON.stringify(layout)
|
||||
);
|
||||
});
|
||||
@ -169,7 +169,7 @@ React.useEffect(() => {
|
||||
|
||||
```tsx title="Loading a layout from localStorage"
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const layoutString = localStorage.getItem('dockview_persistance_layout');
|
||||
const layoutString = localStorage.getItem('dockview_persistence_layout');
|
||||
|
||||
let success = false;
|
||||
|
||||
@ -194,7 +194,7 @@ If you refresh the page you should notice your layout is loaded as you left it.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="layout-dockview"
|
||||
react={DockviewPersistance}
|
||||
react={DockviewPersistence}
|
||||
/>
|
||||
|
||||
## Scrollbars
|
||||
@ -264,6 +264,18 @@ which will be rendered when there are no panels or groups.
|
||||
|
||||
## Drag And Drop
|
||||
|
||||
You can override the conditions of the far edge overlays through the `rootOverlayModel` prop.
|
||||
|
||||
```tsx
|
||||
<DockviewReact
|
||||
{...props}
|
||||
rootOverlayModel={{
|
||||
size: { value: 100, type: 'pixels' },
|
||||
activationSize: { value: 5, type: 'percentage' },
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Built-in behaviours
|
||||
|
||||
Dockview supports a wide variety of built-in Drag and Drop possibilities.
|
||||
|
@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "dockview-docs",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "docusaurus build",
|
||||
"clear": "docusaurus clear",
|
||||
"start": "docusaurus start",
|
||||
"docs:version": "docusaurus docs:version",
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"browserslist": {
|
||||
@ -30,9 +31,10 @@
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"axios": "^1.6.3",
|
||||
"clsx": "^2.1.0",
|
||||
"dockview": "^1.9.0",
|
||||
"dockview": "^1.9.2",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-laag": "^2.0.5",
|
||||
"recoil": "^0.7.7",
|
||||
"source-map-loader": "^4.0.2",
|
||||
"uuid": "^9.0.1"
|
||||
|
@ -14,8 +14,27 @@ import './app.scss';
|
||||
const components = {
|
||||
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
return (
|
||||
<div style={{ height: '100%', overflow: 'auto', color: 'white' }}>
|
||||
{''}
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
color: 'white',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%,-50%)',
|
||||
pointerEvents: 'none',
|
||||
fontSize: '42px',
|
||||
opacity: 0.5,
|
||||
}}
|
||||
>
|
||||
{props.api.title}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@ -68,7 +87,7 @@ const RightControls = (props: IDockviewHeaderActionsProps) => {
|
||||
);
|
||||
|
||||
const [isPopout, setIsPopout] = React.useState<boolean>(
|
||||
props.api.location === 'popout'
|
||||
props.api.location.type === 'popout'
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -77,7 +96,7 @@ const RightControls = (props: IDockviewHeaderActionsProps) => {
|
||||
});
|
||||
|
||||
const disposable2 = props.api.onDidLocationChange(() => {
|
||||
setIsPopout(props.api.location === 'popout');
|
||||
setIsPopout(props.api.location.type === 'popout');
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
@ -180,6 +180,10 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => {
|
||||
className={`${props.theme || 'dockview-theme-abyss'}`}
|
||||
onDidDrop={onDidDrop}
|
||||
showDndOverlay={showDndOverlay}
|
||||
rootOverlayModel={{
|
||||
size: { value: 100, type: 'pixels' },
|
||||
activationSize: { value: 5, type: 'percentage' },
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -127,7 +127,7 @@ const useLocalStorage = <T,>(
|
||||
];
|
||||
};
|
||||
|
||||
export const DockviewPersistance = (props: { theme?: string }) => {
|
||||
export const DockviewPersistence = (props: { theme?: string }) => {
|
||||
const [api, setApi] = React.useState<DockviewApi>();
|
||||
const [layout, setLayout] =
|
||||
useLocalStorage<SerializedDockview>('floating.layout');
|
||||
@ -255,13 +255,13 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
|
||||
const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
const [floating, setFloating] = React.useState<boolean>(
|
||||
props.api.location === 'floating'
|
||||
props.api.location.type === 'floating'
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.group.api.onDidLocationChange(
|
||||
(event) => {
|
||||
setFloating(event.location === 'floating');
|
||||
setFloating(event.location.type === 'floating');
|
||||
}
|
||||
);
|
||||
|
||||
@ -289,7 +289,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default DockviewPersistance;
|
||||
export default DockviewPersistence;
|
||||
|
||||
const Watermark = () => {
|
||||
return <div style={{ color: 'white', padding: '8px' }}>watermark</div>;
|
||||
|
@ -47,11 +47,11 @@ function loadDefaultLayout(api: DockviewApi) {
|
||||
});
|
||||
}
|
||||
|
||||
export const DockviewPersistance = (props: { theme?: string }) => {
|
||||
export const DockviewPersistence = (props: { theme?: string }) => {
|
||||
const [api, setApi] = React.useState<DockviewApi>();
|
||||
|
||||
const clearLayout = () => {
|
||||
localStorage.removeItem('dockview_persistance_layout');
|
||||
localStorage.removeItem('dockview_persistence_layout');
|
||||
if (api) {
|
||||
api.clear();
|
||||
loadDefaultLayout(api);
|
||||
@ -60,7 +60,7 @@ export const DockviewPersistance = (props: { theme?: string }) => {
|
||||
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const layoutString = localStorage.getItem(
|
||||
'dockview_persistance_layout'
|
||||
'dockview_persistence_layout'
|
||||
);
|
||||
|
||||
let success = false;
|
||||
@ -91,7 +91,7 @@ export const DockviewPersistance = (props: { theme?: string }) => {
|
||||
const layout = api.toJSON();
|
||||
|
||||
localStorage.setItem(
|
||||
'dockview_persistance_layout',
|
||||
'dockview_persistence_layout',
|
||||
JSON.stringify(layout)
|
||||
);
|
||||
});
|
||||
@ -125,7 +125,7 @@ export const DockviewPersistance = (props: { theme?: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default DockviewPersistance;
|
||||
export default DockviewPersistence;
|
||||
|
||||
const Watermark = () => {
|
||||
return <div style={{ color: 'white', padding: '8px' }}>watermark</div>;
|
||||
|
@ -9,7 +9,8 @@
|
||||
"dependencies": {
|
||||
"dockview": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"react-laag": "^2.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
|
@ -1,17 +1,38 @@
|
||||
import {
|
||||
DockviewApi,
|
||||
DockviewGroupPanel,
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewHeaderActionsProps,
|
||||
IDockviewPanelProps,
|
||||
SerializedDockview,
|
||||
DockviewPanelApi,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import { Icon } from './utils';
|
||||
import { PopoverMenu } from './popover';
|
||||
|
||||
function usePanelWindowObject(api: DockviewPanelApi): Window {
|
||||
const [document, setDocument] = React.useState<Window>(api.getWindow());
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = api.onDidLocationChange((event) => {
|
||||
setDocument(api.getWindow());
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
const components = {
|
||||
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
const _window = usePanelWindowObject(props.api);
|
||||
|
||||
const [reset, setReset] = React.useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -20,51 +41,55 @@ const components = {
|
||||
background: 'var(--dv-group-view-background-color)',
|
||||
}}
|
||||
>
|
||||
{props.params.title}
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log(_window);
|
||||
setReset(true);
|
||||
setTimeout(() => {
|
||||
setReset(false);
|
||||
}, 2000);
|
||||
}}
|
||||
>
|
||||
Print
|
||||
</button>
|
||||
{!reset && <PopoverMenu window={_window} />}
|
||||
{props.api.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_2',
|
||||
// component: 'default',
|
||||
// });
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
});
|
||||
// api.addPanel({
|
||||
// id: 'panel_3',
|
||||
// component: 'default',
|
||||
// });
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
});
|
||||
// api.addPanel({
|
||||
// id: 'panel_4',
|
||||
// component: 'default',
|
||||
// });
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_5',
|
||||
component: 'default',
|
||||
position: { direction: 'right' },
|
||||
});
|
||||
// api.addPanel({
|
||||
// id: 'panel_5',
|
||||
// component: 'default',
|
||||
// position: { direction: 'right' },
|
||||
// });
|
||||
|
||||
api.addPanel({
|
||||
id: 'panel_6',
|
||||
component: 'default',
|
||||
});
|
||||
// api.addPanel({
|
||||
// id: 'panel_6',
|
||||
// component: 'default',
|
||||
// });
|
||||
}
|
||||
|
||||
let panelCount = 0;
|
||||
@ -214,12 +239,12 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
|
||||
const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
const [popout, setPopout] = React.useState<boolean>(
|
||||
props.api.location === 'popout'
|
||||
props.api.location.type === 'popout'
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.group.api.onDidLocationChange((event) => [
|
||||
setPopout(event.location === 'popout'),
|
||||
setPopout(event.location.type === 'popout'),
|
||||
]);
|
||||
|
||||
return () => {
|
||||
@ -232,7 +257,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
const group = props.containerApi.addGroup();
|
||||
props.group.api.moveTo({ group });
|
||||
} else {
|
||||
props.containerApi.addPopoutGroup(props.group, {
|
||||
const window = props.containerApi.addPopoutGroup(props.group, {
|
||||
popoutUrl: '/popout/index.html',
|
||||
});
|
||||
}
|
||||
|
55
packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx
Normal file
55
packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import { useLayer, Arrow } from 'react-laag';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import * as React from 'react';
|
||||
import { DockviewPanelApi } from 'dockview';
|
||||
|
||||
export function PopoverMenu(props: { window: Window }) {
|
||||
const [isOpen, setOpen] = React.useState(false);
|
||||
|
||||
// helper function to close the menu
|
||||
function close() {
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
const { renderLayer, triggerProps, layerProps, arrowProps } = useLayer({
|
||||
isOpen,
|
||||
onOutsideClick: close, // close the menu when the user clicks outside
|
||||
onDisappear: close, // close the menu when the menu gets scrolled out of sight
|
||||
overflowContainer: false, // keep the menu positioned inside the container
|
||||
auto: true, // automatically find the best placement
|
||||
placement: 'top-end', // we prefer to place the menu "top-end"
|
||||
triggerOffset: 12, // keep some distance to the trigger
|
||||
containerOffset: 16, // give the menu some room to breath relative to the container
|
||||
arrowOffset: 16, // let the arrow have some room to breath also,
|
||||
environment: props.window,
|
||||
container: props.window
|
||||
? () => {
|
||||
const el = props.window.document.body;
|
||||
Object.setPrototypeOf(el, HTMLElement.prototype);
|
||||
return el;
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
// Again, we're using framer-motion for the transition effect
|
||||
return (
|
||||
<>
|
||||
<button {...triggerProps} onClick={() => setOpen(!isOpen)}>
|
||||
{isOpen ? 'Hide' : 'Show'}
|
||||
</button>
|
||||
{renderLayer(
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<motion.ul {...layerProps}>
|
||||
<li>Item 1</li>
|
||||
<li>Item 2</li>
|
||||
<li>Item 3</li>
|
||||
<li>Item 4</li>
|
||||
<Arrow {...arrowProps} />
|
||||
</motion.ul>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -93,7 +93,7 @@ export const App: React.FC = (props: { theme?: string }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Container = () => {
|
||||
const Container = (props: any) => {
|
||||
const [value, setValue] = React.useState<string>('50');
|
||||
|
||||
return (
|
||||
@ -108,7 +108,7 @@ const Container = () => {
|
||||
value={value}
|
||||
/>
|
||||
<div style={{ height: `${value}%`, width: `${value}%` }}>
|
||||
<App />
|
||||
<App {...props} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,873 +0,0 @@
|
||||
---
|
||||
description: Dockview Documentation
|
||||
---
|
||||
|
||||
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app';
|
||||
import SimpleDockview from '@site/sandboxes/simple-dockview/src/app';
|
||||
import ResizeDockview from '@site/sandboxes/resize-dockview/src/app';
|
||||
import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app';
|
||||
import DockviewConstraints from '@site/sandboxes/constraints-dockview/src/app';
|
||||
import DndDockview from '@site/sandboxes/dnd-dockview/src/app';
|
||||
import NestedDockview from '@site/sandboxes/nested-dockview/src/app';
|
||||
import EventsDockview from '@site/sandboxes/events-dockview/src/app';
|
||||
import DockviewGroupControl from '@site/sandboxes/headeractions-dockview/src/app';
|
||||
import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app';
|
||||
import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app';
|
||||
import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app';
|
||||
import DockviewSetTitle from '@site/sandboxes/updatetitle-dockview/src/app';
|
||||
import RenderingDockview from '@site/sandboxes/rendering-dockview/src/app';
|
||||
import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app';
|
||||
import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app';
|
||||
import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app';
|
||||
import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app';
|
||||
import DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app';
|
||||
import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app';
|
||||
import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app';
|
||||
|
||||
import { DocRef, Markdown } from '@site/src/components/ui/reference/docRef';
|
||||
|
||||
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
|
||||
import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app';
|
||||
import { attach as attachTabHeightDockview } from '@site/sandboxes/javascript/tabheight-dockview/src/app';
|
||||
import { attach as attachNativeDockview } from '@site/sandboxes/javascript/fullwidthtab-dockview/src/app';
|
||||
|
||||
# Dockview
|
||||
|
||||
## Introduction
|
||||
|
||||
Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="simple-dockview"
|
||||
react={SimpleDockview}
|
||||
typescript={attachSimpleDockview}
|
||||
/>
|
||||
|
||||
<br />
|
||||
|
||||
> You can access the panels associated group through the `panel.group` variable.
|
||||
> The group will always be defined and will change if a panel is moved into another group.
|
||||
|
||||
## DockviewReact Component
|
||||
|
||||
You can create a Dockview through the use of the `DockviewReact` component.
|
||||
|
||||
<p style={{ fontSize: '1.3em' }}>
|
||||
<span>{'All of these are React props available through the '}</span>
|
||||
<code>DockviewReact</code>
|
||||
<span>{' component.'}</span>
|
||||
</p>
|
||||
<DocRef declaration="IDockviewReactProps" />
|
||||
|
||||
## 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...
|
||||
};
|
||||
```
|
||||
|
||||
<p style={{ fontSize: '1.3em' }}>
|
||||
<span>{'All of these methods are available through the '}</span>
|
||||
<code>api</code>
|
||||
<span>{' property of '}</span>
|
||||
<code>DockviewComponent</code>
|
||||
<span>{' and the '}</span>
|
||||
<code>containerApi</code>
|
||||
<span>{' property of '}</span>
|
||||
<code>IDockviewPanel</code>
|
||||
<span>.</span>
|
||||
</p>
|
||||
|
||||
<DocRef declaration="DockviewApi" />
|
||||
|
||||
## Dockview Panel API
|
||||
|
||||
```tsx
|
||||
const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
// props.api...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
<p style={{ fontSize: '1.3em' }}>
|
||||
<span>{'All of these are methods are available through the '}</span>
|
||||
<code>api</code>
|
||||
<span>{' property of '}</span>
|
||||
<code>IDockviewPanel</code>
|
||||
<span>.</span>
|
||||
</p>
|
||||
|
||||
<DocRef declaration="DockviewPanelApi" />
|
||||
|
||||
## Theme
|
||||
|
||||
As well as importing the `dockview` stylesheet you must provide a class-based theme somewhere in your application. For example.
|
||||
|
||||
```tsx
|
||||
// Providing a theme directly through the DockviewReact component props
|
||||
<DockviewReact className="dockview-theme-dark" />
|
||||
|
||||
// Providing a theme somewhere in the DOM tree
|
||||
<div className="dockview-theme-dark">
|
||||
<div>
|
||||
{/**... */}
|
||||
<DockviewReact />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
You can find more details on theming <Link to="../theme">here</Link>.
|
||||
|
||||
## 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.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="layout-dockview"
|
||||
react={DockviewPersistance}
|
||||
/>
|
||||
|
||||
## Resizing
|
||||
|
||||
### Panel 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.
|
||||
|
||||
<MultiFrameworkContainer sandboxId="resize-dockview" react={ResizeDockview} />
|
||||
|
||||
### Container Resizing
|
||||
|
||||
The component will automatically resize to it's container.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="resizecontainer-dockview"
|
||||
react={DockviewResizeContainer}
|
||||
/>
|
||||
|
||||
## 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.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="watermark-dockview"
|
||||
react={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}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Intercepting Drag Events
|
||||
|
||||
You can intercept drag events to attach your own metadata using the `onWillDragPanel` and `onWillDragGroup` api methods.
|
||||
|
||||
<MultiFrameworkContainer sandboxId="dnd-dockview" react={DndDockview} />
|
||||
|
||||
### Third Party Dnd Libraries
|
||||
|
||||
This shows a simple example of a third-party library used inside a panel that relies on drag
|
||||
and drop functionalities. This examples serves to show that `dockview` doesn't interfer with
|
||||
any drag and drop logic for other controls.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="externaldnd-dockview"
|
||||
react={DockviewExternalDnd}
|
||||
/>
|
||||
|
||||
## Floating Groups
|
||||
|
||||
Dockview has built-in support for floating groups. Each floating container can contain a single group with many panels
|
||||
and you can have as many floating containers as needed. You cannot dock multiple groups together in the same floating container.
|
||||
|
||||
Floating groups can be interacted with whilst holding the `shift` key activating the `event.shiftKey` boolean property on `KeyboardEvent` events.
|
||||
|
||||
> Float an existing tab by holding `shift` whilst interacting with the tab
|
||||
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/float_add.svg')} />
|
||||
|
||||
> Move a floating tab by holding `shift` whilst moving the cursor or dragging the empty
|
||||
> header space
|
||||
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/float_move.svg')} />
|
||||
|
||||
> Move an entire floating group by holding `shift` whilst dragging the empty header space
|
||||
|
||||
<img style={{ width: '60%' }} src={useBaseUrl('/img/float_group.svg')} />
|
||||
|
||||
Floating groups can be programatically added through the dockview `api` method `api.addFloatingGroup(...)` and you can check whether
|
||||
a group is floating via the `group.api.isFloating` property. See examples for full code.
|
||||
|
||||
You can control the bounding box of floating groups through the optional `floatingGroupBounds` options:
|
||||
|
||||
- `boundedWithinViewport` will force the entire floating group to be bounded within the docks viewport.
|
||||
- `{minimumHeightWithinViewport?: number, minimumWidthWithinViewport?: number}` sets the respective dimension minimums that must appears within the docks viewport
|
||||
- If no options are provided the defaults of `100px` minimum height and width within the viewport are set.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
height={600}
|
||||
sandboxId="floatinggroup-dockview"
|
||||
react={DockviewFloating}
|
||||
/>
|
||||
|
||||
## 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` is provided then the `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',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
To add a floating panel you should include the `floating` variable which can be either a `boolean` or an object defining it's bounds.
|
||||
These bounds are relative to the dockview component.
|
||||
|
||||
```ts
|
||||
const panel1 = api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
floating: true,
|
||||
});
|
||||
|
||||
const panel2 = api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
floating: { x: 10, y: 10, width: 300, height: 300 },
|
||||
});
|
||||
```
|
||||
|
||||
### Update Panel
|
||||
|
||||
You can programatically update the `params` passed through to the panel through the panal api using `api.updateParameters`.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
params: {
|
||||
keyA: 'valueA',
|
||||
},
|
||||
});
|
||||
|
||||
// ...
|
||||
|
||||
panel.api.updateParameters({
|
||||
keyB: 'valueB',
|
||||
});
|
||||
|
||||
// ...
|
||||
|
||||
panel.api.updateParameters({
|
||||
keyA: 'anotherValueA',
|
||||
});
|
||||
```
|
||||
|
||||
To delete a parameter you should pass a value of `undefined` for the key.
|
||||
|
||||
```ts
|
||||
panel.api.updateParameters({
|
||||
keyA: undefined, // this will delete 'keyA'.
|
||||
});
|
||||
```
|
||||
|
||||
### Move panel
|
||||
|
||||
You can programatically move a panel using the panel `api`.
|
||||
|
||||
```ts
|
||||
panel.api.moveTo({ group, position, index });
|
||||
```
|
||||
|
||||
An equivalent method for moving groups is avaliable on the group `api`.
|
||||
|
||||
```ts
|
||||
const group = panel.api.group;
|
||||
group.api.moveTo({ group, position });
|
||||
```
|
||||
|
||||
### Remove panel
|
||||
|
||||
You can programatically remove a panel using the panel `api`.
|
||||
|
||||
```ts
|
||||
panel.api.close();
|
||||
```
|
||||
|
||||
Given a reference to the panel you can also use the component `api` to remove it.
|
||||
|
||||
```ts
|
||||
const panel = api.getPanel('myPanel');
|
||||
api.removePanel(panel);
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="rendering-dockview"
|
||||
react={RenderingDockview}
|
||||
/>
|
||||
|
||||
## 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.
|
||||
|
||||
The `DockviewDefaulTab` component accepts a `hideClose` prop if you wish only to hide the close button.
|
||||
|
||||
```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 attaches 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.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="customheader-dockview"
|
||||
react={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.setTitle('my_new_custom_title');
|
||||
```
|
||||
|
||||
> Note this only works when using the default tab implementation.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="updatetitle-dockview"
|
||||
react={DockviewSetTitle}
|
||||
/>
|
||||
|
||||
### 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} />
|
||||
```
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="fullwidthtab-dockview"
|
||||
react={DockviewNative}
|
||||
typescript={attachNativeDockview}
|
||||
/>
|
||||
|
||||
### Tab Height
|
||||
|
||||
Tab height can be controlled through CSS.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="tabheight-dockview"
|
||||
react={DockviewTabheight}
|
||||
typescript={attachTabHeightDockview}
|
||||
/>
|
||||
|
||||
## 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;
|
||||
|
||||
// Or
|
||||
|
||||
panel.group.locked = 'no-drop-target';
|
||||
```
|
||||
|
||||
Use `true` to keep drop zones top, right, bottom, left for the group. Use `no-drop-target` to disable all drop zones. For you to get a
|
||||
better understanding of what this means, try and drag the panels in the example below to the locked groups.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="lockedgroup-dockview"
|
||||
react={DockviewLockedGroup}
|
||||
/>
|
||||
|
||||
### Group Controls Panel
|
||||
|
||||
`DockviewReact` accepts `leftHeaderActionsComponent`, `rightHeaderActionsComponent` and `prefixHeaderActionsComponent` which expect a React component with props `IDockviewHeaderActionsProps`.
|
||||
These controls are rendered to left and right side of the space to the right of the tabs in the header bar as well as before the first tab in the case of the prefix header prop.
|
||||
|
||||
```tsx
|
||||
const Component: React.FunctionComponent<IDockviewHeaderActionsProps> = () => {
|
||||
return <div>{'...'}</div>;
|
||||
};
|
||||
|
||||
return <DockviewReact {...props} leftHeaderActionsComponent={Component} rightHeaderActionsComponent={...} />;
|
||||
```
|
||||
|
||||
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 RightHeaderActionsComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
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>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="groupcontrol-dockview"
|
||||
react={DockviewGroupControl}
|
||||
/>
|
||||
|
||||
### Constraints
|
||||
|
||||
You may wish to specify a minimum or maximum height or width for a group which can be done through the group api.
|
||||
|
||||
```tsx
|
||||
api.group.api.setConstraints(...)
|
||||
```
|
||||
|
||||
> Constraints are currently only supported for groups and not individual panels.
|
||||
> If you specific a constraint on a group and move a panel within that group to another group it will no
|
||||
> longer be subject to those constraints since those constraints were on the group and not on the individual panel.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
height={500}
|
||||
sandboxId="constraints-dockview"
|
||||
react={DockviewConstraints}
|
||||
/>
|
||||
|
||||
## iFrames
|
||||
|
||||
iFrames required special attention because of a particular behaviour in how iFrames render:
|
||||
|
||||
> Re-parenting an iFrame will reload the contents of the iFrame or the rephrase this, moving an iFrame within the DOM will cause a reload of its contents.
|
||||
|
||||
You can find many examples of discussions on this. Two reputable forums for example are linked [here](https://bugzilla.mozilla.org/show_bug.cgi?id=254144) and [here](https://github.com/whatwg/html/issues/5484).
|
||||
|
||||
The problem with iFrames and `dockview` is that when you hide or move a panel that panels DOM element may be moved within the DOM or removed from the DOM completely.
|
||||
If your panel contains an iFrame then that iFrame will reload after being re-positioned within the DOM tree and all state in that iFrame will most likely be lost.
|
||||
|
||||
`dockview` does not provide a built-in solution to this because it's too specific of a problem to include in the library.
|
||||
However the below example does show an implementation of a higher-order component `HoistedDockviewPanel`that you could use to work around this problems and make iFrames behave in `dockview`.
|
||||
|
||||
What the higher-order component is doing is to hoist the panels contents into a DOM element that is always present and then `position: absolute` that element to match the dimensions of it's linked panel.
|
||||
The visibility of these hoisted elements is then controlled through some exposed api methods to hide elements that shouldn't be currently shown.
|
||||
|
||||
You should open this example in CodeSandbox using the provided link to understand the code and make use of this implemention if required.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="iframe-dockview"
|
||||
height={600}
|
||||
react={DockviewWithIFrames}
|
||||
/>
|
||||
|
||||
## Events
|
||||
|
||||
A simple example showing events fired by `dockviewz that can be interacted with.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
height={600}
|
||||
sandboxId="events-dockview"
|
||||
react={EventsDockview}
|
||||
/>
|
||||
|
||||
## Keyboard Navigation
|
||||
|
||||
Keyboard shortcuts
|
||||
|
||||
<MultiFrameworkContainer
|
||||
height={600}
|
||||
sandboxId="keyboard-dockview"
|
||||
react={DockviewKeyboard}
|
||||
/>
|
||||
|
||||
### 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`.
|
||||
|
||||
<MultiFrameworkContainer sandboxId="nested-dockview" react={NestedDockview} />
|
||||
|
||||
### Window-like mananger with tabs
|
||||
|
||||
<DockviewNative2 />
|
@ -1,52 +0,0 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
description: Contributing
|
||||
---
|
||||
|
||||
# Contributing
|
||||
|
||||
# Project description
|
||||
|
||||
Dockview is a layout manager library designed to provide a complete layouting solution.
|
||||
It is written in plain TypeScript and can be used without any framework although
|
||||
an extensive React wrapper has always and will always be provided for those using the React framework.
|
||||
|
||||
The project is hosted on GitHub and developed within a Monorepo powered by [Lerna](https://github.com/lerna/lerna).
|
||||
It is developed using the `yarn` package manager since at the time of creation `yarn` was far superior when it came to managing monorepos.
|
||||
The Monorepo contains three packages:
|
||||
|
||||
#### packages/dockview-core
|
||||
|
||||
The core project is entirely written in plain TypeScript without any frameworks or dependencies and it's source-code can be found
|
||||
within the `dockview-core` package which is also published to npm.
|
||||
|
||||
#### packages/dockview
|
||||
|
||||
A complete collection of React components for use through the React framework to use dockview seamlessly
|
||||
and is published to npm. It depends explicitly on `dockview-core` so there is no need to additionally install `dockview-core`.
|
||||
|
||||
> Dockview was originally a React-only library which is why the React version maintains the name `dockview` after
|
||||
> splitting the core logic into a seperate package named `dockview-core`.
|
||||
|
||||
#### packages/docs
|
||||
|
||||
This package contains the code for this documentation website and examples hosted through **CodeSandbox**. It is **not** a published package on npm.
|
||||
|
||||
# Run the project locally
|
||||
|
||||
1. After you have cloned the project from GitHub run `yarn` at the root of the project which will install all project dependencies.
|
||||
2. In order build `packages/dockview-core` then `packages/dockview`.
|
||||
3. Run the docs website through `npm run start` in the `packages/docs` directory and go to _http://localhost:3000_ which
|
||||
will now be running the local copy of `dockview` that you have just built.
|
||||
|
||||
### Examples
|
||||
|
||||
All examples can be found under [**packages/docs/sandboxes**](https://github.com/mathuo/dockview/tree/master/packages/docs/sandboxes).
|
||||
Each example is an independently runnable example through **CodeSandbox**.
|
||||
Through the documentation you will see links to runnable **CodeSandbox** examples.
|
||||
|
||||
## FAQ
|
||||
|
||||
#### Are there any plans to publish wrapper libraries for other frameworks such as Angular and Vue?
|
||||
|
||||
Currently no but this is open for contributors to try.
|
@ -1,91 +0,0 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
description: A zero dependency layout manager supporting ReactJS and Vanilla TypeScript
|
||||
---
|
||||
|
||||
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/sandboxes/simple-dockview/src/app';
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
# Introduction
|
||||
|
||||
**dockview** is a zero dependency layout manager that supports tab, grids and splitviews.
|
||||
|
||||
## 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';
|
||||
```
|
||||
|
||||
There are 4 components you may want to use:
|
||||
|
||||
<Link to="./components/dockview">
|
||||
<h2>Dockview</h2>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimpleDockview />
|
||||
</div>
|
||||
|
||||
<Link to="./components/splitview">
|
||||
<h2>Splitview</h2>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '100px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimpleSplitview />
|
||||
</div>
|
||||
|
||||
<Link to="./components/gridview">
|
||||
<h2>Gridview</h2>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimpleGridview />
|
||||
</div>
|
||||
|
||||
<Link to="./components/paneview">
|
||||
<h2>Paneview</h2>
|
||||
</Link>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: '300px',
|
||||
backgroundColor: 'rgb(30,30,30)',
|
||||
color: 'white',
|
||||
margin: '20px 0px',
|
||||
}}
|
||||
>
|
||||
<SimplePaneview />
|
||||
</div>
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"label": "Components",
|
||||
"collapsible": true,
|
||||
"collapsed": false,
|
||||
"position": 2,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"title": "Components"
|
||||
}
|
||||
}
|
@ -1,235 +0,0 @@
|
||||
---
|
||||
description: Gridview Documentation
|
||||
---
|
||||
|
||||
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
|
||||
import SimpleGridview from '@site/sandboxes/simple-gridview/src/app';
|
||||
import EditorGridview from '@site/sandboxes/editor-gridview/src/app';
|
||||
// import SimpleGridview from '@site/sandboxes/simple-gridview/src/app';
|
||||
import { EventsGridview } from '@site/src/components/gridview/events';
|
||||
// import IDEExample from '@site/sandboxes/ide-example/src/app';
|
||||
import Link from '@docusaurus/Link';
|
||||
import { DocRef } from '@site/src/components/ui/reference/docRef';
|
||||
|
||||
# Gridview
|
||||
|
||||
Gridview is a collection of nested splitviews and is the foundation for the [Dockview](./dockview) component.
|
||||
Gridview serves a purpose when you want only the nested splitviews with no tabs and no headers.
|
||||
|
||||
## Introduction
|
||||
|
||||
<MultiFrameworkContainer
|
||||
height={600}
|
||||
sandboxId="simple-gridview"
|
||||
react={SimpleGridview}
|
||||
/>
|
||||
|
||||
## GridviewReact Component
|
||||
|
||||
```tsx
|
||||
import { ReactGridview } from 'dockview';
|
||||
```
|
||||
|
||||
<DocRef declaration="IGridviewReactProps" />
|
||||
|
||||
## 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...
|
||||
};
|
||||
```
|
||||
|
||||
<DocRef declaration="GridviewApi" />
|
||||
|
||||
## Gridview Panel API
|
||||
|
||||
```tsx
|
||||
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
|
||||
// props.api...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
<DocRef declaration="GridviewPanelApi" />
|
||||
|
||||
## Resizing
|
||||
|
||||
### Panel Resizing
|
||||
|
||||
You can set the size of a panel using `props.api.setSize(...)`.
|
||||
|
||||
```tsx
|
||||
// it's mandatory to provide either a height or a width, providing both is optional
|
||||
props.api.setSize({
|
||||
height: 100,
|
||||
width: 200,
|
||||
});
|
||||
```
|
||||
|
||||
You can update any constraints on the panel. All parameters are optional.
|
||||
|
||||
```tsx
|
||||
props.api.setConstraints({
|
||||
minimumHeight: 100,
|
||||
maximumHeight: 1000
|
||||
minimumWidth: 100,
|
||||
maximumWidth: 1000
|
||||
});
|
||||
```
|
||||
|
||||
You can hide a panel by setting it's visibility to `false`. Hidden panels retain their size
|
||||
at the point of being hidden, if made visible again they will try to resize to the remembered size.
|
||||
|
||||
```tsx
|
||||
props.api.setVisible(false);
|
||||
```
|
||||
|
||||
## Panels
|
||||
|
||||
### Add Panel
|
||||
|
||||
Using the gridview 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 `GridviewReactComponent` component.
|
||||
|
||||
You can pass bounding constraints to limit the size of the panel.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'my_unique_panel_id',
|
||||
component: 'my_component',
|
||||
minimumHeight: 100,
|
||||
maximumHeight: 1000,
|
||||
minimumWidth: 100,
|
||||
maximumWidth: 1000,
|
||||
});
|
||||
```
|
||||
|
||||
You can pass a `snap` parameter which will hide the panel when an attempt is made to move it beyond a minimum width or height if one exists.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'my_unique_panel_id',
|
||||
component: 'my_component',
|
||||
minimumHeight: 100,
|
||||
snap: true,
|
||||
});
|
||||
```
|
||||
|
||||
You can pass a `priority` parameter which will keep the panel a certain priority when being resized. This is useful when you know you want this
|
||||
panel to always take the first available or last available space. The default is `LayoutPriority.Normal` which defers space allocations to the libraries discression.
|
||||
|
||||
```ts
|
||||
const panel = api.addPanel({
|
||||
id: 'my_unique_panel_id',
|
||||
component: 'my_component',
|
||||
minimumHeight: 100,
|
||||
priority: LayoutPriority.High,
|
||||
});
|
||||
```
|
||||
|
||||
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',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```tsx
|
||||
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 accepts a `referencePanel` which can be the associated id as a string
|
||||
or the panel object reference.
|
||||
- This object accepts a `direction` property which dictates where,
|
||||
relative to the provided reference the new panel will be placed.
|
||||
|
||||
> If a `referencePanel` is not passed then the `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',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters
|
||||
> you want the panel to receive.
|
||||
|
||||
## Theme
|
||||
|
||||
As well as importing the `dockview` stylesheet you must provide a class-based theme somewhere in your application. For example.
|
||||
|
||||
```tsx
|
||||
// Providing a theme directly through the DockviewReact component props
|
||||
<GridviewReact className="dockview-theme-dark" />
|
||||
|
||||
// Providing a theme somewhere in the DOM tree
|
||||
<div className="dockview-theme-dark">
|
||||
<div>
|
||||
{/**... */}
|
||||
<GridviewReact />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
You can find more details on theming <Link to="../theme">here</Link>.
|
||||
|
||||
## 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 />
|
||||
|
||||
## Complex Example
|
||||
|
||||
<MultiFrameworkContainer
|
||||
height={600}
|
||||
sandboxId="editor-gridview"
|
||||
react={EditorGridview}
|
||||
hideThemePicker={true}
|
||||
/>
|
@ -1,228 +0,0 @@
|
||||
---
|
||||
description: Paneview Documentation
|
||||
---
|
||||
|
||||
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
|
||||
import SimplePaneview from '@site/sandboxes/simple-paneview/src/app';
|
||||
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';
|
||||
import { DocRef } from '@site/src/components/ui/reference/docRef';
|
||||
|
||||
# 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
|
||||
|
||||
<MultiFrameworkContainer sandboxId="simple-paneview" react={SimplePaneview} />
|
||||
|
||||
```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';
|
||||
```
|
||||
|
||||
<DocRef declaration="IPaneviewReactProps" />
|
||||
|
||||
## 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...
|
||||
};
|
||||
```
|
||||
|
||||
<DocRef declaration="PaneviewApi" />
|
||||
|
||||
## Paneview Panel API
|
||||
|
||||
```tsx
|
||||
const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => {
|
||||
// props.api...
|
||||
|
||||
return <div>{`My first panel has the title: ${props.params.title}`}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
<DocRef declaration="PaneviewPanelApi" />
|
||||
|
||||
## 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 />
|
@ -1,196 +0,0 @@
|
||||
---
|
||||
description: Splitview Documentation
|
||||
---
|
||||
|
||||
import { SimpleSplitview } from '@site/src/components/simpleSplitview';
|
||||
import { SplitviewExample1 } from '@site/src/components/splitview/active';
|
||||
import Link from '@docusaurus/Link';
|
||||
import { DocRef } from '@site/src/components/ui/reference/docRef';
|
||||
|
||||
# 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.
|
||||
|
||||
<DocRef declaration="ISplitviewReactProps" />
|
||||
|
||||
## 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...
|
||||
};
|
||||
```
|
||||
|
||||
<DocRef declaration="SplitviewApi" />
|
||||
|
||||
## 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>;
|
||||
};
|
||||
```
|
||||
|
||||
<DocRef declaration="SplitviewPanelApi" />
|
||||
|
||||
## 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,
|
||||
});
|
||||
```
|
@ -1,85 +0,0 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
description: Theming Dockview Components
|
||||
---
|
||||
|
||||
# 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 should be imported at some point in your application
|
||||
|
||||
```css title="Example import with .css file"
|
||||
@import './node_modules/dockview/dist/styles/dockview.css';
|
||||
```
|
||||
|
||||
## Provided themes
|
||||
|
||||
`dockview` comes with a number of themes which are all CSS classes and can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss).
|
||||
To use a `dockview` theme the CSS must encapsulate the component. The current list of themes is:
|
||||
|
||||
- `dockview-theme-dark`
|
||||
- `dockview-theme-light`
|
||||
- `dockview-theme-vs`
|
||||
- `dockview-theme-abyss`
|
||||
- `dockview-theme-dracula`
|
||||
- `dockview-theme-replit`
|
||||
|
||||
## 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 | |
|
||||
| | |
|
||||
| --dv-icon-hover-background-color | |
|
||||
| --dv-floating-box-shadow | |
|
||||
| --dv-active-sash-color | |
|
||||
| --dv-background-color | |
|
||||
|
||||
You can further customise the theme through adjusting class properties but this is up you.
|
||||
For 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 title="Additional CSS to show a bottom border on active groups"
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@ -7,7 +7,7 @@ import { MultiFrameworkContainer } from '@site/src/components/ui/container';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app';
|
||||
import DockviewPersistence from '@site/sandboxes/layout-dockview/src/app';
|
||||
import SimpleDockview from '@site/sandboxes/simple-dockview/src/app';
|
||||
import ResizeDockview from '@site/sandboxes/resize-dockview/src/app';
|
||||
import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app';
|
||||
@ -31,6 +31,7 @@ import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app';
|
||||
import DockviewPopoutGroup from '@site/sandboxes/popoutgroup-dockview/src/app';
|
||||
import DockviewMaximizeGroup from '@site/sandboxes/maximizegroup-dockview/src/app';
|
||||
import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app';
|
||||
import DockviewScrollbars from '@site/sandboxes/scrollbars-dockview/src/app';
|
||||
|
||||
import { DocRef } from '@site/src/components/ui/reference/docRef';
|
||||
|
||||
@ -139,7 +140,7 @@ As well as importing the `dockview` stylesheet you must provide a class-based th
|
||||
|
||||
You can find more details on theming <Link to="../theme">here</Link>.
|
||||
|
||||
## Layout Persistance
|
||||
## Layout Persistence
|
||||
|
||||
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.
|
||||
@ -155,7 +156,7 @@ React.useEffect(() => {
|
||||
const layout = api.toJSON();
|
||||
|
||||
localStorage.setItem(
|
||||
'dockview_persistance_layout',
|
||||
'dockview_persistence_layout',
|
||||
JSON.stringify(layout)
|
||||
);
|
||||
});
|
||||
@ -168,7 +169,7 @@ React.useEffect(() => {
|
||||
|
||||
```tsx title="Loading a layout from localStorage"
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
const layoutString = localStorage.getItem('dockview_persistance_layout');
|
||||
const layoutString = localStorage.getItem('dockview_persistence_layout');
|
||||
|
||||
let success = false;
|
||||
|
||||
@ -193,7 +194,23 @@ If you refresh the page you should notice your layout is loaded as you left it.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="layout-dockview"
|
||||
react={DockviewPersistance}
|
||||
react={DockviewPersistence}
|
||||
/>
|
||||
|
||||
## Scrollbars
|
||||
|
||||
Scrollbars will appear if the contents of your view has a fixed height. If you are using a relative height such
|
||||
as *100%* you will need to define an inner container with the appropiate `overflow` value to allow scrollbars to appear.
|
||||
|
||||
The following container three views:
|
||||
- **Panel 1**: Sets `height: 100%` and no scrollbar appears even, the content is clipped.
|
||||
- **Panel 2**: Sets `height: 2000px` and a scrollbar does appear since a fixed height has been used.
|
||||
- **Panel 3**: Sets `height: 100%` and defines an inner component with `overflow: auto` to enable the scrollbars.
|
||||
|
||||
|
||||
<MultiFrameworkContainer
|
||||
sandboxId="scrollbars-dockview"
|
||||
react={DockviewScrollbars}
|
||||
/>
|
||||
|
||||
## Resizing
|
||||
@ -247,6 +264,18 @@ which will be rendered when there are no panels or groups.
|
||||
|
||||
## Drag And Drop
|
||||
|
||||
You can override the conditions of the far edge overlays through the `rootOverlayModel` prop.
|
||||
|
||||
```tsx
|
||||
<DockviewReact
|
||||
{...props}
|
||||
rootOverlayModel={{
|
||||
size: { value: 100, type: 'pixels' },
|
||||
activationSize: { value: 5, type: 'percentage' },
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Built-in behaviours
|
||||
|
||||
Dockview supports a wide variety of built-in Drag and Drop possibilities.
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"tutorialSidebar": [
|
||||
{
|
||||
"type": "autogenerated",
|
||||
"dirName": "."
|
||||
}
|
||||
]
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
[
|
||||
"1.9.0",
|
||||
"1.8.4"
|
||||
"1.9.2"
|
||||
]
|
@ -13882,6 +13882,13 @@ react-json-view-lite@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/react-json-view-lite/-/react-json-view-lite-1.2.1.tgz#c59a0bea4ede394db331d482ee02e293d38f8218"
|
||||
integrity sha512-Itc0g86fytOmKZoIoJyGgvNqohWSbh3NXIKNgH6W6FT9PC1ck4xas1tT3Rr/b3UlFXyA9Jjaw9QSXdZy2JwGMQ==
|
||||
|
||||
react-laag@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/react-laag/-/react-laag-2.0.5.tgz#549f1035b761b9ba09ac98fd128ccad63464c877"
|
||||
integrity sha512-RCvublJhdcgGRHU1wMYJ8kRtnYsKUgYusLvVhMuftg65POnnOB4+fwXvnETm6adc0cMnc1spujlrK6bGIz6aug==
|
||||
dependencies:
|
||||
tiny-warning "^1.0.3"
|
||||
|
||||
react-loadable-ssr-addon-v5-slorber@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883"
|
||||
@ -15678,7 +15685,7 @@ tiny-invariant@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
|
||||
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
|
||||
|
||||
tiny-warning@^1.0.0:
|
||||
tiny-warning@^1.0.0, tiny-warning@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||
|
Loading…
Reference in New Issue
Block a user