Merge pull request #177 from mathuo/176-dnd-to-edge-of-dockview

176 dnd to edge of dockview
This commit is contained in:
mathuo 2023-02-15 22:41:31 +07:00 committed by GitHub
commit 213d3ed235
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 2095 additions and 994 deletions

View File

@ -10,7 +10,11 @@ describe('groupPanelApi', () => {
title: 'test_title', title: 'test_title',
}; };
const accessor: Partial<DockviewComponent> = {}; const accessor: Partial<DockviewComponent> = {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
const groupViewPanel = new GroupPanel( const groupViewPanel = new GroupPanel(
<DockviewComponent>accessor, <DockviewComponent>accessor,
'', '',
@ -44,7 +48,11 @@ describe('groupPanelApi', () => {
id: 'test_id', id: 'test_id',
}; };
const accessor: Partial<DockviewComponent> = {}; const accessor: Partial<DockviewComponent> = {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
const groupViewPanel = new GroupPanel( const groupViewPanel = new GroupPanel(
<DockviewComponent>accessor, <DockviewComponent>accessor,
'', '',

View File

@ -1,16 +1,22 @@
import { Droptarget, Position } from '../../dnd/droptarget'; import {
calculateQuadrantAsPercentage,
calculateQuadrantAsPixels,
directionToPosition,
Droptarget,
Position,
} from '../../dnd/droptarget';
import { fireEvent } from '@testing-library/dom'; import { fireEvent } from '@testing-library/dom';
function createOffsetDragOverEvent(params: { function createOffsetDragOverEvent(params: {
offsetX: number; clientX: number;
offsetY: number; clientY: number;
}): Event { }): Event {
const event = new Event('dragover', { const event = new Event('dragover', {
bubbles: true, bubbles: true,
cancelable: true, cancelable: true,
}); });
Object.defineProperty(event, 'offsetX', { get: () => params.offsetX }); Object.defineProperty(event, 'clientX', { get: () => params.clientX });
Object.defineProperty(event, 'offsetY', { get: () => params.offsetY }); Object.defineProperty(event, 'clientY', { get: () => params.clientY });
return event; return event;
} }
@ -27,12 +33,23 @@ describe('droptarget', () => {
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200); jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200);
}); });
test('directionToPosition', () => {
expect(directionToPosition('above')).toBe('top');
expect(directionToPosition('below')).toBe('bottom');
expect(directionToPosition('left')).toBe('left');
expect(directionToPosition('right')).toBe('right');
expect(directionToPosition('within')).toBe('center');
expect(() => directionToPosition('bad_input' as any)).toThrow(
"invalid direction 'bad_input'"
);
});
test('non-directional', () => { test('non-directional', () => {
let position: Position | undefined = undefined; let position: Position | undefined = undefined;
droptarget = new Droptarget(element, { droptarget = new Droptarget(element, {
canDisplayOverlay: () => true, canDisplayOverlay: () => true,
validOverlays: 'none', acceptedTargetZones: ['center'],
}); });
droptarget.onDrop((event) => { droptarget.onDrop((event) => {
@ -46,7 +63,7 @@ describe('droptarget', () => {
'.drop-target-dropzone' '.drop-target-dropzone'
) as HTMLElement; ) as HTMLElement;
fireEvent.drop(target); fireEvent.drop(target);
expect(position).toBe(Position.Center); expect(position).toBe('center');
}); });
test('drop', () => { test('drop', () => {
@ -54,7 +71,7 @@ describe('droptarget', () => {
droptarget = new Droptarget(element, { droptarget = new Droptarget(element, {
canDisplayOverlay: () => true, canDisplayOverlay: () => true,
validOverlays: 'all', acceptedTargetZones: ['top', 'left', 'right', 'bottom', 'center'],
}); });
droptarget.onDrop((event) => { droptarget.onDrop((event) => {
@ -73,18 +90,21 @@ describe('droptarget', () => {
fireEvent( fireEvent(
target, target,
createOffsetDragOverEvent({ offsetX: 19, offsetY: 0 }) createOffsetDragOverEvent({
clientX: 19,
clientY: 0,
})
); );
expect(position).toBeUndefined(); expect(position).toBeUndefined();
fireEvent.drop(target); fireEvent.drop(target);
expect(position).toBe(Position.Left); expect(position).toBe('left');
}); });
test('default', () => { test('default', () => {
droptarget = new Droptarget(element, { droptarget = new Droptarget(element, {
canDisplayOverlay: () => true, canDisplayOverlay: () => true,
validOverlays: 'all', acceptedTargetZones: ['top', 'left', 'right', 'bottom', 'center'],
}); });
expect(droptarget.state).toBeUndefined(); expect(droptarget.state).toBeUndefined();
@ -106,57 +126,204 @@ describe('droptarget', () => {
fireEvent( fireEvent(
target, target,
createOffsetDragOverEvent({ offsetX: 19, offsetY: 0 }) createOffsetDragOverEvent({ clientX: 19, clientY: 0 })
); );
viewQuery = element.querySelectorAll( viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection.left' '.drop-target > .drop-target-dropzone > .drop-target-selection'
); );
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Left); expect(droptarget.state).toBe('left');
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateX(-25%) scaleX(0.5)');
fireEvent( fireEvent(
target, target,
createOffsetDragOverEvent({ offsetX: 40, offsetY: 19 }) createOffsetDragOverEvent({ clientX: 40, clientY: 19 })
); );
viewQuery = element.querySelectorAll( viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection.top' '.drop-target > .drop-target-dropzone > .drop-target-selection'
); );
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Top); expect(droptarget.state).toBe('top');
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateY(-25%) scaleY(0.5)');
fireEvent( fireEvent(
target, target,
createOffsetDragOverEvent({ offsetX: 160, offsetY: 81 }) createOffsetDragOverEvent({ clientX: 160, clientY: 81 })
); );
viewQuery = element.querySelectorAll( viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection.bottom' '.drop-target > .drop-target-dropzone > .drop-target-selection'
); );
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Bottom); expect(droptarget.state).toBe('bottom');
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateY(25%) scaleY(0.5)');
fireEvent( fireEvent(
target, target,
createOffsetDragOverEvent({ offsetX: 161, offsetY: 0 }) createOffsetDragOverEvent({ clientX: 161, clientY: 0 })
); );
viewQuery = element.querySelectorAll( viewQuery = element.querySelectorAll(
'.drop-target > .drop-target-dropzone > .drop-target-selection.right' '.drop-target > .drop-target-dropzone > .drop-target-selection'
); );
expect(viewQuery.length).toBe(1); expect(viewQuery.length).toBe(1);
expect(droptarget.state).toBe(Position.Right); expect(droptarget.state).toBe('right');
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('translateX(25%) scaleX(0.5)');
fireEvent( fireEvent(
target, target,
createOffsetDragOverEvent({ offsetX: 100, offsetY: 50 }) createOffsetDragOverEvent({ clientX: 100, clientY: 50 })
); );
expect(droptarget.state).toBe(Position.Center); expect(droptarget.state).toBe('center');
expect(
(
element
.getElementsByClassName('drop-target-selection')
.item(0) as HTMLDivElement
).style.transform
).toBe('');
fireEvent.dragLeave(target); fireEvent.dragLeave(target);
expect(droptarget.state).toBeUndefined(); expect(droptarget.state).toBe('center');
viewQuery = element.querySelectorAll('.drop-target'); viewQuery = element.querySelectorAll('.drop-target');
expect(viewQuery.length).toBe(0); expect(viewQuery.length).toBe(0);
}); });
describe('calculateQuadrantAsPercentage', () => {
test('variety of cases', () => {
const inputs: Array<{
directions: Position[];
x: number;
y: number;
result: Position | null;
}> = [
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
{
directions: ['left', 'right'],
x: 81,
y: 50,
result: 'right',
},
{
directions: ['top', 'bottom'],
x: 50,
y: 19,
result: 'top',
},
{
directions: ['top', 'bottom'],
x: 50,
y: 81,
result: 'bottom',
},
{
directions: ['left', 'right', 'top', 'bottom', 'center'],
x: 50,
y: 50,
result: 'center',
},
{
directions: ['left', 'right', 'top', 'bottom'],
x: 50,
y: 50,
result: null,
},
];
for (const input of inputs) {
expect(
calculateQuadrantAsPercentage(
new Set(input.directions),
input.x,
input.y,
100,
100,
20
)
).toBe(input.result);
}
});
});
describe('calculateQuadrantAsPixels', () => {
test('variety of cases', () => {
const inputs: Array<{
directions: Position[];
x: number;
y: number;
result: Position | null;
}> = [
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
{
directions: ['left', 'right'],
x: 81,
y: 50,
result: 'right',
},
{
directions: ['top', 'bottom'],
x: 50,
y: 19,
result: 'top',
},
{
directions: ['top', 'bottom'],
x: 50,
y: 81,
result: 'bottom',
},
{
directions: ['left', 'right', 'top', 'bottom', 'center'],
x: 50,
y: 50,
result: 'center',
},
{
directions: ['left', 'right', 'top', 'bottom'],
x: 50,
y: 50,
result: null,
},
];
for (const input of inputs) {
expect(
calculateQuadrantAsPixels(
new Set(input.directions),
input.x,
input.y,
100,
100,
20
)
).toBe(input.result);
}
});
});
}); });

View File

@ -32,7 +32,7 @@ class PanelContentPartTest implements IContentRenderer {
isDisposed: boolean = false; isDisposed: boolean = false;
constructor(public readonly id: string, component: string) { constructor(public readonly id: string, public readonly component: string) {
this.element.classList.add(`testpanel-${id}`); this.element.classList.add(`testpanel-${id}`);
} }
@ -53,7 +53,7 @@ class PanelContentPartTest implements IContentRenderer {
} }
toJSON(): object { toJSON(): object {
return { id: this.id }; return { id: this.component };
} }
focus(): void { focus(): void {
@ -253,9 +253,9 @@ 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', Position.Right); dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
const group2 = panel1!.group; const group2 = panel1!.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center); dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
expect(dockview.activeGroup).toBe(group2); expect(dockview.activeGroup).toBe(group2);
expect(dockview.activeGroup!.model.activePanel).toBe(panel3); expect(dockview.activeGroup!.model.activePanel).toBe(panel3);
@ -302,12 +302,12 @@ describe('dockviewComponent', () => {
component: 'default', component: 'default',
}); });
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', Position.Right); dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
const group2 = panel1.group; const group2 = panel1.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center); dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
expect(dockview.size).toBe(2); expect(dockview.size).toBe(2);
expect(dockview.totalPanels).toBe(4); expect(dockview.totalPanels).toBe(4);
@ -345,10 +345,10 @@ describe('dockviewComponent', () => {
component: 'default', component: 'default',
}); });
const panel1 = dockview.getGroupPanel('panel1'); const panel1 = dockview.getGroupPanel('panel1')!;
const panel2 = dockview.getGroupPanel('panel2'); const panel2 = dockview.getGroupPanel('panel2')!;
const panel3 = dockview.getGroupPanel('panel3'); const panel3 = dockview.getGroupPanel('panel3')!;
const panel4 = dockview.getGroupPanel('panel4'); const panel4 = dockview.getGroupPanel('panel4')!;
expect(panel1.api.isActive).toBeFalsy(); expect(panel1.api.isActive).toBeFalsy();
expect(panel2.api.isActive).toBeFalsy(); expect(panel2.api.isActive).toBeFalsy();
@ -370,9 +370,9 @@ 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', Position.Right); dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right');
const group2 = panel1.group; const group2 = panel1.group;
dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center); dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center');
expect(dockview.size).toBe(2); expect(dockview.size).toBe(2);
expect(panel1.group).toBe(panel3.group); expect(panel1.group).toBe(panel3.group);
@ -425,9 +425,8 @@ describe('dockviewComponent', () => {
expect(dockview.size).toBe(1); expect(dockview.size).toBe(1);
expect(dockview.totalPanels).toBe(2); expect(dockview.totalPanels).toBe(2);
const panel1 = dockview.getGroupPanel('panel1'); const panel1 = dockview.getGroupPanel('panel1')!;
const panel2 = dockview.getGroupPanel('panel2'); const panel2 = dockview.getGroupPanel('panel2')!;
expect(panel1.group).toBe(panel2.group); expect(panel1.group).toBe(panel2.group);
const group = panel1.group; const group = panel1.group;
@ -440,7 +439,7 @@ 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', Position.Right); dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
expect(dockview.size).toBe(2); expect(dockview.size).toBe(2);
expect(dockview.totalPanels).toBe(2); expect(dockview.totalPanels).toBe(2);
@ -489,8 +488,8 @@ 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', Position.Right); dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right');
viewQuery = container.querySelectorAll( viewQuery = container.querySelectorAll(
'.branch-node > .split-view-container > .view-container > .view' '.branch-node > .split-view-container > .view-container > .view'
@ -975,7 +974,7 @@ describe('dockviewComponent', () => {
panel2.group!, panel2.group!,
panel5.group!.id, panel5.group!.id,
panel5.id, panel5.id,
Position.Center 'center'
); );
expect(events).toEqual([ expect(events).toEqual([
{ type: 'REMOVE_PANEL', panel: panel5 }, { type: 'REMOVE_PANEL', panel: panel5 },
@ -994,7 +993,7 @@ describe('dockviewComponent', () => {
panel2.group!, panel2.group!,
panel4.group!.id, panel4.group!.id,
panel4.id, panel4.id,
Position.Center 'center'
); );
expect(events).toEqual([ expect(events).toEqual([
@ -1314,7 +1313,7 @@ describe('dockviewComponent', () => {
panel1.group, panel1.group,
panel2.group.id, panel2.group.id,
'panel2', 'panel2',
Position.Left 'left'
); );
expect(panel1Spy).not.toHaveBeenCalled(); expect(panel1Spy).not.toHaveBeenCalled();
@ -1355,7 +1354,7 @@ describe('dockviewComponent', () => {
panel1.group, panel1.group,
panel2.group.id, panel2.group.id,
'panel2', 'panel2',
Position.Center 'center'
); );
expect(panel1Spy).not.toHaveBeenCalled(); expect(panel1Spy).not.toHaveBeenCalled();
@ -1394,7 +1393,7 @@ describe('dockviewComponent', () => {
panel1.group, panel1.group,
panel1.group.id, panel1.group.id,
'panel1', 'panel1',
Position.Center, 'center',
0 0
); );
@ -1515,6 +1514,53 @@ describe('dockviewComponent', () => {
expect(panel2Spy).toBeCalledTimes(1); expect(panel2Spy).toBeCalledTimes(1);
}); });
test('move entire group into another group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
components: { default: PanelContentPartTest },
});
dockview.layout(500, 1000);
const panel1 = dockview.addPanel({
id: 'panel1',
component: 'default',
tabComponent: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel2',
component: 'default',
tabComponent: 'default',
position: {
referencePanel: panel1,
},
});
const panel3 = dockview.addPanel({
id: 'panel3',
component: 'default',
tabComponent: 'default',
position: {
referencePanel: panel1,
direction: 'right',
},
});
const panel1Spy = jest.spyOn(panel1.group, 'dispose');
expect(dockview.groups.length).toBe(2);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
undefined,
'center'
);
expect(dockview.groups.length).toBe(1);
expect(panel1Spy).toBeCalledTimes(1);
});
test('fromJSON events should still fire', () => { test('fromJSON events should still fire', () => {
jest.useFakeTimers(); jest.useFakeTimers();
@ -1635,8 +1681,6 @@ describe('dockviewComponent', () => {
jest.runAllTimers(); jest.runAllTimers();
console.log(activePanel.map((_) => _?.id).join(' '));
expect(addGroup.length).toBe(4); expect(addGroup.length).toBe(4);
expect(removeGroup.length).toBe(0); expect(removeGroup.length).toBe(0);
expect(activeGroup.length).toBe(1); expect(activeGroup.length).toBe(1);
@ -1973,4 +2017,379 @@ describe('dockviewComponent', () => {
// load a layout with a default tab identifier when react default is present // load a layout with a default tab identifier when react default is present
// load a layout with invialid panel identifier // load a layout with invialid panel identifier
test('orthogonal realigment #1', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.deserializer = new ReactPanelDeserialzier(dockview);
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
dockview.fromJSON({
activeGroup: 'group-1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 500,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.VERTICAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
},
});
expect(dockview.orientation).toBe(Orientation.VERTICAL);
dockview.addPanel({
id: 'panel2',
component: 'default',
position: {
direction: 'left',
},
});
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
activeGroup: '1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel2'],
id: '1',
activeView: 'panel2',
},
size: 500,
},
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 1000,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.HORIZONTAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
panel2: {
id: 'panel2',
view: { content: { id: 'default' } },
title: 'panel2',
},
},
options: {},
});
});
test('orthogonal realigment #2', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.deserializer = new ReactPanelDeserialzier(dockview);
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
dockview.fromJSON({
activeGroup: 'group-1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 500,
},
{
type: 'leaf',
data: {
views: ['panel2'],
id: 'group-2',
activeView: 'panel2',
},
size: 500,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.VERTICAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
panel2: {
id: 'panel2',
view: { content: { id: 'default' } },
title: 'panel2',
},
},
});
expect(dockview.orientation).toBe(Orientation.VERTICAL);
dockview.addPanel({
id: 'panel3',
component: 'default',
position: {
direction: 'left',
},
});
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
activeGroup: '1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel3'],
id: '1',
activeView: 'panel3',
},
size: 500,
},
{
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 500,
},
{
type: 'leaf',
data: {
views: ['panel2'],
id: 'group-2',
activeView: 'panel2',
},
size: 500,
},
],
size: 500,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.HORIZONTAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
panel2: {
id: 'panel2',
view: { content: { id: 'default' } },
title: 'panel2',
},
panel3: {
id: 'panel3',
view: { content: { id: 'default' } },
title: 'panel3',
},
},
options: {},
});
});
test('orthogonal realigment #3', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.deserializer = new ReactPanelDeserialzier(dockview);
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
dockview.fromJSON({
activeGroup: 'group-1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 500,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.VERTICAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
},
});
expect(dockview.orientation).toBe(Orientation.VERTICAL);
dockview.addPanel({
id: 'panel2',
component: 'default',
position: {
direction: 'above',
},
});
dockview.addPanel({
id: 'panel3',
component: 'default',
position: {
direction: 'below',
},
});
expect(dockview.orientation).toBe(Orientation.VERTICAL);
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
activeGroup: '2',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel2'],
id: '1',
activeView: 'panel2',
},
size: 333,
},
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 333,
},
{
type: 'leaf',
data: {
views: ['panel3'],
id: '2',
activeView: 'panel3',
},
size: 334,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.VERTICAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
panel2: {
id: 'panel2',
view: { content: { id: 'default' } },
title: 'panel2',
},
panel3: {
id: 'panel3',
view: { content: { id: 'default' } },
title: 'panel3',
},
},
options: {},
});
});
}); });

