feat: events cleanup

This commit is contained in:
mathuo 2024-02-03 22:56:38 +00:00
parent 89fe866ac5
commit 807ccf80de
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
24 changed files with 1506 additions and 567 deletions

View File

@ -257,9 +257,17 @@ describe('dockviewComponent', () => {
const panel4 = dockview.getGroupPanel('panel4'); const panel4 = dockview.getGroupPanel('panel4');
const group1 = panel1!.group; const group1 = panel1!.group;
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel1' },
to: { group: group1, position: 'right' },
});
const group2 = panel1!.group; const group2 = panel1!.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel3' },
to: { group: group2, position: 'center' },
});
expect(dockview.activeGroup).toBe(group2); expect(dockview.activeGroup).toBe(group2);
expect(dockview.activeGroup!.model.activePanel).toBe(panel3); expect(dockview.activeGroup!.model.activePanel).toBe(panel3);
@ -309,9 +317,16 @@ describe('dockviewComponent', () => {
const panel1 = dockview.getGroupPanel('panel1')!; const panel1 = dockview.getGroupPanel('panel1')!;
const panel2 = dockview.getGroupPanel('panel2')!; const panel2 = dockview.getGroupPanel('panel2')!;
const group1 = panel1.group; const group1 = panel1.group;
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel1' },
to: { group: group1, position: 'right' },
});
const group2 = panel1.group; const group2 = panel1.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center'); dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel3' },
to: { group: group2, position: 'center' },
});
expect(dockview.size).toBe(2); expect(dockview.size).toBe(2);
expect(dockview.totalPanels).toBe(4); expect(dockview.totalPanels).toBe(4);
@ -374,9 +389,16 @@ describe('dockviewComponent', () => {
expect(panel4.api.isActive).toBeFalsy(); expect(panel4.api.isActive).toBeFalsy();
const group1 = panel1.group; const group1 = panel1.group;
dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel1' },
to: { group: group1, position: 'right' },
});
const group2 = panel1.group; const group2 = panel1.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center'); dockview.moveGroupOrPanel({
from: { groupId: group1.id, panelId: 'panel3' },
to: { group: group2, position: 'center' },
});
expect(dockview.size).toBe(2); expect(dockview.size).toBe(2);
expect(panel1.group).toBe(panel3.group); expect(panel1.group).toBe(panel3.group);
@ -443,7 +465,10 @@ describe('dockviewComponent', () => {
expect(group.model.indexOf(panel1)).toBe(0); expect(group.model.indexOf(panel1)).toBe(0);
expect(group.model.indexOf(panel2)).toBe(1); expect(group.model.indexOf(panel2)).toBe(1);
dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right'); dockview.moveGroupOrPanel({
from: { groupId: group.id, panelId: 'panel1' },
to: { group, position: 'right' },
});
expect(dockview.size).toBe(2); expect(dockview.size).toBe(2);
expect(dockview.totalPanels).toBe(2); expect(dockview.totalPanels).toBe(2);
@ -493,7 +518,10 @@ describe('dockviewComponent', () => {
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
const group = dockview.getGroupPanel('panel1')!.group; const group = dockview.getGroupPanel('panel1')!.group;
dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right'); dockview.moveGroupOrPanel({
from: { groupId: group.id, panelId: 'panel1' },
to: { group, position: 'right' },
});
viewQuery = container.querySelectorAll( viewQuery = container.querySelectorAll(
'.branch-node > .split-view-container > .view-container > .view' '.branch-node > .split-view-container > .view-container > .view'
@ -908,8 +936,8 @@ describe('dockviewComponent', () => {
expect(events).toEqual([ expect(events).toEqual([
{ type: 'ADD_GROUP', group: panel1.group }, { type: 'ADD_GROUP', group: panel1.group },
{ type: 'ACTIVE_GROUP', group: panel1.group },
{ type: 'ADD_PANEL', panel: panel1 }, { type: 'ADD_PANEL', panel: panel1 },
{ type: 'ACTIVE_GROUP', group: panel1.group },
{ type: 'ACTIVE_PANEL', panel: panel1 }, { type: 'ACTIVE_PANEL', panel: panel1 },
]); ]);
@ -956,8 +984,8 @@ describe('dockviewComponent', () => {
expect(events).toEqual([ expect(events).toEqual([
{ type: 'ADD_GROUP', group: panel4.group }, { type: 'ADD_GROUP', group: panel4.group },
{ type: 'ACTIVE_GROUP', group: panel4.group },
{ type: 'ADD_PANEL', panel: panel4 }, { type: 'ADD_PANEL', panel: panel4 },
{ type: 'ACTIVE_GROUP', group: panel4.group },
{ type: 'ACTIVE_PANEL', panel: panel4 }, { type: 'ACTIVE_PANEL', panel: panel4 },
]); ]);
@ -973,36 +1001,24 @@ describe('dockviewComponent', () => {
]); ]);
events = []; events = [];
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel2.group!, from: { groupId: panel5.group.id, panelId: panel5.id },
panel5.group!.id, to: { group: panel2.group, position: 'center' },
panel5.id, });
'center'
);
expect(events).toEqual([ expect(events).toEqual([{ type: 'ACTIVE_GROUP', group: panel2.group }]);
{ type: 'REMOVE_PANEL', panel: panel5 },
{ type: 'ACTIVE_PANEL', panel: panel4 },
{ type: 'ADD_PANEL', panel: panel5 },
{ type: 'ACTIVE_PANEL', panel: panel5 },
{ type: 'ACTIVE_GROUP', group: panel2.group },
]);
events = []; events = [];
const groupReferenceBeforeMove = panel4.group; const groupReferenceBeforeMove = panel4.group;
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel2.group!, from: { groupId: panel4.group.id, panelId: panel4.id },
panel4.group!.id, to: { group: panel2.group, position: 'center' },
panel4.id, });
'center'
);
expect(events).toEqual([ expect(events).toEqual([
{ type: 'REMOVE_PANEL', panel: panel4 },
{ type: 'REMOVE_GROUP', group: groupReferenceBeforeMove }, { type: 'REMOVE_GROUP', group: groupReferenceBeforeMove },
{ type: 'ADD_PANEL', panel: panel4 },
{ type: 'ACTIVE_PANEL', panel: panel4 }, { type: 'ACTIVE_PANEL', panel: panel4 },
]); ]);
@ -1022,8 +1038,8 @@ describe('dockviewComponent', () => {
expect(events).toEqual([ expect(events).toEqual([
{ type: 'ADD_GROUP', group: panel6.group }, { type: 'ADD_GROUP', group: panel6.group },
{ type: 'ADD_PANEL', panel: panel6 }, { type: 'ADD_PANEL', panel: panel6 },
{ type: 'ACTIVE_PANEL', panel: panel6 },
{ type: 'ACTIVE_GROUP', group: panel6.group }, { type: 'ACTIVE_GROUP', group: panel6.group },
{ type: 'ACTIVE_PANEL', panel: panel6 },
]); ]);
events = []; events = [];
@ -1038,8 +1054,8 @@ describe('dockviewComponent', () => {
expect(events).toEqual([ expect(events).toEqual([
{ type: 'ADD_GROUP', group: panel7.group }, { type: 'ADD_GROUP', group: panel7.group },
{ type: 'ADD_PANEL', panel: panel7 }, { type: 'ADD_PANEL', panel: panel7 },
{ type: 'ACTIVE_PANEL', panel: panel7 },
{ type: 'ACTIVE_GROUP', group: panel7.group }, { type: 'ACTIVE_GROUP', group: panel7.group },
{ type: 'ACTIVE_PANEL', panel: panel7 },
]); ]);
expect(dockview.activePanel === panel7).toBeTruthy(); expect(dockview.activePanel === panel7).toBeTruthy();
@ -1061,6 +1077,7 @@ describe('dockviewComponent', () => {
{ type: 'REMOVE_PANEL', panel: panel6 }, { type: 'REMOVE_PANEL', panel: panel6 },
{ type: 'REMOVE_GROUP', group: panel6Group }, { type: 'REMOVE_GROUP', group: panel6Group },
{ type: 'ACTIVE_GROUP', group: undefined }, { type: 'ACTIVE_GROUP', group: undefined },
{ type: 'ACTIVE_PANEL', group: undefined },
]); ]);
expect(dockview.size).toBe(0); expect(dockview.size).toBe(0);
@ -1317,12 +1334,10 @@ describe('dockviewComponent', () => {
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id, panelId: 'panel2' },
panel2.group.id, to: { group: panel1.group, position: 'left' },
'panel2', });
'left'
);
expect(panel1Spy).not.toHaveBeenCalled(); expect(panel1Spy).not.toHaveBeenCalled();
expect(panel2Spy).not.toHaveBeenCalled(); expect(panel2Spy).not.toHaveBeenCalled();
@ -1359,12 +1374,10 @@ describe('dockviewComponent', () => {
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id, panelId: 'panel2' },
panel2.group.id, to: { group: panel1.group, position: 'center' },
'panel2', });
'center'
);
expect(panel1Spy).not.toHaveBeenCalled(); expect(panel1Spy).not.toHaveBeenCalled();
expect(panel2Spy).not.toHaveBeenCalled(); expect(panel2Spy).not.toHaveBeenCalled();
@ -1399,13 +1412,10 @@ describe('dockviewComponent', () => {
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel1.group.id, panelId: 'panel1' },
panel1.group.id, to: { group: panel1.group, position: 'center', index: 0 },
'panel1', });
'center',
0
);
expect(panel1Spy).not.toHaveBeenCalled(); expect(panel1Spy).not.toHaveBeenCalled();
expect(panel2Spy).not.toHaveBeenCalled(); expect(panel2Spy).not.toHaveBeenCalled();
@ -1563,12 +1573,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel3.group, from: { groupId: panel1.group.id },
panel1.group.id, to: { group: panel3.group, position: 'center' },
undefined, });
'center'
);
expect(dockview.groups.length).toBe(1); expect(dockview.groups.length).toBe(1);
expect(panel1Spy).toBeCalledTimes(1); expect(panel1Spy).toBeCalledTimes(1);
@ -1697,7 +1705,7 @@ describe('dockviewComponent', () => {
expect(activeGroup.length).toBe(1); expect(activeGroup.length).toBe(1);
expect(addPanel.length).toBe(5); expect(addPanel.length).toBe(5);
expect(removePanel.length).toBe(0); expect(removePanel.length).toBe(0);
expect(activePanel.length).toBe(5); expect(activePanel.length).toBe(1);
expect(layoutChange).toBe(1); expect(layoutChange).toBe(1);
expect(layoutChangeFromJson).toBe(1); expect(layoutChangeFromJson).toBe(1);
@ -2728,32 +2736,26 @@ describe('dockviewComponent', () => {
expect(dockview.element.querySelectorAll('.view').length).toBe(1); expect(dockview.element.querySelectorAll('.view').length).toBe(1);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel3.group, from: { groupId: panel3.group.id, panelId: panel3.id },
panel3.group.id, to: { group: panel3.group, position: 'right' },
panel3.id, });
'right'
);
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.element.querySelectorAll('.view').length).toBe(2); expect(dockview.element.querySelectorAll('.view').length).toBe(2);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel3.group, from: { groupId: panel2.group.id, panelId: panel2.id },
panel2.group.id, to: { group: panel3.group, position: 'bottom' },
panel2.id, });
'bottom'
);
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.element.querySelectorAll('.view').length).toBe(4); expect(dockview.element.querySelectorAll('.view').length).toBe(4);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel2.group, from: { groupId: panel1.group.id, panelId: panel1.id },
panel1.group.id, to: { group: panel2.group, position: 'center' },
panel1.id, });
'center'
);
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
@ -3463,12 +3465,10 @@ describe('dockviewComponent', () => {
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.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id },
panel2.group.id, to: { group: panel1.group, position: 'right' },
undefined, });
'right'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -3508,12 +3508,10 @@ describe('dockviewComponent', () => {
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.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id },
panel2.group.id, to: { group: panel1.group, position: 'center' },
undefined, });
'center'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -3560,12 +3558,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel2.group, from: { groupId: panel3.group.id },
panel3.group.id, to: { group: panel2.group, position: 'center' },
undefined, });
'center'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('floating'); expect(panel2.group.api.location.type).toBe('floating');
@ -3613,12 +3609,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id },
panel2.group.id, to: { group: panel1.group, position: 'right' },
undefined, });
'right'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -3666,12 +3660,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id },
panel2.group.id, to: { group: panel1.group, position: 'center' },
undefined, });
'center'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -3726,12 +3718,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4); expect(dockview.panels.length).toBe(4);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel4.group, from: { groupId: panel2.group.id },
panel2.group.id, to: { group: panel4.group, position: 'center' },
undefined, });
'center'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('floating'); expect(panel2.group.api.location.type).toBe('floating');
@ -3773,12 +3763,10 @@ describe('dockviewComponent', () => {
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.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id, panelId: panel2.id },
panel2.group.id, to: { group: panel1.group, position: 'right' },
panel2.id, });
'right'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -3818,12 +3806,10 @@ describe('dockviewComponent', () => {
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.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id, panelId: panel2.id },
panel2.group.id, to: { group: panel1.group, position: 'center' },
panel2.id, });
'center'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -3870,12 +3856,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel2.group, from: { groupId: panel3.group.id, panelId: panel3.id },
panel3.group.id, to: { group: panel2.group, position: 'center' },
panel3.id, });
'center'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('floating'); expect(panel2.group.api.location.type).toBe('floating');
@ -3923,12 +3907,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id, panelId: panel2.id },
panel2.group.id, to: { group: panel1.group, position: 'right' },
panel2.id, });
'right'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -3976,12 +3958,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel1.group, from: { groupId: panel2.group.id, panelId: panel2.id },
panel2.group.id, to: { group: panel1.group, position: 'center' },
panel2.id, });
'center'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -4036,12 +4016,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4); expect(dockview.panels.length).toBe(4);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel4.group, from: { groupId: panel2.group.id, panelId: panel2.id },
panel2.group.id, to: { group: panel4.group, position: 'center' },
panel2.id, });
'center'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('floating'); expect(panel2.group.api.location.type).toBe('floating');
@ -4090,12 +4068,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel3.group, from: { groupId: panel1.group.id, panelId: panel1.id },
panel1.group.id, to: { group: panel3.group, position: 'center' },
panel1.id, });
'center'
);
expect(panel1.group.api.location.type).toBe('floating'); expect(panel1.group.api.location.type).toBe('floating');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -4142,12 +4118,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel3.group, from: { groupId: panel1.group.id, panelId: panel1.id },
panel1.group.id, to: { group: panel3.group, position: 'center' },
panel1.id, });
'center'
);
expect(panel1.group.api.location.type).toBe('floating'); expect(panel1.group.api.location.type).toBe('floating');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -4195,12 +4169,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel3.group, from: { groupId: panel1.group.id },
panel1.group.id, to: { group: panel3.group, position: 'center' },
undefined, });
'center'
);
expect(panel1.group.api.location.type).toBe('floating'); expect(panel1.group.api.location.type).toBe('floating');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -4247,12 +4219,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel3.group, from: { groupId: panel1.group.id },
panel1.group.id, to: { group: panel3.group, position: 'center' },
undefined, });
'center'
);
expect(panel1.group.api.location.type).toBe('floating'); expect(panel1.group.api.location.type).toBe('floating');
expect(panel2.group.api.location.type).toBe('floating'); expect(panel2.group.api.location.type).toBe('floating');
@ -4427,6 +4397,7 @@ describe('dockviewComponent', () => {
document: fromPartial<Document>({ document: fromPartial<Document>({
body: document.createElement('body'), body: document.createElement('body'),
}), }),
focus: jest.fn(),
addEventListener: jest addEventListener: jest
.fn() .fn()
.mockImplementation((name, cb) => { .mockImplementation((name, cb) => {
@ -4560,12 +4531,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel3.api.group, from: { groupId: panel2.group.id, panelId: panel2.id },
panel2.api.group.id, to: { group: panel3.group, position: 'right' },
panel2.api.id, });
'right'
);
expect(panel1.group.api.location.type).toBe('popout'); expect(panel1.group.api.location.type).toBe('popout');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');
@ -4573,12 +4542,10 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(4); expect(dockview.groups.length).toBe(4);
expect(dockview.panels.length).toBe(3); expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel({
panel3.api.group, from: { groupId: panel1.group.id, panelId: panel1.id },
panel1.api.group.id, to: { group: panel3.group, position: 'center' },
panel1.api.id, });
'center'
);
expect(panel1.group.api.location.type).toBe('grid'); expect(panel1.group.api.location.type).toBe('grid');
expect(panel2.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid');

View File

@ -196,7 +196,14 @@ export class TestPanel implements IDockviewPanel {
this._params = params; this._params = params;
} }
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void { updateParentGroup(
group: DockviewGroupPanel,
options: { isGroupActive: boolean }
): void {
//
}
runEvents(): void {
// //
} }
@ -624,7 +631,7 @@ describe('dockviewGroupPanelModel', () => {
renderer: 'onlyWhenVisibile', renderer: 'onlyWhenVisibile',
} as any); } as any);
cut.openPanel(panel3, { skipSetPanelActive: true }); cut.openPanel(panel3, { skipRender: true });
expect(contentContainer.length).toBe(1); expect(contentContainer.length).toBe(1);
expect(contentContainer.item(0)).toBe(panel2.view.content.element); expect(contentContainer.item(0)).toBe(panel2.view.content.element);

