test: floating group tests

This commit is contained in:
mathuo 2023-07-02 21:10:06 +01:00
parent b2b58a4e57
commit 1462b6a37a
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
23 changed files with 1638 additions and 68 deletions

View File

@ -118,4 +118,62 @@ describe('abstractDragHandler', () => {
expect(webview.style.pointerEvents).toBe('auto');
expect(span.style.pointerEvents).toBeFalsy();
});
test('that .preventDefault() is called for cancelled events', () => {
const element = document.createElement('div');
const handler = new (class TestClass extends DragHandler {
constructor(el: HTMLElement) {
super(el);
}
protected isCancelled(_event: DragEvent): boolean {
return true;
}
getData(): IDisposable {
return {
dispose: () => {
// /
},
};
}
})(element);
const event = new Event('dragstart');
const spy = jest.spyOn(event, 'preventDefault');
fireEvent(element, event);
expect(spy).toBeCalledTimes(1);
handler.dispose();
});
test('that .preventDefault() is not called for non-cancelled events', () => {
const element = document.createElement('div');
const handler = new (class TestClass extends DragHandler {
constructor(el: HTMLElement) {
super(el);
}
protected isCancelled(_event: DragEvent): boolean {
return false;
}
getData(): IDisposable {
return {
dispose: () => {
// /
},
};
}
})(element);
const event = new Event('dragstart');
const spy = jest.spyOn(event, 'preventDefault');
fireEvent(element, event);
expect(spy).toBeCalledTimes(0);
handler.dispose();
});
});

View File

@ -34,6 +34,48 @@ describe('droptarget', () => {
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200);
});
test('that dragover events are marked', () => {
droptarget = new Droptarget(element, {
canDisplayOverlay: () => true,
acceptedTargetZones: ['center'],
});
fireEvent.dragEnter(element);
const event = new Event('dragover');
fireEvent(element, event);
expect(
(event as any)['__dockview_droptarget_event_is_used__']
).toBeTruthy();
});
test('that the drop target is removed when receiving a marked dragover event', () => {
let position: Position | undefined = undefined;
droptarget = new Droptarget(element, {
canDisplayOverlay: () => true,
acceptedTargetZones: ['center'],
});
droptarget.onDrop((event) => {
position = event.position;
});
fireEvent.dragEnter(element);
fireEvent.dragOver(element);
const target = element.querySelector(
'.drop-target-dropzone'
) as HTMLElement;
fireEvent.drop(target);
expect(position).toBe('center');
const event = new Event('dragover');
(event as any)['__dockview_droptarget_event_is_used__'] = true;
fireEvent(element, event);
expect(element.querySelector('.drop-target-dropzone')).toBeNull();
});
test('directionToPosition', () => {
expect(directionToPosition('above')).toBe('top');
expect(directionToPosition('below')).toBe('bottom');

View File

@ -0,0 +1,101 @@
import { fireEvent } from '@testing-library/dom';
import { GroupDragHandler } from '../../dnd/groupDragHandler';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer';
describe('groupDragHandler', () => {
test('that the dnd transfer object is setup and torndown', () => {
const element = document.createElement('div');
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
id: 'test_group_id',
isFloating: false,
};
return partial as DockviewGroupPanel;
});
const group = new groupMock();
const cut = new GroupDragHandler(element, 'test_accessor_id', group);
fireEvent.dragStart(element, new Event('dragstart'));
expect(
LocalSelectionTransfer.getInstance<PanelTransfer>().hasData(
PanelTransfer.prototype
)
).toBeTruthy();
const transferObject =
LocalSelectionTransfer.getInstance<PanelTransfer>().getData(
PanelTransfer.prototype
)![0];
expect(transferObject).toBeTruthy();
expect(transferObject.viewId).toBe('test_accessor_id');
expect(transferObject.groupId).toBe('test_group_id');
expect(transferObject.panelId).toBeNull();
fireEvent.dragStart(element, new Event('dragend'));
expect(
LocalSelectionTransfer.getInstance<PanelTransfer>().hasData(
PanelTransfer.prototype
)
).toBeFalsy();
cut.dispose();
});
test('that the event is cancelled when isFloating and shiftKey=true', () => {
const element = document.createElement('div');
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
isFloating: true,
};
return partial as DockviewGroupPanel;
});
const group = new groupMock();
const cut = new GroupDragHandler(element, 'accessor_id', group);
const event = new KeyboardEvent('dragstart', { shiftKey: false });
const spy = jest.spyOn(event, 'preventDefault');
fireEvent(element, event);
expect(spy).toBeCalledTimes(1);
const event2 = new KeyboardEvent('dragstart', { shiftKey: true });
const spy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(element, event);
expect(spy2).toBeCalledTimes(0);
cut.dispose();
});
test('that the event is never cancelled when the group is not floating', () => {
const element = document.createElement('div');
const groupMock = jest.fn<DockviewGroupPanel, []>(() => {
const partial: Partial<DockviewGroupPanel> = {
isFloating: false,
};
return partial as DockviewGroupPanel;
});
const group = new groupMock();
const cut = new GroupDragHandler(element, 'accessor_id', group);
const event = new KeyboardEvent('dragstart', { shiftKey: false });
const spy = jest.spyOn(event, 'preventDefault');
fireEvent(element, event);
expect(spy).toBeCalledTimes(0);
const event2 = new KeyboardEvent('dragstart', { shiftKey: true });
const spy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(element, event);
expect(spy2).toBeCalledTimes(0);
cut.dispose();
});
});

