feat: add additional methods and docs for floating groups

This commit is contained in:
mathuo 2023-07-08 14:44:01 +01:00
parent 42b95e5f0a
commit 307780d15a
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
29 changed files with 772 additions and 428 deletions

View File

@ -5,6 +5,12 @@ import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
describe('groupPanelApi', () => {
test('title', () => {
const accessor: Partial<DockviewComponent> = {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
const panelMock = jest.fn<DockviewPanel, []>(() => {
return {
update: jest.fn(),
@ -18,7 +24,11 @@ describe('groupPanelApi', () => {
const panel = new panelMock();
const group = new groupMock();
const cut = new DockviewPanelApiImpl(panel, group);
const cut = new DockviewPanelApiImpl(
panel,
group,
<DockviewComponent>accessor
);
cut.setTitle('test_title');
expect(panel.setTitle).toBeCalledTimes(1);
@ -44,7 +54,8 @@ describe('groupPanelApi', () => {
const cut = new DockviewPanelApiImpl(
<IDockviewPanel>groupPanel,
<DockviewGroupPanel>groupViewPanel
<DockviewGroupPanel>groupViewPanel,
<DockviewComponent>accessor
);
cut.updateParameters({ keyA: 'valueA' });
@ -73,7 +84,8 @@ describe('groupPanelApi', () => {
const cut = new DockviewPanelApiImpl(
<IDockviewPanel>groupPanel,
<DockviewGroupPanel>groupViewPanel
<DockviewGroupPanel>groupViewPanel,
<DockviewComponent>accessor
);
let events = 0;

View File

@ -10,7 +10,7 @@ describe('groupDragHandler', () => {
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
id: 'test_group_id',
isFloating: false,
api: { isFloating: false } as any,
};
return partial as DockviewGroupPanel;
});
@ -48,7 +48,7 @@ describe('groupDragHandler', () => {
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
isFloating: true,
api: { isFloating: true } as any,
};
return partial as DockviewGroupPanel;
});
@ -76,7 +76,7 @@ describe('groupDragHandler', () => {
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
isFloating: false,
api: { isFloating: false } as any,
};
return partial as DockviewGroupPanel;
});

View File

@ -39,7 +39,7 @@ describe('overlay', () => {
});
});
test('#1', () => {
test('that out-of-bounds dimensions are fixed', () => {
const container = document.createElement('div');
const content = document.createElement('div');
@ -77,6 +77,46 @@ describe('overlay', () => {
});
});
test('setBounds', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 1000,
width: 1000,
left: 0,
top: 0,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
const element: HTMLElement = container.querySelector(
'.dv-resize-container'
)!;
expect(element).toBeTruthy();
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
return { left: 300, top: 400, width: 1000, height: 1000 } as any;
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 0, top: 0, width: 1000, height: 1000 } as any;
}
);
cut.setBounds({ height: 100, width: 200, left: 300, top: 400 });
expect(element.style.height).toBe('100px');
expect(element.style.width).toBe('200px');
expect(element.style.left).toBe('300px');
expect(element.style.top).toBe('400px');
});
test('that the resize handles are added', () => {
const container = document.createElement('div');
const content = document.createElement('div');

View File

@ -478,7 +478,7 @@ describe('tabsContainer', () => {
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
isFloating: false,
api: { isFloating: false } as any,
}) as DockviewGroupPanel;
});
@ -506,10 +506,14 @@ describe('tabsContainer', () => {
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event);
expect(accessor.addFloatingGroup).toBeCalledWith(groupPanel, {
x: 100,
y: 60,
});
expect(accessor.addFloatingGroup).toBeCalledWith(
groupPanel,
{
x: 100,
y: 60,
},
{ inDragMode: true }
);
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
expect(eventPreventDefaultSpy).toBeCalledTimes(1);
@ -534,7 +538,7 @@ describe('tabsContainer', () => {
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
isFloating: true,
api: { isFloating: true } as any,
}) as DockviewGroupPanel;
});
@ -587,7 +591,7 @@ describe('tabsContainer', () => {
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
isFloating: true,
api: { isFloating: true } as any,
}) as DockviewGroupPanel;
});

View File

@ -2795,8 +2795,8 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
@ -2807,8 +2807,8 @@ describe('dockviewComponent', () => {
'right'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
@ -2840,8 +2840,8 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
@ -2852,8 +2852,8 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
});
@ -2891,9 +2891,9 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
@ -2904,9 +2904,9 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
@ -2944,9 +2944,9 @@ describe('dockviewComponent', () => {
position: { referencePanel: panel2 },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
@ -2957,9 +2957,9 @@ describe('dockviewComponent', () => {
'right'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
@ -2997,9 +2997,9 @@ describe('dockviewComponent', () => {
position: { referencePanel: panel2 },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
@ -3010,9 +3010,9 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(3);
});
@ -3056,10 +3056,10 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(panel4.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4);
@ -3070,10 +3070,10 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(panel4.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(4);
});
@ -3105,8 +3105,8 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
@ -3117,8 +3117,8 @@ describe('dockviewComponent', () => {
'right'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
@ -3150,8 +3150,8 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
@ -3162,8 +3162,8 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
});
@ -3201,9 +3201,9 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
@ -3214,9 +3214,9 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
@ -3254,9 +3254,9 @@ describe('dockviewComponent', () => {
position: { referencePanel: panel2 },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
@ -3267,9 +3267,9 @@ describe('dockviewComponent', () => {
'right'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
});
@ -3307,9 +3307,9 @@ describe('dockviewComponent', () => {
position: { referencePanel: panel2 },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
@ -3320,9 +3320,9 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
@ -3366,10 +3366,10 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(panel4.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4);
@ -3380,10 +3380,10 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(panel4.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4);
});
@ -3421,9 +3421,9 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
@ -3434,9 +3434,9 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
@ -3473,9 +3473,9 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
@ -3486,9 +3486,9 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
@ -3526,9 +3526,9 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
@ -3539,9 +3539,9 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
@ -3578,9 +3578,9 @@ describe('dockviewComponent', () => {
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
@ -3591,9 +3591,9 @@ describe('dockviewComponent', () => {
'center'
);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(3);
});
@ -3625,15 +3625,15 @@ describe('dockviewComponent', () => {
position: { direction: 'right' },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
@ -3664,15 +3664,15 @@ describe('dockviewComponent', () => {
component: 'default',
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
@ -3704,15 +3704,15 @@ describe('dockviewComponent', () => {
position: { direction: 'right' },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2.group);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
@ -3743,15 +3743,15 @@ describe('dockviewComponent', () => {
component: 'default',
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2.group);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
});

View File

@ -0,0 +1,21 @@
import { quasiDefaultPrevented, quasiPreventDefault } from '../dom';
describe('dom', () => {
test('quasiPreventDefault', () => {
const event = new Event('myevent');
expect((event as any)['dv-quasiPreventDefault']).toBeUndefined();
quasiPreventDefault(event);
expect((event as any)['dv-quasiPreventDefault']).toBe(true);
});
test('quasiDefaultPrevented', () => {
const event = new Event('myevent');
expect(quasiDefaultPrevented(event)).toBeFalsy();
(event as any)['dv-quasiPreventDefault'] = false;
expect(quasiDefaultPrevented(event)).toBeFalsy();
(event as any)['dv-quasiPreventDefault'] = true;
expect(quasiDefaultPrevented(event)).toBeTruthy();
});
});

View File

@ -435,7 +435,7 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
return this.component.addPanel(options);
}
addGroup(options?: AddGroupOptions): IDockviewGroupPanel {
addGroup(options?: AddGroupOptions): DockviewGroupPanel {
return this.component.addGroup(options);
}
@ -459,6 +459,13 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
return this.component.getPanel(id);
}
addFloatingGroup(
item: IDockviewPanel | DockviewGroupPanel,
coord?: { x: number; y: number }
): void {
return this.component.addFloatingGroup(item, coord);
}
fromJSON(data: SerializedDockview): void {
this.component.fromJSON(data);
}

View File

@ -0,0 +1,54 @@
import { Position } from '../dnd/droptarget';
import { DockviewComponent } from '../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
import { Emitter, Event } from '../events';
import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi';
export interface DockviewGroupPanelApi extends GridviewPanelApi {
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent>;
readonly isFloating: boolean;
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void;
}
export interface DockviewGroupPanelFloatingChangeEvent {
readonly isFloating: boolean;
}
export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
private _group: DockviewGroupPanel | undefined;
readonly _onDidFloatingStateChange =
new Emitter<DockviewGroupPanelFloatingChangeEvent>();
readonly onDidFloatingStateChange: Event<DockviewGroupPanelFloatingChangeEvent> =
this._onDidFloatingStateChange.event;
get isFloating() {
if (!this._group) {
throw new Error(`DockviewGroupPanelApiImpl not initialized`);
}
return this._group.model.isFloating;
}
constructor(id: string, private readonly accessor: DockviewComponent) {
super(id);
this.addDisposables(this._onDidFloatingStateChange);
}
moveTo(options: { group: DockviewGroupPanel; position?: Position }): void {
if (!this._group) {
throw new Error(`DockviewGroupPanelApiImpl not initialized`);
}
this.accessor.moveGroupOrPanel(
options.group,
this._group.id,
undefined,
options.position ?? 'center'
);
}
initialize(group: DockviewGroupPanel): void {
this._group = group;
}
}

View File

@ -3,6 +3,8 @@ import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi';
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
import { MutableDisposable } from '../lifecycle';
import { IDockviewPanel } from '../dockview/dockviewPanel';
import { DockviewComponent } from '../dockview/dockviewComponent';
import { Position } from '../dnd/droptarget';
export interface TitleEvent {
readonly title: string;
@ -24,6 +26,11 @@ export interface DockviewPanelApi
readonly onDidGroupChange: Event<void>;
close(): void;
setTitle(title: string): void;
moveTo(options: {
group: DockviewGroupPanel;
position?: Position;
index?: number;
}): void;
}
export class DockviewPanelApiImpl
@ -73,7 +80,11 @@ export class DockviewPanelApiImpl
return this._group;
}
constructor(private panel: IDockviewPanel, group: DockviewGroupPanel) {
constructor(
private panel: IDockviewPanel,
group: DockviewGroupPanel,
private readonly accessor: DockviewComponent
) {
super(panel.id);
this.initialize(panel);
@ -88,11 +99,25 @@ export class DockviewPanelApiImpl
);
}
public setTitle(title: string): void {
moveTo(options: {
group: DockviewGroupPanel;
position?: Position;
index?: number;
}): void {
this.accessor.moveGroupOrPanel(
options.group,
this._group.id,
this.panel.id,
options.position ?? 'center',
options.index
);
}
setTitle(title: string): void {
this.panel.setTitle(title);
}
public close(): void {
close(): void {
this.group.model.closePanel(this.panel);
}
}

View File

@ -23,11 +23,11 @@ export class GroupDragHandler extends DragHandler {
'mousedown',
(e) => {
if (e.shiftKey) {
/**
* You cannot call e.preventDefault() because that will prevent drag events from firing
* but we also need to stop any group overlay drag events from occuring
* Use a custom event marker that can be checked by the overlay drag events
*/
/**
* You cannot call e.preventDefault() because that will prevent drag events from firing
* but we also need to stop any group overlay drag events from occuring
* Use a custom event marker that can be checked by the overlay drag events
*/
quasiPreventDefault(e);
}
},
@ -37,7 +37,7 @@ export class GroupDragHandler extends DragHandler {
}
override isCancelled(_event: DragEvent): boolean {
if (this.group.isFloating && !_event.shiftKey) {
if (this.group.api.isFloating && !_event.shiftKey) {
return true;
}
return false;

View File

@ -7,7 +7,6 @@ import {
} from '../events';
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { clamp } from '../math';
import { getPaneData, getPanelData } from './dataTransfer';
const bringElementToFront = (() => {
let previous: HTMLElement | null = null;
@ -66,6 +65,30 @@ export class Overlay extends CompositeDisposable {
this.renderWithinBoundaryConditions();
}
setBounds(
bounds: Partial<{
height: number;
width: number;
top: number;
left: number;
}>
): void {
if (typeof bounds.height === 'number') {
this._element.style.height = `${bounds.height}px`;
}
if (typeof bounds.width === 'number') {
this._element.style.width = `${bounds.width}px`;
}
if (typeof bounds.top === 'number') {
this._element.style.top = `${bounds.top}px`;
}
if (typeof bounds.left === 'number') {
this._element.style.left = `${bounds.left}px`;
}
this.renderWithinBoundaryConditions();
}
toJSON(): { top: number; left: number; height: number; width: number } {
const container = this.options.container.getBoundingClientRect();
const element = this._element.getBoundingClientRect();
@ -78,6 +101,173 @@ export class Overlay extends CompositeDisposable {
};
}
renderWithinBoundaryConditions(): void {
const containerRect = this.options.container.getBoundingClientRect();
const overlayRect = this._element.getBoundingClientRect();
// a minimum width of minimumViewportWidth must be inside the viewport
const xOffset = Math.max(
0,
overlayRect.width - this.options.minimumInViewportWidth
);
// a minimum height of minimumViewportHeight must be inside the viewport
const yOffset = Math.max(
0,
overlayRect.height - this.options.minimumInViewportHeight
);
const left = clamp(
overlayRect.left - containerRect.left,
-xOffset,
Math.max(0, containerRect.width - overlayRect.width + xOffset)
);
const top = clamp(
overlayRect.top - containerRect.top,
-yOffset,
Math.max(0, containerRect.height - overlayRect.height + yOffset)
);
this._element.style.left = `${left}px`;
this._element.style.top = `${top}px`;
}
setupDrag(
dragTarget: HTMLElement,
options: { inDragMode: boolean } = { inDragMode: false }
): void {
const move = new MutableDisposable();
const track = () => {
let offset: { x: number; y: number } | null = null;
move.value = new CompositeDisposable(
addDisposableWindowListener(window, 'mousemove', (e) => {
const containerRect =
this.options.container.getBoundingClientRect();
const x = e.clientX - containerRect.left;
const y = e.clientY - containerRect.top;
toggleClass(
this._element,
'dv-resize-container-dragging',
true
);
const overlayRect = this._element.getBoundingClientRect();
if (offset === null) {
offset = {
x: e.clientX - overlayRect.left,
y: e.clientY - overlayRect.top,
};
}
const xOffset = Math.max(
0,
overlayRect.width - this.options.minimumInViewportWidth
);
const yOffset = Math.max(
0,
overlayRect.height -
this.options.minimumInViewportHeight
);
const left = clamp(
x - offset.x,
-xOffset,
Math.max(
0,
containerRect.width - overlayRect.width + xOffset
)
);
const top = clamp(
y - offset.y,
-yOffset,
Math.max(
0,
containerRect.height - overlayRect.height + yOffset
)
);
this._element.style.left = `${left}px`;
this._element.style.top = `${top}px`;
}),
addDisposableWindowListener(window, 'mouseup', () => {
toggleClass(
this._element,
'dv-resize-container-dragging',
false
);
move.dispose();
this._onDidChange.fire();
})
);
};
this.addDisposables(
move,
addDisposableListener(dragTarget, 'mousedown', (event) => {
if (event.defaultPrevented) {
event.preventDefault();
return;
}
// if somebody has marked this event then treat as a defaultPrevented
// without actually calling event.preventDefault()
if (quasiDefaultPrevented(event)) {
return;
}
track();
}),
addDisposableListener(
this.options.content,
'mousedown',
(event) => {
if (event.defaultPrevented) {
return;
}
// if somebody has marked this event then treat as a defaultPrevented
// without actually calling event.preventDefault()
if (quasiDefaultPrevented(event)) {
return;
}
if (event.shiftKey) {
track();
}
}
),
addDisposableListener(
this.options.content,
'mousedown',
() => {
bringElementToFront(this._element);
},
true
)
);
bringElementToFront(this._element);
if (options.inDragMode) {
track();
}
}
private setupOverlay(): void {
this._element.style.height = `${this.options.height}px`;
this._element.style.width = `${this.options.width}px`;
this._element.style.left = `${this.options.left}px`;
this._element.style.top = `${this.options.top}px`;
this._element.className = 'dv-resize-container';
}
private setupResize(
direction:
| 'top'
@ -252,173 +442,6 @@ export class Overlay extends CompositeDisposable {
);
}
private setupOverlay(): void {
this._element.style.height = `${this.options.height}px`;
this._element.style.width = `${this.options.width}px`;
this._element.style.left = `${this.options.left}px`;
this._element.style.top = `${this.options.top}px`;
this._element.className = 'dv-resize-container';
}
setupDrag(
dragTarget: HTMLElement,
options: { inDragMode: boolean } = { inDragMode: false }
): void {
const move = new MutableDisposable();
const track = () => {
let offset: { x: number; y: number } | null = null;
move.value = new CompositeDisposable(
addDisposableWindowListener(window, 'mousemove', (e) => {
const containerRect =
this.options.container.getBoundingClientRect();
const x = e.clientX - containerRect.left;
const y = e.clientY - containerRect.top;
toggleClass(
this._element,
'dv-resize-container-dragging',
true
);
const overlayRect = this._element.getBoundingClientRect();
if (offset === null) {
offset = {
x: e.clientX - overlayRect.left,
y: e.clientY - overlayRect.top,
};
}
const xOffset = Math.max(
0,
overlayRect.width - this.options.minimumInViewportWidth
);
const yOffset = Math.max(
0,
overlayRect.height -
this.options.minimumInViewportHeight
);
const left = clamp(
x - offset.x,
-xOffset,
Math.max(
0,
containerRect.width - overlayRect.width + xOffset
)
);
const top = clamp(
y - offset.y,
-yOffset,
Math.max(
0,
containerRect.height - overlayRect.height + yOffset
)
);
this._element.style.left = `${left}px`;
this._element.style.top = `${top}px`;
}),
addDisposableWindowListener(window, 'mouseup', () => {
toggleClass(
this._element,
'dv-resize-container-dragging',
false
);
move.dispose();
this._onDidChange.fire();
})
);
};
this.addDisposables(
move,
addDisposableListener(dragTarget, 'mousedown', (event) => {
if (event.defaultPrevented) {
event.preventDefault();
return;
}
// if somebody has marked this event then treat as a defaultPrevented
// without actually calling event.preventDefault()
if (quasiDefaultPrevented(event)) {
return;
}
track();
}),
addDisposableListener(
this.options.content,
'mousedown',
(event) => {
if (event.defaultPrevented) {
return;
}
// if somebody has marked this event then treat as a defaultPrevented
// without actually calling event.preventDefault()
if (quasiDefaultPrevented(event)) {
return;
}
if (event.shiftKey) {
track();
}
}
),
addDisposableListener(
this.options.content,
'mousedown',
() => {
bringElementToFront(this._element);
},
true
)
);
bringElementToFront(this._element);
if (options.inDragMode) {
track();
}
}
renderWithinBoundaryConditions(): void {
const containerRect = this.options.container.getBoundingClientRect();
const overlayRect = this._element.getBoundingClientRect();
// a minimum width of minimumViewportWidth must be inside the viewport
const xOffset = Math.max(
0,
overlayRect.width - this.options.minimumInViewportWidth
);
// a minimum height of minimumViewportHeight must be inside the viewport
const yOffset = Math.max(
0,
overlayRect.height - this.options.minimumInViewportHeight
);
const left = clamp(
this.options.left,
-xOffset,
Math.max(0, containerRect.width - overlayRect.width + xOffset)
);
const top = clamp(
this.options.top,
-yOffset,
Math.max(0, containerRect.height - overlayRect.height + yOffset)
);
this._element.style.left = `${left}px`;
this._element.style.top = `${top}px`;
}
override dispose(): void {
this._element.remove();
super.dispose();

View File

@ -197,7 +197,7 @@ export class TabsContainer
if (
isFloatingGroupsEnabled &&
event.shiftKey &&
!this.group.isFloating
!this.group.api.isFloating
) {
event.preventDefault();
@ -206,10 +206,14 @@ export class TabsContainer
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(this.group, {
x: left - rootLeft + 20,
y: top - rootTop + 20,
});
this.accessor.addFloatingGroup(
this.group,
{
x: left - rootLeft + 20,
y: top - rootTop + 20,
},
{ inDragMode: true }
);
}
}
),
@ -302,10 +306,14 @@ export class TabsContainer
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(panel as DockviewPanel, {
x: left - rootLeft,
y: top - rootTop,
});
this.accessor.addFloatingGroup(
panel as DockviewPanel,
{
x: left - rootLeft,
y: top - rootTop,
},
{ inDragMode: true }
);
return;
}

View File

@ -1,7 +1,7 @@
import { GroupviewPanelState } from './types';
import { DockviewGroupPanel } from './dockviewGroupPanel';
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
import { IDockviewComponent } from './dockviewComponent';
import { DockviewComponent } from './dockviewComponent';
import { DockviewPanelModel } from './dockviewPanelModel';
import { DockviewApi } from '../api/component.api';
@ -21,7 +21,7 @@ interface LegacyState extends GroupviewPanelState {
}
export class DefaultDockviewDeserialzier implements IPanelDeserializer {
constructor(private readonly layout: IDockviewComponent) {}
constructor(private readonly layout: DockviewComponent) {}
public fromJSON(
panelData: GroupviewPanelState,

View File

@ -8,6 +8,7 @@
left: 0px;
height: 100%;
width: 100%;
z-index: 1;
}
}

View File

@ -7,7 +7,7 @@ import {
import { directionToPosition, Droptarget, Position } from '../dnd/droptarget';
import { tail, sequenceEquals, remove } from '../array';
import { DockviewPanel, IDockviewPanel } from './dockviewPanel';
import { CompositeDisposable, IDisposable } from '../lifecycle';
import { CompositeDisposable } from '../lifecycle';
import { Event, Emitter } from '../events';
import { Watermark } from './components/watermark/watermark';
import {
@ -41,11 +41,15 @@ import {
GroupPanelViewState,
GroupviewDropEvent,
} from './dockviewGroupPanelModel';
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
import { DockviewGroupPanel } from './dockviewGroupPanel';
import { DockviewPanelModel } from './dockviewPanelModel';
import { getPanelData } from '../dnd/dataTransfer';
import { Overlay } from '../dnd/overlay';
import { toggleClass } from '../dom';
import {
DockviewFloatingGroupPanel,
IDockviewFloatingGroupPanel,
} from './dockviewFloatingGroupPanel';
export interface PanelReference {
update: (event: { params: { [key: string]: any } }) => void;
@ -92,6 +96,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly activePanel: IDockviewPanel | undefined;
readonly totalPanels: number;
readonly panels: IDockviewPanel[];
readonly floatingGroups: IDockviewFloatingGroupPanel[];
readonly onDidDrop: Event<DockviewDropEvent>;
readonly orientation: Orientation;
updateOptions(options: DockviewComponentUpdateOptions): void;
@ -110,7 +115,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
getGroupPanel: (id: string) => IDockviewPanel | undefined;
createWatermarkComponent(): IWatermarkRenderer;
// lifecycle
addGroup(options?: AddGroupOptions): IDockviewGroupPanel;
addGroup(options?: AddGroupOptions): DockviewGroupPanel;
closeAllGroups(): void;
// events
moveToNext(options?: MovementOptions): void;
@ -159,11 +164,7 @@ export class DockviewComponent
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
this._onDidActivePanelChange.event;
private readonly floatingGroups: {
instance: DockviewGroupPanel;
disposable: IDisposable;
overlay: Overlay;
}[] = [];
readonly floatingGroups: DockviewFloatingGroupPanel[] = [];
get orientation(): Orientation {
return this.gridview.orientation;
@ -306,7 +307,7 @@ export class DockviewComponent
addFloatingGroup(
item: DockviewPanel | DockviewGroupPanel,
coord?: { x?: number; y?: number; height?: number; width?: number },
options?: { skipRemoveGroup: boolean; inDragMode: boolean }
options?: { skipRemoveGroup?: boolean; inDragMode: boolean }
): void {
let group: DockviewGroupPanel;
@ -356,29 +357,30 @@ export class DockviewComponent
inDragMode:
typeof options?.inDragMode === 'boolean'
? options.inDragMode
: true,
: false,
});
}
const instance = {
instance: group,
const floatingGroupPanel = new DockviewFloatingGroupPanel(
group,
overlay
);
overlay,
disposable: new CompositeDisposable(
overlay,
overlay.onDidChange(() => {
this._bufferOnDidLayoutChange.fire();
}),
{
dispose: () => {
group.model.isFloating = false;
remove(this.floatingGroups, instance);
},
}
),
};
floatingGroupPanel.addDisposables(
overlay.onDidChange(() => {
this._bufferOnDidLayoutChange.fire();
}),
{
dispose: () => {
group.model.isFloating = false;
remove(this.floatingGroups, floatingGroupPanel);
this.updateWatermark();
},
}
);
this.floatingGroups.push(instance);
this.floatingGroups.push(floatingGroupPanel);
this.updateWatermark();
}
private orthogonalize(position: Position): DockviewGroupPanel {
@ -519,7 +521,7 @@ export class DockviewComponent
const floats: SerializedFloatingGroup[] = this.floatingGroups.map(
(floatingGroup) => {
return {
data: floatingGroup.instance.toJSON() as GroupPanelViewState,
data: floatingGroup.group.toJSON() as GroupPanelViewState,
position: floatingGroup.overlay.toJSON(),
};
}
@ -726,7 +728,7 @@ export class DockviewComponent
inDragMode: false,
skipRemoveGroup: true,
});
} else if (referenceGroup.model.isFloating || target === 'center') {
} else if (referenceGroup.api.isFloating || target === 'center') {
panel = this.createPanel(options, referenceGroup);
referenceGroup.model.openPanel(panel);
} else {
@ -807,7 +809,7 @@ export class DockviewComponent
}
private updateWatermark(): void {
if (this.groups.filter((x) => !x.model.isFloating).length === 0) {
if (this.groups.filter((x) => !x.api.isFloating).length === 0) {
if (!this.watermark) {
this.watermark = this.createWatermarkComponent();
@ -920,17 +922,17 @@ export class DockviewComponent
| undefined
): DockviewGroupPanel {
const floatingGroup = this.floatingGroups.find(
(_) => _.instance === group
(_) => _.group === group
);
if (floatingGroup) {
if (!options?.skipDispose) {
floatingGroup.instance.dispose();
floatingGroup.group.dispose();
this._groups.delete(group.id);
}
floatingGroup.disposable.dispose();
floatingGroup.dispose();
return floatingGroup.instance;
return floatingGroup.group;
}
return super.doRemoveGroup(group, options);
@ -986,7 +988,7 @@ export class DockviewComponent
const [targetParentLocation, to] = tail(targetLocation);
const isFloating = this.floatingGroups.find(
(x) => x.instance === sourceGroup
(x) => x.group === sourceGroup
);
if (!isFloating) {
@ -1066,11 +1068,11 @@ export class DockviewComponent
}
} else {
const floatingGroup = this.floatingGroups.find(
(x) => x.instance === sourceGroup
(x) => x.group === sourceGroup
);
if (floatingGroup) {
floatingGroup.disposable.dispose();
floatingGroup.dispose();
} else {
this.gridview.removeView(
getGridLocation(sourceGroup.element)

View File

@ -0,0 +1,37 @@
import { Overlay } from '../dnd/overlay';
import { CompositeDisposable } from '../lifecycle';
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
export interface IDockviewFloatingGroupPanel {
readonly group: IDockviewGroupPanel;
position(
bounds: Partial<{
top: number;
left: number;
height: number;
width: number;
}>
): void;
}
export class DockviewFloatingGroupPanel
extends CompositeDisposable
implements IDockviewFloatingGroupPanel
{
constructor(readonly group: DockviewGroupPanel, readonly overlay: Overlay) {
super();
this.addDisposables(overlay);
}
position(
bounds: Partial<{
top: number;
left: number;
height: number;
width: number;
}>
): void {
this.overlay.setBounds(bounds);
}
}

View File

@ -1,6 +1,5 @@
import { IFrameworkPart } from '../panel/types';
import { DockviewComponent } from '../dockview/dockviewComponent';
import { GridviewPanelApi } from '../api/gridviewPanelApi';
import {
DockviewGroupPanelModel,
GroupOptions,
@ -9,8 +8,13 @@ import {
} from './dockviewGroupPanelModel';
import { GridviewPanel, IGridviewPanel } from '../gridview/gridviewPanel';
import { IDockviewPanel } from '../dockview/dockviewPanel';
import {
DockviewGroupPanelApi,
DockviewGroupPanelApiImpl,
} from '../api/dockviewGroupPanelApi';
export interface IDockviewGroupPanel extends IGridviewPanel {
export interface IDockviewGroupPanel
extends IGridviewPanel<DockviewGroupPanelApi> {
model: IDockviewGroupPanelModel;
locked: boolean;
readonly size: number;
@ -20,10 +24,8 @@ export interface IDockviewGroupPanel extends IGridviewPanel {
export type IDockviewGroupPanelPublic = IDockviewGroupPanel;
export type DockviewGroupPanelApi = GridviewPanelApi;
export class DockviewGroupPanel
extends GridviewPanel
extends GridviewPanel<DockviewGroupPanelApiImpl>
implements IDockviewGroupPanel
{
private readonly _model: DockviewGroupPanelModel;
@ -52,10 +54,6 @@ export class DockviewGroupPanel
this._model.locked = value;
}
get isFloating(): boolean {
return this._model.isFloating;
}
get header(): IHeader {
return this._model.header;
}
@ -65,10 +63,17 @@ export class DockviewGroupPanel
id: string,
options: GroupOptions
) {
super(id, 'groupview_default', {
minimumHeight: 100,
minimumWidth: 100,
});
super(
id,
'groupview_default',
{
minimumHeight: 100,
minimumWidth: 100,
},
new DockviewGroupPanelApiImpl(id, accessor)
);
this.api.initialize(this); // cannot use 'this' after after 'super' call
this._model = new DockviewGroupPanelModel(
this.element,

View File

@ -237,6 +237,10 @@ export class DockviewGroupPanelModel
);
toggleClass(this.container, 'dv-groupview-floating', value);
this.groupPanel.api._onDidFloatingStateChange.fire({
isFloating: this.isFloating,
});
}
constructor(

View File

@ -8,7 +8,7 @@ import { DockviewGroupPanel } from './dockviewGroupPanel';
import { CompositeDisposable, IDisposable } from '../lifecycle';
import { IPanel, PanelUpdateEvent, Parameters } from '../panel/types';
import { IDockviewPanelModel } from './dockviewPanelModel';
import { IDockviewComponent } from './dockviewComponent';
import { DockviewComponent } from './dockviewComponent';
export interface IDockviewPanel extends IDisposable, IPanel {
readonly view: IDockviewPanelModel;
@ -47,7 +47,7 @@ export class DockviewPanel
constructor(
public readonly id: string,
accessor: IDockviewComponent,
accessor: DockviewComponent,
private readonly containerApi: DockviewApi,
group: DockviewGroupPanel,
readonly view: IDockviewPanelModel
@ -55,7 +55,7 @@ export class DockviewPanel
super();
this._group = group;
this.api = new DockviewPanelApiImpl(this, this._group);
this.api = new DockviewPanelApiImpl(this, this._group, accessor);
this.addDisposables(
this.api.onActiveChange(() => {

View File

@ -8,16 +8,14 @@ import {
IWatermarkRenderer,
DockviewDropTargets,
} from './types';
import {
DockviewGroupPanel,
DockviewGroupPanelApi,
} from './dockviewGroupPanel';
import { DockviewGroupPanel } from './dockviewGroupPanel';
import { ISplitviewStyles, Orientation } from '../splitview/splitview';
import { PanelTransfer } from '../dnd/dataTransfer';
import { IDisposable } from '../lifecycle';
import { Position } from '../dnd/droptarget';
import { IDockviewPanel } from './dockviewPanel';
import { FrameworkFactory } from '../panel/componentFactory';
import { DockviewGroupPanelApi } from '../api/dockviewGroupPanelApi';
export interface IHeaderActionsRenderer extends IDisposable {
readonly element: HTMLElement;

View File

@ -28,8 +28,8 @@ export interface GridviewInitParameters extends PanelInitParameters {
isVisible?: boolean;
}
export interface IGridviewPanel
extends BasePanelViewExported<GridviewPanelApi> {
export interface IGridviewPanel<T extends GridviewPanelApi = GridviewPanelApi>
extends BasePanelViewExported<T> {
readonly minimumWidth: number;
readonly maximumWidth: number;
readonly minimumHeight: number;
@ -38,8 +38,10 @@ export interface IGridviewPanel
readonly snap: boolean;
}
export abstract class GridviewPanel
extends BasePanelView<GridviewPanelApiImpl>
export abstract class GridviewPanel<
T extends GridviewPanelApiImpl = GridviewPanelApiImpl
>
extends BasePanelView<T>
implements IGridPanelComponentView, IGridviewPanel
{
private _evaluatedMinimumWidth = 0;
@ -134,9 +136,10 @@ export abstract class GridviewPanel
maximumWidth?: number;
minimumHeight?: number;
maximumHeight?: number;
}
},
api?: T
) {
super(id, component, new GridviewPanelApiImpl(id));
super(id, component, api ?? <T>new GridviewPanelApiImpl(id));
if (typeof options?.minimumWidth === 'number') {
this._minimumWidth = options.minimumWidth;

View File

@ -1,7 +1,5 @@
export * from './dnd/dataTransfer';
export { watchElementResize } from './dom';
/**
* Events, Emitters and Disposables are very common concepts that most codebases will contain.
* We export them with a 'Dockview' prefix here to prevent accidental use by others.
@ -71,6 +69,10 @@ export {
SplitviewPanelApi,
} from './api/splitviewPanelApi';
export { ExpansionEvent, PaneviewPanelApi } from './api/paneviewPanelApi';
export {
DockviewGroupPanelApi,
DockviewGroupPanelFloatingChangeEvent,
} from './api/dockviewGroupPanelApi';
export {
CommonApi,
SplitviewApi,

View File

@ -1,3 +1,24 @@
.dv-debug {
.split-view-container {
.sash-container {
.sash {
&.enabled {
background-color: black;
}
&.disabled {
background-color: orange;
}
&.maximum {
background-color: green;
}
&.minimum {
background-color: red;
}
}
}
}
}
.split-view-container {
position: relative;
overflow: hidden;
@ -12,22 +33,6 @@
}
}
// debug
// .sash {
// &.enabled {
// background-color: black;
// }
// &.disabled {
// background-color: orange;
// }
// &.maximum {
// background-color: green;
// }
// &.minimum {
// background-color: red;
// }
// }
&.horizontal {
height: 100%;

View File

@ -383,7 +383,7 @@ Floating groups can be interacted with whilst holding the `shift` key activating
<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.isFloating` property. See examples for full code.
a group is floating via the `group.api.isFloating` property. See examples for full code.
<Container height={600} sandboxId="floatinggroup-dockview">
<DockviewFloating />
@ -499,6 +499,21 @@ panel.api.updateParameters({
});
```
### 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 });
```
### Panel Rendering
By default `DockviewReact` only adds to the DOM those panels that are visible,

View File

@ -11,28 +11,6 @@ import * as ReactDOM from 'react-dom';
import { v4 } from 'uuid';
import './app.scss';
function useLocalStorageItem(key: string, defaultValue: string): string {
const [item, setItem] = React.useState<string | null>(
localStorage.getItem(key)
);
React.useEffect(() => {
const listener = (event: StorageEvent) => {
setItem(localStorage.getItem(key));
};
window.addEventListener('storage', listener);
setItem(localStorage.getItem(key));
return () => {
window.removeEventListener('storage', listener);
};
}, [key]);
return item === null ? defaultValue : item;
}
const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
@ -213,7 +191,7 @@ const LeftControls = (props: IDockviewHeaderActionsProps) => {
);
};
const DockviewDemo = () => {
const DockviewDemo = (props: { theme?: string }) => {
const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({
id: 'panel_1',
@ -264,11 +242,6 @@ const DockviewDemo = () => {
event.api.getPanel('panel_1')!.api.setActive();
};
const theme = useLocalStorageItem(
'dv-theme-class-name',
'dockview-theme-abyss'
);
return (
<DockviewReact
components={components}
@ -276,7 +249,7 @@ const DockviewDemo = () => {
rightHeaderActionsComponent={RightControls}
leftHeaderActionsComponent={LeftControls}
onReady={onReady}
className={theme}
className={props.theme || 'dockview-theme-abyss'}
/>
);
};

View File

@ -1,11 +1,14 @@
import {
DockviewApi,
DockviewGroupPanel,
DockviewReact,
DockviewReadyEvent,
IDockviewHeaderActionsProps,
IDockviewPanelProps,
SerializedDockview,
} from 'dockview';
import * as React from 'react';
import { Icon } from './utils';
const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => {
@ -68,12 +71,11 @@ function loadDefaultLayout(api: DockviewApi) {
let panelCount = 0;
function addFloatingPanel(api: DockviewApi) {
function addPanel(api: DockviewApi) {
api.addPanel({
id: (++panelCount).toString(),
title: `Tab ${panelCount}`,
component: 'default',
floating: true,
});
}
@ -203,6 +205,8 @@ export const DockviewPersistance = () => {
onReady={onReady}
components={components}
watermarkComponent={Watermark}
leftHeaderActionsComponent={LeftComponent}
rightHeaderActionsComponent={RightComponent}
className="dockview-theme-abyss"
/>
</div>
@ -210,6 +214,51 @@ export const DockviewPersistance = () => {
);
};
const LeftComponent = (props: IDockviewHeaderActionsProps) => {
const onClick = () => {
addPanel(props.containerApi);
};
return (
<div style={{ height: '100%', color: 'white', padding: '0px 4px' }}>
<Icon onClick={onClick} icon={'add'} />
</div>
);
};
const RightComponent = (props: IDockviewHeaderActionsProps) => {
const [floating, setFloating] = React.useState<boolean>(
props.api.isFloating
);
React.useEffect(() => {
const disposable = props.group.api.onDidFloatingStateChange((event) => [
setFloating(event.isFloating),
]);
return () => {
disposable.dispose();
};
}, [props.group.api]);
const onClick = () => {
if (floating) {
const group = props.containerApi.addGroup();
props.group.api.moveTo({ group });
} else {
props.containerApi.addFloatingGroup(props.group);
}
};
return (
<div style={{ height: '100%', color: 'white', padding: '0px 4px' }}>
<Icon
onClick={onClick}
icon={floating ? 'jump_to_element' : 'back_to_tab'}
/>
</div>
);
};
export default DockviewPersistance;
const Watermark = () => {

View File

@ -0,0 +1,30 @@
import * as React from 'react';
export const Icon = (props: {
icon: string;
title?: string;
onClick?: (event: React.MouseEvent) => void;
}) => {
return (
<div
title={props.title}
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '30px',
height: '100%',
fontSize: '18px',
}}
onClick={props.onClick}
>
<span
style={{ fontSize: 'inherit', cursor: 'pointer' }}
className="material-symbols-outlined"
>
{props.icon}
</span>
</div>
);
};

View File

@ -15,7 +15,7 @@ const components = {
},
};
export const App: React.FC = () => {
export const App: React.FC = (props: { theme?: string }) => {
const onReady = (event: DockviewReadyEvent) => {
const panel = event.api.addPanel({
id: 'panel_1',
@ -88,7 +88,7 @@ export const App: React.FC = () => {
<DockviewReact
components={components}
onReady={onReady}
className="dockview-theme-abyss"
className={props.theme || 'dockview-theme-abyss'}
/>
);
};

View File

@ -77,6 +77,28 @@ const themes = [
'dockview-theme-replit',
];
function useLocalStorageItem(key: string, defaultValue: string): string {
const [item, setItem] = React.useState<string | null>(
localStorage.getItem(key)
);
React.useEffect(() => {
const listener = (event: StorageEvent) => {
setItem(localStorage.getItem(key));
};
window.addEventListener('storage', listener);
setItem(localStorage.getItem(key));
return () => {
window.removeEventListener('storage', listener);
};
}, [key]);
return item === null ? defaultValue : item;
}
export const ThemePicker = () => {
const [theme, setTheme] = React.useState<string>(
localStorage.getItem('dv-theme-class-name') || themes[0]
@ -124,6 +146,11 @@ export const MultiFrameworkContainer = (props: {
const [animation, setAnimation] = React.useState<boolean>(false);
const theme = useLocalStorageItem(
'dv-theme-class-name',
'dockview-theme-abyss'
);
React.useEffect(() => {
setAnimation(true);
@ -182,7 +209,7 @@ export const MultiFrameworkContainer = (props: {
<Spinner />
</div>
)}
{framework === 'React' && <props.react />}
{framework === 'React' && <props.react theme={theme} />}
</div>
<div
style={{
@ -226,7 +253,6 @@ export const MultiFrameworkContainer = (props: {
</select>
</div>
<span style={{ flexGrow: 1 }} />
<ThemePicker />
<CodeSandboxButton id={sandboxId} />
</div>
</>