View File

@ -1,7 +1,7 @@
import { DockviewComponent } from '../../dockview/dockviewComponent'; import { DockviewComponent } from '../../dockview/dockviewComponent';
import { DockviewApi } from '../../api/component.api'; import { DockviewApi } from '../../api/component.api';
import { IGroupPanelView } from '../../dockview/defaultGroupPanelView'; import { IGroupPanelView } from '../../dockview/defaultGroupPanelView';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { DockviewPanel } from '../../dockview/dockviewPanel';
import { GroupPanel } from '../../groupview/groupviewPanel'; import { GroupPanel } from '../../groupview/groupviewPanel';
describe('dockviewGroupPanel', () => { describe('dockviewGroupPanel', () => {
@ -20,7 +20,7 @@ describe('dockviewGroupPanel', () => {
const api = new dockviewApiMock(); const api = new dockviewApiMock();
const accessor = new accessorMock(); const accessor = new accessorMock();
const group = new groupMock(); const group = new groupMock();
const cut = new DockviewGroupPanel('fake-id', accessor, api, group); const cut = new DockviewPanel('fake-id', accessor, api, group);
let latestTitle: string | undefined = undefined; let latestTitle: string | undefined = undefined;
@ -55,7 +55,7 @@ describe('dockviewGroupPanel', () => {
const accessor = new accessorMock(); const accessor = new accessorMock();
const group = new groupMock(); const group = new groupMock();
const cut = new DockviewGroupPanel('fake-id', accessor, api, group); const cut = new DockviewPanel('fake-id', accessor, api, group);
const viewMock = jest.fn<IGroupPanelView, []>(() => { const viewMock = jest.fn<IGroupPanelView, []>(() => {
return { return {
@ -86,7 +86,7 @@ describe('dockviewGroupPanel', () => {
const api = new dockviewApiMock(); const api = new dockviewApiMock();
const accessor = new accessorMock(); const accessor = new accessorMock();
const group = new groupMock(); const group = new groupMock();
const cut = new DockviewGroupPanel('fake-id', accessor, api, group); const cut = new DockviewPanel('fake-id', accessor, api, group);
expect(cut.params).toEqual(undefined); expect(cut.params).toEqual(undefined);

View File

@ -1,3 +1,4 @@
import { GridviewPanelApiImpl } from '../../api/gridviewPanelApi';
import { GridviewComponent } from '../../gridview/gridviewComponent'; import { GridviewComponent } from '../../gridview/gridviewComponent';
import { GridviewPanel } from '../../gridview/gridviewPanel'; import { GridviewPanel } from '../../gridview/gridviewPanel';
import { CompositeDisposable } from '../../lifecycle'; import { CompositeDisposable } from '../../lifecycle';
@ -6,7 +7,9 @@ import { Orientation } from '../../splitview/core/splitview';
class TestGridview extends GridviewPanel { class TestGridview extends GridviewPanel {
constructor(id: string, componentName: string) { constructor(id: string, componentName: string) {
super(id, componentName); super(id, componentName, new GridviewPanelApiImpl(id));
this.api.initialize(this);
this.element.id = id; this.element.id = id;
} }
@ -65,7 +68,7 @@ describe('gridview', () => {
expect(gridview.size).toBe(1); expect(gridview.size).toBe(1);
const panel1 = gridview.getPanel('panel1'); const panel1 = gridview.getPanel('panel1')!;
gridview.removePanel(panel1); gridview.removePanel(panel1);
@ -101,9 +104,9 @@ describe('gridview', () => {
component: 'default', component: 'default',
}); });
const panel1 = gridview.getPanel('panel1'); const panel1 = gridview.getPanel('panel1')!;
const panel2 = gridview.getPanel('panel2'); const panel2 = gridview.getPanel('panel2')!;
const panel3 = gridview.getPanel('panel3'); const panel3 = gridview.getPanel('panel3')!;
expect(panel1.api.isActive).toBeFalsy(); expect(panel1.api.isActive).toBeFalsy();
expect(panel2.api.isActive).toBeFalsy(); expect(panel2.api.isActive).toBeFalsy();
@ -192,9 +195,9 @@ describe('gridview', () => {
}); });
gridview.layout(800, 400, true); gridview.layout(800, 400, true);
const panel1 = gridview.getPanel('panel_1'); const panel1 = gridview.getPanel('panel_1')!;
const panel2 = gridview.getPanel('panel_2'); const panel2 = gridview.getPanel('panel_2')!;
const panel3 = gridview.getPanel('panel_3'); const panel3 = gridview.getPanel('panel_3')!;
expect(panel1?.api.isVisible).toBeTruthy(); expect(panel1?.api.isVisible).toBeTruthy();
expect(panel1?.api.id).toBe('panel_1'); expect(panel1?.api.id).toBe('panel_1');
@ -330,7 +333,7 @@ describe('gridview', () => {
component: 'default', component: 'default',
}); });
const panel1 = gridview.getPanel('panel_1'); const panel1 = gridview.getPanel('panel_1')!;
expect(events).toEqual([ expect(events).toEqual([
{ {
@ -349,7 +352,7 @@ describe('gridview', () => {
component: 'default', component: 'default',
}); });
const panel2 = gridview.getPanel('panel_2'); const panel2 = gridview.getPanel('panel_2')!;
expect(events).toEqual([ expect(events).toEqual([
{ {
@ -368,7 +371,7 @@ describe('gridview', () => {
component: 'default', component: 'default',
}); });
const panel3 = gridview.getPanel('panel_3'); const panel3 = gridview.getPanel('panel_3')!;
expect(events).toEqual([ expect(events).toEqual([
{ {
@ -1685,8 +1688,8 @@ describe('gridview', () => {
component: 'default', component: 'default',
}); });
const panel1 = gridview.getPanel('panel1'); const panel1 = gridview.getPanel('panel1')!;
const panel2 = gridview.getPanel('panel2'); const panel2 = gridview.getPanel('panel2')!;
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
@ -1714,8 +1717,8 @@ describe('gridview', () => {
component: 'default', component: 'default',
}); });
const panel1 = gridview.getPanel('panel1'); const panel1 = gridview.getPanel('panel1')!;
const panel2 = gridview.getPanel('panel2'); const panel2 = gridview.getPanel('panel2')!;
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');
@ -1743,8 +1746,8 @@ describe('gridview', () => {
component: 'default', component: 'default',
}); });
const panel1 = gridview.getPanel('panel1'); const panel1 = gridview.getPanel('panel1')!;
const panel2 = gridview.getPanel('panel2'); const panel2 = gridview.getPanel('panel2')!;
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose');

View File

@ -4,7 +4,11 @@ import { GroupPanel } from '../../groupview/groupviewPanel';
describe('gridviewPanel', () => { describe('gridviewPanel', () => {
test('get panel', () => { test('get panel', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => { const accessorMock = jest.fn<DockviewComponent, []>(() => {
return {} as any; return {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
} as any;
}); });
const accessor = new accessorMock(); const accessor = new accessorMock();

View File

@ -225,6 +225,8 @@ describe('groupview', () => {
id: 'dockview-1', id: 'dockview-1',
removePanel: removePanelMock, removePanel: removePanelMock,
removeGroup: removeGroupMock, removeGroup: removeGroupMock,
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}) as DockviewComponent; }) as DockviewComponent;
options = { options = {
@ -616,6 +618,8 @@ describe('groupview', () => {
showDndOverlay: jest.fn(), showDndOverlay: jest.fn(),
}, },
getPanel: jest.fn(), getPanel: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}; };
}); });
const accessor = new accessorMock() as DockviewComponent; const accessor = new accessorMock() as DockviewComponent;
@ -671,6 +675,8 @@ describe('groupview', () => {
}, },
getPanel: jest.fn(), getPanel: jest.fn(),
doSetGroupActive: jest.fn(), doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}; };
}); });
const accessor = new accessorMock() as DockviewComponent; const accessor = new accessorMock() as DockviewComponent;
@ -724,7 +730,7 @@ describe('groupview', () => {
).toBe(0); ).toBe(0);
}); });
test('that should allow drop when not dropping on self for same component id', () => { test('that should not allow drop when dropping on self for same component id', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return { return {
id: 'testcomponentid', id: 'testcomponentid',
@ -733,6 +739,8 @@ describe('groupview', () => {
}, },
getPanel: jest.fn(), getPanel: jest.fn(),
doSetGroupActive: jest.fn(), doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}; };
}); });
const accessor = new accessorMock() as DockviewComponent; const accessor = new accessorMock() as DockviewComponent;
@ -784,7 +792,7 @@ describe('groupview', () => {
expect( expect(
element.getElementsByClassName('drop-target-dropzone').length element.getElementsByClassName('drop-target-dropzone').length
).toBe(1); ).toBe(0);
}); });
test('that should not allow drop when not dropping for different component id', () => { test('that should not allow drop when not dropping for different component id', () => {
@ -796,6 +804,8 @@ describe('groupview', () => {
}, },
getPanel: jest.fn(), getPanel: jest.fn(),
doSetGroupActive: jest.fn(), doSetGroupActive: jest.fn(),
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}; };
}); });
const accessor = new accessorMock() as DockviewComponent; const accessor = new accessorMock() as DockviewComponent;

View File

@ -12,7 +12,11 @@ import { TestPanel } from '../groupview.spec';
describe('tabsContainer', () => { describe('tabsContainer', () => {
test('that an external event does not render a drop target and calls through to the group mode', () => { test('that an external event does not render a drop target and calls through to the group mode', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return {}; return {
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
};
}); });
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => { const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
return { return {
@ -31,7 +35,7 @@ describe('tabsContainer', () => {
const accessor = new accessorMock() as DockviewComponent; const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel; const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {}); const cut = new TabsContainer(accessor, groupPanel);
const emptySpace = cut.element const emptySpace = cut.element
.getElementsByClassName('void-container') .getElementsByClassName('void-container')
@ -62,6 +66,9 @@ describe('tabsContainer', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return { return {
id: 'testcomponentid', id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
}; };
}); });
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => { const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
@ -83,7 +90,7 @@ describe('tabsContainer', () => {
const accessor = new accessorMock() as DockviewComponent; const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel; const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {}); const cut = new TabsContainer(accessor, groupPanel);
const emptySpace = cut.element const emptySpace = cut.element
.getElementsByClassName('void-container') .getElementsByClassName('void-container')
@ -125,6 +132,9 @@ describe('tabsContainer', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return { return {
id: 'testcomponentid', id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
}; };
}); });
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => { const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
@ -146,7 +156,7 @@ describe('tabsContainer', () => {
const accessor = new accessorMock() as DockviewComponent; const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel; const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {}); const cut = new TabsContainer(accessor, groupPanel);
cut.openPanel(new TestPanel('panel1', jest.fn() as any)); cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any)); cut.openPanel(new TestPanel('panel2', jest.fn() as any));
@ -185,6 +195,9 @@ describe('tabsContainer', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return { return {
id: 'testcomponentid', id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
}; };
}); });
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => { const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
@ -206,7 +219,7 @@ describe('tabsContainer', () => {
const accessor = new accessorMock() as DockviewComponent; const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel; const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {}); const cut = new TabsContainer(accessor, groupPanel);
cut.openPanel(new TestPanel('panel1', jest.fn() as any)); cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any)); cut.openPanel(new TestPanel('panel2', jest.fn() as any));
@ -245,6 +258,9 @@ describe('tabsContainer', () => {
const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => { const accessorMock = jest.fn<Partial<DockviewComponent>, []>(() => {
return { return {
id: 'testcomponentid', id: 'testcomponentid',
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
options: {},
}; };
}); });
const groupviewMock = jest.fn<Partial<Groupview>, []>(() => { const groupviewMock = jest.fn<Partial<Groupview>, []>(() => {
@ -265,7 +281,7 @@ describe('tabsContainer', () => {
const accessor = new accessorMock() as DockviewComponent; const accessor = new accessorMock() as DockviewComponent;
const groupPanel = new groupPanelMock() as GroupPanel; const groupPanel = new groupPanelMock() as GroupPanel;
const cut = new TabsContainer(accessor, groupPanel, {}); const cut = new TabsContainer(accessor, groupPanel);
cut.openPanel(new TestPanel('panel1', jest.fn() as any)); cut.openPanel(new TestPanel('panel1', jest.fn() as any));
cut.openPanel(new TestPanel('panel2', jest.fn() as any)); cut.openPanel(new TestPanel('panel2', jest.fn() as any));

View File

@ -325,6 +325,10 @@ export class GridviewApi implements CommonApi<SerializedGridview> {
} }
export class DockviewApi implements CommonApi<SerializedDockview> { export class DockviewApi implements CommonApi<SerializedDockview> {
get id(): string {
return this.component.id;
}
get width(): number { get width(): number {
return this.component.width; return this.component.width;
} }
@ -435,8 +439,8 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
return this.component.addPanel(options); return this.component.addPanel(options);
} }
addEmptyGroup(options?: AddGroupOptions): void { addGroup(options?: AddGroupOptions): IGroupviewPanel {
this.component.addEmptyGroup(options); return this.component.addGroup(options);
} }
moveToNext(options?: MovementOptions): void { moveToNext(options?: MovementOptions): void {

View File

@ -1,4 +1,5 @@
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { IPanel } from '../panel/types';
import { FunctionOrValue } from '../types'; import { FunctionOrValue } from '../types';
import { PanelApiImpl, PanelApi } from './panelApi'; import { PanelApiImpl, PanelApi } from './panelApi';
@ -48,7 +49,7 @@ export class GridviewPanelApiImpl
readonly onDidSizeChange: Event<SizeEvent> = this._onDidSizeChange.event; readonly onDidSizeChange: Event<SizeEvent> = this._onDidSizeChange.event;
// //
constructor(id: string) { constructor(id: string, panel?: IPanel) {
super(id); super(id);
this.addDisposables( this.addDisposables(
@ -56,6 +57,10 @@ export class GridviewPanelApiImpl
this._onDidConstraintsChange, this._onDidConstraintsChange,
this._onDidSizeChange this._onDidSizeChange
); );
if (panel) {
this.initialize(panel);
}
} }
public setConstraints(value: GridConstraintChangeEvent) { public setConstraints(value: GridConstraintChangeEvent) {

View File

@ -71,6 +71,9 @@ export class DockviewPanelApiImpl
constructor(private panel: IDockviewPanel, group: GroupPanel) { constructor(private panel: IDockviewPanel, group: GroupPanel) {
super(panel.id); super(panel.id);
this.initialize(panel);
this._group = group; this._group = group;
this.addDisposables( this.addDisposables(

View File

@ -1,5 +1,6 @@
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { CompositeDisposable } from '../lifecycle'; import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { IPanel, Parameters } from '../panel/types';
export interface FocusEvent { export interface FocusEvent {
readonly isFocused: boolean; readonly isFocused: boolean;
@ -25,6 +26,7 @@ export interface PanelApi {
readonly onDidActiveChange: Event<ActiveEvent>; readonly onDidActiveChange: Event<ActiveEvent>;
setVisible(isVisible: boolean): void; setVisible(isVisible: boolean): void;
setActive(): void; setActive(): void;
updateParameters(parameters: Parameters): void;
/** /**
* The id of the panel that would have been assigned when the panel was created * The id of the panel that would have been assigned when the panel was created
*/ */
@ -61,6 +63,8 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
private _width = 0; private _width = 0;
private _height = 0; private _height = 0;
private readonly panelUpdatesDisposable = new MutableDisposable();
readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>({ readonly _onDidDimensionChange = new Emitter<PanelDimensionChangeEvent>({
replay: true, replay: true,
}); });
@ -94,6 +98,10 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
readonly _onActiveChange = new Emitter<void>(); readonly _onActiveChange = new Emitter<void>();
readonly onActiveChange: Event<void> = this._onActiveChange.event; readonly onActiveChange: Event<void> = this._onActiveChange.event;
// //
readonly _onUpdateParameters = new Emitter<Parameters>();
readonly onUpdateParameters: Event<Parameters> =
this._onUpdateParameters.event;
//
get isFocused() { get isFocused() {
return this._isFocused; return this._isFocused;
@ -118,6 +126,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
super(); super();
this.addDisposables( this.addDisposables(
this.panelUpdatesDisposable,
this._onDidDimensionChange, this._onDidDimensionChange,
this._onDidChangeFocus, this._onDidChangeFocus,
this._onDidVisibilityChange, this._onDidVisibilityChange,
@ -125,6 +134,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
this._onFocusEvent, this._onFocusEvent,
this._onActiveChange, this._onActiveChange,
this._onVisibilityChange, this._onVisibilityChange,
this._onUpdateParameters,
this.onDidFocusChange((event) => { this.onDidFocusChange((event) => {
this._isFocused = event.isFocused; this._isFocused = event.isFocused;
}), }),
@ -141,6 +151,18 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
); );
} }
public initialize(panel: IPanel): void {
this.panelUpdatesDisposable.value = this._onUpdateParameters.event(
(parameters) => {
panel.update({
params: {
params: parameters,
},
});
}
);
}
setVisible(isVisible: boolean) { setVisible(isVisible: boolean) {
this._onVisibilityChange.fire({ isVisible }); this._onVisibilityChange.fire({ isVisible });
} }
@ -149,6 +171,10 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
this._onActiveChange.fire(); this._onActiveChange.fire();
} }
updateParameters(parameters: Parameters): void {
this._onUpdateParameters.fire(parameters);
}
dispose() { dispose() {
super.dispose(); super.dispose();
} }

View File

@ -6,16 +6,11 @@ export interface IDragAndDropObserverCallbacks {
onDragLeave: (e: DragEvent) => void; onDragLeave: (e: DragEvent) => void;
onDrop: (e: DragEvent) => void; onDrop: (e: DragEvent) => void;
onDragEnd: (e: DragEvent) => void; onDragEnd: (e: DragEvent) => void;
onDragOver?: (e: DragEvent) => void; onDragOver?: (e: DragEvent) => void;
} }
export class DragAndDropObserver extends CompositeDisposable { export class DragAndDropObserver extends CompositeDisposable {
// A helper to fix issues with repeated DRAG_ENTER / DRAG_LEAVE private target: EventTarget | null = null;
// calls see https://github.com/microsoft/vscode/issues/14470
// when the element has child elements where the events are fired
// repeadedly.
private counter = 0;
constructor( constructor(
private element: HTMLElement, private element: HTMLElement,
@ -28,28 +23,37 @@ export class DragAndDropObserver extends CompositeDisposable {
private registerListeners(): void { private registerListeners(): void {
this.addDisposables( this.addDisposables(
addDisposableListener(this.element, 'dragenter', (e: DragEvent) => { addDisposableListener(
this.counter++; this.element,
'dragenter',
this.callbacks.onDragEnter(e); (e: DragEvent) => {
}) this.target = e.target;
this.callbacks.onDragEnter(e);
},
true
)
); );
this.addDisposables( this.addDisposables(
addDisposableListener(this.element, 'dragover', (e: DragEvent) => { addDisposableListener(
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) this.element,
'dragover',
(e: DragEvent) => {
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
if (this.callbacks.onDragOver) { if (this.callbacks.onDragOver) {
this.callbacks.onDragOver(e); this.callbacks.onDragOver(e);
} }
}) },
true
)
); );
this.addDisposables( this.addDisposables(
addDisposableListener(this.element, 'dragleave', (e: DragEvent) => { addDisposableListener(this.element, 'dragleave', (e: DragEvent) => {
this.counter--; if (this.target === e.target) {
this.target = null;
if (this.counter === 0) {
this.callbacks.onDragLeave(e); this.callbacks.onDragLeave(e);
} }
}) })
@ -57,14 +61,13 @@ export class DragAndDropObserver extends CompositeDisposable {
this.addDisposables( this.addDisposables(
addDisposableListener(this.element, 'dragend', (e: DragEvent) => { addDisposableListener(this.element, 'dragend', (e: DragEvent) => {
this.counter = 0; this.target = null;
this.callbacks.onDragEnd(e); this.callbacks.onDragEnd(e);
}) })
); );
this.addDisposables( this.addDisposables(
addDisposableListener(this.element, 'drop', (e: DragEvent) => { addDisposableListener(this.element, 'drop', (e: DragEvent) => {
this.counter = 0;
this.callbacks.onDrop(e); this.callbacks.onDrop(e);
}) })
); );

View File

@ -19,22 +19,6 @@
will-change: transform; will-change: transform;
pointer-events: none; pointer-events: none;
&.left {
transform: translateX(-25%) scaleX(0.5)
}
&.right {
transform: translateX(25%) scaleX(0.5)
}
&.top {
transform: translateY(-25%) scaleY(0.5);
}
&.bottom {
transform: translateY(25%) scaleY(0.5);
}
&.small-top { &.small-top {
border-top: 1px solid var(--dv-drag-over-border-color); border-top: 1px solid var(--dv-drag-over-border-color);
} }

View File

@ -2,23 +2,36 @@ import { toggleClass } from '../dom';
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { CompositeDisposable } from '../lifecycle'; import { CompositeDisposable } from '../lifecycle';
import { DragAndDropObserver } from './dnd'; import { DragAndDropObserver } from './dnd';
import { clamp } from '../math';
import { Direction } from '../gridview/baseComponentGridview';
export enum Position { function numberOrFallback(maybeNumber: any, fallback: number): number {
Top = 'Top', return typeof maybeNumber === 'number' ? maybeNumber : fallback;
Left = 'Left',
Bottom = 'Bottom',
Right = 'Right',
Center = 'Center',
} }
export type Quadrant = 'top' | 'bottom' | 'left' | 'right'; export function directionToPosition(direction: Direction): Position {
switch (direction) {
case 'above':
return 'top';
case 'below':
return 'bottom';
case 'left':
return 'left';
case 'right':
return 'right';
case 'within':
return 'center';
default:
throw new Error(`invalid direction '${direction}'`);
}
}
export interface DroptargetEvent { export interface DroptargetEvent {
position: Position; readonly position: Position;
nativeEvent: DragEvent; readonly nativeEvent: DragEvent;
} }
export type DropTargetDirections = 'vertical' | 'horizontal' | 'all' | 'none'; export type Position = 'top' | 'bottom' | 'left' | 'right' | 'center';
function isBooleanValue( function isBooleanValue(
canDisplayOverlay: CanDisplayOverlay canDisplayOverlay: CanDisplayOverlay
@ -28,7 +41,7 @@ function isBooleanValue(
export type CanDisplayOverlay = export type CanDisplayOverlay =
| boolean | boolean
| ((dragEvent: DragEvent, state: Quadrant | null) => boolean); | ((dragEvent: DragEvent, state: Position) => boolean);
export class Droptarget extends CompositeDisposable { export class Droptarget extends CompositeDisposable {
private target: HTMLElement | undefined; private target: HTMLElement | undefined;
@ -38,27 +51,31 @@ export class Droptarget extends CompositeDisposable {
private readonly _onDrop = new Emitter<DroptargetEvent>(); private readonly _onDrop = new Emitter<DroptargetEvent>();
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event; readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
get state() { get state(): Position | undefined {
return this._state; return this._state;
} }
set validOverlays(value: DropTargetDirections) {
this.options.validOverlays = value;
}
set canDisplayOverlay(value: CanDisplayOverlay) {
this.options.canDisplayOverlay = value;
}
constructor( constructor(
private readonly element: HTMLElement, private readonly element: HTMLElement,
private readonly options: { private readonly options: {
canDisplayOverlay: CanDisplayOverlay; canDisplayOverlay: CanDisplayOverlay;
validOverlays: DropTargetDirections; acceptedTargetZones: Position[];
overlayModel?: {
size?: { value: number; type: 'pixels' | 'percentage' };
activationSize?: {
value: number;
type: 'pixels' | 'percentage';
};
};
} }
) { ) {
super(); super();
// use a set to take advantage of #<set>.has
const acceptedTargetZonesSet = new Set(
this.options.acceptedTargetZones
);
this.addDisposables( this.addDisposables(
this._onDrop, this._onDrop,
new DragAndDropObserver(this.element, { new DragAndDropObserver(this.element, {
@ -71,17 +88,25 @@ export class Droptarget extends CompositeDisposable {
return; // avoid div!0 return; // avoid div!0
} }
const x = e.offsetX; const rect = (
const y = e.offsetY; e.currentTarget as HTMLElement
const xp = (100 * x) / width; ).getBoundingClientRect();
const yp = (100 * y) / height; const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const quadrant = this.calculateQuadrant( const quadrant = this.calculateQuadrant(
this.options.validOverlays, acceptedTargetZonesSet,
xp, x,
yp y,
width,
height
); );
if (quadrant === null) {
this.removeDropTarget();
return;
}
if (isBooleanValue(this.options.canDisplayOverlay)) { if (isBooleanValue(this.options.canDisplayOverlay)) {
if (!this.options.canDisplayOverlay) { if (!this.options.canDisplayOverlay) {
return; return;
@ -95,14 +120,14 @@ export class Droptarget extends CompositeDisposable {
this.target.className = 'drop-target-dropzone'; this.target.className = 'drop-target-dropzone';
this.overlay = document.createElement('div'); this.overlay = document.createElement('div');
this.overlay.className = 'drop-target-selection'; this.overlay.className = 'drop-target-selection';
this._state = Position.Center; this._state = 'center';
this.target.appendChild(this.overlay); this.target.appendChild(this.overlay);
this.element.classList.add('drop-target'); this.element.classList.add('drop-target');
this.element.append(this.target); this.element.append(this.target);
} }
if (this.options.validOverlays === 'none') { if (this.options.acceptedTargetZones.length === 0) {
return; return;
} }
@ -110,10 +135,7 @@ export class Droptarget extends CompositeDisposable {
return; return;
} }
const isSmallX = width < 100; this.toggleClasses(quadrant, width, height);
const isSmallY = height < 100;
this.toggleClasses(quadrant, isSmallX, isSmallY);
this.setState(quadrant); this.setState(quadrant);
}, },
@ -139,28 +161,69 @@ export class Droptarget extends CompositeDisposable {
); );
} }
public dispose() { public dispose(): void {
this.removeDropTarget(); this.removeDropTarget();
} }
private toggleClasses( private toggleClasses(
quadrant: Quadrant | null, quadrant: Position,
isSmallX: boolean, width: number,
isSmallY: boolean height: number
) { ): void {
if (!this.overlay) { if (!this.overlay) {
return; return;
} }
const isSmallX = width < 100;
const isSmallY = height < 100;
const isLeft = quadrant === 'left'; const isLeft = quadrant === 'left';
const isRight = quadrant === 'right'; const isRight = quadrant === 'right';
const isTop = quadrant === 'top'; const isTop = quadrant === 'top';
const isBottom = quadrant === 'bottom'; const isBottom = quadrant === 'bottom';
toggleClass(this.overlay, 'right', !isSmallX && isRight); const rightClass = !isSmallX && isRight;
toggleClass(this.overlay, 'left', !isSmallX && isLeft); const leftClass = !isSmallX && isLeft;
toggleClass(this.overlay, 'top', !isSmallY && isTop); const topClass = !isSmallY && isTop;
toggleClass(this.overlay, 'bottom', !isSmallY && isBottom); const bottomClass = !isSmallY && isBottom;
let size = 0.5;
if (this.options.overlayModel?.size?.type === 'percentage') {
size = clamp(this.options.overlayModel.size.value, 0, 100) / 100;
}
if (this.options.overlayModel?.size?.type === 'pixels') {
if (rightClass || leftClass) {
size =
clamp(0, this.options.overlayModel.size.value, width) /
width;
}
if (topClass || bottomClass) {
size =
clamp(0, this.options.overlayModel.size.value, height) /
height;
}
}
const translate = (1 - size) / 2;
const scale = size;
let transform: string;
if (rightClass) {
transform = `translateX(${100 * translate}%) scaleX(${scale})`;
} else if (leftClass) {
transform = `translateX(-${100 * translate}%) scaleX(${scale})`;
} else if (topClass) {
transform = `translateY(-${100 * translate}%) scaleY(${scale})`;
} else if (bottomClass) {
transform = `translateY(${100 * translate}%) scaleY(${scale})`;
} else {
transform = '';
}
this.overlay.style.transform = transform;
toggleClass(this.overlay, 'small-right', isSmallX && isRight); toggleClass(this.overlay, 'small-right', isSmallX && isRight);
toggleClass(this.overlay, 'small-left', isSmallX && isLeft); toggleClass(this.overlay, 'small-left', isSmallX && isLeft);
@ -168,60 +231,61 @@ export class Droptarget extends CompositeDisposable {
toggleClass(this.overlay, 'small-bottom', isSmallY && isBottom); toggleClass(this.overlay, 'small-bottom', isSmallY && isBottom);
} }
private setState(quadrant: Quadrant | null) { private setState(quadrant: Position): void {
switch (quadrant) { switch (quadrant) {
case 'top': case 'top':
this._state = Position.Top; this._state = 'top';
break; break;
case 'left': case 'left':
this._state = Position.Left; this._state = 'left';
break; break;
case 'bottom': case 'bottom':
this._state = Position.Bottom; this._state = 'bottom';
break; break;
case 'right': case 'right':
this._state = Position.Right; this._state = 'right';
break; break;
default: case 'center':
this._state = Position.Center; this._state = 'center';
break; break;
} }
} }
private calculateQuadrant( private calculateQuadrant(
overlayType: DropTargetDirections, overlayType: Set<Position>,
xp: number, x: number,
yp: number y: number,
): Quadrant | null { width: number,
switch (overlayType) { height: number
case 'all': ): Position | null {
if (xp < 20) { const isPercentage =
return 'left'; this.options.overlayModel?.activationSize === undefined ||
} this.options.overlayModel?.activationSize?.type === 'percentage';
if (xp > 80) {
return 'right';
}
if (yp < 20) {
return 'top';
}
if (yp > 80) {
return 'bottom';
}
break;
case 'vertical':
if (yp < 50) {
return 'top';
}
return 'bottom';
case 'horizontal': const value = numberOrFallback(
if (xp < 50) { this.options?.overlayModel?.activationSize?.value,
return 'left'; 20
} );
return 'right';
if (isPercentage) {
return calculateQuadrantAsPercentage(
overlayType,
x,
y,
width,
height,
value
);
} }
return null; return calculateQuadrantAsPixels(
overlayType,
x,
y,
width,
height,
value
);
} }
private removeDropTarget() { private removeDropTarget() {
@ -229,7 +293,67 @@ export class Droptarget extends CompositeDisposable {
this._state = undefined; this._state = undefined;
this.element.removeChild(this.target); this.element.removeChild(this.target);
this.target = undefined; this.target = undefined;
this.overlay = undefined;
this.element.classList.remove('drop-target'); this.element.classList.remove('drop-target');
} }
} }
} }
export function calculateQuadrantAsPercentage(
overlayType: Set<Position>,
x: number,
y: number,
width: number,
height: number,
threshold: number
): Position | null {
const xp = (100 * x) / width;
const yp = (100 * y) / height;
if (overlayType.has('left') && xp < threshold) {
return 'left';
}
if (overlayType.has('right') && xp > 100 - threshold) {
return 'right';
}
if (overlayType.has('top') && yp < threshold) {
return 'top';
}
if (overlayType.has('bottom') && yp > 100 - threshold) {
return 'bottom';
}
if (!overlayType.has('center')) {
return null;
}
return 'center';
}
export function calculateQuadrantAsPixels(
overlayType: Set<Position>,
x: number,
y: number,
width: number,
height: number,
threshold: number
): Position | null {
if (overlayType.has('left') && x < threshold) {
return 'left';
}
if (overlayType.has('right') && x > width - threshold) {
return 'right';
}
if (overlayType.has('top') && y < threshold) {
return 'top';
}
if (overlayType.has('bottom') && y > height - threshold) {
return 'bottom';
}
if (!overlayType.has('center')) {
return null;
}
return 'center';
}

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@ import { Parameters } from '../panel/types';
import { IGroupPanelView } from './defaultGroupPanelView'; import { IGroupPanelView } from './defaultGroupPanelView';
import { DockviewComponent } from './dockviewComponent'; import { DockviewComponent } from './dockviewComponent';
export class DockviewGroupPanel export class DockviewPanel
extends CompositeDisposable extends CompositeDisposable
implements IDockviewPanel implements IDockviewPanel
{ {
@ -38,7 +38,7 @@ export class DockviewGroupPanel
return this._group; return this._group;
} }
get view() { get view(): IGroupPanelView | undefined {
return this._view; return this._view;
} }

View File

@ -14,6 +14,7 @@ import { FrameworkFactory } from '../types';
import { DockviewDropTargets } from '../groupview/dnd'; import { DockviewDropTargets } from '../groupview/dnd';
import { PanelTransfer } from '../dnd/dataTransfer'; import { PanelTransfer } from '../dnd/dataTransfer';
import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer'; import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer';
import { Position } from '../dnd/droptarget';
export interface GroupPanelFrameworkComponentFactory { export interface GroupPanelFrameworkComponentFactory {
content: FrameworkFactory<IContentRenderer>; content: FrameworkFactory<IContentRenderer>;
@ -54,7 +55,8 @@ export interface ViewFactoryData {
export interface DockviewDndOverlayEvent { export interface DockviewDndOverlayEvent {
nativeEvent: DragEvent; nativeEvent: DragEvent;
target: DockviewDropTargets; target: DockviewDropTargets;
group: GroupPanel; position: Position;
group?: GroupPanel;
getData: () => PanelTransfer | undefined; getData: () => PanelTransfer | undefined;
} }
@ -68,6 +70,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
defaultTabComponent?: string; defaultTabComponent?: string;
showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean; showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean;
createGroupControlElement?: (group: GroupPanel) => IGroupControlRenderer; createGroupControlElement?: (group: GroupPanel) => IGroupControlRenderer;
singleTabMode?: 'fullwidth' | 'default';
} }
export interface PanelOptions { export interface PanelOptions {
@ -78,19 +81,81 @@ export interface PanelOptions {
title?: string; title?: string;
} }
type RelativePanel = {
direction?: Direction;
referencePanel: string | IDockviewPanel;
};
type RelativeGroup = {
direction?: Direction;
referenceGroup: string | GroupPanel;
};
type AbsolutePosition = {
direction: Omit<Direction, 'within'>;
};
export type AddPanelPositionOptions =
| RelativePanel
| RelativeGroup
| AbsolutePosition;
export function isPanelOptionsWithPanel(
data: AddPanelPositionOptions
): data is RelativePanel {
if ((data as RelativePanel).referencePanel) {
return true;
}
return false;
}
export function isPanelOptionsWithGroup(
data: AddPanelPositionOptions
): data is RelativeGroup {
if ((data as RelativeGroup).referenceGroup) {
return true;
}
return false;
}
export interface AddPanelOptions export interface AddPanelOptions
extends Omit<PanelOptions, 'component' | 'tabComponent'> { extends Omit<PanelOptions, 'component' | 'tabComponent'> {
component: string; component: string;
tabComponent?: string; tabComponent?: string;
position?: { position?: AddPanelPositionOptions;
direction?: Direction;
referencePanel?: string;
};
} }
export interface AddGroupOptions { type AddGroupOptionsWithPanel = {
direction?: 'left' | 'right' | 'above' | 'below'; referencePanel: string | IDockviewPanel;
referencePanel: string; direction?: Omit<Direction, 'within'>;
};
type AddGroupOptionsWithGroup = {
referenceGroup: string | GroupPanel;
direction?: Omit<Direction, 'within'>;
};
export type AddGroupOptions =
| AddGroupOptionsWithGroup
| AddGroupOptionsWithPanel
| AbsolutePosition;
export function isGroupOptionsWithPanel(
data: AddGroupOptions
): data is AddGroupOptionsWithPanel {
if ((data as AddGroupOptionsWithPanel).referencePanel) {
return true;
}
return false;
}
export function isGroupOptionsWithGroup(
data: AddGroupOptions
): data is AddGroupOptionsWithGroup {
if ((data as AddGroupOptionsWithGroup).referenceGroup) {
return true;
}
return false;
} }
export interface MovementOptions2 { export interface MovementOptions2 {

View File

@ -15,19 +15,19 @@ const nextLayoutId = sequentialNumberGenerator();
export type Direction = 'left' | 'right' | 'above' | 'below' | 'within'; export type Direction = 'left' | 'right' | 'above' | 'below' | 'within';
export function toTarget(direction: Direction) { export function toTarget(direction: Direction): Position {
switch (direction) { switch (direction) {
case 'left': case 'left':
return Position.Left; return 'left';
case 'right': case 'right':
return Position.Right; return 'right';
case 'above': case 'above':
return Position.Top; return 'top';
case 'below': case 'below':
return Position.Bottom; return 'bottom';
case 'within': case 'within':
default: default:
return Position.Center; return 'center';
} }
} }

View File

@ -6,7 +6,7 @@ import {
PanelInitParameters, PanelInitParameters,
IPanel, IPanel,
} from '../panel/types'; } from '../panel/types';
import { PanelApiImpl } from '../api/panelApi'; import { PanelApi, PanelApiImpl } from '../api/panelApi';
export interface BasePanelViewState { export interface BasePanelViewState {
id: string; id: string;
@ -14,7 +14,7 @@ export interface BasePanelViewState {
params?: Record<string, any>; params?: Record<string, any>;
} }
export interface BasePanelViewExported<T extends PanelApiImpl> { export interface BasePanelViewExported<T extends PanelApi> {
readonly id: string; readonly id: string;
readonly api: T; readonly api: T;
readonly width: number; readonly width: number;

View File

@ -9,13 +9,13 @@ import {
Orientation, Orientation,
Sizing, Sizing,
} from '../splitview/core/splitview'; } from '../splitview/core/splitview';
import { Position } from '../dnd/droptarget';
import { tail } from '../array'; import { tail } from '../array';
import { LeafNode } from './leafNode'; import { LeafNode } from './leafNode';
import { BranchNode } from './branchNode'; import { BranchNode } from './branchNode';
import { Node } from './types'; import { Node } from './types';
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { IDisposable, MutableDisposable } from '../lifecycle'; import { IDisposable, MutableDisposable } from '../lifecycle';
import { Position } from '../dnd/droptarget';
function findLeaf(candiateNode: Node, last: boolean): LeafNode { function findLeaf(candiateNode: Node, last: boolean): LeafNode {
if (candiateNode instanceof LeafNode) { if (candiateNode instanceof LeafNode) {
@ -132,22 +132,19 @@ export function getRelativeLocation(
const [rest, _index] = tail(location); const [rest, _index] = tail(location);
let index = _index; let index = _index;
if (direction === Position.Right || direction === Position.Bottom) { if (direction === 'right' || direction === 'bottom') {
index += 1; index += 1;
} }
return [...rest, index]; return [...rest, index];
} else { } else {
const index = const index = direction === 'right' || direction === 'bottom' ? 1 : 0;
direction === Position.Right || direction === Position.Bottom
? 1
: 0;
return [...location, index]; return [...location, index];
} }
} }
export function getDirectionOrientation(direction: Position): Orientation { export function getDirectionOrientation(direction: Position): Orientation {
return direction === Position.Top || direction === Position.Bottom return direction === 'top' || direction === 'bottom'
? Orientation.VERTICAL ? Orientation.VERTICAL
: Orientation.HORIZONTAL; : Orientation.HORIZONTAL;
} }
@ -276,6 +273,10 @@ export class Gridview implements IDisposable {
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> = readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
this._onDidChange.event; this._onDidChange.event;
public get length(): number {
return this._root ? this._root.children.length : 0;
}
public serialize() { public serialize() {
const root = serializeBranchNode(this.getView(), this.orientation); const root = serializeBranchNode(this.getView(), this.orientation);
@ -410,6 +411,43 @@ export class Gridview implements IDisposable {
}); });
} }
/**
* If the root is orientated as a VERTICAL node then nest the existing root within a new HORIZIONTAL root node
* If the root is orientated as a HORIZONTAL node then nest the existing root within a new VERITCAL root node
*/
public insertOrthogonalSplitviewAtRoot(): void {
if (!this._root) {
return;
}
const oldRoot = this.root;
oldRoot.element.remove();
this._root = new BranchNode(
orthogonal(oldRoot.orientation),
this.proportionalLayout,
this.styles,
this.root.orthogonalSize,
this.root.size
);
if (oldRoot.children.length === 1) {
// can remove one level of redundant branching if there is only a single child
const childReference = oldRoot.children[0];
oldRoot.removeChild(0); // remove to prevent disposal when disposing of unwanted root
oldRoot.dispose();
this._root.addChild(childReference, Sizing.Distribute, 0);
} else {
this._root.addChild(oldRoot, Sizing.Distribute, 0);
}
this.element.appendChild(this._root.element);
this.disposable.value = this._root.onDidChange((e) => {
this._onDidChange.fire(e);
});
}
public next(location: number[]) { public next(location: number[]) {
return this.progmaticSelect(location); return this.progmaticSelect(location);
} }

View File

@ -3,7 +3,6 @@ import {
SerializedGridObject, SerializedGridObject,
getGridLocation, getGridLocation,
} from './gridview'; } from './gridview';
import { Position } from '../dnd/droptarget';
import { tail, sequenceEquals } from '../array'; import { tail, sequenceEquals } from '../array';
import { CompositeDisposable } from '../lifecycle'; import { CompositeDisposable } from '../lifecycle';
import { IPanelDeserializer } from '../dockview/deserializer'; import { IPanelDeserializer } from '../dockview/deserializer';
@ -25,6 +24,7 @@ import { BaseComponentOptions } from '../panel/types';
import { Orientation, Sizing } from '../splitview/core/splitview'; import { Orientation, Sizing } from '../splitview/core/splitview';
import { createComponent } from '../panel/componentFactory'; import { createComponent } from '../panel/componentFactory';
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { Position } from '../dnd/droptarget';
export interface SerializedGridview { export interface SerializedGridview {
grid: { grid: {
@ -265,7 +265,7 @@ export class GridviewComponent
} }
const target = toTarget(options.direction); const target = toTarget(options.direction);
if (target === Position.Center) { if (target === 'center') {
throw new Error(`${target} not supported as an option`); throw new Error(`${target} not supported as an option`);
} else { } else {
const location = getGridLocation(referenceGroup.element); const location = getGridLocation(referenceGroup.element);
@ -294,7 +294,7 @@ export class GridviewComponent
} }
const target = toTarget(options.position.direction); const target = toTarget(options.position.direction);
if (target === Position.Center) { if (target === 'center') {
throw new Error(`${target} not supported as an option`); throw new Error(`${target} not supported as an option`);
} else { } else {
const location = getGridLocation(referenceGroup.element); const location = getGridLocation(referenceGroup.element);

View File

@ -9,7 +9,10 @@ import {
BasePanelViewExported, BasePanelViewExported,
BasePanelViewState, BasePanelViewState,
} from './basePanelView'; } from './basePanelView';
import { GridviewPanelApiImpl } from '../api/gridviewPanelApi'; import {
GridviewPanelApi,
GridviewPanelApiImpl,
} from '../api/gridviewPanelApi';
import { LayoutPriority } from '../splitview/core/splitview'; import { LayoutPriority } from '../splitview/core/splitview';
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { IViewSize } from './gridview'; import { IViewSize } from './gridview';
@ -26,7 +29,7 @@ export interface GridviewInitParameters extends PanelInitParameters {
} }
export interface IGridviewPanel export interface IGridviewPanel
extends BasePanelViewExported<GridviewPanelApiImpl> { extends BasePanelViewExported<GridviewPanelApi> {
readonly minimumWidth: number; readonly minimumWidth: number;
readonly maximumWidth: number; readonly maximumWidth: number;
readonly minimumHeight: number; readonly minimumHeight: number;
@ -123,13 +126,11 @@ export abstract class GridviewPanel
return this.api.isActive; return this.api.isActive;
} }
constructor( constructor(id: string, component: string, api: GridviewPanelApiImpl) {
id: string,
component: string,
api = new GridviewPanelApiImpl(id)
) {
super(id, component, api); super(id, component, api);
this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement
this.addDisposables( this.addDisposables(
this._onDidChange, this._onDidChange,
this.api.onVisibilityChange((event) => { this.api.onVisibilityChange((event) => {

View File

@ -2,4 +2,5 @@ export enum DockviewDropTargets {
Tab, Tab,
Panel, Panel,
TabContainer, TabContainer,
Edge,
} }

View File

@ -110,7 +110,11 @@ export interface IGroupview extends IDisposable, IGridPanelView {
panel?: IDockviewPanel; panel?: IDockviewPanel;
suppressRoll?: boolean; suppressRoll?: boolean;
}): void; }): void;
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean; canDisplayOverlay(
event: DragEvent,
position: Position,
target: DockviewDropTargets
): boolean;
} }
export class Groupview extends CompositeDisposable implements IGroupview { export class Groupview extends CompositeDisposable implements IGroupview {
@ -167,6 +171,8 @@ export class Groupview extends CompositeDisposable implements IGroupview {
set locked(value: boolean) { set locked(value: boolean) {
this._locked = value; this._locked = value;
toggleClass(this.container, 'locked-groupview', value);
} }
get isActive(): boolean { get isActive(): boolean {
@ -226,45 +232,48 @@ export class Groupview extends CompositeDisposable implements IGroupview {
private accessor: DockviewComponent, private accessor: DockviewComponent,
public id: string, public id: string,
private readonly options: GroupOptions, private readonly options: GroupOptions,
private readonly parent: GroupPanel private readonly groupPanel: GroupPanel
) { ) {
super(); super();
this.container.classList.add('groupview'); this.container.classList.add('groupview');
this.tabsContainer = new TabsContainer(this.accessor, this.parent, { this.tabsContainer = new TabsContainer(this.accessor, this.groupPanel);
tabHeight: options.tabHeight,
});
this.contentContainer = new ContentContainer(); this.contentContainer = new ContentContainer();
this.dropTarget = new Droptarget(this.contentContainer.element, { this.dropTarget = new Droptarget(this.contentContainer.element, {
validOverlays: 'all', acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
canDisplayOverlay: (event, quadrant) => { canDisplayOverlay: (event, position) => {
if (this.locked && !quadrant) { if (this.locked && position === 'center') {
return false; return false;
} }
const data = getPanelData(); const data = getPanelData();
if (
data &&
data.panelId === null &&
data.viewId === this.accessor.id &&
data.groupId !== this.id
) {
// prevent dropping on self for group dnd
return true;
}
if (data && data.viewId === this.accessor.id) { if (data && data.viewId === this.accessor.id) {
if (data.groupId === this.id) {
if (position === 'center') {
// don't allow to drop on self for center position
return false;
}
if (data.panelId === null) {
// don't allow group move to drop anywhere on self
return false;
}
}
const groupHasOnePanelAndIsActiveDragElement = const groupHasOnePanelAndIsActiveDragElement =
this._panels.length === 1 && data.groupId === this.id; this._panels.length === 1 && data.groupId === this.id;
return !groupHasOnePanelAndIsActiveDragElement; return !groupHasOnePanelAndIsActiveDragElement;
} }
return this.canDisplayOverlay(event, DockviewDropTargets.Panel); return this.canDisplayOverlay(
event,
position,
DockviewDropTargets.Panel
);
}, },
}); });
@ -284,10 +293,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
this._onDidRemovePanel, this._onDidRemovePanel,
this._onDidActivePanelChange, this._onDidActivePanelChange,
this.tabsContainer.onDrop((event) => { this.tabsContainer.onDrop((event) => {
this.handleDropEvent(event.event, Position.Center, event.index); this.handleDropEvent(event.event, 'center', event.index);
}), }),
this.contentContainer.onDidFocus(() => { this.contentContainer.onDidFocus(() => {
this.accessor.doSetGroupActive(this.parent, true); this.accessor.doSetGroupActive(this.groupPanel, true);
}), }),
this.contentContainer.onDidBlur(() => { this.contentContainer.onDidBlur(() => {
// noop // noop
@ -316,12 +325,12 @@ export class Groupview extends CompositeDisposable implements IGroupview {
if (this.accessor.options.createGroupControlElement) { if (this.accessor.options.createGroupControlElement) {
this._control = this.accessor.options.createGroupControlElement( this._control = this.accessor.options.createGroupControlElement(
this.parent this.groupPanel
); );
this.addDisposables(this._control); this.addDisposables(this._control);
this._control.init({ this._control.init({
containerApi: new DockviewApi(this.accessor), containerApi: new DockviewApi(this.accessor),
api: this.parent.api, api: this.groupPanel.api,
}); });
this.tabsContainer.setActionElement(this._control.element); this.tabsContainer.setActionElement(this._control.element);
} }
@ -441,11 +450,11 @@ export class Groupview extends CompositeDisposable implements IGroupview {
const skipSetGroupActive = !!options.skipSetGroupActive; 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.parent, true); panel.updateParentGroup(this.groupPanel, true);
if (this._activePanel === panel) { if (this._activePanel === panel) {
if (!skipSetGroupActive) { if (!skipSetGroupActive) {
this.accessor.doSetGroupActive(this.parent); this.accessor.doSetGroupActive(this.groupPanel);
} }
return; return;
} }
@ -457,7 +466,10 @@ export class Groupview extends CompositeDisposable implements IGroupview {
} }
if (!skipSetGroupActive) { if (!skipSetGroupActive) {
this.accessor.doSetGroupActive(this.parent, !!options.skipFocus); this.accessor.doSetGroupActive(
this.groupPanel,
!!options.skipFocus
);
} }
this.updateContainer(); this.updateContainer();
@ -486,7 +498,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
this.doClose(panel); this.doClose(panel);
} }
} else { } else {
this.accessor.removeGroup(this.parent); this.accessor.removeGroup(this.groupPanel);
} }
} }
@ -643,7 +655,7 @@ export class Groupview extends CompositeDisposable implements IGroupview {
toggleClass(this.container, 'empty', this.isEmpty); toggleClass(this.container, 'empty', this.isEmpty);
this.panels.forEach((panel) => this.panels.forEach((panel) =>
panel.updateParentGroup(this.parent, this.isActive) panel.updateParentGroup(this.groupPanel, this.isActive)
); );
if (this.isEmpty && !this.watermark) { if (this.isEmpty && !this.watermark) {
@ -658,14 +670,14 @@ export class Groupview extends CompositeDisposable implements IGroupview {
addDisposableListener(this.watermark.element, 'click', () => { addDisposableListener(this.watermark.element, 'click', () => {
if (!this.isActive) { if (!this.isActive) {
this.accessor.doSetGroupActive(this.parent); this.accessor.doSetGroupActive(this.groupPanel);
} }
}); });
this.tabsContainer.hide(); this.tabsContainer.hide();
this.contentContainer.element.appendChild(this.watermark.element); this.contentContainer.element.appendChild(this.watermark.element);
this.watermark.updateParentGroup(this.parent, true); this.watermark.updateParentGroup(this.groupPanel, true);
} }
if (!this.isEmpty && this.watermark) { if (!this.isEmpty && this.watermark) {
this.watermark.element.remove(); this.watermark.element.remove();
@ -675,13 +687,18 @@ export class Groupview extends CompositeDisposable implements IGroupview {
} }
} }
canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean { canDisplayOverlay(
event: DragEvent,
position: Position,
target: DockviewDropTargets
): boolean {
// custom overlay handler // custom overlay handler
if (this.accessor.options.showDndOverlay) { if (this.accessor.options.showDndOverlay) {
return this.accessor.options.showDndOverlay({ return this.accessor.options.showDndOverlay({
nativeEvent: event, nativeEvent: event,
target, target,
group: this.accessor.getPanel(this.id)!, group: this.accessor.getPanel(this.id)!,
position,
getData: getPanelData, getData: getPanelData,
}); });
} }

View File

@ -8,28 +8,16 @@ import {
import { toggleClass } from '../dom'; import { toggleClass } from '../dom';
import { IDockviewComponent } from '../dockview/dockviewComponent'; import { IDockviewComponent } from '../dockview/dockviewComponent';
import { ITabRenderer } from './types'; import { ITabRenderer } from './types';
import { IDockviewPanel } from './groupPanel';
import { GroupPanel } from './groupviewPanel'; import { GroupPanel } from './groupviewPanel';
import { DroptargetEvent, Droptarget } from '../dnd/droptarget'; import { DroptargetEvent, Droptarget } from '../dnd/droptarget';
import { DockviewDropTargets } from './dnd'; import { DockviewDropTargets } from './dnd';
import { DragHandler } from '../dnd/abstractDragHandler'; import { DragHandler } from '../dnd/abstractDragHandler';
export enum MouseEventKind {
CLICK = 'CLICK',
}
export interface LayoutMouseEvent {
readonly kind: MouseEventKind;
readonly event: MouseEvent;
readonly panel?: IDockviewPanel;
readonly tab?: boolean;
}
export interface ITab { export interface ITab {
readonly panelId: string; readonly panelId: string;
readonly element: HTMLElement; readonly element: HTMLElement;
setContent: (element: ITabRenderer) => void; setContent: (element: ITabRenderer) => void;
onChanged: Event<LayoutMouseEvent>; onChanged: Event<MouseEvent>;
onDrop: Event<DroptargetEvent>; onDrop: Event<DroptargetEvent>;
setActive(isActive: boolean): void; setActive(isActive: boolean): void;
} }
@ -39,13 +27,13 @@ export class Tab extends CompositeDisposable implements ITab {
private readonly droptarget: Droptarget; private readonly droptarget: Droptarget;
private content?: ITabRenderer; private content?: ITabRenderer;
private readonly _onChanged = new Emitter<LayoutMouseEvent>(); private readonly _onChanged = new Emitter<MouseEvent>();
readonly onChanged: Event<LayoutMouseEvent> = this._onChanged.event; readonly onChanged: Event<MouseEvent> = this._onChanged.event;
private readonly _onDropped = new Emitter<DroptargetEvent>(); private readonly _onDropped = new Emitter<DroptargetEvent>();
readonly onDrop: Event<DroptargetEvent> = this._onDropped.event; readonly onDrop: Event<DroptargetEvent> = this._onDropped.event;
public get element() { public get element(): HTMLElement {
return this._element; return this._element;
} }
@ -104,20 +92,34 @@ export class Tab extends CompositeDisposable implements ITab {
*/ */
event.stopPropagation(); event.stopPropagation();
this._onChanged.fire({ kind: MouseEventKind.CLICK, event }); this._onChanged.fire(event);
}) })
); );
this.droptarget = new Droptarget(this._element, { this.droptarget = new Droptarget(this._element, {
validOverlays: 'none', acceptedTargetZones: ['center'],
canDisplayOverlay: (event) => { canDisplayOverlay: (event, position) => {
if (this.group.locked) {
return false;
}
const data = getPanelData(); const data = getPanelData();
if (data && this.accessor.id === data.viewId) { if (data && this.accessor.id === data.viewId) {
if (
data.panelId === null &&
data.groupId === this.group.id
) {
// don't allow group move to drop on self
return false;
}
return this.panelId !== data.panelId; return this.panelId !== data.panelId;
} }
return this.group.model.canDisplayOverlay( return this.group.model.canDisplayOverlay(
event, event,
position,
DockviewDropTargets.Tab DockviewDropTargets.Tab
); );
}, },
@ -130,12 +132,12 @@ export class Tab extends CompositeDisposable implements ITab {
); );
} }
public setActive(isActive: boolean) { public setActive(isActive: boolean): void {
toggleClass(this.element, 'active-tab', isActive); toggleClass(this.element, 'active-tab', isActive);
toggleClass(this.element, 'inactive-tab', !isActive); toggleClass(this.element, 'inactive-tab', !isActive);
} }
public setContent(part: ITabRenderer) { public setContent(part: ITabRenderer): void {
if (this.content) { if (this.content) {
this._element.removeChild(this.content.element); this._element.removeChild(this.content.element);
} }
@ -143,7 +145,7 @@ export class Tab extends CompositeDisposable implements ITab {
this._element.appendChild(this.content.element); this._element.appendChild(this.content.element);
} }
public dispose() { public dispose(): void {
super.dispose(); super.dispose();
this.droptarget.dispose(); this.droptarget.dispose();
} }

View File

@ -10,6 +10,20 @@
display: none; display: none;
} }
&.dv-single-tab.dv-full-width-single-tab {
.tabs-container {
flex-grow: 1;
.tab {
flex-grow: 1;
}
}
.void-container {
flex-grow: 0;
}
}
.void-container { .void-container {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;

View File

@ -4,11 +4,12 @@ import {
IValueDisposable, IValueDisposable,
} from '../../lifecycle'; } from '../../lifecycle';
import { addDisposableListener, Emitter, Event } from '../../events'; import { addDisposableListener, Emitter, Event } from '../../events';
import { ITab, MouseEventKind, Tab } from '../tab'; import { ITab, Tab } from '../tab';
import { IDockviewPanel } from '../groupPanel'; import { IDockviewPanel } from '../groupPanel';
import { DockviewComponent } from '../../dockview/dockviewComponent'; import { DockviewComponent } from '../../dockview/dockviewComponent';
import { GroupPanel } from '../groupviewPanel'; import { GroupPanel } from '../groupviewPanel';
import { VoidContainer } from './voidContainer'; import { VoidContainer } from './voidContainer';
import { toggleClass } from '../../dom';
export interface TabDropIndexEvent { export interface TabDropIndexEvent {
event: DragEvent; event: DragEvent;
@ -134,8 +135,7 @@ export class TabsContainer
constructor( constructor(
private readonly accessor: DockviewComponent, private readonly accessor: DockviewComponent,
private readonly group: GroupPanel, private readonly group: GroupPanel
readonly options: { tabHeight?: number }
) { ) {
super(); super();
@ -144,7 +144,34 @@ export class TabsContainer
this._element = document.createElement('div'); this._element = document.createElement('div');
this._element.className = 'tabs-and-actions-container'; this._element.className = 'tabs-and-actions-container';
this.height = options.tabHeight; this.height = accessor.options.tabHeight;
toggleClass(
this._element,
'dv-full-width-single-tab',
this.accessor.options.singleTabMode === 'fullwidth'
);
this.addDisposables(
this.accessor.onDidAddPanel((e) => {
if (e.api.group === this.group) {
toggleClass(
this._element,
'dv-single-tab',
this.size === 1
);
}
}),
this.accessor.onDidRemovePanel((e) => {
if (e.api.group === this.group) {
toggleClass(
this._element,
'dv-single-tab',
this.size === 1
);
}
})
);
this.actionContainer = document.createElement('div'); this.actionContainer = document.createElement('div');
this.actionContainer.className = 'action-container'; this.actionContainer.className = 'action-container';
@ -242,17 +269,15 @@ export class TabsContainer
panel.id === this.group.model.activePanel?.id && panel.id === this.group.model.activePanel?.id &&
this.group.model.isContentFocused; this.group.model.isContentFocused;
const isLeftClick = event.event.button === 0; const isLeftClick = event.button === 0;
if (!isLeftClick || event.event.defaultPrevented) { if (!isLeftClick || event.defaultPrevented) {
return; return;
} }
if (event.kind === MouseEventKind.CLICK) { this.group.model.openPanel(panel, {
this.group.model.openPanel(panel, { skipFocus: alreadyFocused,
skipFocus: alreadyFocused, });
});
}
}), }),
tabToAdd.onDrop((event) => { tabToAdd.onDrop((event) => {
this._onDrop.fire({ this._onDrop.fire({

View File

@ -41,17 +41,26 @@ export class VoidContainer extends CompositeDisposable {
const handler = new GroupDragHandler(this._element, accessor.id, group); const handler = new GroupDragHandler(this._element, accessor.id, group);
this.voidDropTarget = new Droptarget(this._element, { this.voidDropTarget = new Droptarget(this._element, {
validOverlays: 'none', acceptedTargetZones: ['center'],
canDisplayOverlay: (event) => { canDisplayOverlay: (event, position) => {
const data = getPanelData(); const data = getPanelData();
if (data && this.accessor.id === data.viewId) { if (data && this.accessor.id === data.viewId) {
if (
data.panelId === null &&
data.groupId === this.group.id
) {
// don't allow group move to drop on self
return false;
}
// don't show the overlay if the tab being dragged is the last panel of this group // don't show the overlay if the tab being dragged is the last panel of this group
return last(this.group.panels)?.id !== data.panelId; return last(this.group.panels)?.id !== data.panelId;
} }
return group.model.canDisplayOverlay( return group.model.canDisplayOverlay(
event, event,
position,
DockviewDropTargets.Panel DockviewDropTargets.Panel
); );
}, },

View File

@ -27,7 +27,7 @@ export * from './react'; // TODO: should be conditional on whether user wants th
export { Event } from './events'; export { Event } from './events';
export { IDisposable } from './lifecycle'; export { IDisposable } from './lifecycle';
export { Position } from './dnd/droptarget'; export { Position as DropTargetDirections } from './dnd/droptarget';
export { export {
FocusEvent, FocusEvent,
PanelDimensionChangeEvent, PanelDimensionChangeEvent,

View File

@ -4,7 +4,7 @@ import {
LocalSelectionTransfer, LocalSelectionTransfer,
PaneTransfer, PaneTransfer,
} from '../dnd/dataTransfer'; } from '../dnd/dataTransfer';
import { Droptarget, DroptargetEvent, Position } from '../dnd/droptarget'; import { Droptarget, DroptargetEvent } from '../dnd/droptarget';
import { Emitter } from '../events'; import { Emitter } from '../events';
import { IDisposable } from '../lifecycle'; import { IDisposable } from '../lifecycle';
import { Orientation } from '../splitview/core/splitview'; import { Orientation } from '../splitview/core/splitview';
@ -70,7 +70,10 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
})(this.header); })(this.header);
this.target = new Droptarget(this.element, { this.target = new Droptarget(this.element, {
validOverlays: 'vertical', acceptedTargetZones: ['top', 'bottom'],
overlayModel: {
activationSize: { type: 'percentage', value: 50 },
},
canDisplayOverlay: (event) => { canDisplayOverlay: (event) => {
const data = getPaneData(); const data = getPaneData();
@ -139,16 +142,10 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel {
const fromIndex = allPanels.indexOf(existingPanel); const fromIndex = allPanels.indexOf(existingPanel);
let toIndex = containerApi.panels.indexOf(this); let toIndex = containerApi.panels.indexOf(this);
if ( if (event.position === 'left' || event.position === 'top') {
event.position === Position.Left ||
event.position === Position.Top
) {
toIndex = Math.max(0, toIndex - 1); toIndex = Math.max(0, toIndex - 1);
} }
if ( if (event.position === 'right' || event.position === 'bottom') {
event.position === Position.Right ||
event.position === Position.Bottom
) {
if (fromIndex > toIndex) { if (fromIndex > toIndex) {
toIndex++; toIndex++;
} }

View File

@ -164,6 +164,8 @@ export abstract class PaneviewPanel
) { ) {
super(id, component, new PaneviewPanelApiImpl(id)); super(id, component, new PaneviewPanelApiImpl(id));
this.api.pane = this; // TODO cannot use 'this' before 'super' this.api.pane = this; // TODO cannot use 'this' before 'super'
this.api.initialize(this);
this._isExpanded = isExpanded; this._isExpanded = isExpanded;
this._headerVisible = isHeaderVisible; this._headerVisible = isHeaderVisible;

View File

@ -1,6 +1,6 @@
import { DockviewComponent } from '../dockview/dockviewComponent'; import { DockviewComponent } from '../dockview/dockviewComponent';
import { GroupviewPanelState, IDockviewPanel } from '../groupview/groupPanel'; import { GroupviewPanelState, IDockviewPanel } from '../groupview/groupPanel';
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel'; import { DockviewPanel } from '../dockview/dockviewPanel';
import { IPanelDeserializer } from '../dockview/deserializer'; import { IPanelDeserializer } from '../dockview/deserializer';
import { createComponent } from '../panel/componentFactory'; import { createComponent } from '../panel/componentFactory';
import { DockviewApi } from '../api/component.api'; import { DockviewApi } from '../api/component.api';
@ -56,7 +56,7 @@ export class ReactPanelDeserialzier implements IPanelDeserializer {
tab, tab,
}); });
const panel = new DockviewGroupPanel( const panel = new DockviewPanel(
panelId, panelId,
this.layout, this.layout,
new DockviewApi(this.layout), new DockviewApi(this.layout),

View File

@ -69,6 +69,7 @@ export interface IDockviewReactProps {
disableAutoResizing?: boolean; disableAutoResizing?: boolean;
defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>; defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>;
groupControlComponent?: React.FunctionComponent<IDockviewGroupControlProps>; groupControlComponent?: React.FunctionComponent<IDockviewGroupControlProps>;
singleTabMode?: 'fullwidth' | 'default';
} }
export const DockviewReact = React.forwardRef( export const DockviewReact = React.forwardRef(
@ -161,6 +162,7 @@ export const DockviewReact = React.forwardRef(
props.groupControlComponent, props.groupControlComponent,
{ addPortal } { addPortal }
), ),
singleTabMode: props.singleTabMode,
}); });
domRef.current?.appendChild(dockview.element); domRef.current?.appendChild(dockview.element);

View File

@ -1,4 +1,5 @@
import { GridviewApi } from '../../api/component.api'; import { GridviewApi } from '../../api/component.api';
import { GridviewPanelApiImpl } from '../../api/gridviewPanelApi';
import { import {
GridviewPanel, GridviewPanel,
GridviewInitParameters, GridviewInitParameters,
@ -14,7 +15,7 @@ export class ReactGridPanelView extends GridviewPanel {
private readonly reactComponent: React.FunctionComponent<IGridviewPanelProps>, private readonly reactComponent: React.FunctionComponent<IGridviewPanelProps>,
private readonly reactPortalStore: ReactPortalStore private readonly reactPortalStore: ReactPortalStore
) { ) {
super(id, component); super(id, component, new GridviewPanelApiImpl(id));
} }
getComponent(): IFrameworkPart { getComponent(): IFrameworkPart {

View File

@ -85,6 +85,8 @@ export abstract class SplitviewPanel
constructor(id: string, componentName: string) { constructor(id: string, componentName: string) {
super(id, componentName, new SplitviewPanelApiImpl(id)); super(id, componentName, new SplitviewPanelApiImpl(id));
this.api.initialize(this);
this.addDisposables( this.addDisposables(
this._onDidChange, this._onDidChange,
this.api.onVisibilityChange((event) => { this.api.onVisibilityChange((event) => {