View File

@ -0,0 +1,78 @@
import { Overlay } from '../../dnd/overlay';
describe('overlay', () => {
test('toJSON', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
left: 10,
top: 20,
minX: 0,
minY: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return { left: 80, top: 100, width: 40, height: 50 } as any;
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 20, top: 30, width: 100, height: 100 } as any;
}
);
expect(cut.toJSON()).toEqual({
top: 70,
left: 60,
width: 40,
height: 50,
});
});
test('that the resize handles are added', () => {
const container = document.createElement('div');
const content = document.createElement('div');
const cut = new Overlay({
height: 500,
width: 500,
left: 100,
top: 200,
minX: 0,
minY: 0,
container,
content,
});
expect(container.querySelector('.dv-resize-handle-top')).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-bottom')
).toBeTruthy();
expect(container.querySelector('.dv-resize-handle-left')).toBeTruthy();
expect(container.querySelector('.dv-resize-handle-right')).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-topleft')
).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-topright')
).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-bottomleft')
).toBeTruthy();
expect(
container.querySelector('.dv-resize-handle-bottomright')
).toBeTruthy();
cut.dispose();
});
});

View File

@ -8,6 +8,7 @@ import { DockviewGroupPanel } from '../../../../dockview/dockviewGroupPanel';
import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanelModel';
import { fireEvent } from '@testing-library/dom';
import { TestPanel } from '../../dockviewGroupPanelModel.spec';
import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
describe('tabsContainer', () => {
test('that an external event does not render a drop target and calls through to the group mode', () => {
@ -463,4 +464,165 @@ describe('tabsContainer', () => {
expect(query.length).toBe(1);
expect(query[0].children.length).toBe(0);
});
test('that a tab will become floating when clicked if not floating and shift is selected', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
}) as DockviewComponent;
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
isFloating: false,
}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
const container = cut.element.querySelector('.void-container')!;
expect(container).toBeTruthy();
jest.spyOn(cut.element, 'getBoundingClientRect').mockImplementation(
() => {
return { top: 50, left: 100, width: 0, height: 0 } as any;
}
);
jest.spyOn(
accessor.element,
'getBoundingClientRect'
).mockImplementation(() => {
return { top: 10, left: 20, width: 0, height: 0 } as any;
});
const event = new KeyboardEvent('mousedown', { shiftKey: true });
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event);
expect(accessor.addFloatingGroup).toBeCalledWith(groupPanel, {
x: 100,
y: 60,
});
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
expect(eventPreventDefaultSpy).toBeCalledTimes(1);
const event2 = new KeyboardEvent('mousedown', { shiftKey: false });
const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(container, event2);
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
expect(eventPreventDefaultSpy2).toBeCalledTimes(0);
});
test('that a tab that is already floating cannot be floated again', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
}) as DockviewComponent;
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
isFloating: true,
}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
const container = cut.element.querySelector('.void-container')!;
expect(container).toBeTruthy();
jest.spyOn(cut.element, 'getBoundingClientRect').mockImplementation(
() => {
return { top: 50, left: 100, width: 0, height: 0 } as any;
}
);
jest.spyOn(
accessor.element,
'getBoundingClientRect'
).mockImplementation(() => {
return { top: 10, left: 20, width: 0, height: 0 } as any;
});
const event = new KeyboardEvent('mousedown', { shiftKey: true });
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event);
expect(accessor.addFloatingGroup).toBeCalledTimes(0);
expect(eventPreventDefaultSpy).toBeCalledTimes(0);
const event2 = new KeyboardEvent('mousedown', { shiftKey: false });
const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(container, event2);
expect(accessor.addFloatingGroup).toBeCalledTimes(0);
expect(eventPreventDefaultSpy2).toBeCalledTimes(0);
});
test('that selecting a tab which shift down will move that tab into a new floating group', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
getGroupPanel: jest.fn(),
}) as DockviewComponent;
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{
isFloating: true,
}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
const panelMock = jest.fn<IDockviewPanel, []>(() => {
const partial: Partial<IDockviewPanel> = {
id: 'test_id',
view: {
tab: {
element: document.createElement('div'),
} as any,
content: {
element: document.createElement('div'),
} as any,
} as any,
};
return partial as IDockviewPanel;
});
const panel = new panelMock();
cut.openPanel(panel);
const el = cut.element.querySelector('.tab')!;
expect(el).toBeTruthy();
const event = new KeyboardEvent('mousedown', { shiftKey: true });
const preventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(el, event);
expect(preventDefaultSpy).toBeCalledTimes(1);
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
});
});

View File

