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

View File

@ -10,7 +10,7 @@ describe('groupDragHandler', () => {
const groupMock = jest.fn<DockviewGroupPanel, []>(() => { const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = { const partial: Partial<DockviewGroupPanel> = {
id: 'test_group_id', id: 'test_group_id',
isFloating: false, api: { isFloating: false } as any,
}; };
return partial as DockviewGroupPanel; return partial as DockviewGroupPanel;
}); });
@ -48,7 +48,7 @@ describe('groupDragHandler', () => {
const groupMock = jest.fn<DockviewGroupPanel, []>(() => { const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = { const partial: Partial<DockviewGroupPanel> = {
isFloating: true, api: { isFloating: true } as any,
}; };
return partial as DockviewGroupPanel; return partial as DockviewGroupPanel;
}); });
@ -76,7 +76,7 @@ describe('groupDragHandler', () => {
const groupMock = jest.fn<DockviewGroupPanel, []>(() => { const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = { const partial: Partial<DockviewGroupPanel> = {
isFloating: false, api: { isFloating: false } as any,
}; };
return partial as DockviewGroupPanel; 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 container = document.createElement('div');
const content = 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', () => { test('that the resize handles are added', () => {
const container = document.createElement('div'); const container = document.createElement('div');
const content = document.createElement('div'); const content = document.createElement('div');

View File

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

View File

@ -2795,8 +2795,8 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
@ -2807,8 +2807,8 @@ describe('dockviewComponent', () => {
'right' 'right'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
}); });
@ -2840,8 +2840,8 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
@ -2852,8 +2852,8 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1); expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
}); });
@ -2891,9 +2891,9 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -2904,9 +2904,9 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -2944,9 +2944,9 @@ describe('dockviewComponent', () => {
position: { referencePanel: panel2 }, position: { referencePanel: panel2 },
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -2957,9 +2957,9 @@ describe('dockviewComponent', () => {
'right' 'right'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeFalsy(); expect(panel3.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -2997,9 +2997,9 @@ describe('dockviewComponent', () => {
position: { referencePanel: panel2 }, position: { referencePanel: panel2 },
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -3010,9 +3010,9 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeFalsy(); expect(panel3.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1); expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -3056,10 +3056,10 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy(); expect(panel4.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4); expect(dockview.panels.length).toBe(4);
@ -3070,10 +3070,10 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy(); expect(panel4.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(4); expect(dockview.panels.length).toBe(4);
}); });
@ -3105,8 +3105,8 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
@ -3117,8 +3117,8 @@ describe('dockviewComponent', () => {
'right' 'right'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
}); });
@ -3150,8 +3150,8 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
@ -3162,8 +3162,8 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1); expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
}); });
@ -3201,9 +3201,9 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -3214,9 +3214,9 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -3254,9 +3254,9 @@ describe('dockviewComponent', () => {
position: { referencePanel: panel2 }, position: { referencePanel: panel2 },
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -3267,9 +3267,9 @@ describe('dockviewComponent', () => {
'right' 'right'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -3307,9 +3307,9 @@ describe('dockviewComponent', () => {
position: { referencePanel: panel2 }, position: { referencePanel: panel2 },
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -3320,9 +3320,9 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -3366,10 +3366,10 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy(); expect(panel4.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4); expect(dockview.panels.length).toBe(4);
@ -3380,10 +3380,10 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy(); expect(panel4.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4); expect(dockview.panels.length).toBe(4);
}); });
@ -3421,9 +3421,9 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -3434,9 +3434,9 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeTruthy(); expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -3473,9 +3473,9 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -3486,9 +3486,9 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeTruthy(); expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -3526,9 +3526,9 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -3539,9 +3539,9 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeTruthy(); expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -3578,9 +3578,9 @@ describe('dockviewComponent', () => {
floating: true, floating: true,
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
@ -3591,9 +3591,9 @@ describe('dockviewComponent', () => {
'center' 'center'
); );
expect(panel1.group.model.isFloating).toBeTruthy(); expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy(); expect(panel3.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(1); expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
}); });
@ -3625,15 +3625,15 @@ describe('dockviewComponent', () => {
position: { direction: 'right' }, position: { direction: 'right' },
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2); dockview.addFloatingGroup(panel2);
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
}); });
@ -3664,15 +3664,15 @@ describe('dockviewComponent', () => {
component: 'default', component: 'default',
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1); expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2); dockview.addFloatingGroup(panel2);
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
}); });
@ -3704,15 +3704,15 @@ describe('dockviewComponent', () => {
position: { direction: 'right' }, position: { direction: 'right' },
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2.group); dockview.addFloatingGroup(panel2.group);
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
}); });
@ -3743,15 +3743,15 @@ describe('dockviewComponent', () => {
component: 'default', component: 'default',
}); });
expect(panel1.group.model.isFloating).toBeFalsy(); expect(panel1.group.api.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy(); expect(panel2.group.api.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1); expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2); expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2.group); dockview.addFloatingGroup(panel2.group);
expect(panel1.group.model.isFloating).toBeTruthy(); expect(panel1.group.api.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeTruthy(); expect(panel2.group.api.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(1); expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2); 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); return this.component.addPanel(options);
} }
addGroup(options?: AddGroupOptions): IDockviewGroupPanel { addGroup(options?: AddGroupOptions): DockviewGroupPanel {
return this.component.addGroup(options); return this.component.addGroup(options);
} }
@ -459,6 +459,13 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
return this.component.getPanel(id); 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 { fromJSON(data: SerializedDockview): void {
this.component.fromJSON(data); 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 { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
import { MutableDisposable } from '../lifecycle'; import { MutableDisposable } from '../lifecycle';
import { IDockviewPanel } from '../dockview/dockviewPanel'; import { IDockviewPanel } from '../dockview/dockviewPanel';
import { DockviewComponent } from '../dockview/dockviewComponent';
import { Position } from '../dnd/droptarget';
export interface TitleEvent { export interface TitleEvent {
readonly title: string; readonly title: string;
@ -24,6 +26,11 @@ export interface DockviewPanelApi
readonly onDidGroupChange: Event<void>; readonly onDidGroupChange: Event<void>;
close(): void; close(): void;
setTitle(title: string): void; setTitle(title: string): void;
moveTo(options: {
group: DockviewGroupPanel;
position?: Position;
index?: number;
}): void;
} }
export class DockviewPanelApiImpl export class DockviewPanelApiImpl
@ -73,7 +80,11 @@ export class DockviewPanelApiImpl
return this._group; return this._group;
} }
constructor(private panel: IDockviewPanel, group: DockviewGroupPanel) { constructor(
private panel: IDockviewPanel,
group: DockviewGroupPanel,
private readonly accessor: DockviewComponent
) {
super(panel.id); super(panel.id);
this.initialize(panel); 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); this.panel.setTitle(title);
} }
public close(): void { close(): void {
this.group.model.closePanel(this.panel); this.group.model.closePanel(this.panel);
} }
} }

