diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index c646ca0a5..c6dc7a038 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -21,6 +21,7 @@ import { DockviewApi } from '../../api/component.api'; import { DockviewDndOverlayEvent } from '../../dockview/options'; import { SizeEvent } from '../../api/gridviewPanelApi'; import { setupMockWindow } from '../__mocks__/mockWindow'; +import { exhaustMicrotaskQueue } from '../__test_utils__/utils'; class PanelContentPartTest implements IContentRenderer { element: HTMLElement = document.createElement('div'); @@ -4867,6 +4868,155 @@ describe('dockviewComponent', () => { ); }); + test('basic', async () => { + jest.useRealTimers(); + + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 1000); + + dockview.fromJSON({ + activeGroup: 'group-1', + grid: { + root: { + type: 'branch', + data: [ + { + type: 'leaf', + data: { + views: ['panel1'], + id: 'group-1', + activeView: 'panel1', + }, + size: 1000, + }, + ], + size: 1000, + }, + height: 1000, + width: 1000, + orientation: Orientation.VERTICAL, + }, + popoutGroups: [ + { + data: { + views: ['panel2'], + id: 'group-2', + activeView: 'panel2', + }, + position: null, + }, + ], + panels: { + panel1: { + id: 'panel1', + contentComponent: 'default', + title: 'panel1', + }, + panel2: { + id: 'panel2', + contentComponent: 'default', + title: 'panel2', + }, + }, + }); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + const panel2 = dockview.api.getPanel('panel2'); + + const windowObject = + panel2?.api.location.type === 'popout' + ? panel2?.api.location.getWindow() + : undefined; + + expect(windowObject).toBeTruthy(); + + windowObject!.close(); + }); + + test('grid -> floating -> popout -> popout closed', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + 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', + position: { direction: 'right' }, + }); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('grid'); + expect(panel3.api.location.type).toBe('grid'); + + dockview.addFloatingGroup(panel2); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + + await dockview.addPopoutGroup(panel2); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('popout'); + expect(panel3.api.location.type).toBe('grid'); + + const windowObject = + panel2.api.location.type === 'popout' + ? panel2.api.location.getWindow() + : undefined; + expect(windowObject).toBeTruthy(); + + windowObject!.close(); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + }); + test('move popout group of 1 panel inside grid', async () => { const container = document.createElement('div'); @@ -5116,7 +5266,7 @@ describe('dockviewComponent', () => { mockWindow.close(); expect(panel1.group.api.location.type).toBe('grid'); - expect(panel2.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(panel3.group.api.location.type).toBe('grid'); dockview.clear(); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index cf7823d9f..9d49df053 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -706,6 +706,8 @@ export class DockviewComponent _window.window!.innerHeight ); + let floatingBox: AnchoredBox | undefined; + if (!options?.overridePopoutGroup && isGroupAddedToDom) { if (itemToPopout instanceof DockviewPanel) { this.movingLock(() => { @@ -727,7 +729,15 @@ export class DockviewComponent break; case 'floating': case 'popout': + floatingBox = this._floatingGroups + .find( + (value) => + value.group.api.id === + itemToPopout.api.id + ) + ?.overlay.toJSON(); this.removeGroup(referenceGroup); + break; } } @@ -825,20 +835,29 @@ export class DockviewComponent }); } } else if (this.getPanel(group.id)) { - this.doRemoveGroup(group, { - skipDispose: true, - skipActive: true, - skipPopoutReturn: true, - }); - const removedGroup = group; - removedGroup.model.renderContainer = - this.overlayRenderContainer; - removedGroup.model.location = { type: 'grid' }; - returnedGroup = removedGroup; + if (floatingBox) { + this.addFloatingGroup(removedGroup, { + height: floatingBox.height, + width: floatingBox.width, + position: floatingBox, + }); + } else { + this.doRemoveGroup(removedGroup, { + skipDispose: true, + skipActive: true, + skipPopoutReturn: true, + }); - this.doAddGroup(removedGroup, [0]); + removedGroup.model.renderContainer = + this.overlayRenderContainer; + removedGroup.model.location = { type: 'grid' }; + returnedGroup = removedGroup; + this.movingLock(() => { + this.doAddGroup(removedGroup, [0]); + }); + } this.doSetGroupAndPanelActive(removedGroup); } }) @@ -1477,6 +1496,22 @@ export class DockviewComponent ); } + if (options.popout) { + const group = this.createGroup(); + this._onDidAddGroup.fire(group); + + this.addPopoutGroup(group); + + const panel = this.createPanel(options, group); + + group.model.openPanel(panel, { + skipSetActive: options.inactive, + skipSetGroupActive: options.inactive, + }); + + return panel; + } + const initial = { width: options.initialWidth, height: options.initialHeight, diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 3e87923a2..d21418730 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -252,6 +252,7 @@ export type AddPanelOptions
= {
inactive?: boolean;
initialWidth?: number;
initialHeight?: number;
+ popout?: boolean;
} & Partial