@ -2647,4 +2647,992 @@ describe('dockviewComponent', () => {
dockview.removePanel(panel);
expect(dockview.groups.length).toBe(0);
});
test('floating: move a floating group of one tab to a new fixed group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
undefined,
'right'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
test('floating: move a floating group of one tab to an existing fixed group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
undefined,
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
});
test('floating: move a floating group of one tab to an existing floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel2.group,
panel3.group.id,
undefined,
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a floating group of many tabs to a new fixed group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
position: { referencePanel: panel2 },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
undefined,
'right'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a floating group of many tabs to an existing fixed group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
position: { referencePanel: panel2 },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
undefined,
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a floating group of many tabs to an existing floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
position: { referencePanel: panel2 },
});
const panel4 = dockview.addPanel({
id: 'panel_4',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4);
dockview.moveGroupOrPanel(
panel4.group,
panel2.group.id,
undefined,
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(4);
});
test('floating: move a floating tab of one tab to a new fixed group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
panel2.id,
'right'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
test('floating: move a floating tab of one tab to an existing fixed group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
panel2.id,
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
});
test('floating: move a floating tab of one tab to an existing floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel2.group,
panel3.group.id,
panel3.id,
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a floating tab of many tabs to a new fixed group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
position: { referencePanel: panel2 },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
panel2.id,
'right'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a floating tab of many tabs to an existing fixed group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
position: { referencePanel: panel2 },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel1.group,
panel2.group.id,
panel2.id,
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a floating tab of many tabs to an existing floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
floating: true,
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
position: { referencePanel: panel2 },
});
const panel4 = dockview.addPanel({
id: 'panel_4',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4);
dockview.moveGroupOrPanel(
panel4.group,
panel2.group.id,
panel2.id,
'center'
);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(panel4.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(4);
});
test('floating: move a fixed tab of one tab to an existing floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
position: { direction: 'right' },
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
panel1.id,
'center'
);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a fixed tab of many tabs to an existing floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
panel1.id,
'center'
);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a fixed group of one tab to an existing floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
position: { direction: 'right' },
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(3);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
undefined,
'center'
);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a fixed group of many tabs to an existing floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
floating: true,
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(3);
dockview.moveGroupOrPanel(
panel3.group,
panel1.group.id,
undefined,
'center'
);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(panel3.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(3);
});
test('floating: move a fixed tab of one tab to a new floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
position: { direction: 'right' },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
test('floating: move a fixed tab of many tabs to a new floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
test('floating: move a fixed group of one tab to a new floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
position: { direction: 'right' },
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2.group);
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(2);
expect(dockview.panels.length).toBe(2);
});
test('floating: move a fixed group of many tabs to a new floating group', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
});
expect(panel1.group.model.isFloating).toBeFalsy();
expect(panel2.group.model.isFloating).toBeFalsy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
dockview.addFloatingGroup(panel2.group);
expect(panel1.group.model.isFloating).toBeTruthy();
expect(panel2.group.model.isFloating).toBeTruthy();
expect(dockview.groups.length).toBe(1);
expect(dockview.panels.length).toBe(2);
});
});

View File