View File

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

View File

@ -7,7 +7,6 @@ import {
} from '../events'; } from '../events';
import { CompositeDisposable, MutableDisposable } from '../lifecycle'; import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { clamp } from '../math'; import { clamp } from '../math';
import { getPaneData, getPanelData } from './dataTransfer';
const bringElementToFront = (() => { const bringElementToFront = (() => {
let previous: HTMLElement | null = null; let previous: HTMLElement | null = null;
@ -66,6 +65,30 @@ export class Overlay extends CompositeDisposable {
this.renderWithinBoundaryConditions(); 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 } { toJSON(): { top: number; left: number; height: number; width: number } {
const container = this.options.container.getBoundingClientRect(); const container = this.options.container.getBoundingClientRect();
const element = this._element.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( private setupResize(
direction: direction:
| 'top' | '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 { override dispose(): void {
this._element.remove(); this._element.remove();
super.dispose(); super.dispose();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
export * from './dnd/dataTransfer'; export * from './dnd/dataTransfer';
export { watchElementResize } from './dom';
/** /**
* Events, Emitters and Disposables are very common concepts that most codebases will contain. * 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. * We export them with a 'Dockview' prefix here to prevent accidental use by others.
@ -71,6 +69,10 @@ export {
SplitviewPanelApi, SplitviewPanelApi,
} from './api/splitviewPanelApi'; } from './api/splitviewPanelApi';
export { ExpansionEvent, PaneviewPanelApi } from './api/paneviewPanelApi'; export { ExpansionEvent, PaneviewPanelApi } from './api/paneviewPanelApi';
export {
DockviewGroupPanelApi,
DockviewGroupPanelFloatingChangeEvent,
} from './api/dockviewGroupPanelApi';
export { export {
CommonApi, CommonApi,
SplitviewApi, 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 { .split-view-container {
position: relative; position: relative;
overflow: hidden; 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 { &.horizontal {
height: 100%; 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')} /> <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 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"> <Container height={600} sandboxId="floatinggroup-dockview">
<DockviewFloating /> <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 ### Panel Rendering
By default `DockviewReact` only adds to the DOM those panels that are visible, 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 { v4 } from 'uuid';
import './app.scss'; 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 = { const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => { default: (props: IDockviewPanelProps<{ title: string }>) => {
return <div style={{ padding: '20px' }}>{props.params.title}</div>; 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) => { const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({ event.api.addPanel({
id: 'panel_1', id: 'panel_1',
@ -264,11 +242,6 @@ const DockviewDemo = () => {
event.api.getPanel('panel_1')!.api.setActive(); event.api.getPanel('panel_1')!.api.setActive();
}; };
const theme = useLocalStorageItem(
'dv-theme-class-name',
'dockview-theme-abyss'
);
return ( return (
<DockviewReact <DockviewReact
components={components} components={components}
@ -276,7 +249,7 @@ const DockviewDemo = () => {
rightHeaderActionsComponent={RightControls} rightHeaderActionsComponent={RightControls}
leftHeaderActionsComponent={LeftControls} leftHeaderActionsComponent={LeftControls}
onReady={onReady} onReady={onReady}
className={theme} className={props.theme || 'dockview-theme-abyss'}
/> />
); );
}; };

View File

@ -1,11 +1,14 @@
import { import {
DockviewApi, DockviewApi,
DockviewGroupPanel,
DockviewReact, DockviewReact,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewHeaderActionsProps,
IDockviewPanelProps, IDockviewPanelProps,
SerializedDockview, SerializedDockview,
} from 'dockview'; } from 'dockview';
import * as React from 'react'; import * as React from 'react';
import { Icon } from './utils';
const components = { const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => { default: (props: IDockviewPanelProps<{ title: string }>) => {
@ -68,12 +71,11 @@ function loadDefaultLayout(api: DockviewApi) {
let panelCount = 0; let panelCount = 0;
function addFloatingPanel(api: DockviewApi) { function addPanel(api: DockviewApi) {
api.addPanel({ api.addPanel({
id: (++panelCount).toString(), id: (++panelCount).toString(),
title: `Tab ${panelCount}`, title: `Tab ${panelCount}`,
component: 'default', component: 'default',
floating: true,
}); });
} }
@ -203,6 +205,8 @@ export const DockviewPersistance = () => {
onReady={onReady} onReady={onReady}
components={components} components={components}
watermarkComponent={Watermark} watermarkComponent={Watermark}
leftHeaderActionsComponent={LeftComponent}
rightHeaderActionsComponent={RightComponent}
className="dockview-theme-abyss" className="dockview-theme-abyss"
/> />
</div> </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; export default DockviewPersistance;
const Watermark = () => { 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 onReady = (event: DockviewReadyEvent) => {
const panel = event.api.addPanel({ const panel = event.api.addPanel({
id: 'panel_1', id: 'panel_1',
@ -88,7 +88,7 @@ export const App: React.FC = () => {
<DockviewReact <DockviewReact
components={components} components={components}
onReady={onReady} onReady={onReady}
className="dockview-theme-abyss" className={props.theme || 'dockview-theme-abyss'}
/> />
); );
}; };

View File

@ -77,6 +77,28 @@ const themes = [
'dockview-theme-replit', '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 = () => { export const ThemePicker = () => {
const [theme, setTheme] = React.useState<string>( const [theme, setTheme] = React.useState<string>(
localStorage.getItem('dv-theme-class-name') || themes[0] localStorage.getItem('dv-theme-class-name') || themes[0]
@ -124,6 +146,11 @@ export const MultiFrameworkContainer = (props: {
const [animation, setAnimation] = React.useState<boolean>(false); const [animation, setAnimation] = React.useState<boolean>(false);
const theme = useLocalStorageItem(
'dv-theme-class-name',
'dockview-theme-abyss'
);
React.useEffect(() => { React.useEffect(() => {
setAnimation(true); setAnimation(true);
@ -182,7 +209,7 @@ export const MultiFrameworkContainer = (props: {
<Spinner /> <Spinner />
</div> </div>
)} )}
{framework === 'React' && <props.react />} {framework === 'React' && <props.react theme={theme} />}
</div> </div>
<div <div
style={{ style={{
@ -226,7 +253,6 @@ export const MultiFrameworkContainer = (props: {
</select> </select>
</div> </div>
<span style={{ flexGrow: 1 }} /> <span style={{ flexGrow: 1 }} />
<ThemePicker />
<CodeSandboxButton id={sandboxId} /> <CodeSandboxButton id={sandboxId} />
</div> </div>
</> </>