View File

@ -18,8 +18,8 @@ describe('dockviewGroupPanel', () => {
element: document.createElement('div'), element: document.createElement('div'),
dispose: jest.fn(), dispose: jest.fn(),
update: jest.fn(), update: jest.fn(),
onGroupChange: jest.fn(), // onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(), // onPanelVisibleChange: jest.fn(),
}; };
return partial as IContentRenderer; return partial as IContentRenderer;
}); });
@ -29,8 +29,8 @@ describe('dockviewGroupPanel', () => {
element: document.createElement('div'), element: document.createElement('div'),
dispose: jest.fn(), dispose: jest.fn(),
update: jest.fn(), update: jest.fn(),
onGroupChange: jest.fn(), // onGroupChange: jest.fn(),
onPanelVisibleChange: jest.fn(), // onPanelVisibleChange: jest.fn(),
}; };
return partial as IContentRenderer; return partial as IContentRenderer;
}); });
@ -82,51 +82,51 @@ describe('dockviewGroupPanel', () => {
expect(cut.tab.update).toHaveBeenCalled(); expect(cut.tab.update).toHaveBeenCalled();
}); });
test('that events are fired', () => { // test('that events are fired', () => {
const cut = new DockviewPanelModel( // const cut = new DockviewPanelModel(
<IDockviewComponent>new accessorMock(), // <IDockviewComponent>new accessorMock(),
'id', // 'id',
'contentComponent', // 'contentComponent',
'tabComponent' // 'tabComponent'
); // );
const group1 = jest.fn() as any; // const group1 = jest.fn() as any;
const group2 = jest.fn() as any; // const group2 = jest.fn() as any;
cut.updateParentGroup(group1, false); // cut.updateParentGroup(group1, false);
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(1, group1); // expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(1, group1);
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(1, group1); // expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(1, group1);
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith( // expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
1, // 1,
false // false
); // );
expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(1, false); // expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(1, false);
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1); // expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1); // expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(1); // expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(1);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(1); // expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(1);
cut.updateParentGroup(group1, true); // cut.updateParentGroup(group1, true);
expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith( // expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith(
2, // 2,
true // true
); // );
expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(2, true); // expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(2, true);
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1); // expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1); // expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1);
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2); // expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2); // expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
cut.updateParentGroup(group2, true); // cut.updateParentGroup(group2, true);
expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(2, group2); // expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(2, group2);
expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(2, group2); // expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(2, group2);
expect(cut.content.onGroupChange).toHaveBeenCalledTimes(2); // expect(cut.content.onGroupChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(2); // expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(2);
expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2); // expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2);
expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2); // expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2);
}); // });
test('that the default tab is created', () => { test('that the default tab is created', () => {
accessorMock = jest.fn<DockviewComponent, []>(() => { accessorMock = jest.fn<DockviewComponent, []>(() => {

View File

@ -114,17 +114,17 @@ describe('baseComponentGridview', () => {
proportionalLayout: true, proportionalLayout: true,
}); });
const events: (TestPanel | undefined)[] = []; const events: { type: string; panel: TestPanel | undefined }[] = [];
const disposable = new CompositeDisposable( const disposable = new CompositeDisposable(
cut.onDidAddGroup((event) => { cut.onDidAdd((event) => {
events.push(event); events.push({ type: 'add', panel: event });
}), }),
cut.onDidRemoveGroup((event) => { cut.onDidRemove((event) => {
events.push(event); events.push({ type: 'remove', panel: event });
}), }),
cut.onDidActiveGroupChange((event) => { cut.onDidActiveChange((event) => {
events.push(event); events.push({ type: 'active', panel: event });
}) })
); );
@ -141,9 +141,8 @@ describe('baseComponentGridview', () => {
cut.doAddGroup(panel1); cut.doAddGroup(panel1);
expect(events.length).toBe(2); expect(events.length).toBe(1);
expect(events[0]).toBe(panel1); expect(events[0]).toEqual({ type: 'add', panel: panel1 });
expect(events[1]).toBe(panel1);
const panel2 = new TestPanel( const panel2 = new TestPanel(
'id', 'id',
@ -158,12 +157,12 @@ describe('baseComponentGridview', () => {
cut.doAddGroup(panel2); cut.doAddGroup(panel2);
expect(events.length).toBe(4); expect(events.length).toBe(2);
expect(events[2]).toBe(panel2); expect(events[1]).toEqual({ type: 'add', panel: panel2 });
cut.doRemoveGroup(panel1); cut.doRemoveGroup(panel1);
expect(events.length).toBe(5); expect(events.length).toBe(3);
expect(events[4]).toBe(panel1); expect(events[2]).toEqual({ type: 'remove', panel: panel1 });
disposable.dispose(); disposable.dispose();
cut.dispose(); cut.dispose();

View File

@ -63,12 +63,15 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl {
direction: positionToDirection(options.position ?? 'right'), direction: positionToDirection(options.position ?? 'right'),
}); });
this.accessor.moveGroupOrPanel( this.accessor.moveGroupOrPanel({
from: { groupId: this._group.id },
to: {
group, group,
this._group.id, position: options.group
undefined, ? options.position ?? 'center'
options.group ? options.position ?? 'center' : 'center' : 'center',
); },
});
} }
maximize(): void { maximize(): void {

View File

@ -14,7 +14,15 @@ export interface TitleEvent {
} }
export interface RendererChangedEvent { export interface RendererChangedEvent {
renderer: DockviewPanelRenderer; readonly renderer: DockviewPanelRenderer;
}
export interface ActiveGroupEvent {
readonly isActive: boolean;
}
export interface GroupChangedEvent {
// empty
} }
export interface DockviewPanelApi export interface DockviewPanelApi
@ -27,8 +35,8 @@ export interface DockviewPanelApi
readonly isGroupActive: boolean; readonly isGroupActive: boolean;
readonly renderer: DockviewPanelRenderer; readonly renderer: DockviewPanelRenderer;
readonly title: string | undefined; readonly title: string | undefined;
readonly onDidActiveGroupChange: Event<void>; readonly onDidActiveGroupChange: Event<ActiveGroupEvent>;
readonly onDidGroupChange: Event<void>; readonly onDidGroupChange: Event<GroupChangedEvent>;
readonly onDidRendererChange: Event<RendererChangedEvent>; readonly onDidRendererChange: Event<RendererChangedEvent>;
readonly location: DockviewGroupLocation; readonly location: DockviewGroupLocation;
readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>; readonly onDidLocationChange: Event<DockviewGroupPanelFloatingChangeEvent>;
@ -58,10 +66,10 @@ export class DockviewPanelApiImpl
readonly _onDidTitleChange = new Emitter<TitleEvent>(); readonly _onDidTitleChange = new Emitter<TitleEvent>();
readonly onDidTitleChange = this._onDidTitleChange.event; readonly onDidTitleChange = this._onDidTitleChange.event;
private readonly _onDidActiveGroupChange = new Emitter<void>(); private readonly _onDidActiveGroupChange = new Emitter<ActiveGroupEvent>();
readonly onDidActiveGroupChange = this._onDidActiveGroupChange.event; readonly onDidActiveGroupChange = this._onDidActiveGroupChange.event;
private readonly _onDidGroupChange = new Emitter<void>(); private readonly _onDidGroupChange = new Emitter<GroupChangedEvent>();
readonly onDidGroupChange = this._onDidGroupChange.event; readonly onDidGroupChange = this._onDidGroupChange.event;
readonly _onDidRendererChange = new Emitter<RendererChangedEvent>(); readonly _onDidRendererChange = new Emitter<RendererChangedEvent>();
@ -93,23 +101,39 @@ export class DockviewPanelApiImpl
set group(value: DockviewGroupPanel) { set group(value: DockviewGroupPanel) {
const isOldGroupActive = this.isGroupActive; const isOldGroupActive = this.isGroupActive;
if (this._group !== value) {
this._group = value; this._group = value;
this._onDidGroupChange.fire(); this._onDidGroupChange.fire({});
let _trackGroupActive = isOldGroupActive; // prevent duplicate events with same state
if (this._group) {
this.groupEventsDisposable.value = new CompositeDisposable( this.groupEventsDisposable.value = new CompositeDisposable(
this.group.api.onDidLocationChange((event) => { this.group.api.onDidLocationChange((event) => {
if (this.group !== this.panel.group) {
return;
}
this._onDidLocationChange.fire(event); this._onDidLocationChange.fire(event);
}), }),
this.group.api.onDidActiveChange(() => { this.group.api.onDidActiveChange(() => {
this._onDidActiveGroupChange.fire(); if (this.group !== this.panel.group) {
return;
}
if (_trackGroupActive !== this.isGroupActive) {
_trackGroupActive = this.isGroupActive;
this._onDidActiveGroupChange.fire({
isActive: this.isGroupActive,
});
}
}) })
); );
if (this.isGroupActive !== isOldGroupActive) { // if (this.isGroupActive !== isOldGroupActive) {
this._onDidActiveGroupChange.fire(); // this._onDidActiveGroupChange.fire({
} // isActive: this.isGroupActive,
// });
// }
this._onDidLocationChange.fire({ this._onDidLocationChange.fire({
location: this.group.api.location, location: this.group.api.location,
@ -132,8 +156,6 @@ export class DockviewPanelApiImpl
this._group = group; this._group = group;
this.addDisposables( this.addDisposables(
this.groupEventsDisposable, this.groupEventsDisposable,
this._onDidRendererChange, this._onDidRendererChange,
@ -153,13 +175,14 @@ export class DockviewPanelApiImpl
position?: Position; position?: Position;
index?: number; index?: number;
}): void { }): void {
this.accessor.moveGroupOrPanel( this.accessor.moveGroupOrPanel({
options.group, from: { groupId: this._group.id, panelId: this.panel.id },
this._group.id, to: {
this.panel.id, group: options.group,
options.position ?? 'center', position: options.position ?? 'center',
options.index index: options.index,
); },
});
} }
setTitle(title: string): void { setTitle(title: string): void {

View File

@ -390,17 +390,15 @@ export class TabsContainer
return; return;
} }
const alreadyFocused =
panel.id === this.group.model.activePanel?.id &&
this.group.model.isContentFocused;
const isLeftClick = event.button === 0; const isLeftClick = event.button === 0;
if (!isLeftClick || event.defaultPrevented) { if (!isLeftClick || event.defaultPrevented) {
return; return;
} }
if (this.group.activePanel !== panel) {
this.group.model.openPanel(panel); this.group.model.openPanel(panel);
}
}), }),
tab.onDrop((event) => { tab.onDrop((event) => {
this._onDrop.fire({ this._onDrop.fire({

View File

@ -232,6 +232,23 @@ export type DockviewComponentUpdateOptions = Pick<
| 'disableDnd' | 'disableDnd'
>; >;
type MoveGroupOptions = {
from: { group: DockviewGroupPanel };
to: { group: DockviewGroupPanel; position: Position };
};
type MoveGroupOrPanelOptions = {
from: {
groupId: string;
panelId?: string;
};
to: {
group: DockviewGroupPanel;
position: Position;
index?: number;
};
};
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> { export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly activePanel: IDockviewPanel | undefined; readonly activePanel: IDockviewPanel | undefined;
readonly totalPanels: number; readonly totalPanels: number;
@ -241,13 +258,8 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent>; readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent>;
readonly orientation: Orientation; readonly orientation: Orientation;
updateOptions(options: DockviewComponentUpdateOptions): void; updateOptions(options: DockviewComponentUpdateOptions): void;
moveGroupOrPanel( moveGroupOrPanel(options: MoveGroupOrPanelOptions): void;
referenceGroup: DockviewGroupPanel, moveGroup(options: MoveGroupOptions): void;
groupId: string,
itemId: string,
target: Position,
index?: number
): void;
doSetGroupActive: (group: DockviewGroupPanel, skipFocus?: boolean) => void; doSetGroupActive: (group: DockviewGroupPanel, skipFocus?: boolean) => void;
removeGroup: (group: DockviewGroupPanel) => void; removeGroup: (group: DockviewGroupPanel) => void;
options: DockviewComponentOptions; options: DockviewComponentOptions;
@ -287,6 +299,9 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
onWillClose?: (event: { id: string; window: Window }) => void; onWillClose?: (event: { id: string; window: Window }) => void;
} }
): Promise<void>; ): Promise<void>;
readonly onDidRemoveGroup: Event<DockviewGroupPanel>;
readonly onDidAddGroup: Event<DockviewGroupPanel>;
readonly onDidActiveGroupChange: Event<DockviewGroupPanel | undefined>;
} }
export class DockviewComponent export class DockviewComponent
@ -335,6 +350,10 @@ export class DockviewComponent
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> = readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
this._onDidActivePanelChange.event; this._onDidActivePanelChange.event;
private readonly _onDidMovePanel = new Emitter<{
panel: IDockviewPanel;
}>();
private readonly _floatingGroups: DockviewFloatingGroupPanel[] = []; private readonly _floatingGroups: DockviewFloatingGroupPanel[] = [];
private readonly _popoutGroups: { private readonly _popoutGroups: {
window: PopoutWindow; window: PopoutWindow;
@ -344,6 +363,22 @@ export class DockviewComponent
}[] = []; }[] = [];
private readonly _rootDropTarget: Droptarget; private readonly _rootDropTarget: Droptarget;
private _ignoreEvents = 0;
private readonly _onDidRemoveGroup = new Emitter<DockviewGroupPanel>();
readonly onDidRemoveGroup: Event<DockviewGroupPanel> =
this._onDidRemoveGroup.event;
protected readonly _onDidAddGroup = new Emitter<DockviewGroupPanel>();
readonly onDidAddGroup: Event<DockviewGroupPanel> =
this._onDidAddGroup.event;
private readonly _onDidActiveGroupChange = new Emitter<
DockviewGroupPanel | undefined
>();
readonly onDidActiveGroupChange: Event<DockviewGroupPanel | undefined> =
this._onDidActiveGroupChange.event;
get orientation(): Orientation { get orientation(): Orientation {
return this.gridview.orientation; return this.gridview.orientation;
} }
@ -404,9 +439,28 @@ export class DockviewComponent
this._onDidLayoutFromJSON, this._onDidLayoutFromJSON,
this._onDidDrop, this._onDidDrop,
this._onWillDrop, this._onWillDrop,
this._onDidMovePanel,
this._onDidAddGroup,
this._onDidRemoveGroup,
this._onDidActiveGroupChange,
this.onDidAdd((event) => {
if (!this._moving) {
this._onDidAddGroup.fire(event);
}
}),
this.onDidRemove((event) => {
if (!this._moving) {
this._onDidRemoveGroup.fire(event);
}
}),
this.onDidActiveChange((event) => {
if (!this._moving) {
this._onDidActiveGroupChange.fire(event);
}
}),
Event.any( Event.any(
this.onDidAddGroup, this.onDidAdd,
this.onDidRemoveGroup this.onDidRemove
)(() => { )(() => {
this.updateWatermark(); this.updateWatermark();
}), }),
@ -515,12 +569,16 @@ export class DockviewComponent
const data = getPanelData(); const data = getPanelData();
if (data) { if (data) {
this.moveGroupOrPanel( this.moveGroupOrPanel({
this.orthogonalize(event.position), from: {
data.groupId, groupId: data.groupId,
data.panelId ?? undefined, panelId: data.panelId ?? undefined,
'center' },
); to: {
group: this.orthogonalize(event.position),
position: 'center',
},
});
} else { } else {
this._onDidDrop.fire( this._onDidDrop.fire(
new DockviewDidDropEvent({ new DockviewDidDropEvent({
@ -574,7 +632,7 @@ export class DockviewComponent
panels.forEach((panel) => { panels.forEach((panel) => {
options.to.model.openPanel(panel, { options.to.model.openPanel(panel, {
skipSetPanelActive: activePanel !== panel, skipRender: activePanel !== panel,
}); });
}); });
} }
@ -651,6 +709,14 @@ export class DockviewComponent
this.createGroup({ id: groupId }); this.createGroup({ id: groupId });
group.model.renderContainer = overlayRenderContainer; group.model.renderContainer = overlayRenderContainer;
if (!options?.overridePopoutGroup) {
this._onDidAddGroup.fire(group);
}
const isMoving = this._moving;
try {
this._moving = true;
if (itemToPopout instanceof DockviewPanel) { if (itemToPopout instanceof DockviewPanel) {
const panel = const panel =
referenceGroup.model.removePanel(itemToPopout); referenceGroup.model.removePanel(itemToPopout);
@ -662,6 +728,9 @@ export class DockviewComponent
}); });
referenceGroup.api.setHidden(true); referenceGroup.api.setHidden(true);
} }
} finally {
this._moving = isMoving;
}
popoutContainer.classList.add('dv-dockview'); popoutContainer.classList.add('dv-dockview');
popoutContainer.style.overflow = 'hidden'; popoutContainer.style.overflow = 'hidden';
@ -674,6 +743,17 @@ export class DockviewComponent
getWindow: () => _window.window!, getWindow: () => _window.window!,
}; };
popoutWindowDisposable.addDisposables(
group.api.onDidActiveChange((event) => {
if (event.isActive) {
_window.window?.focus();
}
}),
group.api.onWillFocus(() => {
_window.window?.focus();
})
);
const value = { const value = {
window: _window, window: _window,
popoutGroup: group, popoutGroup: group,
@ -697,10 +777,15 @@ export class DockviewComponent
overlayRenderContainer, overlayRenderContainer,
Disposable.from(() => { Disposable.from(() => {
if (this.getPanel(referenceGroup.id)) { if (this.getPanel(referenceGroup.id)) {
try {
this._moving = true;
moveGroupWithoutDestroying({ moveGroupWithoutDestroying({
from: group, from: group,
to: referenceGroup, to: referenceGroup,
}); });
} finally {
this._moving = isMoving;
}
if (referenceGroup.api.isHidden) { if (referenceGroup.api.isHidden) {
referenceGroup.api.setHidden(false); referenceGroup.api.setHidden(false);
@ -718,6 +803,7 @@ export class DockviewComponent
this.overlayRenderContainer; this.overlayRenderContainer;
removedGroup.model.location = { type: 'grid' }; removedGroup.model.location = { type: 'grid' };
this.doAddGroup(removedGroup, [0]); this.doAddGroup(removedGroup, [0]);
this.doSetGroupAndPanelActive(removedGroup);
} }
}) })
); );
@ -733,19 +819,27 @@ 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;
skipActiveGroup?: boolean;
}
): void { ): void {
let group: DockviewGroupPanel; let group: DockviewGroupPanel;
if (item instanceof DockviewPanel) { if (item instanceof DockviewPanel) {
group = this.createGroup(); group = this.createGroup();
this._onDidAddGroup.fire(group);
this.movingLock(() =>
this.removePanel(item, { this.removePanel(item, {
removeEmptyGroup: true, removeEmptyGroup: true,
skipDispose: true, skipDispose: true,
}); skipSetActiveGroup: true,
})
);
group.model.openPanel(item); group.model.openPanel(item, { skipSetGroupActive: true });
} else { } else {
group = item; group = item;
@ -841,6 +935,11 @@ export class DockviewComponent
); );
this._floatingGroups.push(floatingGroupPanel); this._floatingGroups.push(floatingGroupPanel);
if (!options?.skipActiveGroup) {
this.doSetGroupAndPanelActive(group);
}
this.updateWatermark(); this.updateWatermark();
} }
@ -952,8 +1051,8 @@ export class DockviewComponent
} }
setActivePanel(panel: IDockviewPanel): void { setActivePanel(panel: IDockviewPanel): void {
this.doSetGroupActive(panel.group);
panel.group.model.openPanel(panel); panel.group.model.openPanel(panel);
this.doSetGroupAndPanelActive(panel.group);
} }
moveToNext(options: MovementOptions = {}): void { moveToNext(options: MovementOptions = {}): void {
@ -1108,7 +1207,7 @@ export class DockviewComponent
activeView === panel.id; activeView === panel.id;
group.model.openPanel(panel, { group.model.openPanel(panel, {
skipSetPanelActive: !isActive, skipRender: !isActive,
skipSetGroupActive: true, skipSetGroupActive: true,
}); });
} }
@ -1241,10 +1340,6 @@ export class DockviewComponent
this.doSetGroupAndPanelActive(undefined); this.doSetGroupAndPanelActive(undefined);
} }
if (hasActivePanel) {
this._onDidActivePanelChange.fire(undefined);
}
this.gridview.clear(); this.gridview.clear();
} }
@ -1301,8 +1396,10 @@ export class DockviewComponent
const group = this.orthogonalize( const group = this.orthogonalize(
directionToPosition(<Direction>options.position.direction) directionToPosition(<Direction>options.position.direction)
); );
const panel = this.createPanel(options, group); const panel = this.createPanel(options, group);
group.model.openPanel(panel); group.model.openPanel(panel);
this.doSetGroupAndPanelActive(group);
return panel; return panel;
} }
} else { } else {
@ -1318,26 +1415,30 @@ export class DockviewComponent
if (options.floating) { if (options.floating) {
const group = this.createGroup(); const group = this.createGroup();
this._onDidAddGroup.fire(group);
const o = const o =
typeof options.floating === 'object' && typeof options.floating === 'object' &&
options.floating !== null options.floating !== null
? options.floating ? options.floating
: {}; : {};
this.addFloatingGroup(group, o, { this.addFloatingGroup(group, o, {
inDragMode: false, inDragMode: false,
skipRemoveGroup: true, skipRemoveGroup: true,
skipActiveGroup: true,
}); });
this._onDidAddGroup.fire(group);
panel = this.createPanel(options, group); panel = this.createPanel(options, group);
group.model.openPanel(panel); group.model.openPanel(panel);
this.doSetGroupAndPanelActive(group);
} else if ( } else if (
referenceGroup.api.location.type === 'floating' || referenceGroup.api.location.type === 'floating' ||
target === 'center' target === 'center'
) { ) {
panel = this.createPanel(options, referenceGroup); panel = this.createPanel(options, referenceGroup);
referenceGroup.model.openPanel(panel); referenceGroup.model.openPanel(panel);
this.doSetGroupAndPanelActive(referenceGroup);
} else { } else {
const location = getGridLocation(referenceGroup.element); const location = getGridLocation(referenceGroup.element);
const relativeLocation = getRelativeLocation( const relativeLocation = getRelativeLocation(
@ -1348,30 +1449,31 @@ export class DockviewComponent
const group = this.createGroupAtLocation(relativeLocation); const group = this.createGroupAtLocation(relativeLocation);
panel = this.createPanel(options, group); panel = this.createPanel(options, group);
group.model.openPanel(panel); group.model.openPanel(panel);
this.doSetGroupAndPanelActive(group);
} }
} else if (options.floating) { } else if (options.floating) {
const group = this.createGroup(); const group = this.createGroup();
this._onDidAddGroup.fire(group);
const o = const o =
typeof options.floating === 'object' && typeof options.floating === 'object' &&
options.floating !== null options.floating !== null
? options.floating ? options.floating
: {}; : {};
this.addFloatingGroup(group, o, { this.addFloatingGroup(group, o, {
inDragMode: false, inDragMode: false,
skipRemoveGroup: true, skipRemoveGroup: true,
skipActiveGroup: true,
}); });
this._onDidAddGroup.fire(group); panel = this.createPanel(options, group);
group.model.openPanel(panel);
} else {
const group = this.createGroupAtLocation();
panel = this.createPanel(options, group); panel = this.createPanel(options, group);
group.model.openPanel(panel); group.model.openPanel(panel);
this.doSetGroupAndPanelActive(group); this.doSetGroupAndPanelActive(group);
} else {
const group = this.createGroupAtLocation();
panel = this.createPanel(options, group);
group.model.openPanel(panel);
} }
return panel; return panel;
@ -1379,7 +1481,11 @@ export class DockviewComponent
removePanel( removePanel(
panel: IDockviewPanel, panel: IDockviewPanel,
options: { removeEmptyGroup: boolean; skipDispose: boolean } = { options: {
removeEmptyGroup: boolean;
skipDispose: boolean;
skipSetActiveGroup?: boolean;
} = {
removeEmptyGroup: true, removeEmptyGroup: true,
skipDispose: false, skipDispose: false,
} }
@ -1392,7 +1498,9 @@ export class DockviewComponent
); );
} }
group.model.removePanel(panel); group.model.removePanel(panel, {
skipSetActiveGroup: options.skipSetActiveGroup,
});
if (!options.skipDispose) { if (!options.skipDispose) {
this.overlayRenderContainer.detatch(panel); this.overlayRenderContainer.detatch(panel);
@ -1400,7 +1508,7 @@ export class DockviewComponent
} }
if (group.size === 0 && options.removeEmptyGroup) { if (group.size === 0 && options.removeEmptyGroup) {
this.removeGroup(group); this.removeGroup(group, { skipActive: options.skipSetActiveGroup });
} }
} }
@ -1486,6 +1594,7 @@ export class DockviewComponent
const group = this.orthogonalize( const group = this.orthogonalize(
directionToPosition(<Direction>options.direction) directionToPosition(<Direction>options.direction)
); );
this.doSetGroupAndPanelActive(group);
return group; return group;
} }
@ -1498,9 +1607,11 @@ export class DockviewComponent
target target
); );
this.doAddGroup(group, relativeLocation); this.doAddGroup(group, relativeLocation);
this.doSetGroupAndPanelActive(group);
return group; return group;
} else { } else {
this.doAddGroup(group); this.doAddGroup(group);
this.doSetGroupAndPanelActive(group);
return group; return group;
} }
} }
@ -1514,22 +1625,7 @@ export class DockviewComponent
} }
| undefined | undefined
): void { ): void {
const panels = [...group.panels]; // reassign since group panels will mutate
for (const panel of panels) {
this.removePanel(panel, {
removeEmptyGroup: false,
skipDispose: options?.skipDispose ?? false,
});
}
const activePanel = this.activePanel;
this.doRemoveGroup(group, options); this.doRemoveGroup(group, options);
if (this.activePanel !== activePanel) {
this._onDidActivePanelChange.fire(this.activePanel);
}
} }
protected override doRemoveGroup( protected override doRemoveGroup(
@ -1542,6 +1638,19 @@ export class DockviewComponent
} }
| undefined | undefined
): DockviewGroupPanel { ): DockviewGroupPanel {
const panels = [...group.panels]; // reassign since group panels will mutate
if (!options?.skipDispose) {
for (const panel of panels) {
this.removePanel(panel, {
removeEmptyGroup: false,
skipDispose: options?.skipDispose ?? false,
});
}
}
const activePanel = this.activePanel;
if (group.api.location.type === 'floating') { if (group.api.location.type === 'floating') {
const floatingGroup = this._floatingGroups.find( const floatingGroup = this._floatingGroups.find(
(_) => _.group === group (_) => _.group === group
@ -1560,7 +1669,7 @@ export class DockviewComponent
if (!options?.skipActive && this._activeGroup === group) { if (!options?.skipActive && this._activeGroup === group) {
const groups = Array.from(this._groups.values()); const groups = Array.from(this._groups.values());
this.doSetGroupActive( this.doSetGroupAndPanelActive(
groups.length > 0 ? groups[0].value : undefined groups.length > 0 ? groups[0].value : undefined
); );
} }
@ -1597,7 +1706,7 @@ export class DockviewComponent
if (!options?.skipActive && this._activeGroup === group) { if (!options?.skipActive && this._activeGroup === group) {
const groups = Array.from(this._groups.values()); const groups = Array.from(this._groups.values());
this.doSetGroupActive( this.doSetGroupAndPanelActive(
groups.length > 0 ? groups[0].value : undefined groups.length > 0 ? groups[0].value : undefined
); );
} }
@ -1609,48 +1718,100 @@ export class DockviewComponent
throw new Error('failed to find popout group'); throw new Error('failed to find popout group');
} }
return super.doRemoveGroup(group, options); const re = super.doRemoveGroup(group, options);
if (!options?.skipActive) {
if (this.activePanel !== activePanel) {
this._onDidActivePanelChange.fire(this.activePanel);
}
} }
moveGroupOrPanel( return re;
destinationGroup: DockviewGroupPanel, }
sourceGroupId: string,
sourceItemId: string | undefined, private _moving = false;
destinationTarget: Position,
destinationIndex?: number movingLock<T>(func: () => T): T {
): void { const isMoving = this._moving;
try {
this._moving = true;
return func();
} finally {
this._moving = isMoving;
}
}
moveGroupOrPanel(options: MoveGroupOrPanelOptions): void {
const destinationGroup = options.to.group;
const sourceGroupId = options.from.groupId;
const sourceItemId = options.from.panelId;
const destinationTarget = options.to.position;
const destinationIndex = options.to.index;
const sourceGroup = sourceGroupId const sourceGroup = sourceGroupId
? this._groups.get(sourceGroupId)?.value ? this._groups.get(sourceGroupId)?.value
: undefined; : undefined;
if (sourceItemId === undefined) { if (!sourceGroup) {
if (sourceGroup) { throw new Error(`Failed to find group id ${sourceGroupId}`);
this.moveGroup(
sourceGroup,
destinationGroup,
destinationTarget
);
} }
if (sourceItemId === undefined) {
/**
* Moving an entire group into another group
*/
this.moveGroup({
from: { group: sourceGroup },
to: {
group: destinationGroup,
position: destinationTarget,
},
});
return; return;
} }
if (!destinationTarget || destinationTarget === 'center') { if (!destinationTarget || destinationTarget === 'center') {
const groupItem: IDockviewPanel | undefined = /**
sourceGroup?.model.removePanel(sourceItemId) ?? * Dropping a panel within another group
this.panels.find((panel) => panel.id === sourceItemId); */
if (!groupItem) { const removedPanel: IDockviewPanel | undefined = this.movingLock(
() =>
sourceGroup.model.removePanel(sourceItemId, {
skipEvents: true,
skipActive: true,
skipSetActiveGroup: true,
})
);
if (!removedPanel) {
throw new Error(`No panel with id ${sourceItemId}`); throw new Error(`No panel with id ${sourceItemId}`);
} }
if (sourceGroup?.model.size === 0) { if (sourceGroup.model.size === 0) {
this.doRemoveGroup(sourceGroup); // remove the group and do not set a new group as active
this.doRemoveGroup(sourceGroup, { skipActive: true });
} }
destinationGroup.model.openPanel(groupItem, { this.movingLock(() =>
destinationGroup.model.openPanel(removedPanel, {
index: destinationIndex, index: destinationIndex,
skipSetGroupActive: true,
})
);
this.doSetGroupAndPanelActive(destinationGroup);
this._onDidMovePanel.fire({
panel: removedPanel,
}); });
} else { } else {
/**
* Dropping a panel to the extremities of a group which will place that panel
* into an adjacent group
*/
const referenceLocation = getGridLocation(destinationGroup.element); const referenceLocation = getGridLocation(destinationGroup.element);
const targetLocation = getRelativeLocation( const targetLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
@ -1658,7 +1819,12 @@ export class DockviewComponent
destinationTarget destinationTarget
); );
if (sourceGroup && sourceGroup.size < 2) { if (sourceGroup.size < 2) {
/**
* If we are moving from a group which only has one panel left we will consider
* moving the group itself rather than moving the panel into a newly created group
*/
const [targetParentLocation, to] = tail(targetLocation); const [targetParentLocation, to] = tail(targetLocation);
if (sourceGroup.api.location.type === 'grid') { if (sourceGroup.api.location.type === 'grid') {
@ -1675,31 +1841,45 @@ export class DockviewComponent
// if a group has one tab - we are essentially moving the 'group' // if a group has one tab - we are essentially moving the 'group'
// which is equivalent to swapping two views in this case // which is equivalent to swapping two views in this case
this.gridview.moveView(sourceParentLocation, from, to); this.gridview.moveView(sourceParentLocation, from, to);
return;
} }
} }
// source group will become empty so delete the group // source group will become empty so delete the group
const targetGroup = this.doRemoveGroup(sourceGroup, { const targetGroup = this.movingLock(() =>
this.doRemoveGroup(sourceGroup, {
skipActive: true, skipActive: true,
skipDispose: true, skipDispose: true,
}); })
);
// after deleting the group we need to re-evaulate the ref location // after deleting the group we need to re-evaulate the ref location
const updatedReferenceLocation = getGridLocation( const updatedReferenceLocation = getGridLocation(
destinationGroup.element destinationGroup.element
); );
const location = getRelativeLocation( const location = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
updatedReferenceLocation, updatedReferenceLocation,
destinationTarget destinationTarget
); );
this.doAddGroup(targetGroup, location); this.movingLock(() => this.doAddGroup(targetGroup, location));
this.doSetGroupAndPanelActive(targetGroup);
} else { } else {
const groupItem: IDockviewPanel | undefined = /**
sourceGroup?.model.removePanel(sourceItemId) ?? * The group we are removing from has many panels, we need to remove the panels we are moving,
this.panels.find((panel) => panel.id === sourceItemId); * create a new group, add the panels to that new group and add the new group in an appropiate position
*/
const removedPanel: IDockviewPanel | undefined =
this.movingLock(() =>
sourceGroup.model.removePanel(sourceItemId, {
skipEvents: true,
skipActive: true,
skipSetActiveGroup: true,
})
);
if (!groupItem) { if (!removedPanel) {
throw new Error(`No panel with id ${sourceItemId}`); throw new Error(`No panel with id ${sourceItemId}`);
} }
@ -1710,42 +1890,58 @@ export class DockviewComponent
); );
const group = this.createGroupAtLocation(dropLocation); const group = this.createGroupAtLocation(dropLocation);
group.model.openPanel(groupItem); this.movingLock(() =>
group.model.openPanel(removedPanel, {
skipEvents: true,
skipSetGroupActive: true,
})
);
this.doSetGroupAndPanelActive(group);
} }
} }
} }
private moveGroup( moveGroup(options: MoveGroupOptions): void {
sourceGroup: DockviewGroupPanel, const from = options.from.group;
referenceGroup: DockviewGroupPanel, const to = options.to.group;
target: Position const target = options.to.position;
): void {
if (sourceGroup) { if (target === 'center') {
if (!target || target === 'center') { const activePanel = from.activePanel;
const activePanel = sourceGroup.activePanel;
const panels = [...sourceGroup.panels].map((p) => const panels = this.movingLock(() =>
sourceGroup.model.removePanel(p.id) [...from.panels].map((p) =>
from.model.removePanel(p.id, {
skipRender: true,
skipEvents: true,
})
)
); );
if (sourceGroup?.model.size === 0) { if (from?.model.size === 0) {
this.doRemoveGroup(sourceGroup); this.doRemoveGroup(from, { skipActive: true });
} }
this.movingLock(() => {
for (const panel of panels) { for (const panel of panels) {
referenceGroup.model.openPanel(panel, { to.model.openPanel(panel, {
skipSetPanelActive: panel !== activePanel, skipRender: panel !== activePanel,
skipSetGroupActive: panel !== activePanel,
}); });
} }
});
panels.forEach((panel) => {
this._onDidMovePanel.fire({ panel });
});
} else { } else {
switch (sourceGroup.api.location.type) { switch (from.api.location.type) {
case 'grid': case 'grid':
this.gridview.removeView( this.gridview.removeView(getGridLocation(from.element));
getGridLocation(sourceGroup.element)
);
break; break;
case 'floating': { case 'floating': {
const selectedFloatingGroup = this._floatingGroups.find( const selectedFloatingGroup = this._floatingGroups.find(
(x) => x.group === sourceGroup (x) => x.group === from
); );
if (!selectedFloatingGroup) { if (!selectedFloatingGroup) {
throw new Error('failed to find floating group'); throw new Error('failed to find floating group');
@ -1755,7 +1951,7 @@ export class DockviewComponent
} }
case 'popout': { case 'popout': {
const selectedPopoutGroup = this._popoutGroups.find( const selectedPopoutGroup = this._popoutGroups.find(
(x) => x.popoutGroup === sourceGroup (x) => x.popoutGroup === from
); );
if (!selectedPopoutGroup) { if (!selectedPopoutGroup) {
throw new Error('failed to find popout group'); throw new Error('failed to find popout group');
@ -1764,32 +1960,51 @@ export class DockviewComponent
} }
} }
const referenceLocation = getGridLocation( const referenceLocation = getGridLocation(to.element);
referenceGroup.element
);
const dropLocation = getRelativeLocation( const dropLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
referenceLocation, referenceLocation,
target target
); );
this.gridview.addView( this.gridview.addView(from, Sizing.Distribute, dropLocation);
sourceGroup,
Sizing.Distribute, from.panels.forEach((panel) => {
dropLocation this._onDidMovePanel.fire({ panel });
); });
}
} }
} }
doSetGroupAndPanelActive( override doSetGroupActive(group: DockviewGroupPanel | undefined): void {
group: DockviewGroupPanel | undefined,
): void {
const activePanel = this.activePanel;
super.doSetGroupActive(group); super.doSetGroupActive(group);
if (this._activeGroup?.activePanel !== activePanel) { const activePanel = this.activePanel;
this._onDidActivePanelChange.fire(this._activeGroup?.activePanel);
if (
!this._moving &&
activePanel !== this._onDidActivePanelChange.value
) {
this._onDidActivePanelChange.fire(activePanel);
}
}
doSetGroupAndPanelActive(group: DockviewGroupPanel | undefined): void {
// if (
// this.activeGroup === group &&
// this._onDidActiveGroupChange.value !== group
// ) {
// this._onDidActiveGroupChange.fire(group);
// }
super.doSetGroupActive(group);
const activePanel = this.activePanel;
if (
!this._moving &&
activePanel !== this._onDidActivePanelChange.value
) {
this._onDidActivePanelChange.fire(activePanel);
} }
} }
@ -1836,7 +2051,14 @@ export class DockviewComponent
}), }),
view.model.onMove((event) => { view.model.onMove((event) => {
const { groupId, itemId, target, index } = event; const { groupId, itemId, target, index } = event;
this.moveGroupOrPanel(view, groupId, itemId, target, index); this.moveGroupOrPanel({
from: { groupId: groupId, panelId: itemId },
to: {
group: view,
position: target,
index,
},
});
}), }),
view.model.onDidDrop((event) => { view.model.onDidDrop((event) => {
this._onDidDrop.fire(event); this._onDidDrop.fire(event);
@ -1853,13 +2075,27 @@ export class DockviewComponent
this._onWillShowOverlay.fire(event); this._onWillShowOverlay.fire(event);
}), }),
view.model.onDidAddPanel((event) => { view.model.onDidAddPanel((event) => {
if (this._moving) {
return;
}
this._onDidAddPanel.fire(event.panel); this._onDidAddPanel.fire(event.panel);
}), }),
view.model.onDidRemovePanel((event) => { view.model.onDidRemovePanel((event) => {
if (this._moving) {
return;
}
this._onDidRemovePanel.fire(event.panel); this._onDidRemovePanel.fire(event.panel);
}), }),
view.model.onDidActivePanelChange((event) => { view.model.onDidActivePanelChange((event) => {
if (this._moving) {
return;
}
if (event.panel !== this.activePanel) {
return;
}
if (this._onDidActivePanelChange.value !== event.panel) {
this._onDidActivePanelChange.fire(event.panel); this._onDidActivePanelChange.fire(event.panel);
}
}) })
); );