@ -247,8 +247,8 @@ describe('groupview', () => {
id: 'dockview-1',
removePanel: removePanelMock,
removeGroup: removeGroupMock,
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
onDidAddPanel: () => ({ dispose: jest.fn() }),
onDidRemovePanel: () => ({ dispose: jest.fn() }),
}) as DockviewComponent;
groupview = new DockviewGroupPanel(dockview, 'groupview-1', options);
@ -858,6 +858,47 @@ describe('groupview', () => {
).toBe(0);
});
test('that the watermark is removed when dispose is called', () => {
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {
return {
canDisplayOverlay: jest.fn(),
};
}
);
const groupView = new groupviewMock() as DockviewGroupPanelModel;
const groupPanelMock = jest.fn<Partial<DockviewGroupPanel>, []>(() => {
return {
id: 'testgroupid',
model: groupView,
};
});
const container = document.createElement('div');
const cut = new DockviewGroupPanelModel(
container,
dockview,
'groupviewid',
{},
new groupPanelMock() as DockviewGroupPanel
);
cut.initialize();
expect(
container.getElementsByClassName('watermark-test-container').length
).toBe(1);
cut.dispose();
expect(
container.getElementsByClassName('watermark-test-container').length
).toBe(0);
});
test('that watermark is added', () => {
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => {

View File

@ -7,7 +7,7 @@
top: 0px;
height: 100%;
width: 100%;
z-index: 10000;
z-index: 1000;
pointer-events: none;
> .drop-target-selection {

View File

@ -54,17 +54,6 @@ export type CanDisplayOverlay =
| boolean
| ((dragEvent: DragEvent, state: Position) => boolean);
const eventMarkTag = 'dv_droptarget_marked';
function markEvent(event: DragEvent): void {
(event as any)[eventMarkTag] = true;
}
function isEventMarked(event: DragEvent) {
const value = (event as any)[eventMarkTag];
return typeof value === 'boolean' && value;
}
export class Droptarget extends CompositeDisposable {
private targetElement: HTMLElement | undefined;
private overlayElement: HTMLElement | undefined;
@ -74,6 +63,8 @@ export class Droptarget extends CompositeDisposable {
private readonly _onDrop = new Emitter<DroptargetEvent>();
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__';
get state(): Position | undefined {
return this._state;
}
@ -125,7 +116,12 @@ export class Droptarget extends CompositeDisposable {
height
);
if (isEventMarked(e) || quadrant === null) {
/**
* If the event has already been used by another DropTarget instance
* then don't show a second drop target, only one target should be
* active at any one time
*/
if (this.isAlreadyUsed(e) || quadrant === null) {
// no drop target should be displayed
this.removeDropTarget();
return;
@ -139,7 +135,7 @@ export class Droptarget extends CompositeDisposable {
return;
}
markEvent(e);
this.markAsUsed(e);
if (!this.targetElement) {
this.targetElement = document.createElement('div');
@ -193,11 +189,26 @@ export class Droptarget extends CompositeDisposable {
this._acceptedTargetZonesSet = new Set(acceptedTargetZones);
}
public dispose(): void {
dispose(): void {
this.removeDropTarget();
super.dispose();
}
/**
* Add a property to the event object for other potential listeners to check
*/
private markAsUsed(event: DragEvent): void {
(event as any)[Droptarget.USED_EVENT_ID] = true;
}
/**
* Check is the event has already been used by another instance od DropTarget
*/
private isAlreadyUsed(event: DragEvent): boolean {
const value = (event as any)[Droptarget.USED_EVENT_ID];
return typeof value === 'boolean' && value;
}
private toggleClasses(
quadrant: Position,
width: number,

View File

@ -17,7 +17,7 @@ export class GroupDragHandler extends DragHandler {
}
override isCancelled(_event: DragEvent): boolean {
if (this.group.model.isFloating && !_event.shiftKey) {
if (this.group.isFloating && !_event.shiftKey) {
return true;
}
return false;

View File

@ -27,10 +27,10 @@
.dv-resize-container {
position: absolute;
z-index: 9997;
z-index: 997;
&.dv-resize-container-priority {
z-index: 9998;
&.dv-bring-to-front {
z-index: 998;
}
border: 1px solid var(--dv-tab-divider-color);
@ -45,7 +45,7 @@
width: calc(100% - 8px);
left: 4px;
top: -2px;
z-index: 9999;
z-index: 999;
position: absolute;
cursor: ns-resize;
}
@ -55,7 +55,7 @@
width: calc(100% - 8px);
left: 4px;
bottom: -2px;
z-index: 9999;
z-index: 999;
position: absolute;
cursor: ns-resize;
}
@ -65,7 +65,7 @@
width: 4px;
left: -2px;
top: 4px;
z-index: 9999;
z-index: 999;
position: absolute;
cursor: ew-resize;
}
@ -75,7 +75,7 @@
width: 4px;
right: -2px;
top: 4px;
z-index: 9999;
z-index: 999;
position: absolute;
cursor: ew-resize;
}
@ -85,7 +85,7 @@
width: 4px;
top: -2px;
left: -2px;
z-index: 9999;
z-index: 999;
position: absolute;
cursor: nw-resize;
}
@ -95,7 +95,7 @@
width: 4px;
right: -2px;
top: -2px;
z-index: 9999;
z-index: 999;
position: absolute;
cursor: ne-resize;
}
@ -105,7 +105,7 @@
width: 4px;
left: -2px;
bottom: -2px;
z-index: 9999;
z-index: 999;
position: absolute;
cursor: sw-resize;
}
@ -115,7 +115,7 @@
width: 4px;
right: -2px;
bottom: -2px;
z-index: 9999;
z-index: 999;
position: absolute;
cursor: se-resize;
}

View File

@ -13,10 +13,10 @@ const bringElementToFront = (() => {
function pushToTop(element: HTMLElement) {
if (previous !== element && previous !== null) {
toggleClass(previous, 'dv-resize-container-priority', false);
toggleClass(previous, 'dv-bring-to-front', false);
}
toggleClass(element, 'dv-resize-container-priority', true);
toggleClass(element, 'dv-bring-to-front', true);
previous = element;
}
@ -46,7 +46,6 @@ export class Overlay extends CompositeDisposable {
this.addDisposables(this._onDidChange);
this.setupOverlay();
// this.setupDrag(true,this._element);
this.setupResize('top');
this.setupResize('bottom');
this.setupResize('left');
@ -58,8 +57,6 @@ export class Overlay extends CompositeDisposable {
this._element.appendChild(this.options.content);
this.options.container.appendChild(this._element);
// this.renderWithinBoundaryConditions();
}
toJSON(): { top: number; left: number; height: number; width: number } {
@ -250,11 +247,14 @@ export class Overlay extends CompositeDisposable {
this._element.style.width = `${this.options.width}px`;
this._element.style.left = `${this.options.left}px`;
this._element.style.top = `${this.options.top}px`;
//
this._element.className = 'dv-resize-container';
}
setupDrag(connect: boolean, dragTarget: HTMLElement): void {
setupDrag(
dragTarget: HTMLElement,
options: { inDragMode: boolean } = { inDragMode: false }
): void {
const move = new MutableDisposable();
const track = () => {
@ -327,10 +327,7 @@ export class Overlay extends CompositeDisposable {
this.addDisposables(
move,
addDisposableListener(dragTarget, 'mousedown', (event) => {
if (
// event.shiftKey ||
event.defaultPrevented
) {
if (event.defaultPrevented) {
event.preventDefault();
return;
}
@ -362,7 +359,7 @@ export class Overlay extends CompositeDisposable {
bringElementToFront(this._element);
if (connect) {
if (options.inDragMode) {
track();
}
}

View File

@ -191,7 +191,7 @@ export class TabsContainer
this.voidContainer.element,
'mousedown',
(event) => {
if (event.shiftKey && !this.group.model.isFloating) {
if (event.shiftKey && !this.group.isFloating) {
event.preventDefault();
const { top, left } =
@ -203,7 +203,6 @@ export class TabsContainer
x: left - rootLeft + 20,
y: top - rootTop + 20,
});
event.preventDefault();
}
}
),

View File

@ -125,7 +125,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly onDidLayoutFromJSON: Event<void>;
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined>;
addFloatingGroup(
item: DockviewPanel | DockviewGroupPanel,
item: IDockviewPanel | DockviewGroupPanel,
coord?: { x: number; y: number }
): void;
}
@ -299,7 +299,7 @@ export class DockviewComponent
addFloatingGroup(
item: DockviewPanel | DockviewGroupPanel,
coord?: { x?: number; y?: number; height?: number; width?: number },
options?: { skipRemoveGroup: boolean; connect: boolean }
options?: { skipRemoveGroup: boolean; inDragMode: boolean }
): void {
let group: DockviewGroupPanel;
@ -326,8 +326,6 @@ export class DockviewComponent
group.model.isFloating = true;
const { left, top } = this.element.getBoundingClientRect();
const overlayLeft =
typeof coord?.x === 'number' ? Math.max(coord.x, 0) : 100;
const overlayTop =
@ -347,10 +345,12 @@ export class DockviewComponent
const el = group.element.querySelector('#dv-group-float-drag-handle');
if (el) {
overlay.setupDrag(
typeof options?.connect === 'boolean' ? options.connect : true,
el as HTMLElement
);
overlay.setupDrag(el as HTMLElement, {
inDragMode:
typeof options?.inDragMode === 'boolean'
? options.inDragMode
: true,
});
}
const instance = {
@ -582,14 +582,12 @@ export class DockviewComponent
this.layout(width, height);
const serializedFloatingGroups = data.floatingGroups || [];
const serializedFloatingGroups = data.floatingGroups ?? [];
for (const serializedFloatingGroup of serializedFloatingGroups) {
const { data, position } = serializedFloatingGroup;
const group = createGroupFromSerializedState(data);
const { left, top } = this.element.getBoundingClientRect();
this.addFloatingGroup(
group,
{
@ -598,7 +596,7 @@ export class DockviewComponent
height: position.height,
width: position.width,
},
{ skipRemoveGroup: true, connect: false }
{ skipRemoveGroup: true, inDragMode: false }
);
}
@ -646,7 +644,7 @@ export class DockviewComponent
}
}
addPanel(options: AddPanelOptions): IDockviewPanel {
addPanel(options: AddPanelOptions): DockviewPanel {
if (this.panels.find((_) => _.id === options.id)) {
throw new Error(`panel with id ${options.id} already exists`);
}
@ -697,7 +695,7 @@ export class DockviewComponent
referenceGroup = this.activeGroup;
}
let panel: IDockviewPanel;
let panel: DockviewPanel;
if (referenceGroup) {
const target = toTarget(
@ -716,7 +714,7 @@ export class DockviewComponent
: {};
this.addFloatingGroup(group, o, {
connect: false,
inDragMode: false,
skipRemoveGroup: true,
});
} else if (referenceGroup.model.isFloating || target === 'center') {
@ -745,7 +743,7 @@ export class DockviewComponent
: {};
this.addFloatingGroup(group, o, {
connect: false,
inDragMode: false,
skipRemoveGroup: true,
});
} else {
@ -886,8 +884,8 @@ export class DockviewComponent
group: DockviewGroupPanel,
options?:
| {
skipActive?: boolean | undefined;
skipDispose?: boolean | undefined;
skipActive?: boolean;
skipDispose?: boolean;
}
| undefined
): void {
@ -907,8 +905,8 @@ export class DockviewComponent
group: DockviewGroupPanel,
options?:
| {
skipActive?: boolean | undefined;
skipDispose?: boolean | undefined;
skipActive?: boolean;
skipDispose?: boolean;
}
| undefined
): DockviewGroupPanel {
@ -1161,7 +1159,7 @@ export class DockviewComponent
private createPanel(
options: AddPanelOptions,
group: DockviewGroupPanel
): IDockviewPanel {
): DockviewPanel {
const contentComponent = options.component;
const tabComponent =
options.tabComponent || this.options.defaultTabComponent;

View File

@ -4,7 +4,6 @@ import { GridviewPanelApi } from '../api/gridviewPanelApi';
import {
DockviewGroupPanelModel,
GroupOptions,
GroupPanelViewState,
IDockviewGroupPanelModel,
IHeader,
} from './dockviewGroupPanelModel';
@ -53,6 +52,10 @@ export class DockviewGroupPanel
this._model.locked = value;
}
get isFloating(): boolean {
return this._model.isFloating;
}
get header(): IHeader {
return this._model.header;
}

View File

@ -792,6 +792,7 @@ export class DockviewGroupPanelModel
public dispose(): void {
super.dispose();
this.watermark?.element.remove();
this.watermark?.dispose?.();
for (const panel of this.panels) {

View File

@ -18,7 +18,6 @@ import { IDisposable } from '../lifecycle';
import { Position } from '../dnd/droptarget';
import { IDockviewPanel } from './dockviewPanel';
import { FrameworkFactory } from '../panel/componentFactory';
import { Optional } from '../types';
export interface IHeaderActionsRenderer extends IDisposable {
readonly element: HTMLElement;
@ -88,6 +87,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
) => IHeaderActionsRenderer;
singleTabMode?: 'fullwidth' | 'default';
parentElement?: HTMLElement;
disableFloatingGroups?: boolean;
}
export interface PanelOptions {

View File

@ -74,7 +74,7 @@ export class Emitter<T> implements IDisposable {
static ENABLE_TRACKING = false;
static readonly MEMORY_LEAK_WATCHER = new LeakageMonitor();
static setLeakageMonitorEnabled(isEnabled: boolean) {
static setLeakageMonitorEnabled(isEnabled: boolean): void {
if (isEnabled !== Emitter.ENABLE_TRACKING) {
Emitter.MEMORY_LEAK_WATCHER.clear();
}

View File

@ -5,7 +5,7 @@ export const clamp = (value: number, min: number, max: number): number => {
return Math.min(max, Math.max(value, min));
};
export const sequentialNumberGenerator = () => {
export const sequentialNumberGenerator = (): { next: () => string } => {
let value = 1;
return { next: () => (value++).toString() };
};

View File

@ -367,6 +367,24 @@ any drag and drop logic for other controls.
Dockview has built-in support for floating groups. Each floating container can contain a single group with many panels
and you can have as many floating containers as needed. You cannot dock multiple groups together in the same floating container.
Floating groups can be interacted with whilst holding the `shift` key activating the `event.shiftKey` boolean property on `KeyboardEvent` events.
> Float an existing tab by holding `shift` whilst interacting with the tab
<img style={{ width: '60%' }} src={useBaseUrl('/img/float_add.svg')} />
> Move a floating tab by holding `shift` whilst moving the cursor or dragging the empty
> header space
<img style={{ width: '60%' }} src={useBaseUrl('/img/float_move.svg')} />
> Move an entire floating group by holding `shift` whilst dragging the empty header space
<img style={{ width: '60%' }} src={useBaseUrl('/img/float_group.svg')} />
Floating groups can be programatically added through the dockview `api` method `api.addFloatingGroup(...)` and you can check whether
a group is floating via the `group.isFloating` property. See examples for full code.
<Container height={600} sandboxId="floatinggroup-dockview">
<DockviewFloating />
</Container>

20
packages/docs/static/img/float_add.svg vendored Normal file
View File

@ -0,0 +1,20 @@
<svg width="156" height="76" viewBox="0 0 156 76" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="14" width="156" height="62" fill="#000C18"/>
<rect width="156" height="14" fill="#1C1C2A"/>
<rect width="30" height="14" fill="#10192C"/>
<rect x="31" width="30" height="14" fill="#10192C"/>
<rect x="30" width="1" height="14" fill="#2B2B4A"/>
<rect x="61" width="1" height="14" fill="#2B2B4A"/>
<rect x="41" y="54" width="30" height="14" fill="#000C18"/>
<rect x="33" y="5" width="15" height="4" rx="2" fill="#777777"/>
<rect x="2" y="5" width="6" height="4" rx="2" fill="#777777"/>
<rect x="10" y="5" width="18" height="4" rx="2" fill="#777777"/>
<rect x="68.5" y="7.5" width="83" height="60" fill="#000C18" stroke="#2B2B4A"/>
<rect x="100" y="8" width="51" height="14" fill="#1C1C2A"/>
<rect x="83" y="13" width="12" height="4" rx="2" fill="white"/>
<rect x="73" y="13" width="7" height="4" rx="2" fill="white"/>
<rect x="99" y="8" width="1" height="14" fill="#2B2B4A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.3445 8.26544C70.5198 8.14263 70.5104 7.87986 70.3266 7.76998L66.1804 5.29049C65.9614 5.15953 65.6906 5.34916 65.7388 5.59974L66.6506 10.344C66.691 10.5542 66.9347 10.653 67.1101 10.5302L67.7716 10.067C67.8669 10.0002 67.9142 9.88362 67.8922 9.76929L67.6024 8.26123C67.5542 8.01064 67.825 7.82101 68.044 7.95197L69.362 8.74015C69.4619 8.79989 69.5876 8.79538 69.683 8.7286L70.3445 8.26544Z" fill="white"/>
<rect x="69.5" y="9.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<path d="M70.9414 4.47461V4.81445H68.9922V4.47461H70.9414ZM70.1484 3.64453V5.71484H69.7871V3.64453H70.1484ZM73.9473 5.28125C73.9473 5.21484 73.9368 5.15625 73.916 5.10547C73.8965 5.05339 73.8613 5.00651 73.8105 4.96484C73.7611 4.92318 73.6921 4.88346 73.6035 4.8457C73.5163 4.80794 73.4056 4.76953 73.2715 4.73047C73.1309 4.6888 73.0039 4.64258 72.8906 4.5918C72.7773 4.53971 72.6803 4.48047 72.5996 4.41406C72.5189 4.34766 72.457 4.27148 72.4141 4.18555C72.3711 4.09961 72.3496 4.0013 72.3496 3.89062C72.3496 3.77995 72.3724 3.67773 72.418 3.58398C72.4635 3.49023 72.5286 3.40885 72.6133 3.33984C72.6992 3.26953 72.8014 3.21484 72.9199 3.17578C73.0384 3.13672 73.1706 3.11719 73.3164 3.11719C73.5299 3.11719 73.7109 3.1582 73.8594 3.24023C74.0091 3.32096 74.123 3.42708 74.2012 3.55859C74.2793 3.6888 74.3184 3.82812 74.3184 3.97656H73.9434C73.9434 3.86979 73.9206 3.77539 73.875 3.69336C73.8294 3.61003 73.7604 3.54492 73.668 3.49805C73.5755 3.44987 73.4583 3.42578 73.3164 3.42578C73.1823 3.42578 73.0716 3.44596 72.9844 3.48633C72.8971 3.52669 72.832 3.58138 72.7891 3.65039C72.7474 3.7194 72.7266 3.79818 72.7266 3.88672C72.7266 3.94661 72.7389 4.0013 72.7637 4.05078C72.7897 4.09896 72.8294 4.14388 72.8828 4.18555C72.9375 4.22721 73.0065 4.26562 73.0898 4.30078C73.1745 4.33594 73.2754 4.36979 73.3926 4.40234C73.554 4.44792 73.6934 4.4987 73.8105 4.55469C73.9277 4.61068 74.0241 4.67383 74.0996 4.74414C74.1764 4.81315 74.2331 4.89193 74.2695 4.98047C74.3073 5.06771 74.3262 5.16667 74.3262 5.27734C74.3262 5.39323 74.3027 5.49805 74.2559 5.5918C74.209 5.68555 74.1419 5.76562 74.0547 5.83203C73.9674 5.89844 73.8626 5.94987 73.7402 5.98633C73.6191 6.02148 73.4837 6.03906 73.334 6.03906C73.2025 6.03906 73.0729 6.02083 72.9453 5.98438C72.819 5.94792 72.7038 5.89323 72.5996 5.82031C72.4967 5.7474 72.4141 5.65755 72.3516 5.55078C72.2904 5.44271 72.2598 5.31771 72.2598 5.17578H72.6348C72.6348 5.27344 72.6536 5.35742 72.6914 5.42773C72.7292 5.49674 72.7806 5.55404 72.8457 5.59961C72.9121 5.64518 72.987 5.67904 73.0703 5.70117C73.1549 5.72201 73.2428 5.73242 73.334 5.73242C73.4655 5.73242 73.5768 5.71419 73.668 5.67773C73.7591 5.64128 73.8281 5.58919 73.875 5.52148C73.9232 5.45378 73.9473 5.3737 73.9473 5.28125ZM76.6641 4.37891V4.68555H75.125V4.37891H76.6641ZM75.1836 3.15625V6H74.8066V3.15625H75.1836ZM76.9922 3.15625V6H76.6172V3.15625H76.9922ZM78.0664 3.15625V6H77.6895V3.15625H78.0664ZM79.1289 3.15625V6H78.752V3.15625H79.1289ZM80.3203 4.43555V4.74414H79.0469V4.43555H80.3203ZM80.5137 3.15625V3.46484H79.0469V3.15625H80.5137ZM82.0527 3.15625V6H81.6816V3.15625H82.0527ZM82.9668 3.15625V3.46484H80.7695V3.15625H82.9668ZM84.6797 3.15625V6H84.3027V3.15625H84.6797ZM86.3965 3.15625L85.2148 4.48242L84.5508 5.17188L84.4883 4.76953L84.9883 4.21875L85.9434 3.15625H86.3965ZM86.0332 6L84.9805 4.61328L85.2051 4.31445L86.4824 6H86.0332ZM88.6211 5.69336V6H87.1152V5.69336H88.6211ZM87.1914 3.15625V6H86.8145V3.15625H87.1914ZM88.4219 4.37891V4.68555H87.1152V4.37891H88.4219ZM88.6016 3.15625V3.46484H87.1152V3.15625H88.6016ZM89.2188 3.15625L89.957 4.58398L90.6973 3.15625H91.125L90.1445 4.9375V6H89.7676V4.9375L88.7871 3.15625H89.2188Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

29
packages/docs/static/img/float_move.svg vendored Normal file
View File

@ -0,0 +1,29 @@
<svg width="156" height="76" viewBox="0 0 156 76" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="14" width="156" height="62" fill="#000C18"/>
<rect width="156" height="14" fill="#1C1C2A"/>
<rect width="30" height="14" fill="#10192C"/>
<rect x="31" width="30" height="14" fill="#10192C"/>
<rect x="30" width="1" height="14" fill="#2B2B4A"/>
<rect x="61" width="1" height="14" fill="#2B2B4A"/>
<rect x="41" y="54" width="30" height="14" fill="#000C18"/>
<rect x="33" y="5" width="15" height="4" rx="2" fill="#777777"/>
<rect x="2" y="5" width="6" height="4" rx="2" fill="#777777"/>
<rect x="10" y="5" width="18" height="4" rx="2" fill="#777777"/>
<g opacity="0.3">
<rect x="28.5" y="20.5" width="83" height="37" fill="#000C18" stroke="#2B2B4A"/>
<rect x="60" y="21" width="51" height="14" fill="#1C1C2A"/>
<rect x="43" y="26" width="12" height="4" rx="2" fill="white"/>
<rect x="33" y="26" width="7" height="4" rx="2" fill="white"/>
<rect x="59" y="21" width="1" height="14" fill="#2B2B4A"/>
</g>
<rect x="11.5" y="29.5" width="83" height="38" fill="#000C18" stroke="#2B2B4A"/>
<rect x="43" y="30" width="51" height="14" fill="#1C1C2A"/>
<rect x="26" y="35" width="12" height="4" rx="2" fill="white"/>
<rect x="16" y="35" width="7" height="4" rx="2" fill="white"/>
<rect x="42" y="30" width="1" height="14" fill="#2B2B4A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.3445 58.2654C70.5198 58.1426 70.5104 57.8799 70.3266 57.77L66.1804 55.2905C65.9614 55.1595 65.6906 55.3492 65.7388 55.5997L66.6506 60.344C66.691 60.5542 66.9347 60.653 67.1101 60.5302L67.7716 60.067C67.8669 60.0002 67.9142 59.8836 67.8922 59.7693L67.6024 58.2612C67.5542 58.0106 67.825 57.821 68.044 57.952L69.362 58.7401C69.4619 58.7999 69.5876 58.7954 69.683 58.7286L70.3445 58.2654Z" fill="white"/>
<rect x="69.5" y="59.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M77.3445 37.2654C77.5198 37.1426 77.5104 36.8799 77.3266 36.77L73.1804 34.2905C72.9614 34.1595 72.6906 34.3492 72.7388 34.5997L73.6506 39.344C73.691 39.5542 73.9347 39.653 74.1101 39.5302L74.7716 39.067C74.8669 39.0002 74.9142 38.8836 74.8922 38.7693L74.6024 37.2612C74.5542 37.0106 74.825 36.821 75.044 36.952L76.362 37.7401C76.4619 37.7999 76.5876 37.7954 76.683 37.7286L77.3445 37.2654Z" fill="white"/>
<rect x="76.5" y="38.5" width="4" height="2" stroke="white" stroke-dasharray="0.25 0.25"/>
<path d="M70.9414 54.4746V54.8145H68.9922V54.4746H70.9414ZM70.1484 53.6445V55.7148H69.7871V53.6445H70.1484ZM73.9473 55.2812C73.9473 55.2148 73.9368 55.1562 73.916 55.1055C73.8965 55.0534 73.8613 55.0065 73.8105 54.9648C73.7611 54.9232 73.6921 54.8835 73.6035 54.8457C73.5163 54.8079 73.4056 54.7695 73.2715 54.7305C73.1309 54.6888 73.0039 54.6426 72.8906 54.5918C72.7773 54.5397 72.6803 54.4805 72.5996 54.4141C72.5189 54.3477 72.457 54.2715 72.4141 54.1855C72.3711 54.0996 72.3496 54.0013 72.3496 53.8906C72.3496 53.7799 72.3724 53.6777 72.418 53.584C72.4635 53.4902 72.5286 53.4089 72.6133 53.3398C72.6992 53.2695 72.8014 53.2148 72.9199 53.1758C73.0384 53.1367 73.1706 53.1172 73.3164 53.1172C73.5299 53.1172 73.7109 53.1582 73.8594 53.2402C74.0091 53.321 74.123 53.4271 74.2012 53.5586C74.2793 53.6888 74.3184 53.8281 74.3184 53.9766H73.9434C73.9434 53.8698 73.9206 53.7754 73.875 53.6934C73.8294 53.61 73.7604 53.5449 73.668 53.498C73.5755 53.4499 73.4583 53.4258 73.3164 53.4258C73.1823 53.4258 73.0716 53.446 72.9844 53.4863C72.8971 53.5267 72.832 53.5814 72.7891 53.6504C72.7474 53.7194 72.7266 53.7982 72.7266 53.8867C72.7266 53.9466 72.7389 54.0013 72.7637 54.0508C72.7897 54.099 72.8294 54.1439 72.8828 54.1855C72.9375 54.2272 73.0065 54.2656 73.0898 54.3008C73.1745 54.3359 73.2754 54.3698 73.3926 54.4023C73.554 54.4479 73.6934 54.4987 73.8105 54.5547C73.9277 54.6107 74.0241 54.6738 74.0996 54.7441C74.1764 54.8132 74.2331 54.8919 74.2695 54.9805C74.3073 55.0677 74.3262 55.1667 74.3262 55.2773C74.3262 55.3932 74.3027 55.498 74.2559 55.5918C74.209 55.6855 74.1419 55.7656 74.0547 55.832C73.9674 55.8984 73.8626 55.9499 73.7402 55.9863C73.6191 56.0215 73.4837 56.0391 73.334 56.0391C73.2025 56.0391 73.0729 56.0208 72.9453 55.9844C72.819 55.9479 72.7038 55.8932 72.5996 55.8203C72.4967 55.7474 72.4141 55.6576 72.3516 55.5508C72.2904 55.4427 72.2598 55.3177 72.2598 55.1758H72.6348C72.6348 55.2734 72.6536 55.3574 72.6914 55.4277C72.7292 55.4967 72.7806 55.554 72.8457 55.5996C72.9121 55.6452 72.987 55.679 73.0703 55.7012C73.1549 55.722 73.2428 55.7324 73.334 55.7324C73.4655 55.7324 73.5768 55.7142 73.668 55.6777C73.7591 55.6413 73.8281 55.5892 73.875 55.5215C73.9232 55.4538 73.9473 55.3737 73.9473 55.2812ZM76.6641 54.3789V54.6855H75.125V54.3789H76.6641ZM75.1836 53.1562V56H74.8066V53.1562H75.1836ZM76.9922 53.1562V56H76.6172V53.1562H76.9922ZM78.0664 53.1562V56H77.6895V53.1562H78.0664ZM79.1289 53.1562V56H78.752V53.1562H79.1289ZM80.3203 54.4355V54.7441H79.0469V54.4355H80.3203ZM80.5137 53.1562V53.4648H79.0469V53.1562H80.5137ZM82.0527 53.1562V56H81.6816V53.1562H82.0527ZM82.9668 53.1562V53.4648H80.7695V53.1562H82.9668ZM84.6797 53.1562V56H84.3027V53.1562H84.6797ZM86.3965 53.1562L85.2148 54.4824L84.5508 55.1719L84.4883 54.7695L84.9883 54.2188L85.9434 53.1562H86.3965ZM86.0332 56L84.9805 54.6133L85.2051 54.3145L86.4824 56H86.0332ZM88.6211 55.6934V56H87.1152V55.6934H88.6211ZM87.1914 53.1562V56H86.8145V53.1562H87.1914ZM88.4219 54.3789V54.6855H87.1152V54.3789H88.4219ZM88.6016 53.1562V53.4648H87.1152V53.1562H88.6016ZM89.2188 53.1562L89.957 54.584L90.6973 53.1562H91.125L90.1445 54.9375V56H89.7676V54.9375L88.7871 53.1562H89.2188Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB