From a24dd21ca2b22d2eb87f230975ef1bfc3ef766f9 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 27 Jan 2024 14:11:47 +0000 Subject: [PATCH] feat: provide means to obtain popoutWindow document --- .../__tests__/dnd/groupDragHandler.spec.ts | 6 +- .../components/titlebar/tabsContainer.spec.ts | 40 ++- .../dockview/dockviewComponent.spec.ts | 244 +++++++++--------- .../__tests__/overlayRenderContainer.spec.ts | 4 +- .../src/api/dockviewGroupPanelApi.ts | 15 +- .../dockview-core/src/api/dockviewPanelApi.ts | 48 +++- .../dockview-core/src/dnd/groupDragHandler.ts | 2 +- .../src/dockview/components/panel/content.ts | 2 +- .../components/titlebar/tabsContainer.ts | 4 +- .../src/dockview/dockviewComponent.ts | 20 +- .../src/dockview/dockviewGroupPanelModel.ts | 9 +- .../src/dockview/dockviewPopoutGroupPanel.ts | 7 +- packages/dockview-core/src/index.ts | 2 + .../src/overlayRenderContainer.ts | 2 +- packages/dockview-core/src/popoutWindow.ts | 74 ++++-- yarn.lock | 9 +- 16 files changed, 288 insertions(+), 200 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts b/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts index b2cd3d44d..0ea0f7f5f 100644 --- a/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts +++ b/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts @@ -11,7 +11,7 @@ describe('groupDragHandler', () => { const groupMock = jest.fn(() => { const partial: Partial = { id: 'test_group_id', - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, }; return partial as DockviewGroupPanel; }); @@ -53,7 +53,7 @@ describe('groupDragHandler', () => { const groupMock = jest.fn(() => { const partial: Partial = { - api: { location: 'floating' } as any, + api: { location: { type: 'floating' } } as any, }; return partial as DockviewGroupPanel; }); @@ -85,7 +85,7 @@ describe('groupDragHandler', () => { const groupMock = jest.fn(() => { const partial: Partial = { - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, }; return partial as DockviewGroupPanel; }); diff --git a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts index 58a007393..266781742 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/tabsContainer.spec.ts @@ -9,6 +9,7 @@ import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanel import { fireEvent } from '@testing-library/dom'; import { TestPanel } from '../../dockviewGroupPanelModel.spec'; import { IDockviewPanel } from '../../../../dockview/dockviewPanel'; +import { fromPartial } from '@total-typescript/shoehorn'; describe('tabsContainer', () => { test('that an external event does not render a drop target and calls through to the group mode', () => { @@ -478,7 +479,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, }) as DockviewGroupPanel; }); @@ -538,7 +539,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'floating' } as any, + api: { location: { type: 'floating' } } as any, }) as DockviewGroupPanel; }); @@ -591,7 +592,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'floating' } as any, + api: { location: { type: 'floating' } } as any, model: {} as any, }) as DockviewGroupPanel; }); @@ -601,23 +602,20 @@ describe('tabsContainer', () => { const cut = new TabsContainer(accessor, groupPanel); - const panelMock = jest.fn((id: string) => { - const partial: Partial = { + const createPanel = (id: string) => + fromPartial({ 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('test_id'); + const panel = createPanel('test_id'); cut.openPanel(panel); const el = cut.element.querySelector('.tab')!; @@ -628,15 +626,15 @@ describe('tabsContainer', () => { fireEvent(el, event); // a floating group with a single tab shouldn't be eligible - expect(preventDefaultSpy).toBeCalledTimes(0); - expect(accessor.addFloatingGroup).toBeCalledTimes(0); + expect(preventDefaultSpy).toHaveBeenCalledTimes(0); + expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0); - const panel2 = new panelMock('test_id_2'); + const panel2 = createPanel('test_id_2'); cut.openPanel(panel2); fireEvent(el, event); - expect(preventDefaultSpy).toBeCalledTimes(1); - expect(accessor.addFloatingGroup).toBeCalledTimes(1); + expect(preventDefaultSpy).toHaveBeenCalledTimes(1); + expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1); }); test('pre header actions', () => { @@ -653,7 +651,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, model: {} as any, }) as DockviewGroupPanel; }); @@ -723,7 +721,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, model: {} as any, }) as DockviewGroupPanel; }); @@ -793,7 +791,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, model: {} as any, }) as DockviewGroupPanel; }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 9dd25066d..15e38dd61 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -3452,8 +3452,8 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); @@ -3464,8 +3464,8 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -3497,8 +3497,8 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); @@ -3509,8 +3509,8 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); }); @@ -3548,9 +3548,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); @@ -3561,9 +3561,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -3601,9 +3601,9 @@ describe('dockviewComponent', () => { position: { referencePanel: panel2 }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -3614,9 +3614,9 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -3654,9 +3654,9 @@ describe('dockviewComponent', () => { position: { referencePanel: panel2 }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -3667,9 +3667,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(3); }); @@ -3713,10 +3713,10 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); - expect(panel4.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); + expect(panel4.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(4); @@ -3727,10 +3727,10 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); - expect(panel4.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); + expect(panel4.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(4); }); @@ -3762,8 +3762,8 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); @@ -3774,8 +3774,8 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -3807,8 +3807,8 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); @@ -3819,8 +3819,8 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); }); @@ -3858,9 +3858,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); @@ -3871,9 +3871,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -3911,9 +3911,9 @@ describe('dockviewComponent', () => { position: { referencePanel: panel2 }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -3924,9 +3924,9 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); }); @@ -3964,9 +3964,9 @@ describe('dockviewComponent', () => { position: { referencePanel: panel2 }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -3977,9 +3977,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -4023,10 +4023,10 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); - expect(panel4.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); + expect(panel4.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(4); @@ -4037,10 +4037,10 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); - expect(panel4.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); + expect(panel4.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(4); }); @@ -4078,9 +4078,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); @@ -4091,9 +4091,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -4130,9 +4130,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -4143,9 +4143,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -4183,9 +4183,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); @@ -4196,9 +4196,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); }); @@ -4235,9 +4235,9 @@ describe('dockviewComponent', () => { floating: true, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -4248,9 +4248,9 @@ describe('dockviewComponent', () => { 'center' ); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('floating'); - expect(panel3.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('floating'); + expect(panel3.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(3); }); @@ -4282,15 +4282,15 @@ describe('dockviewComponent', () => { position: { direction: 'right' }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); dockview.addFloatingGroup(panel2); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -4321,15 +4321,15 @@ describe('dockviewComponent', () => { component: 'default', }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); dockview.addFloatingGroup(panel2); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -4361,15 +4361,15 @@ describe('dockviewComponent', () => { position: { direction: 'right' }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); dockview.addFloatingGroup(panel2.group); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); @@ -4400,15 +4400,15 @@ describe('dockviewComponent', () => { component: 'default', }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); dockview.addFloatingGroup(panel2.group); - expect(panel1.group.api.location).toBe('floating'); - expect(panel2.group.api.location).toBe('floating'); + expect(panel1.group.api.location.type).toBe('floating'); + expect(panel2.group.api.location.type).toBe('floating'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); }); @@ -4440,7 +4440,7 @@ describe('dockviewComponent', () => { expect(dockview.panels.length).toBe(1); expect(dockview.groups.length).toBe(1); - expect(panel1.api.group.api.location).toBe('popout'); + expect(panel1.api.group.api.location.type).toBe('popout'); dockview.removePanel(panel1); @@ -4474,15 +4474,15 @@ describe('dockviewComponent', () => { component: 'default', }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); dockview.addPopoutGroup(panel2.group); - expect(panel1.group.api.location).toBe('popout'); - expect(panel2.group.api.location).toBe('popout'); + expect(panel1.group.api.location.type).toBe('popout'); + expect(panel2.group.api.location.type).toBe('popout'); expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); }); @@ -4521,17 +4521,17 @@ describe('dockviewComponent', () => { }, }); - expect(panel1.group.api.location).toBe('grid'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); dockview.addPopoutGroup(panel2.group); - expect(panel1.group.api.location).toBe('popout'); - expect(panel2.group.api.location).toBe('popout'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('popout'); + expect(panel2.group.api.location.type).toBe('popout'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); @@ -4542,9 +4542,9 @@ describe('dockviewComponent', () => { 'right' ); - expect(panel1.group.api.location).toBe('popout'); - expect(panel2.group.api.location).toBe('grid'); - expect(panel3.group.api.location).toBe('grid'); + expect(panel1.group.api.location.type).toBe('popout'); + expect(panel2.group.api.location.type).toBe('grid'); + expect(panel3.group.api.location.type).toBe('grid'); expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); }); diff --git a/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts b/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts index db61d319a..81310060b 100644 --- a/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts @@ -41,7 +41,7 @@ describe('overlayRenderContainer', () => { }, group: { api: { - location: 'grid', + location: { type: 'grid' }, }, }, }); @@ -77,7 +77,7 @@ describe('overlayRenderContainer', () => { }, group: { api: { - location: 'grid', + location: { type: 'grid' }, }, }, }); diff --git a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts index c4b349bdf..1a999301d 100644 --- a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts @@ -8,6 +8,13 @@ import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi'; export interface DockviewGroupPanelApi extends GridviewPanelApi { readonly onDidLocationChange: Event; readonly location: DockviewGroupLocation; + /** + * + * If you require the documents Window object you can call `document.defaultView`. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/defaultView + */ + getDocument(): Document; moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void; maximize(): void; isMaximized(): boolean; @@ -42,6 +49,12 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { this.addDisposables(this._onDidLocationChange); } + getDocument(): Document { + return this.location.type === 'popout' + ? this.location.getWindow().document + : window.document; + } + moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void { if (!this._group) { throw new Error(NOT_INITIALIZED_MESSAGE); @@ -66,7 +79,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { throw new Error(NOT_INITIALIZED_MESSAGE); } - if (this.location !== 'grid') { + if (this.location.type !== 'grid') { // only grid groups can be maximized return; } diff --git a/packages/dockview-core/src/api/dockviewPanelApi.ts b/packages/dockview-core/src/api/dockviewPanelApi.ts index 6ef8d824a..dffca9297 100644 --- a/packages/dockview-core/src/api/dockviewPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewPanelApi.ts @@ -1,11 +1,13 @@ import { Emitter, Event } from '../events'; import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi'; import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel'; -import { MutableDisposable } from '../lifecycle'; +import { CompositeDisposable, MutableDisposable } from '../lifecycle'; import { DockviewPanel } from '../dockview/dockviewPanel'; import { DockviewComponent } from '../dockview/dockviewComponent'; import { Position } from '../dnd/droptarget'; import { DockviewPanelRenderer } from '../overlayRenderContainer'; +import { DockviewGroupPanelFloatingChangeEvent } from './dockviewGroupPanelApi'; +import { DockviewGroupLocation } from '../dockview/dockviewGroupPanelModel'; export interface TitleEvent { readonly title: string; @@ -28,6 +30,8 @@ export interface DockviewPanelApi readonly onDidActiveGroupChange: Event; readonly onDidGroupChange: Event; readonly onDidRendererChange: Event; + readonly location: DockviewGroupLocation; + readonly onDidLocationChange: Event; close(): void; setTitle(title: string): void; setRenderer(renderer: DockviewPanelRenderer): void; @@ -39,6 +43,13 @@ export interface DockviewPanelApi maximize(): void; isMaximized(): boolean; exitMaximized(): void; + /** + * + * If you require the documents Window object you can call `document.defaultView`. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/defaultView + */ + getDocument(): Document; } export class DockviewPanelApiImpl @@ -59,7 +70,16 @@ export class DockviewPanelApiImpl readonly _onDidRendererChange = new Emitter(); readonly onDidRendererChange = this._onDidRendererChange.event; - private readonly disposable = new MutableDisposable(); + private readonly _onDidLocationChange = + new Emitter(); + readonly onDidLocationChange: Event = + this._onDidLocationChange.event; + + private readonly groupEventsDisposable = new MutableDisposable(); + + get location(): DockviewGroupLocation { + return this.group.api.location; + } get title(): string | undefined { return this.panel.title; @@ -81,13 +101,22 @@ export class DockviewPanelApiImpl this._onDidGroupChange.fire(); if (this._group) { - this.disposable.value = this._group.api.onDidActiveChange(() => { - this._onDidActiveGroupChange.fire(); - }); + this.groupEventsDisposable.value = new CompositeDisposable( + this.group.api.onDidLocationChange((event) => { + this._onDidLocationChange.fire(event); + }), + this.group.api.onDidActiveChange(() => { + this._onDidActiveGroupChange.fire(); + }) + ); if (this.isGroupActive !== isOldGroupActive) { this._onDidActiveGroupChange.fire(); } + + this._onDidLocationChange.fire({ + location: this.group.api.location, + }); } } @@ -107,14 +136,19 @@ export class DockviewPanelApiImpl this._group = group; this.addDisposables( - this.disposable, + this.groupEventsDisposable, this._onDidRendererChange, this._onDidTitleChange, this._onDidGroupChange, - this._onDidActiveGroupChange + this._onDidActiveGroupChange, + this._onDidLocationChange ); } + getDocument(): Document { + return this.group.api.getDocument(); + } + moveTo(options: { group: DockviewGroupPanel; position?: Position; diff --git a/packages/dockview-core/src/dnd/groupDragHandler.ts b/packages/dockview-core/src/dnd/groupDragHandler.ts index de4a3ef04..7138437bf 100644 --- a/packages/dockview-core/src/dnd/groupDragHandler.ts +++ b/packages/dockview-core/src/dnd/groupDragHandler.ts @@ -38,7 +38,7 @@ export class GroupDragHandler extends DragHandler { } override isCancelled(_event: DragEvent): boolean { - if (this.group.api.location === 'floating' && !_event.shiftKey) { + if (this.group.api.location.type === 'floating' && !_event.shiftKey) { return true; } return false; diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index b98b12289..dd762873a 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -71,7 +71,7 @@ export class ContentContainer if ( !data && event.shiftKey && - this.group.location !== 'floating' + this.group.location.type !== 'floating' ) { return false; } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 123608573..6a7caac29 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -247,7 +247,7 @@ export class TabsContainer if ( isFloatingGroupsEnabled && event.shiftKey && - this.group.api.location !== 'floating' + this.group.api.location.type !== 'floating' ) { event.preventDefault(); @@ -350,7 +350,7 @@ export class TabsContainer !this.accessor.options.disableFloatingGroups; const isFloatingWithOnePanel = - this.group.api.location === 'floating' && this.size === 1; + this.group.api.location.type === 'floating' && this.size === 1; if ( isFloatingGroupsEnabled && diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 7595d723a..a46177a95 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -67,6 +67,7 @@ import { DockviewPanelRenderer, OverlayRenderContainer, } from '../overlayRenderContainer'; +import { PopoutWindow } from '../popoutWindow'; const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = { activationSize: { type: 'pixels', value: 10 }, @@ -608,7 +609,7 @@ export class DockviewComponent } } - group.model.location = 'floating'; + group.model.location = { type: 'floating' }; const overlayLeft = typeof coord?.x === 'number' @@ -683,7 +684,7 @@ export class DockviewComponent dispose: () => { disposable.dispose(); - group.model.location = 'grid'; + group.model.location = { type: 'grid' }; remove(this._floatingGroups, floatingGroupPanel); this.updateWatermark(); }, @@ -1173,7 +1174,7 @@ export class DockviewComponent group.model.openPanel(panel); this.doSetGroupAndPanelActive(group); } else if ( - referenceGroup.api.location === 'floating' || + referenceGroup.api.location.type === 'floating' || target === 'center' ) { panel = this.createPanel(options, referenceGroup); @@ -1259,7 +1260,10 @@ export class DockviewComponent } private updateWatermark(): void { - if (this.groups.filter((x) => x.api.location === 'grid').length === 0) { + if ( + this.groups.filter((x) => x.api.location.type === 'grid').length === + 0 + ) { if (!this.watermark) { this.watermark = this.createWatermarkComponent(); @@ -1377,7 +1381,7 @@ export class DockviewComponent } | undefined ): DockviewGroupPanel { - if (group.api.location === 'floating') { + if (group.api.location.type === 'floating') { const floatingGroup = this._floatingGroups.find( (_) => _.group === group ); @@ -1406,7 +1410,7 @@ export class DockviewComponent throw new Error('failed to find floating group'); } - if (group.api.location === 'popout') { + if (group.api.location.type === 'popout') { const selectedGroup = this._popoutGroups.find( (_) => _.group === group ); @@ -1486,7 +1490,7 @@ export class DockviewComponent if (sourceGroup && sourceGroup.size < 2) { const [targetParentLocation, to] = tail(targetLocation); - if (sourceGroup.api.location === 'grid') { + if (sourceGroup.api.location.type === 'grid') { const sourceLocation = getGridLocation(sourceGroup.element); const [sourceParentLocation, from] = tail(sourceLocation); @@ -1562,7 +1566,7 @@ export class DockviewComponent }); } } else { - switch (sourceGroup.api.location) { + switch (sourceGroup.api.location.type) { case 'grid': this.gridview.removeView( getGridLocation(sourceGroup.element) diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 4462add04..a80a7baa0 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -130,7 +130,10 @@ export interface IDockviewGroupPanelModel extends IPanel { ): boolean; } -export type DockviewGroupLocation = 'grid' | 'floating' | 'popout'; +export type DockviewGroupLocation = + | { type: 'grid' } + | { type: 'floating' } + | { type: 'popout'; getWindow: () => Window }; export class DockviewGroupPanelModel extends CompositeDisposable @@ -146,7 +149,7 @@ export class DockviewGroupPanelModel private _leftHeaderActions: IHeaderActionsRenderer | undefined; private _prefixHeaderActions: IHeaderActionsRenderer | undefined; - private _location: DockviewGroupLocation = 'grid'; + private _location: DockviewGroupLocation = { type: 'grid' }; private mostRecentlyUsed: IDockviewPanel[] = []; @@ -253,7 +256,7 @@ export class DockviewGroupPanelModel toggleClass(this.container, 'dv-groupview-floating', false); toggleClass(this.container, 'dv-groupview-popout', false); - switch (value) { + switch (value.type) { case 'grid': this.contentContainer.dropTarget.setTargetZones([ 'top', diff --git a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts index 803fa5411..c95fdce1f 100644 --- a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts @@ -25,13 +25,16 @@ export class DockviewPopoutGroupPanel extends CompositeDisposable { height: this.options.box.height, }); - group.model.location = 'popout'; + group.model.location = { + type: 'popout', + getWindow: () => this.window.window!, + }; this.addDisposables( this.window, { dispose: () => { - group.model.location = 'grid'; + group.model.location = { type: 'grid' }; }, }, this.window.onDidClose(() => { diff --git a/packages/dockview-core/src/index.ts b/packages/dockview-core/src/index.ts index 62d415174..3f5c8bf70 100644 --- a/packages/dockview-core/src/index.ts +++ b/packages/dockview-core/src/index.ts @@ -11,6 +11,8 @@ export { CompositeDisposable as DockviewCompositeDisposable, } from './lifecycle'; +export { PopoutWindow } from './popoutWindow'; + export * from './panel/types'; export * from './panel/componentFactory'; diff --git a/packages/dockview-core/src/overlayRenderContainer.ts b/packages/dockview-core/src/overlayRenderContainer.ts index 5fea8cee0..62095e595 100644 --- a/packages/dockview-core/src/overlayRenderContainer.ts +++ b/packages/dockview-core/src/overlayRenderContainer.ts @@ -93,7 +93,7 @@ export class OverlayRenderContainer extends CompositeDisposable { toggleClass( focusContainer, 'dv-render-overlay-float', - panel.group.api.location === 'floating' + panel.group.api.location.type === 'floating' ); }; diff --git a/packages/dockview-core/src/popoutWindow.ts b/packages/dockview-core/src/popoutWindow.ts index c73334549..672a6d4e5 100644 --- a/packages/dockview-core/src/popoutWindow.ts +++ b/packages/dockview-core/src/popoutWindow.ts @@ -8,19 +8,26 @@ export type PopoutWindowOptions = { } & Box; export class PopoutWindow extends CompositeDisposable { + private readonly _onWillClose = new Emitter(); + readonly onWillClose = this._onWillClose.event; + private readonly _onDidClose = new Emitter(); readonly onDidClose = this._onDidClose.event; private _window: { value: Window; disposable: IDisposable } | null = null; + get window(): Window | null { + return this._window?.value ?? null; + } + constructor( - private readonly id: string, + private readonly target: string, private readonly className: string, private readonly options: PopoutWindowOptions ) { super(); - this.addDisposables(this._onDidClose, { + this.addDisposables(this._onWillClose, this._onDidClose, { dispose: () => { this.close(); }, @@ -42,9 +49,13 @@ export class PopoutWindow extends CompositeDisposable { close(): void { if (this._window) { + this._onWillClose.fire(); + this._window.disposable.dispose(); this._window.value.close(); this._window = null; + + this._onDidClose.fire(); } } @@ -64,8 +75,10 @@ export class PopoutWindow extends CompositeDisposable { .map(([key, value]) => `${key}=${value}`) .join(','); - // https://developer.mozilla.org/en-US/docs/Web/API/Window/open - const externalWindow = window.open(url, this.id, features); + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/open + */ + const externalWindow = window.open(url, this.target, features); if (!externalWindow) { return; @@ -75,44 +88,55 @@ export class PopoutWindow extends CompositeDisposable { this._window = { value: externalWindow, disposable }; - const cleanUp = () => { - this._onDidClose.fire(); - this._window = null; - }; - - // prevent any default content from loading - // externalWindow.document.body.replaceWith(document.createElement('div')); - disposable.addDisposables( addDisposableWindowListener(window, 'beforeunload', () => { - cleanUp(); + /** + * before the main window closes we should close this popup too + * to be good citizens + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event + */ this.close(); }) ); externalWindow.addEventListener('load', () => { + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event + */ + const externalDocument = externalWindow.document; externalDocument.title = document.title; - const div = document.createElement('div'); - div.classList.add('dv-popout-window'); - div.style.position = 'absolute'; - div.style.width = '100%'; - div.style.height = '100%'; - div.style.top = '0px'; - div.style.left = '0px'; - div.classList.add(this.className); - div.appendChild(content); + const container = this.createPopoutWindowContainer(); + container.classList.add(this.className); + container.appendChild(content); - externalDocument.body.replaceChildren(div); + // externalDocument.body.replaceChildren(container); + externalDocument.body.appendChild(container); externalDocument.body.classList.add(this.className); addStyles(externalDocument, window.document.styleSheets); externalWindow.addEventListener('beforeunload', () => { - // TODO: indicate external window is closing - cleanUp(); + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event + */ + this.close(); }); }); } + + private createPopoutWindowContainer(): HTMLElement { + const el = document.createElement('div'); + el.classList.add('dv-popout-window'); + el.id = 'dv-popout-window'; + el.style.position = 'absolute'; + el.style.width = '100%'; + el.style.height = '100%'; + el.style.top = '0px'; + el.style.left = '0px'; + + return el; + } } diff --git a/yarn.lock b/yarn.lock index 7844d2be6..448c87069 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13882,6 +13882,13 @@ react-json-view-lite@^1.2.0: resolved "https://registry.yarnpkg.com/react-json-view-lite/-/react-json-view-lite-1.2.1.tgz#c59a0bea4ede394db331d482ee02e293d38f8218" integrity sha512-Itc0g86fytOmKZoIoJyGgvNqohWSbh3NXIKNgH6W6FT9PC1ck4xas1tT3Rr/b3UlFXyA9Jjaw9QSXdZy2JwGMQ== +react-laag@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/react-laag/-/react-laag-2.0.5.tgz#549f1035b761b9ba09ac98fd128ccad63464c877" + integrity sha512-RCvublJhdcgGRHU1wMYJ8kRtnYsKUgYusLvVhMuftg65POnnOB4+fwXvnETm6adc0cMnc1spujlrK6bGIz6aug== + dependencies: + tiny-warning "^1.0.3" + react-loadable-ssr-addon-v5-slorber@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz#2cdc91e8a744ffdf9e3556caabeb6e4278689883" @@ -15678,7 +15685,7 @@ tiny-invariant@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== -tiny-warning@^1.0.0: +tiny-warning@^1.0.0, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==