View File

@ -88,6 +88,13 @@ export class DockviewGroupPanel
); );
} }
override focus(): void {
if (!this.api.isActive) {
this.api.setActive();
}
super.focus();
}
initialize(): void { initialize(): void {
this._model.initialize(); this._model.initialize();
} }

View File

@ -460,7 +460,7 @@ export class DockviewGroupPanelModel
// must be run after the constructor otherwise this.parent may not be // must be run after the constructor otherwise this.parent may not be
// correctly initialized // correctly initialized
this.setActive(this.isActive, true, true); this.setActive(this.isActive, true);
this.updateContainer(); this.updateContainer();
if (this.accessor.options.createRightHeaderActionsElement) { if (this.accessor.options.createRightHeaderActionsElement) {
@ -604,17 +604,25 @@ export class DockviewGroupPanelModel
} }
focus(): void { focus(): void {
this._activePanel?.focus?.(); this._activePanel?.focus();
} }
public openPanel( public openPanel(
panel: IDockviewPanel, panel: IDockviewPanel,
options: { options: {
index?: number; index?: number;
skipSetPanelActive?: boolean; skipRender?: boolean;
skipEvents?: boolean;
skipSetGroupActive?: boolean; skipSetGroupActive?: boolean;
} = {} } = {}
): void { ): void {
/**
* set the panel group
* add the panel
* check if group active
* check if panel active
*/
if ( if (
typeof options.index !== 'number' || typeof options.index !== 'number' ||
options.index > this.panels.length options.index > this.panels.length
@ -622,34 +630,47 @@ export class DockviewGroupPanelModel
options.index = this.panels.length; options.index = this.panels.length;
} }
const skipSetPanelActive = !!options.skipSetPanelActive; const skipRender = !!options.skipRender;
const skipSetGroupActive = !!options.skipSetGroupActive;
// ensure the group is updated before we fire any events // ensure the group is updated before we fire any events
panel.updateParentGroup(this.groupPanel, true); panel.updateParentGroup(this.groupPanel, { isGroupActive: true });
this.doAddPanel(panel, options.index, {
skipRender,
});
if (this._activePanel === panel) { if (this._activePanel === panel) {
if (!skipSetGroupActive) {
this.accessor.doSetGroupActive(this.groupPanel);
}
this.contentContainer.renderPanel(panel, { asActive: true }); this.contentContainer.renderPanel(panel, { asActive: true });
return; return;
} }
this.doAddPanel(panel, options.index, skipSetPanelActive); if (!skipRender) {
if (!skipSetPanelActive) {
this.doSetActivePanel(panel); this.doSetActivePanel(panel);
} }
if (!skipSetGroupActive) { if (!options.skipSetGroupActive) {
this.accessor.doSetGroupActive(this.groupPanel); this.accessor.doSetGroupActive(this.groupPanel);
} }
if (!options.skipEvents) {
panel.runEvents();
this.updateContainer(); this.updateContainer();
} }
}
public removePanel(groupItemOrId: IDockviewPanel | string): IDockviewPanel { public removePanel(
groupItemOrId: IDockviewPanel | string,
options: {
skipRender?: boolean;
skipEvents?: boolean;
skipActive?: boolean;
skipSetActiveGroup?: boolean;
} = {
skipRender: false,
skipEvents: false,
skipActive: false,
}
): IDockviewPanel {
const id = const id =
typeof groupItemOrId === 'string' typeof groupItemOrId === 'string'
? groupItemOrId ? groupItemOrId
@ -661,7 +682,7 @@ export class DockviewGroupPanelModel
throw new Error('invalid operation'); throw new Error('invalid operation');
} }
return this._removePanel(panelToRemove); return this._removePanel(panelToRemove, options);
} }
public closeAllPanels(): void { public closeAllPanels(): void {
@ -692,15 +713,8 @@ export class DockviewGroupPanelModel
this.tabsContainer.setRightActionsElement(element); this.tabsContainer.setRightActionsElement(element);
} }
public setActive( public setActive(isGroupActive: boolean, force = false): void {
isGroupActive: boolean,
skipFocus = false,
force = false
): void {
if (!force && this.isActive === isGroupActive) { if (!force && this.isActive === isGroupActive) {
if (!skipFocus) {
this._activePanel?.focus?.();
}
return; return;
} }
@ -716,12 +730,6 @@ export class DockviewGroupPanelModel
} }
this.updateContainer(); this.updateContainer();
if (isGroupActive) {
if (!skipFocus) {
this._activePanel?.focus?.();
}
}
} }
public layout(width: number, height: number): void { public layout(width: number, height: number): void {
@ -735,21 +743,35 @@ export class DockviewGroupPanelModel
} }
} }
private _removePanel(panel: IDockviewPanel): IDockviewPanel { private _removePanel(
panel: IDockviewPanel,
options: {
skipRender?: boolean;
skipEvents?: boolean;
skipSetActiveGroup?: boolean;
}
): IDockviewPanel {
const isActivePanel = this._activePanel === panel; const isActivePanel = this._activePanel === panel;
this.doRemovePanel(panel); this.doRemovePanel(panel);
if (isActivePanel && this.panels.length > 0) { if (isActivePanel && this.panels.length > 0) {
const nextPanel = this.mostRecentlyUsed[0]; const nextPanel = this.mostRecentlyUsed[0];
this.openPanel(nextPanel); this.openPanel(nextPanel, {
skipRender: options.skipRender,
skipEvents: options.skipEvents,
skipSetGroupActive: options.skipSetActiveGroup,
});
} }
if (this._activePanel && this.panels.length === 0) { if (this._activePanel && this.panels.length === 0) {
this.doSetActivePanel(undefined); this.doSetActivePanel(undefined);
} }
if (!options.skipEvents) {
this.updateContainer(); this.updateContainer();
}
return panel; return panel;
} }
@ -776,7 +798,9 @@ export class DockviewGroupPanelModel
private doAddPanel( private doAddPanel(
panel: IDockviewPanel, panel: IDockviewPanel,
index: number = this.panels.length, index: number = this.panels.length,
skipSetActive = false options: {
skipRender: boolean;
} = { skipRender: false }
): void { ): void {
const existingPanel = this._panels.indexOf(panel); const existingPanel = this._panels.indexOf(panel);
const hasExistingPanel = existingPanel > -1; const hasExistingPanel = existingPanel > -1;
@ -786,7 +810,7 @@ export class DockviewGroupPanelModel
this.tabsContainer.openPanel(panel, index); this.tabsContainer.openPanel(panel, index);
if (!skipSetActive) { if (!options.skipRender) {
this.contentContainer.openPanel(panel); this.contentContainer.openPanel(panel);
} }
@ -802,6 +826,10 @@ export class DockviewGroupPanelModel
} }
private doSetActivePanel(panel: IDockviewPanel | undefined): void { private doSetActivePanel(panel: IDockviewPanel | undefined): void {
if (this._activePanel === panel) {
return;
}
this._activePanel = panel; this._activePanel = panel;
if (panel) { if (panel) {
@ -811,7 +839,9 @@ export class DockviewGroupPanelModel
this.updateMru(panel); this.updateMru(panel);
this._onDidActivePanelChange.fire({ panel }); this._onDidActivePanelChange.fire({
panel,
});
} }
} }
@ -829,7 +859,10 @@ export class DockviewGroupPanelModel
toggleClass(this.container, 'empty', this.isEmpty); toggleClass(this.container, 'empty', this.isEmpty);
this.panels.forEach((panel) => this.panels.forEach((panel) =>
panel.updateParentGroup(this.groupPanel, this.isActive) // panel.updateParentGroup(this.groupPanel, {
// isGroupActive: this.isActive,
// })
panel.runEvents()
); );
if (this.isEmpty && !this.watermark) { if (this.isEmpty && !this.watermark) {

View File

@ -18,11 +18,15 @@ export interface IDockviewPanel extends IDisposable, IPanel {
readonly api: DockviewPanelApi; readonly api: DockviewPanelApi;
readonly title: string | undefined; readonly title: string | undefined;
readonly params: Parameters | undefined; readonly params: Parameters | undefined;
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void; updateParentGroup(
group: DockviewGroupPanel,
options: { isGroupActive: boolean }
): void;
init(params: IGroupPanelInitParameters): void; init(params: IGroupPanelInitParameters): void;
toJSON(): GroupviewPanelState; toJSON(): GroupviewPanelState;
setTitle(title: string): void; setTitle(title: string): void;
update(event: PanelUpdateEvent): void; update(event: PanelUpdateEvent): void;
runEvents(): void;
} }
export class DockviewPanel export class DockviewPanel
@ -94,16 +98,6 @@ export class DockviewPanel
} }
focus(): void { focus(): void {
/**
* This is a progmatic request of focus -
* We need to tell the active panel that it can choose it's focus
* If the panel doesn't choose the panels container for it
*/
if (!this.api.isActive) {
this.api.setActive();
}
const event = new WillFocusEvent(); const event = new WillFocusEvent();
this.api._onWillFocus.fire(event); this.api._onWillFocus.fire(event);
@ -111,7 +105,9 @@ export class DockviewPanel
return; return;
} }
this.group.model.focusContent(); if (!this.api.isActive) {
this.api.setActive();
}
} }
public toJSON(): GroupviewPanelState { public toJSON(): GroupviewPanelState {
@ -183,24 +179,49 @@ export class DockviewPanel
public updateParentGroup( public updateParentGroup(
group: DockviewGroupPanel, group: DockviewGroupPanel,
isGroupActive: boolean options: { isGroupActive: boolean }
): void { ): void {
this._group = group; this._group = group;
this.api.group = group;
// const isPanelVisible = this._group.model.isPanelActive(this);
// const isActive = options.isGroupActive && isPanelVisible;
// if (this.api.isActive !== isActive) {
// this.api._onDidActiveChange.fire({
// isActive: options.isGroupActive && isPanelVisible,
// });
// }
// if (this.api.isVisible !== isPanelVisible) {
// this.api._onDidVisibilityChange.fire({
// isVisible: isPanelVisible,
// });
// }
// this.view.updateParentGroup(
// this._group,
// this._group.model.isPanelActive(this)
// );
}
runEvents(): void {
this.api.group = this._group;
const isPanelVisible = this._group.model.isPanelActive(this); const isPanelVisible = this._group.model.isPanelActive(this);
const isActive = this.group.api.isActive && isPanelVisible;
if (this.api.isActive !== isActive) {
this.api._onDidActiveChange.fire({ this.api._onDidActiveChange.fire({
isActive: isGroupActive && isPanelVisible, isActive: this.group.api.isActive && isPanelVisible,
}); });
}
if (this.api.isVisible !== isPanelVisible) {
this.api._onDidVisibilityChange.fire({ this.api._onDidVisibilityChange.fire({
isVisible: isPanelVisible, isVisible: isPanelVisible,
}); });
}
this.view.updateParentGroup(
this._group,
this._group.model.isPanelActive(this)
);
} }
public layout(width: number, height: number): void { public layout(width: number, height: number): void {

View File

@ -25,9 +25,6 @@ export class DockviewPanelModel implements IDockviewPanelModel {
private readonly _content: IContentRenderer; private readonly _content: IContentRenderer;
private readonly _tab: ITabRenderer; private readonly _tab: ITabRenderer;
private _group: DockviewGroupPanel | null = null;
private _isPanelVisible: boolean | null = null;
get content(): IContentRenderer { get content(): IContentRenderer {
return this._content; return this._content;
} }
@ -52,28 +49,10 @@ export class DockviewPanelModel implements IDockviewPanelModel {
} }
updateParentGroup( updateParentGroup(
group: DockviewGroupPanel, _group: DockviewGroupPanel,
isPanelVisible: boolean _isPanelVisible: boolean
): void { ): void {
if (group !== this._group) { // noop
this._group = group;
if (this._content.onGroupChange) {
this._content.onGroupChange(group);
}
if (this._tab.onGroupChange) {
this._tab.onGroupChange(group);
}
}
if (isPanelVisible !== this._isPanelVisible) {
this._isPanelVisible = isPanelVisible;
if (this._content.onPanelVisibleChange) {
this._content.onPanelVisibleChange(isPanelVisible);
}
if (this._tab.onPanelVisibleChange) {
this._tab.onPanelVisibleChange(isPanelVisible);
}
}
} }
layout(width: number, height: number): void { layout(width: number, height: number): void {

View File

@ -47,8 +47,6 @@ export interface ITabRenderer
> { > {
readonly element: HTMLElement; readonly element: HTMLElement;
init(parameters: GroupPanelPartInitParameters): void; init(parameters: GroupPanelPartInitParameters): void;
onGroupChange?(group: DockviewGroupPanel): void;
onPanelVisibleChange?(isPanelVisible: boolean): void;
} }
export interface IContentRenderer export interface IContentRenderer
@ -60,8 +58,6 @@ export interface IContentRenderer
readonly onDidFocus?: Event<void>; readonly onDidFocus?: Event<void>;
readonly onDidBlur?: Event<void>; readonly onDidBlur?: Event<void>;
init(parameters: GroupPanelContentPartInitParameters): void; init(parameters: GroupPanelContentPartInitParameters): void;
onGroupChange?(group: DockviewGroupPanel): void;
onPanelVisibleChange?(isPanelVisible: boolean): void;
} }
// watermark component // watermark component

View File

@ -93,6 +93,10 @@ export class Emitter<T> implements IDisposable {
Emitter.ENABLE_TRACKING = isEnabled; Emitter.ENABLE_TRACKING = isEnabled;
} }
get value(): T | undefined {
return this._last;
}
constructor(private readonly options?: EmitterOptions) {} constructor(private readonly options?: EmitterOptions) {}
get event(): Event<T> { get event(): Event<T> {

View File

@ -54,10 +54,6 @@ export interface IBaseGrid<T extends IGridPanelView> {
readonly activeGroup: T | undefined; readonly activeGroup: T | undefined;
readonly size: number; readonly size: number;
readonly groups: T[]; readonly groups: T[];
readonly onDidLayoutChange: Event<void>;
readonly onDidRemoveGroup: Event<T>;
readonly onDidAddGroup: Event<T>;
readonly onDidActiveGroupChange: Event<T | undefined>;
getPanel(id: string): T | undefined; getPanel(id: string): T | undefined;
toJSON(): object; toJSON(): object;
fromJSON(data: any): void; fromJSON(data: any): void;
@ -70,6 +66,7 @@ export interface IBaseGrid<T extends IGridPanelView> {
exitMaximizedGroup(): void; exitMaximizedGroup(): void;
hasMaximizedGroup(): boolean; hasMaximizedGroup(): boolean;
readonly onDidMaxmizedGroupChange: Event<void>; readonly onDidMaxmizedGroupChange: Event<void>;
readonly onDidLayoutChange: Event<void>;
} }
export abstract class BaseGrid<T extends IGridPanelView> export abstract class BaseGrid<T extends IGridPanelView>
@ -85,15 +82,15 @@ export abstract class BaseGrid<T extends IGridPanelView>
private _onDidLayoutChange = new Emitter<void>(); private _onDidLayoutChange = new Emitter<void>();
readonly onDidLayoutChange = this._onDidLayoutChange.event; readonly onDidLayoutChange = this._onDidLayoutChange.event;
protected readonly _onDidRemoveGroup = new Emitter<T>(); private readonly _onDidRemove = new Emitter<T>();
readonly onDidRemoveGroup: Event<T> = this._onDidRemoveGroup.event; readonly onDidRemove: Event<T> = this._onDidRemove.event;
protected readonly _onDidAddGroup = new Emitter<T>(); private readonly _onDidAdd = new Emitter<T>();
readonly onDidAddGroup: Event<T> = this._onDidAddGroup.event; readonly onDidAdd: Event<T> = this._onDidAdd.event;
private readonly _onDidActiveGroupChange = new Emitter<T | undefined>(); private readonly _onDidActiveChange = new Emitter<T | undefined>();
readonly onDidActiveGroupChange: Event<T | undefined> = readonly onDidActiveChange: Event<T | undefined> =
this._onDidActiveGroupChange.event; this._onDidActiveChange.event;
protected readonly _bufferOnDidLayoutChange = new TickDelayedEvent(); protected readonly _bufferOnDidLayoutChange = new TickDelayedEvent();
@ -167,9 +164,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
this._bufferOnDidLayoutChange.fire(); this._bufferOnDidLayoutChange.fire();
}), }),
Event.any( Event.any(
this.onDidAddGroup, this.onDidAdd,
this.onDidRemoveGroup, this.onDidRemove,
this.onDidActiveGroupChange this.onDidActiveChange
)(() => { )(() => {
this._bufferOnDidLayoutChange.fire(); this._bufferOnDidLayoutChange.fire();
}), }),
@ -222,9 +219,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
): void { ): void {
this.gridview.addView(group, size ?? Sizing.Distribute, location); this.gridview.addView(group, size ?? Sizing.Distribute, location);
this._onDidAddGroup.fire(group); this._onDidAdd.fire(group);
this.doSetGroupActive(group); // this.doSetGroupActive(group);
} }
protected doRemoveGroup( protected doRemoveGroup(
@ -243,10 +240,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
item.disposable.dispose(); item.disposable.dispose();
item.value.dispose(); item.value.dispose();
this._groups.delete(group.id); this._groups.delete(group.id);
this._onDidRemove.fire(group);
} }
this._onDidRemoveGroup.fire(group);
if (!options?.skipActive && this._activeGroup === group) { if (!options?.skipActive && this._activeGroup === group) {
const groups = Array.from(this._groups.values()); const groups = Array.from(this._groups.values());
@ -276,7 +272,7 @@ export abstract class BaseGrid<T extends IGridPanelView>
this._activeGroup = group; this._activeGroup = group;
this._onDidActiveGroupChange.fire(group); this._onDidActiveChange.fire(group);
} }
public removeGroup(group: T): void { public removeGroup(group: T): void {
@ -330,9 +326,9 @@ export abstract class BaseGrid<T extends IGridPanelView>
} }
public dispose(): void { public dispose(): void {
this._onDidActiveGroupChange.dispose(); this._onDidActiveChange.dispose();
this._onDidAddGroup.dispose(); this._onDidAdd.dispose();
this._onDidRemoveGroup.dispose(); this._onDidRemove.dispose();
this._onDidLayoutChange.dispose(); this._onDidLayoutChange.dispose();
for (const group of this.groups) { for (const group of this.groups) {

View File

@ -71,6 +71,9 @@ export interface IGridviewComponent extends IBaseGrid<GridviewPanel> {
): void; ): void;
setVisible(panel: IGridviewPanel, visible: boolean): void; setVisible(panel: IGridviewPanel, visible: boolean): void;
setActive(panel: IGridviewPanel): void; setActive(panel: IGridviewPanel): void;
readonly onDidRemoveGroup: Event<GridviewPanel>;
readonly onDidAddGroup: Event<GridviewPanel>;
readonly onDidActiveGroupChange: Event<GridviewPanel | undefined>;
} }
export class GridviewComponent export class GridviewComponent
@ -83,6 +86,19 @@ export class GridviewComponent
private readonly _onDidLayoutfromJSON = new Emitter<void>(); private readonly _onDidLayoutfromJSON = new Emitter<void>();
readonly onDidLayoutFromJSON: Event<void> = this._onDidLayoutfromJSON.event; readonly onDidLayoutFromJSON: Event<void> = this._onDidLayoutfromJSON.event;
private readonly _onDidRemoveGroup = new Emitter<GridviewPanel>();
readonly onDidRemoveGroup: Event<GridviewPanel> =
this._onDidRemoveGroup.event;
protected readonly _onDidAddGroup = new Emitter<GridviewPanel>();
readonly onDidAddGroup: Event<GridviewPanel> = this._onDidAddGroup.event;
private readonly _onDidActiveGroupChange = new Emitter<
GridviewPanel | undefined
>();
readonly onDidActiveGroupChange: Event<GridviewPanel | undefined> =
this._onDidActiveGroupChange.event;
get orientation(): Orientation { get orientation(): Orientation {
return this.gridview.orientation; return this.gridview.orientation;
} }
@ -114,6 +130,21 @@ export class GridviewComponent
this._options = options; this._options = options;
this.addDisposables(
this._onDidAddGroup,
this._onDidRemoveGroup,
this._onDidActiveGroupChange,
this.onDidAdd((event) => {
this._onDidAddGroup.fire(event);
}),
this.onDidRemove((event) => {
this._onDidRemoveGroup.fire(event);
}),
this.onDidActiveChange((event) => {
this._onDidActiveGroupChange.fire(event);
})
);
if (!this.options.components) { if (!this.options.components) {
this.options.components = {}; this.options.components = {};
} }
@ -364,6 +395,7 @@ export class GridviewComponent
this.registerPanel(view); this.registerPanel(view);
this.doAddGroup(view, relativeLocation, options.size); this.doAddGroup(view, relativeLocation, options.size);
this.doSetGroupActive(view);
return view; return view;
} }

