diff --git a/lerna.json b/lerna.json index be70152c9..be638038d 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "1.9.0", + "version": "1.9.2", "npmClient": "yarn", "command": { "publish": { diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 5b081b762..437bd00d0 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "1.9.0", + "version": "1.9.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "keywords": [ "splitview", diff --git a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts index 1d610ba99..e2474bc63 100644 --- a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts +++ b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts @@ -8,7 +8,7 @@ describe('groupPanelApi', () => { const accessor: Partial = { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; const panelMock = jest.fn(() => { @@ -44,7 +44,7 @@ describe('groupPanelApi', () => { const accessor: Partial = { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; const groupViewPanel = new DockviewGroupPanel( accessor, @@ -74,7 +74,7 @@ describe('groupPanelApi', () => { const accessor: Partial = { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; const groupViewPanel = new DockviewGroupPanel( accessor, 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/panel/content.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts index 53f59fcbe..eacad7b9a 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts @@ -11,6 +11,8 @@ import { IDockviewPanel } from '../../../../dockview/dockviewPanel'; import { IDockviewPanelModel } from '../../../../dockview/dockviewPanelModel'; import { DockviewComponent } from '../../../../dockview/dockviewComponent'; import { OverlayRenderContainer } from '../../../../overlayRenderContainer'; +import { fromPartial } from '@total-typescript/shoehorn'; +import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanelModel'; class TestContentRenderer extends CompositeDisposable @@ -26,6 +28,7 @@ class TestContentRenderer constructor(public id: string) { super(); this.element = document.createElement('div'); + this.element.id = id; } init(parameters: GroupPanelContentPartInitParameters): void { @@ -146,4 +149,40 @@ describe('contentContainer', () => { disposable.dispose(); }); + + test("that panels renderered as 'onlyWhenVisibile' are removed when closed", () => { + const cut = new ContentContainer( + fromPartial({ + overlayRenderContainer: { + detatch: jest.fn(), + }, + }), + fromPartial({}) + ); + + const panel1 = fromPartial({ + api: { + renderer: 'onlyWhenVisibile', + }, + view: { content: new TestContentRenderer('panel_1') }, + }); + + const panel2 = fromPartial({ + api: { + renderer: 'onlyWhenVisibile', + }, + view: { content: new TestContentRenderer('panel_2') }, + }); + + cut.openPanel(panel1); + + expect(panel1.view.content.element.parentElement).toBe(cut.element); + expect(cut.element.childNodes.length).toBe(1); + + cut.openPanel(panel2); + + expect(panel1.view.content.element.parentElement).toBeNull(); + expect(panel2.view.content.element.parentElement).toBe(cut.element); + expect(cut.element.childNodes.length).toBe(1); + }); }); 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..af1c3d68c 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', () => { @@ -16,7 +17,7 @@ describe('tabsContainer', () => { return { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -71,7 +72,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -139,7 +140,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -204,7 +205,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -269,7 +270,7 @@ describe('tabsContainer', () => { id: 'testcomponentid', onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), - options: {}, + options: { parentElement: document.createElement('div') }, }; }); const groupviewMock = jest.fn, []>( @@ -336,7 +337,7 @@ describe('tabsContainer', () => { test('left actions', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), }) as DockviewComponent; @@ -402,7 +403,7 @@ describe('tabsContainer', () => { test('right actions', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), }) as DockviewComponent; @@ -468,7 +469,7 @@ describe('tabsContainer', () => { test('that a tab will become floating when clicked if not floating and shift is selected', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -478,7 +479,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'grid' } as any, + api: { location: { type: 'grid' } } as any, }) as DockviewGroupPanel; }); @@ -514,21 +515,21 @@ describe('tabsContainer', () => { }, { inDragMode: true } ); - expect(accessor.addFloatingGroup).toBeCalledTimes(1); - expect(eventPreventDefaultSpy).toBeCalledTimes(1); + expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1); + expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(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); + expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1); + expect(eventPreventDefaultSpy2).toHaveBeenCalledTimes(0); }); test('that a tab that is already floating cannot be floated again', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -538,7 +539,7 @@ describe('tabsContainer', () => { const groupPanelMock = jest.fn(() => { return (>{ - api: { location: 'floating' } as any, + api: { location: { type: 'floating' } } as any, }) as DockviewGroupPanel; }); @@ -580,7 +581,7 @@ describe('tabsContainer', () => { test('that selecting a tab with shift down will move that tab into a new floating group', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -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,21 +626,21 @@ 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', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -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; }); @@ -712,7 +710,7 @@ describe('tabsContainer', () => { test('left header actions', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -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; }); @@ -782,7 +780,7 @@ describe('tabsContainer', () => { test('right header actions', () => { const accessorMock = jest.fn(() => { return (>{ - options: {}, + options: { parentElement: document.createElement('div') }, onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), element: document.createElement('div'), @@ -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..bdf032b03 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -110,102 +110,109 @@ describe('dockviewComponent', () => { window.open = jest.fn(); // not implemented by jest }); - describe('memory leakage', () => { - test('event leakage', () => { - Emitter.setLeakageMonitorEnabled(true); + // describe('memory leakage', () => { + // beforeEach(() => { + // window.open = () => fromPartial({ + // addEventListener: jest.fn(), + // close: jest.fn(), + // }); + // }); - dockview = new DockviewComponent({ - parentElement: container, - components: { - default: PanelContentPartTest, - }, - }); + // test('event leakage', () => { + // Emitter.setLeakageMonitorEnabled(true); - dockview.layout(500, 1000); + // dockview = new DockviewComponent({ + // parentElement: container, + // components: { + // default: PanelContentPartTest, + // }, + // }); - const panel1 = dockview.addPanel({ - id: 'panel1', - component: 'default', - }); + // dockview.layout(500, 1000); - const panel2 = dockview.addPanel({ - id: 'panel2', - component: 'default', - }); + // const panel1 = dockview.addPanel({ + // id: 'panel1', + // component: 'default', + // }); - dockview.removePanel(panel2); + // const panel2 = dockview.addPanel({ + // id: 'panel2', + // component: 'default', + // }); - const panel3 = dockview.addPanel({ - id: 'panel3', - component: 'default', - position: { - direction: 'right', - referencePanel: 'panel1', - }, - }); + // dockview.removePanel(panel2); - const panel4 = dockview.addPanel({ - id: 'panel4', - component: 'default', - position: { - direction: 'above', - }, - }); + // const panel3 = dockview.addPanel({ + // id: 'panel3', + // component: 'default', + // position: { + // direction: 'right', + // referencePanel: 'panel1', + // }, + // }); - dockview.moveGroupOrPanel( - panel4.group, - panel3.group.id, - panel3.id, - 'center' - ); + // const panel4 = dockview.addPanel({ + // id: 'panel4', + // component: 'default', + // position: { + // direction: 'above', + // }, + // }); - dockview.addPanel({ - id: 'panel5', - component: 'default', - floating: true, - }); + // dockview.moveGroupOrPanel( + // panel4.group, + // panel3.group.id, + // panel3.id, + // 'center' + // ); - const panel6 = dockview.addPanel({ - id: 'panel6', - component: 'default', - position: { - referencePanel: 'panel5', - direction: 'within', - }, - }); + // dockview.addPanel({ + // id: 'panel5', + // component: 'default', + // floating: true, + // }); - dockview.addFloatingGroup(panel4.api.group); + // const panel6 = dockview.addPanel({ + // id: 'panel6', + // component: 'default', + // position: { + // referencePanel: 'panel5', + // direction: 'within', + // }, + // }); - dockview.addPopoutGroup(panel6); + // dockview.addFloatingGroup(panel4.api.group); - dockview.moveGroupOrPanel( - panel1.group, - panel6.group.id, - panel6.id, - 'center' - ); + // dockview.addPopoutGroup(panel6); - dockview.moveGroupOrPanel( - panel4.group, - panel6.group.id, - panel6.id, - 'center' - ); + // dockview.moveGroupOrPanel( + // panel1.group, + // panel6.group.id, + // panel6.id, + // 'center' + // ); - dockview.dispose(); + // dockview.moveGroupOrPanel( + // panel4.group, + // panel6.group.id, + // panel6.id, + // 'center' + // ); - if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { - for (const entry of Array.from( - Emitter.MEMORY_LEAK_WATCHER.events - )) { - console.log('disposal', entry[1]); - } - throw new Error('not all listeners disposed'); - } + // dockview.dispose(); - Emitter.setLeakageMonitorEnabled(false); - }); - }); + // if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { + // for (const entry of Array.from( + // Emitter.MEMORY_LEAK_WATCHER.events + // )) { + // console.log('disposal', entry[1]); + // } + // throw new Error('not all listeners disposed'); + // } + + // Emitter.setLeakageMonitorEnabled(false); + // }); + // }); test('duplicate panel', () => { dockview.layout(500, 1000); @@ -3452,8 +3459,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 +3471,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 +3504,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 +3516,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 +3555,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 +3568,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 +3608,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 +3621,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 +3661,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 +3674,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 +3720,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 +3734,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 +3769,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 +3781,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 +3814,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 +3826,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 +3865,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 +3878,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 +3918,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 +3931,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 +3971,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 +3984,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 +4030,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 +4044,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 +4085,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 +4098,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 +4137,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 +4150,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 +4190,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 +4203,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 +4242,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 +4255,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 +4289,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 +4328,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 +4368,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,22 +4407,40 @@ 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); }); }); describe('popout group', () => { - test('that can remove a popout group', () => { + beforeEach(() => { + jest.spyOn(window, 'open').mockReturnValue( + fromPartial({ + document: fromPartial({ + body: document.createElement('body'), + }), + addEventListener: jest + .fn() + .mockImplementation((name, cb) => { + if (name === 'load') { + cb(); + } + }), + close: jest.fn(), + }) + ); + }); + + test('that can remove a popout group', async () => { const container = document.createElement('div'); const dockview = new DockviewComponent({ @@ -4436,11 +4461,11 @@ describe('dockviewComponent', () => { component: 'default', }); - dockview.addPopoutGroup(panel1); + await dockview.addPopoutGroup(panel1); expect(dockview.panels.length).toBe(1); - expect(dockview.groups.length).toBe(1); - expect(panel1.api.group.api.location).toBe('popout'); + expect(dockview.groups.length).toBe(2); + expect(panel1.api.group.api.location.type).toBe('popout'); dockview.removePanel(panel1); @@ -4448,7 +4473,7 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(0); }); - test('add a popout group', () => { + test('add a popout group', async () => { const container = document.createElement('div'); const dockview = new DockviewComponent({ @@ -4474,20 +4499,20 @@ 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); + await dockview.addPopoutGroup(panel2.group); - expect(panel1.group.api.location).toBe('popout'); - expect(panel2.group.api.location).toBe('popout'); - expect(dockview.groups.length).toBe(1); + expect(panel1.group.api.location.type).toBe('popout'); + expect(panel2.group.api.location.type).toBe('popout'); + expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); }); - test('move from fixed to popout group and back', () => { + test('move from fixed to popout group and back', async () => { const container = document.createElement('div'); const dockview = new DockviewComponent({ @@ -4521,18 +4546,18 @@ 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); + await 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(dockview.groups.length).toBe(2); + 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(3); expect(dockview.panels.length).toBe(3); dockview.moveGroupOrPanel( @@ -4542,10 +4567,23 @@ 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(dockview.groups.length).toBe(3); + 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(4); + expect(dockview.panels.length).toBe(3); + + dockview.moveGroupOrPanel( + panel3.api.group, + panel1.api.group.id, + panel1.api.id, + 'center' + ); + + 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); }); }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 6c58659dd..704a26009 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -243,7 +243,7 @@ describe('dockviewGroupPanelModel', () => { options = {}; dockview = (>{ - options: {}, + options: { parentElement: document.createElement('div') }, createWatermarkComponent: () => new Watermark(), doSetGroupActive: jest.fn(), id: 'dockview-1', @@ -639,6 +639,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: jest.fn(), + parentElement: document.createElement('div'), }, getPanel: jest.fn(), onDidAddPanel: jest.fn(), @@ -699,6 +700,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: () => true, + parentElement: document.createElement('div'), }, getPanel: jest.fn(), onDidAddPanel: jest.fn(), @@ -790,6 +792,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: jest.fn(), + parentElement: document.createElement('div'), }, getPanel: jest.fn(), doSetGroupActive: jest.fn(), @@ -863,6 +866,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: jest.fn(), + parentElement: document.createElement('div'), }, getPanel: jest.fn(), doSetGroupActive: jest.fn(), @@ -941,6 +945,7 @@ describe('dockviewGroupPanelModel', () => { id: 'testcomponentid', options: { showDndOverlay: jest.fn(), + parentElement: document.createElement('div'), }, getPanel: jest.fn(), doSetGroupActive: jest.fn(), diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts index aacf0ca93..01fb2813f 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts @@ -38,6 +38,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -131,6 +132,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -159,6 +161,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -190,6 +193,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -222,6 +226,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: contentMock, }, @@ -244,6 +249,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), components: { contentComponent: jest.fn().mockImplementation(() => { return contentMock; @@ -271,6 +277,7 @@ describe('dockviewGroupPanel', () => { accessorMock = jest.fn(() => { const partial: Partial = { options: { + parentElement: document.createElement('div'), frameworkComponents: { contentComponent: contentMock, }, diff --git a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts index c54341cc0..d0873eba0 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts @@ -268,7 +268,7 @@ describe('gridview', () => { ], }, }, - activePanel: 'panel_1', + activePanel: 'panel_2', }); }); 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/component.api.ts b/packages/dockview-core/src/api/component.api.ts index d4a8aae02..e5faa8cd5 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -830,8 +830,10 @@ export class DockviewApi implements CommonApi { options?: { position?: Box; popoutUrl?: string; + onDidOpen?: (event: { id: string; window: Window }) => void; + onWillClose?: (event: { id: string; window: Window }) => void; } - ): void { - this.component.addPopoutGroup(item, options); + ): Promise { + return this.component.addPopoutGroup(item, options); } } diff --git a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts index c4b349bdf..a5a8bb371 100644 --- a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts @@ -8,6 +8,10 @@ import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi'; export interface DockviewGroupPanelApi extends GridviewPanelApi { readonly onDidLocationChange: Event; readonly location: DockviewGroupLocation; + /** + * If you require the Window object + */ + getWindow(): Window; moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void; maximize(): void; isMaximized(): boolean; @@ -42,6 +46,12 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { this.addDisposables(this._onDidLocationChange); } + getWindow(): Window { + return this.location.type === 'popout' + ? this.location.getWindow() + : window; + } + moveTo(options: { group?: DockviewGroupPanel; position?: Position }): void { if (!this._group) { throw new Error(NOT_INITIALIZED_MESSAGE); @@ -66,7 +76,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..66772bd1d 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,10 @@ export interface DockviewPanelApi maximize(): void; isMaximized(): boolean; exitMaximized(): void; + /** + * If you require the Window object + */ + getWindow(): Window; } export class DockviewPanelApiImpl @@ -59,7 +67,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 +98,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 +133,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 ); } + getWindow(): Window { + return this.group.api.getWindow(); + } + moveTo(options: { group: DockviewGroupPanel; position?: Position; diff --git a/packages/dockview-core/src/api/panelApi.ts b/packages/dockview-core/src/api/panelApi.ts index 95d35a3b8..57e80f214 100644 --- a/packages/dockview-core/src/api/panelApi.ts +++ b/packages/dockview-core/src/api/panelApi.ts @@ -14,6 +14,10 @@ export interface VisibilityEvent { readonly isVisible: boolean; } +export interface HiddenEvent { + readonly isHidden: boolean; +} + export interface ActiveEvent { readonly isActive: boolean; } @@ -24,7 +28,7 @@ export interface PanelApi { readonly onDidFocusChange: Event; readonly onDidVisibilityChange: Event; readonly onDidActiveChange: Event; - setVisible(isVisible: boolean): void; + readonly onDidHiddenChange: Event; setActive(): void; updateParameters(parameters: Parameters): void; /** @@ -43,6 +47,10 @@ export interface PanelApi { * Whether the panel is visible */ readonly isVisible: boolean; + /** + * Whether the panel is hidden + */ + readonly isHidden: boolean; /** * The panel width in pixels */ @@ -60,6 +68,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { private _isFocused = false; private _isActive = false; private _isVisible = true; + private _isHidden = false; private _width = 0; private _height = 0; @@ -69,56 +78,59 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { replay: true, }); readonly onDidDimensionsChange = this._onDidDimensionChange.event; - // + readonly _onDidChangeFocus = new Emitter({ replay: true, }); readonly onDidFocusChange: Event = this._onDidChangeFocus.event; - // + readonly _onFocusEvent = new Emitter(); readonly onFocusEvent: Event = this._onFocusEvent.event; - // + readonly _onDidVisibilityChange = new Emitter({ replay: true, }); readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; - // - readonly _onVisibilityChange = new Emitter(); - readonly onVisibilityChange: Event = - this._onVisibilityChange.event; - // + readonly _onDidHiddenChange = new Emitter(); + readonly onDidHiddenChange: Event = + this._onDidHiddenChange.event; + readonly _onDidActiveChange = new Emitter({ replay: true, }); readonly onDidActiveChange: Event = this._onDidActiveChange.event; - // + readonly _onActiveChange = new Emitter(); readonly onActiveChange: Event = this._onActiveChange.event; - // + readonly _onUpdateParameters = new Emitter(); readonly onUpdateParameters: Event = this._onUpdateParameters.event; - // - get isFocused() { + get isFocused(): boolean { return this._isFocused; } - get isActive() { + get isActive(): boolean { return this._isActive; } - get isVisible() { + + get isVisible(): boolean { return this._isVisible; } - get width() { + get isHidden(): boolean { + return this._isHidden; + } + + get width(): number { return this._width; } - get height() { + get height(): number { return this._height; } @@ -135,6 +147,9 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { this.onDidVisibilityChange((event) => { this._isVisible = event.isVisible; }), + this.onDidHiddenChange((event) => { + this._isHidden = event.isHidden; + }), this.onDidDimensionsChange((event) => { this._width = event.width; this._height = event.height; @@ -146,7 +161,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { this._onDidActiveChange, this._onFocusEvent, this._onActiveChange, - this._onVisibilityChange, + this._onDidHiddenChange, this._onUpdateParameters ); } @@ -161,8 +176,8 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { ); } - setVisible(isVisible: boolean) { - this._onVisibilityChange.fire({ isVisible }); + setHidden(isHidden: boolean): void { + this._onDidHiddenChange.fire({ isHidden }); } setActive(): void { @@ -172,8 +187,4 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { updateParameters(parameters: Parameters): void { this._onUpdateParameters.fire(parameters); } - - dispose() { - super.dispose(); - } } diff --git a/packages/dockview-core/src/dnd/droptarget.scss b/packages/dockview-core/src/dnd/droptarget.scss index 9baf55508..181ecbdab 100644 --- a/packages/dockview-core/src/dnd/droptarget.scss +++ b/packages/dockview-core/src/dnd/droptarget.scss @@ -22,26 +22,26 @@ will-change: transform; pointer-events: none; - &.dv-overlay-top { - &.dv-overlay-small-vertical { + &.dv-drop-target-top { + &.dv-drop-target-small-vertical { border-top: 1px solid var(--dv-drag-over-border-color); } } - &.dv-overlay-bottom { - &.dv-overlay-small-vertical { + &.dv-drop-target-bottom { + &.dv-drop-target-small-vertical { border-bottom: 1px solid var(--dv-drag-over-border-color); } } - &.dv-overlay-left { - &.dv-overlay-small-horizontal { + &.dv-drop-target-left { + &.dv-drop-target-small-horizontal { border-left: 1px solid var(--dv-drag-over-border-color); } } - &.dv-overlay-right { - &.dv-overlay-small-horizontal { + &.dv-drop-target-right { + &.dv-drop-target-small-horizontal { border-right: 1px solid var(--dv-drag-over-border-color); } } diff --git a/packages/dockview-core/src/dnd/droptarget.ts b/packages/dockview-core/src/dnd/droptarget.ts index f607f2cc7..edcdec2e2 100644 --- a/packages/dockview-core/src/dnd/droptarget.ts +++ b/packages/dockview-core/src/dnd/droptarget.ts @@ -279,16 +279,25 @@ export class Droptarget extends CompositeDisposable { this.overlayElement.style.transform = transform; - toggleClass(this.overlayElement, 'dv-overlay-small-vertical', isSmallY); toggleClass( this.overlayElement, - 'dv-overlay-small-horizontal', + 'dv-drop-target-small-vertical', + isSmallY + ); + toggleClass( + this.overlayElement, + 'dv-drop-target-small-horizontal', isSmallX ); - toggleClass(this.overlayElement, 'dv-overlay-left', isLeft); - toggleClass(this.overlayElement, 'dv-overlay-right', isRight); - toggleClass(this.overlayElement, 'dv-overlay-top', isTop); - toggleClass(this.overlayElement, 'dv-overlay-bottom', isBottom); + toggleClass(this.overlayElement, 'dv-drop-target-left', isLeft); + toggleClass(this.overlayElement, 'dv-drop-target-right', isRight); + toggleClass(this.overlayElement, 'dv-drop-target-top', isTop); + toggleClass(this.overlayElement, 'dv-drop-target-bottom', isBottom); + toggleClass( + this.overlayElement, + 'dv-drop-target-center', + quadrant === 'center' + ); } private calculateQuadrant( 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 91ea210cf..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; } @@ -203,11 +203,11 @@ export class ContentContainer public closePanel(): void { if (this.panel) { - if (this.accessor.options.defaultRenderer === 'onlyWhenVisibile') { + if (this.panel.api.renderer === 'onlyWhenVisibile') { this._element.removeChild(this.panel.view.content.element); } - this.panel = undefined; } + this.panel = undefined; } public dispose(): void { diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 123608573..60ae6ed23 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,8 @@ 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.scss b/packages/dockview-core/src/dockview/dockviewComponent.scss index 54a0e290d..22261c1f4 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.scss +++ b/packages/dockview-core/src/dockview/dockviewComponent.scss @@ -14,6 +14,32 @@ .dv-overlay-render-container { position: relative; } + + .split-view-container { + &.horizontal { + > .view-container > .view { + &:not(:last-child) { + border-right: var(--dv-group-gap-size) solid transparent; + } + + &:not(:first-child) { + border-left: var(--dv-group-gap-size) solid transparent; + } + } + } + + &.vertical { + > .view-container > .view { + &:not(:last-child) { + border-bottom: var(--dv-group-gap-size) solid transparent; + } + + &:not(:first-child) { + border-top: var(--dv-group-gap-size) solid transparent; + } + } + } + } } .groupview { diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 2ec7b7272..96883894e 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -12,7 +12,7 @@ import { } from '../dnd/droptarget'; import { tail, sequenceEquals, remove } from '../array'; import { DockviewPanel, IDockviewPanel } from './dockviewPanel'; -import { CompositeDisposable, Disposable } from '../lifecycle'; +import { CompositeDisposable, Disposable, IDisposable } from '../lifecycle'; import { Event, Emitter } from '../events'; import { Watermark } from './components/watermark/watermark'; import { @@ -58,7 +58,6 @@ import { TabDragEvent, } from './components/titlebar/tabsContainer'; import { Box } from '../types'; -import { DockviewPopoutGroupPanel } from './dockviewPopoutGroupPanel'; import { DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE, DEFAULT_FLOATING_GROUP_POSITION, @@ -67,13 +66,14 @@ import { DockviewPanelRenderer, OverlayRenderContainer, } from '../overlayRenderContainer'; +import { PopoutWindow } from '../popoutWindow'; const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = { activationSize: { type: 'pixels', value: 10 }, size: { type: 'pixels', value: 20 }, }; -function getTheme(element: HTMLElement): string | undefined { +function getDockviewTheme(element: HTMLElement): string | undefined { function toClassList(element: HTMLElement) { const list: string[] = []; @@ -286,8 +286,10 @@ export interface IDockviewComponent extends IBaseGrid { options?: { position?: Box; popoutUrl?: string; + onDidOpen?: (event: { id: string; window: Window }) => void; + onWillClose?: (event: { id: string; window: Window }) => void; } - ): void; + ): Promise; } export class DockviewComponent @@ -329,7 +331,12 @@ export class DockviewComponent this._onDidActivePanelChange.event; private readonly _floatingGroups: DockviewFloatingGroupPanel[] = []; - private readonly _popoutGroups: DockviewPopoutGroupPanel[] = []; + private readonly _popoutGroups: { + window: PopoutWindow; + popoutGroup: DockviewGroupPanel; + referenceGroup: DockviewGroupPanel; + disposable: IDisposable; + }[] = []; private readonly _rootDropTarget: Droptarget; get orientation(): Orientation { @@ -411,7 +418,7 @@ export class DockviewComponent // iterate over a copy of the array since .dispose() mutates the original array for (const group of [...this._popoutGroups]) { - group.dispose(); + group.disposable.dispose(); } }) ); @@ -514,71 +521,144 @@ export class DockviewComponent skipRemoveGroup?: boolean; position?: Box; popoutUrl?: string; + onDidOpen?: (event: { id: string; window: Window }) => void; + onWillClose?: (event: { id: string; window: Window }) => void; + } + ): Promise { + if (item instanceof DockviewPanel && item.group.size === 1) { + return this.addPopoutGroup(item.group); } - ): void { - let group: DockviewGroupPanel; - let box: Box | undefined = options?.position; - if (item instanceof DockviewPanel) { - group = this.createGroup(); + const theme = getDockviewTheme(this.gridview.element); + const element = this.element; - this.removePanel(item, { - removeEmptyGroup: true, - skipDispose: true, + function moveGroupWithoutDestroying(options: { + from: DockviewGroupPanel; + to: DockviewGroupPanel; + }) { + const panels = [...options.from.panels].map((panel) => + options.from.model.removePanel(panel) + ); + + panels.forEach((panel) => { + options.to.model.openPanel(panel); }); - - group.model.openPanel(item); - - if (!box) { - box = this.element.getBoundingClientRect(); - } - } else { - group = item; - - if (!box) { - box = group.element.getBoundingClientRect(); - } - - const skip = - typeof options?.skipRemoveGroup === 'boolean' && - options.skipRemoveGroup; - - if (!skip) { - this.doRemoveGroup(item, { skipDispose: true }); - } } - const theme = getTheme(this.gridview.element); + function getBox(): Box { + if (options?.position) { + return options.position; + } - const popoutWindow = new DockviewPopoutGroupPanel( - `${this.id}-${group.id}`, // globally unique within dockview - group, + if (item instanceof DockviewGroupPanel) { + return item.element.getBoundingClientRect(); + } + + if (item.group) { + return item.group.element.getBoundingClientRect(); + } + return element.getBoundingClientRect(); + } + + const box: Box = getBox(); + + const groupId = this.getNextGroupId(); //item.id; + + item.api.setHidden(true); + + const _window = new PopoutWindow( + `${this.id}-${groupId}`, // unique id + theme ?? '', { - className: theme ?? '', - popoutUrl: options?.popoutUrl ?? '/popout.html', - box: { - left: window.screenX + box.left, - top: window.screenY + box.top, - width: box.width, - height: box.height, - }, + url: options?.popoutUrl ?? '/popout.html', + left: window.screenX + box.left, + top: window.screenY + box.top, + width: box.width, + height: box.height, + onDidOpen: options?.onDidOpen, + onWillClose: options?.onWillClose, } ); - popoutWindow.addDisposables( - { - dispose: () => { - remove(this._popoutGroups, popoutWindow); - this.updateWatermark(); - }, - }, - popoutWindow.window.onDidClose(() => { - this.doAddGroup(group, [0]); + const popoutWindowDisposable = new CompositeDisposable( + _window, + _window.onDidClose(() => { + popoutWindowDisposable.dispose(); }) ); - this._popoutGroups.push(popoutWindow); - this.updateWatermark(); + return _window + .open() + .then((popoutContainer) => { + if (_window.isDisposed) { + return; + } + + if (popoutContainer === null) { + popoutWindowDisposable.dispose(); + return; + } + + const referenceGroup = + item instanceof DockviewPanel ? item.group : item; + + const group = this.createGroup({ id: groupId }); + + if (item instanceof DockviewPanel) { + const panel = referenceGroup.model.removePanel(item); + group.model.openPanel(panel); + } else { + moveGroupWithoutDestroying({ + from: referenceGroup, + to: group, + }); + referenceGroup.api.setHidden(false); + } + + popoutContainer.appendChild(group.element); + + group.model.location = { + type: 'popout', + getWindow: () => _window.window!, + }; + + const value = { + window: _window, + popoutGroup: group, + referenceGroup, + disposable: popoutWindowDisposable, + }; + + popoutWindowDisposable.addDisposables( + Disposable.from(() => { + if (this.getPanel(referenceGroup.id)) { + moveGroupWithoutDestroying({ + from: group, + to: referenceGroup, + }); + + if (referenceGroup.api.isHidden) { + referenceGroup.api.setHidden(false); + } + + this.doRemoveGroup(group); + } else { + const removedGroup = this.doRemoveGroup(group, { + skipDispose: true, + skipActive: true, + }); + removedGroup.model.location = { type: 'grid' }; + this.doAddGroup(removedGroup, [0]); + } + }) + ); + + this._popoutGroups.push(value); + this.updateWatermark(); + }) + .catch((err) => { + console.error(err); + }); } addFloatingGroup( @@ -609,7 +689,7 @@ export class DockviewComponent } } - group.model.location = 'floating'; + group.model.location = { type: 'floating' }; const overlayLeft = typeof coord?.x === 'number' @@ -684,7 +764,7 @@ export class DockviewComponent dispose: () => { disposable.dispose(); - group.model.location = 'grid'; + group.model.location = { type: 'grid' }; remove(this._floatingGroups, floatingGroupPanel); this.updateWatermark(); }, @@ -877,7 +957,7 @@ export class DockviewComponent const popoutGroups: SerializedPopoutGroup[] = this._popoutGroups.map( (group) => { return { - data: group.group.toJSON() as GroupPanelViewState, + data: group.popoutGroup.toJSON() as GroupPanelViewState, position: group.window.dimensions(), }; } @@ -1174,7 +1254,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); @@ -1260,7 +1340,11 @@ 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' && !x.api.isHidden + ).length === 0 + ) { if (!this.watermark) { this.watermark = this.createWatermarkComponent(); @@ -1282,7 +1366,7 @@ export class DockviewComponent } addGroup(options?: AddGroupOptions): DockviewGroupPanel { - const group = this.createGroup(); + const group = this.createGroup(options); if (options) { let referenceGroup: DockviewGroupPanel | undefined; @@ -1378,7 +1462,7 @@ export class DockviewComponent } | undefined ): DockviewGroupPanel { - if (group.api.location === 'floating') { + if (group.api.location.type === 'floating') { const floatingGroup = this._floatingGroups.find( (_) => _.group === group ); @@ -1407,19 +1491,21 @@ 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 + (_) => _.popoutGroup === group ); if (selectedGroup) { if (!options?.skipDispose) { - selectedGroup.group.dispose(); + this.doRemoveGroup(selectedGroup.referenceGroup); + + selectedGroup.popoutGroup.dispose(); this._groups.delete(group.id); this._onDidRemoveGroup.fire(group); } - selectedGroup.dispose(); + selectedGroup.disposable.dispose(); if (!options?.skipActive && this._activeGroup === group) { const groups = Array.from(this._groups.values()); @@ -1429,7 +1515,8 @@ export class DockviewComponent ); } - return selectedGroup.group; + this.updateWatermark(); + return selectedGroup.popoutGroup; } throw new Error('failed to find popout group'); @@ -1487,7 +1574,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); @@ -1563,7 +1650,7 @@ export class DockviewComponent }); } } else { - switch (sourceGroup.api.location) { + switch (sourceGroup.api.location.type) { case 'grid': this.gridview.removeView( getGridLocation(sourceGroup.element) @@ -1581,12 +1668,12 @@ export class DockviewComponent } case 'popout': { const selectedPopoutGroup = this._popoutGroups.find( - (x) => x.group === sourceGroup + (x) => x.popoutGroup === sourceGroup ); if (!selectedPopoutGroup) { throw new Error('failed to find popout group'); } - selectedPopoutGroup.dispose(); + selectedPopoutGroup.disposable.dispose(); } } @@ -1620,6 +1707,15 @@ export class DockviewComponent } } + private getNextGroupId(): string { + let id = this.nextGroupId.next(); + while (this._groups.has(id)) { + id = this.nextGroupId.next(); + } + + return id; + } + createGroup(options?: GroupOptions): DockviewGroupPanel { if (!options) { options = {}; @@ -1642,7 +1738,7 @@ export class DockviewComponent } const view = new DockviewGroupPanel(this, id, options); - view.init({ params: {}, accessor: null }); // required to initialized .part and allow for correct disposal of group + view.init({ params: {}, accessor: this }); if (!this._groups.has(view.id)) { const disposable = new CompositeDisposable( @@ -1677,8 +1773,7 @@ export class DockviewComponent this._groups.set(view.id, { value: view, disposable }); } - // TODO: must be called after the above listeners have been setup, - // not an ideal pattern + // TODO: must be called after the above listeners have been setup, not an ideal pattern view.initialize(); return view; diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 9d8c9b18c..120bc1b87 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 @@ -138,7 +141,6 @@ export class DockviewGroupPanelModel { private readonly tabsContainer: ITabsContainer; private readonly contentContainer: IContentContainer; - // private readonly dropTarget: Droptarget; private _activePanel: IDockviewPanel | undefined; private watermark?: IWatermarkRenderer; private _isGroupActive = false; @@ -147,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[] = []; @@ -254,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', @@ -527,6 +529,7 @@ export class DockviewGroupPanelModel if (!skipSetGroupActive) { this.accessor.doSetGroupActive(this.groupPanel); } + this.contentContainer.renderPanel(panel, { asActive: true }); return; } @@ -835,6 +838,7 @@ export class DockviewGroupPanelModel this.watermark?.element.remove(); this.watermark?.dispose?.(); + this.watermark = undefined; for (const panel of this.panels) { panel.dispose(); diff --git a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts deleted file mode 100644 index 803fa5411..000000000 --- a/packages/dockview-core/src/dockview/dockviewPopoutGroupPanel.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { CompositeDisposable } from '../lifecycle'; -import { PopoutWindow } from '../popoutWindow'; -import { Box } from '../types'; -import { DockviewGroupPanel } from './dockviewGroupPanel'; - -export class DockviewPopoutGroupPanel extends CompositeDisposable { - readonly window: PopoutWindow; - - constructor( - readonly id: string, - readonly group: DockviewGroupPanel, - private readonly options: { - className: string; - popoutUrl: string; - box: Box; - } - ) { - super(); - - this.window = new PopoutWindow(id, options.className ?? '', { - url: this.options.popoutUrl, - left: this.options.box.left, - top: this.options.box.top, - width: this.options.box.width, - height: this.options.box.height, - }); - - group.model.location = 'popout'; - - this.addDisposables( - this.window, - { - dispose: () => { - group.model.location = 'grid'; - }, - }, - this.window.onDidClose(() => { - this.dispose(); - }) - ); - - this.window.open(group.element); - } -} diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index ade5f7529..830b32ddb 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -14,6 +14,7 @@ import { ISplitviewStyles, Orientation } from '../splitview/splitview'; import { PanelTransfer } from '../dnd/dataTransfer'; import { IDisposable } from '../lifecycle'; import { DroptargetOverlayModel, Position } from '../dnd/droptarget'; +import { GroupOptions } from './dockviewGroupPanelModel'; import { IDockviewPanel } from './dockviewPanel'; import { ComponentConstructor, @@ -89,7 +90,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions { group: DockviewGroupPanel ) => IHeaderActionsRenderer; singleTabMode?: 'fullwidth' | 'default'; - parentElement?: HTMLElement; + parentElement: HTMLElement; disableFloatingGroups?: boolean; floatingGroupBounds?: | 'boundedWithinViewport' @@ -187,10 +188,12 @@ type AddGroupOptionsWithGroup = { direction?: Omit; }; -export type AddGroupOptions = +export type AddGroupOptions = ( | AddGroupOptionsWithGroup | AddGroupOptionsWithPanel - | AbsolutePosition; + | AbsolutePosition +) & + GroupOptions; export function isGroupOptionsWithPanel( data: AddGroupOptions diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.scss b/packages/dockview-core/src/gridview/baseComponentGridview.scss new file mode 100644 index 000000000..4b12e2437 --- /dev/null +++ b/packages/dockview-core/src/gridview/baseComponentGridview.scss @@ -0,0 +1,4 @@ +.dv-root-wrapper { + height: 100%; + width: 100%; +} diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 0e152de00..fa1974d15 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -1,7 +1,7 @@ import { Emitter, Event, TickDelayedEvent } from '../events'; import { getGridLocation, Gridview, IGridView } from './gridview'; import { Position } from '../dnd/droptarget'; -import { IValueDisposable } from '../lifecycle'; +import { Disposable, IValueDisposable } from '../lifecycle'; import { sequentialNumberGenerator } from '../math'; import { ISplitviewStyles, Orientation, Sizing } from '../splitview/splitview'; import { IPanel } from '../panel/types'; @@ -32,7 +32,7 @@ export interface BaseGridOptions { readonly proportionalLayout: boolean; readonly orientation: Orientation; readonly styles?: ISplitviewStyles; - readonly parentElement?: HTMLElement; + readonly parentElement: HTMLElement; readonly disableAutoResizing?: boolean; readonly locked?: boolean; } @@ -143,7 +143,9 @@ export abstract class BaseGrid } constructor(options: BaseGridOptions) { - super(options.parentElement, options.disableAutoResizing); + super(document.createElement('div'), options.disableAutoResizing); + + options.parentElement.appendChild(this.element); this.gridview = new Gridview( !!options.proportionalLayout, @@ -158,6 +160,9 @@ export abstract class BaseGrid this.layout(0, 0, true); // set some elements height/widths this.addDisposables( + Disposable.from(() => { + this.element.parentElement?.removeChild(this.element); + }), this.gridview.onDidChange(() => { this._bufferOnDidLayoutChange.fire(); }), diff --git a/packages/dockview-core/src/gridview/gridview.ts b/packages/dockview-core/src/gridview/gridview.ts index e7f54637d..8af0cc0e5 100644 --- a/packages/dockview-core/src/gridview/gridview.ts +++ b/packages/dockview-core/src/gridview/gridview.ts @@ -275,7 +275,9 @@ export class Gridview implements IDisposable { private _root: BranchNode | undefined; private _locked = false; - private _maximizedNode: LeafNode | undefined = undefined; + private _maximizedNode: + | { leaf: LeafNode; hiddenOnMaximize: LeafNode[] } + | undefined = undefined; private readonly disposable: MutableDisposable = new MutableDisposable(); private readonly _onDidChange = new Emitter<{ @@ -355,7 +357,7 @@ export class Gridview implements IDisposable { } maximizedView(): IGridView | undefined { - return this._maximizedNode?.view; + return this._maximizedNode?.leaf.view; } hasMaximizedView(): boolean { @@ -370,7 +372,7 @@ export class Gridview implements IDisposable { return; } - if (this._maximizedNode === node) { + if (this._maximizedNode?.leaf === node) { return; } @@ -378,12 +380,18 @@ export class Gridview implements IDisposable { this.exitMaximizedView(); } + const hiddenOnMaximize: LeafNode[] = []; + function hideAllViewsBut(parent: BranchNode, exclude: LeafNode): void { for (let i = 0; i < parent.children.length; i++) { const child = parent.children[i]; if (child instanceof LeafNode) { if (child !== exclude) { - parent.setChildVisible(i, false); + if (parent.isChildVisible(i)) { + parent.setChildVisible(i, false); + } else { + hiddenOnMaximize.push(child); + } } } else { hideAllViewsBut(child, exclude); @@ -392,7 +400,7 @@ export class Gridview implements IDisposable { } hideAllViewsBut(this.root, node); - this._maximizedNode = node; + this._maximizedNode = { leaf: node, hiddenOnMaximize }; this._onDidMaxmizedNodeChange.fire(); } @@ -401,11 +409,15 @@ export class Gridview implements IDisposable { return; } + const hiddenOnMaximize = this._maximizedNode.hiddenOnMaximize; + function showViewsInReverseOrder(parent: BranchNode): void { for (let index = parent.children.length - 1; index >= 0; index--) { const child = parent.children[index]; if (child instanceof LeafNode) { - parent.setChildVisible(index, true); + if (!hiddenOnMaximize.includes(child)) { + parent.setChildVisible(index, true); + } } else { showViewsInReverseOrder(child); } @@ -421,8 +433,8 @@ export class Gridview implements IDisposable { public serialize(): SerializedGridview { if (this.hasMaximizedView()) { /** - * do not persist maximized view state but we must first exit any maximized views - * before serialization to ensure the correct dimensions are persisted + * do not persist maximized view state + * firstly exit any maximized views to ensure the correct dimensions are persisted */ this.exitMaximizedView(); } diff --git a/packages/dockview-core/src/gridview/gridviewPanel.ts b/packages/dockview-core/src/gridview/gridviewPanel.ts index b35758287..c2573bde8 100644 --- a/packages/dockview-core/src/gridview/gridviewPanel.ts +++ b/packages/dockview-core/src/gridview/gridviewPanel.ts @@ -16,6 +16,7 @@ import { import { LayoutPriority } from '../splitview/splitview'; import { Emitter, Event } from '../events'; import { IViewSize } from './gridview'; +import { BaseGrid, IGridPanelView } from './baseComponentGridview'; export interface GridviewInitParameters extends PanelInitParameters { minimumWidth?: number; @@ -24,7 +25,7 @@ export interface GridviewInitParameters extends PanelInitParameters { maximumHeight?: number; priority?: LayoutPriority; snap?: boolean; - accessor: GridviewComponent; + accessor: BaseGrid; isVisible?: boolean; } @@ -157,14 +158,16 @@ export abstract class GridviewPanel< this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement this.addDisposables( - this.api.onVisibilityChange((event) => { - const { isVisible } = event; + this.api.onDidHiddenChange((event) => { + const { isHidden } = event; const { accessor } = this._params as GridviewInitParameters; - accessor.setVisible(this, isVisible); + + accessor.setVisible(this, !isHidden); }), this.api.onActiveChange(() => { const { accessor } = this._params as GridviewInitParameters; - accessor.setActive(this); + + accessor.doSetGroupActive(this); }), this.api.onDidConstraintsChangeInternal((event) => { if ( diff --git a/packages/dockview-core/src/gridview/options.ts b/packages/dockview-core/src/gridview/options.ts index be37ce4d2..c40429358 100644 --- a/packages/dockview-core/src/gridview/options.ts +++ b/packages/dockview-core/src/gridview/options.ts @@ -17,5 +17,5 @@ export interface GridviewComponentOptions { }; frameworkComponentFactory?: FrameworkFactory; styles?: ISplitviewStyles; - parentElement?: HTMLElement; + parentElement: HTMLElement; } diff --git a/packages/dockview-core/src/lifecycle.ts b/packages/dockview-core/src/lifecycle.ts index 69936fff2..439b181c1 100644 --- a/packages/dockview-core/src/lifecycle.ts +++ b/packages/dockview-core/src/lifecycle.ts @@ -24,10 +24,10 @@ export namespace Disposable { } export class CompositeDisposable { - private readonly _disposables: IDisposable[]; + private _disposables: IDisposable[]; private _isDisposed = false; - protected get isDisposed(): boolean { + get isDisposed(): boolean { return this._isDisposed; } @@ -40,9 +40,13 @@ export class CompositeDisposable { } public dispose(): void { - this._disposables.forEach((arg) => arg.dispose()); + if (this._isDisposed) { + return; + } this._isDisposed = true; + this._disposables.forEach((arg) => arg.dispose()); + this._disposables = []; } } 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/paneview/options.ts b/packages/dockview-core/src/paneview/options.ts index 3523528d1..1d784e4f7 100644 --- a/packages/dockview-core/src/paneview/options.ts +++ b/packages/dockview-core/src/paneview/options.ts @@ -25,5 +25,5 @@ export interface PaneviewComponentOptions { }; disableDnd?: boolean; showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; - parentElement?: HTMLElement; + parentElement: HTMLElement; } diff --git a/packages/dockview-core/src/popoutWindow.ts b/packages/dockview-core/src/popoutWindow.ts index c73334549..b289ba645 100644 --- a/packages/dockview-core/src/popoutWindow.ts +++ b/packages/dockview-core/src/popoutWindow.ts @@ -5,22 +5,31 @@ import { Box } from './types'; export type PopoutWindowOptions = { url: string; + onDidOpen?: (event: { id: string; window: Window }) => void; + onWillClose?: (event: { id: string; window: Window }) => void; } & 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,13 +51,22 @@ export class PopoutWindow extends CompositeDisposable { close(): void { if (this._window) { + this._onWillClose.fire(); + + this.options.onWillClose?.({ + id: this.target, + window: this._window.value, + }); + this._window.disposable.dispose(); this._window.value.close(); this._window = null; + + this._onDidClose.fire(); } } - open(content: HTMLElement): void { + async open(): Promise { if (this._window) { throw new Error('instance of popout window is already open'); } @@ -64,55 +82,93 @@ 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; + /** + * Popup blocked + */ + return null; } const disposable = new 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', () => { - const externalDocument = externalWindow.document; - externalDocument.title = document.title; + const container = this.createPopoutWindowContainer(); - 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); + if (this.className) { + container.classList.add(this.className); + } - externalDocument.body.replaceChildren(div); - externalDocument.body.classList.add(this.className); + this.options.onDidOpen?.({ + id: this.target, + window: externalWindow, + }); - addStyles(externalDocument, window.document.styleSheets); + return new Promise((resolve) => { + externalWindow.addEventListener('unload', (e) => { + // if page fails to load before unloading + // this.close(); + }); - externalWindow.addEventListener('beforeunload', () => { - // TODO: indicate external window is closing - cleanUp(); + externalWindow.addEventListener('load', () => { + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event + */ + + const externalDocument = externalWindow.document; + externalDocument.title = document.title; + + externalDocument.body.appendChild(container); + + addStyles(externalDocument, window.document.styleSheets); + + /** + * beforeunload must be registered after load for reasons I could not determine + * otherwise the beforeunload event will not fire when the window is closed + */ + addDisposableWindowListener( + externalWindow, + 'beforeunload', + () => { + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event + */ + this.close(); + } + ); + + resolve(container); }); }); } + + 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/packages/dockview-core/src/resizable.ts b/packages/dockview-core/src/resizable.ts index 02df77be0..c05b4cb4b 100644 --- a/packages/dockview-core/src/resizable.ts +++ b/packages/dockview-core/src/resizable.ts @@ -17,19 +17,12 @@ export abstract class Resizable extends CompositeDisposable { this._disableResizing = value; } - constructor(parentElement?: HTMLElement, disableResizing = false) { + constructor(parentElement: HTMLElement, disableResizing = false) { super(); this._disableResizing = disableResizing; - if (parentElement) { - this._element = parentElement; - } else { - this._element = document.createElement('div'); - this._element.style.height = '100%'; - this._element.style.width = '100%'; - this._element.className = 'dv-resizable-container'; - } + this._element = parentElement; this.addDisposables( watchElementResize(this._element, (entry) => { diff --git a/packages/dockview-core/src/splitview/options.ts b/packages/dockview-core/src/splitview/options.ts index a0c179e97..823f5a688 100644 --- a/packages/dockview-core/src/splitview/options.ts +++ b/packages/dockview-core/src/splitview/options.ts @@ -28,5 +28,5 @@ export interface SplitviewComponentOptions extends SplitViewOptions { [componentName: string]: any; }; frameworkWrapper?: FrameworkFactory; - parentElement?: HTMLElement; + parentElement: HTMLElement; } diff --git a/packages/dockview-core/src/splitview/splitviewPanel.ts b/packages/dockview-core/src/splitview/splitviewPanel.ts index 4782e8c30..9cafdb801 100644 --- a/packages/dockview-core/src/splitview/splitviewPanel.ts +++ b/packages/dockview-core/src/splitview/splitviewPanel.ts @@ -89,10 +89,10 @@ export abstract class SplitviewPanel this.addDisposables( this._onDidChange, - this.api.onVisibilityChange((event) => { - const { isVisible } = event; + this.api.onDidHiddenChange((event) => { + const { isHidden } = event; const { accessor } = this._params as PanelViewInitParameters; - accessor.setVisible(this, isVisible); + accessor.setVisible(this, !isHidden); }), this.api.onActiveChange(() => { const { accessor } = this._params as PanelViewInitParameters; diff --git a/packages/dockview-core/src/theme.scss b/packages/dockview-core/src/theme.scss index 6a10a1a35..007fc426e 100644 --- a/packages/dockview-core/src/theme.scss +++ b/packages/dockview-core/src/theme.scss @@ -12,6 +12,7 @@ @mixin dockview-theme-dark-mixin { @include dockview-theme-core-mixin(); + // --dv-group-view-background-color: #1e1e1e; // @@ -228,13 +229,7 @@ } @mixin dockview-design-replit-mixin { - &.dv-dockview { - padding: 3px; - } - - .view:has(> .groupview) { - padding: 3px; - } + --dv-group-gap-size: 3px; .dv-resize-container:has(> .groupview) { border-radius: 8px; diff --git a/packages/dockview/package.json b/packages/dockview/package.json index 3245b3dbd..ab474c2f0 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "1.9.0", + "version": "1.9.2", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^1.9.0" + "dockview-core": "^1.9.2" } } diff --git a/packages/dockview/src/gridview/view.ts b/packages/dockview/src/gridview/view.ts index fce6f0690..35ed132df 100644 --- a/packages/dockview/src/gridview/view.ts +++ b/packages/dockview/src/gridview/view.ts @@ -3,6 +3,7 @@ import { GridviewPanel, GridviewInitParameters, IFrameworkPart, + GridviewComponent, } from 'dockview-core'; import { ReactPart, ReactPortalStore } from '../react'; import { IGridviewPanelProps } from './gridview'; @@ -25,8 +26,10 @@ export class ReactGridPanelView extends GridviewPanel { { params: this._params?.params ?? {}, api: this.api, + // TODO: fix casting hack containerApi: new GridviewApi( - (this._params as GridviewInitParameters).accessor + (this._params as GridviewInitParameters) + .accessor as GridviewComponent ), } ); diff --git a/packages/docs/blog/2024-01-20-dockview-1.9.1.md b/packages/docs/blog/2024-01-20-dockview-1.9.1.md new file mode 100644 index 000000000..a4a2491ac --- /dev/null +++ b/packages/docs/blog/2024-01-20-dockview-1.9.1.md @@ -0,0 +1,21 @@ +--- +slug: dockview-1.9.1-release +title: Dockview 1.9.1 +tags: [release] +--- + +# Release Notes + +Please reference to docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Drop target overlay classnames [#452](https://github.com/mathuo/dockview/issues/452) + +- Expose root drop target configuration options [#431](https://github.com/mathuo/dockview/issues/431) + +## 🛠 Miscs + +- Bug: Floating groups position reset when display:none applied to component [#458](https://github.com/mathuo/dockview/issues/458) + +## 🔥 Breaking changes diff --git a/packages/docs/blog/2024-01-23-dockview-1.9.2.md b/packages/docs/blog/2024-01-23-dockview-1.9.2.md new file mode 100644 index 000000000..0ad003326 --- /dev/null +++ b/packages/docs/blog/2024-01-23-dockview-1.9.2.md @@ -0,0 +1,19 @@ +--- +slug: dockview-1.9.2-release +title: Dockview 1.9.2 +tags: [release] +--- + +# Release Notes + +Please reference to docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Expose addGroup options [#465](https://github.com/mathuo/dockview/issues/465) + +## 🛠 Miscs + +- Bug: Panel rendering broken when closing adjacent tabs [#472](https://github.com/mathuo/dockview/issues/472) + +## 🔥 Breaking changes diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index e31864d21..40f020fb0 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -7,7 +7,7 @@ import { MultiFrameworkContainer } from '@site/src/components/ui/container'; import Link from '@docusaurus/Link'; import useBaseUrl from '@docusaurus/useBaseUrl'; -import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app'; +import DockviewPersistence from '@site/sandboxes/layout-dockview/src/app'; import SimpleDockview from '@site/sandboxes/simple-dockview/src/app'; import ResizeDockview from '@site/sandboxes/resize-dockview/src/app'; import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app'; @@ -140,7 +140,7 @@ As well as importing the `dockview` stylesheet you must provide a class-based th You can find more details on theming here. -## Layout Persistance +## Layout Persistence Layouts are loaded and saved via to `fromJSON` and `toJSON` methods on the Dockview api. The api also exposes an event `onDidLayoutChange` you can listen on to determine when the layout has changed. @@ -156,7 +156,7 @@ React.useEffect(() => { const layout = api.toJSON(); localStorage.setItem( - 'dockview_persistance_layout', + 'dockview_persistence_layout', JSON.stringify(layout) ); }); @@ -169,7 +169,7 @@ React.useEffect(() => { ```tsx title="Loading a layout from localStorage" const onReady = (event: DockviewReadyEvent) => { - const layoutString = localStorage.getItem('dockview_persistance_layout'); + const layoutString = localStorage.getItem('dockview_persistence_layout'); let success = false; @@ -194,7 +194,7 @@ If you refresh the page you should notice your layout is loaded as you left it. ## Scrollbars @@ -264,6 +264,18 @@ which will be rendered when there are no panels or groups. ## Drag And Drop +You can override the conditions of the far edge overlays through the `rootOverlayModel` prop. + +```tsx + +``` + ### Built-in behaviours Dockview supports a wide variety of built-in Drag and Drop possibilities. diff --git a/packages/docs/package.json b/packages/docs/package.json index bb2b3e9eb..6f40f715e 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,11 +1,12 @@ { "name": "dockview-docs", - "version": "1.9.0", + "version": "1.9.2", "private": true, "scripts": { "build": "docusaurus build", "clear": "docusaurus clear", "start": "docusaurus start", + "docs:version": "docusaurus docs:version", "typecheck": "tsc" }, "browserslist": { @@ -30,9 +31,10 @@ "@radix-ui/react-popover": "^1.0.7", "axios": "^1.6.3", "clsx": "^2.1.0", - "dockview": "^1.9.0", + "dockview": "^1.9.2", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", + "react-laag": "^2.0.5", "recoil": "^0.7.7", "source-map-loader": "^4.0.2", "uuid": "^9.0.1" diff --git a/packages/docs/sandboxes/demo-dockview/src/app.tsx b/packages/docs/sandboxes/demo-dockview/src/app.tsx index 58d0fd440..975f3f6be 100644 --- a/packages/docs/sandboxes/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/demo-dockview/src/app.tsx @@ -14,8 +14,27 @@ import './app.scss'; const components = { default: (props: IDockviewPanelProps<{ title: string }>) => { return ( -
- {''} +
+ + {props.api.title} +
); }, @@ -68,7 +87,7 @@ const RightControls = (props: IDockviewHeaderActionsProps) => { ); const [isPopout, setIsPopout] = React.useState( - props.api.location === 'popout' + props.api.location.type === 'popout' ); React.useEffect(() => { @@ -77,7 +96,7 @@ const RightControls = (props: IDockviewHeaderActionsProps) => { }); const disposable2 = props.api.onDidLocationChange(() => { - setIsPopout(props.api.location === 'popout'); + setIsPopout(props.api.location.type === 'popout'); }); return () => { diff --git a/packages/docs/sandboxes/dnd-dockview/src/app.tsx b/packages/docs/sandboxes/dnd-dockview/src/app.tsx index b930cd968..b2015b834 100644 --- a/packages/docs/sandboxes/dnd-dockview/src/app.tsx +++ b/packages/docs/sandboxes/dnd-dockview/src/app.tsx @@ -180,6 +180,10 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { className={`${props.theme || 'dockview-theme-abyss'}`} onDidDrop={onDidDrop} showDndOverlay={showDndOverlay} + rootOverlayModel={{ + size: { value: 100, type: 'pixels' }, + activationSize: { value: 5, type: 'percentage' }, + }} />
); diff --git a/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx b/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx index 990913d2e..c90e32cec 100644 --- a/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx +++ b/packages/docs/sandboxes/floatinggroup-dockview/src/app.tsx @@ -127,7 +127,7 @@ const useLocalStorage = ( ]; }; -export const DockviewPersistance = (props: { theme?: string }) => { +export const DockviewPersistence = (props: { theme?: string }) => { const [api, setApi] = React.useState(); const [layout, setLayout] = useLocalStorage('floating.layout'); @@ -255,13 +255,13 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => { const RightComponent = (props: IDockviewHeaderActionsProps) => { const [floating, setFloating] = React.useState( - props.api.location === 'floating' + props.api.location.type === 'floating' ); React.useEffect(() => { const disposable = props.group.api.onDidLocationChange( (event) => { - setFloating(event.location === 'floating'); + setFloating(event.location.type === 'floating'); } ); @@ -289,7 +289,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => { ); }; -export default DockviewPersistance; +export default DockviewPersistence; const Watermark = () => { return
watermark
; diff --git a/packages/docs/sandboxes/layout-dockview/src/app.tsx b/packages/docs/sandboxes/layout-dockview/src/app.tsx index 740202840..85d76bd39 100644 --- a/packages/docs/sandboxes/layout-dockview/src/app.tsx +++ b/packages/docs/sandboxes/layout-dockview/src/app.tsx @@ -47,11 +47,11 @@ function loadDefaultLayout(api: DockviewApi) { }); } -export const DockviewPersistance = (props: { theme?: string }) => { +export const DockviewPersistence = (props: { theme?: string }) => { const [api, setApi] = React.useState(); const clearLayout = () => { - localStorage.removeItem('dockview_persistance_layout'); + localStorage.removeItem('dockview_persistence_layout'); if (api) { api.clear(); loadDefaultLayout(api); @@ -60,7 +60,7 @@ export const DockviewPersistance = (props: { theme?: string }) => { const onReady = (event: DockviewReadyEvent) => { const layoutString = localStorage.getItem( - 'dockview_persistance_layout' + 'dockview_persistence_layout' ); let success = false; @@ -91,7 +91,7 @@ export const DockviewPersistance = (props: { theme?: string }) => { const layout = api.toJSON(); localStorage.setItem( - 'dockview_persistance_layout', + 'dockview_persistence_layout', JSON.stringify(layout) ); }); @@ -125,7 +125,7 @@ export const DockviewPersistance = (props: { theme?: string }) => { ); }; -export default DockviewPersistance; +export default DockviewPersistence; const Watermark = () => { return
watermark
; diff --git a/packages/docs/sandboxes/popoutgroup-dockview/package.json b/packages/docs/sandboxes/popoutgroup-dockview/package.json index 9a533bff7..9b8601416 100644 --- a/packages/docs/sandboxes/popoutgroup-dockview/package.json +++ b/packages/docs/sandboxes/popoutgroup-dockview/package.json @@ -9,7 +9,8 @@ "dependencies": { "dockview": "*", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-laag": "^2.0.5" }, "devDependencies": { "@types/react": "^18.0.28", @@ -29,4 +30,4 @@ "not ie <= 11", "not op_mini all" ] -} +} \ No newline at end of file diff --git a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx index 99770d3b9..fbca7560d 100644 --- a/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx +++ b/packages/docs/sandboxes/popoutgroup-dockview/src/app.tsx @@ -1,17 +1,38 @@ import { DockviewApi, - DockviewGroupPanel, DockviewReact, DockviewReadyEvent, IDockviewHeaderActionsProps, IDockviewPanelProps, SerializedDockview, + DockviewPanelApi, } from 'dockview'; import * as React from 'react'; import { Icon } from './utils'; +import { PopoverMenu } from './popover'; + +function usePanelWindowObject(api: DockviewPanelApi): Window { + const [document, setDocument] = React.useState(api.getWindow()); + + React.useEffect(() => { + const disposable = api.onDidLocationChange((event) => { + setDocument(api.getWindow()); + }); + + return () => { + disposable.dispose(); + }; + }, [api]); + + return document; +} const components = { default: (props: IDockviewPanelProps<{ title: string }>) => { + const _window = usePanelWindowObject(props.api); + + const [reset, setReset] = React.useState(false); + return (
- {props.params.title} + + {!reset && } + {props.api.title}
); }, }; -const counter = (() => { - let i = 0; - - return { - next: () => ++i, - }; -})(); - function loadDefaultLayout(api: DockviewApi) { api.addPanel({ id: 'panel_1', component: 'default', }); - api.addPanel({ - id: 'panel_2', - component: 'default', - }); + // api.addPanel({ + // id: 'panel_2', + // component: 'default', + // }); - api.addPanel({ - id: 'panel_3', - component: 'default', - }); + // api.addPanel({ + // id: 'panel_3', + // component: 'default', + // }); - api.addPanel({ - id: 'panel_4', - component: 'default', - }); + // api.addPanel({ + // id: 'panel_4', + // component: 'default', + // }); - api.addPanel({ - id: 'panel_5', - component: 'default', - position: { direction: 'right' }, - }); + // api.addPanel({ + // id: 'panel_5', + // component: 'default', + // position: { direction: 'right' }, + // }); - api.addPanel({ - id: 'panel_6', - component: 'default', - }); + // api.addPanel({ + // id: 'panel_6', + // component: 'default', + // }); } let panelCount = 0; @@ -214,12 +239,12 @@ const LeftComponent = (props: IDockviewHeaderActionsProps) => { const RightComponent = (props: IDockviewHeaderActionsProps) => { const [popout, setPopout] = React.useState( - props.api.location === 'popout' + props.api.location.type === 'popout' ); React.useEffect(() => { const disposable = props.group.api.onDidLocationChange((event) => [ - setPopout(event.location === 'popout'), + setPopout(event.location.type === 'popout'), ]); return () => { @@ -232,7 +257,7 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => { const group = props.containerApi.addGroup(); props.group.api.moveTo({ group }); } else { - props.containerApi.addPopoutGroup(props.group, { + const window = props.containerApi.addPopoutGroup(props.group, { popoutUrl: '/popout/index.html', }); } diff --git a/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx b/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx new file mode 100644 index 000000000..9d9663443 --- /dev/null +++ b/packages/docs/sandboxes/popoutgroup-dockview/src/popover.tsx @@ -0,0 +1,55 @@ +import { useLayer, Arrow } from 'react-laag'; +import { motion, AnimatePresence } from 'framer-motion'; +import * as React from 'react'; +import { DockviewPanelApi } from 'dockview'; + +export function PopoverMenu(props: { window: Window }) { + const [isOpen, setOpen] = React.useState(false); + + // helper function to close the menu + function close() { + setOpen(false); + } + + const { renderLayer, triggerProps, layerProps, arrowProps } = useLayer({ + isOpen, + onOutsideClick: close, // close the menu when the user clicks outside + onDisappear: close, // close the menu when the menu gets scrolled out of sight + overflowContainer: false, // keep the menu positioned inside the container + auto: true, // automatically find the best placement + placement: 'top-end', // we prefer to place the menu "top-end" + triggerOffset: 12, // keep some distance to the trigger + containerOffset: 16, // give the menu some room to breath relative to the container + arrowOffset: 16, // let the arrow have some room to breath also, + environment: props.window, + container: props.window + ? () => { + const el = props.window.document.body; + Object.setPrototypeOf(el, HTMLElement.prototype); + return el; + } + : undefined, + }); + + // Again, we're using framer-motion for the transition effect + return ( + <> + + {renderLayer( + + {isOpen && ( + +
  • Item 1
  • +
  • Item 2
  • +
  • Item 3
  • +
  • Item 4
  • + +
    + )} +
    + )} + + ); +} diff --git a/packages/docs/sandboxes/resizecontainer-dockview/src/app.tsx b/packages/docs/sandboxes/resizecontainer-dockview/src/app.tsx index 95fb87945..25c397bc2 100644 --- a/packages/docs/sandboxes/resizecontainer-dockview/src/app.tsx +++ b/packages/docs/sandboxes/resizecontainer-dockview/src/app.tsx @@ -93,7 +93,7 @@ export const App: React.FC = (props: { theme?: string }) => { ); }; -const Container = () => { +const Container = (props: any) => { const [value, setValue] = React.useState('50'); return ( @@ -108,7 +108,7 @@ const Container = () => { value={value} />
    - +
    ); diff --git a/packages/docs/versioned_docs/version-1.8.4/components/dockview.mdx b/packages/docs/versioned_docs/version-1.8.4/components/dockview.mdx deleted file mode 100644 index 6c9528f88..000000000 --- a/packages/docs/versioned_docs/version-1.8.4/components/dockview.mdx +++ /dev/null @@ -1,873 +0,0 @@ ---- -description: Dockview Documentation ---- - -import { MultiFrameworkContainer } from '@site/src/components/ui/container'; - -import Link from '@docusaurus/Link'; -import useBaseUrl from '@docusaurus/useBaseUrl'; - -import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app'; -import SimpleDockview from '@site/sandboxes/simple-dockview/src/app'; -import ResizeDockview from '@site/sandboxes/resize-dockview/src/app'; -import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app'; -import DockviewConstraints from '@site/sandboxes/constraints-dockview/src/app'; -import DndDockview from '@site/sandboxes/dnd-dockview/src/app'; -import NestedDockview from '@site/sandboxes/nested-dockview/src/app'; -import EventsDockview from '@site/sandboxes/events-dockview/src/app'; -import DockviewGroupControl from '@site/sandboxes/headeractions-dockview/src/app'; -import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app'; -import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app'; -import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app'; -import DockviewSetTitle from '@site/sandboxes/updatetitle-dockview/src/app'; -import RenderingDockview from '@site/sandboxes/rendering-dockview/src/app'; -import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app'; -import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app'; -import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app'; -import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app'; -import DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app'; -import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app'; -import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app'; - -import { DocRef, Markdown } from '@site/src/components/ui/reference/docRef'; - -import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app'; -import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app'; -import { attach as attachTabHeightDockview } from '@site/sandboxes/javascript/tabheight-dockview/src/app'; -import { attach as attachNativeDockview } from '@site/sandboxes/javascript/fullwidthtab-dockview/src/app'; - -# Dockview - -## Introduction - -Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels. - - - -
    - -> You can access the panels associated group through the `panel.group` variable. -> The group will always be defined and will change if a panel is moved into another group. - -## DockviewReact Component - -You can create a Dockview through the use of the `DockviewReact` component. - -

    - {'All of these are React props available through the '} - DockviewReact - {' component.'} -

    - - -## Dockview API - -The Dockview API is exposed both at the `onReady` event and on each panel through `props.containerApi`. -Through this API you can control general features of the component and access all added panels. - -```tsx title="Dockview API via Panel component" -const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => { - // props.containerApi... - - return
    {`My first panel has the title: ${props.params.title}`}
    ; -}; -``` - -```tsx title="Dockview API via the onReady callback" -const onReady = (event: DockviewReadyEvent) => { - // event.api... -}; -``` - -

    - {'All of these methods are available through the '} - api - {' property of '} - DockviewComponent - {' and the '} - containerApi - {' property of '} - IDockviewPanel - . -

    - - - -## Dockview Panel API - -```tsx -const MyComponent = (props: IDockviewPanelProps<{ title: string }>) => { - // props.api... - - return
    {`My first panel has the title: ${props.params.title}`}
    ; -}; -``` - -

    - {'All of these are methods are available through the '} - api - {' property of '} - IDockviewPanel - . -

    - - - -## Theme - -As well as importing the `dockview` stylesheet you must provide a class-based theme somewhere in your application. For example. - -```tsx -// Providing a theme directly through the DockviewReact component props - - -// Providing a theme somewhere in the DOM tree -
    -
    - {/**... */} - -
    -
    -``` - -You can find more details on theming here. - -## Layout Persistance - -Layouts are loaded and saved via to `fromJSON` and `toJSON` methods on the Dockview api. -The api also exposes an event `onDidLayoutChange` you can listen on to determine when the layout has changed. -Below are some snippets showing how you might load from and save to localStorage. - -```tsx title="Saving the layout state to localStorage" -React.useEffect(() => { - if (!api) { - return; - } - - const disposable = api.onDidLayoutChange(() => { - const layout = api.toJSON(); - - localStorage.setItem( - 'dockview_persistance_layout', - JSON.stringify(layout) - ); - }); - - return () => { - disposable.dispose(); - }; -}, [api]); -``` - -```tsx title="Loading a layout from localStorage" -const onReady = (event: DockviewReadyEvent) => { - const layoutString = localStorage.getItem('dockview_persistance_layout'); - - let success = false; - - if (layoutString) { - try { - const layout = JSON.parse(layoutString); - event.api.fromJSON(layout); - success = true; - } catch (err) { - // - } - } - - if (!success) { - // do something if there is no layout or there was a loading error - } -}; -``` - -Here is an example using the above code loading from and saving to localStorage. -If you refresh the page you should notice your layout is loaded as you left it. - - - -## Resizing - -### Panel Resizing - -Each Dockview contains of a number of groups and each group has a number of panels. -Logically a user may want to resize a panel, but this translates to resizing the group which contains that panel. - -You can set the size of a panel using `props.api.setSize(...)`. -You can also set the size of the group associated with the panel using `props.api.group.api.setSize(...)` although this isn't recommended -due to the clunky syntax. - -```tsx -// it's mandatory to provide either a height or a width, providing both is optional -props.api.setSize({ - height: 100, - width: 200, -}); - -// you could also resize the panels group, although not recommended it achieved the same result -props.api.group.api.setSize({ - height: 100, - width: 200, -}); -``` - -You can see an example invoking both approaches below. - - - -### Container Resizing - -The component will automatically resize to it's container. - - - -## Watermark - -When the dockview is empty you may want to display some fallback content, this is refered to as the `watermark`. -By default there the watermark has no content but you can provide as a prop to `DockviewReact` a `watermarkComponent` -which will be rendered when there are no panels or groups. - - - -## Drag And Drop - -### Built-in behaviours - -Dockview supports a wide variety of built-in Drag and Drop possibilities. -Below are some examples of the operations you can perform. - - - -> Drag a tab onto another tab to place it inbetween existing tabs. - - - -> Drag a tab to the right of the last tab to place it after the existing tabs. - - - -> Drag a group onto an existing group to merge the two groups. - -
    - - -
    - -> Drag into the left/right/top/bottom target zone of a panel to create a new group in the selected direction. - -> Drag into the center of a panel to add to that group. - -> Drag to the edge of the dockview component to create a new group on the selected edge. - -### Extended behaviours - -For interaction with the Drag events directly the component exposes some method to help determine whether external drag events should be interacted with or not. - -```tsx -/** - * called when an ondrop event which does not originate from the dockview libray and - * passes the showDndOverlay condition occurs - **/ -const onDidDrop = (event: DockviewDropEvent) => { - const { group } = event; - - event.api.addPanel({ - id: 'test', - component: 'default', - position: { - referencePanel: group.activePanel.id, - direction: 'within', - }, - }); -}; - -/** - * called for drag over events which do not originate from the dockview library - * allowing the developer to decide where the overlay should be shown for a - * particular drag event - **/ -const showDndOverlay = (event: DockviewDndOverlayEvent) => { - return true; -}; - -return ( - -); -``` - -### Intercepting Drag Events - -You can intercept drag events to attach your own metadata using the `onWillDragPanel` and `onWillDragGroup` api methods. - - - -### Third Party Dnd Libraries - -This shows a simple example of a third-party library used inside a panel that relies on drag -and drop functionalities. This examples serves to show that `dockview` doesn't interfer with -any drag and drop logic for other controls. - - - -## Floating Groups - -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 - - - -> Move a floating tab by holding `shift` whilst moving the cursor or dragging the empty -> header space - - - -> Move an entire floating group by holding `shift` whilst dragging the empty header space - - - -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.api.isFloating` property. See examples for full code. - -You can control the bounding box of floating groups through the optional `floatingGroupBounds` options: - -- `boundedWithinViewport` will force the entire floating group to be bounded within the docks viewport. -- `{minimumHeightWithinViewport?: number, minimumWidthWithinViewport?: number}` sets the respective dimension minimums that must appears within the docks viewport -- If no options are provided the defaults of `100px` minimum height and width within the viewport are set. - - - -## Panels - -### Add Panel - -Using the dockview API you can access the `addPanel` method which returns an instance of the created panel. -The minimum method signature is: - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', -}); -``` - -where `id` is the unique id of the panel and `component` is the implenentation which -will be used to render the panel. You will have registered this using the `components` prop of the `DockviewReactComponent` component. - -You can optionally provide a `tabComponent` parameters to the `addPanel` method which will render the tab using a custom renderer. -You will have registered this using the `tabComponents` prop of the `DockviewReactComponent` component. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - tabComponent: 'my_tab_component', -}); -``` - -You can pass properties to the panel using the `params` key. -You can update these properties through the panels `api` object and its `updateParameters` method. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - params: { - myCustomKey: 'my_custom_value', - }, -}); - -panel.api.updateParameters({ - myCustomKey: 'my_custom_value', - myOtherCustomKey: 'my_other_custom_key', -}); -``` - -> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters -> you want the panel to receive. - -Finally `addPanel` accepts a `position` object which tells dockview where to place the panel. - -- This object optionally accepts either a `referencePanel` or `referenceGroup` which can be the associated id as a string - or the panel/group object reference. -- This object accepts a `direction` property which dictates where, - relative to the provided reference the new panel will be placed. - -> If neither a `referencePanel` or `referenceGroup` is provided then the `direction` will be treated as absolute. - -> If no `direction` is provided the library will place the new panel in a pre-determined position. - -```ts -const panel = api.addPanel({ - id: 'panel_1', - component: 'default', -}); - -const panel2 = api.addPanel({ - id: 'panel_2', - component: 'default', - position: { - referencePanel: panel1, - direction: 'right', - }, -}); -``` - -To add a floating panel you should include the `floating` variable which can be either a `boolean` or an object defining it's bounds. -These bounds are relative to the dockview component. - -```ts -const panel1 = api.addPanel({ - id: 'panel_2', - component: 'default', - floating: true, -}); - -const panel2 = api.addPanel({ - id: 'panel_2', - component: 'default', - floating: { x: 10, y: 10, width: 300, height: 300 }, -}); -``` - -### Update Panel - -You can programatically update the `params` passed through to the panel through the panal api using `api.updateParameters`. - -```ts -const panel = api.addPanel({ - id: 'panel_1', - component: 'default', - params: { - keyA: 'valueA', - }, -}); - -// ... - -panel.api.updateParameters({ - keyB: 'valueB', -}); - -// ... - -panel.api.updateParameters({ - keyA: 'anotherValueA', -}); -``` - -To delete a parameter you should pass a value of `undefined` for the key. - -```ts -panel.api.updateParameters({ - keyA: undefined, // this will delete 'keyA'. -}); -``` - -### Move panel - -You can programatically move a panel using the panel `api`. - -```ts -panel.api.moveTo({ group, position, index }); -``` - -An equivalent method for moving groups is avaliable on the group `api`. - -```ts -const group = panel.api.group; -group.api.moveTo({ group, position }); -``` - -### Remove panel - -You can programatically remove a panel using the panel `api`. - -```ts -panel.api.close(); -``` - -Given a reference to the panel you can also use the component `api` to remove it. - -```ts -const panel = api.getPanel('myPanel'); -api.removePanel(panel); -``` - -### Panel Rendering - -By default `DockviewReact` only adds to the DOM those panels that are visible, -if a panel is not the active tab and not shown the contents of the hidden panel will be removed from the DOM. - -However the React Components associated with each panel are only created once and will always exist for as long as the panel exists, hidden or not. - -> For example this means that any hooks in those components will run whether the panel is visible or not which may lead to excessive background work depending -> on the panels implementation. - -This is the default behaviour to ensure the greatest flexibility for the user but through the panels `props.api` you can listen to the visiblity state of the panel -and write additional logic to optimize your application. - -For example if you wanted to unmount the React Components when the panel is not visible you could create a Higher-Order-Component that listens to the panels -visiblity state and only renders the panel when visible. - -```tsx title="Only rendering the React Component when the panel is visible, otherwise rendering a null React Component" -import { IDockviewPanelProps } from 'dockview'; -import * as React from 'react'; - -function RenderWhenVisible( - component: React.FunctionComponent -) { - const HigherOrderComponent = (props: IDockviewPanelProps) => { - const [visible, setVisible] = React.useState( - props.api.isVisible - ); - - React.useEffect(() => { - const disposable = props.api.onDidVisibilityChange((event) => - setVisible(event.isVisible) - ); - - return () => { - disposable.dispose(); - }; - }, [props.api]); - - if (!visible) { - return null; - } - - return React.createElement(component, props); - }; - return HigherOrderComponent; -} -``` - -```tsx -const components = { default: RenderWhenVisible(MyComponent) }; -``` - -Toggling the checkbox you can see that when you only render those panels which are visible the underling React component is destroyed when it becomes hidden and re-created when it becomes visible. - - - -## Headers - -### Custom Tab Headers - -You can provide custom renderers for your tab headers for maximum customization. -A default implementation of `DockviewDefaultTab` is provided should you only wish to attach minor -changes and events that do not alter the default behaviour, for example to add a custom context menu event -handler. - -The `DockviewDefaulTab` component accepts a `hideClose` prop if you wish only to hide the close button. - -```tsx title="Attaching a custom context menu event handlers to a custom header" -import { IDockviewPanelHeaderProps, DockviewDefaultTab } from 'dockview'; - -const MyCustomheader = (props: IDockviewPanelHeaderProps) => { - const onContextMenu = (event: React.MouseEvent) => { - event.preventDefault(); - alert('context menu'); - }; - return ; -}; -``` - -You are also free to define a custom renderer entirely from scratch and not make use of the `DockviewDefaultTab` component. -To use a custom renderer you can must register a collection of tab components. - -```tsx -const tabComponents = { - myCustomHeader: MyCustomHeader, -}; - -return ; -``` - -```tsx -api.addPanel({ - id: 'panel_1', - component: 'default', - tabComponent: 'myCustomHeader', // <-- your registered renderers - title: 'Panel 1', -}); -``` - -You can also override the default tab renderer which will be used when no `tabComponent` is provided to the `addPanel` function. - -```tsx -; -``` - -As a simple example the below attaches a custom event handler for the context menu on all tabs as a default tab renderer - -The below example uses a custom tab renderer to reigster a popover when the user right clicked on a tab. -This still makes use of the `DockviewDefaultTab` since it's only a minor change. - - - -### Default Tab Title - -If you are using the default tab renderer you can set the title of a tab when creating it - -```tsx -api.addPanel({ - id: 'panel_1', - component: 'my_component', - title: 'my_custom_title', // <-- special param for title -}); -``` - -You can update the title through the panel api which can be accessed via `props.api` if you are inside the panel -component or via `api.getPanel('panel1').api` if you are accessing from outside of the panel component. - -```tsx -api.setTitle('my_new_custom_title'); -``` - -> Note this only works when using the default tab implementation. - - - -### Custom Tab Title - -If you are using a custom tab implementation you should pass variables through as a parameter and render them -through your tab components implementation. - -```tsx title="Add a panel with custom parameters" -api.addPanel({ - id: 'panel_2', - component: 'my_component', - tabComponent: 'my_tab', - params: { - myTitle: 'Window 2', // <-- passing a variable to use as a title - }, -}); -``` - -```tsx title="Accessing custom parameters from a custom tab renderer" -const tabComponents = { - default: (props: IDockviewPanelHeaderProps<{ myTitle: string }>) => { - const title = props.params.myTitle; // <-- accessing my custom varaible - return
    {/** tab implementation as chosen by developer */}
    ; - }, -}; -``` - -### Hidden Headers - -You may wish to hide the header section of a group. This can achieved through the `hidden` variable on `panel.group.header`. - -```tsx -panel.group.header.hidden = true; -``` - -### Full width tabs - -`DockviewReactComponent` accepts the prop `singleTabMode`. If set `singleTabMode=fullwidth` then when there is only one tab in a group this tab will expand -to the entire width of the group. For example: - -> This can be conmbined with Locked Groups to create an application that feels more like a Window Manager -> rather than a collection of groups and tabs. - -```tsx - -``` - - - -### Tab Height - -Tab height can be controlled through CSS. - - - -## Groups - -### Locked group - -Locking a group will disable all drop events for this group ensuring no additional panels can be added to the group through drop events. -You can still add groups to a locked panel programatically using the API though. - -```tsx -panel.group.locked = true; - -// Or - -panel.group.locked = 'no-drop-target'; -``` - -Use `true` to keep drop zones top, right, bottom, left for the group. Use `no-drop-target` to disable all drop zones. For you to get a -better understanding of what this means, try and drag the panels in the example below to the locked groups. - - - -### Group Controls Panel - -`DockviewReact` accepts `leftHeaderActionsComponent`, `rightHeaderActionsComponent` and `prefixHeaderActionsComponent` which expect a React component with props `IDockviewHeaderActionsProps`. -These controls are rendered to left and right side of the space to the right of the tabs in the header bar as well as before the first tab in the case of the prefix header prop. - -```tsx -const Component: React.FunctionComponent = () => { - return
    {'...'}
    ; -}; - -return ; -``` - -As a simple example the below uses the `groupControlComponent` to render a small control that indicates whether the group -is active and which panel is active in that group. - -```tsx -const RightHeaderActionsComponent = (props: IDockviewHeaderActionsProps) => { - const isGroupActive = props.isGroupActive; - const activePanel = props.activePanel; - - return ( -
    - - {isGroupActive ? 'Group Active' : 'Group Inactive'} - - {`activePanel: ${ - activePanel?.id || 'null' - }`} -
    - ); -}; -``` - - - -### Constraints - -You may wish to specify a minimum or maximum height or width for a group which can be done through the group api. - -```tsx -api.group.api.setConstraints(...) -``` - -> Constraints are currently only supported for groups and not individual panels. -> If you specific a constraint on a group and move a panel within that group to another group it will no -> longer be subject to those constraints since those constraints were on the group and not on the individual panel. - - - -## iFrames - -iFrames required special attention because of a particular behaviour in how iFrames render: - -> Re-parenting an iFrame will reload the contents of the iFrame or the rephrase this, moving an iFrame within the DOM will cause a reload of its contents. - -You can find many examples of discussions on this. Two reputable forums for example are linked [here](https://bugzilla.mozilla.org/show_bug.cgi?id=254144) and [here](https://github.com/whatwg/html/issues/5484). - -The problem with iFrames and `dockview` is that when you hide or move a panel that panels DOM element may be moved within the DOM or removed from the DOM completely. -If your panel contains an iFrame then that iFrame will reload after being re-positioned within the DOM tree and all state in that iFrame will most likely be lost. - -`dockview` does not provide a built-in solution to this because it's too specific of a problem to include in the library. -However the below example does show an implementation of a higher-order component `HoistedDockviewPanel`that you could use to work around this problems and make iFrames behave in `dockview`. - -What the higher-order component is doing is to hoist the panels contents into a DOM element that is always present and then `position: absolute` that element to match the dimensions of it's linked panel. -The visibility of these hoisted elements is then controlled through some exposed api methods to hide elements that shouldn't be currently shown. - -You should open this example in CodeSandbox using the provided link to understand the code and make use of this implemention if required. - - - -## Events - -A simple example showing events fired by `dockviewz that can be interacted with. - - - -## Keyboard Navigation - -Keyboard shortcuts - - - -### Nested Dockviews - -You can safely create multiple dockview instances within one page and nest dockviews within other dockviews. -If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `showDndOverlay` and `onDidDrop` props on `DockviewReact`. - - - -### Window-like mananger with tabs - - diff --git a/packages/docs/versioned_docs/version-1.8.4/contributing.mdx b/packages/docs/versioned_docs/version-1.8.4/contributing.mdx deleted file mode 100644 index e35ed70d4..000000000 --- a/packages/docs/versioned_docs/version-1.8.4/contributing.mdx +++ /dev/null @@ -1,52 +0,0 @@ ---- -sidebar_position: 3 -description: Contributing ---- - -# Contributing - -# Project description - -Dockview is a layout manager library designed to provide a complete layouting solution. -It is written in plain TypeScript and can be used without any framework although -an extensive React wrapper has always and will always be provided for those using the React framework. - -The project is hosted on GitHub and developed within a Monorepo powered by [Lerna](https://github.com/lerna/lerna). -It is developed using the `yarn` package manager since at the time of creation `yarn` was far superior when it came to managing monorepos. -The Monorepo contains three packages: - -#### packages/dockview-core - -The core project is entirely written in plain TypeScript without any frameworks or dependencies and it's source-code can be found -within the `dockview-core` package which is also published to npm. - -#### packages/dockview - -A complete collection of React components for use through the React framework to use dockview seamlessly -and is published to npm. It depends explicitly on `dockview-core` so there is no need to additionally install `dockview-core`. - -> Dockview was originally a React-only library which is why the React version maintains the name `dockview` after -> splitting the core logic into a seperate package named `dockview-core`. - -#### packages/docs - -This package contains the code for this documentation website and examples hosted through **CodeSandbox**. It is **not** a published package on npm. - -# Run the project locally - -1. After you have cloned the project from GitHub run `yarn` at the root of the project which will install all project dependencies. -2. In order build `packages/dockview-core` then `packages/dockview`. -3. Run the docs website through `npm run start` in the `packages/docs` directory and go to _http://localhost:3000_ which - will now be running the local copy of `dockview` that you have just built. - -### Examples - -All examples can be found under [**packages/docs/sandboxes**](https://github.com/mathuo/dockview/tree/master/packages/docs/sandboxes). -Each example is an independently runnable example through **CodeSandbox**. -Through the documentation you will see links to runnable **CodeSandbox** examples. - -## FAQ - -#### Are there any plans to publish wrapper libraries for other frameworks such as Angular and Vue? - -Currently no but this is open for contributors to try. diff --git a/packages/docs/versioned_docs/version-1.8.4/index.mdx b/packages/docs/versioned_docs/version-1.8.4/index.mdx deleted file mode 100644 index 9ea7f0923..000000000 --- a/packages/docs/versioned_docs/version-1.8.4/index.mdx +++ /dev/null @@ -1,91 +0,0 @@ ---- -sidebar_position: 0 -description: A zero dependency layout manager supporting ReactJS and Vanilla TypeScript ---- - -import { SimpleSplitview } from '@site/src/components/simpleSplitview'; -import { SimpleGridview } from '@site/src/components/simpleGridview'; -import { SimplePaneview } from '@site/src/components/simplePaneview'; -import SimpleDockview from '@site/sandboxes/simple-dockview/src/app'; -import Link from '@docusaurus/Link'; - -# Introduction - -**dockview** is a zero dependency layout manager that supports tab, grids and splitviews. - -## Quick start - -`dockview` has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. To install `dockview` you can run: - -```shell -npm install dockview -``` - -You must also import the dockview stylesheet found under [`dockview/dict/styles/dockview.css`](https://unpkg.com/browse/dockview@latest/dist/styles/dockview.css), -depending on your solution this might be: - -```css -@import './node_modules/dockview/dist/styles/dockview.css'; -``` - -There are 4 components you may want to use: - - -

    Dockview

    - - -
    - -
    - - -

    Splitview

    - - -
    - -
    - - -

    Gridview

    - - -
    - -
    - - -

    Paneview

    - - -
    - -
    diff --git a/packages/docs/versioned_docs/version-1.9.0/components/_category_.json b/packages/docs/versioned_docs/version-1.9.0/components/_category_.json deleted file mode 100644 index 07caf3db9..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/components/_category_.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "label": "Components", - "collapsible": true, - "collapsed": false, - "position": 2, - "link": { - "type": "generated-index", - "title": "Components" - } -} \ No newline at end of file diff --git a/packages/docs/versioned_docs/version-1.9.0/components/gridview.mdx b/packages/docs/versioned_docs/version-1.9.0/components/gridview.mdx deleted file mode 100644 index 5d61ed21a..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/components/gridview.mdx +++ /dev/null @@ -1,235 +0,0 @@ ---- -description: Gridview Documentation ---- - -import { MultiFrameworkContainer } from '@site/src/components/ui/container'; -import SimpleGridview from '@site/sandboxes/simple-gridview/src/app'; -import EditorGridview from '@site/sandboxes/editor-gridview/src/app'; -// import SimpleGridview from '@site/sandboxes/simple-gridview/src/app'; -import { EventsGridview } from '@site/src/components/gridview/events'; -// import IDEExample from '@site/sandboxes/ide-example/src/app'; -import Link from '@docusaurus/Link'; -import { DocRef } from '@site/src/components/ui/reference/docRef'; - -# Gridview - -Gridview is a collection of nested splitviews and is the foundation for the [Dockview](./dockview) component. -Gridview serves a purpose when you want only the nested splitviews with no tabs and no headers. - -## Introduction - - - -## GridviewReact Component - -```tsx -import { ReactGridview } from 'dockview'; -``` - - - -## Gridview API - -```tsx -const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => { - // props.containerApi... - - return
    {`My first panel has the title: ${props.params.title}`}
    ; -}; -``` - -```tsx -const onReady = (event: GridviewReadyEvent) => { - // event.api... -}; -``` - - - -## Gridview Panel API - -```tsx -const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => { - // props.api... - - return
    {`My first panel has the title: ${props.params.title}`}
    ; -}; -``` - - - -## Resizing - -### Panel Resizing - -You can set the size of a panel using `props.api.setSize(...)`. - -```tsx -// it's mandatory to provide either a height or a width, providing both is optional -props.api.setSize({ - height: 100, - width: 200, -}); -``` - -You can update any constraints on the panel. All parameters are optional. - -```tsx -props.api.setConstraints({ - minimumHeight: 100, - maximumHeight: 1000 - minimumWidth: 100, - maximumWidth: 1000 -}); -``` - -You can hide a panel by setting it's visibility to `false`. Hidden panels retain their size -at the point of being hidden, if made visible again they will try to resize to the remembered size. - -```tsx -props.api.setVisible(false); -``` - -## Panels - -### Add Panel - -Using the gridview API you can access the `addPanel` method which returns an instance of the created panel. -The minimum method signature is: - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', -}); -``` - -where `id` is the unique id of the panel and `component` is the implenentation which -will be used to render the panel. You will have registered this using the `components` prop of the `GridviewReactComponent` component. - -You can pass bounding constraints to limit the size of the panel. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - minimumHeight: 100, - maximumHeight: 1000, - minimumWidth: 100, - maximumWidth: 1000, -}); -``` - -You can pass a `snap` parameter which will hide the panel when an attempt is made to move it beyond a minimum width or height if one exists. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - minimumHeight: 100, - snap: true, -}); -``` - -You can pass a `priority` parameter which will keep the panel a certain priority when being resized. This is useful when you know you want this -panel to always take the first available or last available space. The default is `LayoutPriority.Normal` which defers space allocations to the libraries discression. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - minimumHeight: 100, - priority: LayoutPriority.High, -}); -``` - -You can pass properties to the panel using the `params` key. -You can update these properties through the panels `api` object and its `updateParameters` method. - -```ts -const panel = api.addPanel({ - id: 'my_unique_panel_id', - component: 'my_component', - params: { - myCustomKey: 'my_custom_value', - }, -}); -``` - -```tsx -panel.api.updateParameters({ - myCustomKey: 'my_custom_value', - myOtherCustomKey: 'my_other_custom_key', -}); -``` - -> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters -> you want the panel to receive. - -Finally `addPanel` accepts a `position` object which tells dockview where to place the panel. - -- This object accepts a `referencePanel` which can be the associated id as a string - or the panel object reference. -- This object accepts a `direction` property which dictates where, - relative to the provided reference the new panel will be placed. - -> If a `referencePanel` is not passed then the `direction` will be treated as absolute. - -> If no `direction` is provided the library will place the new panel in a pre-determined position. - -```ts -const panel = api.addPanel({ - id: 'panel_1', - component: 'default', -}); - -const panel2 = api.addPanel({ - id: 'panel_2', - component: 'default', - position: { - referencePanel: panel1, - direction: 'right', - }, -}); -``` - -> Note `updateParameters` does not accept partial parameter updates, you should call it with the entire set of parameters -> you want the panel to receive. - -## Theme - -As well as importing the `dockview` stylesheet you must provide a class-based theme somewhere in your application. For example. - -```tsx -// Providing a theme directly through the DockviewReact component props - - -// Providing a theme somewhere in the DOM tree -
    -
    - {/**... */} - -
    -
    -``` - -You can find more details on theming here. - -## Events - -`GridviewReact` exposes a number of events that the developer can listen to and below is a simple example with a log panel showing those events that occur. - - - -## Complex Example - - diff --git a/packages/docs/versioned_docs/version-1.9.0/components/paneview.mdx b/packages/docs/versioned_docs/version-1.9.0/components/paneview.mdx deleted file mode 100644 index 95e025478..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/components/paneview.mdx +++ /dev/null @@ -1,228 +0,0 @@ ---- -description: Paneview Documentation ---- - -import { MultiFrameworkContainer } from '@site/src/components/ui/container'; -import SimplePaneview from '@site/sandboxes/simple-paneview/src/app'; -import { CustomHeaderPaneview } from '@site/src/components/paneview/customHeader'; -import { DragAndDropPaneview } from '@site/src/components/paneview/dragAndDrop'; -import { SideBySidePaneview } from '@site/src/components/paneview/sideBySide'; -import Link from '@docusaurus/Link'; -import { DocRef } from '@site/src/components/ui/reference/docRef'; - -# Paneview - -A paneview is a collapsed collection of vertically stacked panels and panel headers. -The panel header will always remain visible however the panel will only be visible when the panel is expanded. - -:::info - -Paneview panels can be re-ordered by dragging and dropping the panel headers. - -::: - ---- - -# Introduction - - - -```tsx title="Simple Paneview example" -import { - IPaneviewPanelProps, - PaneviewReact, - PaneviewReadyEvent, -} from 'dockview'; - -const components = { - default: (props: IPaneviewPanelProps<{ title: string }>) => { - return ( -
    - {props.params.title} -
    - ); - }, -}; - -SimplePaneview = () => { - const onReady = (event: PaneviewReadyEvent) => { - event.api.addPanel({ - id: 'panel_1', - component: 'default', - params: { - title: 'Panel 1', - }, - title: 'Panel 1', - }); - - event.api.addPanel({ - id: 'panel_2', - component: 'default', - params: { - title: 'Panel 2', - }, - title: 'Panel 2', - }); - - event.api.addPanel({ - id: 'panel_3', - component: 'default', - params: { - title: 'Panel 3', - }, - title: 'Panel 3', - }); - }; - - return ( - - ); -}; -``` - -## PaneviewReact Component - -You can create a Paneview through the use of the `ReactPaneview` component. - -```tsx -import { ReactPaneview } from 'dockview'; -``` - - - -## Paneview API - -The Paneview API is exposed both at the `onReady` event and on each panel through `props.containerApi`. -Through this API you can control general features of the component and access all added panels. - -```tsx title="Paneview API via Panel component" -const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => { - // props.containerApi... - - return
    {`My first panel has the title: ${props.params.title}`}
    ; -}; -``` - -```tsx title="Paneview API via the onReady callback" -const onReady = (event: GridviewReadyEvent) => { - // event.api... -}; -``` - - - -## Paneview Panel API - -```tsx -const MyComponent = (props: IGridviewPanelProps<{ title: string }>) => { - // props.api... - - return
    {`My first panel has the title: ${props.params.title}`}
    ; -}; -``` - - - -## Advanced Features - -### Custom Header - -You can provide a custom component to render an alternative header. - -
    - -
    - -You can provide a `headerComponent` option when creating a panel to tell the library to use a custom header component. - -```tsx -const onReady = (event: PaneviewReadyEvent) => { - event.api.addPanel({ - id: 'panel_1', - component: 'default', - headerComponent: 'myHeaderComponent', - params: { - valueA: 'A', - }, - title: 'Panel 1', - }); -}; -``` - -This header must be defined in the collection of components provided to the `headerComponents` props for `ReactPaneivew` - -```tsx -import { IPaneviewPanelProps } from 'dockview'; - -const MyHeaderComponent = (props: IPaneviewPanelProps<{ title: string }>) => { - const [expanded, setExpanded] = React.useState( - props.api.isExpanded - ); - - React.useEffect(() => { - const disposable = props.api.onDidExpansionChange((event) => { - setExpanded(event.isExpanded); - }); - - return () => { - disposable.dispose(); - }; - }, []); - - const onClick = () => { - props.api.setExpanded(!expanded); - }; - - return ( - - ); -}; - -const headerComponents = { myHeaderComponent: MyHeaderComponent }; -``` - -### Drag And Drop - -If you provide the `PaneviewReact` component with the prop `onDidDrop` you will be able to interact with custom drop events. - - - -### Interactions - -You can safely create multiple paneview instances within one page. They will not interact with each other by default. - -If you wish to interact with the drop event from one paneview instance in another paneview instance you can implement the `showDndOverlay` and `onDidDrop` props on `PaneviewReact`. - -As an example see how dragging a header from one control to another will only trigger an interactable event for the developer if the checkbox is enabled. - - diff --git a/packages/docs/versioned_docs/version-1.9.0/components/splitview.mdx b/packages/docs/versioned_docs/version-1.9.0/components/splitview.mdx deleted file mode 100644 index ede28b404..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/components/splitview.mdx +++ /dev/null @@ -1,196 +0,0 @@ ---- -description: Splitview Documentation ---- - -import { SimpleSplitview } from '@site/src/components/simpleSplitview'; -import { SplitviewExample1 } from '@site/src/components/splitview/active'; -import Link from '@docusaurus/Link'; -import { DocRef } from '@site/src/components/ui/reference/docRef'; - -# Splitview - -## Introduction - -A Splitview is a collection of resizable horizontally or vertically stacked panels. - -
    - -
    - -```tsx title="Simple Splitview example" -import { - ISplitviewPanelProps, - Orientation, - SplitviewReact, - SplitviewReadyEvent, -} from 'dockview'; - -const components = { - default: (props: ISplitviewPanelProps<{ title: string }>) => { - return
    {props.params.title}
    ; - }, -}; - -export const SimpleSplitview = () => { - const onReady = (event: SplitviewReadyEvent) => { - event.api.addPanel({ - id: 'panel_1', - component: 'default', - params: { - title: 'Panel 1', - }, - }); - - event.api.addPanel({ - id: 'panel_2', - component: 'default', - params: { - title: 'Panel 2', - }, - }); - - event.api.addPanel({ - id: 'panel_3', - component: 'default', - params: { - title: 'Panel 3', - }, - }); - }; - - return ( - - ); -}; -``` - -## SplitviewReact Component - -You can create a Splitview through the use of the `ReactSplitview` component. - -```tsx -import { ReactSplitview } from 'dockview'; -``` - -Using the `onReady` prop you can access to the component `api` and add panels either through deserialization or the individual addition of panels. - - - -## Splitview API - -The Splitview API is exposed both at the `onReady` event and on each panel through `props.containerApi`. -Through this API you can control general features of the component and access all added panels. - -```tsx title="Splitview API via Panel component" -const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => { - // props.containerApi... - - return
    {`My first panel has the title: ${props.params.title}`}
    ; -}; -``` - -```tsx title="Splitview API via the onReady callback" -const onReady = (event: SplitviewReadyEvent) => { - // event.api... -}; -``` - - - -## Splitview Panel API - -The Splitview panel API is exposed on each panel containing actions and variables specific to that panel. - -```tsx title="Splitview panel API via Panel component" -const MyComponent = (props: ISplitviewPanelProps<{ title: string }>) => { - // props.api... - - return
    {`My first panel has the title: ${props.params.title}`}
    ; -}; -``` - - - -## Advanced Features - -Listed below are some functionalities avalaible through both the panel and component APIs. The live demo shows examples of these in real-time. - -
    - -
    - -### Visibility - -A panels visibility can be controlled and monitored through the following code. -A panel with visibility set to `false` will remain as a part of the components list of panels but will not be rendered. - -```tsx -const disposable = props.api.onDidVisibilityChange(({ isVisible }) => { - // -}); -``` - -```tsx -api.setVisible(true); -``` - -### Active - -Only one panel in the `splitview` can be the active panel at any one time. -Setting a panel as active will set all the others as inactive. -A focused panel is always the active panel but an active panel is not always focused. - -```tsx -const disposable = props.api.onDidActiveChange(({ isActive }) => { - // -}); -``` - -```tsx -api.setActive(); -``` - -### Contraints - -When adding a panel you can specify pixel size contraints - -```tsx -event.api.addPanel({ - id: 'panel_3', - component: 'default', - minimumSize: 100, - maximumSize: 1000, -}); -``` - -These contraints can be updated throughout the lifecycle of the `splitview` using the panel API - -```tsx -props.api.onDidConstraintsChange(({ maximumSize, minimumSize }) => { - // -}); -``` - -```tsx -api.setConstraints({ - maximumSize: 200, - minimumSize: 400, -}); -``` diff --git a/packages/docs/versioned_docs/version-1.9.0/theme.mdx b/packages/docs/versioned_docs/version-1.9.0/theme.mdx deleted file mode 100644 index a3d119323..000000000 --- a/packages/docs/versioned_docs/version-1.9.0/theme.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -sidebar_position: 1 -description: Theming Dockview Components ---- - -# Theme - -## Introduction - -`dockview` requires some CSS to work correctly. -The CSS is exported as one file under [`dockview/dict/styles/dockview.css`](https://unpkg.com/browse/dockview@latest/dist/styles/dockview.css) -and should be imported at some point in your application - -```css title="Example import with .css file" -@import './node_modules/dockview/dist/styles/dockview.css'; -``` - -## Provided themes - -`dockview` comes with a number of themes which are all CSS classes and can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss). -To use a `dockview` theme the CSS must encapsulate the component. The current list of themes is: - -- `dockview-theme-dark` -- `dockview-theme-light` -- `dockview-theme-vs` -- `dockview-theme-abyss` -- `dockview-theme-dracula` -- `dockview-theme-replit` - -## Customizing Theme - -`dockview` supports theming through the use of css properties. -You can view the built-in themes at [`dockview/src/theme.scss`](https://github.com/mathuo/dockview/blob/master/packages/dockview/src/theme.scss) -and are free to build your own themes based on these css properties. - -| CSS Property | Description | -| ---------------------------------------------------- | ----------- | -| --dv-paneview-active-outline-color | | -| --dv-tabs-and-actions-container-font-size | | -| --dv-tabs-and-actions-container-height | | -| --dv-tab-close-icon | | -| --dv-drag-over-background-color | | -| --dv-drag-over-border-color | | -| --dv-tabs-container-scrollbar-color | | -| | | -| --dv-group-view-background-color | | -| | | -| --dv-tabs-and-actions-container-background-color | | -| | | -| --dv-activegroup-visiblepanel-tab-background-color | | -| --dv-activegroup-hiddenpanel-tab-background-color | | -| --dv-inactivegroup-visiblepanel-tab-background-color | | -| --dv-inactivegroup-hiddenpanel-tab-background-color | | -| --dv-tab-divider-color | | -| | | -| --dv-activegroup-visiblepanel-tab-color | | -| --dv-activegroup-hiddenpanel-tab-color | | -| --dv-inactivegroup-visiblepanel-tab-color | | -| --dv-inactivegroup-hiddenpanel-tab-color | | -| | | -| --dv-separator-border | | -| --dv-paneview-header-border-color | | -| | | -| --dv-icon-hover-background-color | | -| --dv-floating-box-shadow | | -| --dv-active-sash-color | | -| --dv-background-color | | - -You can further customise the theme through adjusting class properties but this is up you. -For example if you wanted to add a bottom border to the tab container for an active group in the `DockviewReact` component you could write: - -```css title="Additional CSS to show a bottom border on active groups" -.groupview { - &.active-group { - > .tabs-and-actions-container { - border-bottom: 2px solid var(--dv-activegroup-visiblepanel-tab-background-color); - } - } - &.inactive-group { - > .tabs-and-actions-container { - border-bottom: 2px solid var(--dv-inactivegroup-visiblepanel-tab-background-color); - } - } -} -``` diff --git a/packages/docs/versioned_docs/version-1.8.4/components/_category_.json b/packages/docs/versioned_docs/version-1.9.2/components/_category_.json similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/components/_category_.json rename to packages/docs/versioned_docs/version-1.9.2/components/_category_.json diff --git a/packages/docs/versioned_docs/version-1.9.0/components/dockview.mdx b/packages/docs/versioned_docs/version-1.9.2/components/dockview.mdx similarity index 96% rename from packages/docs/versioned_docs/version-1.9.0/components/dockview.mdx rename to packages/docs/versioned_docs/version-1.9.2/components/dockview.mdx index 0472c1e1a..40f020fb0 100644 --- a/packages/docs/versioned_docs/version-1.9.0/components/dockview.mdx +++ b/packages/docs/versioned_docs/version-1.9.2/components/dockview.mdx @@ -7,7 +7,7 @@ import { MultiFrameworkContainer } from '@site/src/components/ui/container'; import Link from '@docusaurus/Link'; import useBaseUrl from '@docusaurus/useBaseUrl'; -import DockviewPersistance from '@site/sandboxes/layout-dockview/src/app'; +import DockviewPersistence from '@site/sandboxes/layout-dockview/src/app'; import SimpleDockview from '@site/sandboxes/simple-dockview/src/app'; import ResizeDockview from '@site/sandboxes/resize-dockview/src/app'; import DockviewWatermark from '@site/sandboxes/watermark-dockview/src/app'; @@ -31,6 +31,7 @@ import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app'; import DockviewPopoutGroup from '@site/sandboxes/popoutgroup-dockview/src/app'; import DockviewMaximizeGroup from '@site/sandboxes/maximizegroup-dockview/src/app'; import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app'; +import DockviewScrollbars from '@site/sandboxes/scrollbars-dockview/src/app'; import { DocRef } from '@site/src/components/ui/reference/docRef'; @@ -139,7 +140,7 @@ As well as importing the `dockview` stylesheet you must provide a class-based th You can find more details on theming here. -## Layout Persistance +## Layout Persistence Layouts are loaded and saved via to `fromJSON` and `toJSON` methods on the Dockview api. The api also exposes an event `onDidLayoutChange` you can listen on to determine when the layout has changed. @@ -155,7 +156,7 @@ React.useEffect(() => { const layout = api.toJSON(); localStorage.setItem( - 'dockview_persistance_layout', + 'dockview_persistence_layout', JSON.stringify(layout) ); }); @@ -168,7 +169,7 @@ React.useEffect(() => { ```tsx title="Loading a layout from localStorage" const onReady = (event: DockviewReadyEvent) => { - const layoutString = localStorage.getItem('dockview_persistance_layout'); + const layoutString = localStorage.getItem('dockview_persistence_layout'); let success = false; @@ -193,7 +194,23 @@ If you refresh the page you should notice your layout is loaded as you left it. + +## Scrollbars + +Scrollbars will appear if the contents of your view has a fixed height. If you are using a relative height such +as *100%* you will need to define an inner container with the appropiate `overflow` value to allow scrollbars to appear. + +The following container three views: +- **Panel 1**: Sets `height: 100%` and no scrollbar appears even, the content is clipped. +- **Panel 2**: Sets `height: 2000px` and a scrollbar does appear since a fixed height has been used. +- **Panel 3**: Sets `height: 100%` and defines an inner component with `overflow: auto` to enable the scrollbars. + + + ## Resizing @@ -247,6 +264,18 @@ which will be rendered when there are no panels or groups. ## Drag And Drop +You can override the conditions of the far edge overlays through the `rootOverlayModel` prop. + +```tsx + +``` + ### Built-in behaviours Dockview supports a wide variety of built-in Drag and Drop possibilities. diff --git a/packages/docs/versioned_docs/version-1.8.4/components/gridview.mdx b/packages/docs/versioned_docs/version-1.9.2/components/gridview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/components/gridview.mdx rename to packages/docs/versioned_docs/version-1.9.2/components/gridview.mdx diff --git a/packages/docs/versioned_docs/version-1.8.4/components/paneview.mdx b/packages/docs/versioned_docs/version-1.9.2/components/paneview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/components/paneview.mdx rename to packages/docs/versioned_docs/version-1.9.2/components/paneview.mdx diff --git a/packages/docs/versioned_docs/version-1.8.4/components/splitview.mdx b/packages/docs/versioned_docs/version-1.9.2/components/splitview.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/components/splitview.mdx rename to packages/docs/versioned_docs/version-1.9.2/components/splitview.mdx diff --git a/packages/docs/versioned_docs/version-1.9.0/contributing.mdx b/packages/docs/versioned_docs/version-1.9.2/contributing.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.0/contributing.mdx rename to packages/docs/versioned_docs/version-1.9.2/contributing.mdx diff --git a/packages/docs/versioned_docs/version-1.9.0/index.mdx b/packages/docs/versioned_docs/version-1.9.2/index.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.9.0/index.mdx rename to packages/docs/versioned_docs/version-1.9.2/index.mdx diff --git a/packages/docs/versioned_docs/version-1.8.4/theme.mdx b/packages/docs/versioned_docs/version-1.9.2/theme.mdx similarity index 100% rename from packages/docs/versioned_docs/version-1.8.4/theme.mdx rename to packages/docs/versioned_docs/version-1.9.2/theme.mdx diff --git a/packages/docs/versioned_sidebars/version-1.9.0-sidebars.json b/packages/docs/versioned_sidebars/version-1.9.0-sidebars.json deleted file mode 100644 index caea0c03b..000000000 --- a/packages/docs/versioned_sidebars/version-1.9.0-sidebars.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "tutorialSidebar": [ - { - "type": "autogenerated", - "dirName": "." - } - ] -} diff --git a/packages/docs/versioned_sidebars/version-1.8.4-sidebars.json b/packages/docs/versioned_sidebars/version-1.9.2-sidebars.json similarity index 100% rename from packages/docs/versioned_sidebars/version-1.8.4-sidebars.json rename to packages/docs/versioned_sidebars/version-1.9.2-sidebars.json diff --git a/packages/docs/versions.json b/packages/docs/versions.json index c1af97d2c..7c264b1cf 100644 --- a/packages/docs/versions.json +++ b/packages/docs/versions.json @@ -1,4 +1,3 @@ [ - "1.9.0", - "1.8.4" -] + "1.9.2" +] \ No newline at end of file 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==