View File

@ -33,6 +33,8 @@ import DockviewMaximizeGroup from '@site/sandboxes/maximizegroup-dockview/src/ap
import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app'; import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app';
import DockviewScrollbars from '@site/sandboxes/scrollbars-dockview/src/app'; import DockviewScrollbars from '@site/sandboxes/scrollbars-dockview/src/app';
import DockviewFocus from '@site/sandboxes/focus-dockview/src/app';
import { DocRef } from '@site/src/components/ui/reference/docRef'; import { DocRef } from '@site/src/components/ui/reference/docRef';
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app'; import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
@ -42,6 +44,11 @@ import { attach as attachNativeDockview } from '@site/sandboxes/javascript/fullw
# Dockview # Dockview
<MultiFrameworkContainer
sandboxId="focus-dockview"
react={DockviewFocus}
/>
## Introduction ## Introduction
Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels. Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels.

View File

@ -5,14 +5,169 @@ import {
IDockviewPanelHeaderProps, IDockviewPanelHeaderProps,
IDockviewPanelProps, IDockviewPanelProps,
IDockviewHeaderActionsProps, IDockviewHeaderActionsProps,
DockviewPanelApi,
DockviewPanelRenderer,
DockviewGroupLocation,
DockviewApi,
} from 'dockview'; } from 'dockview';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import './app.scss'; import './app.scss';
interface PanelApiMetadata {
isActive: {
value: boolean;
count: number;
};
isVisible: {
value: boolean;
count: number;
};
isHidden: {
value: boolean;
count: number;
};
renderer: {
value: DockviewPanelRenderer;
count: number;
};
isGroupActive: {
value: boolean;
count: number;
};
groupChanged: {
count: number;
};
location: {
value: DockviewGroupLocation;
count: number;
};
didFocus: {
count: number;
};
dimensions: {
count: number;
height: number;
width: number;
};
}
function usePanelApiMetadata(api: DockviewPanelApi): PanelApiMetadata {
const [state, setState] = React.useState<PanelApiMetadata>({
isActive: { value: api.isActive, count: 0 },
isVisible: { value: api.isVisible, count: 0 },
isHidden: { value: api.isHidden, count: 0 },
renderer: { value: api.renderer, count: 0 },
isGroupActive: { value: api.isGroupActive, count: 0 },
groupChanged: { count: 0 },
location: { value: api.location, count: 0 },
didFocus: { count: 0 },
dimensions: { count: 0, height: api.height, width: api.width },
});
React.useEffect(() => {
const d1 = api.onDidActiveChange((event) => {
setState((_) => ({
..._,
isActive: {
value: event.isActive,
count: _.isActive.count + 1,
},
}));
});
const d2 = api.onDidActiveGroupChange((event) => {
setState((_) => ({
..._,
isGroupActive: {
value: event.isActive,
count: _.isGroupActive.count + 1,
},
}));
});
const d3 = api.onDidDimensionsChange((event) => {
setState((_) => ({
..._,
dimensions: {
count: _.dimensions.count + 1,
height: event.height,
width: event.width,
},
}));
});
const d4 = api.onDidFocusChange((event) => {
setState((_) => ({
..._,
didFocus: {
count: _.didFocus.count + 1,
},
}));
});
const d5 = api.onDidGroupChange((event) => {
setState((_) => ({
..._,
groupChanged: {
count: _.groupChanged.count + 1,
},
}));
});
const d6 = api.onDidHiddenChange((event) => {
setState((_) => ({
..._,
isHidden: {
value: event.isHidden,
count: _.isHidden.count + 1,
},
}));
});
const d7 = api.onDidLocationChange((event) => {
setState((_) => ({
..._,
location: {
value: event.location,
count: _.location.count + 1,
},
}));
});
const d8 = api.onDidRendererChange((event) => {
setState((_) => ({
..._,
renderer: {
value: event.renderer,
count: _.renderer.count + 1,
},
}));
});
const d9 = api.onDidVisibilityChange((event) => {
setState((_) => ({
..._,
isVisible: {
value: event.isVisible,
count: _.isVisible.count + 1,
},
}));
});
return () => {
d1.dispose();
d2.dispose();
d3.dispose();
d4.dispose();
d5.dispose();
d6.dispose();
d7.dispose();
d8.dispose();
d9.dispose();
};
}, [api]);
return state;
}
const components = { const components = {
default: (props: IDockviewPanelProps<{ title: string }>) => { default: (props: IDockviewPanelProps) => {
const metadata = usePanelApiMetadata(props.api);
return ( return (
<div <div
style={{ style={{
@ -22,6 +177,9 @@ const components = {
position: 'relative', position: 'relative',
}} }}
> >
<pre style={{ fontSize: '11px' }}>
{JSON.stringify(metadata, null, 4)}
</pre>
<span <span
style={{ style={{
position: 'absolute', position: 'absolute',
@ -198,7 +356,62 @@ const PrefixHeaderControls = (props: IDockviewHeaderActionsProps) => {
}; };
const DockviewDemo = (props: { theme?: string }) => { const DockviewDemo = (props: { theme?: string }) => {
const [logLines, setLogLines] = React.useState<any[]>([]);
const [panels, setPanels] = React.useState<string[]>([]);
const [groups, setGroups] = React.useState<string[]>([]);
const [api, setApi] = React.useState<DockviewApi>();
const [activePanel, setActivePanel] = React.useState<string>();
const [activeGroup, setActiveGroup] = React.useState<string>();
const onReady = (event: DockviewReadyEvent) => { const onReady = (event: DockviewReadyEvent) => {
setApi(event.api);
event.api.onDidAddPanel((event) => {
setPanels((_) => [..._, event.id]);
setLogLines((line) => [`Panel Added ${event.id}`, ...line]);
});
event.api.onDidActivePanelChange((event) => {
setActivePanel(event?.id);
setLogLines((line) => [`Panel Activated ${event?.id}`, ...line]);
});
event.api.onDidRemovePanel((event) => {
setPanels((_) => {
const next = [..._];
next.splice(
next.findIndex((x) => x === event.id),
1
);
return next;
});
setLogLines((line) => [`Panel Removed ${event.id}`, ...line]);
});
event.api.onDidAddGroup((event) => {
setGroups((_) => [..._, event.id]);
setLogLines((line) => [`Group Added ${event.id}`, ...line]);
});
event.api.onDidRemoveGroup((event) => {
setGroups((_) => {
const next = [..._];
next.splice(
next.findIndex((x) => x === event.id),
1
);
return next;
});
setLogLines((line) => [`Group Removed ${event.id}`, ...line]);
});
event.api.onDidActiveGroupChange((event) => {
setActiveGroup(event?.id);
setLogLines((line) => [`Group Activated ${event?.id}`, ...line]);
});
const panel1 = event.api.addPanel({ const panel1 = event.api.addPanel({
id: 'panel_1', id: 'panel_1',
component: 'default', component: 'default',
@ -257,7 +470,141 @@ const DockviewDemo = (props: { theme?: string }) => {
panel1.api.setActive(); panel1.api.setActive();
}; };
const onAddPanel = () => {
api?.addPanel({
id: `id_${Date.now().toString()}`,
component: 'default',
title: `Tab ${counter++}`,
});
};
const onAddGroup = () => {
api?.addGroup();
};
return ( return (
<div
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
>
<div>
<div
style={{
display: 'flex',
height: '25px',
padding: '2px 0px',
}}
>
<button onClick={onAddPanel}>Add Panel</button>
<button onClick={onAddGroup}>Add Group</button>
</div>
<div
style={{
display: 'flex',
height: '25px',
padding: '2px 0px',
}}
>
{panels.map((x) => {
const onClick = () => {
api?.getPanel(x)?.focus();
};
return (
<>
<button
onClick={onClick}
style={{
minWidth: '50px',
border: 'none',
margin: '0px 2px',
padding: '0px 2px',
backgroundColor:
activePanel === x
? 'blueviolet'
: 'dodgerblue',
borderRadius: '2px',
}}
>
{x}
</button>
<button
onClick={() => {
const panel = api?.getPanel(x);
if (panel) {
api?.addFloatingGroup(panel);
}
}}
>
float
</button>
<button
onClick={() => {
const panel = api?.getPanel(x);
if (panel) {
api?.addPopoutGroup(panel);
}
}}
>
pop
</button>
</>
);
})}
</div>
<div
style={{
display: 'flex',
height: '25px',
padding: '2px 0px',
}}
>
{groups.map((x) => {
const onClick = () => {
api?.getGroup(x)?.focus();
};
return (
<>
<button
onClick={onClick}
style={{
minWidth: '50px',
border: 'none',
margin: '0px 2px',
padding: '0px 2px',
backgroundColor:
activeGroup === x
? 'blueviolet'
: 'dodgerblue',
borderRadius: '2px',
}}
>
{x}
</button>
<button
onClick={() => {
const panel = api?.getGroup(x);
if (panel) {
api?.addFloatingGroup(panel);
}
}}
>
float
</button>
<button
onClick={() => {
const panel = api?.getGroup(x);
if (panel) {
api?.addPopoutGroup(panel);
}
}}
>
pop
</button>
</>
);
})}
</div>
</div>
<div style={{ flexGrow: 1 }}>
<DockviewReact <DockviewReact
components={components} components={components}
defaultTabComponent={headerComponents.default} defaultTabComponent={headerComponents.default}
@ -267,6 +614,35 @@ const DockviewDemo = (props: { theme?: string }) => {
onReady={onReady} onReady={onReady}
className={props.theme || 'dockview-theme-abyss'} className={props.theme || 'dockview-theme-abyss'}
/> />
</div>
<div
style={{
height: '200px',
backgroundColor: 'black',
color: 'white',
overflow: 'auto',
}}
>
{logLines.map((line, i) => {
return (
<div key={i}>
<span
style={{
display: 'inline-block',
width: '30px',
color: 'gray',
borderRight: '1px solid gray',
marginRight: '4px',
}}
>
{logLines.length - i}
</span>
<span>{line}</span>
</div>
);
})}
</div>
</div>
); );
}; };

View File

@ -0,0 +1,32 @@
{
"name": "focus-dockview",
"description": "",
"keywords": [
"dockview"
],
"version": "1.0.0",
"main": "src/index.tsx",
"dependencies": {
"dockview": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"typescript": "^4.9.5",
"react-scripts": "*"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
}

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,125 @@
import {
DockviewApi,
DockviewReact,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview';
import * as React from 'react';
const components = {
default: (props: IDockviewPanelProps) => {
React.useEffect(() => {
const d1 = props.api.onWillFocus((event) => {
console.log('willFocus');
});
const d2 = props.api.onDidActiveChange((event) => {
console.log(props.api.title, event, 'active');
});
const d3 = props.api.onDidActiveGroupChange((event) => {
console.log(
props.api.title,
props.api.group.api.isActive,
'active-group'
);
});
const d4 = props.api.onDidGroupChange((event) => {
console.log(
props.api.title,
props.api.group.id,
'group-change'
);
});
return () => {
d1.dispose();
d2.dispose();
d3.dispose();
};
}, [props.api]);
return (
<div style={{ padding: '20px', color: 'white' }}>
{props.api.title}
</div>
);
},
};
export const App: React.FC = (props: { theme?: string }) => {
const [api, setApi] = React.useState<DockviewApi>();
const onReady = (event: DockviewReadyEvent) => {
setApi(event.api);
event.api.addPanel({
id: 'panel_1',
title: 'Panel 1',
component: 'default',
});
event.api.addPanel({
id: 'panel_2',
title: 'Panel 2',
component: 'default',
});
// event.api.onDidAddPanel((event) => {
// console.log('add panel', event);
// });
// event.api.onDidActivePanelChange((event) => {
// console.log('active panel', event);
// });
// event.api.onDidRemovePanel((event) => {
// console.log('remove panel', event);
// });
};
return (
<div
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
>
<div>
<button
onClick={() => {
api?.getPanel('panel_1')?.focus();
}}
>
{'Focus Panel 1'}
</button>
<button
onClick={() => {
api?.getPanel('panel_2')?.focus();
}}
>
{'Focus Panel 2'}
</button>
<button
onClick={() => {
api?.getPanel('panel_1')?.api.setActive();
}}
>
{'Active Panel 1'}
</button>
<button
onClick={() => {
api?.getPanel('panel_2')?.api.setActive();
}}
>
{'Active Panel 2'}
</button>
</div>
<div style={{ flexGrow: 1 }}>
<DockviewReact
components={components}
onReady={onReady}
className={props.theme || 'dockview-theme-abyss'}
/>
</div>
</div>
);
};
export default App;

View File

@ -0,0 +1,20 @@
import { StrictMode } from 'react';
import * as ReactDOMClient from 'react-dom/client';
import './styles.css';
import 'dockview/dist/styles/dockview.css';
import App from './app';
const rootElement = document.getElementById('root');
if (rootElement) {
const root = ReactDOMClient.createRoot(rootElement);
root.render(
<StrictMode>
<div className="app">
<App />
</div>
</StrictMode>
);
}

View File

@ -0,0 +1,16 @@
body {
margin: 0px;
color: white;
font-family: sans-serif;
text-align: center;
}
#root {
height: 100vh;
width: 100vw;
}
.app {
height: 100%;
}

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"outDir": "build/dist",
"module": "esnext",
"target": "es5",
"lib": ["es6", "dom"],
"sourceMap": true,
"allowJs": true,
"jsx": "react-jsx",
"moduleResolution": "node",
"rootDir": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true
}
}